http://git-wip-us.apache.org/repos/asf/bval/blob/59bd964b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/ReflectionBuilder.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/ReflectionBuilder.java b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/ReflectionBuilder.java new file mode 100644 index 0000000..fc21ea7 --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/ReflectionBuilder.java @@ -0,0 +1,272 @@ +/* + * 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.metadata; + +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.AnnotatedParameterizedType; +import java.lang.reflect.AnnotatedType; +import java.lang.reflect.Constructor; +import java.lang.reflect.Executable; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.EnumMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.TreeMap; +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.GroupSequence; +import javax.validation.Valid; +import javax.validation.constraintvalidation.ValidationTarget; +import javax.validation.groups.ConvertGroup; + +import org.apache.bval.jsr.ApacheValidatorFactory; +import org.apache.bval.jsr.ConstraintAnnotationAttributes; +import org.apache.bval.jsr.descriptor.GroupConversion; +import org.apache.bval.jsr.util.AnnotationsManager; +import org.apache.bval.jsr.util.Methods; +import org.apache.bval.jsr.util.ToUnmodifiable; +import org.apache.bval.util.ObjectUtils; +import org.apache.bval.util.Validate; +import org.apache.bval.util.reflection.Reflection; +import org.apache.commons.weaver.privilizer.Privilizing; +import org.apache.commons.weaver.privilizer.Privilizing.CallTo; + +@Privilizing(@CallTo(Reflection.class)) +public class ReflectionBuilder { + + private class ForBean implements MetadataBuilder.ForBean { + private final Metas<Class<?>> meta; + + ForBean(Metas<Class<?>> meta) { + super(); + this.meta = Validate.notNull(meta, "meta"); + } + + @Override + public MetadataBuilder.ForClass getClass(Metas<Class<?>> ignored) { + return new ReflectionBuilder.ForClass(meta); + } + + @Override + public Map<String, MetadataBuilder.ForContainer<Field>> getFields(Metas<Class<?>> ignored) { + final Field[] declaredFields = Reflection.getDeclaredFields(meta.getHost()); + if (declaredFields.length == 0) { + return Collections.emptyMap(); + } + return Stream.of(declaredFields).collect( + Collectors.toMap(Field::getName, f -> new ReflectionBuilder.ForContainer<>(new Metas.ForField(f)))); + } + + @Override + public Map<String, MetadataBuilder.ForContainer<Method>> getGetters(Metas<Class<?>> ignored) { + return Stream.of(Reflection.getDeclaredMethods(meta.getHost())).filter(Methods::isGetter) + .collect(ToUnmodifiable.map(Methods::propertyName, + g -> new ReflectionBuilder.ForContainer<>(new Metas.ForMethod(g)))); + } + + @Override + public Map<Signature, MetadataBuilder.ForExecutable<Constructor<?>>> getConstructors(Metas<Class<?>> ignored) { + final Constructor<?>[] declaredConstructors = Reflection.getDeclaredConstructors(meta.getHost()); + if (declaredConstructors.length == 0) { + return Collections.emptyMap(); + } + return Stream.of(declaredConstructors).collect( + Collectors.toMap(Signature::of, c -> new ReflectionBuilder.ForExecutable<>(new Metas.ForConstructor(c), + validatorFactory.getParameterNameProvider()::getParameterNames))); + } + + @Override + public Map<Signature, MetadataBuilder.ForExecutable<Method>> getMethods(Metas<Class<?>> ignored) { + final Method[] declaredMethods = Reflection.getDeclaredMethods(meta.getHost()); + if (declaredMethods.length == 0) { + return Collections.emptyMap(); + } + return Stream.of(declaredMethods).filter(((Predicate<Method>) Methods::isGetter).negate()).collect( + Collectors.toMap(Signature::of, m -> new ReflectionBuilder.ForExecutable<>(new Metas.ForMethod(m), + validatorFactory.getParameterNameProvider()::getParameterNames))); + } + } + + private abstract class ForElement<E extends AnnotatedElement> implements MetadataBuilder.ForElement<E> { + final Metas<E> meta; + + ForElement(Metas<E> meta) { + super(); + this.meta = Validate.notNull(meta, "meta"); + } + + @Override + public Annotation[] getDeclaredConstraints(Metas<E> ignored) { + return AnnotationsManager.getDeclaredConstraints(meta); + } + } + + private class ForClass extends ForElement<Class<?>> implements MetadataBuilder.ForClass { + + ForClass(Metas<Class<?>> meta) { + super(meta); + } + + @Override + public List<Class<?>> getGroupSequence(Metas<Class<?>> ignored) { + final GroupSequence groupSequence = meta.getHost().getAnnotation(GroupSequence.class); + return groupSequence == null ? null : Collections.unmodifiableList(Arrays.asList(groupSequence.value())); + } + } + + private class ForContainer<E extends AnnotatedElement> extends ReflectionBuilder.ForElement<E> + implements MetadataBuilder.ForContainer<E> { + + ForContainer(Metas<E> meta) { + super(meta); + } + + @Override + public Map<ContainerElementKey, MetadataBuilder.ForContainer<AnnotatedType>> getContainerElementTypes( + Metas<E> ignored) { + final AnnotatedType annotatedType = meta.getAnnotatedType(); + if (annotatedType instanceof AnnotatedParameterizedType) { + + final AnnotatedParameterizedType container = (AnnotatedParameterizedType) annotatedType; + + final Map<ContainerElementKey, MetadataBuilder.ForContainer<AnnotatedType>> result = new TreeMap<>(); + + final AnnotatedType[] typeArgs = container.getAnnotatedActualTypeArguments(); + for (int i = 0; i < typeArgs.length; i++) { + ContainerElementKey key = new ContainerElementKey(container, i); + result.put(key, new ReflectionBuilder.ForContainer<>(new Metas.ForContainerElement(meta, key))); + } + return result; + } + return Collections.emptyMap(); + } + + @Override + public boolean isCascade(Metas<E> ignored) { + return meta.getHost().isAnnotationPresent(Valid.class); + } + + @Override + public Set<GroupConversion> getGroupConversions(Metas<E> ignored) { + return Stream.of(meta.getHost().getDeclaredAnnotationsByType(ConvertGroup.class)) + .map(cg -> GroupConversion.from(cg.from()).to(cg.to())).collect(ToUnmodifiable.set()); + } + } + + private class ForExecutable<E extends Executable> implements MetadataBuilder.ForExecutable<E> { + + final Metas<E> meta; + final Function<E, List<String>> getParameterNames; + + ForExecutable(Metas<E> meta, Function<E, List<String>> getParameterNames) { + super(); + this.meta = Validate.notNull(meta, "meta"); + this.getParameterNames = Validate.notNull(getParameterNames, "getParameterNames"); + } + + @Override + public List<MetadataBuilder.ForContainer<Parameter>> getParameters(Metas<E> ignored) { + final Parameter[] parameters = meta.getHost().getParameters(); + if (parameters.length == 0) { + return Collections.emptyList(); + } + final List<String> parameterNames = getParameterNames.apply(meta.getHost()); + + return IntStream.range(0, parameters.length).mapToObj( + n -> new ReflectionBuilder.ForContainer<>(new Metas.ForParameter(parameters[n], parameterNames.get(n)))) + .collect(ToUnmodifiable.list()); + } + + @Override + public ForContainer<E> getReturnValue(Metas<E> ignored) { + return new ReflectionBuilder.ForContainer<E>(meta) { + + @Override + public Annotation[] getDeclaredConstraints(Metas<E> meta) { + return getConstraints(meta, ValidationTarget.ANNOTATED_ELEMENT); + } + }; + } + + @Override + public MetadataBuilder.ForElement<E> getCrossParameter(Metas<E> ignored) { + return new ReflectionBuilder.ForElement<E>(meta) { + @Override + public Annotation[] getDeclaredConstraints(Metas<E> meta) { + return getConstraints(meta, ValidationTarget.PARAMETERS); + } + }; + } + + private Annotation[] getConstraints(Metas<E> ignored, ValidationTarget validationTarget) { + return Optional.of(getConstraintsByTarget(meta)).map(m -> m.get(validationTarget)) + .map(l -> l.toArray(new Annotation[l.size()])).orElse(ObjectUtils.EMPTY_ANNOTATION_ARRAY); + } + + private Map<ValidationTarget, List<Annotation>> getConstraintsByTarget(Metas<E> ignored) { + final Annotation[] declaredConstraints = AnnotationsManager.getDeclaredConstraints(meta); + if (ObjectUtils.isEmpty(declaredConstraints)) { + return Collections.emptyMap(); + } + final Map<ValidationTarget, List<Annotation>> result = new EnumMap<>(ValidationTarget.class); + + for (Annotation constraint : declaredConstraints) { + final Class<? extends Annotation> constraintType = constraint.annotationType(); + final Optional<ValidationTarget> explicitTarget = + Optional.of(ConstraintAnnotationAttributes.VALIDATION_APPLIES_TO.analyze(constraintType)) + .filter(ConstraintAnnotationAttributes.Worker::isValid).map(w -> w.read(constraint)); + + final ValidationTarget target = explicitTarget.orElseGet(() -> { + final Set<ValidationTarget> supportedTargets = + validatorFactory.getAnnotationsManager().supportedTargets(constraintType); + + Validate.validState(supportedTargets.size() == 1, + "Found %d possible %s types for constraint type %s and no explicit assignment via #%s()", + supportedTargets.size(), ValidationTarget.class.getSimpleName(), constraintType.getName(), + ConstraintAnnotationAttributes.VALIDATION_APPLIES_TO.getAttributeName()); + + return supportedTargets.iterator().next(); + }); + result.computeIfAbsent(target, k -> new ArrayList<>()).add(constraint); + } + return result; + } + } + + private final ApacheValidatorFactory validatorFactory; + + public ReflectionBuilder(ApacheValidatorFactory validatorFactory) { + super(); + this.validatorFactory = Validate.notNull(validatorFactory, "validatorFactory"); + } + + public <T> MetadataBuilder.ForBean forBean(Class<?> beanClass) { + return new ReflectionBuilder.ForBean(new Metas.ForClass(beanClass)); + } +}
http://git-wip-us.apache.org/repos/asf/bval/blob/59bd964b/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 new file mode 100644 index 0000000..8def7ae --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/Signature.java @@ -0,0 +1,75 @@ +/* + * 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.metadata; + +import java.lang.reflect.Executable; +import java.util.Arrays; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.apache.bval.util.Lazy; +import org.apache.bval.util.LazyInt; +import org.apache.bval.util.StringUtils; +import org.apache.bval.util.Validate; + +public final class Signature { + public static Signature of(Executable x) { + return new Signature(x.getName(), x.getParameterTypes()); + } + + private final String name; + private final Class<?>[] parameterTypes; + private final LazyInt hashCode; + private final Lazy<String> toString; + + public Signature(String name, Class<?>... parameterTypes) { + super(); + this.name = Validate.notNull(name, "name"); + Validate.isTrue(StringUtils.isNotBlank(name), "name is blank"); + this.parameterTypes = Validate.notNull(parameterTypes, "parameterTypes").clone(); + hashCode = new LazyInt(() -> Arrays.deepHashCode(new Object[] { this.name, this.parameterTypes })); + toString = new Lazy<>(() -> String.format("%s: %s(%s)", getClass().getSimpleName(), this.name, + Stream.of(this.parameterTypes).map(Class::getName).collect(Collectors.joining(", ")))); + } + + public String getName() { + return name; + } + + public Class<?>[] getParameterTypes() { + return parameterTypes.clone(); + } + + @Override + public boolean equals(Object obj) { + return obj == this || Optional.ofNullable(obj).filter(Signature.class::isInstance).map(Signature.class::cast) + .filter(sig -> Objects.equals(name, sig.name) && Objects.deepEquals(parameterTypes, sig.parameterTypes)) + .isPresent(); + } + + @Override + public int hashCode() { + return hashCode.getAsInt(); + } + + @Override + public String toString() { + return toString.get(); + } +} http://git-wip-us.apache.org/repos/asf/bval/blob/59bd964b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/ValidatorMapping.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/ValidatorMapping.java b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/ValidatorMapping.java new file mode 100644 index 0000000..bd2ce7f --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/ValidatorMapping.java @@ -0,0 +1,121 @@ +/* + * 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.metadata; + +import java.lang.annotation.Annotation; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +import javax.validation.ConstraintValidator; + +import org.apache.bval.util.Validate; + +public class ValidatorMapping<A extends Annotation> implements HasAnnotationBehavior { + @SuppressWarnings({ "rawtypes", "unchecked" }) + private static final ValidatorMapping EMPTY = new ValidatorMapping("empty", Collections.emptyList()); + + @SuppressWarnings("unchecked") + public static <A extends Annotation> ValidatorMapping<A> empty() { + return EMPTY; + } + + public static <A extends Annotation> ValidatorMapping<A> merge( + List<? extends ValidatorMapping<A>> validatorMappings, + AnnotationBehaviorMergeStrategy annotationBehaviorMergeStrategy) { + + final AnnotationBehavior behavior = annotationBehaviorMergeStrategy.apply(validatorMappings); + + final List<? extends ValidatorMapping<A>> nonEmpty = + validatorMappings.stream().filter(m -> !m.isEmpty()).collect(Collectors.toList()); + + if (nonEmpty.size() <= 1) { + // avoid creating the composite instance if behavior matches: + final ValidatorMapping<A> simpleResult = nonEmpty.isEmpty() ? empty() : nonEmpty.get(0); + + if (simpleResult.hasBehavior(behavior)) { + return simpleResult; + } + } + final String source = + nonEmpty.stream().map(ValidatorMapping::getSource).collect(Collectors.joining(";", "[", "]")); + + return new ValidatorMapping<>(source, nonEmpty.stream().map(ValidatorMapping::getValidatorTypes) + .flatMap(Collection::stream).distinct().collect(Collectors.toList()), behavior); + } + + private final String source; + private final List<Class<? extends ConstraintValidator<A, ?>>> validatorTypes; + private final AnnotationBehavior annotationBehavior; + + public ValidatorMapping(String source, List<Class<? extends ConstraintValidator<A, ?>>> validatorTypes) { + this(source, validatorTypes, AnnotationBehavior.ABSTAIN); + } + + public ValidatorMapping(String source, List<Class<? extends ConstraintValidator<A, ?>>> validatorTypes, + AnnotationBehavior annotationBehavior) { + this.source = Objects.toString(source, "unspecified"); + this.validatorTypes = Collections.unmodifiableList(Validate.notNull(validatorTypes, "validatorTypes")); + this.annotationBehavior = Validate.notNull(annotationBehavior, "annotationBehavior"); + } + + public List<Class<? extends ConstraintValidator<A, ?>>> getValidatorTypes() { + return validatorTypes; + } + + public AnnotationBehavior getAnnotationBehavior() { + return annotationBehavior; + } + + public boolean isEmpty() { + return validatorTypes.isEmpty(); + } + + public String getSource() { + return source; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!getClass().isInstance(obj)) { + return false; + } + final ValidatorMapping<?> other = (ValidatorMapping<?>) obj; + return getSource().equals(other.getSource()) && getAnnotationBehavior() == other.getAnnotationBehavior() + && getValidatorTypes().equals(other.getValidatorTypes()); + } + + @Override + public int hashCode() { + return Objects.hash(getSource(), getAnnotationBehavior(), getValidatorTypes()); + } + + @Override + public String toString() { + return String.format("%s[source: %s; annotationBehavior: %s; validatorTypes: %s]", + ValidatorMapping.class.getSimpleName(), getSource(), getAnnotationBehavior(), getValidatorTypes()); + } + + public boolean hasBehavior(AnnotationBehavior annotationBehavior) { + return getAnnotationBehavior() == annotationBehavior; + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/bval/blob/59bd964b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/ValidatorMappingProvider.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/ValidatorMappingProvider.java b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/ValidatorMappingProvider.java new file mode 100644 index 0000000..8a8cd3f --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/ValidatorMappingProvider.java @@ -0,0 +1,51 @@ +/* + * 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.metadata; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; +import java.util.Optional; + +import javax.validation.ConstraintDefinitionException; +import javax.validation.ConstraintValidator; + +import org.apache.bval.util.Exceptions; +import org.apache.bval.util.reflection.TypeUtils; + +public abstract class ValidatorMappingProvider { + + public final <A extends Annotation> ValidatorMapping<A> getValidatorMapping(Class<A> constraintType) { + final Optional<ValidatorMapping<A>> result = + Optional.ofNullable(this.<A> doGetValidatorMapping(constraintType)); + if (result.isPresent()) { + for (Class<? extends ConstraintValidator<A, ?>> t : result.get().getValidatorTypes()) { + final Type constraintParameter = TypeUtils.getTypeArguments(t, ConstraintValidator.class) + .get(ConstraintValidator.class.getTypeParameters()[0]); + + Exceptions.raiseUnless(constraintType.equals(constraintParameter), ConstraintDefinitionException::new, + "%s %s expected first type parameter of %s, %s; source %s", ConstraintValidator.class, t, + constraintType, constraintParameter, result.get().getSource()); + } + return result.get(); + } + return null; + } + + protected abstract <A extends Annotation> ValidatorMapping<A> doGetValidatorMapping(Class<A> constraintType); +} http://git-wip-us.apache.org/repos/asf/bval/blob/59bd964b/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 new file mode 100644 index 0000000..37082d4 --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/XmlBuilder.java @@ -0,0 +1,694 @@ +/* + * 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.metadata; + +import java.io.Serializable; +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.AnnotatedParameterizedType; +import java.lang.reflect.AnnotatedType; +import java.lang.reflect.Array; +import java.lang.reflect.Constructor; +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; + +import javax.validation.ConstraintDeclarationException; +import javax.validation.ConstraintTarget; +import javax.validation.Payload; +import javax.validation.ValidationException; +import javax.xml.bind.JAXBElement; + +import org.apache.bval.jsr.ConstraintAnnotationAttributes; +import org.apache.bval.jsr.descriptor.GroupConversion; +import org.apache.bval.jsr.util.AnnotationsManager; +import org.apache.bval.jsr.util.ToUnmodifiable; +import org.apache.bval.jsr.xml.AnnotationProxyBuilder; +import org.apache.bval.jsr.xml.AnnotationType; +import org.apache.bval.jsr.xml.BeanType; +import org.apache.bval.jsr.xml.ClassType; +import org.apache.bval.jsr.xml.ConstraintMappingsType; +import org.apache.bval.jsr.xml.ConstraintType; +import org.apache.bval.jsr.xml.ConstructorType; +import org.apache.bval.jsr.xml.ContainerElementTypeType; +import org.apache.bval.jsr.xml.CrossParameterType; +import org.apache.bval.jsr.xml.ElementType; +import org.apache.bval.jsr.xml.FieldType; +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.MethodType; +import org.apache.bval.jsr.xml.ParameterType; +import org.apache.bval.jsr.xml.PayloadType; +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.Validate; +import org.apache.bval.util.reflection.Reflection; +import org.apache.commons.weaver.privilizer.Privilizing; +import org.apache.commons.weaver.privilizer.Privilizing.CallTo; + +@Privilizing(@CallTo(Reflection.class)) +public class XmlBuilder { + //@formatter:off + public enum Version { + v10("1.0"), v11("1.1"), v20("2.0"); + + 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 implements MetadataBuilder.ForBean { + + private final BeanType descriptor; + + ForBean(BeanType descriptor) { + super(); + this.descriptor = Validate.notNull(descriptor, "descriptor"); + } + + Class<?> getBeanClass() { + return resolveClass(descriptor.getClazz()); + } + + @Override + public MetadataBuilder.ForClass getClass(Metas<Class<?>> meta) { + return new XmlBuilder.ForClass(descriptor.getClassType()); + } + + @Override + public Map<String, MetadataBuilder.ForContainer<Field>> getFields(Metas<Class<?>> meta) { + return descriptor.getField().stream() + .collect(ToUnmodifiable.map(FieldType::getName, XmlBuilder.ForField::new)); + } + + @Override + public Map<String, MetadataBuilder.ForContainer<Method>> getGetters(Metas<Class<?>> meta) { + return descriptor.getGetter().stream() + .collect(ToUnmodifiable.map(GetterType::getName, XmlBuilder.ForGetter::new)); + } + + @Override + public Map<Signature, MetadataBuilder.ForExecutable<Constructor<?>>> getConstructors(Metas<Class<?>> meta) { + if (!atLeast(Version.v11)) { + return Collections.emptyMap(); + } + final Function<ConstructorType, Class<?>[]> params = ct -> ct.getParameter().stream() + .map(ParameterType::getType).map(XmlBuilder.this::resolveClass).toArray(Class[]::new); + + final Function<ConstructorType, Signature> signature = + ct -> new Signature(meta.getHost().getSimpleName(), params.apply(ct)); + + return descriptor.getConstructor().stream() + .collect(Collectors.toMap(signature, XmlBuilder.ForConstructor::new)); + } + + @Override + public Map<Signature, MetadataBuilder.ForExecutable<Method>> getMethods(Metas<Class<?>> meta) { + if (!atLeast(Version.v11)) { + return Collections.emptyMap(); + } + final Function<MethodType, Class<?>[]> params = mt -> mt.getParameter().stream().map(ParameterType::getType) + .map(XmlBuilder.this::resolveClass).toArray(Class[]::new); + + final Function<MethodType, Signature> signature = mt -> new Signature(mt.getName(), params.apply(mt)); + + return descriptor.getMethod().stream().collect(Collectors.toMap(signature, XmlBuilder.ForMethod::new)); + } + + @Override + public final AnnotationBehavior getAnnotationBehavior() { + return descriptor.getIgnoreAnnotations() ? AnnotationBehavior.EXCLUDE : AnnotationBehavior.INCLUDE; + } + } + + private class NonRootLevel<SELF extends NonRootLevel<SELF, D>, D> implements HasAnnotationBehavior { + protected final D descriptor; + private Lazy<Boolean> getIgnoreAnnotations; + + public NonRootLevel(D descriptor) { + super(); + this.descriptor = Validate.notNull(descriptor, "descriptor"); + } + + @Override + public final AnnotationBehavior getAnnotationBehavior() { + return Optional.ofNullable(getIgnoreAnnotations).map(Lazy::get) + .map(b -> b.booleanValue() ? AnnotationBehavior.EXCLUDE : AnnotationBehavior.INCLUDE) + .orElse(AnnotationBehavior.ABSTAIN); + } + + @SuppressWarnings("unchecked") + final SELF withGetIgnoreAnnotations(Function<D, Boolean> getIgnoreAnnotations) { + Validate.notNull(getIgnoreAnnotations); + this.getIgnoreAnnotations = new Lazy<>(() -> getIgnoreAnnotations.apply(descriptor)); + return (SELF) this; + } + } + + private class ForElement<SELF extends XmlBuilder.ForElement<SELF, E, D>, E extends AnnotatedElement, D> + extends NonRootLevel<SELF, D> implements MetadataBuilder.ForElement<E> { + + private Lazy<Annotation[]> getDeclaredConstraints; + + ForElement(D descriptor) { + super(descriptor); + } + + @Override + public final Annotation[] getDeclaredConstraints(Metas<E> meta) { + return lazy(getDeclaredConstraints, "getDeclaredConstraints"); + } + + final SELF withGetConstraintTypes(Function<D, List<ConstraintType>> getConstraintTypes) { + return withGetDeclaredConstraints(getConstraintTypes + .andThen(l -> l.stream().map(XmlBuilder.this::createConstraint).toArray(Annotation[]::new))); + } + + @SuppressWarnings("unchecked") + final SELF withGetDeclaredConstraints(Function<D, Annotation[]> getDeclaredConstraints) { + this.getDeclaredConstraints = new Lazy<>(() -> getDeclaredConstraints.apply(descriptor)); + return (SELF) this; + } + } + + private class ForClass extends ForElement<ForClass, Class<?>, ClassType> implements MetadataBuilder.ForClass { + + ForClass(ClassType descriptor) { + super(descriptor); + this.withGetConstraintTypes(ct -> ct.getConstraint()); + } + + @Override + public List<Class<?>> getGroupSequence(Metas<Class<?>> meta) { + final GroupSequenceType groupSequence = descriptor.getGroupSequence(); + return groupSequence == null ? null + : groupSequence.getValue().stream().map(XmlBuilder.this::resolveClass).collect(ToUnmodifiable.list()); + } + } + + private class ForContainer<SELF extends XmlBuilder.ForContainer<SELF, E, D>, E extends AnnotatedElement, D> + extends XmlBuilder.ForElement<SELF, E, D> implements MetadataBuilder.ForContainer<E> { + + private Lazy<Boolean> isCascade; + private Lazy<Set<GroupConversion>> getGroupConversions; + private Lazy<List<ContainerElementTypeType>> getContainerElementTypes; + + ForContainer(D descriptor) { + super(descriptor); + } + + @Override + public boolean isCascade(Metas<E> meta) { + return lazy(isCascade, "isCascade").booleanValue(); + } + + @Override + public Set<GroupConversion> getGroupConversions(Metas<E> meta) { + return lazy(getGroupConversions, "getGroupConversions"); + } + + @Override + public Map<ContainerElementKey, MetadataBuilder.ForContainer<AnnotatedType>> getContainerElementTypes( + Metas<E> meta) { + if (!atLeast(Version.v20)) { + return Collections.emptyMap(); + } + final List<ContainerElementTypeType> elements = lazy(getContainerElementTypes, "getContainerElementTypes"); + final AnnotatedType annotatedType = meta.getAnnotatedType(); + final E host = meta.getHost(); + + if (annotatedType instanceof AnnotatedParameterizedType) { + final AnnotatedType[] actualTypeArguments = + ((AnnotatedParameterizedType) annotatedType).getAnnotatedActualTypeArguments(); + + return elements.stream().collect(ToUnmodifiable.map(cet -> { + Integer typeArgumentIndex = cet.getTypeArgumentIndex(); + if (typeArgumentIndex == null) { + Exceptions.raiseIf(actualTypeArguments.length > 1, ValidationException::new, + "Missing required type argument index for %s", host); + typeArgumentIndex = Integer.valueOf(0); + } + return new ContainerElementKey((AnnotatedParameterizedType) annotatedType, typeArgumentIndex); + }, XmlBuilder.ForContainerElementType::new)); + } + Exceptions.raiseUnless(elements.isEmpty(), ValidationException::new, + "Illegally specified %d container element type(s) for %s", elements.size(), host); + + return Collections.emptyMap(); + } + + @SuppressWarnings("unchecked") + SELF withGetValid(Function<D, String> getValid) { + Validate.notNull(getValid); + this.isCascade = new Lazy<>(() -> getValid.apply(descriptor) != null); + return (SELF) this; + } + + @SuppressWarnings("unchecked") + SELF withGetGroupConversions(Function<D, List<GroupConversionType>> getGroupConversions) { + Validate.notNull(getGroupConversions); + + this.getGroupConversions = new Lazy<>(() -> { + return getGroupConversions.apply(descriptor).stream().map(gc -> { + final Class<?> source = resolveClass(gc.getFrom()); + final Class<?> target = resolveClass(gc.getTo()); + return GroupConversion.from(source).to(target); + }).collect(ToUnmodifiable.set()); + }); + return (SELF) this; + } + + @SuppressWarnings("unchecked") + SELF withGetContainerElementTypes(Function<D, List<ContainerElementTypeType>> getContainerElementTypes) { + Validate.notNull(getContainerElementTypes); + this.getContainerElementTypes = new Lazy<>(() -> getContainerElementTypes.apply(descriptor)); + return (SELF) this; + } + } + + private class ForContainerElementType + extends ForContainer<ForContainerElementType, AnnotatedType, ContainerElementTypeType> { + + ForContainerElementType(ContainerElementTypeType descriptor) { + super(descriptor); + this.withGetConstraintTypes(ContainerElementTypeType::getConstraint) + .withGetValid(ContainerElementTypeType::getValid) + .withGetGroupConversions(ContainerElementTypeType::getConvertGroup) + .withGetContainerElementTypes(ContainerElementTypeType::getContainerElementType); + } + } + + private class ForField extends XmlBuilder.ForContainer<ForField, Field, FieldType> { + + ForField(FieldType descriptor) { + super(descriptor); + this.withGetIgnoreAnnotations(FieldType::getIgnoreAnnotations) + .withGetConstraintTypes(FieldType::getConstraint).withGetValid(FieldType::getValid) + .withGetGroupConversions(FieldType::getConvertGroup) + .withGetContainerElementTypes(FieldType::getContainerElementType); + } + } + + private class ForGetter extends XmlBuilder.ForContainer<ForGetter, Method, GetterType> { + + ForGetter(GetterType descriptor) { + super(descriptor); + this.withGetIgnoreAnnotations(GetterType::getIgnoreAnnotations) + .withGetConstraintTypes(GetterType::getConstraint).withGetValid(GetterType::getValid) + .withGetGroupConversions(GetterType::getConvertGroup) + .withGetContainerElementTypes(GetterType::getContainerElementType); + } + } + + private abstract class ForExecutable<SELF extends ForExecutable<SELF, E, D>, E extends Executable, D> + extends NonRootLevel<SELF, D> implements MetadataBuilder.ForExecutable<E> { + + Lazy<ReturnValueType> getReturnValue; + Lazy<CrossParameterType> getCrossParameter; + Lazy<List<ParameterType>> getParameters; + + ForExecutable(D descriptor) { + super(descriptor); + } + + @Override + public MetadataBuilder.ForElement<E> getCrossParameter(Metas<E> meta) { + return new XmlBuilder.ForCrossParameter<>(lazy(getCrossParameter, "getCrossParameter")); + } + + @Override + public MetadataBuilder.ForContainer<E> getReturnValue(Metas<E> meta) { + return new XmlBuilder.ForReturnValue<>(lazy(getReturnValue, "getReturnValue")); + } + + @Override + public List<MetadataBuilder.ForContainer<Parameter>> getParameters(Metas<E> meta) { + return lazy(getParameters, "getParameters").stream().map(XmlBuilder.ForParameter::new) + .collect(Collectors.toList()); + } + + @SuppressWarnings("unchecked") + SELF withGetReturnValue(Function<D, ReturnValueType> getReturnValue) { + Validate.notNull(getReturnValue); + this.getReturnValue = new Lazy<>(() -> getReturnValue.apply(descriptor)); + return (SELF) this; + } + + @SuppressWarnings("unchecked") + SELF withGetCrossParameter(Function<D, CrossParameterType> getCrossParameter) { + Validate.notNull(getCrossParameter); + this.getCrossParameter = new Lazy<>(() -> getCrossParameter.apply(descriptor)); + return (SELF) this; + } + + @SuppressWarnings("unchecked") + SELF withGetParameters(Function<D, List<ParameterType>> getParameters) { + Validate.notNull(getParameters); + this.getParameters = new Lazy<>(() -> getParameters.apply(descriptor)); + return (SELF) this; + } + } + + private class ForConstructor extends ForExecutable<ForConstructor, Constructor<?>, ConstructorType> { + + ForConstructor(ConstructorType descriptor) { + super(descriptor); + this.withGetIgnoreAnnotations(ConstructorType::getIgnoreAnnotations) + .withGetReturnValue(ConstructorType::getReturnValue) + .withGetCrossParameter(ConstructorType::getCrossParameter) + .withGetParameters(ConstructorType::getParameter); + } + } + + private class ForMethod extends ForExecutable<ForMethod, Method, MethodType> { + + ForMethod(MethodType descriptor) { + super(descriptor); + this.withGetIgnoreAnnotations(MethodType::getIgnoreAnnotations) + .withGetReturnValue(MethodType::getReturnValue).withGetCrossParameter(MethodType::getCrossParameter) + .withGetParameters(MethodType::getParameter); + } + } + + private class ForParameter extends ForContainer<ForParameter, Parameter, ParameterType> { + + ForParameter(ParameterType descriptor) { + super(descriptor); + this.withGetIgnoreAnnotations(ParameterType::getIgnoreAnnotations) + .withGetConstraintTypes(ParameterType::getConstraint).withGetValid(ParameterType::getValid) + .withGetGroupConversions(ParameterType::getConvertGroup) + .withGetContainerElementTypes(ParameterType::getContainerElementType); + } + } + + private class ForCrossParameter<E extends Executable> + extends ForElement<ForCrossParameter<E>, E, CrossParameterType> { + + ForCrossParameter(CrossParameterType descriptor) { + super(descriptor); + this.withGetIgnoreAnnotations(CrossParameterType::getIgnoreAnnotations) + .withGetDeclaredConstraints(d -> d.getConstraint().stream() + .map(ct -> createConstraint(ct, ConstraintTarget.PARAMETERS)).toArray(Annotation[]::new)); + } + } + + private class ForReturnValue<E extends Executable> extends ForContainer<ForReturnValue<E>, E, ReturnValueType> { + + ForReturnValue(ReturnValueType descriptor) { + super(descriptor); + this.withGetDeclaredConstraints(d -> d.getConstraint().stream() + .map(ct -> createConstraint(ct, ConstraintTarget.RETURN_VALUE)).toArray(Annotation[]::new)) + .withGetContainerElementTypes(d -> d.getContainerElementType()); + } + } + + 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) { + Validate.validState(lazy != null, "%s not set", name); + return lazy.get(); + } + + private final ConstraintMappingsType constraintMappings; + private final BigDecimal 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; + } + + public Map<Class<?>, MetadataBuilder.ForBean> forBeans() { + return constraintMappings.getBean().stream().map(XmlBuilder.ForBean::new) + .collect(ToUnmodifiable.map(XmlBuilder.ForBean::getBeanClass, Function.identity())); + } + + public String getDefaultPackage() { + return constraintMappings.getDefaultPackage(); + } + + boolean atLeast(Version v) { + return version.compareTo(v.number) >= 0; + } + + <T> Class<T> resolveClass(String className) { + return loadClass(toQualifiedClassName(className)); + } + + private String toQualifiedClassName(String className) { + if (isQualifiedClass(className)) { + return className; + } + if (className.startsWith("[L") && className.endsWith(";")) { + return "[L" + getDefaultPackage() + "." + className.substring(2); + } + return getDefaultPackage() + "." + className; + } + + private boolean isQualifiedClass(String clazz) { + return clazz.indexOf('.') >= 0; + } + + @SuppressWarnings("unchecked") + private <T> Class<T> loadClass(final String fqn) { + ClassLoader loader = Reflection.getClassLoader(XmlBuilder.class); + if (loader == null) { + loader = getClass().getClassLoader(); + } + try { + return (Class<T>) Class.forName(fqn, true, loader); + } catch (ClassNotFoundException ex) { + throw Exceptions.create(ValidationException::new, ex, "Unable to load class: %d", fqn); + } + } + + private Class<?>[] loadClasses(Supplier<Stream<String>> classNames) { + return streamClasses(classNames).toArray(Class[]::new); + } + + private Stream<Class<?>> streamClasses(Supplier<Stream<String>> classNames) { + return classNames.get().map(this::loadClass); + } + + private <A extends Annotation, T> A createConstraint(final ConstraintType constraint) { + return createConstraint(constraint, ConstraintTarget.IMPLICIT); + } + + @SuppressWarnings("unchecked") + private <A extends Annotation, T> A createConstraint(final ConstraintType constraint, ConstraintTarget target) { + + final Class<A> annotationClass = (Class<A>) loadClass(toQualifiedClassName(constraint.getAnnotation())); + final AnnotationProxyBuilder<A> annoBuilder = new AnnotationProxyBuilder<A>(annotationClass); + + if (constraint.getMessage() != null) { + annoBuilder.setMessage(constraint.getMessage()); + } + annoBuilder.setGroups(getGroups(constraint.getGroups())); + annoBuilder.setPayload(getPayload(constraint.getPayload())); + + if (AnnotationsManager.declaresAttribute(annotationClass, + ConstraintAnnotationAttributes.VALIDATION_APPLIES_TO.getAttributeName())) { + annoBuilder.setValidationAppliesTo(target); + } + + 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); + } + 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); + Exceptions.raiseIf(m == null, ValidationException::new, + "Annotation of type %s does not contain a parameter %s.", annotationClass.getName(), name); + return m.getReturnType(); + } + + private Object getElementValue(ElementType elementType, Class<?> returnType) { + removeEmptyContentElements(elementType); + + final List<Serializable> content = elementType.getContent(); + final int sz = content.size(); + if (returnType.isArray()) { + final Object result = Array.newInstance(returnType.getComponentType(), sz); + for (int i = 0; i < sz; i++) { + Array.set(result, i, getSingleValue(content.get(i), returnType.getComponentType())); + } + return result; + } + Exceptions.raiseIf(sz != 1, ValidationException::new, + "Attempt to specify an array where single value is expected."); + + return getSingleValue(content.get(0), returnType); + } + + private void removeEmptyContentElements(ElementType elementType) { + for (Iterator<Serializable> iter = elementType.getContent().iterator(); iter.hasNext();) { + final Serializable content = iter.next(); + if (content instanceof String && ((String) content).matches("[\\n ].*")) { + iter.remove(); + } + } + } + + @SuppressWarnings("unchecked") + private Object getSingleValue(Serializable serializable, Class<?> returnType) { + if (serializable instanceof String) { + return convertToResultType(returnType, (String) serializable); + } + if (serializable instanceof JAXBElement<?>) { + final JAXBElement<?> elem = (JAXBElement<?>) serializable; + if (String.class.equals(elem.getDeclaredType())) { + return convertToResultType(returnType, (String) elem.getValue()); + } + if (AnnotationType.class.equals(elem.getDeclaredType())) { + AnnotationType annotationType = (AnnotationType) elem.getValue(); + try { + return createAnnotation(annotationType, (Class<? extends Annotation>) returnType); + } catch (ClassCastException e) { + throw new ValidationException("Unexpected parameter value"); + } + } + } + throw new ValidationException("Unexpected parameter value"); + } + + private Object convertToResultType(Class<?> returnType, String value) { + /** + * Class is represented by the fully qualified class name of the class. spec: Note that if the raw string is + * unqualified, default package is taken into account. + */ + if (String.class.equals(returnType)) { + return value; + } + if (Class.class.equals(returnType)) { + return resolveClass(value); + } + if (returnType.isEnum()) { + try { + @SuppressWarnings({ "rawtypes", "unchecked" }) + final Enum e = Enum.valueOf(returnType.asSubclass(Enum.class), value); + return e; + } catch (IllegalArgumentException e) { + 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); + } + if (Character.class.equals(returnType) || char.class.equals(returnType)) { + Exceptions.raiseIf(value.length() > 1, ConstraintDeclarationException::new, + "a char must have a length of 1"); + return value.charAt(0); + } + return Exceptions.raise(ValidationException::new, "Unknown annotation value type %s", returnType.getName()); + } + + private <A extends Annotation> Annotation createAnnotation(AnnotationType annotationType, Class<A> returnType) { + final AnnotationProxyBuilder<A> metaAnnotation = new AnnotationProxyBuilder<>(returnType); + for (ElementType elementType : annotationType.getElement()) { + final String name = elementType.getName(); + metaAnnotation.setValue(name, getElementValue(elementType, getAnnotationParameterType(returnType, name))); + } + return metaAnnotation.createAnnotation(); + } + + private Class<?>[] getGroups(GroupsType groupsType) { + if (groupsType == null) { + return ObjectUtils.EMPTY_CLASS_ARRAY; + } + return loadClasses(groupsType.getValue()::stream); + } + + @SuppressWarnings("unchecked") + private Class<? extends Payload>[] getPayload(PayloadType payloadType) { + if (payloadType == null) { + return (Class<? extends Payload>[]) ObjectUtils.EMPTY_CLASS_ARRAY; + } + return streamClasses(payloadType.getValue()::stream).peek(pc -> { + Exceptions.raiseUnless(Payload.class.isAssignableFrom(pc), ConstraintDeclarationException::new, + "Specified payload class %s does not implement %s", pc.getName(), Payload.class.getName()); + }).<Class<? extends Payload>> map(pc -> pc.asSubclass(Payload.class)).toArray(Class[]::new); + } +} http://git-wip-us.apache.org/repos/asf/bval/blob/59bd964b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/XmlValidationMappingProvider.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/XmlValidationMappingProvider.java b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/XmlValidationMappingProvider.java new file mode 100644 index 0000000..a47d1c6 --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/XmlValidationMappingProvider.java @@ -0,0 +1,64 @@ +/* + * 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.metadata; + +import java.lang.annotation.Annotation; +import java.util.Map; +import java.util.function.Function; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.stream.Collectors; + +import javax.validation.ConstraintValidator; + +import org.apache.bval.jsr.xml.ValidatedByType; +import org.apache.bval.util.Validate; + +public class XmlValidationMappingProvider extends ClassLoadingValidatorMappingProvider { + private static final Logger log = Logger.getLogger(XmlValidationMappingProvider.class.getName()); + + private final Map<Class<? extends Annotation>, ValidatedByType> config; + private final Function<String, String> classNameTransformer; + + public XmlValidationMappingProvider(Map<Class<? extends Annotation>, ValidatedByType> validatorMappings, + Function<String, String> classNameTransformer) { + super(); + this.config = Validate.notNull(validatorMappings, "validatorMappings"); + this.classNameTransformer = Validate.notNull(classNameTransformer, "classNameTransformer"); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Override + public <A extends Annotation> ValidatorMapping<A> doGetValidatorMapping(Class<A> constraintType) { + final ValidatedByType validatedByType = config.get(constraintType); + if (validatedByType == null) { + return null; + } + return new ValidatorMapping<>("XML descriptor", + load(validatedByType.getValue().stream().map(String::trim).map(classNameTransformer), + (Class<ConstraintValidator<A, ?>>) (Class) ConstraintValidator.class, + e -> log.log(Level.SEVERE, "exception loading XML-declared constraint validators", e)) + .collect(Collectors.toList()), + toAnnotationBehavior(validatedByType)); + } + + private AnnotationBehavior toAnnotationBehavior(ValidatedByType validatedByType) { + final Boolean includeExistingValidators = validatedByType.getIncludeExistingValidators(); + return includeExistingValidators == null ? AnnotationBehavior.ABSTAIN + : includeExistingValidators.booleanValue() ? AnnotationBehavior.INCLUDE : AnnotationBehavior.EXCLUDE; + } +}
