Skip to content

Commit

Permalink
Merge pull request quarkusio#5308 from ebullient/quarkus-1888
Browse files Browse the repository at this point in the history
hibernate validator: Remember annotated interface methods
  • Loading branch information
cescoffier authored Nov 12, 2019
2 parents b245147 + 063a0cf commit 8c589a1
Show file tree
Hide file tree
Showing 8 changed files with 145 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
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;

Expand All @@ -30,6 +32,7 @@
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;
Expand Down Expand Up @@ -144,6 +147,7 @@ public void build(HibernateValidatorRecorder recorder, RecorderContext recorderC
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);
Expand All @@ -160,6 +164,8 @@ public void build(HibernateValidatorRecorder recorder, RecorderContext recorderC
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());
Expand All @@ -168,6 +174,8 @@ public void build(HibernateValidatorRecorder recorder, RecorderContext recorderC
// 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
Expand All @@ -183,7 +191,8 @@ public void build(HibernateValidatorRecorder recorder, RecorderContext recorderC
annotationsTransformers
.produce(new AnnotationsTransformerBuildItem(
new MethodValidatedAnnotationsTransformer(consideredAnnotations,
additionalJaxRsMethodAnnotationsDotNames)));
additionalJaxRsMethodAnnotationsDotNames,
inheritedAnnotationsToBeValidated)));

Set<Class<?>> classesToBeValidated = new HashSet<>();
for (DotName className : classNamesToBeValidated) {
Expand Down Expand Up @@ -245,6 +254,16 @@ private static void contributeClassMarkedForCascadingValidation(Set<DotName> cla
}
}

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:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
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;

Expand All @@ -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));
Expand All @@ -60,6 +66,15 @@ public void transform(TransformationContext transformationContext) {

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;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package io.quarkus.it.hibernate.validator;

import javax.annotation.Priority;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.inject.Alternative;

@Alternative
@Priority(1)
@ApplicationScoped
public class EnhancedGreetingService extends GreetingService {

@Override
public String greeting(String name) {
return super.greeting("Enhanced " + name);
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
package io.quarkus.it.hibernate.validator;

import javax.annotation.Priority;
import javax.enterprise.context.ApplicationScoped;
import javax.validation.constraints.NotNull;

@ApplicationScoped
@Priority(2)
public class GreetingService {

public String greeting(@NotNull String name) {
return "hello " + name;
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ public class HibernateValidatorTestResource
@Inject
GreetingService greetingService;

@Inject
EnhancedGreetingService enhancedGreetingService;

@Inject
ZipCodeService zipCodeResource;

@GET
@Path("/basic-features")
@Produces(MediaType.TEXT_PLAIN)
Expand Down Expand Up @@ -132,6 +138,44 @@ public String testInjection() {
return result.build();
}

@GET
@Path("/test-inherited-implements-constraints")
@Produces(MediaType.TEXT_PLAIN)
public String testInheritedImplementsConstraints() {
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();
}

@GET
@Path("/test-inherited-extends-constraints")
@Produces(MediaType.TEXT_PLAIN)
public String testInheritedExtendsConstraints() {
ResultBuilder result = new ResultBuilder();

enhancedGreetingService.greeting("test");

result.append(formatViolations(Collections.emptySet()));

try {
enhancedGreetingService.greeting(null);
} catch (ConstraintViolationException e) {
result.append(formatViolations(e.getConstraintViolations()));
}

return result.build();
}

private String formatViolations(Set<? extends ConstraintViolation<?>> violations) {
if (violations.isEmpty()) {
return "passed";
Expand Down
Original file line number Diff line number Diff line change
@@ -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);

}
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -103,4 +103,28 @@ public void testInjection() throws Exception {
.then()
.body(is(expected.toString()));
}

@Test
public void testInheritedImplementsConstraints() {
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-implements-constraints")
.then()
.body(is(expected.toString()));
}

@Test
public void testInheritedExtendsConstraints() {
StringBuilder expected = new StringBuilder();
expected.append("passed").append("\n");
expected.append("failed: greeting.arg0 (must not be null)");

RestAssured.when()
.get("/hibernate-validator/test/test-inherited-extends-constraints")
.then()
.body(is(expected.toString()));
}
}

0 comments on commit 8c589a1

Please sign in to comment.