diff --git a/src/main/java/org/springframework/data/mapping/context/EntityProjection.java b/src/main/java/org/springframework/data/mapping/context/EntityProjection.java new file mode 100644 index 000000000..1cdf1c91d --- /dev/null +++ b/src/main/java/org/springframework/data/mapping/context/EntityProjection.java @@ -0,0 +1,240 @@ +/* + * Copyright 2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.mapping.context; + +import java.util.Collections; +import java.util.List; +import java.util.function.Consumer; + +import org.springframework.data.mapping.PropertyPath; +import org.springframework.data.util.ClassTypeInformation; +import org.springframework.data.util.TypeInformation; +import org.springframework.lang.Nullable; + +/** + * Descriptor for a top-level mapped type representing a view onto a domain type structure. The view may exactly match + * the domain type or be a DTO/interface {@link #isProjection() projection}. + * + * @param the mapped type acting as view onto the domain type. + * @param the domain type. + * @since 2.7 + */ +public class EntityProjection { + + private final TypeInformation mappedType; + private final TypeInformation domainType; + private final List> properties; + private final boolean projection; + private final boolean closedProjection; + + EntityProjection(TypeInformation mappedType, TypeInformation domainType, + List> properties, boolean projection, boolean closedProjection) { + this.mappedType = mappedType; + this.domainType = domainType; + this.properties = properties; + this.projection = projection; + this.closedProjection = closedProjection; + } + + /** + * Create a projecting variant of a mapped type. + * + * @param mappedType + * @param domainType + * @param properties + * @return + */ + public static EntityProjection projecting(TypeInformation mappedType, TypeInformation domainType, + List> properties, boolean closedProjection) { + return new EntityProjection<>(mappedType, domainType, properties, true, closedProjection); + } + + /** + * Create a non-projecting variant of a mapped type. + * + * @param mappedType + * @param domainType + * @param properties + * @return + */ + public static EntityProjection nonProjecting(TypeInformation mappedType, + TypeInformation domainType, List> properties) { + return new EntityProjection<>(mappedType, domainType, properties, false, false); + } + + /** + * Create a non-projecting variant of a {@code type}. + * + * @param type + * @return + */ + public static EntityProjection nonProjecting(Class type) { + ClassTypeInformation typeInformation = ClassTypeInformation.from(type); + return new EntityProjection<>(typeInformation, typeInformation, Collections.emptyList(), false, false); + } + + /** + * @return the mapped type used by this type view. + */ + public TypeInformation getMappedType() { + return mappedType; + } + + /** + * @return the actual mapped type used by this type view. Should be used for collection-like and map-like properties + * to determine the actual view type. + */ + public TypeInformation getActualMappedType() { + return mappedType.getRequiredActualType(); + } + + /** + * @return the domain type represented by this type view. + */ + public TypeInformation getDomainType() { + return domainType; + } + + /** + * @return the actual domain type represented by this type view. Should be used for collection-like and map-like + * properties to determine the actual domain type. + */ + public TypeInformation getActualDomainType() { + return domainType.getRequiredActualType(); + } + + /** + * @return {@code true} if the {@link #getMappedType()} is a projection. + */ + public boolean isProjection() { + return projection; + } + + /** + * @return {@code true} if the {@link #getMappedType()} is a closed projection. + */ + public boolean isClosedProjection() { + return isProjection() && closedProjection; + } + + List> getProperties() { + 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 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}. + * + * @param name the property name. + * @return the type view, if the property is known; {@code null} otherwise. + */ + @Nullable + public EntityProjection findProperty(String name) { + + for (PropertyProjection descriptor : properties) { + + if (descriptor.propertyPath.getLeafProperty().getSegment().equals(name)) { + return descriptor; + } + } + + return null; + } + + @Override + public String toString() { + + if (isProjection()) { + return String.format("Projection(%s AS %s): %s", getActualDomainType().getType().getName(), + getActualMappedType().getType().getName(), properties); + } + + return String.format("Domain(%s): %s", getActualDomainType().getType().getName(), properties); + } + + /** + * Descriptor for a property-level type along its potential projection. + * + * @param the mapped type acting as view onto the domain type. + * @param the domain type. + */ + public static class PropertyProjection extends EntityProjection { + + private final PropertyPath propertyPath; + + PropertyProjection(PropertyPath propertyPath, TypeInformation mappedType, TypeInformation domainType, + List> properties, boolean projecting, boolean closedProjection) { + super(mappedType, domainType, properties, projecting, closedProjection); + this.propertyPath = propertyPath; + } + + /** + * Create a projecting variant of a mapped type. + * + * @param propertyPath + * @param mappedType + * @param domainType + * @param properties + * @return + */ + public static PropertyProjection projecting(PropertyPath propertyPath, TypeInformation mappedType, + TypeInformation domainType, List> properties, boolean closedProjection) { + return new PropertyProjection<>(propertyPath, mappedType, domainType, properties, true, closedProjection); + } + + /** + * Create a non-projecting variant of a mapped type. + * + * @param propertyPath + * @param mappedType + * @param domainType + * @return + */ + public static PropertyProjection nonProjecting(PropertyPath propertyPath, + TypeInformation mappedType, TypeInformation domainType) { + return new PropertyProjection<>(propertyPath, mappedType, domainType, Collections.emptyList(), false, false); + } + + /** + * @return the property path representing this property within the root domain type. + */ + public PropertyPath getPropertyPath() { + return propertyPath; + } + + @Override + public String toString() { + return String.format("%s AS %s", propertyPath.toDotPath(), getActualMappedType().getType().getName()); + } + } +} diff --git a/src/main/java/org/springframework/data/mapping/context/EntityProjectionIntrospector.java b/src/main/java/org/springframework/data/mapping/context/EntityProjectionIntrospector.java index bc2991a83..7b03f1c77 100644 --- a/src/main/java/org/springframework/data/mapping/context/EntityProjectionIntrospector.java +++ b/src/main/java/org/springframework/data/mapping/context/EntityProjectionIntrospector.java @@ -21,7 +21,6 @@ import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; -import java.util.function.Consumer; import org.springframework.data.mapping.PersistentEntity; import org.springframework.data.mapping.PersistentProperty; @@ -103,18 +102,18 @@ public class EntityProjectionIntrospector { } PersistentEntity persistentEntity = mappingContext.getRequiredPersistentEntity(domainType); - List> propertyDescriptors = getProperties(null, projectionInformation, + List> propertyDescriptors = getProperties(null, projectionInformation, returnedTypeInformation, persistentEntity, null); return EntityProjection.projecting(returnedTypeInformation, domainTypeInformation, propertyDescriptors, true); } - private List> getProperties(@Nullable PropertyPath propertyPath, + private List> getProperties(@Nullable PropertyPath propertyPath, ProjectionInformation projectionInformation, TypeInformation projectionTypeInformation, PersistentEntity persistentEntity, @Nullable CycleGuard cycleGuard) { - List> propertyDescriptors = new ArrayList<>(); + List> propertyDescriptors = new ArrayList<>(); // TODO: PropertyDescriptor only created for DTO's with getters/setters for (PropertyDescriptor inputProperty : projectionInformation.getInputProperties()) { @@ -138,7 +137,7 @@ public class EntityProjectionIntrospector { if (isProjection(returnedType, domainType)) { - List> nestedPropertyDescriptors; + List> nestedPropertyDescriptors; if (cycleGuardToUse.isCycleFree(persistentProperty)) { nestedPropertyDescriptors = getProjectedProperties(nestedPropertyPath, returnedType, domainType, @@ -147,12 +146,12 @@ public class EntityProjectionIntrospector { nestedPropertyDescriptors = Collections.emptyList(); } - propertyDescriptors.add(PropertyProjection.projecting(nestedPropertyPath, property, + propertyDescriptors.add(EntityProjection.PropertyProjection.projecting(nestedPropertyPath, property, persistentProperty.getTypeInformation(), nestedPropertyDescriptors, projectionInformation.isClosed())); } else { propertyDescriptors - .add(PropertyProjection.nonProjecting(nestedPropertyPath, property, + .add(EntityProjection.PropertyProjection.nonProjecting(nestedPropertyPath, property, persistentProperty.getTypeInformation())); } } @@ -165,7 +164,7 @@ public class EntityProjectionIntrospector { domainType.getRequiredActualType().getType()); } - private List> getProjectedProperties(PropertyPath propertyPath, + private List> getProjectedProperties(PropertyPath propertyPath, TypeInformation returnedType, TypeInformation domainType, CycleGuard cycleGuard) { ProjectionInformation projectionInformation = projectionFactory.getProjectionInformation(returnedType.getType()); @@ -177,223 +176,6 @@ public class EntityProjectionIntrospector { : Collections.emptyList(); } - /** - * Descriptor for a top-level mapped type representing a view onto a domain type structure. The view may exactly match - * the domain type or be a DTO/interface {@link #isProjection() projection}. - * - * @param the mapped type acting as view onto the domain type. - * @param the domain type. - */ - public static class EntityProjection { - - private final TypeInformation mappedType; - private final TypeInformation domainType; - private final List> properties; - private final boolean projection; - private final boolean closedProjection; - - EntityProjection(TypeInformation mappedType, TypeInformation domainType, - List> properties, boolean projection, boolean closedProjection) { - this.mappedType = mappedType; - this.domainType = domainType; - this.properties = properties; - this.projection = projection; - this.closedProjection = closedProjection; - } - - /** - * Create a projecting variant of a mapped type. - * - * @param mappedType - * @param domainType - * @param properties - * @return - */ - public static EntityProjection projecting(TypeInformation mappedType, TypeInformation domainType, - List> properties, boolean closedProjection) { - return new EntityProjection<>(mappedType, domainType, properties, true, closedProjection); - } - - /** - * Create a non-projecting variant of a mapped type. - * - * @param mappedType - * @param domainType - * @param properties - * @return - */ - public static EntityProjection nonProjecting(TypeInformation mappedType, - TypeInformation domainType, - List> properties) { - return new EntityProjection<>(mappedType, domainType, properties, false, false); - } - - /** - * Create a non-projecting variant of a mapped type. - * - * @param mappedType - * @param domainType - * @return - */ - public static EntityProjection nonProjecting(Class type) { - ClassTypeInformation typeInformation = ClassTypeInformation.from(type); - return new EntityProjection<>(typeInformation, typeInformation, Collections.emptyList(), false, false); - } - - /** - * @return the mapped type used by this type view. - */ - public TypeInformation getMappedType() { - return mappedType; - } - - /** - * @return the actual mapped type used by this type view. Should be used for collection-like and map-like properties - * to determine the actual view type. - */ - public TypeInformation getActualMappedType() { - return mappedType.getRequiredActualType(); - } - - /** - * @return the domain type represented by this type view. - */ - public TypeInformation getDomainType() { - return domainType; - } - - /** - * @return the actual domain type represented by this type view. Should be used for collection-like and map-like - * properties to determine the actual domain type. - */ - public TypeInformation getActualDomainType() { - return domainType.getRequiredActualType(); - } - - /** - * @return {@code true} if the {@link #getMappedType()} is a projection. - */ - public boolean isProjection() { - return projection; - } - - /** - * @return {@code true} if the {@link #getMappedType()} is a closed projection. - */ - public boolean isClosedProjection() { - return isProjection() && closedProjection; - } - - List> getProperties() { - 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 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}. - * - * @param name the property name. - * @return the type view, if the property is known; {@code null} otherwise. - */ - @Nullable - public EntityProjection findProperty(String name) { - - for (PropertyProjection descriptor : properties) { - - if (descriptor.propertyPath.getLeafProperty().getSegment().equals(name)) { - return descriptor; - } - } - - return null; - } - - @Override - public String toString() { - - if (isProjection()) { - return String.format("Projection(%s AS %s): %s", getActualDomainType().getType().getName(), - getActualMappedType().getType().getName(), properties); - } - - return String.format("Domain(%s): %s", getActualDomainType().getType().getName(), properties); - } - } - - /** - * Descriptor for a property-level type along its potential projection. - * - * @param the mapped type acting as view onto the domain type. - * @param the domain type. - */ - public static class PropertyProjection extends EntityProjection { - - private final PropertyPath propertyPath; - - PropertyProjection(PropertyPath propertyPath, TypeInformation mappedType, TypeInformation domainType, - List> properties, boolean projecting, boolean closedProjection) { - super(mappedType, domainType, properties, projecting, closedProjection); - this.propertyPath = propertyPath; - } - - /** - * Create a projecting variant of a mapped type. - * - * @param propertyPath - * @param mappedType - * @param domainType - * @param properties - * @return - */ - public static PropertyProjection projecting(PropertyPath propertyPath, TypeInformation mappedType, - TypeInformation domainType, List> properties, boolean closedProjection) { - return new PropertyProjection<>(propertyPath, mappedType, domainType, properties, true, closedProjection); - } - - /** - * Create a non-projecting variant of a mapped type. - * - * @param propertyPath - * @param mappedType - * @param domainType - * @return - */ - public static PropertyProjection nonProjecting(PropertyPath propertyPath, - TypeInformation mappedType, - TypeInformation domainType) { - return new PropertyProjection<>(propertyPath, mappedType, domainType, Collections.emptyList(), false, false); - } - - /** - * @return the property path representing this property within the root domain type. - */ - public PropertyPath getPropertyPath() { - return propertyPath; - } - - @Override - public String toString() { - return String.format("%s AS %s", propertyPath.toDotPath(), getActualMappedType().getType().getName()); - } - } - /** * Represents a predicate (boolean-valued function) of a {@link Class target type} and its {@link Class underlying * type}. diff --git a/src/test/java/org/springframework/data/mapping/context/EntityProjectionIntrospectorUnitTests.java b/src/test/java/org/springframework/data/mapping/context/EntityProjectionIntrospectorUnitTests.java index b7bf46d6b..2b3061d60 100644 --- a/src/test/java/org/springframework/data/mapping/context/EntityProjectionIntrospectorUnitTests.java +++ b/src/test/java/org/springframework/data/mapping/context/EntityProjectionIntrospectorUnitTests.java @@ -25,7 +25,6 @@ import java.util.List; import org.junit.jupiter.api.Test; import org.springframework.data.mapping.PropertyPath; -import org.springframework.data.mapping.context.EntityProjectionIntrospector.EntityProjection; import org.springframework.data.mapping.model.SimpleTypeHolder; import org.springframework.data.projection.SpelAwareProxyProjectionFactory;