Skip to content

Commit 9f40990

Browse files
marko-bekhtagsmet
authored andcommitted
HV-1609 Change how we filter which beans to process in CDI extension
As @WithAnnotations does not check the interfaces and superclasses and we cannot rely on usage of @inherited either, filtering is performed "manually" by walking through the class hierarchy and checking all possible places for annotations that would enable the validation.
1 parent 72072e0 commit 9f40990

File tree

3 files changed

+452
-11
lines changed

3 files changed

+452
-11
lines changed

cdi/src/main/java/org/hibernate/validator/cdi/ValidationExtension.java

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import java.util.LinkedList;
1919
import java.util.List;
2020
import java.util.Set;
21+
import java.util.function.Predicate;
2122

2223
import javax.enterprise.event.Observes;
2324
import javax.enterprise.inject.Default;
@@ -32,12 +33,9 @@
3233
import javax.enterprise.inject.spi.Extension;
3334
import javax.enterprise.inject.spi.ProcessAnnotatedType;
3435
import javax.enterprise.inject.spi.ProcessBean;
35-
import javax.enterprise.inject.spi.WithAnnotations;
3636
import javax.enterprise.util.AnnotationLiteral;
3737
import javax.validation.BootstrapConfiguration;
3838
import javax.validation.Configuration;
39-
import javax.validation.Constraint;
40-
import javax.validation.Valid;
4139
import javax.validation.Validation;
4240
import javax.validation.Validator;
4341
import javax.validation.ValidatorFactory;
@@ -47,6 +45,7 @@
4745
import javax.validation.metadata.PropertyDescriptor;
4846

4947
import org.hibernate.validator.cdi.internal.InheritedMethodsHelper;
48+
import org.hibernate.validator.cdi.internal.ValidateableBeanFilter;
5049
import org.hibernate.validator.cdi.internal.ValidationProviderHelper;
5150
import org.hibernate.validator.cdi.internal.ValidatorBean;
5251
import org.hibernate.validator.cdi.internal.ValidatorFactoryBean;
@@ -83,6 +82,7 @@ public class ValidationExtension implements Extension {
8382
EnumSet.of( ExecutableType.CONSTRUCTORS, ExecutableType.NON_GETTER_METHODS, ExecutableType.GETTER_METHODS );
8483
private static final EnumSet<ExecutableType> DEFAULT_EXECUTABLE_TYPES =
8584
EnumSet.of( ExecutableType.CONSTRUCTORS, ExecutableType.NON_GETTER_METHODS );
85+
private static final Predicate<Class<?>> VALIDATEABLE_BEAN_FILTER = new ValidateableBeanFilter();
8686

8787
@SuppressWarnings("serial")
8888
private final Annotation defaultQualifier = new AnnotationLiteral<Default>() {
@@ -219,19 +219,23 @@ else if ( bean.getTypes().contains( Validator.class ) || bean instanceof Validat
219219
* @param processAnnotatedTypeEvent event fired for each annotated type
220220
* @param <T> the annotated type
221221
*/
222-
public <T> void processAnnotatedType(@Observes @WithAnnotations({
223-
Constraint.class,
224-
Valid.class,
225-
ValidateOnExecution.class
226-
}) ProcessAnnotatedType<T> processAnnotatedTypeEvent) {
222+
public <T> void processAnnotatedType(@Observes ProcessAnnotatedType<T> processAnnotatedTypeEvent) {
223+
// NOTE: we cannot use @WithAnnotations for filtering purposes here.
224+
// This annotation does not consider implemented interfaces and super classes for annotations.
225+
// Hence beans with no Hibernate Validator/Bean Validation annotations (@Valid, @Constraint,
226+
// @ValidateOnExecution) on them, that implement interfaces that have such annotations
227+
// will not be pushed to this method if @WithAnnotations is used.
228+
// To prevent filling memory with useless metadata and redundant work in HV we have a custom
229+
// VALIDATEABLE_BEAN_FILTER that checks for annotation presence in the way that we need.
227230
Contracts.assertNotNull( processAnnotatedTypeEvent, "The ProcessAnnotatedType event cannot be null" );
228231

229-
// validation globally disabled
230-
if ( !isExecutableValidationEnabled ) {
232+
AnnotatedType<T> type = processAnnotatedTypeEvent.getAnnotatedType();
233+
234+
// validation globally disabled or annotated type has none of needed annotations:
235+
if ( !isExecutableValidationEnabled || !VALIDATEABLE_BEAN_FILTER.test( type.getJavaClass() ) ) {
231236
return;
232237
}
233238

234-
AnnotatedType<T> type = processAnnotatedTypeEvent.getAnnotatedType();
235239
Set<AnnotatedCallable<? super T>> constrainedCallables = determineConstrainedCallables( type );
236240

237241
if ( !constrainedCallables.isEmpty() ) {
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
/*
2+
* Hibernate Validator, declare and validate application constraints
3+
*
4+
* License: Apache License, Version 2.0
5+
* See the license.txt file in the root directory or <http://www.apache.org/licenses/LICENSE-2.0>.
6+
*/
7+
package org.hibernate.validator.cdi.internal;
8+
9+
import java.lang.annotation.Annotation;
10+
import java.lang.reflect.AnnotatedParameterizedType;
11+
import java.lang.reflect.AnnotatedType;
12+
import java.lang.reflect.Constructor;
13+
import java.lang.reflect.Executable;
14+
import java.lang.reflect.Field;
15+
import java.lang.reflect.Method;
16+
import java.util.Collections;
17+
import java.util.HashSet;
18+
import java.util.Set;
19+
import java.util.function.Predicate;
20+
21+
import javax.enterprise.inject.spi.WithAnnotations;
22+
import javax.validation.Constraint;
23+
import javax.validation.Valid;
24+
import javax.validation.executable.ValidateOnExecution;
25+
26+
import org.hibernate.validator.internal.util.CollectionHelper;
27+
import org.hibernate.validator.internal.util.classhierarchy.ClassHierarchyHelper;
28+
29+
/**
30+
* A filter that checks if the passed class has any of these annotations:
31+
* <ul>
32+
* <li>{@link Valid}</li>
33+
* <li>{@link Constraint}</li>
34+
* <li>{@link ValidateOnExecution}</li>
35+
* </ul>
36+
* <p>
37+
* on a type level, or on any member, or any parameter of any member. Super classes and all implemented interfaces are
38+
* considered while looking for these annotations.
39+
* <p>
40+
* The annotation may be applied as a meta-annotation on any annotation considered.
41+
* <p>
42+
* This filter is required as {@link WithAnnotations} does not look for annotations into the class hierarchy.
43+
*
44+
* @author Marko Bekhta
45+
*/
46+
public class ValidateableBeanFilter implements Predicate<Class<?>> {
47+
48+
private static final Set<Class<? extends Annotation>> MATCHING_ANNOTATIONS = Collections.unmodifiableSet(
49+
CollectionHelper.asSet( Valid.class, Constraint.class, ValidateOnExecution.class )
50+
);
51+
52+
@Override
53+
public boolean test(Class<?> type) {
54+
for ( Class<?> clazz : ClassHierarchyHelper.getHierarchy( type ) ) {
55+
if ( hasMatchingAnnotation( clazz ) ) {
56+
return true;
57+
}
58+
}
59+
60+
return false;
61+
}
62+
63+
private boolean hasMatchingAnnotation(Class<?> clazz) {
64+
Set<Annotation> processedAnnotations = new HashSet<>();
65+
// 1. Is present on a type level:
66+
if ( containsMatchingAnnotation( clazz.getDeclaredAnnotations(), processedAnnotations ) ) {
67+
return true;
68+
}
69+
70+
// 2. Or on a field level
71+
for ( Field field : clazz.getDeclaredFields() ) {
72+
if ( hasMatchingAnnotation( field.getAnnotatedType(), processedAnnotations ) ) {
73+
return true;
74+
}
75+
if ( containsMatchingAnnotation( field.getDeclaredAnnotations(), processedAnnotations ) ) {
76+
return true;
77+
}
78+
}
79+
// 3. Or on any executable
80+
// 3.1 Constructors
81+
for ( Constructor<?> constructor : clazz.getDeclaredConstructors() ) {
82+
if ( hasMatchingAnnotation( constructor, processedAnnotations ) ) {
83+
return true;
84+
}
85+
}
86+
87+
// 3.2 Methods
88+
for ( Method method : clazz.getDeclaredMethods() ) {
89+
if ( hasMatchingAnnotation( method, processedAnnotations ) ) {
90+
return true;
91+
}
92+
}
93+
return false;
94+
}
95+
96+
private boolean containsMatchingAnnotation(Annotation[] annotations, Set<Annotation> processedAnnotations) {
97+
for ( Annotation annotation : annotations ) {
98+
if ( isMatchingAnnotation( annotation, processedAnnotations ) ) {
99+
return true;
100+
}
101+
}
102+
return false;
103+
}
104+
105+
private boolean hasMatchingAnnotation(AnnotatedType annotatedType, Set<Annotation> processedAnnotations) {
106+
if ( containsMatchingAnnotation( annotatedType.getDeclaredAnnotations(), processedAnnotations ) ) {
107+
return true;
108+
}
109+
if ( annotatedType instanceof AnnotatedParameterizedType ) {
110+
for ( AnnotatedType type : ( (AnnotatedParameterizedType) annotatedType ).getAnnotatedActualTypeArguments() ) {
111+
if ( hasMatchingAnnotation( type, processedAnnotations ) ) {
112+
return true;
113+
}
114+
}
115+
}
116+
return false;
117+
}
118+
119+
private boolean hasMatchingAnnotation(Executable executable, Set<Annotation> processedAnnotations) {
120+
// Check the executable itself first
121+
if ( containsMatchingAnnotation( executable.getDeclaredAnnotations(), processedAnnotations ) ) {
122+
return true;
123+
}
124+
// Check its return value
125+
if ( hasMatchingAnnotation( executable.getAnnotatedReturnType(), processedAnnotations ) ) {
126+
return true;
127+
}
128+
// Then check its parameters
129+
for ( AnnotatedType annotatedParameterType : executable.getAnnotatedParameterTypes() ) {
130+
if ( hasMatchingAnnotation( annotatedParameterType, processedAnnotations ) ) {
131+
return true;
132+
}
133+
}
134+
// NOTE: this check looks to be redundant BUT without it, test on BeanWithCustomConstraintOnParameter
135+
// will fail as executable.getAnnotatedParameterTypes() on BeanWithCustomConstraintOnParameter#doDefault()
136+
// will not contain matching annotations
137+
for ( Annotation[] annotations : executable.getParameterAnnotations() ) {
138+
if ( containsMatchingAnnotation( annotations, processedAnnotations ) ) {
139+
return true;
140+
}
141+
}
142+
return false;
143+
}
144+
145+
private boolean isMatchingAnnotation(Annotation annotationToCheck, Set<Annotation> processedAnnotations) {
146+
// Need to have this to prevent infinite loops, for example on annotations like @Target
147+
if ( !processedAnnotations.add( annotationToCheck ) ) {
148+
return false;
149+
}
150+
Class<? extends Annotation> annotationType = annotationToCheck.annotationType();
151+
if ( MATCHING_ANNOTATIONS.contains( annotationType ) ) {
152+
return true;
153+
}
154+
for ( Annotation annotation : annotationType.getDeclaredAnnotations() ) {
155+
if ( isMatchingAnnotation( annotation, processedAnnotations ) ) {
156+
return true;
157+
}
158+
}
159+
return false;
160+
}
161+
}

0 commit comments

Comments
 (0)