Skip to content

Commit 569868b

Browse files
GH-2574 - Ensure consistent behaviour of org.springframework.data.neo4j.core.mapping.NodeDescription#getChildNodeDescriptionsInHierarchy.
This fixes #2574.
1 parent dabbecf commit 569868b

File tree

3 files changed

+160
-3
lines changed

3 files changed

+160
-3
lines changed

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

+3-3
Original file line numberDiff line numberDiff line change
@@ -513,13 +513,13 @@ public Collection<GraphPropertyDescription> getGraphPropertiesInHierarchy() {
513513
public void addChildNodeDescription(NodeDescription<?> child) {
514514
this.childNodeDescriptions.add(child);
515515
updateChildNodeDescriptionCache();
516-
if (this.parentNodeDescription != null) {
517-
((DefaultNeo4jPersistentEntity<?>) this.parentNodeDescription).updateChildNodeDescriptionCache();
518-
}
519516
}
520517

521518
private void updateChildNodeDescriptionCache() {
522519
this.childNodeDescriptionsInHierarchy = computeChildNodeDescriptionInHierarchy();
520+
if (this.parentNodeDescription != null) {
521+
((DefaultNeo4jPersistentEntity<?>) this.parentNodeDescription).updateChildNodeDescriptionCache();
522+
}
523523
}
524524

525525
@Override

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

+60
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;
@@ -532,6 +538,60 @@ void dontFailOnCyclicRelationshipProperties() { // GH-2398
532538
assertThat(persistentEntity.getGraphProperties()).hasSize(3);
533539
}
534540

541+
private static Set<Class<?>> scanAndShuffle(String basePackage) throws ClassNotFoundException {
542+
543+
Comparator<Class<?>> pseudoRandomComparator = new Comparator<Class<?>>() {
544+
private final Map<Object, UUID> uniqueIds = new IdentityHashMap<>();
545+
546+
@Override
547+
public int compare(Class<?> o1, Class<?> o2) {
548+
UUID e1 = uniqueIds.computeIfAbsent(o1, k -> UUID.randomUUID());
549+
UUID e2 = uniqueIds.computeIfAbsent(o2, k -> UUID.randomUUID());
550+
return e1.compareTo(e2);
551+
}
552+
};
553+
554+
Set<Class<?>> scanResult = Neo4jEntityScanner.get().scan(basePackage);
555+
Set<Class<?>> initialEntities = new TreeSet<>(pseudoRandomComparator);
556+
initialEntities.addAll(scanResult);
557+
return initialEntities;
558+
}
559+
560+
561+
@RepeatedTest(10) // GH-2574
562+
void hierarchyMustBeConsistentlyReportedWithIntermediateConcreteClasses() throws ClassNotFoundException {
563+
564+
Neo4jMappingContext neo4jMappingContext = new Neo4jMappingContext();
565+
neo4jMappingContext.setStrict(true);
566+
neo4jMappingContext.setInitialEntitySet(scanAndShuffle("org.springframework.data.neo4j.core.mapping.gh2574"));
567+
neo4jMappingContext.initialize();
568+
569+
Neo4jPersistentEntity<?> b1 = Objects.requireNonNull(neo4jMappingContext.getPersistentEntity(Model.B1.class));
570+
List<String> children = b1.getChildNodeDescriptionsInHierarchy()
571+
.stream().map(NodeDescription::getPrimaryLabel)
572+
.sorted()
573+
.collect(Collectors.toList());
574+
575+
assertThat(children).containsExactly("B2", "B2a", "B3", "B3a");
576+
}
577+
578+
@Test // GH-2574
579+
void hierarchyMustBeConsistentlyReported() throws ClassNotFoundException {
580+
581+
Neo4jMappingContext neo4jMappingContext = new Neo4jMappingContext();
582+
neo4jMappingContext.setStrict(true);
583+
neo4jMappingContext.setInitialEntitySet(scanAndShuffle("org.springframework.data.neo4j.core.mapping.gh2574"));
584+
neo4jMappingContext.initialize();
585+
586+
Neo4jPersistentEntity<?> a1 = Objects.requireNonNull(neo4jMappingContext.getPersistentEntity(Model.A1.class));
587+
List<String> children = a1.getChildNodeDescriptionsInHierarchy()
588+
.stream().map(NodeDescription::getPrimaryLabel)
589+
.sorted()
590+
.collect(Collectors.toList());
591+
592+
assertThat(children).containsExactly("A2", "A3", "A4");
593+
}
594+
535595
static class DummyIdGenerator implements IdGenerator<Void> {
536596

537597
@Override
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)