From 1737c1eaf915a9bb65e441030064837b756d4331 Mon Sep 17 00:00:00 2001 From: Gerrit Meier Date: Mon, 26 Jul 2021 16:53:51 +0200 Subject: [PATCH 1/3] Make SDN class generic. --- .../context/PropertyFilterSupport.java | 185 ++++++++++++++++++ 1 file changed, 185 insertions(+) create mode 100644 src/main/java/org/springframework/data/mapping/context/PropertyFilterSupport.java diff --git a/src/main/java/org/springframework/data/mapping/context/PropertyFilterSupport.java b/src/main/java/org/springframework/data/mapping/context/PropertyFilterSupport.java new file mode 100644 index 0000000000..8a04359c0a --- /dev/null +++ b/src/main/java/org/springframework/data/mapping/context/PropertyFilterSupport.java @@ -0,0 +1,185 @@ +/* + * Copyright 2011-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 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.TypeInformation; + +import java.beans.PropertyDescriptor; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.function.Predicate; + +/** + * This class is responsible for creating a List of {@link PropertyPath} entries that contains all reachable + * properties (w/o circles). + */ +public class PropertyFilterSupport { + + public static List addPropertiesFrom(Class returnType, Class domainType, + ProjectionFactory projectionFactory, + Predicate> simpleTypePredicate, // TODO SimpleTypeHolder or CustomConversions + MappingContext mappingContext) { + + ProjectionInformation projectionInformation = projectionFactory.getProjectionInformation(returnType); + + boolean openProjection = !projectionInformation.isClosed(); // if not closed projection, we would need everything from the entity + boolean typeFromHierarchy = returnType.isAssignableFrom(domainType) || // hierarchy + domainType.isAssignableFrom(returnType); // interface domainType is interface + + if (openProjection || typeFromHierarchy) { + // *Mark wants to do something with object checks here + return Collections.emptyList(); + } + // if ^^ false -> DTO / interface projection + + List propertyPaths = new ArrayList<>(); + for (PropertyDescriptor inputProperty : projectionInformation.getInputProperties()) { + addPropertiesFrom(returnType, domainType, projectionFactory, simpleTypePredicate, propertyPaths, inputProperty.getName(), mappingContext); + } + return propertyPaths; + } + + private static void addPropertiesFrom(Class returnedType, Class domainType, ProjectionFactory factory, + Predicate> simpleTypePredicate, + Collection filteredProperties, String inputProperty, + MappingContext mappingContext) { + + ProjectionInformation projectionInformation = factory.getProjectionInformation(returnedType); + PropertyPath propertyPath; + + // If this is a closed projection we can assume that the return type (possible projection type) contains + // only fields accessible with a property path. + if (projectionInformation.isClosed()) { + propertyPath = PropertyPath.from(inputProperty, returnedType); + } else { + // otherwise the domain type is used right from the start + propertyPath = PropertyPath.from(inputProperty, domainType); + } + + Class propertyType = propertyPath.getLeafType(); + // 1. Simple types can be added directly + // 2. Something that looks like an entity needs to get processed as such + // 3. Embedded projection + if (simpleTypePredicate.test(propertyType)) { + filteredProperties.add(propertyPath); + } else if (mappingContext.hasPersistentEntityFor(propertyType)) { + // avoid recursion / cycles + if (propertyType.equals(domainType)) { + return; + } + processEntity(propertyPath, filteredProperties, simpleTypePredicate, mappingContext); + + } else { + ProjectionInformation nestedProjectionInformation = factory.getProjectionInformation(propertyType); + filteredProperties.add(propertyPath); + // Closed projection should get handled as above (recursion) + if (nestedProjectionInformation.isClosed()) { + for (PropertyDescriptor nestedInputProperty : nestedProjectionInformation.getInputProperties()) { + PropertyPath nestedPropertyPath = propertyPath.nested(nestedInputProperty.getName()); + filteredProperties.add(nestedPropertyPath); + addPropertiesFrom(domainType, returnedType, factory, simpleTypePredicate, filteredProperties, + nestedPropertyPath.toDotPath(), mappingContext); + } + } else { + // an open projection at this place needs to get replaced with the matching (real) entity + PropertyPath domainTypeBasedPropertyPath = PropertyPath.from(propertyPath.toDotPath(), domainType); + processEntity(domainTypeBasedPropertyPath, filteredProperties, simpleTypePredicate, mappingContext); + } + } + } + + private static void processEntity(PropertyPath propertyPath, Collection filteredProperties, + Predicate> ding, + MappingContext mappingContext) { + + PropertyPath leafProperty = propertyPath.getLeafProperty(); + TypeInformation propertyParentType = leafProperty.getOwningType(); + String inputProperty = leafProperty.getSegment(); + + PersistentEntity persistentEntity = mappingContext.getPersistentEntity(propertyParentType); + PersistentProperty persistentProperty = persistentEntity.getPersistentProperty(inputProperty); + Class propertyEntityType = persistentProperty.getActualType(); + + // Use domain type as root type for the property path + addPropertiesFromEntity(filteredProperties, propertyPath, ding, propertyEntityType, mappingContext, new HashSet<>()); + } + + private static void addPropertiesFromEntity(Collection filteredProperties, PropertyPath propertyPath, + Predicate> ding, + Class propertyType, MappingContext mappingContext, + Collection> processedEntities) { + + PersistentEntity persistentEntityFromProperty = mappingContext.getPersistentEntity(propertyType); + // break the recursion / cycles + if (hasProcessedEntity(persistentEntityFromProperty, processedEntities)) { + return; + } + processedEntities.add(persistentEntityFromProperty); + + // save base/root entity/projection type to avoid recursion later + Class pathRootType = propertyPath.getOwningType().getType(); + if (mappingContext.hasPersistentEntityFor(pathRootType)) { + processedEntities.add(mappingContext.getPersistentEntity(pathRootType)); + } + + takeAllPropertiesFromEntity(filteredProperties, ding, propertyPath, mappingContext, persistentEntityFromProperty, processedEntities); + } + + private static boolean hasProcessedEntity(PersistentEntity persistentEntityFromProperty, + Collection> processedEntities) { + + return processedEntities.contains(persistentEntityFromProperty); + } + + private static void takeAllPropertiesFromEntity(Collection filteredProperties, + Predicate> ding, PropertyPath propertyPath, + MappingContext mappingContext, + PersistentEntity persistentEntityFromProperty, + Collection> processedEntities) { + + filteredProperties.add(propertyPath); + + persistentEntityFromProperty.doWithAll(persistentProperty -> { + addPropertiesFromEntity(filteredProperties, propertyPath.nested(persistentProperty.getName()), ding, mappingContext, processedEntities); + }); + } + + private static void addPropertiesFromEntity(Collection filteredProperties, PropertyPath propertyPath, + Predicate> ding, MappingContext mappingContext, + Collection> processedEntities) { + + // break the recursion / cycles + if (filteredProperties.contains(propertyPath)) { + return; + } + Class propertyType = propertyPath.getLeafType(); + // simple types can get added directly to the list. + if (ding.test(propertyType)) { + filteredProperties.add(propertyPath); + // Other types are handled also as entities because there cannot be any nested projection within a real entity. + } else if (mappingContext.hasPersistentEntityFor(propertyType)) { + addPropertiesFromEntity(filteredProperties, propertyPath, ding, propertyType, mappingContext, processedEntities); + } + } +} From 5bc6f49faccd68866ca22e86c5b696f45ce78d2d Mon Sep 17 00:00:00 2001 From: Gerrit Meier Date: Tue, 27 Jul 2021 07:58:49 +0200 Subject: [PATCH 2/3] Naming. --- .../mapping/context/PropertyFilterSupport.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/springframework/data/mapping/context/PropertyFilterSupport.java b/src/main/java/org/springframework/data/mapping/context/PropertyFilterSupport.java index 8a04359c0a..47530d8c80 100644 --- a/src/main/java/org/springframework/data/mapping/context/PropertyFilterSupport.java +++ b/src/main/java/org/springframework/data/mapping/context/PropertyFilterSupport.java @@ -110,7 +110,7 @@ private static void addPropertiesFrom(Class returnedType, Class domainType } private static void processEntity(PropertyPath propertyPath, Collection filteredProperties, - Predicate> ding, + Predicate> simpleTypePredicate, MappingContext mappingContext) { PropertyPath leafProperty = propertyPath.getLeafProperty(); @@ -122,11 +122,11 @@ private static void processEntity(PropertyPath propertyPath, Collection propertyEntityType = persistentProperty.getActualType(); // Use domain type as root type for the property path - addPropertiesFromEntity(filteredProperties, propertyPath, ding, propertyEntityType, mappingContext, new HashSet<>()); + addPropertiesFromEntity(filteredProperties, propertyPath, simpleTypePredicate, propertyEntityType, mappingContext, new HashSet<>()); } private static void addPropertiesFromEntity(Collection filteredProperties, PropertyPath propertyPath, - Predicate> ding, + Predicate> simpleTypePredicate, Class propertyType, MappingContext mappingContext, Collection> processedEntities) { @@ -143,7 +143,7 @@ private static void addPropertiesFromEntity(Collection filteredPro processedEntities.add(mappingContext.getPersistentEntity(pathRootType)); } - takeAllPropertiesFromEntity(filteredProperties, ding, propertyPath, mappingContext, persistentEntityFromProperty, processedEntities); + takeAllPropertiesFromEntity(filteredProperties, simpleTypePredicate, propertyPath, mappingContext, persistentEntityFromProperty, processedEntities); } private static boolean hasProcessedEntity(PersistentEntity persistentEntityFromProperty, @@ -153,7 +153,7 @@ private static boolean hasProcessedEntity(PersistentEntity persistentEntit } private static void takeAllPropertiesFromEntity(Collection filteredProperties, - Predicate> ding, PropertyPath propertyPath, + Predicate> simpleTypePredicate, PropertyPath propertyPath, MappingContext mappingContext, PersistentEntity persistentEntityFromProperty, Collection> processedEntities) { @@ -161,12 +161,12 @@ private static void takeAllPropertiesFromEntity(Collection filtere filteredProperties.add(propertyPath); persistentEntityFromProperty.doWithAll(persistentProperty -> { - addPropertiesFromEntity(filteredProperties, propertyPath.nested(persistentProperty.getName()), ding, mappingContext, processedEntities); + addPropertiesFromEntity(filteredProperties, propertyPath.nested(persistentProperty.getName()), simpleTypePredicate, mappingContext, processedEntities); }); } private static void addPropertiesFromEntity(Collection filteredProperties, PropertyPath propertyPath, - Predicate> ding, MappingContext mappingContext, + Predicate> simpleTypePredicate, MappingContext mappingContext, Collection> processedEntities) { // break the recursion / cycles @@ -175,11 +175,11 @@ private static void addPropertiesFromEntity(Collection filteredPro } Class propertyType = propertyPath.getLeafType(); // simple types can get added directly to the list. - if (ding.test(propertyType)) { + if (simpleTypePredicate.test(propertyType)) { filteredProperties.add(propertyPath); // Other types are handled also as entities because there cannot be any nested projection within a real entity. } else if (mappingContext.hasPersistentEntityFor(propertyType)) { - addPropertiesFromEntity(filteredProperties, propertyPath, ding, propertyType, mappingContext, processedEntities); + addPropertiesFromEntity(filteredProperties, propertyPath, simpleTypePredicate, propertyType, mappingContext, processedEntities); } } } From 08ac9d00a0ddcfb6526c75783f99e2c7cd890eaf Mon Sep 17 00:00:00 2001 From: Gerrit Meier Date: Mon, 20 Sep 2021 10:44:57 +0200 Subject: [PATCH 3/3] GH-2420 - Simplify nested projection code. --- .../context/PropertyFilterSupport.java | 115 +++--------------- 1 file changed, 18 insertions(+), 97 deletions(-) diff --git a/src/main/java/org/springframework/data/mapping/context/PropertyFilterSupport.java b/src/main/java/org/springframework/data/mapping/context/PropertyFilterSupport.java index 47530d8c80..e5487af9e3 100644 --- a/src/main/java/org/springframework/data/mapping/context/PropertyFilterSupport.java +++ b/src/main/java/org/springframework/data/mapping/context/PropertyFilterSupport.java @@ -15,19 +15,14 @@ */ package org.springframework.data.mapping.context; -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.TypeInformation; import java.beans.PropertyDescriptor; -import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; -import java.util.HashSet; -import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import java.util.function.Predicate; /** @@ -36,10 +31,10 @@ */ public class PropertyFilterSupport { - public static List addPropertiesFrom(Class returnType, Class domainType, - ProjectionFactory projectionFactory, - Predicate> simpleTypePredicate, // TODO SimpleTypeHolder or CustomConversions - MappingContext mappingContext) { + public static Map addPropertiesFrom(Class returnType, Class domainType, + ProjectionFactory projectionFactory, + Predicate> simpleTypePredicate, // TODO SimpleTypeHolder or CustomConversions + MappingContext mappingContext) { ProjectionInformation projectionInformation = projectionFactory.getProjectionInformation(returnType); @@ -49,11 +44,11 @@ public static List addPropertiesFrom(Class returnType, Class if (openProjection || typeFromHierarchy) { // *Mark wants to do something with object checks here - return Collections.emptyList(); + return Collections.emptyMap(); } // if ^^ false -> DTO / interface projection - List propertyPaths = new ArrayList<>(); + Map propertyPaths = new ConcurrentHashMap<>(); for (PropertyDescriptor inputProperty : projectionInformation.getInputProperties()) { addPropertiesFrom(returnType, domainType, projectionFactory, simpleTypePredicate, propertyPaths, inputProperty.getName(), mappingContext); } @@ -62,7 +57,7 @@ public static List addPropertiesFrom(Class returnType, Class private static void addPropertiesFrom(Class returnedType, Class domainType, ProjectionFactory factory, Predicate> simpleTypePredicate, - Collection filteredProperties, String inputProperty, + Map filteredProperties, String inputProperty, MappingContext mappingContext) { ProjectionInformation projectionInformation = factory.getProjectionInformation(returnedType); @@ -82,104 +77,30 @@ private static void addPropertiesFrom(Class returnedType, Class domainType // 2. Something that looks like an entity needs to get processed as such // 3. Embedded projection if (simpleTypePredicate.test(propertyType)) { - filteredProperties.add(propertyPath); + filteredProperties.put(propertyPath, false); } else if (mappingContext.hasPersistentEntityFor(propertyType)) { - // avoid recursion / cycles - if (propertyType.equals(domainType)) { - return; - } - processEntity(propertyPath, filteredProperties, simpleTypePredicate, mappingContext); - + filteredProperties.put(propertyPath, true); } else { ProjectionInformation nestedProjectionInformation = factory.getProjectionInformation(propertyType); - filteredProperties.add(propertyPath); // Closed projection should get handled as above (recursion) if (nestedProjectionInformation.isClosed()) { + filteredProperties.put(propertyPath, false); for (PropertyDescriptor nestedInputProperty : nestedProjectionInformation.getInputProperties()) { PropertyPath nestedPropertyPath = propertyPath.nested(nestedInputProperty.getName()); - filteredProperties.add(nestedPropertyPath); + + if (propertyPath.hasNext() && (domainType.equals(propertyPath.getLeafProperty().getOwningType().getType()) + || returnedType.equals(propertyPath.getLeafProperty().getOwningType().getType()))) { + break; + } addPropertiesFrom(domainType, returnedType, factory, simpleTypePredicate, filteredProperties, nestedPropertyPath.toDotPath(), mappingContext); } } else { // an open projection at this place needs to get replaced with the matching (real) entity PropertyPath domainTypeBasedPropertyPath = PropertyPath.from(propertyPath.toDotPath(), domainType); - processEntity(domainTypeBasedPropertyPath, filteredProperties, simpleTypePredicate, mappingContext); + filteredProperties.put(domainTypeBasedPropertyPath, true); } } } - private static void processEntity(PropertyPath propertyPath, Collection filteredProperties, - Predicate> simpleTypePredicate, - MappingContext mappingContext) { - - PropertyPath leafProperty = propertyPath.getLeafProperty(); - TypeInformation propertyParentType = leafProperty.getOwningType(); - String inputProperty = leafProperty.getSegment(); - - PersistentEntity persistentEntity = mappingContext.getPersistentEntity(propertyParentType); - PersistentProperty persistentProperty = persistentEntity.getPersistentProperty(inputProperty); - Class propertyEntityType = persistentProperty.getActualType(); - - // Use domain type as root type for the property path - addPropertiesFromEntity(filteredProperties, propertyPath, simpleTypePredicate, propertyEntityType, mappingContext, new HashSet<>()); - } - - private static void addPropertiesFromEntity(Collection filteredProperties, PropertyPath propertyPath, - Predicate> simpleTypePredicate, - Class propertyType, MappingContext mappingContext, - Collection> processedEntities) { - - PersistentEntity persistentEntityFromProperty = mappingContext.getPersistentEntity(propertyType); - // break the recursion / cycles - if (hasProcessedEntity(persistentEntityFromProperty, processedEntities)) { - return; - } - processedEntities.add(persistentEntityFromProperty); - - // save base/root entity/projection type to avoid recursion later - Class pathRootType = propertyPath.getOwningType().getType(); - if (mappingContext.hasPersistentEntityFor(pathRootType)) { - processedEntities.add(mappingContext.getPersistentEntity(pathRootType)); - } - - takeAllPropertiesFromEntity(filteredProperties, simpleTypePredicate, propertyPath, mappingContext, persistentEntityFromProperty, processedEntities); - } - - private static boolean hasProcessedEntity(PersistentEntity persistentEntityFromProperty, - Collection> processedEntities) { - - return processedEntities.contains(persistentEntityFromProperty); - } - - private static void takeAllPropertiesFromEntity(Collection filteredProperties, - Predicate> simpleTypePredicate, PropertyPath propertyPath, - MappingContext mappingContext, - PersistentEntity persistentEntityFromProperty, - Collection> processedEntities) { - - filteredProperties.add(propertyPath); - - persistentEntityFromProperty.doWithAll(persistentProperty -> { - addPropertiesFromEntity(filteredProperties, propertyPath.nested(persistentProperty.getName()), simpleTypePredicate, mappingContext, processedEntities); - }); - } - - private static void addPropertiesFromEntity(Collection filteredProperties, PropertyPath propertyPath, - Predicate> simpleTypePredicate, MappingContext mappingContext, - Collection> processedEntities) { - - // break the recursion / cycles - if (filteredProperties.contains(propertyPath)) { - return; - } - Class propertyType = propertyPath.getLeafType(); - // simple types can get added directly to the list. - if (simpleTypePredicate.test(propertyType)) { - filteredProperties.add(propertyPath); - // Other types are handled also as entities because there cannot be any nested projection within a real entity. - } else if (mappingContext.hasPersistentEntityFor(propertyType)) { - addPropertiesFromEntity(filteredProperties, propertyPath, simpleTypePredicate, propertyType, mappingContext, processedEntities); - } - } }