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. Reworded the contract of PersistentProperty.getAssociationTargetType() to emphasize the symmetry with ….isAssociation() in implementation classes.

Closes: #2344.
This commit is contained in:
Oliver Drotbohm
2021-03-31 07:51:02 +02:00
parent 85c0ce1450
commit 23ebf3164f
5 changed files with 82 additions and 33 deletions

View File

@@ -383,11 +383,13 @@ public interface PersistentProperty<P extends PersistentProperty<P>> {
}
/**
* Return the type the property refers to in case it's an association.
* Return the type the property refers to in case it's an association, i.e. {@link #isAssociation()} returns
* {@literal true}. That means, that implementations <em>must</em> return a non-{@literal null} value from this method
* in that case. We also recommend to return {@literal null} for non-associations right away to establish symmetry
* between this method and {@link #isAssociation()}.
*
* @return the type the property refers to in case it's an association or {@literal null} in case it's not an
* association, the target entity type is not explicitly defined (either explicitly or through the property
* type itself).
* @return the type the property refers to in case it's an association, i.e. {@link #isAssociation()} returns
* {@literal true}.
* @since 2.1
*/
@Nullable

View File

@@ -22,7 +22,6 @@ import java.util.Collections;
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<P extends PersistentProperty<P>
private final Lazy<Boolean> usePropertyAccess;
private final Lazy<Optional<? extends TypeInformation<?>>> entityTypeInformation;
private final Lazy<Boolean> isAssociation;
private final Lazy<Class<?>> associationTargetType;
private final Method getter;
private final Method setter;
private final Field field;
@@ -86,6 +88,15 @@ public abstract class AbstractPersistentProperty<P extends PersistentProperty<P>
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 abstract class AbstractPersistentProperty<P extends PersistentProperty<P>
* (non-Javadoc)
* @see org.springframework.data.mapping.PersistentProperty#getGetter()
*/
@Nullable
@Override
public Method getGetter() {
return this.getter;
@@ -179,6 +191,7 @@ public abstract class AbstractPersistentProperty<P extends PersistentProperty<P>
* (non-Javadoc)
* @see org.springframework.data.mapping.PersistentProperty#getSetter()
*/
@Nullable
@Override
public Method getSetter() {
return this.setter;
@@ -188,6 +201,7 @@ public abstract class AbstractPersistentProperty<P extends PersistentProperty<P>
* (non-Javadoc)
* @see org.springframework.data.mapping.PersistentProperty#getWither()
*/
@Nullable
@Override
public Method getWither() {
return this.wither;
@@ -245,8 +259,7 @@ public abstract class AbstractPersistentProperty<P extends PersistentProperty<P>
*/
@Override
public boolean isAssociation() {
return isAnnotationPresent(Reference.class) //
|| ASSOCIATION_TYPE != null && ASSOCIATION_TYPE.isAssignableFrom(rawType);
return isAssociation.get();
}
/*
@@ -259,6 +272,16 @@ public abstract class AbstractPersistentProperty<P extends PersistentProperty<P>
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()

View File

@@ -74,6 +74,18 @@ public abstract class AnnotationBasedPersistentProperty<P extends PersistentProp
&& (isAnnotationPresent(Reference.class) || super.isAssociation()));
private final Lazy<Boolean> isId = Lazy.of(() -> isAnnotationPresent(Id.class));
private final Lazy<Boolean> isVersion = Lazy.of(() -> isAnnotationPresent(Version.class));
private final Lazy<Class<?>> 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 abstract class AnnotationBasedPersistentProperty<P extends PersistentProp
@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();
}
/*