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.
This commit is contained in:
Michael Simons
2024-06-19 12:48:35 +02:00
parent a471701b4e
commit f974fefbd5
5 changed files with 109 additions and 6 deletions

View File

@@ -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

View File

@@ -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;

View File

@@ -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 <T> The crux of this class
*/
@RelationshipProperties
public class Properties<T> {
@Id
@GeneratedValue
Long id;
@TargetNode
T target;
}

View File

@@ -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> target;
}

View File

@@ -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 {
}