Refactor PropertyFilterSupport into EntityProjectionIntrospector.
Original Pull Request: #2420
This commit is contained in:
committed by
Christoph Strobl
parent
9948fefe69
commit
caa8c6afc5
@@ -1,354 +0,0 @@
|
||||
/*
|
||||
* 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.beans.PropertyDescriptor;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
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;
|
||||
import org.springframework.data.mapping.PropertyPath;
|
||||
import org.springframework.data.projection.ProjectionFactory;
|
||||
import org.springframework.data.projection.ProjectionInformation;
|
||||
import org.springframework.data.util.Pair;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* This class is introspects the returned type in the context of a domain type for all reachable properties (w/o cycles)
|
||||
* to determine which property paths are subject to projection.
|
||||
*
|
||||
* @author Gerrit Meier
|
||||
* @author Mark Paluch
|
||||
* @since 2.7
|
||||
*/
|
||||
public class EntityProjectionDiscoverer {
|
||||
|
||||
private final ProjectionFactory projectionFactory;
|
||||
private final ProjectionPredicate projectionPredicate;
|
||||
private final MappingContext<?, ?> mappingContext;
|
||||
|
||||
private EntityProjectionDiscoverer(ProjectionFactory projectionFactory, ProjectionPredicate projectionPredicate,
|
||||
MappingContext<?, ?> mappingContext) {
|
||||
this.projectionFactory = projectionFactory;
|
||||
this.projectionPredicate = projectionPredicate;
|
||||
this.mappingContext = mappingContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link EntityProjectionDiscoverer} given {@link ProjectionFactory}, {@link ProjectionPredicate} and
|
||||
* {@link MappingContext}.
|
||||
*
|
||||
* @param projectionFactory must not be {@literal null}.
|
||||
* @param projectionPredicate must not be {@literal null}.
|
||||
* @param mappingContext must not be {@literal null}.
|
||||
* @return a new {@link EntityProjectionDiscoverer} instance.
|
||||
*/
|
||||
public static EntityProjectionDiscoverer create(ProjectionFactory projectionFactory,
|
||||
ProjectionPredicate projectionPredicate, MappingContext<?, ?> mappingContext) {
|
||||
|
||||
Assert.notNull(projectionFactory, "ProjectionFactory must not be null");
|
||||
Assert.notNull(projectionPredicate, "ProjectionPredicate must not be null");
|
||||
Assert.notNull(mappingContext, "MappingContext must not be null");
|
||||
|
||||
return new EntityProjectionDiscoverer(projectionFactory, projectionPredicate, mappingContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Introspect a {@link Class return type} in the context of a {@link Class domain type} whether the returned type is a
|
||||
* projection and what property paths are participating in the projection.
|
||||
* <p>
|
||||
* Nested properties (direct types, within maps, collections) are introspected for nested projections and contain
|
||||
* property paths for closed projections.
|
||||
*
|
||||
* @param returnType
|
||||
* @param domainType
|
||||
* @return
|
||||
*/
|
||||
public ReturnedTypeDescriptor introspectReturnType(Class<?> returnType, Class<?> domainType) {
|
||||
|
||||
boolean isProjection = projectionPredicate.test(returnType, domainType);
|
||||
|
||||
if (!isProjection) {
|
||||
return ReturnedTypeDescriptor.nonProjecting(returnType, domainType, Collections.emptyList());
|
||||
}
|
||||
|
||||
ProjectionInformation projectionInformation = projectionFactory.getProjectionInformation(returnType);
|
||||
|
||||
if (!projectionInformation.isClosed()) {
|
||||
return ReturnedTypeDescriptor.projecting(returnType, domainType, Collections.emptyList());
|
||||
}
|
||||
|
||||
Set<Pair<Class<?>, Class<?>>> cycleGuard = new HashSet<>();
|
||||
|
||||
PersistentEntity<?, ?> persistentEntity = mappingContext.getRequiredPersistentEntity(domainType);
|
||||
List<PropertyProjectionDescriptor> propertyDescriptors = getProperties(null, projectionInformation,
|
||||
persistentEntity, cycleGuard);
|
||||
|
||||
return ReturnedTypeDescriptor.projecting(returnType, domainType, propertyDescriptors);
|
||||
}
|
||||
|
||||
private List<PropertyProjectionDescriptor> getProperties(@Nullable PropertyPath propertyPath,
|
||||
ProjectionInformation projectionInformation, PersistentEntity<?, ?> persistentEntity,
|
||||
Set<Pair<Class<?>, Class<?>>> cycleGuard) {
|
||||
|
||||
List<PropertyProjectionDescriptor> propertyDescriptors = new ArrayList<>();
|
||||
for (PropertyDescriptor inputProperty : projectionInformation.getInputProperties()) {
|
||||
|
||||
PersistentProperty<?> persistentProperty = persistentEntity.getPersistentProperty(inputProperty.getName());
|
||||
|
||||
if (persistentProperty == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Class<?> returnedType = inputProperty.getPropertyType();
|
||||
Class<?> domainType = persistentProperty.getActualType();
|
||||
|
||||
PropertyPath nestedPropertyPath = propertyPath == null
|
||||
? PropertyPath.from(persistentProperty.getName(), persistentEntity.getTypeInformation())
|
||||
: propertyPath.nested(persistentProperty.getName());
|
||||
|
||||
if (projectionPredicate.test(returnedType, domainType)) {
|
||||
|
||||
List<PropertyProjectionDescriptor> nestedPropertyDescriptors;
|
||||
|
||||
if (cycleGuard.add(Pair.of(returnedType, domainType))) {
|
||||
nestedPropertyDescriptors = getProjectedProperties(nestedPropertyPath, returnedType, domainType, cycleGuard);
|
||||
} else {
|
||||
nestedPropertyDescriptors = Collections.emptyList();
|
||||
}
|
||||
|
||||
propertyDescriptors.add(PropertyProjectionDescriptor.projecting(nestedPropertyPath, returnedType, domainType,
|
||||
nestedPropertyDescriptors));
|
||||
} else {
|
||||
propertyDescriptors
|
||||
.add(PropertyProjectionDescriptor.nonProjecting(nestedPropertyPath, returnedType, domainType));
|
||||
}
|
||||
}
|
||||
|
||||
return propertyDescriptors;
|
||||
}
|
||||
|
||||
private List<PropertyProjectionDescriptor> getProjectedProperties(PropertyPath propertyPath, Class<?> returnedType,
|
||||
Class<?> domainType, Set<Pair<Class<?>, Class<?>>> cycleGuard) {
|
||||
|
||||
ProjectionInformation projectionInformation = projectionFactory.getProjectionInformation(returnedType);
|
||||
PersistentEntity<?, ?> persistentEntity = mappingContext.getRequiredPersistentEntity(domainType);
|
||||
|
||||
// Closed projection should get handled as above (recursion)
|
||||
return projectionInformation.isClosed()
|
||||
? getProperties(propertyPath, projectionInformation, persistentEntity, cycleGuard)
|
||||
: Collections.emptyList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Descriptor for a top-level return type.
|
||||
*/
|
||||
public static class ReturnedTypeDescriptor {
|
||||
|
||||
private final Class<?> returnedType;
|
||||
private final Class<?> domainType;
|
||||
private final List<PropertyProjectionDescriptor> nested;
|
||||
private final boolean projecting;
|
||||
|
||||
ReturnedTypeDescriptor(Class<?> returnedType, Class<?> domainType, List<PropertyProjectionDescriptor> nested,
|
||||
boolean projecting) {
|
||||
this.domainType = domainType;
|
||||
this.returnedType = returnedType;
|
||||
this.nested = nested;
|
||||
this.projecting = projecting;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a projecting variant of a return type.
|
||||
*
|
||||
* @param returnedType
|
||||
* @param domainType
|
||||
* @param nested
|
||||
* @return
|
||||
*/
|
||||
public static ReturnedTypeDescriptor projecting(Class<?> returnedType, Class<?> domainType,
|
||||
List<PropertyProjectionDescriptor> nested) {
|
||||
return new ReturnedTypeDescriptor(returnedType, domainType, nested, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a non-projecting variant of a return type.
|
||||
*
|
||||
* @param returnedType
|
||||
* @param domainType
|
||||
* @param nested
|
||||
* @return
|
||||
*/
|
||||
public static ReturnedTypeDescriptor nonProjecting(Class<?> returnedType, Class<?> domainType,
|
||||
List<PropertyProjectionDescriptor> nested) {
|
||||
return new ReturnedTypeDescriptor(returnedType, domainType, nested, false);
|
||||
}
|
||||
|
||||
public Class<?> getDomainType() {
|
||||
return domainType;
|
||||
}
|
||||
|
||||
public Class<?> getReturnedType() {
|
||||
return returnedType;
|
||||
}
|
||||
|
||||
public boolean isProjecting() {
|
||||
return projecting;
|
||||
}
|
||||
|
||||
List<PropertyProjectionDescriptor> getNested() {
|
||||
return nested;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 (PropertyProjectionDescriptor descriptor : nested) {
|
||||
|
||||
if (descriptor.getNested().isEmpty()) {
|
||||
action.accept(descriptor.getPropertyPath());
|
||||
} else {
|
||||
descriptor.forEach(action);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
|
||||
if (isProjecting()) {
|
||||
return String.format("Projection(%s AS %s): %s", getDomainType().getName(), getReturnedType().getName(),
|
||||
nested);
|
||||
}
|
||||
|
||||
return String.format("Domain(%s): %s", getReturnedType().getName(), nested);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Descriptor for a property-level type along its potential projection.
|
||||
*/
|
||||
public static class PropertyProjectionDescriptor extends ReturnedTypeDescriptor {
|
||||
|
||||
private final PropertyPath propertyPath;
|
||||
|
||||
PropertyProjectionDescriptor(PropertyPath propertyPath, Class<?> returnedType, Class<?> domainType,
|
||||
List<PropertyProjectionDescriptor> nested, boolean projecting) {
|
||||
super(returnedType, domainType, nested, projecting);
|
||||
this.propertyPath = propertyPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a projecting variant of a return type.
|
||||
*
|
||||
* @param propertyPath
|
||||
* @param returnedType
|
||||
* @param domainType
|
||||
* @param nested
|
||||
* @return
|
||||
*/
|
||||
public static PropertyProjectionDescriptor projecting(PropertyPath propertyPath, Class<?> returnedType,
|
||||
Class<?> domainType, List<PropertyProjectionDescriptor> nested) {
|
||||
return new PropertyProjectionDescriptor(propertyPath, returnedType, domainType, nested, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a non-projecting variant of a return type.
|
||||
*
|
||||
* @param propertyPath
|
||||
* @param returnedType
|
||||
* @param domainType
|
||||
* @return
|
||||
*/
|
||||
public static PropertyProjectionDescriptor nonProjecting(PropertyPath propertyPath, Class<?> returnedType,
|
||||
Class<?> domainType) {
|
||||
return new PropertyProjectionDescriptor(propertyPath, returnedType, domainType, Collections.emptyList(), false);
|
||||
}
|
||||
|
||||
public PropertyPath getPropertyPath() {
|
||||
return propertyPath;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("%s AS %s", propertyPath.toDotPath(), getReturnedType().getName());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a predicate (boolean-valued function) of a {@link Class target type} and its {@link Class underlying
|
||||
* type}.
|
||||
*/
|
||||
public interface ProjectionPredicate {
|
||||
|
||||
/**
|
||||
* Evaluates this predicate on the given arguments.
|
||||
*
|
||||
* @param target the target type.
|
||||
* @param target the underlying type.
|
||||
* @return {@code true} if the input argument matches the predicate, otherwise {@code false}.
|
||||
*/
|
||||
boolean test(Class<?> target, Class<?> underlyingType);
|
||||
|
||||
/**
|
||||
* Return a composed predicate that represents a short-circuiting logical AND of this predicate and another. When
|
||||
* evaluating the composed predicate, if this predicate is {@code false}, then the {@code other} predicate is not
|
||||
* evaluated.
|
||||
* <p>
|
||||
* Any exceptions thrown during evaluation of either predicate are relayed to the caller; if evaluation of this
|
||||
* predicate throws an exception, the {@code other} predicate will not be evaluated.
|
||||
*
|
||||
* @param other a predicate that will be logically-ANDed with this predicate
|
||||
* @return a composed predicate that represents the short-circuiting logical AND of this predicate and the
|
||||
* {@code other} predicate
|
||||
*/
|
||||
default ProjectionPredicate and(ProjectionPredicate other) {
|
||||
return (target, underlyingType) -> test(target, underlyingType) && other.test(target, underlyingType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a predicate that represents the logical negation of this predicate.
|
||||
*
|
||||
* @return a predicate that represents the logical negation of this predicate
|
||||
*/
|
||||
default ProjectionPredicate negate() {
|
||||
return (target, underlyingType) -> !test(target, underlyingType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a predicate that considers whether the {@code target type} is participating in the type hierarchy.
|
||||
*/
|
||||
static ProjectionPredicate typeHierarchy() {
|
||||
|
||||
ProjectionPredicate predicate = (target, underlyingType) -> target.isAssignableFrom(underlyingType) || // hierarchy
|
||||
underlyingType.isAssignableFrom(target);
|
||||
return predicate.negate();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,445 @@
|
||||
/*
|
||||
* 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.beans.PropertyDescriptor;
|
||||
import java.util.ArrayList;
|
||||
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;
|
||||
import org.springframework.data.mapping.PropertyPath;
|
||||
import org.springframework.data.projection.ProjectionFactory;
|
||||
import org.springframework.data.projection.ProjectionInformation;
|
||||
import org.springframework.data.util.ClassTypeInformation;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* This class is introspects the returned type in the context of a domain type for all reachable properties (w/o cycles)
|
||||
* to determine which property paths are subject to projection.
|
||||
*
|
||||
* @author Gerrit Meier
|
||||
* @author Mark Paluch
|
||||
* @since 2.7
|
||||
*/
|
||||
public class EntityProjectionIntrospector {
|
||||
|
||||
private final ProjectionFactory projectionFactory;
|
||||
private final ProjectionPredicate projectionPredicate;
|
||||
private final MappingContext<?, ?> mappingContext;
|
||||
|
||||
private EntityProjectionIntrospector(ProjectionFactory projectionFactory, ProjectionPredicate projectionPredicate,
|
||||
MappingContext<?, ?> mappingContext) {
|
||||
this.projectionFactory = projectionFactory;
|
||||
this.projectionPredicate = projectionPredicate;
|
||||
this.mappingContext = mappingContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link EntityProjectionIntrospector} given {@link ProjectionFactory}, {@link ProjectionPredicate} and
|
||||
* {@link MappingContext}.
|
||||
*
|
||||
* @param projectionFactory must not be {@literal null}.
|
||||
* @param projectionPredicate must not be {@literal null}.
|
||||
* @param mappingContext must not be {@literal null}.
|
||||
* @return a new {@link EntityProjectionIntrospector} instance.
|
||||
*/
|
||||
public static EntityProjectionIntrospector create(ProjectionFactory projectionFactory,
|
||||
ProjectionPredicate projectionPredicate, MappingContext<?, ?> mappingContext) {
|
||||
|
||||
Assert.notNull(projectionFactory, "ProjectionFactory must not be null");
|
||||
Assert.notNull(projectionPredicate, "ProjectionPredicate must not be null");
|
||||
Assert.notNull(mappingContext, "MappingContext must not be null");
|
||||
|
||||
return new EntityProjectionIntrospector(projectionFactory, projectionPredicate, mappingContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Introspect a {@link Class mapped type} in the context of a {@link Class domain type} whether the returned type is a
|
||||
* projection and what property paths are participating in the projection.
|
||||
* <p>
|
||||
* Nested properties (direct types, within maps, collections) are introspected for nested projections and contain
|
||||
* property paths for closed projections.
|
||||
*
|
||||
* @param mappedType
|
||||
* @param domainType
|
||||
* @return
|
||||
*/
|
||||
public <M, D> EntityProjection<M, D> introspect(Class<M> mappedType, Class<D> domainType) {
|
||||
|
||||
ClassTypeInformation<M> returnedTypeInformation = ClassTypeInformation.from(mappedType);
|
||||
ClassTypeInformation<D> domainTypeInformation = ClassTypeInformation.from(domainType);
|
||||
|
||||
boolean isProjection = projectionPredicate.test(mappedType, domainType);
|
||||
|
||||
if (!isProjection) {
|
||||
return EntityProjection.nonProjecting(returnedTypeInformation, domainTypeInformation, Collections.emptyList());
|
||||
}
|
||||
|
||||
ProjectionInformation projectionInformation = projectionFactory.getProjectionInformation(mappedType);
|
||||
|
||||
if (!projectionInformation.isClosed()) {
|
||||
return EntityProjection.projecting(returnedTypeInformation, domainTypeInformation, Collections.emptyList(),
|
||||
false);
|
||||
}
|
||||
|
||||
|
||||
PersistentEntity<?, ?> persistentEntity = mappingContext.getRequiredPersistentEntity(domainType);
|
||||
List<PropertyProjection<?, ?>> propertyDescriptors = getProperties(null, projectionInformation,
|
||||
returnedTypeInformation,
|
||||
persistentEntity, null);
|
||||
|
||||
return EntityProjection.projecting(returnedTypeInformation, domainTypeInformation, propertyDescriptors, true);
|
||||
}
|
||||
|
||||
|
||||
private List<PropertyProjection<?, ?>> getProperties(@Nullable PropertyPath propertyPath,
|
||||
ProjectionInformation projectionInformation, TypeInformation<?> projectionTypeInformation,
|
||||
PersistentEntity<?, ?> persistentEntity, @Nullable CycleGuard cycleGuard) {
|
||||
|
||||
List<PropertyProjection<?, ?>> propertyDescriptors = new ArrayList<>();
|
||||
for (PropertyDescriptor inputProperty : projectionInformation.getInputProperties()) {
|
||||
|
||||
PersistentProperty<?> persistentProperty = persistentEntity.getPersistentProperty(inputProperty.getName());
|
||||
|
||||
if (persistentProperty == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
CycleGuard cycleGuardToUse = cycleGuard != null ? cycleGuard : new CycleGuard();
|
||||
|
||||
TypeInformation<?> property = projectionTypeInformation.getRequiredProperty(inputProperty.getName());
|
||||
|
||||
PropertyPath nestedPropertyPath = propertyPath == null
|
||||
? PropertyPath.from(persistentProperty.getName(), persistentEntity.getTypeInformation())
|
||||
: propertyPath.nested(persistentProperty.getName());
|
||||
|
||||
TypeInformation<?> returnedType = property.getRequiredActualType();
|
||||
TypeInformation<?> domainType = persistentProperty.getTypeInformation().getRequiredActualType();
|
||||
|
||||
if (isProjection(returnedType, domainType)) {
|
||||
|
||||
List<PropertyProjection<?, ?>> nestedPropertyDescriptors;
|
||||
|
||||
if (cycleGuardToUse.isCycleFree(persistentProperty)) {
|
||||
nestedPropertyDescriptors = getProjectedProperties(nestedPropertyPath, returnedType, domainType,
|
||||
cycleGuardToUse);
|
||||
} else {
|
||||
nestedPropertyDescriptors = Collections.emptyList();
|
||||
}
|
||||
|
||||
propertyDescriptors.add(PropertyProjection.projecting(nestedPropertyPath, property,
|
||||
persistentProperty.getTypeInformation(),
|
||||
nestedPropertyDescriptors, projectionInformation.isClosed()));
|
||||
} else {
|
||||
propertyDescriptors
|
||||
.add(PropertyProjection.nonProjecting(nestedPropertyPath, property,
|
||||
persistentProperty.getTypeInformation()));
|
||||
}
|
||||
}
|
||||
|
||||
return propertyDescriptors;
|
||||
}
|
||||
|
||||
private boolean isProjection(TypeInformation<?> returnedType, TypeInformation<?> domainType) {
|
||||
return projectionPredicate.test(returnedType.getRequiredActualType().getType(),
|
||||
domainType.getRequiredActualType().getType());
|
||||
}
|
||||
|
||||
private List<PropertyProjection<?, ?>> getProjectedProperties(PropertyPath propertyPath,
|
||||
TypeInformation<?> returnedType, TypeInformation<?> domainType, CycleGuard cycleGuard) {
|
||||
|
||||
ProjectionInformation projectionInformation = projectionFactory.getProjectionInformation(returnedType.getType());
|
||||
PersistentEntity<?, ?> persistentEntity = mappingContext.getRequiredPersistentEntity(domainType);
|
||||
|
||||
// Closed projection should get handled as above (recursion)
|
||||
return projectionInformation.isClosed()
|
||||
? getProperties(propertyPath, projectionInformation, returnedType, persistentEntity, cycleGuard)
|
||||
: 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 <M> the mapped type acting as view onto the domain type.
|
||||
* @param <D> the domain type.
|
||||
*/
|
||||
public static class EntityProjection<M, D> {
|
||||
|
||||
private final TypeInformation<M> mappedType;
|
||||
private final TypeInformation<D> domainType;
|
||||
private final List<PropertyProjection<?, ?>> properties;
|
||||
private final boolean projection;
|
||||
private final boolean closedProjection;
|
||||
|
||||
EntityProjection(TypeInformation<M> mappedType, TypeInformation<D> domainType,
|
||||
List<PropertyProjection<?, ?>> 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 <M, D> EntityProjection<M, D> projecting(TypeInformation<M> mappedType, TypeInformation<D> domainType,
|
||||
List<PropertyProjection<?, ?>> 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 <M, D> EntityProjection<M, D> nonProjecting(TypeInformation<M> mappedType,
|
||||
TypeInformation<D> domainType,
|
||||
List<PropertyProjection<?, ?>> properties) {
|
||||
return new EntityProjection<>(mappedType, domainType, properties, false, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the mapped type used by this type view.
|
||||
*/
|
||||
public TypeInformation<M> 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<D> 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<PropertyProjection<?, ?>> 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<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}.
|
||||
*
|
||||
* @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 <M> the mapped type acting as view onto the domain type.
|
||||
* @param <D> the domain type.
|
||||
*/
|
||||
public static class PropertyProjection<M, D> extends EntityProjection<M, D> {
|
||||
|
||||
private final PropertyPath propertyPath;
|
||||
|
||||
PropertyProjection(PropertyPath propertyPath, TypeInformation<M> mappedType, TypeInformation<D> domainType,
|
||||
List<PropertyProjection<?, ?>> 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 <M, D> PropertyProjection<M, D> projecting(PropertyPath propertyPath, TypeInformation<M> mappedType,
|
||||
TypeInformation<D> domainType, List<PropertyProjection<?, ?>> 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 <M, D> PropertyProjection<M, D> nonProjecting(PropertyPath propertyPath,
|
||||
TypeInformation<M> mappedType,
|
||||
TypeInformation<D> 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}.
|
||||
*/
|
||||
public interface ProjectionPredicate {
|
||||
|
||||
/**
|
||||
* Evaluates this predicate on the given arguments.
|
||||
*
|
||||
* @param target the target type.
|
||||
* @param target the underlying type.
|
||||
* @return {@code true} if the input argument matches the predicate, otherwise {@code false}.
|
||||
*/
|
||||
boolean test(Class<?> target, Class<?> underlyingType);
|
||||
|
||||
/**
|
||||
* Return a composed predicate that represents a short-circuiting logical AND of this predicate and another. When
|
||||
* evaluating the composed predicate, if this predicate is {@code false}, then the {@code other} predicate is not
|
||||
* evaluated.
|
||||
* <p>
|
||||
* Any exceptions thrown during evaluation of either predicate are relayed to the caller; if evaluation of this
|
||||
* predicate throws an exception, the {@code other} predicate will not be evaluated.
|
||||
*
|
||||
* @param other a predicate that will be logically-ANDed with this predicate
|
||||
* @return a composed predicate that represents the short-circuiting logical AND of this predicate and the
|
||||
* {@code other} predicate
|
||||
*/
|
||||
default ProjectionPredicate and(ProjectionPredicate other) {
|
||||
return (target, underlyingType) -> test(target, underlyingType) && other.test(target, underlyingType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a predicate that represents the logical negation of this predicate.
|
||||
*
|
||||
* @return a predicate that represents the logical negation of this predicate
|
||||
*/
|
||||
default ProjectionPredicate negate() {
|
||||
return (target, underlyingType) -> !test(target, underlyingType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a predicate that considers whether the {@code target type} is participating in the type hierarchy.
|
||||
*/
|
||||
static ProjectionPredicate typeHierarchy() {
|
||||
|
||||
ProjectionPredicate predicate = (target, underlyingType) -> target.isAssignableFrom(underlyingType) || // hierarchy
|
||||
underlyingType.isAssignableFrom(target);
|
||||
return predicate.negate();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class CycleGuard {
|
||||
Set<PersistentProperty<?>> seen = new LinkedHashSet<>();
|
||||
|
||||
public boolean isCycleFree(PersistentProperty<?> property) {
|
||||
return seen.add(property);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user