6
6
7
7
import java .lang .reflect .ParameterizedType ;
8
8
import java .lang .reflect .Type ;
9
- import java .util .Collections ;
10
9
import java .util .HashMap ;
11
10
import java .util .HashSet ;
12
11
import java .util .Map ;
13
12
import java .util .Set ;
14
13
14
+ import org .hibernate .validator .internal .engine .valueextraction .ValueExtractorDescriptor ;
15
15
import org .hibernate .validator .internal .metadata .aggregated .BeanMetaData ;
16
16
import org .hibernate .validator .internal .metadata .aggregated .CascadingMetaData ;
17
17
import org .hibernate .validator .internal .metadata .aggregated .ContainerCascadingMetaData ;
18
+ import org .hibernate .validator .internal .metadata .aggregated .PotentiallyContainerCascadingMetaData ;
18
19
import org .hibernate .validator .internal .metadata .facets .Cascadable ;
19
20
import org .hibernate .validator .internal .properties .Signature ;
20
21
import org .hibernate .validator .internal .util .CollectionHelper ;
@@ -29,11 +30,11 @@ public class PredefinedScopeProcessedBeansTrackingStrategy implements ProcessedB
29
30
30
31
public PredefinedScopeProcessedBeansTrackingStrategy (Map <Class <?>, BeanMetaData <?>> rawBeanMetaDataMap ) {
31
32
// TODO: build the maps from the information inside the beanMetaDataManager
32
- // There is a good chance we will need a structure with the whole hierarchy of constraint classes.
33
- // That's something we could add to PredefinedScopeBeanMetaDataManager, as we are already doing similar things
34
- // there (see the ClassHierarchyHelper.getHierarchy call).
35
- // In the predefined scope case, we will have the whole hierarchy of constrained classes passed to
36
- // PredefinedScopeBeanMetaDataManager.
33
+ // There is a good chance we will need a structure with the whole hierarchy of constraint classes.
34
+ // That's something we could add to PredefinedScopeBeanMetaDataManager, as we are already doing similar things
35
+ // there (see the ClassHierarchyHelper.getHierarchy call).
36
+ // In the predefined scope case, we will have the whole hierarchy of constrained classes passed to
37
+ // PredefinedScopeBeanMetaDataManager.
37
38
38
39
this .trackingEnabledForBeans = CollectionHelper .toImmutableMap (
39
40
new TrackingEnabledStrategyBuilder ( rawBeanMetaDataMap ).build ()
@@ -45,10 +46,21 @@ public PredefinedScopeProcessedBeansTrackingStrategy(Map<Class<?>, BeanMetaData<
45
46
private static class TrackingEnabledStrategyBuilder {
46
47
private final Map <Class <?>, BeanMetaData <?>> rawBeanMetaDataMap ;
47
48
private final Map <Class <?>, Boolean > classToBeanTrackingEnabled ;
49
+ // Map values are a set of subtypes for the key class, including "self" i.e. the "key":
50
+ private final Map <Class <?>, Set <Class <?>>> subtypesMap ;
48
51
49
52
TrackingEnabledStrategyBuilder (Map <Class <?>, BeanMetaData <?>> rawBeanMetaDataMap ) {
50
53
this .rawBeanMetaDataMap = rawBeanMetaDataMap ;
51
54
this .classToBeanTrackingEnabled = CollectionHelper .newHashMap ( rawBeanMetaDataMap .size () );
55
+ this .subtypesMap = CollectionHelper .newHashMap ( rawBeanMetaDataMap .size () );
56
+ for ( Class <?> beanClass : rawBeanMetaDataMap .keySet () ) {
57
+ for ( Class <?> otherBeanClass : rawBeanMetaDataMap .keySet () ) {
58
+ if ( beanClass .isAssignableFrom ( otherBeanClass ) ) {
59
+ subtypesMap .computeIfAbsent ( beanClass , k -> new HashSet <>() )
60
+ .add ( otherBeanClass );
61
+ }
62
+ }
63
+ }
52
64
}
53
65
54
66
public Map <Class <?>, Boolean > build () {
@@ -95,8 +107,16 @@ public Map<Class<?>, Boolean> build() {
95
107
// -----
96
108
// A, B, C have cycles; D does not have a cycle.
97
109
//
110
+ //
111
+ // We also need to account for the case when the subtype is used at runtime that may change the cycles:
112
+ // 4) A -> B -> C -> D
113
+ // And C1 extends C where C1 -> A
114
+ // Hence, at runtime we "may" get:
115
+ // A -> B -> C1 -> D
116
+ // ^ |
117
+ // | |
118
+ // -----------
98
119
private boolean determineTrackingRequired (Class <?> beanClass , Set <Class <?>> beanClassesInPath ) {
99
-
100
120
final Boolean isBeanTrackingEnabled = classToBeanTrackingEnabled .get ( beanClass );
101
121
if ( isBeanTrackingEnabled != null ) {
102
122
// It was already determined for beanClass.
@@ -157,36 +177,59 @@ private boolean determineTrackingRequired(Class<?> beanClass, Set<Class<?>> bean
157
177
158
178
// TODO: is there a more concise way to do this?
159
179
private <T > Set <Class <?>> getDirectCascadedBeanClasses (Class <T > beanClass ) {
160
- final BeanMetaData <?> beanMetaData = rawBeanMetaDataMap .get ( beanClass );
161
-
162
- if ( beanMetaData == null || !beanMetaData .hasCascadables () ) {
163
- return Collections .emptySet ();
180
+ final Set <Class <?>> directCascadedBeanClasses = new HashSet <>();
181
+ // At runtime, if we are not looking at the root bean the actual value of a cascadable
182
+ // can be either the same `beanClass` or one of its subtypes... since subtypes can potentially add
183
+ // more constraints we want to iterate through the subclasses (for which there is some metadata defined)
184
+ // and include the info from them too.
185
+ Set <Class <?>> classes = subtypesMap .get ( beanClass );
186
+ if ( classes == null ) {
187
+ // It may be that some bean property without any constraints is marked for cascading validation,
188
+ // In that case the metadata entry will be missing from the map, but we
189
+ return Set .of ();
164
190
}
191
+ for ( Class <?> otherBeanClass : classes ) {
192
+ final BeanMetaData <?> beanMetaData = rawBeanMetaDataMap .get ( otherBeanClass );
165
193
166
- final Set <Class <?>> directCascadedBeanClasses = new HashSet <>();
167
- for ( Cascadable cascadable : beanMetaData .getCascadables () ) {
168
- final CascadingMetaData cascadingMetaData = cascadable .getCascadingMetaData ();
169
- if ( cascadingMetaData .isContainer () ) {
170
- final ContainerCascadingMetaData containerCascadingMetaData = (ContainerCascadingMetaData ) cascadingMetaData ;
171
- if ( containerCascadingMetaData .getEnclosingType () instanceof ParameterizedType ) {
172
- ParameterizedType parameterizedType = (ParameterizedType ) containerCascadingMetaData .getEnclosingType ();
173
- for ( Type typeArgument : parameterizedType .getActualTypeArguments () ) {
174
- if ( typeArgument instanceof Class ) {
175
- directCascadedBeanClasses .add ( (Class <?>) typeArgument );
194
+ if ( beanMetaData == null || !beanMetaData .hasCascadables () ) {
195
+ continue ;
196
+ }
197
+
198
+ for ( Cascadable cascadable : beanMetaData .getCascadables () ) {
199
+ final CascadingMetaData cascadingMetaData = cascadable .getCascadingMetaData ();
200
+ if ( cascadingMetaData .isContainer () ) {
201
+ final ContainerCascadingMetaData containerCascadingMetaData = (ContainerCascadingMetaData ) cascadingMetaData ;
202
+ if ( containerCascadingMetaData .getEnclosingType () instanceof ParameterizedType parameterizedType ) {
203
+ for ( Type typeArgument : parameterizedType .getActualTypeArguments () ) {
204
+ if ( typeArgument instanceof Class <?> typeArgumentClass ) {
205
+ directCascadedBeanClasses .add ( typeArgumentClass );
206
+ }
207
+ else {
208
+ throw new UnsupportedOperationException ( "Only ParameterizedType values of type Class are supported" );
209
+ }
176
210
}
177
- else {
178
- throw new UnsupportedOperationException ( "Only ParameterizedType values of type Class are supported" );
211
+ }
212
+ else {
213
+ // If we do not have the type arguments then we can go though the value extractors,
214
+ // as they are required to define the `@ExtractedValue(type = ???)` ...
215
+ // this way we should get the type we want:
216
+ for ( ValueExtractorDescriptor valueExtractorCandidate : containerCascadingMetaData .getValueExtractorCandidates () ) {
217
+ valueExtractorCandidate .getExtractedType ().ifPresent ( directCascadedBeanClasses ::add );
179
218
}
180
219
}
181
220
}
221
+ else if ( cascadingMetaData instanceof PotentiallyContainerCascadingMetaData potentiallyContainerCascadingMetaData ) {
222
+ // if it's a potentially container cascading one, we are "in trouble" as thing can be "almost anything".
223
+ // TODO: would it be enough to just take the type as defined ?
224
+ directCascadedBeanClasses .add ( (Class <?>) cascadable .getCascadableType () );
225
+ // TODO: or be much more cautious and just assume that it can be "anything":
226
+ directCascadedBeanClasses .add ( Object .class );
227
+ }
182
228
else {
183
- throw new UnsupportedOperationException ( "Non-parameterized containers are not supported yet." );
229
+ // TODO: For now, assume non-container Cascadables are always beans. Truee???
230
+ directCascadedBeanClasses .add ( (Class <?>) cascadable .getCascadableType () );
184
231
}
185
232
}
186
- else {
187
- // TODO: For now, assume non-container Cascadables are always beans. Truee???
188
- directCascadedBeanClasses .add ( (Class <?>) cascadable .getCascadableType () );
189
- }
190
233
}
191
234
return directCascadedBeanClasses ;
192
235
}
0 commit comments