@@ -63,6 +63,12 @@ public final class ReferenceLookupDelegate {
63
63
private final SpELContext spELContext ;
64
64
private final ParameterBindingDocumentCodec codec ;
65
65
66
+ /**
67
+ * Create a new {@link ReferenceLookupDelegate}.
68
+ *
69
+ * @param mappingContext must not be {@literal null}.
70
+ * @param spELContext must not be {@literal null}.
71
+ */
66
72
public ReferenceLookupDelegate (
67
73
MappingContext <? extends MongoPersistentEntity <?>, MongoPersistentProperty > mappingContext ,
68
74
SpELContext spELContext ) {
@@ -75,8 +81,18 @@ public ReferenceLookupDelegate(
75
81
this .codec = new ParameterBindingDocumentCodec ();
76
82
}
77
83
84
+ /**
85
+ * Read the reference expressed by the given property.
86
+ *
87
+ * @param property the reference defining property. Must not be {@literal null}. THe
88
+ * @param value the source value identifying to the referenced entity. Must not be {@literal null}.
89
+ * @param lookupFunction to execute a lookup query. Must not be {@literal null}.
90
+ * @param entityReader the callback to convert raw source values into actual domain types. Must not be
91
+ * {@literal null}.
92
+ * @return can be {@literal null}.
93
+ */
78
94
@ Nullable
79
- Object readReference (MongoPersistentProperty property , Object value , LookupFunction lookupFunction ,
95
+ public Object readReference (MongoPersistentProperty property , Object value , LookupFunction lookupFunction ,
80
96
MongoEntityReader entityReader ) {
81
97
82
98
DocumentReferenceQuery filter = computeFilter (property , value , spELContext );
@@ -98,10 +114,12 @@ Object readReference(MongoPersistentProperty property, Object value, LookupFunct
98
114
private ReferenceCollection computeReferenceContext (MongoPersistentProperty property , Object value ,
99
115
SpELContext spELContext ) {
100
116
117
+ // Use the first value as a reference for others in case of collection like
101
118
if (value instanceof Iterable ) {
102
119
value = ((Iterable <?>) value ).iterator ().next ();
103
120
}
104
121
122
+ // handle DBRef value
105
123
if (value instanceof DBRef ) {
106
124
return ReferenceCollection .fromDBRef ((DBRef ) value );
107
125
}
@@ -110,21 +128,21 @@ private ReferenceCollection computeReferenceContext(MongoPersistentProperty prop
110
128
111
129
if (value instanceof Document ) {
112
130
113
- Document ref = (Document ) value ;
131
+ Document documentPointer = (Document ) value ;
114
132
115
133
if (property .isDocumentReference ()) {
116
134
117
135
ParameterBindingContext bindingContext = bindingContext (property , value , spELContext );
118
136
DocumentReference documentReference = property .getDocumentReference ();
119
137
120
138
String targetDatabase = parseValueOrGet (documentReference .db (), bindingContext ,
121
- () -> ref .get ("db" , String .class ));
139
+ () -> documentPointer .get ("db" , String .class ));
122
140
String targetCollection = parseValueOrGet (documentReference .collection (), bindingContext ,
123
- () -> ref .get ("collection" , collection ));
141
+ () -> documentPointer .get ("collection" , collection ));
124
142
return new ReferenceCollection (targetDatabase , targetCollection );
125
143
}
126
144
127
- return new ReferenceCollection (ref .getString ("db" ), ref .get ("collection" , collection ));
145
+ return new ReferenceCollection (documentPointer .getString ("db" ), documentPointer .get ("collection" , collection ));
128
146
}
129
147
130
148
if (property .isDocumentReference ()) {
@@ -141,19 +159,34 @@ private ReferenceCollection computeReferenceContext(MongoPersistentProperty prop
141
159
return new ReferenceCollection (null , collection );
142
160
}
143
161
162
+ /**
163
+ * Use the given {@link ParameterBindingContext} to compute potential expressions against the value.
164
+ *
165
+ * @param value must not be {@literal null}.
166
+ * @param bindingContext must not be {@literal null}.
167
+ * @param defaultValue
168
+ * @param <T>
169
+ * @return can be {@literal null}.
170
+ */
171
+ @ Nullable
144
172
@ SuppressWarnings ("unchecked" )
145
173
private <T > T parseValueOrGet (String value , ParameterBindingContext bindingContext , Supplier <T > defaultValue ) {
146
174
147
175
if (!StringUtils .hasText (value )) {
148
176
return defaultValue .get ();
149
177
}
150
178
179
+ // parameter binding requires a document, since we do not have one, construct it.
151
180
if (!BsonUtils .isJsonDocument (value ) && value .contains ("?#{" )) {
152
181
String s = "{ 'target-value' : " + value + "}" ;
153
182
T evaluated = (T ) codec .decode (s , bindingContext ).get ("target-value" );
154
183
return evaluated != null ? evaluated : defaultValue .get ();
155
184
}
156
185
186
+ if (BsonUtils .isJsonDocument (value )) {
187
+ return (T ) codec .decode (value , bindingContext );
188
+ }
189
+
157
190
T evaluated = (T ) bindingContext .evaluateExpression (value );
158
191
return evaluated != null ? evaluated : defaultValue .get ();
159
192
}
@@ -165,8 +198,8 @@ ParameterBindingContext bindingContext(MongoPersistentProperty property, Object
165
198
}
166
199
167
200
ValueProvider valueProviderFor (Object source ) {
168
- return (index ) -> {
169
201
202
+ return (index ) -> {
170
203
if (source instanceof Document ) {
171
204
return Streamable .of (((Document ) source ).values ()).toList ().get (index );
172
205
}
@@ -183,13 +216,22 @@ EvaluationContext evaluationContextFor(MongoPersistentProperty property, Object
183
216
return ctx ;
184
217
}
185
218
219
+ /**
220
+ * Compute the query to retrieve linked documents.
221
+ *
222
+ * @param property must not be {@literal null}.
223
+ * @param value must not be {@literal null}.
224
+ * @param spELContext must not be {@literal null}.
225
+ * @return never {@literal null}.
226
+ */
186
227
@ SuppressWarnings ("unchecked" )
187
228
DocumentReferenceQuery computeFilter (MongoPersistentProperty property , Object value , SpELContext spELContext ) {
188
229
189
230
DocumentReference documentReference = property .getDocumentReference ();
190
231
String lookup = documentReference .lookup ();
191
232
192
- Document sort = parseValueOrGet (documentReference .sort (), bindingContext (property , value , spELContext ), () -> null );
233
+ Document sort = parseValueOrGet (documentReference .sort (), bindingContext (property , value , spELContext ),
234
+ () -> new Document ());
193
235
194
236
if (property .isCollectionLike () && value instanceof Collection ) {
195
237
@@ -219,45 +261,59 @@ DocumentReferenceQuery computeFilter(MongoPersistentProperty property, Object va
219
261
return new SingleDocumentReferenceQuery (codec .decode (lookup , bindingContext (property , value , spELContext )), sort );
220
262
}
221
263
264
+ /**
265
+ * {@link DocumentReferenceQuery} implementation fetching a single {@link Document}.
266
+ */
222
267
static class SingleDocumentReferenceQuery implements DocumentReferenceQuery {
223
268
224
- Document filter ;
225
- Document sort ;
269
+ private final Document query ;
270
+ private final Document sort ;
271
+
272
+ public SingleDocumentReferenceQuery (Document query , Document sort ) {
226
273
227
- public SingleDocumentReferenceQuery (Document filter , Document sort ) {
228
- this .filter = filter ;
274
+ this .query = query ;
229
275
this .sort = sort ;
230
276
}
231
277
232
278
@ Override
233
279
public Bson getQuery () {
234
- return filter ;
280
+ return query ;
281
+ }
282
+
283
+ @ Override
284
+ public Document getSort () {
285
+ return sort ;
235
286
}
236
287
237
288
@ Override
238
289
public Iterable <Document > apply (MongoCollection <Document > collection ) {
239
290
240
- Document result = collection .find (getQuery ()).limit (1 ).first ();
291
+ Document result = collection .find (getQuery ()).sort ( getSort ()). limit (1 ).first ();
241
292
return result != null ? Collections .singleton (result ) : Collections .emptyList ();
242
293
}
243
294
}
244
295
296
+ /**
297
+ * {@link DocumentReferenceQuery} implementation to retrieve linked {@link Document documents} stored inside a
298
+ * {@link Map} structure. Restores the original map order by matching individual query documents against the actual
299
+ * values.
300
+ */
245
301
static class MapDocumentReferenceQuery implements DocumentReferenceQuery {
246
302
247
- private final Document filter ;
303
+ private final Document query ;
248
304
private final Document sort ;
249
305
private final Map <Object , Document > filterOrderMap ;
250
306
251
- public MapDocumentReferenceQuery (Document filter , Document sort , Map <Object , Document > filterOrderMap ) {
307
+ public MapDocumentReferenceQuery (Document query , Document sort , Map <Object , Document > filterOrderMap ) {
252
308
253
- this .filter = filter ;
309
+ this .query = query ;
254
310
this .sort = sort ;
255
311
this .filterOrderMap = filterOrderMap ;
256
312
}
257
313
258
314
@ Override
259
315
public Bson getQuery () {
260
- return filter ;
316
+ return query ;
261
317
}
262
318
263
319
@ Override
@@ -283,33 +339,38 @@ public Iterable<Document> restoreOrder(Iterable<Document> documents) {
283
339
}
284
340
}
285
341
342
+ /**
343
+ * {@link DocumentReferenceQuery} implementation to retrieve linked {@link Document documents} stored inside a
344
+ * {@link Collection} like structure. Restores the original order by matching individual query documents against the
345
+ * actual values.
346
+ */
286
347
static class ListDocumentReferenceQuery implements DocumentReferenceQuery {
287
348
288
- private final Document filter ;
349
+ private final Document query ;
289
350
private final Document sort ;
290
351
291
- public ListDocumentReferenceQuery (Document filter , Document sort ) {
352
+ public ListDocumentReferenceQuery (Document query , Document sort ) {
292
353
293
- this .filter = filter ;
354
+ this .query = query ;
294
355
this .sort = sort ;
295
356
}
296
357
297
358
@ Override
298
359
public Iterable <Document > restoreOrder (Iterable <Document > documents ) {
299
360
300
- if (filter .containsKey ("$or" )) {
301
- List <Document > ors = filter .get ("$or" , List .class );
302
- List <Document > target = documents instanceof List ? (List <Document >) documents
303
- : Streamable .of (documents ).toList ();
304
- return target .stream ().sorted ((o1 , o2 ) -> compareAgainstReferenceIndex (ors , o1 , o2 ))
305
- .collect (Collectors .toList ());
361
+ List <Document > target = documents instanceof List ? (List <Document >) documents
362
+ : Streamable .of (documents ).toList ();
363
+
364
+ if (!sort .isEmpty () || !query .containsKey ("$or" )) {
365
+ return target ;
306
366
}
307
367
308
- return documents ;
368
+ List <Document > ors = query .get ("$or" , List .class );
369
+ return target .stream ().sorted ((o1 , o2 ) -> compareAgainstReferenceIndex (ors , o1 , o2 )).collect (Collectors .toList ());
309
370
}
310
371
311
372
public Document getQuery () {
312
- return filter ;
373
+ return query ;
313
374
}
314
375
315
376
@ Override
@@ -333,9 +394,18 @@ int compareAgainstReferenceIndex(List<Document> referenceList, Document document
333
394
}
334
395
}
335
396
397
+ /**
398
+ * The function that can execute a given {@link DocumentReferenceQuery} within the {@link ReferenceCollection} to
399
+ * obtain raw results.
400
+ */
336
401
@ FunctionalInterface
337
402
interface LookupFunction {
338
403
404
+ /**
405
+ * @param referenceQuery never {@literal null}.
406
+ * @param referenceCollection never {@literal null}.
407
+ * @return never {@literal null}.
408
+ */
339
409
Iterable <Document > apply (DocumentReferenceQuery referenceQuery , ReferenceCollection referenceCollection );
340
410
}
341
411
}
0 commit comments