1616package org .springframework .data .neo4j .core .mapping ;
1717
1818import java .lang .reflect .Constructor ;
19+ import java .lang .reflect .Field ;
1920import java .lang .reflect .Method ;
2021import java .lang .reflect .Modifier ;
2122import java .lang .reflect .ParameterizedType ;
2223import java .lang .reflect .Type ;
24+ import java .util .Arrays ;
25+ import java .util .Collections ;
2326import java .util .HashMap ;
27+ import java .util .HashSet ;
28+ import java .util .LinkedHashSet ;
2429import java .util .List ;
2530import java .util .Locale ;
2631import java .util .Map ;
4045import org .springframework .beans .factory .config .AutowireCapableBeanFactory ;
4146import org .springframework .context .ApplicationContext ;
4247import org .springframework .core .GenericTypeResolver ;
48+ import org .springframework .core .KotlinDetector ;
49+ import org .springframework .core .annotation .AnnotationUtils ;
4350import org .springframework .data .mapping .MappingException ;
4451import org .springframework .data .mapping .PersistentEntity ;
52+ import org .springframework .data .mapping .callback .EntityCallbacks ;
4553import org .springframework .data .mapping .context .AbstractMappingContext ;
4654import org .springframework .data .mapping .model .EntityInstantiator ;
4755import org .springframework .data .mapping .model .EntityInstantiators ;
5260import org .springframework .data .neo4j .core .convert .Neo4jConversions ;
5361import org .springframework .data .neo4j .core .convert .Neo4jPersistentPropertyConverter ;
5462import org .springframework .data .neo4j .core .convert .Neo4jPersistentPropertyConverterFactory ;
63+ import org .springframework .data .neo4j .core .mapping .callback .EventSupport ;
5564import org .springframework .data .neo4j .core .schema .IdGenerator ;
5665import org .springframework .data .neo4j .core .schema .Node ;
66+ import org .springframework .data .neo4j .core .schema .PostLoad ;
5767import org .springframework .data .util .TypeInformation ;
5868import org .springframework .lang .Nullable ;
5969import org .springframework .util .ReflectionUtils ;
@@ -78,6 +88,8 @@ public final class Neo4jMappingContext extends AbstractMappingContext<Neo4jPersi
7888 */
7989 private static final EntityInstantiators INSTANTIATORS = new EntityInstantiators ();
8090
91+ private static final Set <Class <?>> VOID_TYPES = new HashSet <>(Arrays .asList (Void .class , void .class ));
92+
8193 /**
8294 * A map of fallback id generators, that have not been added to the application context
8395 */
@@ -95,6 +107,10 @@ public final class Neo4jMappingContext extends AbstractMappingContext<Neo4jPersi
95107
96108 private final Neo4jConversionService conversionService ;
97109
110+ private final Map <Neo4jPersistentEntity , Set <MethodHolder >> postLoadMethods = new ConcurrentHashMap <>();
111+
112+ private EventSupport eventSupport ;
113+
98114 private @ Nullable AutowireCapableBeanFactory beanFactory ;
99115
100116 private boolean strict = false ;
@@ -133,12 +149,14 @@ public Neo4jMappingContext(Neo4jConversions neo4jConversions, @Nullable TypeSyst
133149
134150 this .conversionService = new DefaultNeo4jConversionService (neo4jConversions );
135151 this .typeSystem = typeSystem == null ? InternalTypeSystem .TYPE_SYSTEM : typeSystem ;
152+ this .eventSupport = EventSupport .useExistingCallbacks (this , EntityCallbacks .create ());
136153
137154 super .setSimpleTypeHolder (neo4jConversions .getSimpleTypeHolder ());
138155 }
139156
140157 public Neo4jEntityConverter getEntityConverter () {
141- return new DefaultNeo4jEntityConverter (INSTANTIATORS , conversionService , nodeDescriptionStore , typeSystem );
158+ return new DefaultNeo4jEntityConverter (INSTANTIATORS , nodeDescriptionStore , conversionService , eventSupport ,
159+ typeSystem );
142160 }
143161
144162 public Neo4jConversionService getConversionService () {
@@ -170,25 +188,33 @@ protected <T> Neo4jPersistentEntity<?> createPersistentEntity(TypeInformation<T>
170188 if (!newEntity .describesInterface ()) {
171189 if (this .nodeDescriptionStore .containsKey (primaryLabel )) {
172190
173- Neo4jPersistentEntity existingEntity = (Neo4jPersistentEntity ) this .nodeDescriptionStore .get (primaryLabel );
174- if (!existingEntity .getTypeInformation ().getRawTypeInformation ().equals (typeInformation .getRawTypeInformation ())) {
175- String message = String .format (Locale .ENGLISH , "The schema already contains a node description under the primary label %s" , primaryLabel );
191+ Neo4jPersistentEntity existingEntity = (Neo4jPersistentEntity ) this .nodeDescriptionStore .get (
192+ primaryLabel );
193+ if (!existingEntity .getTypeInformation ().getRawTypeInformation ()
194+ .equals (typeInformation .getRawTypeInformation ())) {
195+ String message = String .format (Locale .ENGLISH ,
196+ "The schema already contains a node description under the primary label %s" , primaryLabel );
176197 throw new MappingException (message );
177198 }
178199 }
179200
180201 if (this .nodeDescriptionStore .containsValue (newEntity )) {
181- Optional <String > label = this .nodeDescriptionStore .entrySet ().stream ().filter (e -> e .getValue ().equals (newEntity )).map (Map .Entry ::getKey ).findFirst ();
202+ Optional <String > label = this .nodeDescriptionStore .entrySet ().stream ()
203+ .filter (e -> e .getValue ().equals (newEntity )).map (Map .Entry ::getKey ).findFirst ();
182204
183- String message = String .format (Locale .ENGLISH , "The schema already contains description %s under the primary label %s" , newEntity , label .orElse ("n/a" ));
205+ String message = String .format (Locale .ENGLISH ,
206+ "The schema already contains description %s under the primary label %s" , newEntity ,
207+ label .orElse ("n/a" ));
184208 throw new MappingException (message );
185209 }
186210
187211 NodeDescription <?> existingDescription = this .getNodeDescription (newEntity .getUnderlyingClass ());
188212 if (existingDescription != null ) {
189213
190214 if (!existingDescription .getPrimaryLabel ().equals (newEntity .getPrimaryLabel ())) {
191- String message = String .format (Locale .ENGLISH , "The schema already contains description with the underlying class %s under the primary label %s" , newEntity .getUnderlyingClass ().getName (), existingDescription .getPrimaryLabel ());
215+ String message = String .format (Locale .ENGLISH ,
216+ "The schema already contains description with the underlying class %s under the primary label %s" ,
217+ newEntity .getUnderlyingClass ().getName (), existingDescription .getPrimaryLabel ());
192218 throw new MappingException (message );
193219 }
194220 }
@@ -221,7 +247,7 @@ private static boolean isValidParentNode(@Nullable Class<?> parentClass) {
221247
222248 // Either a concrete class explicitly annotated as Node or an abstract class
223249 return Modifier .isAbstract (parentClass .getModifiers ()) ||
224- parentClass .isAnnotationPresent (Node .class );
250+ parentClass .isAnnotationPresent (Node .class );
225251 }
226252
227253 /*
@@ -339,18 +365,21 @@ Constructor<?> findConstructor(Class<?> clazz, Class<?>... parameterTypes) {
339365 }
340366 }
341367
342- private <T extends Neo4jPersistentPropertyConverterFactory > T getOrCreateConverterFactoryOfType (Class <T > converterFactoryType ) {
368+ private <T extends Neo4jPersistentPropertyConverterFactory > T getOrCreateConverterFactoryOfType (
369+ Class <T > converterFactoryType ) {
343370
344371 return converterFactoryType .cast (this .converterFactories .computeIfAbsent (converterFactoryType , t -> {
345372 Constructor <?> optionalConstructor ;
346373 optionalConstructor = findConstructor (t , BeanFactory .class , Neo4jConversionService .class );
347374 if (optionalConstructor != null ) {
348- return t .cast (BeanUtils .instantiateClass (optionalConstructor , this .beanFactory , this .conversionService ));
375+ return t .cast (
376+ BeanUtils .instantiateClass (optionalConstructor , this .beanFactory , this .conversionService ));
349377 }
350378
351379 optionalConstructor = findConstructor (t , Neo4jConversionService .class , BeanFactory .class );
352380 if (optionalConstructor != null ) {
353- return t .cast (BeanUtils .instantiateClass (optionalConstructor , this .beanFactory , this .conversionService ));
381+ return t .cast (
382+ BeanUtils .instantiateClass (optionalConstructor , this .beanFactory , this .conversionService ));
354383 }
355384
356385 optionalConstructor = findConstructor (t , BeanFactory .class );
@@ -379,8 +408,10 @@ Neo4jPersistentPropertyConverter<?> getOptionalCustomConversionsFor(Neo4jPersist
379408 }
380409
381410 ConvertWith convertWith = persistentProperty .getRequiredAnnotation (ConvertWith .class );
382- Neo4jPersistentPropertyConverterFactory persistentPropertyConverterFactory = this .getOrCreateConverterFactoryOfType (convertWith .converterFactory ());
383- Neo4jPersistentPropertyConverter <?> customConverter = persistentPropertyConverterFactory .getPropertyConverterFor (persistentProperty );
411+ Neo4jPersistentPropertyConverterFactory persistentPropertyConverterFactory = this .getOrCreateConverterFactoryOfType (
412+ convertWith .converterFactory ());
413+ Neo4jPersistentPropertyConverter <?> customConverter = persistentPropertyConverterFactory .getPropertyConverterFor (
414+ persistentProperty );
384415
385416 boolean forCollection = false ;
386417 if (persistentProperty .isCollectionLike ()) {
@@ -406,20 +437,22 @@ Neo4jPersistentPropertyConverter<?> getOptionalCustomConversionsFor(Neo4jPersist
406437 persistentProperty .getType ().equals (((ParameterizedType ) propertyType ).getRawType ());
407438 }
408439
409- return new NullSafeNeo4jPersistentPropertyConverter <>(customConverter , persistentProperty .isComposite (), forCollection );
440+ return new NullSafeNeo4jPersistentPropertyConverter <>(customConverter , persistentProperty .isComposite (),
441+ forCollection );
410442 }
411443
412444 @ Override
413445 public void setApplicationContext (ApplicationContext applicationContext ) throws BeansException {
414446 super .setApplicationContext (applicationContext );
415447
416448 this .beanFactory = applicationContext .getAutowireCapableBeanFactory ();
449+ this .eventSupport = EventSupport .discoverCallbacks (this , this .beanFactory );
417450 }
418451
419452 public CreateRelationshipStatementHolder createStatement (Neo4jPersistentEntity <?> neo4jPersistentEntity ,
420- NestedRelationshipContext relationshipContext ,
421- Object relatedValue ,
422- boolean isNewRelationship ) {
453+ NestedRelationshipContext relationshipContext ,
454+ Object relatedValue ,
455+ boolean isNewRelationship ) {
423456
424457 if (relationshipContext .hasRelationshipWithProperties ()) {
425458 MappingSupport .RelationshipPropertiesWithEntityHolder relatedValueEntityHolder =
@@ -436,25 +469,30 @@ public CreateRelationshipStatementHolder createStatement(Neo4jPersistentEntity<?
436469
437470 String dynamicRelationshipType = null ;
438471 if (relationshipContext .getRelationship ().isDynamic ()) {
439- TypeInformation <?> keyType = relationshipContext .getInverse ().getTypeInformation ().getRequiredComponentType ();
472+ TypeInformation <?> keyType = relationshipContext .getInverse ().getTypeInformation ()
473+ .getRequiredComponentType ();
440474 Object key = ((Map .Entry ) relatedValue ).getKey ();
441- dynamicRelationshipType = conversionService .writeValue (key , keyType , relationshipContext .getInverse ().getOptionalConverter ()).asString ();
475+ dynamicRelationshipType = conversionService .writeValue (key , keyType ,
476+ relationshipContext .getInverse ().getOptionalConverter ()).asString ();
442477 }
443478 return createStatementForRelationShipWithProperties (
444479 neo4jPersistentEntity , relationshipContext ,
445480 dynamicRelationshipType , relatedValueEntityHolder , isNewRelationship
446481 );
447482 } else {
448- return createStatementForRelationshipWithoutProperties (neo4jPersistentEntity , relationshipContext , relatedValue );
483+ return createStatementForRelationshipWithoutProperties (neo4jPersistentEntity , relationshipContext ,
484+ relatedValue );
449485 }
450486 }
451487
452- private CreateRelationshipStatementHolder createStatementForRelationShipWithProperties (Neo4jPersistentEntity <?> neo4jPersistentEntity ,
488+ private CreateRelationshipStatementHolder createStatementForRelationShipWithProperties (
489+ Neo4jPersistentEntity <?> neo4jPersistentEntity ,
453490 NestedRelationshipContext relationshipContext , @ Nullable String dynamicRelationshipType ,
454- MappingSupport .RelationshipPropertiesWithEntityHolder relatedValue , boolean isNewRelationship ) {
491+ MappingSupport .RelationshipPropertiesWithEntityHolder relatedValue , boolean isNewRelationship ) {
455492
456493 Statement relationshipCreationQuery = CypherGenerator .INSTANCE .prepareSaveOfRelationshipWithProperties (
457- neo4jPersistentEntity , relationshipContext .getRelationship (), isNewRelationship , dynamicRelationshipType );
494+ neo4jPersistentEntity , relationshipContext .getRelationship (), isNewRelationship ,
495+ dynamicRelationshipType );
458496
459497 Map <String , Object > propMap = new HashMap <>();
460498 // write relationship properties
@@ -481,4 +519,79 @@ private CreateRelationshipStatementHolder createStatementForRelationshipWithoutP
481519 neo4jPersistentEntity , relationshipContext .getRelationship (), relationshipType );
482520 return new CreateRelationshipStatementHolder (relationshipCreationQuery );
483521 }
522+
523+ /**
524+ * Executes all post load methods of the given instance.
525+ *
526+ * @param entity The entity definition
527+ * @param instance The instance whose post load methods should be executed
528+ * @param <T> Type of the entity
529+ * @return The instance
530+ */
531+ public <T > T invokePostLoad (Neo4jPersistentEntity <T > entity , T instance ) {
532+
533+ getPostLoadMethods (entity ).forEach (methodHolder -> methodHolder .invoke (instance ));
534+ return instance ;
535+ }
536+
537+ Set <MethodHolder > getPostLoadMethods (Neo4jPersistentEntity <?> entity ) {
538+ return this .postLoadMethods .computeIfAbsent (entity , Neo4jMappingContext ::computePostLoadMethods );
539+ }
540+
541+ private static Set <MethodHolder > computePostLoadMethods (Neo4jPersistentEntity <?> entity ) {
542+
543+ Set <MethodHolder > postLoadMethods = new LinkedHashSet <>();
544+ ReflectionUtils .MethodFilter isValidPostLoad = method -> {
545+ int modifiers = method .getModifiers ();
546+ return !Modifier .isStatic (modifiers ) && method .getParameterCount () == 0 && VOID_TYPES .contains (
547+ method .getReturnType ()) && AnnotationUtils .findAnnotation (method , PostLoad .class ) != null ;
548+ };
549+ Class <?> underlyingClass = entity .getUnderlyingClass ();
550+ ReflectionUtils .doWithMethods (underlyingClass , method -> postLoadMethods .add (new MethodHolder (method , null )),
551+ isValidPostLoad );
552+ if (KotlinDetector .isKotlinType (underlyingClass )) {
553+ ReflectionUtils .doWithFields (underlyingClass , field -> {
554+ ReflectionUtils .doWithMethods (field .getType (),
555+ method -> postLoadMethods .add (new MethodHolder (method , field )), isValidPostLoad );
556+ }, field -> field .isSynthetic () && field .getName ().startsWith ("$$delegate_" ));
557+ }
558+
559+ return Collections .unmodifiableSet (postLoadMethods );
560+ }
561+
562+ static class MethodHolder {
563+
564+ private final Method method ;
565+
566+ @ Nullable
567+ private final Field delegate ;
568+
569+ MethodHolder (Method method , @ Nullable Field delegate ) {
570+ this .method = method ;
571+ this .delegate = delegate ;
572+ }
573+
574+ String getName () {
575+ return method .getName ();
576+ }
577+
578+ void invoke (Object instance ) {
579+
580+ method .setAccessible (true );
581+ ReflectionUtils .invokeMethod (method , getInstanceOrDelegate (instance , delegate ));
582+ }
583+
584+ static Object getInstanceOrDelegate (Object instance , @ Nullable Field delegateHolder ) {
585+ if (delegateHolder == null ) {
586+ return instance ;
587+ } else {
588+ try {
589+ delegateHolder .setAccessible (true );
590+ return delegateHolder .get (instance );
591+ } catch (IllegalAccessException e ) {
592+ throw new RuntimeException (e );
593+ }
594+ }
595+ }
596+ }
484597}
0 commit comments