@@ -395,81 +395,6 @@ private Object doReadOrProject(ConversionContext context, Bson source, TypeInfor
395395 return readDocument (context , source , typeHint );
396396 }
397397
398- static class AssociationConversionContext implements ConversionContext {
399-
400- private final ConversionContext delegate ;
401-
402- public AssociationConversionContext (ConversionContext delegate ) {
403- this .delegate = delegate ;
404- }
405-
406- @ Override
407- public <S > S convert (Object source , TypeInformation <? extends S > typeHint , ConversionContext context ) {
408- return delegate .convert (source , typeHint , context );
409- }
410-
411- @ Override
412- public ConversionContext withPath (ObjectPath currentPath ) {
413- return new AssociationConversionContext (delegate .withPath (currentPath ));
414- }
415-
416- @ Override
417- public ObjectPath getPath () {
418- return delegate .getPath ();
419- }
420-
421- @ Override
422- public CustomConversions getCustomConversions () {
423- return delegate .getCustomConversions ();
424- }
425-
426- @ Override
427- public MongoConverter getSourceConverter () {
428- return delegate .getSourceConverter ();
429- }
430-
431- @ Override
432- public boolean resolveIdsInContext () {
433- return true ;
434- }
435- }
436-
437- class ProjectingConversionContext extends DefaultConversionContext {
438-
439- private final EntityProjection <?, ?> returnedTypeDescriptor ;
440-
441- ProjectingConversionContext (MongoConverter sourceConverter , CustomConversions customConversions , ObjectPath path ,
442- ContainerValueConverter <Collection <?>> collectionConverter , ContainerValueConverter <Bson > mapConverter ,
443- ContainerValueConverter <DBRef > dbRefConverter , ValueConverter <Object > elementConverter ,
444- EntityProjection <?, ?> projection ) {
445- super (sourceConverter , customConversions , path ,
446- (context , source , typeHint ) -> doReadOrProject (context , source , typeHint , projection ),
447-
448- collectionConverter , mapConverter , dbRefConverter , elementConverter );
449- this .returnedTypeDescriptor = projection ;
450- }
451-
452- @ Override
453- public DefaultConversionContext forProperty (String name ) {
454-
455- EntityProjection <?, ?> property = returnedTypeDescriptor .findProperty (name );
456- if (property == null ) {
457- return new DefaultConversionContext (sourceConverter , conversions , path ,
458- MappingMongoConverter .this ::readDocument , collectionConverter , mapConverter , dbRefConverter ,
459- elementConverter );
460- }
461-
462- return new ProjectingConversionContext (sourceConverter , conversions , path , collectionConverter , mapConverter ,
463- dbRefConverter , elementConverter , property );
464- }
465-
466- @ Override
467- public DefaultConversionContext withPath (ObjectPath currentPath ) {
468- return new ProjectingConversionContext (sourceConverter , conversions , currentPath , collectionConverter ,
469- mapConverter , dbRefConverter , elementConverter , returnedTypeDescriptor );
470- }
471- }
472-
473398 static class MapPersistentPropertyAccessor implements PersistentPropertyAccessor <Map <String , Object >> {
474399
475400 Map <String , Object > map = new LinkedHashMap <>();
@@ -580,16 +505,14 @@ private ParameterValueProvider<MongoPersistentProperty> getParameterProvider(Con
580505
581506 private <S > S read (ConversionContext context , MongoPersistentEntity <S > entity , Document bson ) {
582507
508+ S existing = context .findContextualEntity (entity , bson );
509+ if (existing != null ) {
510+ return existing ;
511+ }
512+
583513 SpELExpressionEvaluator evaluator = new DefaultSpELExpressionEvaluator (bson , spELContext );
584514 DocumentAccessor documentAccessor = new DocumentAccessor (bson );
585515
586- if (context .resolveIdsInContext () && hasIdentifier (bson )) {
587- S existing = findContextualEntity (context , entity , bson );
588- if (existing != null ) {
589- return existing ;
590- }
591- }
592-
593516 PreferredConstructor <S , MongoPersistentProperty > persistenceConstructor = entity .getPersistenceConstructor ();
594517
595518 ParameterValueProvider <MongoPersistentProperty > provider = persistenceConstructor != null
@@ -607,16 +530,6 @@ private <S> S read(ConversionContext context, MongoPersistentEntity<S> entity, D
607530 return instance ;
608531 }
609532
610- private boolean hasIdentifier (Document bson ) {
611- return bson .get (BasicMongoPersistentProperty .ID_FIELD_NAME ) != null ;
612- }
613-
614- @ Nullable
615- private <S > S findContextualEntity (ConversionContext context , MongoPersistentEntity <S > entity , Document bson ) {
616- return context .getPath ().getPathItem (bson .get (BasicMongoPersistentProperty .ID_FIELD_NAME ), entity .getCollection (),
617- entity .getType ());
618- }
619-
620533 private <S > S populateProperties (ConversionContext context , MongoPersistentEntity <S > entity ,
621534 DocumentAccessor documentAccessor , SpELExpressionEvaluator evaluator , S instance ) {
622535
@@ -2240,40 +2153,130 @@ public TypeDescriptor toTypeDescriptor() {
22402153 }
22412154 }
22422155
2156+ /**
2157+ * Conversion context defining an interface for graph-traversal-based conversion of documents. Entrypoint for
2158+ * recursive conversion of {@link Document} and other types.
2159+ *
2160+ * @since 3.4.3
2161+ */
22432162 interface ConversionContext {
22442163
2164+ /**
2165+ * Converts a source object into {@link TypeInformation target}.
2166+ *
2167+ * @param source must not be {@literal null}.
2168+ * @param typeHint must not be {@literal null}.
2169+ * @return the converted object.
2170+ */
22452171 default <S extends Object > S convert (Object source , TypeInformation <? extends S > typeHint ) {
22462172 return convert (source , typeHint , this );
22472173 }
22482174
2175+ /**
2176+ * Converts a source object into {@link TypeInformation target}.
2177+ *
2178+ * @param source must not be {@literal null}.
2179+ * @param typeHint must not be {@literal null}.
2180+ * @param context must not be {@literal null}.
2181+ * @return the converted object.
2182+ */
22492183 <S extends Object > S convert (Object source , TypeInformation <? extends S > typeHint , ConversionContext context );
22502184
2185+ /**
2186+ * Create a new {@link ConversionContext} with {@link ObjectPath currentPath} applied.
2187+ *
2188+ * @param currentPath must not be {@literal null}.
2189+ * @return a new {@link ConversionContext} with {@link ObjectPath currentPath} applied.
2190+ */
22512191 ConversionContext withPath (ObjectPath currentPath );
22522192
2253- ObjectPath getPath ();
2254-
2193+ /**
2194+ * Obtain a {@link ConversionContext} for the given property {@code name}.
2195+ *
2196+ * @param name must not be {@literal null}.
2197+ * @return the {@link ConversionContext} to be used for conversion of the given property.
2198+ */
22552199 default ConversionContext forProperty (String name ) {
22562200 return this ;
22572201 }
22582202
2259- default ConversionContext forProperty (@ Nullable PersistentProperty property ) {
2203+ /**
2204+ * Obtain a {@link ConversionContext} for the given {@link MongoPersistentProperty}.
2205+ *
2206+ * @param property must not be {@literal null}.
2207+ * @return the {@link ConversionContext} to be used for conversion of the given property.
2208+ */
2209+ default ConversionContext forProperty (MongoPersistentProperty property ) {
22602210
2261- if (property != null ) {
2262- if (property .isAssociation ()) {
2263- return new AssociationConversionContext (forProperty (property .getName ()));
2264- }
2265- return forProperty (property .getName ());
2266- }
2267- return this ;
2211+ return property .isAssociation () ? new AssociationConversionContext (forProperty (property .getName ()))
2212+ : forProperty (property .getName ());
22682213 }
22692214
2270- default boolean resolveIdsInContext () {
2271- return false ;
2215+ /**
2216+ * Lookup a potentially existing entity instance of the given {@link MongoPersistentEntity} and {@link Document}
2217+ *
2218+ * @param entity
2219+ * @param document
2220+ * @return
2221+ * @param <S>
2222+ */
2223+ @ Nullable
2224+ default <S > S findContextualEntity (MongoPersistentEntity <S > entity , Document document ) {
2225+ return null ;
22722226 }
22732227
2228+ ObjectPath getPath ();
2229+
22742230 CustomConversions getCustomConversions ();
22752231
22762232 MongoConverter getSourceConverter ();
2233+
2234+ }
2235+
2236+ /**
2237+ * @since 3.4.3
2238+ */
2239+ static class AssociationConversionContext implements ConversionContext {
2240+
2241+ private final ConversionContext delegate ;
2242+
2243+ public AssociationConversionContext (ConversionContext delegate ) {
2244+ this .delegate = delegate ;
2245+ }
2246+
2247+ @ Override
2248+ public <S > S convert (Object source , TypeInformation <? extends S > typeHint , ConversionContext context ) {
2249+ return delegate .convert (source , typeHint , context );
2250+ }
2251+
2252+ @ Override
2253+ public ConversionContext withPath (ObjectPath currentPath ) {
2254+ return new AssociationConversionContext (delegate .withPath (currentPath ));
2255+ }
2256+
2257+ @ Override
2258+ public <S > S findContextualEntity (MongoPersistentEntity <S > entity , Document document ) {
2259+
2260+ Object identifier = document .get (BasicMongoPersistentProperty .ID_FIELD_NAME );
2261+
2262+ return identifier != null ? getPath ().getPathItem (identifier , entity .getCollection (), entity .getType ()) : null ;
2263+ }
2264+
2265+ @ Override
2266+ public ObjectPath getPath () {
2267+ return delegate .getPath ();
2268+ }
2269+
2270+ @ Override
2271+ public CustomConversions getCustomConversions () {
2272+ return delegate .getCustomConversions ();
2273+ }
2274+
2275+ @ Override
2276+ public MongoConverter getSourceConverter () {
2277+ return delegate .getSourceConverter ();
2278+ }
2279+
22772280 }
22782281
22792282 /**
@@ -2309,14 +2312,8 @@ protected static class DefaultConversionContext implements ConversionContext {
23092312 this .elementConverter = elementConverter ;
23102313 }
23112314
2312- /**
2313- * Converts a source object into {@link TypeInformation target}.
2314- *
2315- * @param source must not be {@literal null}.
2316- * @param typeHint must not be {@literal null}.
2317- * @return the converted object.
2318- */
23192315 @ SuppressWarnings ("unchecked" )
2316+ @ Override
23202317 public <S extends Object > S convert (Object source , TypeInformation <? extends S > typeHint ,
23212318 ConversionContext context ) {
23222319
@@ -2382,28 +2379,20 @@ public MongoConverter getSourceConverter() {
23822379 return sourceConverter ;
23832380 }
23842381
2385- /**
2386- * Create a new {@link DefaultConversionContext} with {@link ObjectPath currentPath} applied.
2387- *
2388- * @param currentPath must not be {@literal null}.
2389- * @return a new {@link DefaultConversionContext} with {@link ObjectPath currentPath} applied.
2390- */
2391- public DefaultConversionContext withPath (ObjectPath currentPath ) {
2382+ @ Override
2383+ public ConversionContext withPath (ObjectPath currentPath ) {
23922384
23932385 Assert .notNull (currentPath , "ObjectPath must not be null" );
23942386
23952387 return new DefaultConversionContext (sourceConverter , conversions , currentPath , documentConverter ,
23962388 collectionConverter , mapConverter , dbRefConverter , elementConverter );
23972389 }
23982390
2391+ @ Override
23992392 public ObjectPath getPath () {
24002393 return path ;
24012394 }
24022395
2403- public DefaultConversionContext forProperty (String name ) {
2404- return this ;
2405- }
2406-
24072396 /**
24082397 * Converts a simple {@code source} value into {@link TypeInformation the target type}.
24092398 *
@@ -2429,6 +2418,45 @@ interface ContainerValueConverter<T> {
24292418
24302419 }
24312420
2421+ /**
2422+ * @since 3.4.3
2423+ */
2424+ class ProjectingConversionContext extends DefaultConversionContext {
2425+
2426+ private final EntityProjection <?, ?> returnedTypeDescriptor ;
2427+
2428+ ProjectingConversionContext (MongoConverter sourceConverter , CustomConversions customConversions , ObjectPath path ,
2429+ ContainerValueConverter <Collection <?>> collectionConverter , ContainerValueConverter <Bson > mapConverter ,
2430+ ContainerValueConverter <DBRef > dbRefConverter , ValueConverter <Object > elementConverter ,
2431+ EntityProjection <?, ?> projection ) {
2432+ super (sourceConverter , customConversions , path ,
2433+ (context , source , typeHint ) -> doReadOrProject (context , source , typeHint , projection ),
2434+
2435+ collectionConverter , mapConverter , dbRefConverter , elementConverter );
2436+ this .returnedTypeDescriptor = projection ;
2437+ }
2438+
2439+ @ Override
2440+ public ConversionContext forProperty (String name ) {
2441+
2442+ EntityProjection <?, ?> property = returnedTypeDescriptor .findProperty (name );
2443+ if (property == null ) {
2444+ return new DefaultConversionContext (sourceConverter , conversions , path ,
2445+ MappingMongoConverter .this ::readDocument , collectionConverter , mapConverter , dbRefConverter ,
2446+ elementConverter );
2447+ }
2448+
2449+ return new ProjectingConversionContext (sourceConverter , conversions , path , collectionConverter , mapConverter ,
2450+ dbRefConverter , elementConverter , property );
2451+ }
2452+
2453+ @ Override
2454+ public ConversionContext withPath (ObjectPath currentPath ) {
2455+ return new ProjectingConversionContext (sourceConverter , conversions , currentPath , collectionConverter ,
2456+ mapConverter , dbRefConverter , elementConverter , returnedTypeDescriptor );
2457+ }
2458+ }
2459+
24322460 private static class PropertyTranslatingPropertyAccessor <T > implements PersistentPropertyPathAccessor <T > {
24332461
24342462 private final PersistentPropertyAccessor <T > delegate ;
0 commit comments