hibernate validator: Remember annotated interface methods

resolves #1888
This commit is contained in:
Erin Schnabel
2019-11-07 16:44:08 -05:00
parent 21d431fe91
commit 45f875bd1c
6 changed files with 92 additions and 2 deletions

View File

@@ -5,8 +5,10 @@ import static io.quarkus.deployment.annotations.ExecutionTime.STATIC_INIT;
import java.lang.annotation.Annotation;
import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Predicate;
@@ -30,6 +32,7 @@ import org.jboss.jandex.AnnotationTarget;
import org.jboss.jandex.ClassInfo;
import org.jboss.jandex.DotName;
import org.jboss.jandex.IndexView;
import org.jboss.jandex.MethodInfo;
import org.jboss.jandex.Type;
import io.quarkus.arc.deployment.AdditionalBeanBuildItem;
@@ -144,6 +147,7 @@ class HibernateValidatorProcessor {
consideredAnnotations.add(VALIDATE_ON_EXECUTION);
Set<DotName> classNamesToBeValidated = new HashSet<>();
Map<DotName, Set<String>> inheritedAnnotationsToBeValidated = new HashMap<>();
for (DotName consideredAnnotation : consideredAnnotations) {
Collection<AnnotationInstance> annotationInstances = indexView.getAnnotations(consideredAnnotation);
@@ -160,6 +164,8 @@ class HibernateValidatorProcessor {
reflectiveMethods.produce(new ReflectiveMethodBuildItem(annotation.target().asMethod()));
contributeClassMarkedForCascadingValidation(classNamesToBeValidated, indexView, consideredAnnotation,
annotation.target().asMethod().returnType());
contributeMethodsWithInheritedValidation(inheritedAnnotationsToBeValidated, indexView,
annotation.target().asMethod());
} else if (annotation.target().kind() == AnnotationTarget.Kind.METHOD_PARAMETER) {
contributeClass(classNamesToBeValidated, indexView,
annotation.target().asMethodParameter().method().declaringClass().name());
@@ -168,6 +174,8 @@ class HibernateValidatorProcessor {
// FIXME this won't work in the case of synthetic parameters
annotation.target().asMethodParameter().method().parameters()
.get(annotation.target().asMethodParameter().position()));
contributeMethodsWithInheritedValidation(inheritedAnnotationsToBeValidated, indexView,
annotation.target().asMethodParameter().method());
} else if (annotation.target().kind() == AnnotationTarget.Kind.CLASS) {
contributeClass(classNamesToBeValidated, indexView, annotation.target().asClass().name());
// no need for reflection in the case of a class level constraint
@@ -183,7 +191,8 @@ class HibernateValidatorProcessor {
annotationsTransformers
.produce(new AnnotationsTransformerBuildItem(
new MethodValidatedAnnotationsTransformer(consideredAnnotations,
additionalJaxRsMethodAnnotationsDotNames)));
additionalJaxRsMethodAnnotationsDotNames,
inheritedAnnotationsToBeValidated)));
Set<Class<?>> classesToBeValidated = new HashSet<>();
for (DotName className : classNamesToBeValidated) {
@@ -245,6 +254,16 @@ class HibernateValidatorProcessor {
}
}
private static void contributeMethodsWithInheritedValidation(Map<DotName, Set<String>> inheritedAnnotationsToBeValidated,
IndexView indexView, MethodInfo method) {
ClassInfo clazz = method.declaringClass();
if (Modifier.isInterface(clazz.flags())) {
// Remember annotated interface methods that must be validated
inheritedAnnotationsToBeValidated.computeIfAbsent(clazz.name(), k -> new HashSet<String>())
.add(method.name().toString());
}
}
private static DotName getClassName(Type type) {
switch (type.kind()) {
case CLASS:

View File

@@ -3,9 +3,11 @@ package io.quarkus.hibernate.validator.deployment;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import org.jboss.jandex.AnnotationTarget.Kind;
import org.jboss.jandex.ClassInfo;
import org.jboss.jandex.DotName;
import org.jboss.jandex.MethodInfo;
@@ -30,10 +32,14 @@ public class MethodValidatedAnnotationsTransformer implements AnnotationsTransfo
private final Set<DotName> consideredAnnotations;
private final Collection<DotName> effectiveJaxRsMethodDefiningAnnotations;
private final Map<DotName, Set<String>> inheritedAnnotationsToBeValidated;
MethodValidatedAnnotationsTransformer(Set<DotName> consideredAnnotations,
Collection<DotName> additionalJaxRsMethodAnnotationsDotNames) {
Collection<DotName> additionalJaxRsMethodAnnotationsDotNames,
Map<DotName, Set<String>> inheritedAnnotationsToBeValidated) {
this.consideredAnnotations = consideredAnnotations;
this.inheritedAnnotationsToBeValidated = inheritedAnnotationsToBeValidated;
this.effectiveJaxRsMethodDefiningAnnotations = new ArrayList<>(
JAXRS_METHOD_ANNOTATIONS.length + additionalJaxRsMethodAnnotationsDotNames.size());
effectiveJaxRsMethodDefiningAnnotations.addAll(Arrays.asList(JAXRS_METHOD_ANNOTATIONS));
@@ -60,6 +66,15 @@ public class MethodValidatedAnnotationsTransformer implements AnnotationsTransfo
private boolean requiresValidation(MethodInfo method) {
if (method.annotations().isEmpty()) {
// This method has no annotations of its own: look for inherited annotations
ClassInfo clazz = method.declaringClass();
String methodName = method.name().toString();
for (Map.Entry<DotName, Set<String>> validatedMethod : inheritedAnnotationsToBeValidated.entrySet()) {
if (clazz.interfaceNames().contains(validatedMethod.getKey())
&& validatedMethod.getValue().contains(methodName)) {
return true;
}
}
return false;
}

View File

@@ -38,6 +38,9 @@ public class HibernateValidatorTestResource
@Inject
GreetingService greetingService;
@Inject
ZipCodeService zipCodeResource;
@GET
@Path("/basic-features")
@Produces(MediaType.TEXT_PLAIN)
@@ -132,6 +135,25 @@ public class HibernateValidatorTestResource
return result.build();
}
@GET
@Path("/test-inherited-constraints")
@Produces(MediaType.TEXT_PLAIN)
public String testInheritedConstraints() {
ResultBuilder result = new ResultBuilder();
zipCodeResource.echoZipCode("12345");
result.append(formatViolations(Collections.emptySet()));
try {
zipCodeResource.echoZipCode("1234");
} catch (ConstraintViolationException e) {
result.append(formatViolations(e.getConstraintViolations()));
}
return result.build();
}
private String formatViolations(Set<? extends ConstraintViolation<?>> violations) {
if (violations.isEmpty()) {
return "passed";

View File

@@ -0,0 +1,10 @@
package io.quarkus.it.hibernate.validator;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
public interface ZipCodeService {
public String echoZipCode(@NotNull @Size(min = 5, max = 5) String zipCode);
}

View File

@@ -0,0 +1,12 @@
package io.quarkus.it.hibernate.validator;
import javax.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class ZipCodeServiceImpl implements ZipCodeService {
@Override
public String echoZipCode(String zipCode) {
return zipCode;
}
}

View File

@@ -103,4 +103,16 @@ public class HibernateValidatorFunctionalityTest {
.then()
.body(is(expected.toString()));
}
@Test
public void testInheritedConstraints() {
StringBuilder expected = new StringBuilder();
expected.append("passed").append("\n")
.append("failed: echoZipCode.arg0 (size must be between 5 and 5)");
RestAssured.when()
.get("/hibernate-validator/test/test-inherited-constraints")
.then()
.body(is(expected.toString()));
}
}