properly implement method inheritance rules wrt ValidateOnExecution
diff --git a/bval-jsr/src/main/java/org/apache/bval/cdi/BValInterceptor.java b/bval-jsr/src/main/java/org/apache/bval/cdi/BValInterceptor.java
index 4735369..042fa7e 100644
--- a/bval-jsr/src/main/java/org/apache/bval/cdi/BValInterceptor.java
+++ b/bval-jsr/src/main/java/org/apache/bval/cdi/BValInterceptor.java
@@ -24,6 +24,8 @@
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@@ -55,9 +57,10 @@
import org.apache.bval.jsr.util.ExecutableTypes;
import org.apache.bval.jsr.util.Methods;
import org.apache.bval.jsr.util.Proxies;
+import org.apache.bval.util.ObjectUtils;
+import org.apache.bval.util.Validate;
import org.apache.bval.util.reflection.Reflection;
import org.apache.bval.util.reflection.Reflection.Interfaces;
-import org.apache.bval.util.reflection.TypeUtils;
/**
* Interceptor class for the {@link BValBinding} {@link InterceptorBinding}.
@@ -69,6 +72,20 @@
// TODO: maybe add it through ASM to be compliant with CDI 1.0 containers using simply this class as a template to
// generate another one for CDI 1.1 impl
public class BValInterceptor implements Serializable {
+ private static Collection<ExecutableType> removeFrom(Collection<ExecutableType> coll,
+ ExecutableType... executableTypes) {
+ Validate.notNull(coll, "collection was null");
+ if (!(coll.isEmpty() || ObjectUtils.isEmptyArray(executableTypes))) {
+ final List<ExecutableType> toRemove = Arrays.asList(executableTypes);
+ if (!Collections.disjoint(coll, toRemove)) {
+ final Set<ExecutableType> result = EnumSet.copyOf(coll);
+ result.removeAll(toRemove);
+ return result;
+ }
+ }
+ return coll;
+ }
+
private transient volatile Set<ExecutableType> classConfiguration;
private transient volatile Map<Signature, Boolean> executableValidation;
@@ -174,13 +191,16 @@
if (classConfiguration == null) {
synchronized (this) {
if (classConfiguration == null) {
- final ValidateOnExecution annotation = CDI.current().getBeanManager()
- .createAnnotatedType(targetClass).getAnnotation(ValidateOnExecution.class);
+ final AnnotatedType<?> annotatedType = CDI.current().getBeanManager()
+ .createAnnotatedType(targetClass);
- if (annotation == null) {
- classConfiguration = globalConfiguration.getGlobalExecutableTypes();
+ if (annotatedType.isAnnotationPresent(ValidateOnExecution.class)) {
+ // implicit does not apply at the class level:
+ classConfiguration = ExecutableTypes.interpret(
+ removeFrom(Arrays.asList(annotatedType.getAnnotation(ValidateOnExecution.class).type()),
+ ExecutableType.IMPLICIT));
} else {
- classConfiguration = ExecutableTypes.interpret(annotation.type());
+ classConfiguration = globalConfiguration.getGlobalExecutableTypes();
}
}
}
@@ -203,41 +223,49 @@
}
private <T> boolean computeIsMethodValidated(Class<T> targetClass, Method method) {
- Collection<ExecutableType> declaredExecutableTypes = null;
+ final Signature signature = Signature.of(method);
+
+ AnnotatedMethod<?> declaringMethod = null;
for (final Class<?> c : Reflection.hierarchy(targetClass, Interfaces.INCLUDE)) {
final AnnotatedType<?> annotatedType = CDI.current().getBeanManager().createAnnotatedType(c);
final AnnotatedMethod<?> annotatedMethod = annotatedType.getMethods().stream()
- .filter(am -> Signature.of(am.getJavaMember()).equals(Signature.of(method))).findFirst().orElse(null);
+ .filter(am -> Signature.of(am.getJavaMember()).equals(signature)).findFirst().orElse(null);
- if (annotatedMethod == null) {
- continue;
+ if (annotatedMethod != null) {
+ declaringMethod = annotatedMethod;
}
- if (annotatedMethod.isAnnotationPresent(ValidateOnExecution.class)) {
- final List<ExecutableType> validatedTypesOnMethod =
- Arrays.asList(annotatedMethod.getAnnotation(ValidateOnExecution.class).type());
+ }
+ if (declaringMethod == null) {
+ return false;
+ }
+ final Collection<ExecutableType> declaredExecutableTypes;
- // implicit directly on method -> early return:
- if (validatedTypesOnMethod.contains(ExecutableType.IMPLICIT)) {
- return true;
- }
- declaredExecutableTypes = validatedTypesOnMethod;
- // ignore the hierarchy once the lowest method is found:
- break;
+ if (declaringMethod.isAnnotationPresent(ValidateOnExecution.class)) {
+ final List<ExecutableType> validatedTypesOnMethod =
+ Arrays.asList(declaringMethod.getAnnotation(ValidateOnExecution.class).type());
+
+ // implicit directly on method -> early return:
+ if (validatedTypesOnMethod.contains(ExecutableType.IMPLICIT)) {
+ return true;
}
- if (declaredExecutableTypes == null) {
- if (annotatedType.isAnnotationPresent(ValidateOnExecution.class)) {
- declaredExecutableTypes =
- Arrays.asList(annotatedType.getAnnotation(ValidateOnExecution.class).type());
+ declaredExecutableTypes = validatedTypesOnMethod;
+ } else {
+ final AnnotatedType<?> declaringType = declaringMethod.getDeclaringType();
+ if (declaringType.isAnnotationPresent(ValidateOnExecution.class)) {
+ // IMPLICIT is meaningless at class level:
+ declaredExecutableTypes =
+ removeFrom(Arrays.asList(declaringType.getAnnotation(ValidateOnExecution.class).type()),
+ ExecutableType.IMPLICIT);
+ } else {
+ final Package pkg = declaringType.getJavaClass().getPackage();
+ if (pkg != null && pkg.isAnnotationPresent(ValidateOnExecution.class)) {
+ // presumably IMPLICIT is likewise meaningless at package level:
+ declaredExecutableTypes = removeFrom(
+ Arrays.asList(pkg.getAnnotation(ValidateOnExecution.class).type()), ExecutableType.IMPLICIT);
} else {
- final Optional<Package> pkg = Optional.of(annotatedType).map(AnnotatedType::getBaseType)
- .map(t -> TypeUtils.getRawType(t, null)).map(Class::getPackage)
- .filter(p -> p.isAnnotationPresent(ValidateOnExecution.class));
- if (pkg.isPresent()) {
- declaredExecutableTypes =
- Arrays.asList(pkg.get().getAnnotation(ValidateOnExecution.class).type());
- }
+ declaredExecutableTypes = null;
}
}
}
diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/HierarchyBuilder.java b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/HierarchyBuilder.java
index b449a6b..da84490 100644
--- a/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/HierarchyBuilder.java
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/HierarchyBuilder.java
@@ -129,6 +129,7 @@
return getters;
}
final Map<String, MetadataBuilder.ForContainer<Method>> result = new LinkedHashMap<>();
+ final List<ContainerDelegate<Method>> delegates = new ArrayList<>();
getters.forEach((k, v) -> {
final Method getter = Methods.getter(hierarchyElement.getHost(), k);
@@ -136,8 +137,12 @@
Exceptions.raiseIf(getter == null, IllegalStateException::new,
"delegate builder specified unknown getter");
- result.put(k, new ContainerDelegate<Method>(v, new Meta.ForMethod(getter)));
+ final ContainerDelegate<Method> d = new ContainerDelegate<>(v, new Meta.ForMethod(getter));
+ result.put(k, d);
+ delegates.add(d);
});
+ Liskov.validateValidateOnExecution(delegates);
+
return result;
}
@@ -148,11 +153,17 @@
return methods;
}
final Map<Signature, MetadataBuilder.ForExecutable<Method>> result = new LinkedHashMap<>();
- methods
- .forEach((k, v) -> result.put(k,
- new ExecutableDelegate<>(v, new Meta.ForMethod(
+ final List<ExecutableDelegate<Method>> delegates = new ArrayList<>();
+ methods.forEach((k, v) -> {
+ final ExecutableDelegate<Method> d = new ExecutableDelegate<>(v,
+ new Meta.ForMethod(
Reflection.getDeclaredMethod(hierarchyElement.getHost(), k.getName(), k.getParameterTypes())),
- ParameterNameProvider::getParameterNames)));
+ ParameterNameProvider::getParameterNames);
+ result.put(k, d);
+ delegates.add(d);
+ });
+ Liskov.validateValidateOnExecution(delegates);
+
return result;
}
}
diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/Liskov.java b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/Liskov.java
index 956f937..18dfadc 100644
--- a/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/Liskov.java
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/Liskov.java
@@ -21,7 +21,6 @@
import java.util.Collections;
import java.util.EnumSet;
import java.util.LinkedHashMap;
-import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
@@ -34,6 +33,7 @@
import javax.validation.ConstraintDeclarationException;
import javax.validation.ElementKind;
import javax.validation.Valid;
+import javax.validation.executable.ValidateOnExecution;
import org.apache.bval.jsr.metadata.HierarchyBuilder.ContainerDelegate;
import org.apache.bval.jsr.metadata.HierarchyBuilder.ElementDelegate;
@@ -44,7 +44,7 @@
class Liskov {
//@formatter:off
private enum ValidationElement {
- constraints, cascades, groupConversions;
+ constraints, cascades, groupConversions, validateOnExecution;
}
private enum StrengtheningIssue implements Predicate<Map<Meta<?>, Set<ValidationElement>>> {
@@ -106,19 +106,19 @@
}
}
- static void validateContainerHierarchy(List<? extends ContainerDelegate<?>> delegates, ElementKind elementKind) {
+ static void validateContainerHierarchy(Collection<? extends ContainerDelegate<?>> delegates, ElementKind elementKind) {
if (Validate.notNull(delegates, "delegates").isEmpty()) {
return;
}
if (Validate.notNull(elementKind, "elementKind") == ElementKind.CONTAINER_ELEMENT) {
- elementKind = getContainer(delegates.get(0).getHierarchyElement());
+ elementKind = getContainer(delegates.iterator().next().getHierarchyElement());
}
switch (elementKind) {
case RETURN_VALUE:
noRedeclarationOfReturnValueCascading(delegates);
final Map<Meta<?>, Set<ValidationElement>> detectedValidationElements =
- detectValidationElements(delegates, detectGroupConversion());
+ detectValidationElements(delegates, ElementDelegate::getHierarchyElement, detectGroupConversion());
// pre-check return value overridden hierarchy:
Stream.of(StrengtheningIssue.values())
@@ -135,13 +135,17 @@
}
}
- static void validateCrossParameterHierarchy(List<? extends ElementDelegate<?, ?>> delegates) {
+ static void validateCrossParameterHierarchy(Collection<? extends ElementDelegate<?, ?>> delegates) {
if (Validate.notNull(delegates, "delegates").isEmpty()) {
return;
}
noStrengtheningOfPreconditions(delegates, detectConstraints());
}
+ static void validateValidateOnExecution(Collection<? extends HierarchyDelegate<?, ?>> delegates) {
+ noStrengtheningOfPreconditions(delegates, detectValidateOnExecution());
+ }
+
private static ElementKind getContainer(Meta<?> meta) {
Meta<?> m = meta;
while (m.getElementType() == ElementType.TYPE_USE) {
@@ -157,7 +161,7 @@
}
}
- private static void noRedeclarationOfReturnValueCascading(List<? extends ContainerDelegate<?>> delegates) {
+ private static void noRedeclarationOfReturnValueCascading(Collection<? extends ContainerDelegate<?>> delegates) {
final Map<Class<?>, Meta<?>> cascadedReturnValues =
delegates.stream().filter(ContainerDelegate::isCascade).map(HierarchyDelegate::getHierarchyElement)
.collect(Collectors.toMap(Meta::getDeclaringClass, Function.identity()));
@@ -171,11 +175,11 @@
}
@SafeVarargs
- private static <D extends ElementDelegate<?, ?>> void noStrengtheningOfPreconditions(List<? extends D> delegates,
+ private static <D extends HierarchyDelegate<?, ?>> void noStrengtheningOfPreconditions(Collection<? extends D> delegates,
Function<? super D, ValidationElement>... detectors) {
final Map<Meta<?>, Set<ValidationElement>> detectedValidationElements =
- detectValidationElements(delegates, detectors);
+ detectValidationElements(delegates, HierarchyDelegate::getHierarchyElement, detectors);
if (detectedValidationElements.isEmpty()) {
return;
@@ -186,11 +190,11 @@
}
@SafeVarargs
- private static <D extends ElementDelegate<?, ?>> Map<Meta<?>, Set<ValidationElement>> detectValidationElements(
- List<? extends D> delegates, Function<? super D, ValidationElement>... detectors) {
+ private static <T> Map<Meta<?>, Set<ValidationElement>> detectValidationElements(Collection<? extends T> delegates,
+ Function<? super T, Meta<?>> toMeta, Function<? super T, ValidationElement>... detectors) {
final Map<Meta<?>, Set<ValidationElement>> detectedValidationElements = new LinkedHashMap<>();
delegates.forEach(d -> {
- detectedValidationElements.put(d.getHierarchyElement(),
+ detectedValidationElements.put(toMeta.apply(d),
Stream.of(detectors).map(dt -> dt.apply(d)).filter(Objects::nonNull)
.collect(Collectors.toCollection(() -> EnumSet.noneOf(ValidationElement.class))));
});
@@ -217,6 +221,11 @@
return d -> d.getGroupConversions().isEmpty() ? null : ValidationElement.groupConversions;
}
+ private static Function<HierarchyDelegate<?, ?>, ValidationElement> detectValidateOnExecution() {
+ return d -> d.getHierarchyElement().getHost().isAnnotationPresent(ValidateOnExecution.class)
+ ? ValidationElement.validateOnExecution : null;
+ }
+
private Liskov() {
}
}