@@ -63,6 +63,12 @@ public final class ReferenceLookupDelegate {
6363 private final SpELContext spELContext ;
6464 private final ParameterBindingDocumentCodec codec ;
6565
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+ */
6672 public ReferenceLookupDelegate (
6773 MappingContext <? extends MongoPersistentEntity <?>, MongoPersistentProperty > mappingContext ,
6874 SpELContext spELContext ) {
@@ -75,8 +81,18 @@ public ReferenceLookupDelegate(
7581 this .codec = new ParameterBindingDocumentCodec ();
7682 }
7783
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+ */
7894 @ Nullable
79- Object readReference (MongoPersistentProperty property , Object value , LookupFunction lookupFunction ,
95+ public Object readReference (MongoPersistentProperty property , Object value , LookupFunction lookupFunction ,
8096 MongoEntityReader entityReader ) {
8197
8298 DocumentReferenceQuery filter = computeFilter (property , value , spELContext );
@@ -98,10 +114,12 @@ Object readReference(MongoPersistentProperty property, Object value, LookupFunct
98114 private ReferenceCollection computeReferenceContext (MongoPersistentProperty property , Object value ,
99115 SpELContext spELContext ) {
100116
117+ // Use the first value as a reference for others in case of collection like
101118 if (value instanceof Iterable ) {
102119 value = ((Iterable <?>) value ).iterator ().next ();
103120 }
104121
122+ // handle DBRef value
105123 if (value instanceof DBRef ) {
106124 return ReferenceCollection .fromDBRef ((DBRef ) value );
107125 }
@@ -110,21 +128,21 @@ private ReferenceCollection computeReferenceContext(MongoPersistentProperty prop
110128
111129 if (value instanceof Document ) {
112130
113- Document ref = (Document ) value ;
131+ Document documentPointer = (Document ) value ;
114132
115133 if (property .isDocumentReference ()) {
116134
117135 ParameterBindingContext bindingContext = bindingContext (property , value , spELContext );
118136 DocumentReference documentReference = property .getDocumentReference ();
119137
120138 String targetDatabase = parseValueOrGet (documentReference .db (), bindingContext ,
121- () -> ref .get ("db" , String .class ));
139+ () -> documentPointer .get ("db" , String .class ));
122140 String targetCollection = parseValueOrGet (documentReference .collection (), bindingContext ,
123- () -> ref .get ("collection" , collection ));
141+ () -> documentPointer .get ("collection" , collection ));
124142 return new ReferenceCollection (targetDatabase , targetCollection );
125143 }
126144
127- return new ReferenceCollection (ref .getString ("db" ), ref .get ("collection" , collection ));
145+ return new ReferenceCollection (documentPointer .getString ("db" ), documentPointer .get ("collection" , collection ));
128146 }
129147
130148 if (property .isDocumentReference ()) {
@@ -141,19 +159,34 @@ private ReferenceCollection computeReferenceContext(MongoPersistentProperty prop
141159 return new ReferenceCollection (null , collection );
142160 }
143161
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
144172 @ SuppressWarnings ("unchecked" )
145173 private <T > T parseValueOrGet (String value , ParameterBindingContext bindingContext , Supplier <T > defaultValue ) {
146174
147175 if (!StringUtils .hasText (value )) {
148176 return defaultValue .get ();
149177 }
150178
179+ // parameter binding requires a document, since we do not have one, construct it.
151180 if (!BsonUtils .isJsonDocument (value ) && value .contains ("?#{" )) {
152181 String s = "{ 'target-value' : " + value + "}" ;
153182 T evaluated = (T ) codec .decode (s , bindingContext ).get ("target-value" );
154183 return evaluated != null ? evaluated : defaultValue .get ();
155184 }
156185
186+ if (BsonUtils .isJsonDocument (value )) {
187+ return (T ) codec .decode (value , bindingContext );
188+ }
189+
157190 T evaluated = (T ) bindingContext .evaluateExpression (value );
158191 return evaluated != null ? evaluated : defaultValue .get ();
159192 }
@@ -165,8 +198,8 @@ ParameterBindingContext bindingContext(MongoPersistentProperty property, Object
165198 }
166199
167200 ValueProvider valueProviderFor (Object source ) {
168- return (index ) -> {
169201
202+ return (index ) -> {
170203 if (source instanceof Document ) {
171204 return Streamable .of (((Document ) source ).values ()).toList ().get (index );
172205 }
@@ -183,13 +216,22 @@ EvaluationContext evaluationContextFor(MongoPersistentProperty property, Object
183216 return ctx ;
184217 }
185218
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+ */
186227 @ SuppressWarnings ("unchecked" )
187228 DocumentReferenceQuery computeFilter (MongoPersistentProperty property , Object value , SpELContext spELContext ) {
188229
189230 DocumentReference documentReference = property .getDocumentReference ();
190231 String lookup = documentReference .lookup ();
191232
192- Document sort = parseValueOrGet (documentReference .sort (), bindingContext (property , value , spELContext ), () -> null );
233+ Document sort = parseValueOrGet (documentReference .sort (), bindingContext (property , value , spELContext ),
234+ () -> new Document ());
193235
194236 if (property .isCollectionLike () && value instanceof Collection ) {
195237
@@ -219,45 +261,59 @@ DocumentReferenceQuery computeFilter(MongoPersistentProperty property, Object va
219261 return new SingleDocumentReferenceQuery (codec .decode (lookup , bindingContext (property , value , spELContext )), sort );
220262 }
221263
264+ /**
265+ * {@link DocumentReferenceQuery} implementation fetching a single {@link Document}.
266+ */
222267 static class SingleDocumentReferenceQuery implements DocumentReferenceQuery {
223268
224- Document filter ;
225- Document sort ;
269+ private final Document query ;
270+ private final Document sort ;
271+
272+ public SingleDocumentReferenceQuery (Document query , Document sort ) {
226273
227- public SingleDocumentReferenceQuery (Document filter , Document sort ) {
228- this .filter = filter ;
274+ this .query = query ;
229275 this .sort = sort ;
230276 }
231277
232278 @ Override
233279 public Bson getQuery () {
234- return filter ;
280+ return query ;
281+ }
282+
283+ @ Override
284+ public Document getSort () {
285+ return sort ;
235286 }
236287
237288 @ Override
238289 public Iterable <Document > apply (MongoCollection <Document > collection ) {
239290
240- Document result = collection .find (getQuery ()).limit (1 ).first ();
291+ Document result = collection .find (getQuery ()).sort ( getSort ()). limit (1 ).first ();
241292 return result != null ? Collections .singleton (result ) : Collections .emptyList ();
242293 }
243294 }
244295
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+ */
245301 static class MapDocumentReferenceQuery implements DocumentReferenceQuery {
246302
247- private final Document filter ;
303+ private final Document query ;
248304 private final Document sort ;
249305 private final Map <Object , Document > filterOrderMap ;
250306
251- public MapDocumentReferenceQuery (Document filter , Document sort , Map <Object , Document > filterOrderMap ) {
307+ public MapDocumentReferenceQuery (Document query , Document sort , Map <Object , Document > filterOrderMap ) {
252308
253- this .filter = filter ;
309+ this .query = query ;
254310 this .sort = sort ;
255311 this .filterOrderMap = filterOrderMap ;
256312 }
257313
258314 @ Override
259315 public Bson getQuery () {
260- return filter ;
316+ return query ;
261317 }
262318
263319 @ Override
@@ -283,33 +339,38 @@ public Iterable<Document> restoreOrder(Iterable<Document> documents) {
283339 }
284340 }
285341
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+ */
286347 static class ListDocumentReferenceQuery implements DocumentReferenceQuery {
287348
288- private final Document filter ;
349+ private final Document query ;
289350 private final Document sort ;
290351
291- public ListDocumentReferenceQuery (Document filter , Document sort ) {
352+ public ListDocumentReferenceQuery (Document query , Document sort ) {
292353
293- this .filter = filter ;
354+ this .query = query ;
294355 this .sort = sort ;
295356 }
296357
297358 @ Override
298359 public Iterable <Document > restoreOrder (Iterable <Document > documents ) {
299360
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 ;
306366 }
307367
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 ());
309370 }
310371
311372 public Document getQuery () {
312- return filter ;
373+ return query ;
313374 }
314375
315376 @ Override
@@ -333,9 +394,18 @@ int compareAgainstReferenceIndex(List<Document> referenceList, Document document
333394 }
334395 }
335396
397+ /**
398+ * The function that can execute a given {@link DocumentReferenceQuery} within the {@link ReferenceCollection} to
399+ * obtain raw results.
400+ */
336401 @ FunctionalInterface
337402 interface LookupFunction {
338403
404+ /**
405+ * @param referenceQuery never {@literal null}.
406+ * @param referenceCollection never {@literal null}.
407+ * @return never {@literal null}.
408+ */
339409 Iterable <Document > apply (DocumentReferenceQuery referenceQuery , ReferenceCollection referenceCollection );
340410 }
341411}
0 commit comments