3 changed files with 510 additions and 396 deletions
@ -1,354 +0,0 @@
@@ -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 @@
@@ -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); |
||||
} |
||||
} |
||||
|
||||
} |
||||
Loading…
Reference in new issue