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 ;
15
14
import org .hibernate .validator .internal .metadata .aggregated .BeanMetaData ;
16
15
import org .hibernate .validator .internal .metadata .aggregated .CascadingMetaData ;
17
16
import org .hibernate .validator .internal .metadata .aggregated .ContainerCascadingMetaData ;
17
+ import org .hibernate .validator .internal .metadata .aggregated .PotentiallyContainerCascadingMetaData ;
18
18
import org .hibernate .validator .internal .metadata .facets .Cascadable ;
19
19
import org .hibernate .validator .internal .properties .Signature ;
20
20
import org .hibernate .validator .internal .util .CollectionHelper ;
@@ -29,11 +29,11 @@ public class PredefinedScopeProcessedBeansTrackingStrategy implements ProcessedB
29
29
30
30
public PredefinedScopeProcessedBeansTrackingStrategy (Map <Class <?>, BeanMetaData <?>> rawBeanMetaDataMap ) {
31
31
// 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.
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.
37
37
38
38
this .trackingEnabledForBeans = CollectionHelper .toImmutableMap (
39
39
new TrackingEnabledStrategyBuilder ( rawBeanMetaDataMap ).build ()
@@ -45,10 +45,21 @@ public PredefinedScopeProcessedBeansTrackingStrategy(Map<Class<?>, BeanMetaData<
45
45
private static class TrackingEnabledStrategyBuilder {
46
46
private final Map <Class <?>, BeanMetaData <?>> rawBeanMetaDataMap ;
47
47
private final Map <Class <?>, Boolean > classToBeanTrackingEnabled ;
48
+ // Map values are a set of subtypes for the key class, including "self" i.e. the "key":
49
+ private final Map <Class <?>, Set <Class <?>>> subtypesMap ;
48
50
49
51
TrackingEnabledStrategyBuilder (Map <Class <?>, BeanMetaData <?>> rawBeanMetaDataMap ) {
50
52
this .rawBeanMetaDataMap = rawBeanMetaDataMap ;
51
53
this .classToBeanTrackingEnabled = CollectionHelper .newHashMap ( rawBeanMetaDataMap .size () );
54
+ this .subtypesMap = CollectionHelper .newHashMap ( rawBeanMetaDataMap .size () );
55
+ for ( Class <?> beanClass : rawBeanMetaDataMap .keySet () ) {
56
+ for ( Class <?> otherBeanClass : rawBeanMetaDataMap .keySet () ) {
57
+ if ( beanClass .isAssignableFrom ( otherBeanClass ) ) {
58
+ subtypesMap .computeIfAbsent ( beanClass , k -> new HashSet <>() )
59
+ .add ( otherBeanClass );
60
+ }
61
+ }
62
+ }
52
63
}
53
64
54
65
public Map <Class <?>, Boolean > build () {
@@ -95,8 +106,16 @@ public Map<Class<?>, Boolean> build() {
95
106
// -----
96
107
// A, B, C have cycles; D does not have a cycle.
97
108
//
109
+ //
110
+ // We also need to account for the case when the subtype is used at runtime that may change the cycles:
111
+ // 4) A -> B -> C -> D
112
+ // And C1 extends C where C1 -> A
113
+ // Hence, at runtime we "may" get:
114
+ // A -> B -> C1 -> D
115
+ // ^ |
116
+ // | |
117
+ // -----------
98
118
private boolean determineTrackingRequired (Class <?> beanClass , Set <Class <?>> beanClassesInPath ) {
99
-
100
119
final Boolean isBeanTrackingEnabled = classToBeanTrackingEnabled .get ( beanClass );
101
120
if ( isBeanTrackingEnabled != null ) {
102
121
// It was already determined for beanClass.
@@ -157,36 +176,50 @@ private boolean determineTrackingRequired(Class<?> beanClass, Set<Class<?>> bean
157
176
158
177
// TODO: is there a more concise way to do this?
159
178
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 ();
179
+ final Set <Class <?>> directCascadedBeanClasses = new HashSet <>();
180
+ // At runtime, if we are not looking at the root bean the actual value of a cascadable
181
+ // can be either the same `beanClass` or one of its subtypes... since subtypes can potentially add
182
+ // more constraints we want to iterate through the subclasses (for which there is some metadata defined)
183
+ // and include the info from them too.
184
+ Set <Class <?>> classes = subtypesMap .get ( beanClass );
185
+ if ( classes == null ) {
186
+ // It may be that some bean property without any constraints is marked for cascading validation,
187
+ // In that case the metadata entry will be missing from the map, but we
188
+ return Set .of ();
164
189
}
190
+ for ( Class <?> otherBeanClass : classes ) {
191
+ final BeanMetaData <?> beanMetaData = rawBeanMetaDataMap .get ( otherBeanClass );
165
192
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 );
176
- }
177
- else {
178
- throw new UnsupportedOperationException ( "Only ParameterizedType values of type Class are supported" );
193
+ if ( beanMetaData == null || !beanMetaData .hasCascadables () ) {
194
+ continue ;
195
+ }
196
+
197
+ for ( Cascadable cascadable : beanMetaData .getCascadables () ) {
198
+ final CascadingMetaData cascadingMetaData = cascadable .getCascadingMetaData ();
199
+ if ( cascadingMetaData .isContainer () ) {
200
+ final ContainerCascadingMetaData containerCascadingMetaData = (ContainerCascadingMetaData ) cascadingMetaData ;
201
+ if ( containerCascadingMetaData .getEnclosingType () instanceof ParameterizedType parameterizedType ) {
202
+ for ( Type typeArgument : parameterizedType .getActualTypeArguments () ) {
203
+ if ( typeArgument instanceof Class ) {
204
+ directCascadedBeanClasses .add ( (Class <?>) typeArgument );
205
+ }
206
+ else {
207
+ throw new UnsupportedOperationException ( "Only ParameterizedType values of type Class are supported" );
208
+ }
179
209
}
180
210
}
211
+ else {
212
+ throw new UnsupportedOperationException ( "Non-parameterized containers are not supported yet." );
213
+ }
214
+ }
215
+ else if ( cascadingMetaData instanceof PotentiallyContainerCascadingMetaData ) {
216
+ // if it's a potentially container cascading one ...
181
217
}
182
218
else {
183
- throw new UnsupportedOperationException ( "Non-parameterized containers are not supported yet." );
219
+ // TODO: For now, assume non-container Cascadables are always beans. Truee???
220
+ directCascadedBeanClasses .add ( (Class <?>) cascadable .getCascadableType () );
184
221
}
185
222
}
186
- else {
187
- // TODO: For now, assume non-container Cascadables are always beans. Truee???
188
- directCascadedBeanClasses .add ( (Class <?>) cascadable .getCascadableType () );
189
- }
190
223
}
191
224
return directCascadedBeanClasses ;
192
225
}
0 commit comments