XML config-related work for more TCK progress
Project: http://git-wip-us.apache.org/repos/asf/bval/repo Commit: http://git-wip-us.apache.org/repos/asf/bval/commit/e243ee93 Tree: http://git-wip-us.apache.org/repos/asf/bval/tree/e243ee93 Diff: http://git-wip-us.apache.org/repos/asf/bval/diff/e243ee93 Branch: refs/heads/bv2 Commit: e243ee9334864c7a160de076e405515bed8b5e0e Parents: cc8a72c Author: Matt Benson <[email protected]> Authored: Wed Apr 18 16:37:30 2018 -0500 Committer: Matt Benson <[email protected]> Committed: Tue Oct 16 12:28:20 2018 -0500 ---------------------------------------------------------------------- .../apache/bval/jsr/ApacheValidatorFactory.java | 2 - .../bval/jsr/descriptor/DescriptorManager.java | 3 +- .../bval/jsr/descriptor/MetadataReader.java | 8 +- .../apache/bval/jsr/descriptor/ParameterD.java | 32 ++- .../bval/jsr/job/ValidateReturnValue.java | 17 +- .../bval/jsr/metadata/CompositeBuilder.java | 12 +- .../apache/bval/jsr/metadata/DualBuilder.java | 130 +++++++++- .../java/org/apache/bval/jsr/metadata/Meta.java | 19 +- .../org/apache/bval/jsr/metadata/Signature.java | 5 + .../apache/bval/jsr/metadata/XmlBuilder.java | 97 ++++---- .../apache/bval/jsr/xml/MappingValidator.java | 244 +++++++++++++++++++ .../bval/jsr/xml/ValidationMappingParser.java | 20 +- 12 files changed, 512 insertions(+), 77 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/bval/blob/e243ee93/bval-jsr/src/main/java/org/apache/bval/jsr/ApacheValidatorFactory.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/ApacheValidatorFactory.java b/bval-jsr/src/main/java/org/apache/bval/jsr/ApacheValidatorFactory.java index 2b67c30..481b501 100644 --- a/bval-jsr/src/main/java/org/apache/bval/jsr/ApacheValidatorFactory.java +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/ApacheValidatorFactory.java @@ -369,7 +369,5 @@ public class ApacheValidatorFactory implements ValidatorFactory, Cloneable { }; participantFactory.loadServices(MetadataSource.class) .forEach(ms -> ms.process(configuration, getConstraintsCache()::add, addBuilder)); - - getMetadataBuilders().getCustomizedTypes().forEach(getDescriptorManager()::getBeanDescriptor); } } http://git-wip-us.apache.org/repos/asf/bval/blob/e243ee93/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/DescriptorManager.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/DescriptorManager.java b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/DescriptorManager.java index 9495f7a..7c84f87 100644 --- a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/DescriptorManager.java +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/DescriptorManager.java @@ -90,7 +90,8 @@ public class DescriptorManager { final MetadataBuilder.ForBean<T> customBuilder = new HierarchyBuilder(validatorFactory, this::customBuilder).forBean(beanClass); - return customBuilder.isEmpty() ? primaryBuilder : DualBuilder.forBean(primaryBuilder, customBuilder); + return customBuilder.isEmpty() ? primaryBuilder + : DualBuilder.forBean(beanClass, primaryBuilder, customBuilder, validatorFactory); } private <T> MetadataBuilder.ForBean<T> customBuilder(Class<T> beanClass) { http://git-wip-us.apache.org/repos/asf/bval/blob/e243ee93/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/MetadataReader.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/MetadataReader.java b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/MetadataReader.java index ec4fc66..0828933 100644 --- a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/MetadataReader.java +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/MetadataReader.java @@ -19,7 +19,6 @@ package org.apache.bval.jsr.descriptor; import java.lang.annotation.Annotation; -import java.lang.annotation.ElementType; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.AnnotatedType; import java.lang.reflect.Constructor; @@ -75,7 +74,10 @@ 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.commons.weaver.privilizer.Privilizing; +import org.apache.commons.weaver.privilizer.Privilizing.CallTo; +@Privilizing(@CallTo(Reflection.class)) class MetadataReader { class ForElement<E extends AnnotatedElement, B extends MetadataBuilder.ForElement<E>> { @@ -294,10 +296,6 @@ class MetadataReader { final Set<GroupConversion> groupConversions = builder.getGroupConversions(meta); if (!groupConversions.isEmpty()) { if (!isCascaded()) { - // ignore group conversions without cascade on property getters: - if (meta.getElementType() == ElementType.METHOD && Methods.isGetter((Method) meta.getHost())) { - return Collections.emptySet(); - } Exceptions.raise(ConstraintDeclarationException::new, "@%s declared without @%s on %s", ConvertGroup.class.getSimpleName(), Valid.class.getSimpleName(), meta.describeHost()); } http://git-wip-us.apache.org/repos/asf/bval/blob/e243ee93/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ParameterD.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ParameterD.java b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ParameterD.java index 52e3040..9bb4276 100644 --- a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ParameterD.java +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ParameterD.java @@ -18,7 +18,11 @@ */ package org.apache.bval.jsr.descriptor; +import java.lang.reflect.Array; +import java.lang.reflect.GenericArrayType; import java.lang.reflect.Parameter; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; import javax.validation.metadata.ParameterDescriptor; @@ -42,7 +46,7 @@ public class ParameterD<P extends ExecutableD<?, ?, P>> extends CascadableContai this.index = index; name = reader.meta.getName(); - type = TypeUtils.getRawType(reader.meta.getType(), parent.getElementClass()); + type = resolveType(); } @Override @@ -59,4 +63,30 @@ public class ParameterD<P extends ExecutableD<?, ?, P>> extends CascadableContai public String getName() { return name; } + + private Class<?> resolveType() { + final Class<?> declaringClass = getTarget().getDeclaringExecutable().getDeclaringClass(); + + Type t = getTarget().getParameterizedType(); + + int arrayDepth = 0; + while (t instanceof GenericArrayType) { + arrayDepth++; + t = ((GenericArrayType) t).getGenericComponentType(); + } + + Class<?> result = null; + + while (result == null) { + result = TypeUtils.getRawType(t, declaringClass); + if (result != null) { + break; + } + if (t instanceof TypeVariable<?>) { + final TypeVariable<?> tv = (TypeVariable<?>) t; + t = tv.getBounds()[0]; + } + } + return arrayDepth > 0 ? Array.newInstance(result, new int[arrayDepth]).getClass() : result; + } } http://git-wip-us.apache.org/repos/asf/bval/blob/e243ee93/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidateReturnValue.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidateReturnValue.java b/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidateReturnValue.java index 774566a..2cf92e3 100644 --- a/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidateReturnValue.java +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidateReturnValue.java @@ -21,11 +21,12 @@ import java.lang.reflect.Executable; import java.lang.reflect.Method; import java.lang.reflect.Type; +import javax.validation.metadata.ExecutableDescriptor; + import org.apache.bval.jsr.ApacheFactoryContext; import org.apache.bval.jsr.ConstraintViolationImpl; import org.apache.bval.jsr.GraphContext; import org.apache.bval.jsr.descriptor.ConstraintD; -import org.apache.bval.jsr.descriptor.ExecutableD; import org.apache.bval.jsr.descriptor.ReturnValueD; import org.apache.bval.jsr.metadata.Meta; import org.apache.bval.jsr.util.NodeImpl; @@ -57,11 +58,9 @@ public abstract class ValidateReturnValue<E extends Executable, T> extends Valid return (Class<T>) object.getClass(); } - @SuppressWarnings("unchecked") @Override - protected ExecutableD<Method, ?, ?> describe() { - return (ExecutableD<Method, ?, ?>) validatorContext.getDescriptorManager() - .getBeanDescriptor(object.getClass()) + protected ExecutableDescriptor describe() { + return validatorContext.getDescriptorManager().getBeanDescriptor(object.getClass()) .getConstraintsForMethod(executable.getName(), executable.getParameterTypes()); } @@ -96,11 +95,9 @@ public abstract class ValidateReturnValue<E extends Executable, T> extends Valid return (Class<T>) executable.getDeclaringClass(); } - @SuppressWarnings("unchecked") @Override - protected ExecutableD<Constructor<T>, ?, ?> describe() { - return (ExecutableD<Constructor<T>, ?, ?>) validatorContext.getDescriptorManager() - .getBeanDescriptor(executable.getDeclaringClass()) + protected ExecutableDescriptor describe() { + return validatorContext.getDescriptorManager().getBeanDescriptor(executable.getDeclaringClass()) .getConstraintsForConstructor(executable.getParameterTypes()); } @@ -147,7 +144,7 @@ public abstract class ValidateReturnValue<E extends Executable, T> extends Valid return describe() != null; } - protected abstract ExecutableD<?, ?, ?> describe(); + protected abstract ExecutableDescriptor describe(); protected abstract T getRootBean(); http://git-wip-us.apache.org/repos/asf/bval/blob/e243ee93/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/CompositeBuilder.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/CompositeBuilder.java b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/CompositeBuilder.java index 60419fb..b02f149 100644 --- a/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/CompositeBuilder.java +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/CompositeBuilder.java @@ -237,7 +237,7 @@ public class CompositeBuilder { private final AnnotationBehaviorMergeStrategy annotationBehaviorStrategy; protected final ApacheValidatorFactory validatorFactory; - CompositeBuilder(ApacheValidatorFactory validatorFactory, + protected CompositeBuilder(ApacheValidatorFactory validatorFactory, AnnotationBehaviorMergeStrategy annotationBehaviorMergeStrategy) { super(); this.annotationBehaviorStrategy = @@ -246,7 +246,15 @@ public class CompositeBuilder { } public <T> Collector<MetadataBuilder.ForBean<T>, ?, MetadataBuilder.ForBean<T>> compose() { - return Collectors.collectingAndThen(Collectors.toList(), CompositeBuilder.ForBean::new); + return Collectors.collectingAndThen(Collectors.toList(), + delegates -> delegates.isEmpty() ? EmptyBuilder.instance().forBean() + : delegates.size() == 1 ? delegates.get(0) : new CompositeBuilder.ForBean<>(delegates)); + } + + public <E extends AnnotatedElement> Collector<MetadataBuilder.ForContainer<E>, ?, MetadataBuilder.ForContainer<E>> composeContainer() { + return Collectors.collectingAndThen(Collectors.toList(), + delegates -> delegates.isEmpty() ? EmptyBuilder.instance().forContainer() + : delegates.size() == 1 ? delegates.get(0) : new CompositeBuilder.ForContainer<>(delegates)); } protected final <E extends Executable> List<Meta<Parameter>> getMetaParameters(Meta<E> meta, http://git-wip-us.apache.org/repos/asf/bval/blob/e243ee93/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/DualBuilder.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/DualBuilder.java b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/DualBuilder.java index 3c6b07b..7ca5ebd 100644 --- a/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/DualBuilder.java +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/DualBuilder.java @@ -28,7 +28,9 @@ import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; +import java.util.TreeMap; import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.Supplier; @@ -36,7 +38,9 @@ import java.util.stream.Collectors; import java.util.stream.IntStream; import java.util.stream.Stream; +import org.apache.bval.jsr.ApacheValidatorFactory; import org.apache.bval.jsr.groups.GroupConversion; +import org.apache.bval.jsr.util.Methods; import org.apache.bval.jsr.util.ToUnmodifiable; import org.apache.bval.util.Validate; @@ -236,8 +240,128 @@ public class DualBuilder { } } - public static <T> MetadataBuilder.ForBean<T> forBean(MetadataBuilder.ForBean<T> primaryDelegate, - MetadataBuilder.ForBean<T> customDelegate) { - return new DualBuilder.ForBean<>(primaryDelegate, customDelegate); + private static class CustomWrapper { + private static class ForBean<T> implements MetadataBuilder.ForBean<T> { + + private final MetadataBuilder.ForBean<T> wrapped; + private final Map<String, MetadataBuilder.ForContainer<Method>> getters; + private final Map<Signature, MetadataBuilder.ForExecutable<Method>> methods; + + ForBean(MetadataBuilder.ForBean<T> wrapped, Map<String, MetadataBuilder.ForContainer<Method>> getters, + Map<Signature, MetadataBuilder.ForExecutable<Method>> methods) { + super(); + this.wrapped = Validate.notNull(wrapped, "wrapped"); + this.getters = Validate.notNull(getters, "getters"); + this.methods = Validate.notNull(methods, "methods"); + } + + @Override + public AnnotationBehavior getAnnotationBehavior() { + return wrapped.getAnnotationBehavior(); + } + + @Override + public MetadataBuilder.ForClass<T> getClass(Meta<Class<T>> meta) { + return wrapped.getClass(meta); + } + + @Override + public Map<String, MetadataBuilder.ForContainer<Field>> getFields(Meta<Class<T>> meta) { + return wrapped.getFields(meta); + } + + @Override + public Map<String, MetadataBuilder.ForContainer<Method>> getGetters(Meta<Class<T>> meta) { + return getters; + } + + @Override + public Map<Signature, MetadataBuilder.ForExecutable<Constructor<? extends T>>> getConstructors( + Meta<Class<T>> meta) { + return wrapped.getConstructors(meta); + } + + @Override + public Map<Signature, MetadataBuilder.ForExecutable<Method>> getMethods(Meta<Class<T>> meta) { + return methods; + } + } + + private static class ForGetterMethod implements MetadataBuilder.ForExecutable<Method> { + private final MetadataBuilder.ForContainer<Method> returnValue; + + private ForGetterMethod(MetadataBuilder.ForContainer<Method> returnValue) { + super(); + this.returnValue = Validate.notNull(returnValue, "returnValue"); + } + + @Override + public AnnotationBehavior getAnnotationBehavior() { + return returnValue.getAnnotationBehavior(); + } + + @Override + public MetadataBuilder.ForContainer<Method> getReturnValue(Meta<Method> meta) { + return returnValue; + } + + @Override + public MetadataBuilder.ForElement<Method> getCrossParameter(Meta<Method> meta) { + return EmptyBuilder.instance().forElement(); + } + + @Override + public List<MetadataBuilder.ForContainer<Parameter>> getParameters(Meta<Method> meta) { + return Collections.emptyList(); + } + } + } + + public static <T> MetadataBuilder.ForBean<T> forBean(Class<T> beanClass, MetadataBuilder.ForBean<T> primaryDelegate, + MetadataBuilder.ForBean<T> customDelegate, ApacheValidatorFactory validatorFactory) { + return new DualBuilder.ForBean<>(primaryDelegate, wrapCustom(customDelegate, beanClass, validatorFactory)); + } + + private static <T> MetadataBuilder.ForBean<T> wrapCustom(MetadataBuilder.ForBean<T> customDelegate, + Class<T> beanClass, ApacheValidatorFactory validatorFactory) { + final Meta.ForClass<T> meta = new Meta.ForClass<>(beanClass); + + final Map<String, MetadataBuilder.ForContainer<Method>> getters = customDelegate.getGetters(meta); + final Map<Signature, MetadataBuilder.ForExecutable<Method>> methods = customDelegate.getMethods(meta); + + if (getters.isEmpty() && methods.keySet().stream().noneMatch(Signature::isGetter)) { + // nothing to merge + return customDelegate; + } + final CompositeBuilder composite = + CompositeBuilder.with(validatorFactory, AnnotationBehaviorMergeStrategy.consensus()); + + final Map<String, MetadataBuilder.ForContainer<Method>> mergedGetters = new TreeMap<>(getters); + + methods.forEach((k, v) -> { + if (k.isGetter()) { + mergedGetters.compute(Methods.propertyName(k.getName()), (p, d) -> { + final Method getter = Methods.getter(beanClass, p); + return Stream.of(d, v.getReturnValue(new Meta.ForMethod(getter))).filter(Objects::nonNull) + .collect(composite.composeContainer()); + }); + } + }); + final Map<Signature, MetadataBuilder.ForExecutable<Method>> mergedMethods = new TreeMap<>(methods); + + getters.forEach((k, v) -> { + final Method getter = Methods.getter(beanClass, k); + final Signature signature = Signature.of(getter); + + final MetadataBuilder.ForContainer<Method> rv; + if (methods.containsKey(signature)) { + rv = Stream.of(methods.get(signature).getReturnValue(new Meta.ForMethod(getter)), v) + .collect(composite.composeContainer()); + } else { + rv = v; + } + mergedMethods.put(signature, new CustomWrapper.ForGetterMethod(rv)); + }); + return new CustomWrapper.ForBean<>(customDelegate, mergedGetters, mergedMethods); } } http://git-wip-us.apache.org/repos/asf/bval/blob/e243ee93/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/Meta.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/Meta.java b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/Meta.java index 48b6d96..dcdf0a6 100644 --- a/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/Meta.java +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/Meta.java @@ -27,12 +27,16 @@ import java.lang.reflect.Field; import java.lang.reflect.Member; import java.lang.reflect.Method; import java.lang.reflect.Parameter; +import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.util.Map; import java.util.Objects; import org.apache.bval.util.EmulatedAnnotatedType; import org.apache.bval.util.Lazy; import org.apache.bval.util.Validate; +import org.apache.bval.util.reflection.TypeUtils; /** * Validation class model. @@ -214,7 +218,7 @@ public abstract class Meta<E extends AnnotatedElement> { @Override public Type getType() { - return getHost().getType(); + return getHost().getParameterizedType(); } @Override @@ -261,7 +265,18 @@ public abstract class Meta<E extends AnnotatedElement> { @Override public Type getType() { - return getHost().getType(); + Type result = getHost().getType(); + if (result instanceof TypeVariable<?>) { + final Type parentType = parent.getType(); + if (parentType instanceof ParameterizedType) { + final Map<TypeVariable<?>, Type> typeArguments = + TypeUtils.getTypeArguments((ParameterizedType) parentType); + if (typeArguments.containsKey(result)) { + return typeArguments.get(result); + } + } + } + return result; } @Override http://git-wip-us.apache.org/repos/asf/bval/blob/e243ee93/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/Signature.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/Signature.java b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/Signature.java index 2f28a3e..e8e534c 100644 --- a/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/Signature.java +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/Signature.java @@ -24,6 +24,7 @@ import java.util.Optional; import java.util.stream.Collectors; import java.util.stream.Stream; +import org.apache.bval.jsr.util.Methods; import org.apache.bval.util.Comparators; import org.apache.bval.util.Lazy; import org.apache.bval.util.LazyInt; @@ -83,4 +84,8 @@ public final class Signature implements Comparable<Signature> { public int compareTo(Signature sig) { return COMPARATOR.compare(this, sig); } + + public boolean isGetter() { + return parameterTypes.length == 0 && Methods.isGetter(name); + } } http://git-wip-us.apache.org/repos/asf/bval/blob/e243ee93/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/XmlBuilder.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/XmlBuilder.java b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/XmlBuilder.java index 5713402..b96f194 100644 --- a/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/XmlBuilder.java +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/XmlBuilder.java @@ -27,16 +27,13 @@ import java.lang.reflect.Executable; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Parameter; -import java.math.BigDecimal; import java.util.Collections; -import java.util.EnumSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.function.Function; -import java.util.function.Predicate; import java.util.function.Supplier; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -66,6 +63,7 @@ import org.apache.bval.jsr.xml.GetterType; import org.apache.bval.jsr.xml.GroupConversionType; import org.apache.bval.jsr.xml.GroupSequenceType; import org.apache.bval.jsr.xml.GroupsType; +import org.apache.bval.jsr.xml.MappingValidator; import org.apache.bval.jsr.xml.MethodType; import org.apache.bval.jsr.xml.ParameterType; import org.apache.bval.jsr.xml.PayloadType; @@ -73,6 +71,7 @@ import org.apache.bval.jsr.xml.ReturnValueType; import org.apache.bval.util.Exceptions; import org.apache.bval.util.Lazy; import org.apache.bval.util.ObjectUtils; +import org.apache.bval.util.StringUtils; import org.apache.bval.util.Validate; import org.apache.bval.util.reflection.Reflection; import org.apache.commons.weaver.privilizer.Privilizing; @@ -83,20 +82,33 @@ public class XmlBuilder { //@formatter:off public enum Version { v10("1.0"), v11("1.1"), v20("2.0"); + //@formatter:on + + static Version of(ConstraintMappingsType constraintMappings) { + Validate.notNull(constraintMappings); + String version = constraintMappings.getVersion(); + if (StringUtils.isBlank(version)) { + return v10; + } + version = version.trim(); + for (Version candidate : values()) { + if (candidate.id.equals(version)) { + return candidate; + } + } + throw new ValidationException("Unknown schema version: " + version); + } - final BigDecimal number; private final String id; private Version(String number) { this.id = number; - this.number = new BigDecimal(number); } public String getId() { return id; } } - //@formatter:on private class ForBean<T> implements MetadataBuilder.ForBean<T> { @@ -458,30 +470,20 @@ public class XmlBuilder { } } - private static final Set<ConstraintAnnotationAttributes> RESERVED_PARAMS = Collections - .unmodifiableSet(EnumSet.of(ConstraintAnnotationAttributes.GROUPS, ConstraintAnnotationAttributes.MESSAGE, - ConstraintAnnotationAttributes.PAYLOAD, ConstraintAnnotationAttributes.VALIDATION_APPLIES_TO)); - - static final <T> T lazy(Lazy<T> lazy, String name) { + private static final <T> T lazy(Lazy<T> lazy, String name) { Validate.validState(lazy != null, "%s not set", name); return lazy.get(); } private final ConstraintMappingsType constraintMappings; - private final BigDecimal version; + private final Version version; public XmlBuilder(ConstraintMappingsType constraintMappings) { super(); this.constraintMappings = constraintMappings; Validate.notNull(constraintMappings, "constraintMappings"); - - BigDecimal v; - try { - v = new BigDecimal(constraintMappings.getVersion()); - } catch (NumberFormatException e) { - v = Version.v10.number; - } - this.version = v; + this.version = Version.of(constraintMappings); + new MappingValidator(constraintMappings, this::resolveClass).validateMappings(); } public Map<Class<?>, MetadataBuilder.ForBean<?>> forBeans() { @@ -494,7 +496,7 @@ public class XmlBuilder { } boolean atLeast(Version v) { - return version.compareTo(v.number) >= 0; + return version.compareTo(v) >= 0; } <T> Class<T> resolveClass(String className) { @@ -555,8 +557,6 @@ public class XmlBuilder { } for (final ElementType elementType : constraint.getElement()) { final String name = elementType.getName(); - checkValidName(name); - final Class<?> returnType = getAnnotationParameterType(annotationClass, name); final Object elementValue = getElementValue(elementType, returnType); annoBuilder.setValue(name, elementValue); @@ -564,11 +564,6 @@ public class XmlBuilder { return annoBuilder.createAnnotation(); } - private void checkValidName(String name) { - Exceptions.raiseIf(RESERVED_PARAMS.stream().map(ConstraintAnnotationAttributes::getAttributeName) - .anyMatch(Predicate.isEqual(name)), ValidationException::new, "%s is a reserved parameter name.", name); - } - private <A extends Annotation> Class<?> getAnnotationParameterType(final Class<A> annotationClass, final String name) { final Method m = Reflection.getPublicMethod(annotationClass, name); @@ -646,27 +641,31 @@ public class XmlBuilder { throw new ConstraintDeclarationException(e); } } - if (Byte.class.equals(returnType) || byte.class.equals(returnType)) { - // spec mandates it: - return Byte.parseByte(value); - } - if (Short.class.equals(returnType) || short.class.equals(returnType)) { - return Short.parseShort(value); - } - if (Integer.class.equals(returnType) || int.class.equals(returnType)) { - return Integer.parseInt(value); - } - if (Long.class.equals(returnType) || long.class.equals(returnType)) { - return Long.parseLong(value); - } - if (Float.class.equals(returnType) || float.class.equals(returnType)) { - return Float.parseFloat(value); - } - if (Double.class.equals(returnType) || double.class.equals(returnType)) { - return Double.parseDouble(value); - } - if (Boolean.class.equals(returnType) || boolean.class.equals(returnType)) { - return Boolean.parseBoolean(value); + try { + if (Byte.class.equals(returnType) || byte.class.equals(returnType)) { + // spec mandates it: + return Byte.parseByte(value); + } + if (Short.class.equals(returnType) || short.class.equals(returnType)) { + return Short.parseShort(value); + } + if (Integer.class.equals(returnType) || int.class.equals(returnType)) { + return Integer.parseInt(value); + } + if (Long.class.equals(returnType) || long.class.equals(returnType)) { + return Long.parseLong(value); + } + if (Float.class.equals(returnType) || float.class.equals(returnType)) { + return Float.parseFloat(value); + } + if (Double.class.equals(returnType) || double.class.equals(returnType)) { + return Double.parseDouble(value); + } + if (Boolean.class.equals(returnType) || boolean.class.equals(returnType)) { + return Boolean.parseBoolean(value); + } + } catch (Exception e) { + Exceptions.raise(ValidationException::new, e, "Unable to coerce value '%s' to %s", value, returnType); } if (Character.class.equals(returnType) || char.class.equals(returnType)) { Exceptions.raiseIf(value.length() > 1, ConstraintDeclarationException::new, http://git-wip-us.apache.org/repos/asf/bval/blob/e243ee93/bval-jsr/src/main/java/org/apache/bval/jsr/xml/MappingValidator.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/xml/MappingValidator.java b/bval-jsr/src/main/java/org/apache/bval/jsr/xml/MappingValidator.java new file mode 100644 index 0000000..f3b017e --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/xml/MappingValidator.java @@ -0,0 +1,244 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.bval.jsr.xml; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import java.util.Collections; +import java.util.EnumSet; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.function.BinaryOperator; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import javax.validation.ValidationException; + +import org.apache.bval.jsr.ConstraintAnnotationAttributes; +import org.apache.bval.jsr.metadata.ContainerElementKey; +import org.apache.bval.jsr.metadata.Meta; +import org.apache.bval.jsr.metadata.Meta.ForConstructor; +import org.apache.bval.jsr.metadata.Signature; +import org.apache.bval.jsr.util.Methods; +import org.apache.bval.util.Exceptions; +import org.apache.bval.util.Validate; +import org.apache.bval.util.reflection.Reflection; +import org.apache.bval.util.reflection.TypeUtils; +import org.apache.commons.weaver.privilizer.Privilizing; +import org.apache.commons.weaver.privilizer.Privilizing.CallTo; + +@Privilizing(@CallTo(Reflection.class)) +public class MappingValidator { + private static final Set<ConstraintAnnotationAttributes> RESERVED_CONSTRAINT_ELEMENT_NAMES = Collections + .unmodifiableSet(EnumSet.of(ConstraintAnnotationAttributes.GROUPS, ConstraintAnnotationAttributes.MESSAGE, + ConstraintAnnotationAttributes.PAYLOAD, ConstraintAnnotationAttributes.VALIDATION_APPLIES_TO)); + + private static <T> BinaryOperator<T> enforceUniqueness(String message, Function<? super T, ?> describe) { + return (t, u) -> { + throw Exceptions.create(ValidationException::new, message, describe.apply(t)); + }; + } + + private final ConstraintMappingsType constraintMappings; + private final Function<String, Class<?>> resolveClass; + + public MappingValidator(ConstraintMappingsType constraintMappings, Function<String, Class<?>> resolveClass) { + super(); + this.constraintMappings = Validate.notNull(constraintMappings, "constraintMappings"); + this.resolveClass = Validate.notNull(resolveClass, "resolveClass"); + } + + public void validateMappings() { + constraintMappings.getBean().stream().map(this::applyChecks).collect(Collectors.toMap(Function.identity(), + Function.identity(), enforceUniqueness("Duplicate XML constrained bean %s", Class::getName))); + } + + private Class<?> applyChecks(BeanType bean) { + final Class<?> t = resolveClass.apply(bean.getClazz()); + + final ClassType classType = bean.getClassType(); + if (classType != null) { + constraints(new Meta.ForClass<>(t), classType.getConstraint()); + } + final Set<String> fieldProperties = fieldProperties(t, bean.getField()); + final Set<String> getterProperties = getterProperties(t, bean.getGetter()); + final Set<Signature> methods = methods(t, bean.getMethod()); + @SuppressWarnings("unused") + final Set<Signature> constructors = constructors(t, bean.getConstructor()); + + final Set<String> propertyOverlap = new HashSet<>(fieldProperties); + propertyOverlap.retainAll(getterProperties); + + if (!propertyOverlap.isEmpty()) { + Exceptions.raise(ValidationException::new, + "The following %s properties were specified via XML field and getter: %s", bean.getClazz(), + propertyOverlap); + } + final Set<String> getterMethodOverlap = methods.stream().filter(s -> s.getParameterTypes().length == 0) + .map(Signature::getName).filter(Methods::isGetter).map(Methods::propertyName) + .filter(getterProperties::contains).collect(Collectors.toSet()); + + if (!getterMethodOverlap.isEmpty()) { + Exceptions.raise(ValidationException::new, + "The following %s getters were specified via XML getter and method: %s", bean.getClazz(), + getterMethodOverlap); + } + return t; + } + + private Set<String> fieldProperties(Class<?> t, List<FieldType> fields) { + return fields.stream().peek(f -> { + final Field fld = Reflection.find(t, c -> Reflection.getDeclaredField(c, f.getName())); + if (fld == null) { + Exceptions.raise(ValidationException::new, "Unknown XML constrained field %s of %s", f.getName(), t); + } + final Meta.ForField metaField = new Meta.ForField(fld); + constraints(metaField, f.getConstraint()); + containerElements(metaField, f.getContainerElementType()); + }).collect(Collectors.toMap(FieldType::getName, Function.identity(), + enforceUniqueness("Duplicate XML constrained field %s of " + t, FieldType::getName))).keySet(); + } + + private Set<String> getterProperties(Class<?> t, List<GetterType> getters) { + return getters.stream().peek(g -> { + final Method getter = Methods.getter(t, g.getName()); + if (getter == null) { + Exceptions.raise(ValidationException::new, "Unknown XML constrained getter for property %s of %s", + g.getName(), t); + } + final Meta.ForMethod metaGetter = new Meta.ForMethod(getter); + constraints(metaGetter, g.getConstraint()); + containerElements(metaGetter, g.getContainerElementType()); + }).collect(Collectors.toMap(GetterType::getName, Function.identity(), + enforceUniqueness("Duplicate XML constrained getter %s of " + t, GetterType::getName))).keySet(); + } + + private Set<Signature> methods(Class<?> t, List<MethodType> methods) { + return methods.stream().map(mt -> { + final Class<?>[] parameterTypes = getParameterTypes(mt.getParameter()); + final Signature result = new Signature(mt.getName(), parameterTypes); + final Method m = Reflection.find(t, c -> Reflection.getDeclaredMethod(c, mt.getName(), parameterTypes)); + Exceptions.raiseIf(m == null, ValidationException::new, "Unknown method %s of %s", result, t); + + Optional.of(mt).map(MethodType::getReturnValue).ifPresent(rv -> { + final Meta.ForMethod metaMethod = new Meta.ForMethod(m); + constraints(metaMethod, rv.getConstraint()); + containerElements(metaMethod, rv.getContainerElementType()); + }); + final Parameter[] params = m.getParameters(); + + IntStream.range(0, parameterTypes.length).forEach(n -> { + final Meta.ForParameter metaParam = new Meta.ForParameter(params[n], params[n].getName()); + final ParameterType parameterType = mt.getParameter().get(n); + constraints(metaParam, parameterType.getConstraint()); + containerElements(metaParam, parameterType.getContainerElementType()); + }); + + return result; + }).collect(Collectors.toSet()); + } + + private Set<Signature> constructors(Class<?> t, List<ConstructorType> ctors) { + return ctors.stream().map(ctor -> { + final Class<?>[] parameterTypes = getParameterTypes(ctor.getParameter()); + final Signature result = new Signature(t.getSimpleName(), parameterTypes); + final Constructor<?> dc = Reflection.getDeclaredConstructor(t, parameterTypes); + Exceptions.raiseIf(dc == null, ValidationException::new, "Unknown %s constructor %s", t, result); + + final ForConstructor<?> metaCtor = new Meta.ForConstructor<>(dc); + constraints(metaCtor, ctor.getReturnValue().getConstraint()); + containerElements(metaCtor, ctor.getReturnValue().getContainerElementType()); + + final Parameter[] params = dc.getParameters(); + + IntStream.range(0, parameterTypes.length).forEach(n -> { + final Meta.ForParameter metaParam = new Meta.ForParameter(params[n], params[n].getName()); + final ParameterType parameterType = ctor.getParameter().get(n); + constraints(metaParam, parameterType.getConstraint()); + containerElements(metaParam, parameterType.getContainerElementType()); + }); + return result; + }).collect(Collectors.toSet()); + } + + private Class<?>[] getParameterTypes(List<ParameterType> paramElements) { + return paramElements.stream().map(ParameterType::getType).map(resolveClass).toArray(Class[]::new); + } + + private Set<ContainerElementKey> containerElements(Meta<?> meta, + List<ContainerElementTypeType> containerElementTypes) { + if (containerElementTypes.isEmpty()) { + return Collections.emptySet(); + } + final Class<?> containerType = TypeUtils.getRawType(meta.getType(), null); + final int typeParameterCount = containerType.getTypeParameters().length; + if (typeParameterCount == 0) { + Exceptions.raise(ValidationException::new, "Cannot specify container element types for %s", + meta.describeHost()); + } + return containerElementTypes.stream().map(e -> { + Integer typeArgumentIndex = e.getTypeArgumentIndex(); + if (typeArgumentIndex == null) { + if (typeParameterCount > 1) { + Exceptions.raise(ValidationException::new, + "Unable to resolve unspecified type argument index for %s", meta.describeHost()); + } + typeArgumentIndex = Integer.valueOf(0); + } + final ContainerElementKey result = new ContainerElementKey(containerType, typeArgumentIndex); + + final Meta.ForContainerElement elementMeta = new Meta.ForContainerElement(meta, result); + + constraints(elementMeta, e.getConstraint()); + containerElements(elementMeta, e.getContainerElementType()); + + return result; + }).collect(Collectors.toMap(Function.identity(), ContainerElementKey::getTypeArgumentIndex, enforceUniqueness( + "Duplicate XML constrained container element %d of " + meta.describeHost(), Function.identity()))).keySet(); + } + + private void constraints(Meta<?> meta, List<ConstraintType> constraints) { + constraints.forEach(constraint -> { + final Class<?> annotation = resolveClass.apply(constraint.getAnnotation()); + Exceptions.raiseUnless(annotation.isAnnotation(), ValidationException::new, "%s is not an annotation", + annotation); + + final Set<String> missingElements = Stream.of(Reflection.getDeclaredMethods(annotation)) + .filter(m -> m.getParameterCount() == 0 && m.getDefaultValue() == null).map(Method::getName) + .collect(Collectors.toSet()); + + for (final ElementType elementType : constraint.getElement()) { + final String name = elementType.getName(); + if (RESERVED_CONSTRAINT_ELEMENT_NAMES.stream().map(ConstraintAnnotationAttributes::getAttributeName) + .anyMatch(Predicate.isEqual(name))) { + Exceptions.raise(ValidationException::new, "Constraint of %s declares reserved parameter name %s.", + meta.describeHost(), name); + } + missingElements.remove(name); + } + Exceptions.raiseUnless(missingElements.isEmpty(), ValidationException::new, + "Missing required elements of %s: %s", annotation, missingElements); + }); + } +} http://git-wip-us.apache.org/repos/asf/bval/blob/e243ee93/bval-jsr/src/main/java/org/apache/bval/jsr/xml/ValidationMappingParser.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/xml/ValidationMappingParser.java b/bval-jsr/src/main/java/org/apache/bval/jsr/xml/ValidationMappingParser.java index f03d4cd..651ff9f 100644 --- a/bval-jsr/src/main/java/org/apache/bval/jsr/xml/ValidationMappingParser.java +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/xml/ValidationMappingParser.java @@ -19,15 +19,20 @@ package org.apache.bval.jsr.xml; import java.io.IOException; import java.io.InputStream; import java.lang.annotation.Annotation; +import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; import java.util.Optional; +import java.util.Set; import java.util.function.BiConsumer; import java.util.function.Consumer; +import java.util.stream.Collectors; import javax.validation.ValidationException; import javax.validation.spi.ConfigurationState; +import org.apache.bval.jsr.metadata.MetadataBuilder; import org.apache.bval.jsr.metadata.MetadataBuilder.ForBean; import org.apache.bval.jsr.metadata.MetadataSource; import org.apache.bval.jsr.metadata.ValidatorMappingProvider; @@ -59,10 +64,21 @@ public class ValidationMappingParser implements MetadataSource { if (configurationState.isIgnoreXmlConfiguration()) { return; } + final Set<Class<?>> beanTypes = new HashSet<>(); for (final InputStream xmlStream : configurationState.getMappingStreams()) { final ConstraintMappingsType mapping = parseXmlMappings(xmlStream); + Optional.of(mapping).map(this::toMappingProvider).ifPresent(addValidatorMappingProvider); - new XmlBuilder(mapping).forBeans().forEach(addBuilder::accept); + + final Map<Class<?>, MetadataBuilder.ForBean<?>> builders = new XmlBuilder(mapping).forBeans(); + if (Collections.disjoint(beanTypes, builders.keySet())) { + builders.forEach(addBuilder::accept); + beanTypes.addAll(builders.keySet()); + } else { + Exceptions.raise(ValidationException::new, + builders.keySet().stream().filter(beanTypes::contains).map(Class::getName).collect(Collectors + .joining("bean classes specified multiple times for XML validation mapping: [", "; ", "]"))); + } } } @@ -102,7 +118,7 @@ public class ValidationMappingParser implements MetadataSource { final Class<? extends Annotation> annotationClass = clazz.asSubclass(Annotation.class); Exceptions.raiseIf(validatorMappings.containsKey(annotationClass), ValidationException::new, - "Constraint validator for %s already configured.", annotationClass); + "XML constraint validator(s) for %s already configured.", annotationClass); validatorMappings.put(annotationClass, constraintDefinition.getValidatedBy()); }
