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 .security .AccessController ;
25+ import java .security .PrivilegedAction ;
26+ import java .util .Arrays ;
27+ import java .util .Collections ;
2328import java .util .HashMap ;
29+ import java .util .HashSet ;
30+ import java .util .LinkedHashSet ;
2431import java .util .List ;
2532import java .util .Locale ;
2633import java .util .Map ;
4047import org .springframework .beans .factory .config .AutowireCapableBeanFactory ;
4148import org .springframework .context .ApplicationContext ;
4249import org .springframework .core .GenericTypeResolver ;
50+ import org .springframework .core .KotlinDetector ;
51+ import org .springframework .core .annotation .AnnotationUtils ;
4352import org .springframework .data .mapping .MappingException ;
4453import org .springframework .data .mapping .PersistentEntity ;
54+ import org .springframework .data .mapping .callback .EntityCallbacks ;
4555import org .springframework .data .mapping .context .AbstractMappingContext ;
4656import org .springframework .data .mapping .model .EntityInstantiator ;
4757import org .springframework .data .mapping .model .EntityInstantiators ;
5262import org .springframework .data .neo4j .core .convert .Neo4jConversions ;
5363import org .springframework .data .neo4j .core .convert .Neo4jPersistentPropertyConverter ;
5464import org .springframework .data .neo4j .core .convert .Neo4jPersistentPropertyConverterFactory ;
65+ import org .springframework .data .neo4j .core .mapping .callback .EventSupport ;
5566import org .springframework .data .neo4j .core .schema .IdGenerator ;
5667import org .springframework .data .neo4j .core .schema .Node ;
68+ import org .springframework .data .neo4j .core .schema .PostLoad ;
5769import org .springframework .data .util .TypeInformation ;
5870import org .springframework .lang .Nullable ;
5971import org .springframework .util .ReflectionUtils ;
@@ -78,6 +90,8 @@ public final class Neo4jMappingContext extends AbstractMappingContext<Neo4jPersi
7890 */
7991 private static final EntityInstantiators INSTANTIATORS = new EntityInstantiators ();
8092
93+ private static final Set <Class <?>> VOID_TYPES = new HashSet <>(Arrays .asList (Void .class , void .class ));
94+
8195 /**
8296 * A map of fallback id generators, that have not been added to the application context
8397 */
@@ -95,6 +109,10 @@ public final class Neo4jMappingContext extends AbstractMappingContext<Neo4jPersi
95109
96110 private final Neo4jConversionService conversionService ;
97111
112+ private final Map <Neo4jPersistentEntity , Set <MethodHolder >> postLoadMethods = new ConcurrentHashMap <>();
113+
114+ private EventSupport eventSupport ;
115+
98116 private @ Nullable AutowireCapableBeanFactory beanFactory ;
99117
100118 private boolean strict = false ;
@@ -133,12 +151,14 @@ public Neo4jMappingContext(Neo4jConversions neo4jConversions, @Nullable TypeSyst
133151
134152 this .conversionService = new DefaultNeo4jConversionService (neo4jConversions );
135153 this .typeSystem = typeSystem == null ? InternalTypeSystem .TYPE_SYSTEM : typeSystem ;
154+ this .eventSupport = EventSupport .useExistingCallbacks (this , EntityCallbacks .create ());
136155
137156 super .setSimpleTypeHolder (neo4jConversions .getSimpleTypeHolder ());
138157 }
139158
140159 public Neo4jEntityConverter getEntityConverter () {
141- return new DefaultNeo4jEntityConverter (INSTANTIATORS , conversionService , nodeDescriptionStore , typeSystem );
160+ return new DefaultNeo4jEntityConverter (INSTANTIATORS , nodeDescriptionStore , conversionService , eventSupport ,
161+ typeSystem );
142162 }
143163
144164 public Neo4jConversionService getConversionService () {
@@ -170,25 +190,33 @@ protected <T> Neo4jPersistentEntity<?> createPersistentEntity(TypeInformation<T>
170190 if (!newEntity .describesInterface ()) {
171191 if (this .nodeDescriptionStore .containsKey (primaryLabel )) {
172192
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 );
193+ Neo4jPersistentEntity existingEntity = (Neo4jPersistentEntity ) this .nodeDescriptionStore .get (
194+ primaryLabel );
195+ if (!existingEntity .getTypeInformation ().getRawTypeInformation ()
196+ .equals (typeInformation .getRawTypeInformation ())) {
197+ String message = String .format (Locale .ENGLISH ,
198+ "The schema already contains a node description under the primary label %s" , primaryLabel );
176199 throw new MappingException (message );
177200 }
178201 }
179202
180203 if (this .nodeDescriptionStore .containsValue (newEntity )) {
181- Optional <String > label = this .nodeDescriptionStore .entrySet ().stream ().filter (e -> e .getValue ().equals (newEntity )).map (Map .Entry ::getKey ).findFirst ();
204+ Optional <String > label = this .nodeDescriptionStore .entrySet ().stream ()
205+ .filter (e -> e .getValue ().equals (newEntity )).map (Map .Entry ::getKey ).findFirst ();
182206
183- String message = String .format (Locale .ENGLISH , "The schema already contains description %s under the primary label %s" , newEntity , label .orElse ("n/a" ));
207+ String message = String .format (Locale .ENGLISH ,
208+ "The schema already contains description %s under the primary label %s" , newEntity ,
209+ label .orElse ("n/a" ));
184210 throw new MappingException (message );
185211 }
186212
187213 NodeDescription <?> existingDescription = this .getNodeDescription (newEntity .getUnderlyingClass ());
188214 if (existingDescription != null ) {
189215
190216 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 ());
217+ String message = String .format (Locale .ENGLISH ,
218+ "The schema already contains description with the underlying class %s under the primary label %s" ,
219+ newEntity .getUnderlyingClass ().getName (), existingDescription .getPrimaryLabel ());
192220 throw new MappingException (message );
193221 }
194222 }
@@ -221,7 +249,7 @@ private static boolean isValidParentNode(@Nullable Class<?> parentClass) {
221249
222250 // Either a concrete class explicitly annotated as Node or an abstract class
223251 return Modifier .isAbstract (parentClass .getModifiers ()) ||
224- parentClass .isAnnotationPresent (Node .class );
252+ parentClass .isAnnotationPresent (Node .class );
225253 }
226254
227255 /*
@@ -339,18 +367,21 @@ Constructor<?> findConstructor(Class<?> clazz, Class<?>... parameterTypes) {
339367 }
340368 }
341369
342- private <T extends Neo4jPersistentPropertyConverterFactory > T getOrCreateConverterFactoryOfType (Class <T > converterFactoryType ) {
370+ private <T extends Neo4jPersistentPropertyConverterFactory > T getOrCreateConverterFactoryOfType (
371+ Class <T > converterFactoryType ) {
343372
344373 return converterFactoryType .cast (this .converterFactories .computeIfAbsent (converterFactoryType , t -> {
345374 Constructor <?> optionalConstructor ;
346375 optionalConstructor = findConstructor (t , BeanFactory .class , Neo4jConversionService .class );
347376 if (optionalConstructor != null ) {
348- return t .cast (BeanUtils .instantiateClass (optionalConstructor , this .beanFactory , this .conversionService ));
377+ return t .cast (
378+ BeanUtils .instantiateClass (optionalConstructor , this .beanFactory , this .conversionService ));
349379 }
350380
351381 optionalConstructor = findConstructor (t , Neo4jConversionService .class , BeanFactory .class );
352382 if (optionalConstructor != null ) {
353- return t .cast (BeanUtils .instantiateClass (optionalConstructor , this .beanFactory , this .conversionService ));
383+ return t .cast (
384+ BeanUtils .instantiateClass (optionalConstructor , this .beanFactory , this .conversionService ));
354385 }
355386
356387 optionalConstructor = findConstructor (t , BeanFactory .class );
@@ -379,8 +410,10 @@ Neo4jPersistentPropertyConverter<?> getOptionalCustomConversionsFor(Neo4jPersist
379410 }
380411
381412 ConvertWith convertWith = persistentProperty .getRequiredAnnotation (ConvertWith .class );
382- Neo4jPersistentPropertyConverterFactory persistentPropertyConverterFactory = this .getOrCreateConverterFactoryOfType (convertWith .converterFactory ());
383- Neo4jPersistentPropertyConverter <?> customConverter = persistentPropertyConverterFactory .getPropertyConverterFor (persistentProperty );
413+ Neo4jPersistentPropertyConverterFactory persistentPropertyConverterFactory = this .getOrCreateConverterFactoryOfType (
414+ convertWith .converterFactory ());
415+ Neo4jPersistentPropertyConverter <?> customConverter = persistentPropertyConverterFactory .getPropertyConverterFor (
416+ persistentProperty );
384417
385418 boolean forCollection = false ;
386419 if (persistentProperty .isCollectionLike ()) {
@@ -406,20 +439,22 @@ Neo4jPersistentPropertyConverter<?> getOptionalCustomConversionsFor(Neo4jPersist
406439 persistentProperty .getType ().equals (((ParameterizedType ) propertyType ).getRawType ());
407440 }
408441
409- return new NullSafeNeo4jPersistentPropertyConverter <>(customConverter , persistentProperty .isComposite (), forCollection );
442+ return new NullSafeNeo4jPersistentPropertyConverter <>(customConverter , persistentProperty .isComposite (),
443+ forCollection );
410444 }
411445
412446 @ Override
413447 public void setApplicationContext (ApplicationContext applicationContext ) throws BeansException {
414448 super .setApplicationContext (applicationContext );
415449
416450 this .beanFactory = applicationContext .getAutowireCapableBeanFactory ();
451+ this .eventSupport = EventSupport .discoverCallbacks (this , this .beanFactory );
417452 }
418453
419454 public CreateRelationshipStatementHolder createStatement (Neo4jPersistentEntity <?> neo4jPersistentEntity ,
420- NestedRelationshipContext relationshipContext ,
421- Object relatedValue ,
422- boolean isNewRelationship ) {
455+ NestedRelationshipContext relationshipContext ,
456+ Object relatedValue ,
457+ boolean isNewRelationship ) {
423458
424459 if (relationshipContext .hasRelationshipWithProperties ()) {
425460 MappingSupport .RelationshipPropertiesWithEntityHolder relatedValueEntityHolder =
@@ -436,25 +471,30 @@ public CreateRelationshipStatementHolder createStatement(Neo4jPersistentEntity<?
436471
437472 String dynamicRelationshipType = null ;
438473 if (relationshipContext .getRelationship ().isDynamic ()) {
439- TypeInformation <?> keyType = relationshipContext .getInverse ().getTypeInformation ().getRequiredComponentType ();
474+ TypeInformation <?> keyType = relationshipContext .getInverse ().getTypeInformation ()
475+ .getRequiredComponentType ();
440476 Object key = ((Map .Entry ) relatedValue ).getKey ();
441- dynamicRelationshipType = conversionService .writeValue (key , keyType , relationshipContext .getInverse ().getOptionalConverter ()).asString ();
477+ dynamicRelationshipType = conversionService .writeValue (key , keyType ,
478+ relationshipContext .getInverse ().getOptionalConverter ()).asString ();
442479 }
443480 return createStatementForRelationShipWithProperties (
444481 neo4jPersistentEntity , relationshipContext ,
445482 dynamicRelationshipType , relatedValueEntityHolder , isNewRelationship
446483 );
447484 } else {
448- return createStatementForRelationshipWithoutProperties (neo4jPersistentEntity , relationshipContext , relatedValue );
485+ return createStatementForRelationshipWithoutProperties (neo4jPersistentEntity , relationshipContext ,
486+ relatedValue );
449487 }
450488 }
451489
452- private CreateRelationshipStatementHolder createStatementForRelationShipWithProperties (Neo4jPersistentEntity <?> neo4jPersistentEntity ,
490+ private CreateRelationshipStatementHolder createStatementForRelationShipWithProperties (
491+ Neo4jPersistentEntity <?> neo4jPersistentEntity ,
453492 NestedRelationshipContext relationshipContext , @ Nullable String dynamicRelationshipType ,
454- MappingSupport .RelationshipPropertiesWithEntityHolder relatedValue , boolean isNewRelationship ) {
493+ MappingSupport .RelationshipPropertiesWithEntityHolder relatedValue , boolean isNewRelationship ) {
455494
456495 Statement relationshipCreationQuery = CypherGenerator .INSTANCE .prepareSaveOfRelationshipWithProperties (
457- neo4jPersistentEntity , relationshipContext .getRelationship (), isNewRelationship , dynamicRelationshipType );
496+ neo4jPersistentEntity , relationshipContext .getRelationship (), isNewRelationship ,
497+ dynamicRelationshipType );
458498
459499 Map <String , Object > propMap = new HashMap <>();
460500 // write relationship properties
@@ -481,4 +521,89 @@ private CreateRelationshipStatementHolder createStatementForRelationshipWithoutP
481521 neo4jPersistentEntity , relationshipContext .getRelationship (), relationshipType );
482522 return new CreateRelationshipStatementHolder (relationshipCreationQuery );
483523 }
524+
525+ /**
526+ * Executes all post load methods of the given instance.
527+ *
528+ * @param entity The entity definition
529+ * @param instance The instance whose post load methods should be executed
530+ * @param <T> Type of the entity
531+ * @return The instance
532+ */
533+ public <T > T invokePostLoad (Neo4jPersistentEntity <T > entity , T instance ) {
534+
535+ getPostLoadMethods (entity ).forEach (methodHolder -> methodHolder .invoke (instance ));
536+ return instance ;
537+ }
538+
539+ Set <MethodHolder > getPostLoadMethods (Neo4jPersistentEntity <?> entity ) {
540+ return this .postLoadMethods .computeIfAbsent (entity , Neo4jMappingContext ::computePostLoadMethods );
541+ }
542+
543+ private static Set <MethodHolder > computePostLoadMethods (Neo4jPersistentEntity <?> entity ) {
544+
545+ Set <MethodHolder > postLoadMethods = new LinkedHashSet <>();
546+ ReflectionUtils .MethodFilter isValidPostLoad = method -> {
547+ int modifiers = method .getModifiers ();
548+ return !Modifier .isStatic (modifiers ) && method .getParameterCount () == 0 && VOID_TYPES .contains (
549+ method .getReturnType ()) && AnnotationUtils .findAnnotation (method , PostLoad .class ) != null ;
550+ };
551+ Class <?> underlyingClass = entity .getUnderlyingClass ();
552+ ReflectionUtils .doWithMethods (underlyingClass , method -> postLoadMethods .add (new MethodHolder (method , null )),
553+ isValidPostLoad );
554+ if (KotlinDetector .isKotlinType (underlyingClass )) {
555+ ReflectionUtils .doWithFields (underlyingClass , field -> {
556+ ReflectionUtils .doWithMethods (field .getType (),
557+ method -> postLoadMethods .add (new MethodHolder (method , field )), isValidPostLoad );
558+ }, field -> field .isSynthetic () && field .getName ().startsWith ("$$delegate_" ));
559+ }
560+
561+ return Collections .unmodifiableSet (postLoadMethods );
562+ }
563+
564+ static class MethodHolder {
565+
566+ private final Method method ;
567+
568+ @ Nullable
569+ private final Field delegate ;
570+
571+ MethodHolder (Method method , @ Nullable Field delegate ) {
572+ this .method = method ;
573+ this .delegate = delegate ;
574+ }
575+
576+ Method getMethod () {
577+ return method ;
578+ }
579+
580+ String getName () {
581+ return method .getName ();
582+ }
583+
584+ void invoke (Object instance ) {
585+ Method methodToInvoke = AccessController .doPrivileged ((PrivilegedAction <Method >) () -> {
586+ if (!method .isAccessible ()) {
587+ method .setAccessible (true );
588+ }
589+ return method ;
590+ });
591+ ReflectionUtils .invokeMethod (methodToInvoke , getInstanceOrDelegate (instance , delegate ));
592+ }
593+
594+ static Object getInstanceOrDelegate (Object instance , @ Nullable Field delegateHolder ) {
595+ if (delegateHolder == null ) {
596+ return instance ;
597+ } else {
598+ return AccessController .doPrivileged ((PrivilegedAction <Object >) () -> {
599+ try {
600+ delegateHolder .setAccessible (true );
601+ return delegateHolder .get (instance );
602+ } catch (IllegalAccessException e ) {
603+ throw new RuntimeException (e );
604+ }
605+ });
606+ }
607+ }
608+ }
484609}
0 commit comments