diff --git a/src/main/java/org/springframework/data/neo4j/core/mapping/DefaultNeo4jPersistentProperty.java b/src/main/java/org/springframework/data/neo4j/core/mapping/DefaultNeo4jPersistentProperty.java index 0e0d9bd13..1d27b11e7 100644 --- a/src/main/java/org/springframework/data/neo4j/core/mapping/DefaultNeo4jPersistentProperty.java +++ b/src/main/java/org/springframework/data/neo4j/core/mapping/DefaultNeo4jPersistentProperty.java @@ -16,8 +16,10 @@ package org.springframework.data.neo4j.core.mapping; import java.lang.reflect.Field; +import java.lang.reflect.ParameterizedType; import java.util.Optional; +import org.springframework.core.ResolvableType; import org.springframework.data.annotation.ReadOnlyProperty; import org.springframework.data.mapping.Association; import org.springframework.data.mapping.MappingException; @@ -116,8 +118,8 @@ final class DefaultNeo4jPersistentProperty extends AnnotationBasedPersistentProp Neo4jPersistentEntity relationshipPropertiesClass = null; if (this.hasActualTypeAnnotation(RelationshipProperties.class)) { - Class type = getRelationshipPropertiesTargetType(getActualType()); - obverseOwner = this.mappingContext.addPersistentEntity(TypeInformation.of(type)).get(); + TypeInformation typeInformation = getRelationshipPropertiesTargetType(getActualType()); + obverseOwner = this.mappingContext.addPersistentEntity(typeInformation).get(); relationshipPropertiesClass = this.mappingContext.addPersistentEntity(TypeInformation.of(getActualType())).get(); } else { Class associationTargetType = this.getAssociationTargetType(); @@ -135,8 +137,8 @@ final class DefaultNeo4jPersistentProperty extends AnnotationBasedPersistentProp mapValueType.getType().isAnnotationPresent(RelationshipProperties.class); if (relationshipPropertiesCollection) { - Class type = getRelationshipPropertiesTargetType(mapValueType.getActualType().getType()); - obverseOwner = this.mappingContext.addPersistentEntity(TypeInformation.of(type)).get(); + TypeInformation typeInformation = getRelationshipPropertiesTargetType(mapValueType.getActualType().getType()); + obverseOwner = this.mappingContext.addPersistentEntity(typeInformation).get(); relationshipPropertiesClass = this.mappingContext .addPersistentEntity(mapValueType.getComponentType()).get(); @@ -178,7 +180,7 @@ final class DefaultNeo4jPersistentProperty extends AnnotationBasedPersistentProp } @NonNull - private Class getRelationshipPropertiesTargetType(Class relationshipPropertiesType) { + private TypeInformation getRelationshipPropertiesTargetType(Class relationshipPropertiesType) { Field targetNodeField = ReflectionUtils.findField(relationshipPropertiesType, field -> field.isAnnotationPresent(TargetNode.class)); @@ -187,7 +189,11 @@ final class DefaultNeo4jPersistentProperty extends AnnotationBasedPersistentProp throw new MappingException("Missing @TargetNode declaration in " + relationshipPropertiesType); } TypeInformation relationshipPropertiesTypeInformation = TypeInformation.of(relationshipPropertiesType); - return relationshipPropertiesTypeInformation.getProperty(targetNodeField.getName()).getType(); + Class type = relationshipPropertiesTypeInformation.getProperty(targetNodeField.getName()).getType(); + if (Object.class == type && this.getField().getGenericType() instanceof ParameterizedType pt && pt.getActualTypeArguments().length == 1) { + return TypeInformation.of(ResolvableType.forType(pt.getActualTypeArguments()[0])); + } + return TypeInformation.of(type); } @Override diff --git a/src/test/java/org/springframework/data/neo4j/core/mapping/Neo4jMappingContextTest.java b/src/test/java/org/springframework/data/neo4j/core/mapping/Neo4jMappingContextTest.java index b86543424..d8e659ef2 100644 --- a/src/test/java/org/springframework/data/neo4j/core/mapping/Neo4jMappingContextTest.java +++ b/src/test/java/org/springframework/data/neo4j/core/mapping/Neo4jMappingContextTest.java @@ -59,6 +59,8 @@ import org.springframework.data.neo4j.core.mapping.datagraph1446.R1; import org.springframework.data.neo4j.core.mapping.datagraph1446.R2; import org.springframework.data.neo4j.core.mapping.datagraph1448.A_S3; import org.springframework.data.neo4j.core.mapping.datagraph1448.RelatedThing; +import org.springframework.data.neo4j.core.mapping.genericRelProperties.Source; +import org.springframework.data.neo4j.core.mapping.genericRelProperties.Target; import org.springframework.data.neo4j.core.mapping.gh2574.Model; import org.springframework.data.neo4j.core.schema.CompositeProperty; import org.springframework.data.neo4j.core.schema.GeneratedValue; @@ -682,6 +684,16 @@ class Neo4jMappingContextTest { assertThat(children).containsExactly("A2", "A3", "A4"); } + @Test // GH-2911 + void shouldDealWithGenericRelationshipProperties() { + Neo4jMappingContext schema = new Neo4jMappingContext(); + Neo4jPersistentEntity persistentEntity = schema.getPersistentEntity(Source.class); + assertThat(persistentEntity.getRelationships()).hasSize(1).first().satisfies(r -> { + var target = r.getTarget(); + assertThat(target.getUnderlyingClass()).isEqualTo(Target.class); + }); + } + static class EntityWithPostLoadMethods { String m1; diff --git a/src/test/java/org/springframework/data/neo4j/core/mapping/genericRelProperties/Properties.java b/src/test/java/org/springframework/data/neo4j/core/mapping/genericRelProperties/Properties.java new file mode 100644 index 000000000..4fadd696a --- /dev/null +++ b/src/test/java/org/springframework/data/neo4j/core/mapping/genericRelProperties/Properties.java @@ -0,0 +1,36 @@ +/* + * Copyright 2011-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.neo4j.core.mapping.genericRelProperties; + +import org.springframework.data.neo4j.core.schema.GeneratedValue; +import org.springframework.data.neo4j.core.schema.Id; +import org.springframework.data.neo4j.core.schema.RelationshipProperties; +import org.springframework.data.neo4j.core.schema.TargetNode; + +/** + * @author Michael J. Simons + * @param The crux of this class + */ +@RelationshipProperties +public class Properties { + + @Id + @GeneratedValue + Long id; + + @TargetNode + T target; +} diff --git a/src/test/java/org/springframework/data/neo4j/core/mapping/genericRelProperties/Source.java b/src/test/java/org/springframework/data/neo4j/core/mapping/genericRelProperties/Source.java new file mode 100644 index 000000000..c625f1b7d --- /dev/null +++ b/src/test/java/org/springframework/data/neo4j/core/mapping/genericRelProperties/Source.java @@ -0,0 +1,27 @@ +/* + * Copyright 2011-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.neo4j.core.mapping.genericRelProperties; + +import org.springframework.data.neo4j.core.schema.Relationship; + +/** + * @author Michael J. Simons + */ +public class Source { + + @Relationship("IS_RELATED_TO") + private Properties target; +} diff --git a/src/test/java/org/springframework/data/neo4j/core/mapping/genericRelProperties/Target.java b/src/test/java/org/springframework/data/neo4j/core/mapping/genericRelProperties/Target.java new file mode 100644 index 000000000..3dbed6480 --- /dev/null +++ b/src/test/java/org/springframework/data/neo4j/core/mapping/genericRelProperties/Target.java @@ -0,0 +1,22 @@ +/* + * Copyright 2011-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.neo4j.core.mapping.genericRelProperties; + +/** + * @author Michael J. Simons + */ +public class Target { +}