|
17 | 17 |
|
18 | 18 | import org.apiguardian.api.API;
|
19 | 19 | import org.springframework.data.mapping.PropertyPath;
|
| 20 | +import org.springframework.data.neo4j.core.mapping.GraphPropertyDescription; |
20 | 21 | import org.springframework.data.neo4j.core.mapping.Neo4jMappingContext;
|
| 22 | +import org.springframework.data.neo4j.core.mapping.Neo4jPersistentEntity; |
| 23 | +import org.springframework.data.neo4j.core.mapping.PropertyFilter; |
| 24 | +import org.springframework.data.neo4j.core.mapping.RelationshipDescription; |
21 | 25 | import org.springframework.data.projection.ProjectionFactory;
|
22 | 26 | import org.springframework.data.projection.ProjectionInformation;
|
23 | 27 | import org.springframework.data.repository.query.ResultProcessor;
|
24 | 28 | import org.springframework.data.repository.query.ReturnedType;
|
| 29 | +import org.springframework.data.util.TypeInformation; |
| 30 | +import org.springframework.lang.Nullable; |
25 | 31 |
|
26 | 32 | import java.beans.PropertyDescriptor;
|
| 33 | +import java.util.Collection; |
27 | 34 | import java.util.Collections;
|
28 |
| -import java.util.HashMap; |
29 |
| -import java.util.Map; |
| 35 | +import java.util.HashSet; |
30 | 36 |
|
31 | 37 | /**
|
32 | 38 | * This class is responsible for creating a List of {@link PropertyPath} entries that contains all reachable
|
|
35 | 41 | @API(status = API.Status.INTERNAL, since = "6.1.3")
|
36 | 42 | public final class PropertyFilterSupport {
|
37 | 43 |
|
38 |
| - public static Map<PropertyPath, Boolean> getInputProperties(ResultProcessor resultProcessor, ProjectionFactory factory, |
39 |
| - Neo4jMappingContext mappingContext) { |
| 44 | + public static Collection<PropertyFilter.ProjectedPath> getInputProperties(ResultProcessor resultProcessor, ProjectionFactory factory, |
| 45 | + Neo4jMappingContext mappingContext) { |
40 | 46 |
|
41 | 47 | ReturnedType returnedType = resultProcessor.getReturnedType();
|
42 |
| - Map<PropertyPath, Boolean> filteredProperties = new HashMap<>(); |
| 48 | + Collection<PropertyFilter.ProjectedPath> filteredProperties = new HashSet<>(); |
43 | 49 |
|
44 | 50 | boolean isProjecting = returnedType.isProjecting();
|
45 | 51 | boolean isClosedProjection = factory.getProjectionInformation(returnedType.getReturnedType()).isClosed();
|
46 | 52 |
|
47 | 53 | if (!isProjecting || !isClosedProjection) {
|
48 |
| - return Collections.emptyMap(); |
| 54 | + return Collections.emptySet(); |
49 | 55 | }
|
50 | 56 |
|
51 | 57 | for (String inputProperty : returnedType.getInputProperties()) {
|
52 | 58 | addPropertiesFrom(returnedType.getDomainType(), returnedType.getReturnedType(), factory,
|
53 |
| - filteredProperties, inputProperty, mappingContext); |
| 59 | + filteredProperties, new ProjectionPathProcessor(inputProperty, PropertyPath.from(inputProperty, returnedType.getReturnedType()).getLeafProperty().getTypeInformation()), mappingContext); |
54 | 60 | }
|
55 | 61 |
|
56 | 62 | return filteredProperties;
|
57 | 63 | }
|
58 | 64 |
|
59 |
| - static Map<PropertyPath, Boolean> addPropertiesFrom(Class<?> domainType, Class<?> returnType, |
60 |
| - ProjectionFactory projectionFactory, |
61 |
| - Neo4jMappingContext neo4jMappingContext) { |
| 65 | + static Collection<PropertyFilter.ProjectedPath> addPropertiesFrom(Class<?> domainType, Class<?> returnType, |
| 66 | + ProjectionFactory projectionFactory, |
| 67 | + Neo4jMappingContext neo4jMappingContext) { |
62 | 68 |
|
63 | 69 | ProjectionInformation projectionInformation = projectionFactory.getProjectionInformation(returnType);
|
64 |
| - Map<PropertyPath, Boolean> propertyPaths = new HashMap<>(); |
| 70 | + Collection<PropertyFilter.ProjectedPath> propertyPaths = new HashSet<>(); |
| 71 | + Neo4jPersistentEntity<?> domainEntity = neo4jMappingContext.getRequiredPersistentEntity(domainType); |
| 72 | + |
65 | 73 | for (PropertyDescriptor inputProperty : projectionInformation.getInputProperties()) {
|
66 |
| - addPropertiesFrom(domainType, returnType, projectionFactory, propertyPaths, inputProperty.getName(), neo4jMappingContext); |
| 74 | + TypeInformation<?> typeInformation = null; |
| 75 | + if (projectionInformation.isClosed()) { |
| 76 | + typeInformation = PropertyPath.from(inputProperty.getName(), returnType).getTypeInformation(); |
| 77 | + } else { |
| 78 | + // try to figure out the right property by name |
| 79 | + for (GraphPropertyDescription graphProperty : domainEntity.getGraphProperties()) { |
| 80 | + if (graphProperty.getPropertyName().equals(inputProperty.getName())) { |
| 81 | + typeInformation = domainEntity.getPersistentProperty(graphProperty.getFieldName()).getTypeInformation(); |
| 82 | + break; |
| 83 | + } |
| 84 | + } |
| 85 | + // it could still be null for relationships |
| 86 | + if (typeInformation == null) { |
| 87 | + for (RelationshipDescription relationshipDescription : domainEntity.getRelationships()) { |
| 88 | + if (relationshipDescription.getFieldName().equals(inputProperty.getName())) { |
| 89 | + typeInformation = domainEntity.getPersistentProperty(relationshipDescription.getFieldName()).getTypeInformation(); |
| 90 | + break; |
| 91 | + } |
| 92 | + } |
| 93 | + } |
| 94 | + } |
| 95 | + addPropertiesFrom(domainType, returnType, projectionFactory, propertyPaths, new ProjectionPathProcessor(inputProperty.getName(), typeInformation), neo4jMappingContext); |
67 | 96 | }
|
68 | 97 | return propertyPaths;
|
69 | 98 | }
|
70 | 99 |
|
71 | 100 | private static void addPropertiesFrom(Class<?> domainType, Class<?> returnedType, ProjectionFactory factory,
|
72 |
| - Map<PropertyPath, Boolean> filteredProperties, String inputProperty, |
| 101 | + Collection<PropertyFilter.ProjectedPath> filteredProperties, ProjectionPathProcessor projectionPathProcessor, |
73 | 102 | Neo4jMappingContext mappingContext) {
|
74 | 103 |
|
75 | 104 | ProjectionInformation projectionInformation = factory.getProjectionInformation(returnedType);
|
76 |
| - PropertyPath propertyPath; |
| 105 | + PropertyFilter.RelaxedPropertyPath propertyPath; |
77 | 106 |
|
78 | 107 | // If this is a closed projection we can assume that the return type (possible projection type) contains
|
79 | 108 | // only fields accessible with a property path.
|
80 | 109 | if (projectionInformation.isClosed()) {
|
81 |
| - propertyPath = PropertyPath.from(inputProperty, returnedType); |
| 110 | + propertyPath = PropertyFilter.RelaxedPropertyPath.withRootType(returnedType).append(projectionPathProcessor.path); |
82 | 111 | } else {
|
83 | 112 | // otherwise the domain type is used right from the start
|
84 |
| - propertyPath = PropertyPath.from(inputProperty, domainType); |
| 113 | + propertyPath = PropertyFilter.RelaxedPropertyPath.withRootType(domainType).append(projectionPathProcessor.path); |
| 114 | + } |
| 115 | + |
| 116 | + Class<?> propertyType = projectionPathProcessor.typeInformation.getType(); |
| 117 | + TypeInformation<?> currentTypeInformation = projectionPathProcessor.typeInformation.getActualType(); |
| 118 | + if (projectionPathProcessor.typeInformation.isMap()) { |
| 119 | + // deep inspection into the map to look for the related entity type. |
| 120 | + TypeInformation<?> mapValueType = projectionPathProcessor.typeInformation.getRequiredMapValueType(); |
| 121 | + if (mapValueType.isCollectionLike()) { |
| 122 | + currentTypeInformation = projectionPathProcessor.typeInformation.getRequiredMapValueType().getComponentType(); |
| 123 | + propertyType = projectionPathProcessor.typeInformation.getRequiredMapValueType().getComponentType().getType(); |
| 124 | + } else { |
| 125 | + currentTypeInformation = projectionPathProcessor.typeInformation.getRequiredMapValueType(); |
| 126 | + propertyType = projectionPathProcessor.typeInformation.getRequiredMapValueType().getType(); |
| 127 | + } |
| 128 | + } else if (projectionPathProcessor.typeInformation.isCollectionLike()) { |
| 129 | + currentTypeInformation = projectionPathProcessor.typeInformation.getComponentType(); |
| 130 | + propertyType = projectionPathProcessor.typeInformation.getComponentType().getType(); |
85 | 131 | }
|
86 | 132 |
|
87 |
| - Class<?> propertyType = propertyPath.getLeafType(); |
88 | 133 | // 1. Simple types can be added directly
|
89 | 134 | // 2. Something that looks like an entity needs to get processed as such
|
90 | 135 | // 3. Embedded projection
|
91 | 136 | if (mappingContext.getConversionService().isSimpleType(propertyType)) {
|
92 |
| - filteredProperties.put(propertyPath, false); |
| 137 | + filteredProperties.add(new PropertyFilter.ProjectedPath(propertyPath, false)); |
93 | 138 | } else if (mappingContext.hasPersistentEntityFor(propertyType)) {
|
94 |
| - filteredProperties.put(propertyPath, true); |
| 139 | + filteredProperties.add(new PropertyFilter.ProjectedPath(propertyPath, true)); |
95 | 140 | } else {
|
96 | 141 | ProjectionInformation nestedProjectionInformation = factory.getProjectionInformation(propertyType);
|
97 | 142 | // Closed projection should get handled as above (recursion)
|
98 | 143 | if (nestedProjectionInformation.isClosed()) {
|
99 |
| - filteredProperties.put(propertyPath, false); |
| 144 | + filteredProperties.add(new PropertyFilter.ProjectedPath(propertyPath, false)); |
100 | 145 | for (PropertyDescriptor nestedInputProperty : nestedProjectionInformation.getInputProperties()) {
|
101 |
| - PropertyPath nestedPropertyPath = propertyPath.nested(nestedInputProperty.getName()); |
102 |
| - if (propertyPath.hasNext() && (domainType.equals(propertyPath.getLeafProperty().getOwningType().getType()) |
103 |
| - || returnedType.equals(propertyPath.getLeafProperty().getOwningType().getType()))) { |
| 146 | + TypeInformation<?> typeInformation = currentTypeInformation.getProperty(nestedInputProperty.getName()); |
| 147 | + ProjectionPathProcessor nextProjectionPathProcessor = projectionPathProcessor.next(nestedInputProperty, typeInformation); |
| 148 | + |
| 149 | + if (projectionPathProcessor.isChildLevel() && (domainType.equals(nextProjectionPathProcessor.typeInformation.getType()) |
| 150 | + || returnedType.equals(nextProjectionPathProcessor.typeInformation.getType()))) { |
104 | 151 | break;
|
105 | 152 | }
|
106 | 153 |
|
107 | 154 | addPropertiesFrom(domainType, returnedType, factory, filteredProperties,
|
108 |
| - nestedPropertyPath.toDotPath(), mappingContext); |
| 155 | + nextProjectionPathProcessor, mappingContext); |
109 | 156 | }
|
110 | 157 | } else {
|
111 | 158 | // An open projection at this place needs to get replaced with the matching (real) entity
|
112 | 159 | // Use domain type as root type for the property path
|
113 |
| - PropertyPath domainBasedPropertyPath = PropertyPath.from(inputProperty, domainType); |
114 |
| - filteredProperties.put(domainBasedPropertyPath, true); |
| 160 | + PropertyFilter.RelaxedPropertyPath domainBasedPropertyPath = PropertyFilter.RelaxedPropertyPath.withRootType(domainType).append(projectionPathProcessor.path); |
| 161 | + filteredProperties.add(new PropertyFilter.ProjectedPath(domainBasedPropertyPath, true)); |
115 | 162 | }
|
116 | 163 | }
|
117 | 164 | }
|
118 | 165 |
|
| 166 | + private static class ProjectionPathProcessor { |
| 167 | + final TypeInformation<?> typeInformation; |
| 168 | + final String path; |
| 169 | + final String name; |
| 170 | + |
| 171 | + private ProjectionPathProcessor(String name, String path, @Nullable TypeInformation<?> typeInformation) { |
| 172 | + this.typeInformation = typeInformation; |
| 173 | + this.path = path; |
| 174 | + this.name = name; |
| 175 | + } |
| 176 | + |
| 177 | + private ProjectionPathProcessor(String name, @Nullable TypeInformation<?> typeInformation) { |
| 178 | + this(name, name, typeInformation); |
| 179 | + } |
| 180 | + |
| 181 | + public ProjectionPathProcessor next(PropertyDescriptor nextProperty, TypeInformation<?> nextTypeInformation) { |
| 182 | + String nextPropertyName = nextProperty.getName(); |
| 183 | + return new ProjectionPathProcessor(nextPropertyName, path + "." + nextPropertyName, nextTypeInformation); |
| 184 | + } |
| 185 | + |
| 186 | + public boolean isChildLevel() { |
| 187 | + return path.contains("."); |
| 188 | + } |
| 189 | + } |
| 190 | + |
119 | 191 | }
|
0 commit comments