From 519ac959abc3544829be5b6c31bfd7e217d42278 Mon Sep 17 00:00:00 2001 From: Oliver Drotbohm Date: Wed, 31 Mar 2021 07:51:02 +0200 Subject: [PATCH] Support for association target type detection for jMolecules. We now detect the component type of jMolecules' Association as association target type in AbstractPersistentProperty. AnnotationBasedPersistentProperty keeps the lookup of the explicit @Reference around but falls back to the Association analysis for that. @Reference is now only considered by AnnotationBasedPersistentProperty. It was previously also (erroneously) considered by AbstractPersistentProperty. It was also changed to return `null` in case no association target type could be looked up instead of returning the sole property's type in case that is an entity. This was done to satisfy the behavior documented on the interface but also to avoid the call to isEntity() during the calculation which might use association information in turn and thus lead to a stack overflow. --- .../model/AbstractPersistentProperty.java | 29 +++++++++++++-- .../AnnotationBasedPersistentProperty.java | 25 +++++++------ .../AbstractPersistentPropertyUnitTests.java | 14 ++++--- ...ationBasedPersistentPropertyUnitTests.java | 37 +++++++++++++++---- 4 files changed, 76 insertions(+), 29 deletions(-) diff --git a/src/main/java/org/springframework/data/mapping/model/AbstractPersistentProperty.java b/src/main/java/org/springframework/data/mapping/model/AbstractPersistentProperty.java index 321503ad97..49618a8a2a 100644 --- a/src/main/java/org/springframework/data/mapping/model/AbstractPersistentProperty.java +++ b/src/main/java/org/springframework/data/mapping/model/AbstractPersistentProperty.java @@ -22,7 +22,6 @@ import java.util.Map; import java.util.Optional; -import org.springframework.data.annotation.Reference; import org.springframework.data.mapping.Association; import org.springframework.data.mapping.PersistentEntity; import org.springframework.data.mapping.PersistentProperty; @@ -64,6 +63,9 @@ public abstract class AbstractPersistentProperty

private final Lazy usePropertyAccess; private final Lazy>> entityTypeInformation; + private final Lazy isAssociation; + private final Lazy> associationTargetType; + private final Method getter; private final Method setter; private final Field field; @@ -86,6 +88,15 @@ public AbstractPersistentProperty(Property property, PersistentEntity owne this.hashCode = Lazy.of(property::hashCode); this.usePropertyAccess = Lazy.of(() -> owner.getType().isInterface() || CAUSE_FIELD.equals(getField())); + this.isAssociation = Lazy.of(() -> ASSOCIATION_TYPE != null && ASSOCIATION_TYPE.isAssignableFrom(rawType)); + this.associationTargetType = ASSOCIATION_TYPE == null + ? Lazy.empty() + : Lazy.of(() -> Optional.of(getTypeInformation()) + .map(it -> it.getSuperTypeInformation(ASSOCIATION_TYPE)) + .map(TypeInformation::getComponentType) + .map(TypeInformation::getType) + .orElse(null)); + this.entityTypeInformation = Lazy.of(() -> Optional.ofNullable(information.getActualType())// .filter(it -> !simpleTypeHolder.isSimpleType(it.getType()))// .filter(it -> !it.isCollectionLike())// @@ -170,6 +181,7 @@ public Iterable> getPersistentEntityTypes() { * (non-Javadoc) * @see org.springframework.data.mapping.PersistentProperty#getGetter() */ + @Nullable @Override public Method getGetter() { return this.getter; @@ -179,6 +191,7 @@ public Method getGetter() { * (non-Javadoc) * @see org.springframework.data.mapping.PersistentProperty#getSetter() */ + @Nullable @Override public Method getSetter() { return this.setter; @@ -188,6 +201,7 @@ public Method getSetter() { * (non-Javadoc) * @see org.springframework.data.mapping.PersistentProperty#getWither() */ + @Nullable @Override public Method getWither() { return this.wither; @@ -245,8 +259,7 @@ public boolean isImmutable() { */ @Override public boolean isAssociation() { - return isAnnotationPresent(Reference.class) // - || ASSOCIATION_TYPE != null && ASSOCIATION_TYPE.isAssignableFrom(rawType); + return isAssociation.get(); } /* @@ -259,6 +272,16 @@ public Association

getAssociation() { return association.orElse(null); } + /* + * (non-Javadoc) + * @see org.springframework.data.mapping.PersistentProperty#getAssociationTargetType() + */ + @Nullable + @Override + public Class getAssociationTargetType() { + return associationTargetType.getNullable(); + } + /* * (non-Javadoc) * @see org.springframework.data.mapping.PersistentProperty#isCollectionLike() diff --git a/src/main/java/org/springframework/data/mapping/model/AnnotationBasedPersistentProperty.java b/src/main/java/org/springframework/data/mapping/model/AnnotationBasedPersistentProperty.java index b7eb902e8e..ffca2689ee 100644 --- a/src/main/java/org/springframework/data/mapping/model/AnnotationBasedPersistentProperty.java +++ b/src/main/java/org/springframework/data/mapping/model/AnnotationBasedPersistentProperty.java @@ -74,6 +74,18 @@ public abstract class AnnotationBasedPersistentProperty

isId = Lazy.of(() -> isAnnotationPresent(Id.class)); private final Lazy isVersion = Lazy.of(() -> isAnnotationPresent(Version.class)); + private final Lazy> associationTargetType = Lazy.of(() -> { + + if (!isAssociation()) { + return null; + } + + return Optional.of(Reference.class) // + .map(this::findAnnotation) // + .map(Reference::to) // + .map(it -> !Class.class.equals(it) ? it : getActualType()) // + .orElseGet(() -> super.getAssociationTargetType()); + }); /** * Creates a new {@link AnnotationBasedPersistentProperty}. @@ -285,18 +297,7 @@ public boolean usePropertyAccess() { @Nullable @Override public Class getAssociationTargetType() { - - Reference reference = findAnnotation(Reference.class); - - if (reference == null) { - return isEntity() ? getActualType() : null; - } - - Class targetType = reference.to(); - - return Class.class.equals(targetType) // - ? isEntity() ? getActualType() : null // - : targetType; + return associationTargetType.getNullable(); } /* diff --git a/src/test/java/org/springframework/data/mapping/model/AbstractPersistentPropertyUnitTests.java b/src/test/java/org/springframework/data/mapping/model/AbstractPersistentPropertyUnitTests.java index e17b8ec7b0..06cd5300bf 100755 --- a/src/test/java/org/springframework/data/mapping/model/AbstractPersistentPropertyUnitTests.java +++ b/src/test/java/org/springframework/data/mapping/model/AbstractPersistentPropertyUnitTests.java @@ -32,6 +32,8 @@ import java.util.TreeMap; import java.util.TreeSet; +import org.jmolecules.ddd.types.AggregateRoot; +import org.jmolecules.ddd.types.Identifier; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.data.mapping.Association; @@ -230,6 +232,7 @@ void detectsJMoleculesAssociation() { SamplePersistentProperty property = getProperty(JMolecules.class, "association"); assertThat(property.isAssociation()).isTrue(); + assertThat(property.getAssociationTargetType()).isEqualTo(JMoleculesAggregate.class); } private BasicPersistentEntity getEntity(Class type) { @@ -371,11 +374,6 @@ public boolean isAnnotationPresent(Class annotationType) { public A findPropertyOrOwnerAnnotation(Class annotationType) { return null; } - - @Override - public Class getAssociationTargetType() { - return null; - } } static class Sample { @@ -392,6 +390,10 @@ class TreeMapWrapper { } class JMolecules { - org.jmolecules.ddd.types.Association association; + org.jmolecules.ddd.types.Association association; + } + + interface JMoleculesAggregate extends AggregateRoot { + } } diff --git a/src/test/java/org/springframework/data/mapping/model/AnnotationBasedPersistentPropertyUnitTests.java b/src/test/java/org/springframework/data/mapping/model/AnnotationBasedPersistentPropertyUnitTests.java index 35c8db2a92..badaf11e56 100755 --- a/src/test/java/org/springframework/data/mapping/model/AnnotationBasedPersistentPropertyUnitTests.java +++ b/src/test/java/org/springframework/data/mapping/model/AnnotationBasedPersistentPropertyUnitTests.java @@ -24,11 +24,17 @@ import java.lang.annotation.Target; import java.util.Map; import java.util.Optional; +import java.util.function.Function; import java.util.stream.Stream; +import org.assertj.core.api.ClassAssert; +import org.jmolecules.ddd.types.AggregateRoot; import org.jmolecules.ddd.types.Association; +import org.jmolecules.ddd.types.Identifier; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestFactory; import org.springframework.core.annotation.AliasFor; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.data.annotation.AccessType; @@ -242,12 +248,21 @@ public void getRequiredAnnotationThrowsException() { assertThatThrownBy(() -> property.getRequiredAnnotation(Transient.class)).isInstanceOf(IllegalStateException.class); } - @Test // DATACMNS-1318 - public void detectsUltimateAssociationTargetClass() { + @TestFactory // DATACMNS-1318 + public Stream detectsUltimateAssociationTargetClass() { - Stream.of("toSample", "toSample2", "sample", "withoutAnnotation").forEach(it -> { - assertThat(getProperty(WithReferences.class, it).getAssociationTargetType()).isEqualTo(Sample.class); - }); + Function verifier = it -> assertThat( + getProperty(WithReferences.class, it).getAssociationTargetType()); + + Stream positives = DynamicTest.stream(Stream.of("toSample", "toSample2", "sample"), // + it -> String.format("Property %s resolves to %s.", it, Sample.class.getSimpleName()), // + it -> verifier.apply(it).isEqualTo(Sample.class)); + + Stream negatives = DynamicTest.stream(Stream.of("withoutAnnotation"), // + it -> String.format("Property %s resolves to null.", it), // + it -> verifier.apply(it).isNull()); + + return Stream.concat(positives, negatives); } @Test // DATACMNS-1359 @@ -295,8 +310,12 @@ public void missingRequiredFieldThrowsException() { } @Test // GH-2315 - void detectesJMoleculesAssociation() { - assertThat(getProperty(JMolecules.class, "association").isAssociation()).isTrue(); + void detectsJMoleculesAssociation() { + + SamplePersistentProperty property = getProperty(JMolecules.class, "association"); + + assertThat(property.isAssociation()).isTrue(); + assertThat(property.getAssociationTargetType()).isEqualTo(JMoleculesAggregate.class); } @SuppressWarnings("unchecked") @@ -484,6 +503,8 @@ interface NoField { } static class JMolecules { - Association association; + Association association; } + + interface JMoleculesAggregate extends AggregateRoot {} }