16
16
package org .springframework .data .neo4j .core .mapping ;
17
17
18
18
import java .lang .reflect .Constructor ;
19
+ import java .lang .reflect .Field ;
19
20
import java .lang .reflect .Method ;
20
21
import java .lang .reflect .Modifier ;
21
22
import java .lang .reflect .ParameterizedType ;
22
23
import java .lang .reflect .Type ;
24
+ import java .security .AccessController ;
25
+ import java .security .PrivilegedAction ;
26
+ import java .util .Arrays ;
27
+ import java .util .Collections ;
23
28
import java .util .HashMap ;
29
+ import java .util .HashSet ;
30
+ import java .util .LinkedHashSet ;
24
31
import java .util .List ;
25
32
import java .util .Locale ;
26
33
import java .util .Map ;
40
47
import org .springframework .beans .factory .config .AutowireCapableBeanFactory ;
41
48
import org .springframework .context .ApplicationContext ;
42
49
import org .springframework .core .GenericTypeResolver ;
50
+ import org .springframework .core .KotlinDetector ;
51
+ import org .springframework .core .annotation .AnnotationUtils ;
43
52
import org .springframework .data .mapping .MappingException ;
44
53
import org .springframework .data .mapping .PersistentEntity ;
54
+ import org .springframework .data .mapping .callback .EntityCallbacks ;
45
55
import org .springframework .data .mapping .context .AbstractMappingContext ;
46
56
import org .springframework .data .mapping .model .EntityInstantiator ;
47
57
import org .springframework .data .mapping .model .EntityInstantiators ;
52
62
import org .springframework .data .neo4j .core .convert .Neo4jConversions ;
53
63
import org .springframework .data .neo4j .core .convert .Neo4jPersistentPropertyConverter ;
54
64
import org .springframework .data .neo4j .core .convert .Neo4jPersistentPropertyConverterFactory ;
65
+ import org .springframework .data .neo4j .core .mapping .callback .EventSupport ;
55
66
import org .springframework .data .neo4j .core .schema .IdGenerator ;
56
67
import org .springframework .data .neo4j .core .schema .Node ;
68
+ import org .springframework .data .neo4j .core .schema .PostLoad ;
57
69
import org .springframework .data .util .TypeInformation ;
58
70
import org .springframework .lang .Nullable ;
59
71
import org .springframework .util .ReflectionUtils ;
@@ -78,6 +90,8 @@ public final class Neo4jMappingContext extends AbstractMappingContext<Neo4jPersi
78
90
*/
79
91
private static final EntityInstantiators INSTANTIATORS = new EntityInstantiators ();
80
92
93
+ private static final Set <Class <?>> VOID_TYPES = new HashSet <>(Arrays .asList (Void .class , void .class ));
94
+
81
95
/**
82
96
* A map of fallback id generators, that have not been added to the application context
83
97
*/
@@ -95,6 +109,10 @@ public final class Neo4jMappingContext extends AbstractMappingContext<Neo4jPersi
95
109
96
110
private final Neo4jConversionService conversionService ;
97
111
112
+ private final Map <Neo4jPersistentEntity , Set <MethodHolder >> postLoadMethods = new ConcurrentHashMap <>();
113
+
114
+ private EventSupport eventSupport ;
115
+
98
116
private @ Nullable AutowireCapableBeanFactory beanFactory ;
99
117
100
118
private boolean strict = false ;
@@ -133,12 +151,14 @@ public Neo4jMappingContext(Neo4jConversions neo4jConversions, @Nullable TypeSyst
133
151
134
152
this .conversionService = new DefaultNeo4jConversionService (neo4jConversions );
135
153
this .typeSystem = typeSystem == null ? InternalTypeSystem .TYPE_SYSTEM : typeSystem ;
154
+ this .eventSupport = EventSupport .useExistingCallbacks (this , EntityCallbacks .create ());
136
155
137
156
super .setSimpleTypeHolder (neo4jConversions .getSimpleTypeHolder ());
138
157
}
139
158
140
159
public Neo4jEntityConverter getEntityConverter () {
141
- return new DefaultNeo4jEntityConverter (INSTANTIATORS , conversionService , nodeDescriptionStore , typeSystem );
160
+ return new DefaultNeo4jEntityConverter (INSTANTIATORS , nodeDescriptionStore , conversionService , eventSupport ,
161
+ typeSystem );
142
162
}
143
163
144
164
public Neo4jConversionService getConversionService () {
@@ -170,25 +190,33 @@ protected <T> Neo4jPersistentEntity<?> createPersistentEntity(TypeInformation<T>
170
190
if (!newEntity .describesInterface ()) {
171
191
if (this .nodeDescriptionStore .containsKey (primaryLabel )) {
172
192
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 );
176
199
throw new MappingException (message );
177
200
}
178
201
}
179
202
180
203
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 ();
182
206
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" ));
184
210
throw new MappingException (message );
185
211
}
186
212
187
213
NodeDescription <?> existingDescription = this .getNodeDescription (newEntity .getUnderlyingClass ());
188
214
if (existingDescription != null ) {
189
215
190
216
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 ());
192
220
throw new MappingException (message );
193
221
}
194
222
}
@@ -221,7 +249,7 @@ private static boolean isValidParentNode(@Nullable Class<?> parentClass) {
221
249
222
250
// Either a concrete class explicitly annotated as Node or an abstract class
223
251
return Modifier .isAbstract (parentClass .getModifiers ()) ||
224
- parentClass .isAnnotationPresent (Node .class );
252
+ parentClass .isAnnotationPresent (Node .class );
225
253
}
226
254
227
255
/*
@@ -339,18 +367,21 @@ Constructor<?> findConstructor(Class<?> clazz, Class<?>... parameterTypes) {
339
367
}
340
368
}
341
369
342
- private <T extends Neo4jPersistentPropertyConverterFactory > T getOrCreateConverterFactoryOfType (Class <T > converterFactoryType ) {
370
+ private <T extends Neo4jPersistentPropertyConverterFactory > T getOrCreateConverterFactoryOfType (
371
+ Class <T > converterFactoryType ) {
343
372
344
373
return converterFactoryType .cast (this .converterFactories .computeIfAbsent (converterFactoryType , t -> {
345
374
Constructor <?> optionalConstructor ;
346
375
optionalConstructor = findConstructor (t , BeanFactory .class , Neo4jConversionService .class );
347
376
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 ));
349
379
}
350
380
351
381
optionalConstructor = findConstructor (t , Neo4jConversionService .class , BeanFactory .class );
352
382
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 ));
354
385
}
355
386
356
387
optionalConstructor = findConstructor (t , BeanFactory .class );
@@ -379,8 +410,10 @@ Neo4jPersistentPropertyConverter<?> getOptionalCustomConversionsFor(Neo4jPersist
379
410
}
380
411
381
412
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 );
384
417
385
418
boolean forCollection = false ;
386
419
if (persistentProperty .isCollectionLike ()) {
@@ -406,20 +439,22 @@ Neo4jPersistentPropertyConverter<?> getOptionalCustomConversionsFor(Neo4jPersist
406
439
persistentProperty .getType ().equals (((ParameterizedType ) propertyType ).getRawType ());
407
440
}
408
441
409
- return new NullSafeNeo4jPersistentPropertyConverter <>(customConverter , persistentProperty .isComposite (), forCollection );
442
+ return new NullSafeNeo4jPersistentPropertyConverter <>(customConverter , persistentProperty .isComposite (),
443
+ forCollection );
410
444
}
411
445
412
446
@ Override
413
447
public void setApplicationContext (ApplicationContext applicationContext ) throws BeansException {
414
448
super .setApplicationContext (applicationContext );
415
449
416
450
this .beanFactory = applicationContext .getAutowireCapableBeanFactory ();
451
+ this .eventSupport = EventSupport .discoverCallbacks (this , this .beanFactory );
417
452
}
418
453
419
454
public CreateRelationshipStatementHolder createStatement (Neo4jPersistentEntity <?> neo4jPersistentEntity ,
420
- NestedRelationshipContext relationshipContext ,
421
- Object relatedValue ,
422
- boolean isNewRelationship ) {
455
+ NestedRelationshipContext relationshipContext ,
456
+ Object relatedValue ,
457
+ boolean isNewRelationship ) {
423
458
424
459
if (relationshipContext .hasRelationshipWithProperties ()) {
425
460
MappingSupport .RelationshipPropertiesWithEntityHolder relatedValueEntityHolder =
@@ -436,25 +471,30 @@ public CreateRelationshipStatementHolder createStatement(Neo4jPersistentEntity<?
436
471
437
472
String dynamicRelationshipType = null ;
438
473
if (relationshipContext .getRelationship ().isDynamic ()) {
439
- TypeInformation <?> keyType = relationshipContext .getInverse ().getTypeInformation ().getRequiredComponentType ();
474
+ TypeInformation <?> keyType = relationshipContext .getInverse ().getTypeInformation ()
475
+ .getRequiredComponentType ();
440
476
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 ();
442
479
}
443
480
return createStatementForRelationShipWithProperties (
444
481
neo4jPersistentEntity , relationshipContext ,
445
482
dynamicRelationshipType , relatedValueEntityHolder , isNewRelationship
446
483
);
447
484
} else {
448
- return createStatementForRelationshipWithoutProperties (neo4jPersistentEntity , relationshipContext , relatedValue );
485
+ return createStatementForRelationshipWithoutProperties (neo4jPersistentEntity , relationshipContext ,
486
+ relatedValue );
449
487
}
450
488
}
451
489
452
- private CreateRelationshipStatementHolder createStatementForRelationShipWithProperties (Neo4jPersistentEntity <?> neo4jPersistentEntity ,
490
+ private CreateRelationshipStatementHolder createStatementForRelationShipWithProperties (
491
+ Neo4jPersistentEntity <?> neo4jPersistentEntity ,
453
492
NestedRelationshipContext relationshipContext , @ Nullable String dynamicRelationshipType ,
454
- MappingSupport .RelationshipPropertiesWithEntityHolder relatedValue , boolean isNewRelationship ) {
493
+ MappingSupport .RelationshipPropertiesWithEntityHolder relatedValue , boolean isNewRelationship ) {
455
494
456
495
Statement relationshipCreationQuery = CypherGenerator .INSTANCE .prepareSaveOfRelationshipWithProperties (
457
- neo4jPersistentEntity , relationshipContext .getRelationship (), isNewRelationship , dynamicRelationshipType );
496
+ neo4jPersistentEntity , relationshipContext .getRelationship (), isNewRelationship ,
497
+ dynamicRelationshipType );
458
498
459
499
Map <String , Object > propMap = new HashMap <>();
460
500
// write relationship properties
@@ -481,4 +521,89 @@ private CreateRelationshipStatementHolder createStatementForRelationshipWithoutP
481
521
neo4jPersistentEntity , relationshipContext .getRelationship (), relationshipType );
482
522
return new CreateRelationshipStatementHolder (relationshipCreationQuery );
483
523
}
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
+ }
484
609
}
0 commit comments