@@ -384,81 +384,6 @@ private Object doReadOrProject(ConversionContext context, Bson source, TypeInfor
384384		return  readDocument (context , source , typeHint );
385385	}
386386
387- 	static  class  AssociationConversionContext  implements  ConversionContext  {
388- 
389- 		private  final  ConversionContext  delegate ;
390- 
391- 		public  AssociationConversionContext (ConversionContext  delegate ) {
392- 			this .delegate  = delegate ;
393- 		}
394- 
395- 		@ Override 
396- 		public  <S > S  convert (Object  source , TypeInformation <? extends  S > typeHint , ConversionContext  context ) {
397- 			return  delegate .convert (source , typeHint , context );
398- 		}
399- 
400- 		@ Override 
401- 		public  ConversionContext  withPath (ObjectPath  currentPath ) {
402- 			return  new  AssociationConversionContext (delegate .withPath (currentPath ));
403- 		}
404- 
405- 		@ Override 
406- 		public  ObjectPath  getPath () {
407- 			return  delegate .getPath ();
408- 		}
409- 
410- 		@ Override 
411- 		public  CustomConversions  getCustomConversions () {
412- 			return  delegate .getCustomConversions ();
413- 		}
414- 
415- 		@ Override 
416- 		public  MongoConverter  getSourceConverter () {
417- 			return  delegate .getSourceConverter ();
418- 		}
419- 
420- 		@ Override 
421- 		public  boolean  resolveIdsInContext () {
422- 			return  true ;
423- 		}
424- 	}
425- 
426- 	class  ProjectingConversionContext  extends  DefaultConversionContext  {
427- 
428- 		private  final  EntityProjection <?, ?> returnedTypeDescriptor ;
429- 
430- 		ProjectingConversionContext (MongoConverter  sourceConverter , CustomConversions  customConversions , ObjectPath  path ,
431- 				ContainerValueConverter <Collection <?>> collectionConverter , ContainerValueConverter <Bson > mapConverter ,
432- 				ContainerValueConverter <DBRef > dbRefConverter , ValueConverter <Object > elementConverter ,
433- 				EntityProjection <?, ?> projection ) {
434- 			super (sourceConverter , customConversions , path ,
435- 					(context , source , typeHint ) -> doReadOrProject (context , source , typeHint , projection ),
436- 
437- 					collectionConverter , mapConverter , dbRefConverter , elementConverter );
438- 			this .returnedTypeDescriptor  = projection ;
439- 		}
440- 
441- 		@ Override 
442- 		public  DefaultConversionContext  forProperty (String  name ) {
443- 
444- 			EntityProjection <?, ?> property  = returnedTypeDescriptor .findProperty (name );
445- 			if  (property  == null ) {
446- 				return  new  DefaultConversionContext (sourceConverter , conversions , path ,
447- 						MappingMongoConverter .this ::readDocument , collectionConverter , mapConverter , dbRefConverter ,
448- 						elementConverter );
449- 			}
450- 
451- 			return  new  ProjectingConversionContext (sourceConverter , conversions , path , collectionConverter , mapConverter ,
452- 					dbRefConverter , elementConverter , property );
453- 		}
454- 
455- 		@ Override 
456- 		public  DefaultConversionContext  withPath (ObjectPath  currentPath ) {
457- 			return  new  ProjectingConversionContext (sourceConverter , conversions , currentPath , collectionConverter ,
458- 					mapConverter , dbRefConverter , elementConverter , returnedTypeDescriptor );
459- 		}
460- 	}
461- 
462387	static  class  MapPersistentPropertyAccessor  implements  PersistentPropertyAccessor <Map <String , Object >> {
463388
464389		Map <String , Object > map  = new  LinkedHashMap <>();
@@ -565,16 +490,14 @@ private ParameterValueProvider<MongoPersistentProperty> getParameterProvider(Con
565490
566491	private  <S > S  read (ConversionContext  context , MongoPersistentEntity <S > entity , Document  bson ) {
567492
493+ 		S  existing  = context .findContextualEntity (entity , bson );
494+ 		if  (existing  != null ) {
495+ 			return  existing ;
496+ 		}
497+ 
568498		SpELExpressionEvaluator  evaluator  = new  DefaultSpELExpressionEvaluator (bson , spELContext );
569499		DocumentAccessor  documentAccessor  = new  DocumentAccessor (bson );
570500
571- 		if  (context .resolveIdsInContext () && hasIdentifier (bson )) {
572- 			S  existing  = findContextualEntity (context , entity , bson );
573- 			if  (existing  != null ) {
574- 				return  existing ;
575- 			}
576- 		}
577- 
578501		PreferredConstructor <S , MongoPersistentProperty > persistenceConstructor  = entity .getPersistenceConstructor ();
579502
580503		ParameterValueProvider <MongoPersistentProperty > provider  = persistenceConstructor  != null 
@@ -592,16 +515,6 @@ private <S> S read(ConversionContext context, MongoPersistentEntity<S> entity, D
592515		return  instance ;
593516	}
594517
595- 	private  boolean  hasIdentifier (Document  bson ) {
596- 		return  bson .get (BasicMongoPersistentProperty .ID_FIELD_NAME ) != null ;
597- 	}
598- 
599- 	@ Nullable 
600- 	private  <S > S  findContextualEntity (ConversionContext  context , MongoPersistentEntity <S > entity , Document  bson ) {
601- 		return  context .getPath ().getPathItem (bson .get (BasicMongoPersistentProperty .ID_FIELD_NAME ), entity .getCollection (),
602- 				entity .getType ());
603- 	}
604- 
605518	private  <S > S  populateProperties (ConversionContext  context , MongoPersistentEntity <S > entity ,
606519			DocumentAccessor  documentAccessor , SpELExpressionEvaluator  evaluator , S  instance ) {
607520
@@ -2197,40 +2110,130 @@ public TypeDescriptor toTypeDescriptor() {
21972110		}
21982111	}
21992112
2113+ 	/** 
2114+ 	 * Conversion context defining an interface for graph-traversal-based conversion of documents. Entrypoint for 
2115+ 	 * recursive conversion of {@link Document} and other types. 
2116+ 	 * 
2117+ 	 * @since 3.4.3 
2118+ 	 */ 
22002119	interface  ConversionContext  {
22012120
2121+ 		/** 
2122+ 		 * Converts a source object into {@link TypeInformation target}. 
2123+ 		 * 
2124+ 		 * @param source must not be {@literal null}. 
2125+ 		 * @param typeHint must not be {@literal null}. 
2126+ 		 * @return the converted object. 
2127+ 		 */ 
22022128		default  <S  extends  Object > S  convert (Object  source , TypeInformation <? extends  S > typeHint ) {
22032129			return  convert (source , typeHint , this );
22042130		}
22052131
2132+ 		/** 
2133+ 		 * Converts a source object into {@link TypeInformation target}. 
2134+ 		 * 
2135+ 		 * @param source must not be {@literal null}. 
2136+ 		 * @param typeHint must not be {@literal null}. 
2137+ 		 * @param context must not be {@literal null}. 
2138+ 		 * @return the converted object. 
2139+ 		 */ 
22062140		<S  extends  Object > S  convert (Object  source , TypeInformation <? extends  S > typeHint , ConversionContext  context );
22072141
2142+ 		/** 
2143+ 		 * Create a new {@link ConversionContext} with {@link ObjectPath currentPath} applied. 
2144+ 		 * 
2145+ 		 * @param currentPath must not be {@literal null}. 
2146+ 		 * @return a new {@link ConversionContext} with {@link ObjectPath currentPath} applied. 
2147+ 		 */ 
22082148		ConversionContext  withPath (ObjectPath  currentPath );
22092149
2210- 		ObjectPath  getPath ();
2211- 
2150+ 		/** 
2151+ 		 * Obtain a {@link ConversionContext} for the given property {@code name}. 
2152+ 		 * 
2153+ 		 * @param name must not be {@literal null}. 
2154+ 		 * @return the {@link ConversionContext} to be used for conversion of the given property. 
2155+ 		 */ 
22122156		default  ConversionContext  forProperty (String  name ) {
22132157			return  this ;
22142158		}
22152159
2216- 		default  ConversionContext  forProperty (@ Nullable  PersistentProperty  property ) {
2160+ 		/** 
2161+ 		 * Obtain a {@link ConversionContext} for the given {@link MongoPersistentProperty}. 
2162+ 		 * 
2163+ 		 * @param property must not be {@literal null}. 
2164+ 		 * @return the {@link ConversionContext} to be used for conversion of the given property. 
2165+ 		 */ 
2166+ 		default  ConversionContext  forProperty (MongoPersistentProperty  property ) {
22172167
2218- 			if  (property  != null ) {
2219- 				if  (property .isAssociation ()) {
2220- 					return  new  AssociationConversionContext (forProperty (property .getName ()));
2221- 				}
2222- 				return  forProperty (property .getName ());
2223- 			}
2224- 			return  this ;
2168+ 			return  property .isAssociation () ? new  AssociationConversionContext (forProperty (property .getName ()))
2169+ 					: forProperty (property .getName ());
22252170		}
22262171
2227- 		default  boolean  resolveIdsInContext () {
2228- 			return  false ;
2172+ 		/** 
2173+ 		 * Lookup a potentially existing entity instance of the given {@link MongoPersistentEntity} and {@link Document} 
2174+ 		 * 
2175+ 		 * @param entity 
2176+ 		 * @param document 
2177+ 		 * @return 
2178+ 		 * @param <S> 
2179+ 		 */ 
2180+ 		@ Nullable 
2181+ 		default  <S > S  findContextualEntity (MongoPersistentEntity <S > entity , Document  document ) {
2182+ 			return  null ;
22292183		}
22302184
2185+ 		ObjectPath  getPath ();
2186+ 
22312187		CustomConversions  getCustomConversions ();
22322188
22332189		MongoConverter  getSourceConverter ();
2190+ 
2191+ 	}
2192+ 
2193+ 	/** 
2194+ 	 * @since 3.4.3 
2195+ 	 */ 
2196+ 	static  class  AssociationConversionContext  implements  ConversionContext  {
2197+ 
2198+ 		private  final  ConversionContext  delegate ;
2199+ 
2200+ 		public  AssociationConversionContext (ConversionContext  delegate ) {
2201+ 			this .delegate  = delegate ;
2202+ 		}
2203+ 
2204+ 		@ Override 
2205+ 		public  <S > S  convert (Object  source , TypeInformation <? extends  S > typeHint , ConversionContext  context ) {
2206+ 			return  delegate .convert (source , typeHint , context );
2207+ 		}
2208+ 
2209+ 		@ Override 
2210+ 		public  ConversionContext  withPath (ObjectPath  currentPath ) {
2211+ 			return  new  AssociationConversionContext (delegate .withPath (currentPath ));
2212+ 		}
2213+ 
2214+ 		@ Override 
2215+ 		public  <S > S  findContextualEntity (MongoPersistentEntity <S > entity , Document  document ) {
2216+ 
2217+ 			Object  identifier  = document .get (BasicMongoPersistentProperty .ID_FIELD_NAME );
2218+ 
2219+ 			return  identifier  != null  ? getPath ().getPathItem (identifier , entity .getCollection (), entity .getType ()) : null ;
2220+ 		}
2221+ 
2222+ 		@ Override 
2223+ 		public  ObjectPath  getPath () {
2224+ 			return  delegate .getPath ();
2225+ 		}
2226+ 
2227+ 		@ Override 
2228+ 		public  CustomConversions  getCustomConversions () {
2229+ 			return  delegate .getCustomConversions ();
2230+ 		}
2231+ 
2232+ 		@ Override 
2233+ 		public  MongoConverter  getSourceConverter () {
2234+ 			return  delegate .getSourceConverter ();
2235+ 		}
2236+ 
22342237	}
22352238
22362239	/** 
@@ -2266,14 +2269,8 @@ protected static class DefaultConversionContext implements ConversionContext {
22662269			this .elementConverter  = elementConverter ;
22672270		}
22682271
2269- 		/** 
2270- 		 * Converts a source object into {@link TypeInformation target}. 
2271- 		 * 
2272- 		 * @param source must not be {@literal null}. 
2273- 		 * @param typeHint must not be {@literal null}. 
2274- 		 * @return the converted object. 
2275- 		 */ 
22762272		@ SuppressWarnings ("unchecked" )
2273+ 		@ Override 
22772274		public  <S  extends  Object > S  convert (Object  source , TypeInformation <? extends  S > typeHint ,
22782275				ConversionContext  context ) {
22792276
@@ -2339,28 +2336,20 @@ public MongoConverter getSourceConverter() {
23392336			return  sourceConverter ;
23402337		}
23412338
2342- 		/** 
2343- 		 * Create a new {@link DefaultConversionContext} with {@link ObjectPath currentPath} applied. 
2344- 		 * 
2345- 		 * @param currentPath must not be {@literal null}. 
2346- 		 * @return a new {@link DefaultConversionContext} with {@link ObjectPath currentPath} applied. 
2347- 		 */ 
2348- 		public  DefaultConversionContext  withPath (ObjectPath  currentPath ) {
2339+ 		@ Override 
2340+ 		public  ConversionContext  withPath (ObjectPath  currentPath ) {
23492341
23502342			Assert .notNull (currentPath , "ObjectPath must not be null" );
23512343
23522344			return  new  DefaultConversionContext (sourceConverter , conversions , currentPath , documentConverter ,
23532345					collectionConverter , mapConverter , dbRefConverter , elementConverter );
23542346		}
23552347
2348+ 		@ Override 
23562349		public  ObjectPath  getPath () {
23572350			return  path ;
23582351		}
23592352
2360- 		public  DefaultConversionContext  forProperty (String  name ) {
2361- 			return  this ;
2362- 		}
2363- 
23642353		/** 
23652354		 * Converts a simple {@code source} value into {@link TypeInformation the target type}. 
23662355		 * 
@@ -2386,6 +2375,45 @@ interface ContainerValueConverter<T> {
23862375
23872376	}
23882377
2378+ 	/** 
2379+ 	 * @since 3.4.3 
2380+ 	 */ 
2381+ 	class  ProjectingConversionContext  extends  DefaultConversionContext  {
2382+ 
2383+ 		private  final  EntityProjection <?, ?> returnedTypeDescriptor ;
2384+ 
2385+ 		ProjectingConversionContext (MongoConverter  sourceConverter , CustomConversions  customConversions , ObjectPath  path ,
2386+ 				ContainerValueConverter <Collection <?>> collectionConverter , ContainerValueConverter <Bson > mapConverter ,
2387+ 				ContainerValueConverter <DBRef > dbRefConverter , ValueConverter <Object > elementConverter ,
2388+ 				EntityProjection <?, ?> projection ) {
2389+ 			super (sourceConverter , customConversions , path ,
2390+ 					(context , source , typeHint ) -> doReadOrProject (context , source , typeHint , projection ),
2391+ 
2392+ 					collectionConverter , mapConverter , dbRefConverter , elementConverter );
2393+ 			this .returnedTypeDescriptor  = projection ;
2394+ 		}
2395+ 
2396+ 		@ Override 
2397+ 		public  ConversionContext  forProperty (String  name ) {
2398+ 
2399+ 			EntityProjection <?, ?> property  = returnedTypeDescriptor .findProperty (name );
2400+ 			if  (property  == null ) {
2401+ 				return  new  DefaultConversionContext (sourceConverter , conversions , path ,
2402+ 						MappingMongoConverter .this ::readDocument , collectionConverter , mapConverter , dbRefConverter ,
2403+ 						elementConverter );
2404+ 			}
2405+ 
2406+ 			return  new  ProjectingConversionContext (sourceConverter , conversions , path , collectionConverter , mapConverter ,
2407+ 					dbRefConverter , elementConverter , property );
2408+ 		}
2409+ 
2410+ 		@ Override 
2411+ 		public  ConversionContext  withPath (ObjectPath  currentPath ) {
2412+ 			return  new  ProjectingConversionContext (sourceConverter , conversions , currentPath , collectionConverter ,
2413+ 					mapConverter , dbRefConverter , elementConverter , returnedTypeDescriptor );
2414+ 		}
2415+ 	}
2416+ 
23892417	private  static  class  PropertyTranslatingPropertyAccessor <T > implements  PersistentPropertyPathAccessor <T > {
23902418
23912419		private  final  PersistentPropertyAccessor <T > delegate ;
0 commit comments