Skip to content

Commit b18bdaf

Browse files
committed
GH-2533 - Consider right entity types in dynamic relationships.
1 parent 23e6f18 commit b18bdaf

21 files changed

+264
-135
lines changed

src/main/java/org/springframework/data/neo4j/core/Neo4jTemplate.java

+8-8
Original file line numberDiff line numberDiff line change
@@ -341,7 +341,7 @@ private Object convertIdValues(@Nullable Neo4jPersistentProperty idProperty, Obj
341341
@Override
342342
public <T> T save(T instance) {
343343

344-
return saveImpl(instance, Collections.emptyMap(), null);
344+
return saveImpl(instance, Collections.emptySet(), null);
345345
}
346346

347347
@Override
@@ -368,7 +368,7 @@ public <T, R> R saveAs(T instance, Class<R> resultType) {
368368
}
369369

370370
ProjectionInformation projectionInformation = projectionFactory.getProjectionInformation(resultType);
371-
Map<PropertyPath, Boolean> pps = PropertyFilterSupport.addPropertiesFrom(instance.getClass(), resultType,
371+
Collection<PropertyFilter.ProjectedPath> pps = PropertyFilterSupport.addPropertiesFrom(instance.getClass(), resultType,
372372
projectionFactory, neo4jMappingContext);
373373

374374
T savedInstance = saveImpl(instance, pps, null);
@@ -383,7 +383,7 @@ public <T, R> R saveAs(T instance, Class<R> resultType) {
383383
this.findById(propertyAccessor.getProperty(idProperty), savedInstance.getClass()).get());
384384
}
385385

386-
private <T> T saveImpl(T instance, @Nullable Map<PropertyPath, Boolean> includedProperties, @Nullable NestedRelationshipProcessingStateMachine stateMachine) {
386+
private <T> T saveImpl(T instance, @Nullable Collection<PropertyFilter.ProjectedPath> includedProperties, @Nullable NestedRelationshipProcessingStateMachine stateMachine) {
387387

388388
if (stateMachine != null && stateMachine.hasProcessedValue(instance)) {
389389
return instance;
@@ -462,10 +462,10 @@ private <T> DynamicLabels determineDynamicLabels(T entityToBeSaved, Neo4jPersist
462462

463463
@Override
464464
public <T> List<T> saveAll(Iterable<T> instances) {
465-
return saveAllImpl(instances, Collections.emptyMap(), null);
465+
return saveAllImpl(instances, Collections.emptySet(), null);
466466
}
467467

468-
private <T> List<T> saveAllImpl(Iterable<T> instances, @Nullable Map<PropertyPath, Boolean> includedProperties, @Nullable BiPredicate<PropertyPath, Neo4jPersistentProperty> includeProperty) {
468+
private <T> List<T> saveAllImpl(Iterable<T> instances, @Nullable Collection<PropertyFilter.ProjectedPath> includedProperties, @Nullable BiPredicate<PropertyPath, Neo4jPersistentProperty> includeProperty) {
469469

470470
Set<Class<?>> types = new HashSet<>();
471471
List<T> entities = new ArrayList<>();
@@ -481,7 +481,7 @@ private <T> List<T> saveAllImpl(Iterable<T> instances, @Nullable Map<PropertyPat
481481
boolean heterogeneousCollection = types.size() > 1;
482482
Class<?> domainClass = types.iterator().next();
483483

484-
Map<PropertyPath, Boolean> pps = includeProperty == null ?
484+
Collection<PropertyFilter.ProjectedPath> pps = includeProperty == null ?
485485
includedProperties :
486486
TemplateSupport.computeIncludedPropertiesFromPredicate(this.neo4jMappingContext, domainClass,
487487
includeProperty);
@@ -557,7 +557,7 @@ public <T, R> List<R> saveAllAs(Iterable<T> instances, Class<R> resultType) {
557557

558558
ProjectionInformation projectionInformation = projectionFactory.getProjectionInformation(resultType);
559559

560-
Map<PropertyPath, Boolean> pps = PropertyFilterSupport.addPropertiesFrom(commonElementType, resultType,
560+
Collection<PropertyFilter.ProjectedPath> pps = PropertyFilterSupport.addPropertiesFrom(commonElementType, resultType,
561561
projectionFactory, neo4jMappingContext);
562562

563563
List<T> savedInstances = saveAllImpl(instances, pps, null);
@@ -969,7 +969,7 @@ <T, R> List<R> doSave(Iterable<R> instances, Class<T> domainType) {
969969

970970
Class<?> resultType = TemplateSupport.findCommonElementType(instances);
971971

972-
Map<PropertyPath, Boolean> pps = PropertyFilterSupport.addPropertiesFrom(domainType, resultType,
972+
Collection<PropertyFilter.ProjectedPath> pps = PropertyFilterSupport.addPropertiesFrom(domainType, resultType,
973973
projectionFactory, neo4jMappingContext);
974974

975975
NestedRelationshipProcessingStateMachine stateMachine = new NestedRelationshipProcessingStateMachine(neo4jMappingContext);

src/main/java/org/springframework/data/neo4j/core/PropertyFilterSupport.java

+98-26
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,22 @@
1717

1818
import org.apiguardian.api.API;
1919
import org.springframework.data.mapping.PropertyPath;
20+
import org.springframework.data.neo4j.core.mapping.GraphPropertyDescription;
2021
import org.springframework.data.neo4j.core.mapping.Neo4jMappingContext;
22+
import org.springframework.data.neo4j.core.mapping.Neo4jPersistentEntity;
23+
import org.springframework.data.neo4j.core.mapping.PropertyFilter;
24+
import org.springframework.data.neo4j.core.mapping.RelationshipDescription;
2125
import org.springframework.data.projection.ProjectionFactory;
2226
import org.springframework.data.projection.ProjectionInformation;
2327
import org.springframework.data.repository.query.ResultProcessor;
2428
import org.springframework.data.repository.query.ReturnedType;
29+
import org.springframework.data.util.TypeInformation;
30+
import org.springframework.lang.Nullable;
2531

2632
import java.beans.PropertyDescriptor;
33+
import java.util.Collection;
2734
import java.util.Collections;
28-
import java.util.HashMap;
29-
import java.util.Map;
35+
import java.util.HashSet;
3036

3137
/**
3238
* This class is responsible for creating a List of {@link PropertyPath} entries that contains all reachable
@@ -35,85 +41,151 @@
3541
@API(status = API.Status.INTERNAL, since = "6.1.3")
3642
public final class PropertyFilterSupport {
3743

38-
public static Map<PropertyPath, Boolean> getInputProperties(ResultProcessor resultProcessor, ProjectionFactory factory,
39-
Neo4jMappingContext mappingContext) {
44+
public static Collection<PropertyFilter.ProjectedPath> getInputProperties(ResultProcessor resultProcessor, ProjectionFactory factory,
45+
Neo4jMappingContext mappingContext) {
4046

4147
ReturnedType returnedType = resultProcessor.getReturnedType();
42-
Map<PropertyPath, Boolean> filteredProperties = new HashMap<>();
48+
Collection<PropertyFilter.ProjectedPath> filteredProperties = new HashSet<>();
4349

4450
boolean isProjecting = returnedType.isProjecting();
4551
boolean isClosedProjection = factory.getProjectionInformation(returnedType.getReturnedType()).isClosed();
4652

4753
if (!isProjecting || !isClosedProjection) {
48-
return Collections.emptyMap();
54+
return Collections.emptySet();
4955
}
5056

5157
for (String inputProperty : returnedType.getInputProperties()) {
5258
addPropertiesFrom(returnedType.getDomainType(), returnedType.getReturnedType(), factory,
53-
filteredProperties, inputProperty, mappingContext);
59+
filteredProperties, new ProjectionPathProcessor(inputProperty, PropertyPath.from(inputProperty, returnedType.getReturnedType()).getLeafProperty().getTypeInformation()), mappingContext);
5460
}
5561

5662
return filteredProperties;
5763
}
5864

59-
static Map<PropertyPath, Boolean> addPropertiesFrom(Class<?> domainType, Class<?> returnType,
60-
ProjectionFactory projectionFactory,
61-
Neo4jMappingContext neo4jMappingContext) {
65+
static Collection<PropertyFilter.ProjectedPath> addPropertiesFrom(Class<?> domainType, Class<?> returnType,
66+
ProjectionFactory projectionFactory,
67+
Neo4jMappingContext neo4jMappingContext) {
6268

6369
ProjectionInformation projectionInformation = projectionFactory.getProjectionInformation(returnType);
64-
Map<PropertyPath, Boolean> propertyPaths = new HashMap<>();
70+
Collection<PropertyFilter.ProjectedPath> propertyPaths = new HashSet<>();
71+
Neo4jPersistentEntity<?> domainEntity = neo4jMappingContext.getRequiredPersistentEntity(domainType);
72+
6573
for (PropertyDescriptor inputProperty : projectionInformation.getInputProperties()) {
66-
addPropertiesFrom(domainType, returnType, projectionFactory, propertyPaths, inputProperty.getName(), neo4jMappingContext);
74+
TypeInformation<?> typeInformation = null;
75+
if (projectionInformation.isClosed()) {
76+
typeInformation = PropertyPath.from(inputProperty.getName(), returnType).getTypeInformation();
77+
} else {
78+
// try to figure out the right property by name
79+
for (GraphPropertyDescription graphProperty : domainEntity.getGraphProperties()) {
80+
if (graphProperty.getPropertyName().equals(inputProperty.getName())) {
81+
typeInformation = domainEntity.getPersistentProperty(graphProperty.getFieldName()).getTypeInformation();
82+
break;
83+
}
84+
}
85+
// it could still be null for relationships
86+
if (typeInformation == null) {
87+
for (RelationshipDescription relationshipDescription : domainEntity.getRelationships()) {
88+
if (relationshipDescription.getFieldName().equals(inputProperty.getName())) {
89+
typeInformation = domainEntity.getPersistentProperty(relationshipDescription.getFieldName()).getTypeInformation();
90+
break;
91+
}
92+
}
93+
}
94+
}
95+
addPropertiesFrom(domainType, returnType, projectionFactory, propertyPaths, new ProjectionPathProcessor(inputProperty.getName(), typeInformation), neo4jMappingContext);
6796
}
6897
return propertyPaths;
6998
}
7099

71100
private static void addPropertiesFrom(Class<?> domainType, Class<?> returnedType, ProjectionFactory factory,
72-
Map<PropertyPath, Boolean> filteredProperties, String inputProperty,
101+
Collection<PropertyFilter.ProjectedPath> filteredProperties, ProjectionPathProcessor projectionPathProcessor,
73102
Neo4jMappingContext mappingContext) {
74103

75104
ProjectionInformation projectionInformation = factory.getProjectionInformation(returnedType);
76-
PropertyPath propertyPath;
105+
PropertyFilter.RelaxedPropertyPath propertyPath;
77106

78107
// If this is a closed projection we can assume that the return type (possible projection type) contains
79108
// only fields accessible with a property path.
80109
if (projectionInformation.isClosed()) {
81-
propertyPath = PropertyPath.from(inputProperty, returnedType);
110+
propertyPath = PropertyFilter.RelaxedPropertyPath.withRootType(returnedType).append(projectionPathProcessor.path);
82111
} else {
83112
// otherwise the domain type is used right from the start
84-
propertyPath = PropertyPath.from(inputProperty, domainType);
113+
propertyPath = PropertyFilter.RelaxedPropertyPath.withRootType(domainType).append(projectionPathProcessor.path);
114+
}
115+
116+
Class<?> propertyType = projectionPathProcessor.typeInformation.getType();
117+
TypeInformation<?> currentTypeInformation = projectionPathProcessor.typeInformation.getActualType();
118+
if (projectionPathProcessor.typeInformation.isMap()) {
119+
// deep inspection into the map to look for the related entity type.
120+
TypeInformation<?> mapValueType = projectionPathProcessor.typeInformation.getRequiredMapValueType();
121+
if (mapValueType.isCollectionLike()) {
122+
currentTypeInformation = projectionPathProcessor.typeInformation.getRequiredMapValueType().getComponentType();
123+
propertyType = projectionPathProcessor.typeInformation.getRequiredMapValueType().getComponentType().getType();
124+
} else {
125+
currentTypeInformation = projectionPathProcessor.typeInformation.getRequiredMapValueType();
126+
propertyType = projectionPathProcessor.typeInformation.getRequiredMapValueType().getType();
127+
}
128+
} else if (projectionPathProcessor.typeInformation.isCollectionLike()) {
129+
currentTypeInformation = projectionPathProcessor.typeInformation.getComponentType();
130+
propertyType = projectionPathProcessor.typeInformation.getComponentType().getType();
85131
}
86132

87-
Class<?> propertyType = propertyPath.getLeafType();
88133
// 1. Simple types can be added directly
89134
// 2. Something that looks like an entity needs to get processed as such
90135
// 3. Embedded projection
91136
if (mappingContext.getConversionService().isSimpleType(propertyType)) {
92-
filteredProperties.put(propertyPath, false);
137+
filteredProperties.add(new PropertyFilter.ProjectedPath(propertyPath, false));
93138
} else if (mappingContext.hasPersistentEntityFor(propertyType)) {
94-
filteredProperties.put(propertyPath, true);
139+
filteredProperties.add(new PropertyFilter.ProjectedPath(propertyPath, true));
95140
} else {
96141
ProjectionInformation nestedProjectionInformation = factory.getProjectionInformation(propertyType);
97142
// Closed projection should get handled as above (recursion)
98143
if (nestedProjectionInformation.isClosed()) {
99-
filteredProperties.put(propertyPath, false);
144+
filteredProperties.add(new PropertyFilter.ProjectedPath(propertyPath, false));
100145
for (PropertyDescriptor nestedInputProperty : nestedProjectionInformation.getInputProperties()) {
101-
PropertyPath nestedPropertyPath = propertyPath.nested(nestedInputProperty.getName());
102-
if (propertyPath.hasNext() && (domainType.equals(propertyPath.getLeafProperty().getOwningType().getType())
103-
|| returnedType.equals(propertyPath.getLeafProperty().getOwningType().getType()))) {
146+
TypeInformation<?> typeInformation = currentTypeInformation.getProperty(nestedInputProperty.getName());
147+
ProjectionPathProcessor nextProjectionPathProcessor = projectionPathProcessor.next(nestedInputProperty, typeInformation);
148+
149+
if (projectionPathProcessor.isChildLevel() && (domainType.equals(nextProjectionPathProcessor.typeInformation.getType())
150+
|| returnedType.equals(nextProjectionPathProcessor.typeInformation.getType()))) {
104151
break;
105152
}
106153

107154
addPropertiesFrom(domainType, returnedType, factory, filteredProperties,
108-
nestedPropertyPath.toDotPath(), mappingContext);
155+
nextProjectionPathProcessor, mappingContext);
109156
}
110157
} else {
111158
// An open projection at this place needs to get replaced with the matching (real) entity
112159
// Use domain type as root type for the property path
113-
PropertyPath domainBasedPropertyPath = PropertyPath.from(inputProperty, domainType);
114-
filteredProperties.put(domainBasedPropertyPath, true);
160+
PropertyFilter.RelaxedPropertyPath domainBasedPropertyPath = PropertyFilter.RelaxedPropertyPath.withRootType(domainType).append(projectionPathProcessor.path);
161+
filteredProperties.add(new PropertyFilter.ProjectedPath(domainBasedPropertyPath, true));
115162
}
116163
}
117164
}
118165

166+
private static class ProjectionPathProcessor {
167+
final TypeInformation<?> typeInformation;
168+
final String path;
169+
final String name;
170+
171+
private ProjectionPathProcessor(String name, String path, @Nullable TypeInformation<?> typeInformation) {
172+
this.typeInformation = typeInformation;
173+
this.path = path;
174+
this.name = name;
175+
}
176+
177+
private ProjectionPathProcessor(String name, @Nullable TypeInformation<?> typeInformation) {
178+
this(name, name, typeInformation);
179+
}
180+
181+
public ProjectionPathProcessor next(PropertyDescriptor nextProperty, TypeInformation<?> nextTypeInformation) {
182+
String nextPropertyName = nextProperty.getName();
183+
return new ProjectionPathProcessor(nextPropertyName, path + "." + nextPropertyName, nextTypeInformation);
184+
}
185+
186+
public boolean isChildLevel() {
187+
return path.contains(".");
188+
}
189+
}
190+
119191
}

src/main/java/org/springframework/data/neo4j/core/ReactiveNeo4jTemplate.java

+8-8
Original file line numberDiff line numberDiff line change
@@ -326,7 +326,7 @@ private Object convertIdValues(@Nullable Neo4jPersistentProperty idProperty, Obj
326326
@Override
327327
public <T> Mono<T> save(T instance) {
328328

329-
return saveImpl(instance, Collections.emptyMap(), null);
329+
return saveImpl(instance, Collections.emptySet(), null);
330330
}
331331

332332
@Override
@@ -353,7 +353,7 @@ public <T, R> Mono<R> saveAs(T instance, Class<R> resultType) {
353353
}
354354

355355
ProjectionInformation projectionInformation = projectionFactory.getProjectionInformation(resultType);
356-
Map<PropertyPath, Boolean> pps = PropertyFilterSupport.addPropertiesFrom(instance.getClass(), resultType,
356+
Collection<PropertyFilter.ProjectedPath> pps = PropertyFilterSupport.addPropertiesFrom(instance.getClass(), resultType,
357357
projectionFactory, neo4jMappingContext);
358358

359359
Mono<T> savingPublisher = saveImpl(instance, pps, null);
@@ -379,7 +379,7 @@ <T, R> Flux<R> doSave(Iterable<R> instances, Class<T> domainType) {
379379

380380
Class<?> resultType = TemplateSupport.findCommonElementType(instances);
381381

382-
Map<PropertyPath, Boolean> pps = PropertyFilterSupport.addPropertiesFrom(domainType, resultType,
382+
Collection<PropertyFilter.ProjectedPath> pps = PropertyFilterSupport.addPropertiesFrom(domainType, resultType,
383383
projectionFactory, neo4jMappingContext);
384384

385385
NestedRelationshipProcessingStateMachine stateMachine = new NestedRelationshipProcessingStateMachine(neo4jMappingContext);
@@ -395,7 +395,7 @@ <T, R> Flux<R> doSave(Iterable<R> instances, Class<T> domainType) {
395395
});
396396
}
397397

398-
private <T> Mono<T> saveImpl(T instance, @Nullable Map<PropertyPath, Boolean> includedProperties, @Nullable NestedRelationshipProcessingStateMachine stateMachine) {
398+
private <T> Mono<T> saveImpl(T instance, @Nullable Collection<PropertyFilter.ProjectedPath> includedProperties, @Nullable NestedRelationshipProcessingStateMachine stateMachine) {
399399

400400
if (stateMachine != null && stateMachine.hasProcessedValue(instance)) {
401401
return Mono.just(instance);
@@ -475,7 +475,7 @@ private <T> Mono<Tuple2<T, DynamicLabels>> determineDynamicLabels(T entityToBeSa
475475

476476
@Override
477477
public <T> Flux<T> saveAll(Iterable<T> instances) {
478-
return saveAllImpl(instances, Collections.emptyMap(), null);
478+
return saveAllImpl(instances, Collections.emptySet(), null);
479479
}
480480

481481
@Override
@@ -496,7 +496,7 @@ public <T, R> Flux<R> saveAllAs(Iterable<T> instances, Class<R> resultType) {
496496
}
497497

498498
ProjectionInformation projectionInformation = projectionFactory.getProjectionInformation(resultType);
499-
Map<PropertyPath, Boolean> pps = PropertyFilterSupport.addPropertiesFrom(commonElementType, resultType,
499+
Collection<PropertyFilter.ProjectedPath> pps = PropertyFilterSupport.addPropertiesFrom(commonElementType, resultType,
500500
projectionFactory, neo4jMappingContext);
501501

502502
Flux<T> savedInstances = saveAllImpl(instances, pps, null);
@@ -513,7 +513,7 @@ public <T, R> Flux<R> saveAllAs(Iterable<T> instances, Class<R> resultType) {
513513
}).map(instance -> projectionFactory.createProjection(resultType, instance));
514514
}
515515

516-
private <T> Flux<T> saveAllImpl(Iterable<T> instances, @Nullable Map<PropertyPath, Boolean> includedProperties, @Nullable BiPredicate<PropertyPath, Neo4jPersistentProperty> includeProperty) {
516+
private <T> Flux<T> saveAllImpl(Iterable<T> instances, @Nullable Collection<PropertyFilter.ProjectedPath> includedProperties, @Nullable BiPredicate<PropertyPath, Neo4jPersistentProperty> includeProperty) {
517517

518518
Set<Class<?>> types = new HashSet<>();
519519
List<T> entities = new ArrayList<>();
@@ -529,7 +529,7 @@ private <T> Flux<T> saveAllImpl(Iterable<T> instances, @Nullable Map<PropertyPat
529529
boolean heterogeneousCollection = types.size() > 1;
530530
Class<?> domainClass = types.iterator().next();
531531

532-
Map<PropertyPath, Boolean> pps = includeProperty == null ?
532+
Collection<PropertyFilter.ProjectedPath> pps = includeProperty == null ?
533533
includedProperties :
534534
TemplateSupport.computeIncludedPropertiesFromPredicate(this.neo4jMappingContext, domainClass,
535535
includeProperty);

0 commit comments

Comments
 (0)