Skip to content

Commit f799a3f

Browse files
committed
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. 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.
1 parent 85c0ce1 commit f799a3f

File tree

4 files changed

+77
-28
lines changed

4 files changed

+77
-28
lines changed

src/main/java/org/springframework/data/mapping/model/AbstractPersistentProperty.java

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,9 @@ public abstract class AbstractPersistentProperty<P extends PersistentProperty<P>
6464
private final Lazy<Boolean> usePropertyAccess;
6565
private final Lazy<Optional<? extends TypeInformation<?>>> entityTypeInformation;
6666

67+
private final Lazy<Boolean> isAssociation;
68+
private final Lazy<Class<?>> associationTargetType;
69+
6770
private final Method getter;
6871
private final Method setter;
6972
private final Field field;
@@ -86,6 +89,16 @@ public AbstractPersistentProperty(Property property, PersistentEntity<?, P> owne
8689
this.hashCode = Lazy.of(property::hashCode);
8790
this.usePropertyAccess = Lazy.of(() -> owner.getType().isInterface() || CAUSE_FIELD.equals(getField()));
8891

92+
this.isAssociation = Lazy.of(() -> isAnnotationPresent(Reference.class) //
93+
|| ASSOCIATION_TYPE != null && ASSOCIATION_TYPE.isAssignableFrom(rawType));
94+
this.associationTargetType = ASSOCIATION_TYPE == null
95+
? Lazy.empty()
96+
: Lazy.of(() -> Optional.of(getTypeInformation())
97+
.map(it -> it.getSuperTypeInformation(ASSOCIATION_TYPE))
98+
.map(TypeInformation::getComponentType)
99+
.map(TypeInformation::getType)
100+
.orElse(null));
101+
89102
this.entityTypeInformation = Lazy.of(() -> Optional.ofNullable(information.getActualType())//
90103
.filter(it -> !simpleTypeHolder.isSimpleType(it.getType()))//
91104
.filter(it -> !it.isCollectionLike())//
@@ -170,6 +183,7 @@ public Iterable<? extends TypeInformation<?>> getPersistentEntityTypes() {
170183
* (non-Javadoc)
171184
* @see org.springframework.data.mapping.PersistentProperty#getGetter()
172185
*/
186+
@Nullable
173187
@Override
174188
public Method getGetter() {
175189
return this.getter;
@@ -179,6 +193,7 @@ public Method getGetter() {
179193
* (non-Javadoc)
180194
* @see org.springframework.data.mapping.PersistentProperty#getSetter()
181195
*/
196+
@Nullable
182197
@Override
183198
public Method getSetter() {
184199
return this.setter;
@@ -188,6 +203,7 @@ public Method getSetter() {
188203
* (non-Javadoc)
189204
* @see org.springframework.data.mapping.PersistentProperty#getWither()
190205
*/
206+
@Nullable
191207
@Override
192208
public Method getWither() {
193209
return this.wither;
@@ -245,8 +261,7 @@ public boolean isImmutable() {
245261
*/
246262
@Override
247263
public boolean isAssociation() {
248-
return isAnnotationPresent(Reference.class) //
249-
|| ASSOCIATION_TYPE != null && ASSOCIATION_TYPE.isAssignableFrom(rawType);
264+
return isAssociation.get();
250265
}
251266

252267
/*
@@ -259,6 +274,16 @@ public Association<P> getAssociation() {
259274
return association.orElse(null);
260275
}
261276

277+
/*
278+
* (non-Javadoc)
279+
* @see org.springframework.data.mapping.PersistentProperty#getAssociationTargetType()
280+
*/
281+
@Nullable
282+
@Override
283+
public Class<?> getAssociationTargetType() {
284+
return associationTargetType.getNullable();
285+
}
286+
262287
/*
263288
* (non-Javadoc)
264289
* @see org.springframework.data.mapping.PersistentProperty#isCollectionLike()

src/main/java/org/springframework/data/mapping/model/AnnotationBasedPersistentProperty.java

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,18 @@ public abstract class AnnotationBasedPersistentProperty<P extends PersistentProp
7474
&& (isAnnotationPresent(Reference.class) || super.isAssociation()));
7575
private final Lazy<Boolean> isId = Lazy.of(() -> isAnnotationPresent(Id.class));
7676
private final Lazy<Boolean> isVersion = Lazy.of(() -> isAnnotationPresent(Version.class));
77+
private final Lazy<Class<?>> associationTargetType = Lazy.of(() -> {
78+
79+
if (!isAssociation()) {
80+
return null;
81+
}
82+
83+
return Optional.of(Reference.class) //
84+
.map(this::findAnnotation) //
85+
.map(Reference::to) //
86+
.map(it -> !Class.class.equals(it) ? it : getActualType()) //
87+
.orElseGet(() -> super.getAssociationTargetType());
88+
});
7789

7890
/**
7991
* Creates a new {@link AnnotationBasedPersistentProperty}.
@@ -285,18 +297,7 @@ public boolean usePropertyAccess() {
285297
@Nullable
286298
@Override
287299
public Class<?> getAssociationTargetType() {
288-
289-
Reference reference = findAnnotation(Reference.class);
290-
291-
if (reference == null) {
292-
return isEntity() ? getActualType() : null;
293-
}
294-
295-
Class<?> targetType = reference.to();
296-
297-
return Class.class.equals(targetType) //
298-
? isEntity() ? getActualType() : null //
299-
: targetType;
300+
return associationTargetType.getNullable();
300301
}
301302

302303
/*

src/test/java/org/springframework/data/mapping/model/AbstractPersistentPropertyUnitTests.java

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
3232
import java.util.TreeMap;
3333
import java.util.TreeSet;
3434

35+
import org.jmolecules.ddd.types.AggregateRoot;
36+
import org.jmolecules.ddd.types.Identifier;
3537
import org.junit.jupiter.api.BeforeEach;
3638
import org.junit.jupiter.api.Test;
3739
import org.springframework.data.mapping.Association;
@@ -230,6 +232,7 @@ void detectsJMoleculesAssociation() {
230232
SamplePersistentProperty property = getProperty(JMolecules.class, "association");
231233

232234
assertThat(property.isAssociation()).isTrue();
235+
assertThat(property.getAssociationTargetType()).isEqualTo(JMoleculesAggregate.class);
233236
}
234237

235238
private <T> BasicPersistentEntity<T, SamplePersistentProperty> getEntity(Class<T> type) {
@@ -371,11 +374,6 @@ public boolean isAnnotationPresent(Class<? extends Annotation> annotationType) {
371374
public <A extends Annotation> A findPropertyOrOwnerAnnotation(Class<A> annotationType) {
372375
return null;
373376
}
374-
375-
@Override
376-
public Class<?> getAssociationTargetType() {
377-
return null;
378-
}
379377
}
380378

381379
static class Sample {
@@ -392,6 +390,10 @@ class TreeMapWrapper {
392390
}
393391

394392
class JMolecules {
395-
org.jmolecules.ddd.types.Association association;
393+
org.jmolecules.ddd.types.Association<JMoleculesAggregate, Identifier> association;
394+
}
395+
396+
interface JMoleculesAggregate extends AggregateRoot<JMoleculesAggregate, Identifier> {
397+
396398
}
397399
}

src/test/java/org/springframework/data/mapping/model/AnnotationBasedPersistentPropertyUnitTests.java

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,17 @@
2424
import java.lang.annotation.Target;
2525
import java.util.Map;
2626
import java.util.Optional;
27+
import java.util.function.Function;
2728
import java.util.stream.Stream;
2829

30+
import org.assertj.core.api.ClassAssert;
31+
import org.jmolecules.ddd.types.AggregateRoot;
2932
import org.jmolecules.ddd.types.Association;
33+
import org.jmolecules.ddd.types.Identifier;
3034
import org.junit.jupiter.api.BeforeEach;
35+
import org.junit.jupiter.api.DynamicTest;
3136
import org.junit.jupiter.api.Test;
37+
import org.junit.jupiter.api.TestFactory;
3238
import org.springframework.core.annotation.AliasFor;
3339
import org.springframework.core.annotation.AnnotationUtils;
3440
import org.springframework.data.annotation.AccessType;
@@ -242,12 +248,21 @@ public void getRequiredAnnotationThrowsException() {
242248
assertThatThrownBy(() -> property.getRequiredAnnotation(Transient.class)).isInstanceOf(IllegalStateException.class);
243249
}
244250

245-
@Test // DATACMNS-1318
246-
public void detectsUltimateAssociationTargetClass() {
251+
@TestFactory // DATACMNS-1318
252+
public Stream<DynamicTest> detectsUltimateAssociationTargetClass() {
247253

248-
Stream.of("toSample", "toSample2", "sample", "withoutAnnotation").forEach(it -> {
249-
assertThat(getProperty(WithReferences.class, it).getAssociationTargetType()).isEqualTo(Sample.class);
250-
});
254+
Function<String, ClassAssert> verifier = it -> assertThat(
255+
getProperty(WithReferences.class, it).getAssociationTargetType());
256+
257+
Stream<DynamicTest> positives = DynamicTest.stream(Stream.of("toSample", "toSample2", "sample"), //
258+
it -> String.format("Property %s resolves to %s.", it, Sample.class.getSimpleName()), //
259+
it -> verifier.apply(it).isEqualTo(Sample.class));
260+
261+
Stream<DynamicTest> negatives = DynamicTest.stream(Stream.of("withoutAnnotation"), //
262+
it -> String.format("Property %s resolves to null.", it), //
263+
it -> verifier.apply(it).isNull());
264+
265+
return Stream.concat(positives, negatives);
251266
}
252267

253268
@Test // DATACMNS-1359
@@ -295,8 +310,12 @@ public void missingRequiredFieldThrowsException() {
295310
}
296311

297312
@Test // GH-2315
298-
void detectesJMoleculesAssociation() {
299-
assertThat(getProperty(JMolecules.class, "association").isAssociation()).isTrue();
313+
void detectsJMoleculesAssociation() {
314+
315+
SamplePersistentProperty property = getProperty(JMolecules.class, "association");
316+
317+
assertThat(property.isAssociation()).isTrue();
318+
assertThat(property.getAssociationTargetType()).isEqualTo(JMoleculesAggregate.class);
300319
}
301320

302321
@SuppressWarnings("unchecked")
@@ -484,6 +503,8 @@ interface NoField {
484503
}
485504

486505
static class JMolecules {
487-
Association association;
506+
Association<JMoleculesAggregate, Identifier> association;
488507
}
508+
509+
interface JMoleculesAggregate extends AggregateRoot<JMoleculesAggregate, Identifier> {}
489510
}

0 commit comments

Comments
 (0)