Skip to content

Commit 9ef26c3

Browse files
GH-2574 - Ensure consistent behaviour of org.springframework.data.neo4j.core.mapping.NodeDescription#getChildNodeDescriptionsInHierarchy.
This fixes #2574.
1 parent 4463ccc commit 9ef26c3

File tree

3 files changed

+159
-3
lines changed

3 files changed

+159
-3
lines changed

src/main/java/org/springframework/data/neo4j/core/mapping/DefaultNeo4jPersistentEntity.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -515,13 +515,13 @@ public Collection<GraphPropertyDescription> getGraphPropertiesInHierarchy() {
515515
public void addChildNodeDescription(NodeDescription<?> child) {
516516
this.childNodeDescriptions.add(child);
517517
updateChildNodeDescriptionCache();
518-
if (this.parentNodeDescription != null) {
519-
((DefaultNeo4jPersistentEntity<?>) this.parentNodeDescription).updateChildNodeDescriptionCache();
520-
}
521518
}
522519

523520
private void updateChildNodeDescriptionCache() {
524521
this.childNodeDescriptionsInHierarchy = computeChildNodeDescriptionInHierarchy();
522+
if (this.parentNodeDescription != null) {
523+
((DefaultNeo4jPersistentEntity<?>) this.parentNodeDescription).updateChildNodeDescriptionCache();
524+
}
525525
}
526526

527527
@Override

src/test/java/org/springframework/data/neo4j/core/mapping/Neo4jMappingContextTest.java

+59
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,18 @@
2525
import java.util.Collections;
2626
import java.util.Comparator;
2727
import java.util.HashSet;
28+
import java.util.IdentityHashMap;
2829
import java.util.List;
2930
import java.util.Map;
31+
import java.util.Objects;
3032
import java.util.Set;
33+
import java.util.TreeSet;
3134
import java.util.UUID;
35+
import java.util.stream.Collectors;
3236

3337
import org.assertj.core.api.Assertions;
3438
import org.junit.jupiter.api.Nested;
39+
import org.junit.jupiter.api.RepeatedTest;
3540
import org.junit.jupiter.api.Test;
3641
import org.junit.jupiter.api.extension.ExtendWith;
3742
import org.junit.jupiter.params.ParameterizedTest;
@@ -54,6 +59,7 @@
5459
import org.springframework.data.neo4j.core.mapping.datagraph1446.R2;
5560
import org.springframework.data.neo4j.core.mapping.datagraph1448.A_S3;
5661
import org.springframework.data.neo4j.core.mapping.datagraph1448.RelatedThing;
62+
import org.springframework.data.neo4j.core.mapping.gh2574.Model;
5763
import org.springframework.data.neo4j.core.schema.CompositeProperty;
5864
import org.springframework.data.neo4j.core.schema.GeneratedValue;
5965
import org.springframework.data.neo4j.core.schema.Id;
@@ -592,6 +598,59 @@ void shouldInvokePostLoadInKotlinClassesByDelegate() {
592598
assertThat(instance.getBaseName()).isEqualTo("someValue");
593599
}
594600

601+
private static Set<Class<?>> scanAndShuffle(String basePackage) throws ClassNotFoundException {
602+
603+
Comparator<Class<?>> pseudoRandomComparator = new Comparator<Class<?>>() {
604+
private final Map<Object, UUID> uniqueIds = new IdentityHashMap<>();
605+
606+
@Override
607+
public int compare(Class<?> o1, Class<?> o2) {
608+
UUID e1 = uniqueIds.computeIfAbsent(o1, k -> UUID.randomUUID());
609+
UUID e2 = uniqueIds.computeIfAbsent(o2, k -> UUID.randomUUID());
610+
return e1.compareTo(e2);
611+
}
612+
};
613+
614+
Set<Class<?>> scanResult = Neo4jEntityScanner.get().scan(basePackage);
615+
Set<Class<?>> initialEntities = new TreeSet<>(pseudoRandomComparator);
616+
initialEntities.addAll(scanResult);
617+
return initialEntities;
618+
}
619+
620+
@RepeatedTest(10) // GH-2574
621+
void hierarchyMustBeConsistentlyReportedWithIntermediateConcreteClasses() throws ClassNotFoundException {
622+
623+
Neo4jMappingContext neo4jMappingContext = new Neo4jMappingContext();
624+
neo4jMappingContext.setStrict(true);
625+
neo4jMappingContext.setInitialEntitySet(scanAndShuffle("org.springframework.data.neo4j.core.mapping.gh2574"));
626+
neo4jMappingContext.initialize();
627+
628+
Neo4jPersistentEntity<?> b1 = Objects.requireNonNull(neo4jMappingContext.getPersistentEntity(Model.B1.class));
629+
List<String> children = b1.getChildNodeDescriptionsInHierarchy()
630+
.stream().map(NodeDescription::getPrimaryLabel)
631+
.sorted()
632+
.collect(Collectors.toList());
633+
634+
assertThat(children).containsExactly("B2", "B2a", "B3", "B3a");
635+
}
636+
637+
@Test // GH-2574
638+
void hierarchyMustBeConsistentlyReported() throws ClassNotFoundException {
639+
640+
Neo4jMappingContext neo4jMappingContext = new Neo4jMappingContext();
641+
neo4jMappingContext.setStrict(true);
642+
neo4jMappingContext.setInitialEntitySet(scanAndShuffle("org.springframework.data.neo4j.core.mapping.gh2574"));
643+
neo4jMappingContext.initialize();
644+
645+
Neo4jPersistentEntity<?> a1 = Objects.requireNonNull(neo4jMappingContext.getPersistentEntity(Model.A1.class));
646+
List<String> children = a1.getChildNodeDescriptionsInHierarchy()
647+
.stream().map(NodeDescription::getPrimaryLabel)
648+
.sorted()
649+
.collect(Collectors.toList());
650+
651+
assertThat(children).containsExactly("A2", "A3", "A4");
652+
}
653+
595654
static class EntityWithPostLoadMethods {
596655

597656
String m1;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/*
2+
* Copyright 2011-2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.neo4j.core.mapping.gh2574;
17+
18+
import org.springframework.data.neo4j.core.schema.Id;
19+
import org.springframework.data.neo4j.core.schema.Node;
20+
21+
/**
22+
* A hierachical model with and without intermediate abstract classes.
23+
*
24+
* @author Michael J. Simons
25+
*/
26+
public abstract class Model {
27+
28+
/**
29+
* Shut up checkstyle.
30+
*/
31+
@Node
32+
public abstract static class A1 {
33+
@Id
34+
String id;
35+
}
36+
37+
/**
38+
* Shut up checkstyle.
39+
*/
40+
@Node
41+
public abstract static class A2 extends A1 {
42+
}
43+
44+
/**
45+
* Shut up checkstyle.
46+
*/
47+
@Node
48+
public abstract static class A3 extends A2 {
49+
}
50+
51+
/**
52+
* Shut up checkstyle.
53+
*/
54+
@Node
55+
public static class A4 extends A3 {
56+
}
57+
58+
/**
59+
* Shut up checkstyle.
60+
*/
61+
@Node
62+
public abstract static class B1 {
63+
@Id
64+
String id;
65+
}
66+
67+
/**
68+
* Shut up checkstyle.
69+
*/
70+
@Node
71+
public abstract static class B2 extends B1 {
72+
}
73+
74+
/**
75+
* Shut up checkstyle.
76+
*/
77+
@Node
78+
public static class B2a extends B2 {
79+
}
80+
81+
/**
82+
* Shut up checkstyle.
83+
*/
84+
@Node
85+
public abstract static class B3 extends B2 {
86+
}
87+
88+
/**
89+
* Shut up checkstyle.
90+
*/
91+
@Node
92+
public static class B3a extends B3 {
93+
}
94+
95+
private Model() {
96+
}
97+
}

0 commit comments

Comments
 (0)