diff --git a/pom.xml b/pom.xml index 9001e043d4..17ce3e93dc 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-commons - 2.6.0-SNAPSHOT + 2.6.0-GH-2390-SNAPSHOT Spring Data Core diff --git a/src/main/java/org/springframework/data/mapping/context/AbstractMappingContext.java b/src/main/java/org/springframework/data/mapping/context/AbstractMappingContext.java index 63f01a3552..625bc7dab0 100644 --- a/src/main/java/org/springframework/data/mapping/context/AbstractMappingContext.java +++ b/src/main/java/org/springframework/data/mapping/context/AbstractMappingContext.java @@ -30,10 +30,10 @@ import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.function.Predicate; import java.util.stream.Collectors; +import java.util.stream.StreamSupport; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - import org.springframework.beans.BeanUtils; import org.springframework.beans.BeansException; import org.springframework.beans.factory.InitializingBean; @@ -61,6 +61,7 @@ import org.springframework.data.spel.ExtensionAwareEvaluationContextProvider; import org.springframework.data.util.ClassTypeInformation; import org.springframework.data.util.KotlinReflectionUtils; +import org.springframework.data.util.NullableWrapperConverters; import org.springframework.data.util.Optionals; import org.springframework.data.util.Streamable; import org.springframework.data.util.TypeInformation; @@ -114,7 +115,8 @@ protected AbstractMappingContext() { this.persistentPropertyPathFactory = new PersistentPropertyPathFactory<>(this); EntityInstantiators instantiators = new EntityInstantiators(); - PersistentPropertyAccessorFactory accessorFactory = NativeDetector.inNativeImage() ? BeanWrapperPropertyAccessorFactory.INSTANCE + PersistentPropertyAccessorFactory accessorFactory = NativeDetector.inNativeImage() + ? BeanWrapperPropertyAccessorFactory.INSTANCE : new ClassGeneratingPropertyAccessorFactory(); this.persistentPropertyAccessorFactory = new InstantiationAwarePropertyAccessorFactory(accessorFactory, @@ -484,6 +486,10 @@ protected boolean shouldCreatePersistentEntityFor(TypeInformation type) { return false; } + if (NullableWrapperConverters.supports(type.getType())) { + return false; + } + return !KotlinDetector.isKotlinType(type.getType()) || KotlinReflectionUtils.isSupportedKotlinClass(type.getType()); } @@ -559,6 +565,7 @@ private void createAndRegisterProperty(Property input) { if (shouldSkipOverrideProperty(property)) { return; } + entity.addPersistentProperty(property); if (property.isAssociation()) { @@ -569,7 +576,9 @@ private void createAndRegisterProperty(Property input) { return; } - property.getPersistentEntityTypes().forEach(AbstractMappingContext.this::addPersistentEntity); + StreamSupport.stream(property.getPersistentEntityTypes().spliterator(), false) // + .filter(AbstractMappingContext.this::shouldCreatePersistentEntityFor) // + .forEach(AbstractMappingContext.this::addPersistentEntity); } protected boolean shouldSkipOverrideProperty(P property) { @@ -651,7 +660,6 @@ private Class getPropertyType(PersistentProperty persistentProperty) { } } - /** * Filter rejecting static fields as well as artificially introduced ones. See * {@link PersistentPropertyFilter#UNMAPPED_PROPERTIES} for details. 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 836984db31..346137f999 100644 --- a/src/main/java/org/springframework/data/mapping/model/AbstractPersistentProperty.java +++ b/src/main/java/org/springframework/data/mapping/model/AbstractPersistentProperty.java @@ -18,11 +18,16 @@ import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; +import java.util.Collection; import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.Set; import java.util.function.Supplier; +import org.springframework.core.GenericTypeResolver; import org.springframework.data.mapping.Association; import org.springframework.data.mapping.PersistentEntity; import org.springframework.data.mapping.PersistentProperty; @@ -64,6 +69,7 @@ public abstract class AbstractPersistentProperty

private final Lazy isAssociation; private final Lazy> associationTargetType; + private final Lazy>> entityTypes; private final Method getter; private final Method setter; @@ -111,6 +117,43 @@ public AbstractPersistentProperty(Property property, PersistentEntity owne } else { this.immutable = false; } + + this.entityTypes = Lazy.of(() -> collectEntityTypes(simpleTypeHolder, information, new LinkedHashSet<>())); + } + + protected Set> collectEntityTypes(SimpleTypeHolder simpleTypeHolder, @Nullable TypeInformation typeInformation, Set> entityTypes) { + + if(typeInformation == null || entityTypes.contains(typeInformation) || simpleTypeHolder.isSimpleType(typeInformation.getType())) { + return entityTypes; + } + + if(typeInformation.isMap()) { + + collectEntityTypes(simpleTypeHolder, typeInformation.getComponentType(), entityTypes); + collectEntityTypes(simpleTypeHolder, typeInformation.getMapValueType(), entityTypes); + return entityTypes; + } + + if(typeInformation.isCollectionLike()) { + + collectEntityTypes(simpleTypeHolder, typeInformation.getComponentType(), entityTypes); + return entityTypes; + } + + if(typeInformation.isNullableWrapper()) { + + collectEntityTypes(simpleTypeHolder, typeInformation.getActualType(), entityTypes); + return entityTypes; + } + + if(ASSOCIATION_TYPE != null && ASSOCIATION_TYPE.isAssignableFrom(typeInformation.getType())) { + + entityTypes.add(getAssociationOrActualType()); + return entityTypes; + } + + entityTypes.add(typeInformation); + return entityTypes; } protected abstract Association

createAssociation(); @@ -167,13 +210,15 @@ public TypeInformation getTypeInformation() { @Override public Iterable> getPersistentEntityTypes() { + if(isMap() || isCollectionLike()) { + return entityTypes.get(); + } + if (!isEntity()) { return Collections.emptySet(); } - TypeInformation result = getAssociationTypeOr(() -> entityTypeInformation.getNullable()); - - return result != null ? Collections.singleton(result) : Collections.emptySet(); + return entityTypes.get(); } /* diff --git a/src/main/java/org/springframework/data/util/TypeDiscoverer.java b/src/main/java/org/springframework/data/util/TypeDiscoverer.java index 6ce897ac4e..141249f423 100644 --- a/src/main/java/org/springframework/data/util/TypeDiscoverer.java +++ b/src/main/java/org/springframework/data/util/TypeDiscoverer.java @@ -309,6 +309,10 @@ public TypeInformation getActualType() { return getComponentType(); } + if (isNullableWrapper()) { + return getComponentType(); + } + return this; } @@ -384,6 +388,10 @@ protected TypeInformation doGetComponentType() { return getTypeArgument(Iterable.class, 0); } + if(isNullableWrapper()) { + return getTypeArgument(rawType, 0); + } + List> arguments = getTypeArguments(); return arguments.size() > 0 ? arguments.get(0) : null; diff --git a/src/main/java/org/springframework/data/util/TypeInformation.java b/src/main/java/org/springframework/data/util/TypeInformation.java index 014ffad0ea..17ec425d36 100644 --- a/src/main/java/org/springframework/data/util/TypeInformation.java +++ b/src/main/java/org/springframework/data/util/TypeInformation.java @@ -273,4 +273,14 @@ default TypeInformation getRequiredSuperTypeInformation(Class superType) { default boolean isSubTypeOf(Class type) { return !type.equals(getType()) && type.isAssignableFrom(getType()); } + + /** + * Returns whether the current type is considered a {@literal null} value wrapper or not. + * + * @return {@literal true} if the type is considered to be a {@literal null} value wrapper such as {@link java.util.Optional}. + */ + default boolean isNullableWrapper() { + return NullableWrapperConverters.supports(getType()); + } + } diff --git a/src/test/java/org/springframework/data/mapping/context/AbstractMappingContextUnitTests.java b/src/test/java/org/springframework/data/mapping/context/AbstractMappingContextUnitTests.java index 650afde73c..4e4de0a7d2 100755 --- a/src/test/java/org/springframework/data/mapping/context/AbstractMappingContextUnitTests.java +++ b/src/test/java/org/springframework/data/mapping/context/AbstractMappingContextUnitTests.java @@ -26,11 +26,14 @@ import lombok.Value; import java.time.LocalDateTime; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; +import java.util.Map; +import java.util.Optional; import java.util.TreeMap; import java.util.function.Supplier; @@ -272,6 +275,36 @@ void shouldIgnoreNonAssignableOverridePropertyInSuperClass() { }); } + @Test // GH-2390 + void shouldNotCreatePersistentEntityForOptionalButItsGenericTypeArgument() { + + context.getPersistentEntity(WithOptionals.class); + + assertThat(context.getPersistentEntities()).map(it -> (Class) it.getType()) + .contains(WithOptionals.class, Person.class, Base.class) + .doesNotContain(Optional.class, List.class, ArrayList.class); + } + + @Test // GH-2390 + void shouldNotCreatePersistentEntityForListButItsGenericTypeArgument() { + + context.getPersistentEntity(WithNestedLists.class); + + assertThat(context.getPersistentEntities()).map(it -> (Class) it.getType()) + .contains(Base.class) + .doesNotContain(List.class, ArrayList.class); + } + + @Test // GH-2390 + void shouldNotCreatePersistentEntityForMapButItsGenericTypeArguments() { + + context.getPersistentEntity(WithMap.class); + + assertThat(context.getPersistentEntities()).map(it -> (Class) it.getType()) + .contains(Base.class, Person.class, MapKey.class) + .doesNotContain(List.class, Map.class, String.class, Integer.class); + } + private static void assertHasEntityFor(Class type, SampleMappingContext context, boolean expected) { boolean found = false; @@ -432,4 +465,26 @@ static class ShadowingPropertyAssignable extends ShadowedPropertyAssignable { } } + class WithOptionals { + + Optional optionalOfString; + Optional optionalOfPerson; + List> listOfOptionalOfBase; + } + + class WithNestedLists { + ArrayList> arrayListOfOptionalOfBase; + } + + class MapKey { + + } + + class WithMap { + + Map> mapOfStringToList; + Map mapOfStringToPerson; + Map mapOfKeyToPerson; + } + }