Add support for properties using deep map-in-map/list-in-map nesting.
Original Pull Request: #2420
This commit is contained in:
committed by
Christoph Strobl
parent
0744580766
commit
a63774e6ec
@@ -15,12 +15,16 @@
|
||||
*/
|
||||
package org.springframework.data.mapping.context;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.springframework.data.mapping.PropertyPath;
|
||||
import org.springframework.data.util.ClassTypeInformation;
|
||||
import org.springframework.data.util.Streamable;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
@@ -32,7 +36,7 @@ import org.springframework.lang.Nullable;
|
||||
* @param <D> the domain type.
|
||||
* @since 2.7
|
||||
*/
|
||||
public class EntityProjection<M, D> {
|
||||
public class EntityProjection<M, D> implements Streamable<EntityProjection.PropertyProjection<?, ?>> {
|
||||
|
||||
private final TypeInformation<M> mappedType;
|
||||
private final TypeInformation<D> domainType;
|
||||
@@ -86,6 +90,34 @@ public class EntityProjection<M, D> {
|
||||
return new EntityProjection<>(typeInformation, typeInformation, Collections.emptyList(), false, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the given action for each element of the {@link Streamable} recursively until all elements of the graph
|
||||
* have been processed or the action throws an exception. Unless otherwise specified by the implementing class,
|
||||
* actions are performed in the order of iteration (if an iteration order is specified). Exceptions thrown by the
|
||||
* action are relayed to the caller.
|
||||
*
|
||||
* @param action
|
||||
*/
|
||||
public void forEachRecursive(Consumer<? super PropertyProjection<?, ?>> action) {
|
||||
|
||||
for (PropertyProjection<?, ?> descriptor : properties) {
|
||||
|
||||
if (descriptor instanceof ContainerPropertyProjection) {
|
||||
action.accept(descriptor);
|
||||
descriptor.forEachRecursive(action);
|
||||
} else if (descriptor.getProperties().isEmpty()) {
|
||||
action.accept(descriptor);
|
||||
} else {
|
||||
descriptor.forEachRecursive(action);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<PropertyProjection<?, ?>> iterator() {
|
||||
return properties.iterator();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the mapped type used by this type view.
|
||||
*/
|
||||
@@ -134,24 +166,6 @@ public class EntityProjection<M, D> {
|
||||
return properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the given {@code action} for each element of the {@code ReturnedTypeDescriptor} until all elements have
|
||||
* been processed or the action throws an exception.
|
||||
*
|
||||
* @param action the action to be performed for each element
|
||||
*/
|
||||
public void forEach(Consumer<PropertyPath> action) {
|
||||
|
||||
for (PropertyProjection<?, ?> descriptor : properties) {
|
||||
|
||||
if (descriptor.getProperties().isEmpty()) {
|
||||
action.accept(descriptor.getPropertyPath());
|
||||
} else {
|
||||
descriptor.forEach(action);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a {@link EntityProjection} for a property identified by {@code name}.
|
||||
*
|
||||
@@ -236,5 +250,53 @@ public class EntityProjection<M, D> {
|
||||
public String toString() {
|
||||
return String.format("%s AS %s", propertyPath.toDotPath(), getActualMappedType().getType().getName());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Descriptor for a property-level type along its potential projection that is held within a {@link Collection}-like
|
||||
* or {@link Map}-like container. Property paths within containers use the deeply unwrapped actual type of the
|
||||
* container as root type and as they cannot be tied immediately to the root entity.
|
||||
*
|
||||
* @param <M> the mapped type acting as view onto the domain type.
|
||||
* @param <D> the domain type.
|
||||
*/
|
||||
public static class ContainerPropertyProjection<M, D> extends PropertyProjection<M, D> {
|
||||
|
||||
ContainerPropertyProjection(PropertyPath propertyPath, TypeInformation<M> mappedType, TypeInformation<D> domainType,
|
||||
List<PropertyProjection<?, ?>> properties, boolean projecting, boolean closedProjection) {
|
||||
super(propertyPath, mappedType, domainType, properties, projecting, closedProjection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a projecting variant of a mapped type.
|
||||
*
|
||||
* @param propertyPath
|
||||
* @param mappedType
|
||||
* @param domainType
|
||||
* @param properties
|
||||
* @return
|
||||
*/
|
||||
public static <M, D> ContainerPropertyProjection<M, D> projecting(PropertyPath propertyPath,
|
||||
TypeInformation<M> mappedType, TypeInformation<D> domainType, List<PropertyProjection<?, ?>> properties,
|
||||
boolean closedProjection) {
|
||||
return new ContainerPropertyProjection<>(propertyPath, mappedType, domainType, properties, true,
|
||||
closedProjection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a non-projecting variant of a mapped type.
|
||||
*
|
||||
* @param propertyPath
|
||||
* @param mappedType
|
||||
* @param domainType
|
||||
* @return
|
||||
*/
|
||||
public static <M, D> ContainerPropertyProjection<M, D> nonProjecting(PropertyPath propertyPath,
|
||||
TypeInformation<M> mappedType, TypeInformation<D> domainType) {
|
||||
return new ContainerPropertyProjection<>(propertyPath, mappedType, domainType, Collections.emptyList(), false,
|
||||
false);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,10 +78,14 @@ public class EntityProjectionIntrospector {
|
||||
* <p>
|
||||
* Nested properties (direct types, within maps, collections) are introspected for nested projections and contain
|
||||
* property paths for closed projections.
|
||||
* <p>
|
||||
* Deeply nested types (e.g. {@code Map<?, List<Person>>}) are represented with a property path that uses
|
||||
* the unwrapped type and no longer the root domain type {@code D}.
|
||||
*
|
||||
* @param mappedType
|
||||
* @param domainType
|
||||
* @return
|
||||
* @param mappedType must not be {@literal null}.
|
||||
* @param domainType must not be {@literal null}.
|
||||
* @return the introspection result.
|
||||
* @see org.springframework.data.mapping.context.EntityProjection.ContainerPropertyProjection
|
||||
*/
|
||||
public <M, D> EntityProjection<M, D> introspect(Class<M> mappedType, Class<D> domainType) {
|
||||
|
||||
@@ -103,8 +107,7 @@ public class EntityProjectionIntrospector {
|
||||
|
||||
PersistentEntity<?, ?> persistentEntity = mappingContext.getRequiredPersistentEntity(domainType);
|
||||
List<EntityProjection.PropertyProjection<?, ?>> propertyDescriptors = getProperties(null, projectionInformation,
|
||||
returnedTypeInformation,
|
||||
persistentEntity, null);
|
||||
returnedTypeInformation, persistentEntity, null);
|
||||
|
||||
return EntityProjection.projecting(returnedTypeInformation, domainTypeInformation, propertyDescriptors, true);
|
||||
}
|
||||
@@ -127,44 +130,71 @@ public class EntityProjectionIntrospector {
|
||||
CycleGuard cycleGuardToUse = cycleGuard != null ? cycleGuard : new CycleGuard();
|
||||
|
||||
TypeInformation<?> property = projectionTypeInformation.getRequiredProperty(inputProperty.getName());
|
||||
TypeInformation<?> actualType = property.getRequiredActualType();
|
||||
|
||||
boolean container = isContainer(actualType);
|
||||
|
||||
PropertyPath nestedPropertyPath = propertyPath == null
|
||||
? PropertyPath.from(persistentProperty.getName(), persistentEntity.getTypeInformation())
|
||||
: propertyPath.nested(persistentProperty.getName());
|
||||
|
||||
TypeInformation<?> returnedType = property.getRequiredActualType();
|
||||
TypeInformation<?> domainType = persistentProperty.getTypeInformation().getRequiredActualType();
|
||||
TypeInformation<?> unwrappedReturnedType = unwrapContainerType(property.getRequiredActualType());
|
||||
TypeInformation<?> unwrappedDomainType = unwrapContainerType(
|
||||
persistentProperty.getTypeInformation().getRequiredActualType());
|
||||
|
||||
if (isProjection(returnedType, domainType)) {
|
||||
if (isProjection(unwrappedReturnedType, unwrappedDomainType)) {
|
||||
|
||||
List<EntityProjection.PropertyProjection<?, ?>> nestedPropertyDescriptors;
|
||||
|
||||
if (cycleGuardToUse.isCycleFree(persistentProperty)) {
|
||||
nestedPropertyDescriptors = getProjectedProperties(nestedPropertyPath, returnedType, domainType,
|
||||
cycleGuardToUse);
|
||||
nestedPropertyDescriptors = getProjectedProperties(container ? null : nestedPropertyPath,
|
||||
unwrappedReturnedType, unwrappedDomainType, cycleGuardToUse);
|
||||
} else {
|
||||
nestedPropertyDescriptors = Collections.emptyList();
|
||||
}
|
||||
|
||||
propertyDescriptors.add(EntityProjection.PropertyProjection.projecting(nestedPropertyPath, property,
|
||||
persistentProperty.getTypeInformation(),
|
||||
nestedPropertyDescriptors, projectionInformation.isClosed()));
|
||||
if (container) {
|
||||
propertyDescriptors.add(EntityProjection.ContainerPropertyProjection.projecting(nestedPropertyPath, property,
|
||||
persistentProperty.getTypeInformation(), nestedPropertyDescriptors, projectionInformation.isClosed()));
|
||||
} else {
|
||||
propertyDescriptors.add(EntityProjection.PropertyProjection.projecting(nestedPropertyPath, property,
|
||||
persistentProperty.getTypeInformation(), nestedPropertyDescriptors, projectionInformation.isClosed()));
|
||||
}
|
||||
|
||||
} else {
|
||||
propertyDescriptors
|
||||
.add(EntityProjection.PropertyProjection.nonProjecting(nestedPropertyPath, property,
|
||||
persistentProperty.getTypeInformation()));
|
||||
if (container) {
|
||||
propertyDescriptors.add(EntityProjection.ContainerPropertyProjection.nonProjecting(nestedPropertyPath,
|
||||
property, persistentProperty.getTypeInformation()));
|
||||
} else {
|
||||
propertyDescriptors.add(EntityProjection.PropertyProjection.nonProjecting(nestedPropertyPath, property,
|
||||
persistentProperty.getTypeInformation()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return propertyDescriptors;
|
||||
}
|
||||
|
||||
private static TypeInformation<?> unwrapContainerType(TypeInformation<?> type) {
|
||||
|
||||
TypeInformation<?> unwrapped = type;
|
||||
while (isContainer(unwrapped)) {
|
||||
unwrapped = unwrapped.getRequiredActualType();
|
||||
}
|
||||
|
||||
return unwrapped;
|
||||
}
|
||||
|
||||
private static boolean isContainer(TypeInformation<?> actualType) {
|
||||
return actualType.isCollectionLike() || actualType.isMap();
|
||||
}
|
||||
|
||||
private boolean isProjection(TypeInformation<?> returnedType, TypeInformation<?> domainType) {
|
||||
return projectionPredicate.test(returnedType.getRequiredActualType().getType(),
|
||||
domainType.getRequiredActualType().getType());
|
||||
}
|
||||
|
||||
private List<EntityProjection.PropertyProjection<?, ?>> getProjectedProperties(PropertyPath propertyPath,
|
||||
private List<EntityProjection.PropertyProjection<?, ?>> getProjectedProperties(@Nullable PropertyPath propertyPath,
|
||||
TypeInformation<?> returnedType, TypeInformation<?> domainType, CycleGuard cycleGuard) {
|
||||
|
||||
ProjectionInformation projectionInformation = projectionFactory.getProjectionInformation(returnedType.getType());
|
||||
|
||||
Reference in New Issue
Block a user