Skip to content

Commit 90a5bf5

Browse files
fix: Take generic field type into account for @RelationshipProperties.
This falls back to checking the field type, so that generic relationship properties can be used instead of a concrete implementation. Closes #2911.
1 parent 988a300 commit 90a5bf5

File tree

5 files changed

+109
-6
lines changed

5 files changed

+109
-6
lines changed

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

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,11 @@
1616
package org.springframework.data.neo4j.core.mapping;
1717

1818
import java.lang.reflect.Field;
19+
import java.lang.reflect.ParameterizedType;
1920
import java.util.Collections;
2021
import java.util.Optional;
2122

23+
import org.springframework.core.ResolvableType;
2224
import org.springframework.data.annotation.ReadOnlyProperty;
2325
import org.springframework.data.mapping.Association;
2426
import org.springframework.data.mapping.MappingException;
@@ -117,8 +119,8 @@ protected Association<Neo4jPersistentProperty> createAssociation() {
117119
Neo4jPersistentEntity<?> relationshipPropertiesClass = null;
118120

119121
if (this.hasActualTypeAnnotation(RelationshipProperties.class)) {
120-
Class<?> type = getRelationshipPropertiesTargetType(getActualType());
121-
obverseOwner = this.mappingContext.addPersistentEntity(TypeInformation.of(type)).get();
122+
TypeInformation<?> typeInformation = getRelationshipPropertiesTargetType(getActualType());
123+
obverseOwner = this.mappingContext.addPersistentEntity(typeInformation).get();
122124
relationshipPropertiesClass = this.mappingContext.addPersistentEntity(TypeInformation.of(getActualType())).get();
123125
} else {
124126
Class<?> associationTargetType = this.getAssociationTargetType();
@@ -136,8 +138,8 @@ protected Association<Neo4jPersistentProperty> createAssociation() {
136138
mapValueType.getType().isAnnotationPresent(RelationshipProperties.class);
137139

138140
if (relationshipPropertiesCollection) {
139-
Class<?> type = getRelationshipPropertiesTargetType(mapValueType.getActualType().getType());
140-
obverseOwner = this.mappingContext.addPersistentEntity(TypeInformation.of(type)).get();
141+
TypeInformation<?> typeInformation = getRelationshipPropertiesTargetType(mapValueType.getActualType().getType());
142+
obverseOwner = this.mappingContext.addPersistentEntity(typeInformation).get();
141143
relationshipPropertiesClass = this.mappingContext
142144
.addPersistentEntity(mapValueType.getComponentType()).get();
143145

@@ -179,7 +181,7 @@ protected Association<Neo4jPersistentProperty> createAssociation() {
179181
}
180182

181183
@NonNull
182-
private Class<?> getRelationshipPropertiesTargetType(Class<?> relationshipPropertiesType) {
184+
private TypeInformation<?> getRelationshipPropertiesTargetType(Class<?> relationshipPropertiesType) {
183185

184186
Field targetNodeField = ReflectionUtils.findField(relationshipPropertiesType,
185187
field -> field.isAnnotationPresent(TargetNode.class));
@@ -188,7 +190,11 @@ private Class<?> getRelationshipPropertiesTargetType(Class<?> relationshipProper
188190
throw new MappingException("Missing @TargetNode declaration in " + relationshipPropertiesType);
189191
}
190192
TypeInformation<?> relationshipPropertiesTypeInformation = TypeInformation.of(relationshipPropertiesType);
191-
return relationshipPropertiesTypeInformation.getProperty(targetNodeField.getName()).getType();
193+
Class<?> type = relationshipPropertiesTypeInformation.getProperty(targetNodeField.getName()).getType();
194+
if (Object.class == type && this.getField().getGenericType() instanceof ParameterizedType pt && pt.getActualTypeArguments().length == 1) {
195+
return TypeInformation.of(ResolvableType.forType(pt.getActualTypeArguments()[0]));
196+
}
197+
return TypeInformation.of(type);
192198
}
193199

194200
@Override

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@
5959
import org.springframework.data.neo4j.core.mapping.datagraph1446.R2;
6060
import org.springframework.data.neo4j.core.mapping.datagraph1448.A_S3;
6161
import org.springframework.data.neo4j.core.mapping.datagraph1448.RelatedThing;
62+
import org.springframework.data.neo4j.core.mapping.genericRelProperties.Source;
63+
import org.springframework.data.neo4j.core.mapping.genericRelProperties.Target;
6264
import org.springframework.data.neo4j.core.mapping.gh2574.Model;
6365
import org.springframework.data.neo4j.core.schema.CompositeProperty;
6466
import org.springframework.data.neo4j.core.schema.GeneratedValue;
@@ -682,6 +684,16 @@ void hierarchyMustBeConsistentlyReported() throws ClassNotFoundException {
682684
assertThat(children).containsExactly("A2", "A3", "A4");
683685
}
684686

687+
@Test // GH-2911
688+
void shouldDealWithGenericRelationshipProperties() {
689+
Neo4jMappingContext schema = new Neo4jMappingContext();
690+
Neo4jPersistentEntity<?> persistentEntity = schema.getPersistentEntity(Source.class);
691+
assertThat(persistentEntity.getRelationships()).hasSize(1).first().satisfies(r -> {
692+
var target = r.getTarget();
693+
assertThat(target.getUnderlyingClass()).isEqualTo(Target.class);
694+
});
695+
}
696+
685697
static class EntityWithPostLoadMethods {
686698

687699
String m1;
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Copyright 2011-2024 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.genericRelProperties;
17+
18+
import org.springframework.data.neo4j.core.schema.GeneratedValue;
19+
import org.springframework.data.neo4j.core.schema.Id;
20+
import org.springframework.data.neo4j.core.schema.RelationshipProperties;
21+
import org.springframework.data.neo4j.core.schema.TargetNode;
22+
23+
/**
24+
* @author Michael J. Simons
25+
* @param <T> The crux of this class
26+
*/
27+
@RelationshipProperties
28+
public class Properties<T> {
29+
30+
@Id
31+
@GeneratedValue
32+
String elementId;
33+
34+
@TargetNode
35+
T target;
36+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
* Copyright 2011-2024 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.genericRelProperties;
17+
18+
import org.springframework.data.neo4j.core.schema.Relationship;
19+
20+
/**
21+
* @author Michael J. Simons
22+
*/
23+
public class Source {
24+
25+
@Relationship("IS_RELATED_TO")
26+
private Properties<Target> target;
27+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/*
2+
* Copyright 2011-2024 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.genericRelProperties;
17+
18+
/**
19+
* @author Michael J. Simons
20+
*/
21+
public class Target {
22+
}

0 commit comments

Comments
 (0)