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