@@ -78,10 +78,14 @@ public static EntityProjectionIntrospector create(ProjectionFactory projectionFa
78
78
* <p>
79
79
* Nested properties (direct types, within maps, collections) are introspected for nested projections and contain
80
80
* property paths for closed projections.
81
+ * <p>
82
+ * Deeply nested types (e.g. {@code Map<?, List<Person>>}) are represented with a property path that uses
83
+ * the unwrapped type and no longer the root domain type {@code D}.
81
84
*
82
- * @param mappedType
83
- * @param domainType
84
- * @return
85
+ * @param mappedType must not be {@literal null}.
86
+ * @param domainType must not be {@literal null}.
87
+ * @return the introspection result.
88
+ * @see org.springframework.data.mapping.context.EntityProjection.ContainerPropertyProjection
85
89
*/
86
90
public <M , D > EntityProjection <M , D > introspect (Class <M > mappedType , Class <D > domainType ) {
87
91
@@ -103,8 +107,7 @@ public <M, D> EntityProjection<M, D> introspect(Class<M> mappedType, Class<D> do
103
107
104
108
PersistentEntity <?, ?> persistentEntity = mappingContext .getRequiredPersistentEntity (domainType );
105
109
List <EntityProjection .PropertyProjection <?, ?>> propertyDescriptors = getProperties (null , projectionInformation ,
106
- returnedTypeInformation ,
107
- persistentEntity , null );
110
+ returnedTypeInformation , persistentEntity , null );
108
111
109
112
return EntityProjection .projecting (returnedTypeInformation , domainTypeInformation , propertyDescriptors , true );
110
113
}
@@ -127,44 +130,71 @@ public <M, D> EntityProjection<M, D> introspect(Class<M> mappedType, Class<D> do
127
130
CycleGuard cycleGuardToUse = cycleGuard != null ? cycleGuard : new CycleGuard ();
128
131
129
132
TypeInformation <?> property = projectionTypeInformation .getRequiredProperty (inputProperty .getName ());
133
+ TypeInformation <?> actualType = property .getRequiredActualType ();
134
+
135
+ boolean container = isContainer (actualType );
130
136
131
137
PropertyPath nestedPropertyPath = propertyPath == null
132
138
? PropertyPath .from (persistentProperty .getName (), persistentEntity .getTypeInformation ())
133
139
: propertyPath .nested (persistentProperty .getName ());
134
140
135
- TypeInformation <?> returnedType = property .getRequiredActualType ();
136
- TypeInformation <?> domainType = persistentProperty .getTypeInformation ().getRequiredActualType ();
141
+ TypeInformation <?> unwrappedReturnedType = unwrapContainerType (property .getRequiredActualType ());
142
+ TypeInformation <?> unwrappedDomainType = unwrapContainerType (
143
+ persistentProperty .getTypeInformation ().getRequiredActualType ());
137
144
138
- if (isProjection (returnedType , domainType )) {
145
+ if (isProjection (unwrappedReturnedType , unwrappedDomainType )) {
139
146
140
147
List <EntityProjection .PropertyProjection <?, ?>> nestedPropertyDescriptors ;
141
148
142
149
if (cycleGuardToUse .isCycleFree (persistentProperty )) {
143
- nestedPropertyDescriptors = getProjectedProperties (nestedPropertyPath , returnedType , domainType ,
144
- cycleGuardToUse );
150
+ nestedPropertyDescriptors = getProjectedProperties (container ? null : nestedPropertyPath ,
151
+ unwrappedReturnedType , unwrappedDomainType , cycleGuardToUse );
145
152
} else {
146
153
nestedPropertyDescriptors = Collections .emptyList ();
147
154
}
148
155
149
- propertyDescriptors .add (EntityProjection .PropertyProjection .projecting (nestedPropertyPath , property ,
150
- persistentProperty .getTypeInformation (),
151
- nestedPropertyDescriptors , projectionInformation .isClosed ()));
156
+ if (container ) {
157
+ propertyDescriptors .add (EntityProjection .ContainerPropertyProjection .projecting (nestedPropertyPath , property ,
158
+ persistentProperty .getTypeInformation (), nestedPropertyDescriptors , projectionInformation .isClosed ()));
159
+ } else {
160
+ propertyDescriptors .add (EntityProjection .PropertyProjection .projecting (nestedPropertyPath , property ,
161
+ persistentProperty .getTypeInformation (), nestedPropertyDescriptors , projectionInformation .isClosed ()));
162
+ }
163
+
152
164
} else {
153
- propertyDescriptors
154
- .add (EntityProjection .PropertyProjection .nonProjecting (nestedPropertyPath , property ,
155
- persistentProperty .getTypeInformation ()));
165
+ if (container ) {
166
+ propertyDescriptors .add (EntityProjection .ContainerPropertyProjection .nonProjecting (nestedPropertyPath ,
167
+ property , persistentProperty .getTypeInformation ()));
168
+ } else {
169
+ propertyDescriptors .add (EntityProjection .PropertyProjection .nonProjecting (nestedPropertyPath , property ,
170
+ persistentProperty .getTypeInformation ()));
171
+ }
156
172
}
157
173
}
158
174
159
175
return propertyDescriptors ;
160
176
}
161
177
178
+ private static TypeInformation <?> unwrapContainerType (TypeInformation <?> type ) {
179
+
180
+ TypeInformation <?> unwrapped = type ;
181
+ while (isContainer (unwrapped )) {
182
+ unwrapped = unwrapped .getRequiredActualType ();
183
+ }
184
+
185
+ return unwrapped ;
186
+ }
187
+
188
+ private static boolean isContainer (TypeInformation <?> actualType ) {
189
+ return actualType .isCollectionLike () || actualType .isMap ();
190
+ }
191
+
162
192
private boolean isProjection (TypeInformation <?> returnedType , TypeInformation <?> domainType ) {
163
193
return projectionPredicate .test (returnedType .getRequiredActualType ().getType (),
164
194
domainType .getRequiredActualType ().getType ());
165
195
}
166
196
167
- private List <EntityProjection .PropertyProjection <?, ?>> getProjectedProperties (PropertyPath propertyPath ,
197
+ private List <EntityProjection .PropertyProjection <?, ?>> getProjectedProperties (@ Nullable PropertyPath propertyPath ,
168
198
TypeInformation <?> returnedType , TypeInformation <?> domainType , CycleGuard cycleGuard ) {
169
199
170
200
ProjectionInformation projectionInformation = projectionFactory .getProjectionInformation (returnedType .getType ());
0 commit comments