| /* |
| * 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.util.Collections; |
| 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.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.validation.groups.Default; |
| import javax.xml.bind.JAXBElement; |
| |
| import org.apache.bval.jsr.ApacheValidatorFactory; |
| import org.apache.bval.jsr.ConstraintAnnotationAttributes; |
| import org.apache.bval.jsr.groups.GroupConversion; |
| import org.apache.bval.jsr.util.AnnotationProxyBuilder; |
| import org.apache.bval.jsr.util.ToUnmodifiable; |
| 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.MappingValidator; |
| 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.StringUtils; |
| 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"), v30("3.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); |
| } |
| |
| private final String id; |
| |
| private Version(String number) { |
| this.id = number; |
| } |
| |
| public String getId() { |
| return id; |
| } |
| } |
| |
| private class ForBean<T> implements MetadataBuilder.ForBean<T> { |
| |
| private final BeanType descriptor; |
| |
| ForBean(BeanType descriptor) { |
| super(); |
| this.descriptor = Validate.notNull(descriptor, "descriptor"); |
| } |
| |
| Class<?> getBeanClass() { |
| return resolveClass(descriptor.getClazz()); |
| } |
| |
| @Override |
| public MetadataBuilder.ForClass<T> getClass(Meta<Class<T>> meta) { |
| final ClassType classType = descriptor.getClassType(); |
| return classType == null ? EmptyBuilder.instance().<T> forBean().getClass(meta) |
| : new XmlBuilder.ForClass<T>(classType); |
| } |
| |
| @Override |
| public Map<String, MetadataBuilder.ForContainer<Field>> getFields(Meta<Class<T>> meta) { |
| return descriptor.getField().stream() |
| .collect(ToUnmodifiable.map(FieldType::getName, XmlBuilder.ForField::new)); |
| } |
| |
| @Override |
| public Map<String, MetadataBuilder.ForContainer<Method>> getGetters(Meta<Class<T>> meta) { |
| return descriptor.getGetter().stream() |
| .collect(ToUnmodifiable.map(GetterType::getName, XmlBuilder.ForGetter::new)); |
| } |
| |
| @Override |
| public Map<Signature, MetadataBuilder.ForExecutable<Constructor<? extends T>>> getConstructors(Meta<Class<T>> 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().getName(), params.apply(ct)); |
| |
| return descriptor.getConstructor().stream() |
| .collect(Collectors.toMap(signature, XmlBuilder.ForConstructor::new)); |
| } |
| |
| @Override |
| public Map<Signature, MetadataBuilder.ForExecutable<Method>> getMethods(Meta<Class<T>> 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(Meta<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<T> extends ForElement<ForClass<T>, Class<T>, ClassType> implements MetadataBuilder.ForClass<T> { |
| |
| ForClass(ClassType descriptor) { |
| super(descriptor); |
| this.withGetConstraintTypes(ClassType::getConstraint) |
| .withGetIgnoreAnnotations(ClassType::getIgnoreAnnotations); |
| } |
| |
| @Override |
| public List<Class<?>> getGroupSequence(Meta<Class<T>> 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(Meta<E> meta) { |
| return Boolean.TRUE.equals(lazy(isCascade, "isCascade")); |
| } |
| |
| @Override |
| public Set<GroupConversion> getGroupConversions(Meta<E> meta) { |
| return lazy(getGroupConversions, "getGroupConversions"); |
| } |
| |
| @Override |
| public Map<ContainerElementKey, MetadataBuilder.ForContainer<AnnotatedType>> getContainerElementTypes( |
| Meta<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(annotatedType, typeArgumentIndex); |
| }, XmlBuilder.ForContainerElementType::new)); |
| } |
| if (!elements.isEmpty()) { |
| Exceptions.raise(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 String from = gc.getFrom(); |
| final Class<?> source = from == null ? Default.class : resolveClass(from); |
| 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(Meta<E> meta) { |
| final CrossParameterType cp = lazy(getCrossParameter, "getCrossParameter"); |
| if (cp == null) { |
| return EmptyBuilder.instance().<E> forExecutable().getCrossParameter(meta); |
| } |
| return new XmlBuilder.ForCrossParameter<>(cp); |
| } |
| |
| @Override |
| public MetadataBuilder.ForContainer<E> getReturnValue(Meta<E> meta) { |
| final ReturnValueType rv = lazy(getReturnValue, "getReturnValue"); |
| if (rv == null) { |
| return EmptyBuilder.instance().<E> forExecutable().getReturnValue(meta); |
| } |
| return new XmlBuilder.ForReturnValue<>(rv); |
| } |
| |
| @Override |
| public List<MetadataBuilder.ForContainer<Parameter>> getParameters(Meta<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<T> extends ForExecutable<ForConstructor<T>, Constructor<? extends T>, 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)) |
| .withGetIgnoreAnnotations(ReturnValueType::getIgnoreAnnotations).withGetValid(ReturnValueType::getValid) |
| .withGetGroupConversions(ReturnValueType::getConvertGroup) |
| .withGetContainerElementTypes(ReturnValueType::getContainerElementType); |
| } |
| } |
| |
| private static final <T> T lazy(Lazy<T> lazy, String name) { |
| Validate.validState(lazy != null, "%s not set", name); |
| return lazy.get(); |
| } |
| |
| private final ApacheValidatorFactory validatorFactory; |
| private final ConstraintMappingsType constraintMappings; |
| private final Version version; |
| |
| public XmlBuilder(ApacheValidatorFactory validatorFactory, ConstraintMappingsType constraintMappings) { |
| super(); |
| this.validatorFactory = Validate.notNull(validatorFactory, "validatorFactory"); |
| this.constraintMappings = Validate.notNull(constraintMappings, "constraintMappings"); |
| this.version = Version.of(constraintMappings); |
| new MappingValidator(constraintMappings, this::resolveClass).validateMappings(); |
| } |
| |
| 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) >= 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.loaderFromThreadOrClass(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); |
| } |
| |
| private <A extends Annotation, T> A createConstraint(final ConstraintType constraint, ConstraintTarget target) { |
| final Class<A> annotationClass = this.<A> loadClass(toQualifiedClassName(constraint.getAnnotation())); |
| final AnnotationProxyBuilder<A> annoBuilder = |
| validatorFactory.getAnnotationsManager().buildProxyFor(annotationClass); |
| |
| if (constraint.getMessage() != null) { |
| annoBuilder.setMessage(constraint.getMessage()); |
| } |
| annoBuilder.setGroups(getGroups(constraint.getGroups())); |
| annoBuilder.setPayload(getPayload(constraint.getPayload())); |
| |
| if (ConstraintAnnotationAttributes.VALIDATION_APPLIES_TO.analyze(annotationClass).isValid()) { |
| annoBuilder.setValidationAppliesTo(target); |
| } |
| for (final ElementType elementType : constraint.getElement()) { |
| final String name = elementType.getName(); |
| final Class<?> returnType = getAnnotationParameterType(annotationClass, name); |
| final Object elementValue = getElementValue(elementType, returnType); |
| annoBuilder.setValue(name, elementValue); |
| } |
| return annoBuilder.createAnnotation(); |
| } |
| |
| 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); |
| } |
| } |
| 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, |
| "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 = |
| validatorFactory.getAnnotationsManager().buildProxyFor(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); |
| } |
| } |