http://git-wip-us.apache.org/repos/asf/bval/blob/3f287a7a/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/3f287a7a/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; + } +} http://git-wip-us.apache.org/repos/asf/bval/blob/3f287a7a/bval-jsr/src/main/java/org/apache/bval/jsr/parameter/DefaultParameterNameProvider.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/parameter/DefaultParameterNameProvider.java b/bval-jsr/src/main/java/org/apache/bval/jsr/parameter/DefaultParameterNameProvider.java index 2b43bcd..dec9ae8 100644 --- a/bval-jsr/src/main/java/org/apache/bval/jsr/parameter/DefaultParameterNameProvider.java +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/parameter/DefaultParameterNameProvider.java @@ -18,30 +18,29 @@ */ package org.apache.bval.jsr.parameter; -import javax.validation.ParameterNameProvider; import java.lang.reflect.Constructor; +import java.lang.reflect.Executable; import java.lang.reflect.Method; -import java.util.ArrayList; +import java.lang.reflect.Parameter; import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import javax.validation.ParameterNameProvider; public class DefaultParameterNameProvider implements ParameterNameProvider { - private static final String ARG = "arg"; + + private static List<String> parameterNames(Executable exe) { + return Stream.of(exe.getParameters()).map(Parameter::getName).collect(Collectors.toList()); + } @Override public List<String> getParameterNames(Constructor<?> constructor) { - return names(constructor.getParameterTypes().length); + return parameterNames(constructor); } @Override public List<String> getParameterNames(Method method) { - return names(method.getParameterTypes().length); - } - - private static List<String> names(final int length) { - final List<String> list = new ArrayList<String>(); - for (int i = 0; i < length; i++) { - list.add(ARG + i); - } - return list; + return parameterNames(method); } } http://git-wip-us.apache.org/repos/asf/bval/blob/3f287a7a/bval-jsr/src/main/java/org/apache/bval/jsr/resolver/CachingTraversableResolver.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/resolver/CachingTraversableResolver.java b/bval-jsr/src/main/java/org/apache/bval/jsr/resolver/CachingTraversableResolver.java index 05639c7..2f212de 100644 --- a/bval-jsr/src/main/java/org/apache/bval/jsr/resolver/CachingTraversableResolver.java +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/resolver/CachingTraversableResolver.java @@ -21,6 +21,7 @@ import javax.validation.TraversableResolver; import java.lang.annotation.ElementType; import java.util.HashMap; import java.util.Map; +import java.util.Objects; /** * Cache results of a delegated traversable resovler to optimize calls @@ -34,7 +35,7 @@ import java.util.Map; */ public class CachingTraversableResolver implements TraversableResolver, CachingRelevant { private TraversableResolver delegate; - private Map<CacheEntry, CacheEntry> cache = new HashMap<CacheEntry, CacheEntry>(); + private Map<CacheEntry, CacheEntry> cache = new HashMap<>(); /** * Convenience method to check whether caching is necessary on a given {@link TraversableResolver}. @@ -61,11 +62,8 @@ public class CachingTraversableResolver implements TraversableResolver, CachingR * @see #needsCaching(TraversableResolver) */ public static TraversableResolver cacheFor(TraversableResolver traversableResolver) { - if (needsCaching(traversableResolver)) { - return new CachingTraversableResolver(traversableResolver); - } else { - return traversableResolver; - } + return needsCaching(traversableResolver) ? new CachingTraversableResolver(traversableResolver) + : traversableResolver; } /** @@ -158,15 +156,14 @@ public class CachingTraversableResolver implements TraversableResolver, CachingR if (this == o) { return true; } - if (o == null || getClass() != o.getClass()) { + if (o == null || !getClass().equals(o.getClass())) { return false; } CacheEntry that = (CacheEntry) o; - return elementType == that.elementType && path.equals(that.path) && type.equals(that.type) - && !(object != null ? !object.equals(that.object) : that.object != null) && node.equals(that.node); - + return elementType == that.elementType && Objects.equals(path, that.path) && Objects.equals(type, that.type) + && Objects.equals(object, that.object) && Objects.equals(node, that.node); } /** @@ -178,12 +175,7 @@ public class CachingTraversableResolver implements TraversableResolver, CachingR } private int buildHashCode() { - int result = object != null ? object.hashCode() : 0; - result = 31 * result + node.hashCode(); - result = 31 * result + type.hashCode(); - result = 31 * result + path.hashCode(); - result = 31 * result + elementType.hashCode(); - return result; + return Objects.hash(object, node, type, path, elementType); } } -} \ No newline at end of file +} http://git-wip-us.apache.org/repos/asf/bval/blob/3f287a7a/bval-jsr/src/main/java/org/apache/bval/jsr/util/AnnotationsManager.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/util/AnnotationsManager.java b/bval-jsr/src/main/java/org/apache/bval/jsr/util/AnnotationsManager.java new file mode 100644 index 0000000..b53b513 --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/util/AnnotationsManager.java @@ -0,0 +1,359 @@ +/* + * 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.util; + +import java.lang.annotation.Annotation; +import java.lang.annotation.Repeatable; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Collection; +import java.util.Collections; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import javax.validation.Constraint; +import javax.validation.ConstraintDefinitionException; +import javax.validation.ConstraintTarget; +import javax.validation.OverridesAttribute; +import javax.validation.Payload; +import javax.validation.ValidationException; +import javax.validation.constraintvalidation.ValidationTarget; + +import org.apache.bval.jsr.ApacheValidatorFactory; +import org.apache.bval.jsr.ConfigurationImpl; +import org.apache.bval.jsr.ConstraintAnnotationAttributes; +import org.apache.bval.jsr.ConstraintCached.ConstraintValidatorInfo; +import org.apache.bval.jsr.groups.Group; +import org.apache.bval.jsr.groups.Groups; +import org.apache.bval.jsr.groups.GroupsComputer; +import org.apache.bval.jsr.metadata.Metas; +import org.apache.bval.jsr.xml.AnnotationProxyBuilder; +import org.apache.bval.util.Exceptions; +import org.apache.bval.util.Lazy; +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; + +/** + * Manages (constraint) annotations according to the BV spec. + * + * @since 2.0 + */ +@Privilizing(@CallTo(Reflection.class)) +public class AnnotationsManager { + private static final class OverriddenAnnotationSpecifier { + final Class<? extends Annotation> annotationType; + final boolean impliesSingleComposingConstraint; + final int constraintIndex; + + OverriddenAnnotationSpecifier(OverridesAttribute annotation) { + this(annotation.constraint(), annotation.constraintIndex()); + } + + OverriddenAnnotationSpecifier(Class<? extends Annotation> annotationType, int constraintIndex) { + super(); + this.annotationType = annotationType; + this.impliesSingleComposingConstraint = constraintIndex < 0; + this.constraintIndex = Math.max(constraintIndex, 0); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj == null || !obj.getClass().equals(getClass())) { + return false; + } + final OverriddenAnnotationSpecifier other = (OverriddenAnnotationSpecifier) obj; + return Objects.equals(annotationType, other.annotationType) && constraintIndex == other.constraintIndex; + } + + @Override + public int hashCode() { + return Objects.hash(annotationType, constraintIndex); + } + } + + private static class Composition { + final Lazy<Map<OverriddenAnnotationSpecifier, Map<String, String>>> overrides = new Lazy<>(HashMap::new); + final Annotation[] components; + + Composition(Class<? extends Annotation> annotationType) { + // TODO detect recursion + components = getDeclaredConstraints(annotationType); + + if (!isComposed()) { + return; + } + final Map<Class<? extends Annotation>, AtomicInteger> constraintCounts = new HashMap<>(); + for (Annotation a : components) { + constraintCounts.computeIfAbsent(a.annotationType(), k -> new AtomicInteger()).incrementAndGet(); + } + // create a map of overridden constraints to overridden attributes: + for (Method m : Reflection.getDeclaredMethods(annotationType)) { + final String from = m.getName(); + for (OverridesAttribute overridesAttribute : m.getDeclaredAnnotationsByType(OverridesAttribute.class)) { + final String to = + Optional.of(overridesAttribute.name()).filter(StringUtils::isNotBlank).orElse(from); + + final OverriddenAnnotationSpecifier spec = new OverriddenAnnotationSpecifier(overridesAttribute); + final int count = constraintCounts.get(spec.annotationType).get(); + + if (spec.impliesSingleComposingConstraint) { + Exceptions.raiseUnless(count == 1, ConstraintDefinitionException::new, + "Expected a single composing %s constraint", spec.annotationType); + } else { + Exceptions.raiseUnless(count > spec.constraintIndex, ConstraintDefinitionException::new, + "Expected at least %s composing %s constraints", spec.constraintIndex + 1, + spec.annotationType); + } + final Map<String, String> attributeMapping = + overrides.get().computeIfAbsent(spec, k -> new HashMap<>()); + + Exceptions.raiseIf(attributeMapping.containsKey(to), ConstraintDefinitionException::new, + "Attempt to override %s#%s() index %d from multiple sources", overridesAttribute.constraint(), + to, overridesAttribute.constraintIndex()); + + attributeMapping.put(to, from); + } + } + } + + boolean isComposed() { + return components.length > 0; + } + + Annotation[] getComponents(Annotation source) { + final Class<?>[] groups = + ConstraintAnnotationAttributes.GROUPS.analyze(source.annotationType()).read(source); + + final Class<? extends Payload>[] payload = + ConstraintAnnotationAttributes.PAYLOAD.analyze(source.annotationType()).read(source); + + final Optional<ConstraintTarget> constraintTarget = + Optional.of(source.annotationType()).map(ConstraintAnnotationAttributes.VALIDATION_APPLIES_TO::analyze) + .filter(ConstraintAnnotationAttributes.Worker::isValid).map(w -> w.read(source)); + + final Map<Class<? extends Annotation>, AtomicInteger> constraintCounts = new HashMap<>(); + + return Stream.of(components).map(c -> { + final int index = + constraintCounts.computeIfAbsent(c.annotationType(), k -> new AtomicInteger()).getAndIncrement(); + + final AnnotationProxyBuilder<Annotation> proxyBuilder = new AnnotationProxyBuilder<>(c); + + proxyBuilder.setGroups(groups); + proxyBuilder.setPayload(payload); + constraintTarget.ifPresent(proxyBuilder::setValidationAppliesTo); + + overrides.optional().map(o -> o.get(new OverriddenAnnotationSpecifier(c.annotationType(), index))) + .ifPresent(m -> { + final Map<String, Object> sourceAttributes = readAttributes(source); + m.forEach((k, v) -> proxyBuilder.setValue(k, sourceAttributes.get(v))); + }); + return proxyBuilder.isChanged() ? proxyBuilder.createAnnotation() : c; + }).toArray(Annotation[]::new); + } + } + + public static Map<String, Object> readAttributes(Annotation a) { + final Lazy<Map<String, Object>> result = new Lazy<>(LinkedHashMap::new); + + Stream.of(Reflection.getDeclaredMethods(a.annotationType())).filter(m -> m.getParameterCount() == 0) + .forEach(m -> { + final boolean mustUnset = Reflection.setAccessible(m, true); + try { + result.get().put(m.getName(), m.invoke(a)); + } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { + Exceptions.raise(ValidationException::new, e, "Caught exception reading attributes of %s", a); + } finally { + if (mustUnset) { + Reflection.setAccessible(m, false); + } + } + }); + return result.optional().map(Collections::unmodifiableMap).orElseGet(Collections::emptyMap); + } + + /** + * Meta-annotation aware. + * + * @param e + * @param t + * @return {@code boolean} + * @see AnnotatedElement#isAnnotationPresent(Class) + */ + public static boolean isAnnotationPresent(AnnotatedElement e, Class<? extends Annotation> t) { + if (e.isAnnotationPresent(t)) { + return true; + } + return Stream.of(e.getAnnotations()).map(Annotation::annotationType).anyMatch(a -> isAnnotationPresent(a, t)); + } + + /** + * Get declared annotations with a particular meta-annotation. + * + * @param e + * @param meta + * @return {@link Annotation}[] + */ + public static Annotation[] getDeclared(AnnotatedElement e, Class<? extends Annotation> meta) { + return Stream.of(e.getDeclaredAnnotations()).filter(ann -> isAnnotationPresent(ann.annotationType(), meta)) + .toArray(Annotation[]::new); + } + + /** + * Accounts for {@link Constraint} meta-annotation AND {@link Repeatable} + * constraint annotations. + * + * @param meta + * @return Annotation[] + */ + public static Annotation[] getDeclaredConstraints(Metas<?> meta) { + final Annotation[] result = getDeclaredConstraints(meta.getHost()); + final Class<?> dc = meta.getDeclaringClass(); + if (dc.isInterface()) { + final GroupsComputer groupsComputer = new GroupsComputer(); + // ensure interface group is implied by Default group: + Stream.of(result).map(c -> { + final Groups groups = groupsComputer + .computeGroups(ConstraintAnnotationAttributes.GROUPS.analyze(c.annotationType()).read(c)); + if (groups.getGroups().stream().anyMatch(Group::isDefault)) { + final Set<Class<?>> groupClasses = groups.getGroups().stream().map(Group::getGroup) + .collect(Collectors.toCollection(LinkedHashSet::new)); + if (groupClasses.add(dc)) { + final AnnotationProxyBuilder<?> proxyBuilder = new AnnotationProxyBuilder<>(c); + proxyBuilder.setGroups(groupClasses.toArray(new Class[groupClasses.size()])); + return proxyBuilder.createAnnotation(); + } + } + return c; + }).toArray(n -> result); + } + return result; + } + + private static Annotation[] getDeclaredConstraints(AnnotatedElement e) { + return Stream.of(e.getDeclaredAnnotations()).flatMap((Function<Annotation, Stream<Annotation>>) a -> { + final ConstraintAnnotationAttributes.Worker<? extends Annotation> analyzer = + ConstraintAnnotationAttributes.VALUE.analyze(a.annotationType()); + if (analyzer.isValid()) { + return Stream.of(analyzer.<Annotation[]> read(a)); + } + return Stream.of(a); + }).filter(a -> a.annotationType().isAnnotationPresent(Constraint.class)).toArray(Annotation[]::new); + } + + public static boolean declaresAttribute(Class<? extends Annotation> annotationType, String name) { + try { + annotationType.getDeclaredMethod(name); + return true; + } catch (NoSuchMethodException | SecurityException e) { + return false; + } + } + + private final ApacheValidatorFactory validatorFactory; + private final LRUCache<Class<? extends Annotation>, Composition> compositions; + + public AnnotationsManager(ApacheValidatorFactory validatorFactory) { + super(); + this.validatorFactory = Validate.notNull(validatorFactory); + final String cacheSize = + validatorFactory.getProperties().get(ConfigurationImpl.Properties.CONSTRAINTS_CACHE_SIZE); + try { + compositions = new LRUCache<>(Integer.parseInt(cacheSize)); + } catch (NumberFormatException e) { + throw Exceptions.create(IllegalStateException::new, e, + "Cannot parse value %s for configuration property %s", cacheSize, + ConfigurationImpl.Properties.CONSTRAINTS_CACHE_SIZE); + } + } + + /** + * Retrieve the composing constraints for the specified constraint + * {@link Annotation}. + * + * @param a + * @return {@link Annotation}[] + */ + public Annotation[] getComposingConstraints(Annotation a) { + return getComposition(a.annotationType()).getComponents(a); + } + + /** + * Learn whether {@code a} is composed. + * + * @param a + * @return {@code boolean} + */ + public boolean isComposed(Annotation a) { + return getComposition(a.annotationType()).isComposed(); + } + + /** + * Get the supported targets for {@code constraintType}. + * + * @param constraintType + * @return {@link Set} of {@link ValidationTarget} + */ + public <A extends Annotation> Set<ValidationTarget> supportedTargets(Class<A> constraintType) { + final Set<ConstraintValidatorInfo<A>> constraintValidatorInfo = + validatorFactory.getConstraintsCache().getConstraintValidatorInfo(constraintType); + final Stream<Set<ValidationTarget>> s; + if (constraintValidatorInfo.isEmpty()) { + // must be for composition: + s = Stream.of(new Composition(constraintType).components).map(Annotation::annotationType) + .map(this::supportedTargets); + } else { + s = constraintValidatorInfo.stream().map(ConstraintValidatorInfo::getSupportedTargets); + } + return s.flatMap(Collection::stream) + .collect(Collectors.toCollection(() -> EnumSet.noneOf(ValidationTarget.class))); + } + + private Composition getComposition(Class<? extends Annotation> annotationType) { + return compositions.computeIfAbsent(annotationType, ct -> { + final Set<ValidationTarget> composedTargets = supportedTargets(annotationType); + final Composition result = new Composition(annotationType); + Stream.of(result.components).map(Annotation::annotationType).forEach(at -> { + final Set<ValidationTarget> composingTargets = supportedTargets(at); + Exceptions.raiseIf(Collections.disjoint(composingTargets, composedTargets), + ConstraintDefinitionException::new, + "Attempt to compose %s of %s but validator types are incompatible", annotationType.getName(), + at.getName()); + }); + return result; + }); + } +} http://git-wip-us.apache.org/repos/asf/bval/blob/3f287a7a/bval-jsr/src/main/java/org/apache/bval/jsr/util/ClassHelper.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/util/ClassHelper.java b/bval-jsr/src/main/java/org/apache/bval/jsr/util/ClassHelper.java index 9d3bd85..73c82a6 100644 --- a/bval-jsr/src/main/java/org/apache/bval/jsr/util/ClassHelper.java +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/util/ClassHelper.java @@ -20,7 +20,11 @@ package org.apache.bval.jsr.util; import java.io.Serializable; import java.security.AccessController; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; import java.util.List; +import java.util.Set; /** * Common operations on classes that do not require an {@link AccessController}. @@ -28,6 +32,7 @@ import java.util.List; * @author Carlos Vara */ public class ClassHelper { + private static final Set<Class<?>> IGNORED_TYPES = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(null,Object.class,Serializable.class,Cloneable.class))); private ClassHelper() { // No instances please @@ -42,10 +47,7 @@ public class ClassHelper { * @param clazz */ public static List<Class<?>> fillFullClassHierarchyAsList(List<Class<?>> allClasses, Class<?> clazz) { - if (clazz == null || clazz == Object.class || clazz == Serializable.class || clazz == Cloneable.class) { - return allClasses; - } - if (allClasses.contains(clazz)) { + if (IGNORED_TYPES.contains(clazz) || allClasses.contains(clazz)) { return allClasses; } allClasses.add(clazz); http://git-wip-us.apache.org/repos/asf/bval/blob/3f287a7a/bval-jsr/src/main/java/org/apache/bval/jsr/util/ContainerElementNodeBuilderCustomizableContextImpl.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/util/ContainerElementNodeBuilderCustomizableContextImpl.java b/bval-jsr/src/main/java/org/apache/bval/jsr/util/ContainerElementNodeBuilderCustomizableContextImpl.java new file mode 100644 index 0000000..c0cff10 --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/util/ContainerElementNodeBuilderCustomizableContextImpl.java @@ -0,0 +1,77 @@ +/* + * 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.util; + +import javax.validation.ConstraintValidatorContext; +import javax.validation.ConstraintValidatorContext.ConstraintViolationBuilder.ContainerElementNodeBuilderCustomizableContext; +import javax.validation.ConstraintValidatorContext.ConstraintViolationBuilder.ContainerElementNodeContextBuilder; +import javax.validation.ConstraintValidatorContext.ConstraintViolationBuilder.LeafNodeBuilderCustomizableContext; +import javax.validation.ConstraintValidatorContext.ConstraintViolationBuilder.NodeBuilderCustomizableContext; + +import org.apache.bval.jsr.job.ConstraintValidatorContextImpl; + +public class ContainerElementNodeBuilderCustomizableContextImpl + implements ContainerElementNodeBuilderCustomizableContext { + private final ConstraintValidatorContextImpl<?> context; + private final String template; + private final PathImpl path; + private NodeImpl node; + + public ContainerElementNodeBuilderCustomizableContextImpl(ConstraintValidatorContextImpl<?> context, String template, + PathImpl path, String name, Class<?> containerType, Integer typeArgumentIndex) { + super(); + this.context = context; + this.path = path; + this.template = template; + this.node = new NodeImpl.ContainerElementNodeImpl(name, containerType, typeArgumentIndex); + } + + @Override + public ContainerElementNodeContextBuilder inIterable() { + node.setInIterable(true); + return new ContainerElementNodeContextBuilderImpl(context, template, path, node); + } + + @Override + public NodeBuilderCustomizableContext addPropertyNode(String name) { + path.addNode(node); + return new NodeBuilderCustomizableContextImpl(context, template, path, name); + } + + @Override + public LeafNodeBuilderCustomizableContext addBeanNode() { + path.addNode(node); + return new LeafNodeBuilderCustomizableContextImpl(context, template, path); + } + + @Override + public ContainerElementNodeBuilderCustomizableContext addContainerElementNode(String name, Class<?> containerType, + Integer typeArgumentIndex) { + path.addNode(node); + node = new NodeImpl.ContainerElementNodeImpl(name, containerType, typeArgumentIndex); + return this; + } + + @Override + public ConstraintValidatorContext addConstraintViolation() { + context.addError(template, path); + return context; + } + +} http://git-wip-us.apache.org/repos/asf/bval/blob/3f287a7a/bval-jsr/src/main/java/org/apache/bval/jsr/util/ContainerElementNodeBuilderDefinedContextImpl.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/util/ContainerElementNodeBuilderDefinedContextImpl.java b/bval-jsr/src/main/java/org/apache/bval/jsr/util/ContainerElementNodeBuilderDefinedContextImpl.java new file mode 100644 index 0000000..6077d87 --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/util/ContainerElementNodeBuilderDefinedContextImpl.java @@ -0,0 +1,65 @@ +/* + * 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.util; + +import javax.validation.ConstraintValidatorContext; +import javax.validation.ConstraintValidatorContext.ConstraintViolationBuilder.ContainerElementNodeBuilderCustomizableContext; +import javax.validation.ConstraintValidatorContext.ConstraintViolationBuilder.ContainerElementNodeBuilderDefinedContext; +import javax.validation.ConstraintValidatorContext.ConstraintViolationBuilder.LeafNodeBuilderCustomizableContext; +import javax.validation.ConstraintValidatorContext.ConstraintViolationBuilder.NodeBuilderCustomizableContext; + +import org.apache.bval.jsr.job.ConstraintValidatorContextImpl; + +public class ContainerElementNodeBuilderDefinedContextImpl implements ContainerElementNodeBuilderDefinedContext { + private final ConstraintValidatorContextImpl<?> context; + private final String template; + private final PathImpl path; + + ContainerElementNodeBuilderDefinedContextImpl(ConstraintValidatorContextImpl<?> context, String template, + PathImpl path) { + super(); + this.context = context; + this.template = template; + this.path = path; + } + + @Override + public NodeBuilderCustomizableContext addPropertyNode(String name) { + return new NodeBuilderCustomizableContextImpl(context, template, path, name); + } + + @Override + public LeafNodeBuilderCustomizableContext addBeanNode() { + return new LeafNodeBuilderCustomizableContextImpl(context, template, path); + } + + @Override + public ContainerElementNodeBuilderCustomizableContext addContainerElementNode(String name, Class<?> containerType, + Integer typeArgumentIndex) { + return new ContainerElementNodeBuilderCustomizableContextImpl(context, name, path, name, containerType, + typeArgumentIndex); + } + + @Override + public ConstraintValidatorContext addConstraintViolation() { + context.addError(template, path); + return context; + } + +} http://git-wip-us.apache.org/repos/asf/bval/blob/3f287a7a/bval-jsr/src/main/java/org/apache/bval/jsr/util/ContainerElementNodeContextBuilderImpl.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/util/ContainerElementNodeContextBuilderImpl.java b/bval-jsr/src/main/java/org/apache/bval/jsr/util/ContainerElementNodeContextBuilderImpl.java new file mode 100644 index 0000000..f05ef76 --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/util/ContainerElementNodeContextBuilderImpl.java @@ -0,0 +1,85 @@ +/* + * 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.util; + +import javax.validation.ConstraintValidatorContext; +import javax.validation.ConstraintValidatorContext.ConstraintViolationBuilder.ContainerElementNodeBuilderCustomizableContext; +import javax.validation.ConstraintValidatorContext.ConstraintViolationBuilder.ContainerElementNodeBuilderDefinedContext; +import javax.validation.ConstraintValidatorContext.ConstraintViolationBuilder.ContainerElementNodeContextBuilder; +import javax.validation.ConstraintValidatorContext.ConstraintViolationBuilder.LeafNodeBuilderCustomizableContext; +import javax.validation.ConstraintValidatorContext.ConstraintViolationBuilder.NodeBuilderCustomizableContext; + +import org.apache.bval.jsr.job.ConstraintValidatorContextImpl; + +public class ContainerElementNodeContextBuilderImpl implements ContainerElementNodeContextBuilder { + private final ConstraintValidatorContextImpl<?> context; + private final String template; + private final PathImpl path; + private final NodeImpl node; + + ContainerElementNodeContextBuilderImpl(ConstraintValidatorContextImpl<?> context, String template, + PathImpl path, NodeImpl node) { + super(); + this.context = context; + this.template = template; + this.path = path; + this.node = node; + } + + @Override + public ContainerElementNodeBuilderDefinedContext atKey(Object key) { + node.setKey(key); + path.addNode(node); + return new ContainerElementNodeBuilderDefinedContextImpl(context, template, path); + } + + @Override + public ContainerElementNodeBuilderDefinedContext atIndex(Integer index) { + node.setIndex(index); + path.addNode(node); + return new ContainerElementNodeBuilderDefinedContextImpl(context, template, path); + } + + @Override + public NodeBuilderCustomizableContext addPropertyNode(String name) { + path.addNode(node); + return new NodeBuilderCustomizableContextImpl(context, name, path, name); + } + + @Override + public LeafNodeBuilderCustomizableContext addBeanNode() { + path.addNode(node); + return new LeafNodeBuilderCustomizableContextImpl(context, template, path); + } + + @Override + public ContainerElementNodeBuilderCustomizableContext addContainerElementNode(String name, Class<?> containerType, + Integer typeArgumentIndex) { + path.addNode(node); + return new ContainerElementNodeBuilderCustomizableContextImpl(context, template, path, name, containerType, + typeArgumentIndex); + } + + @Override + public ConstraintValidatorContext addConstraintViolation() { + context.addError(template, path); + return context; + } + +} http://git-wip-us.apache.org/repos/asf/bval/blob/3f287a7a/bval-jsr/src/main/java/org/apache/bval/jsr/util/IOs.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/util/IOs.java b/bval-jsr/src/main/java/org/apache/bval/jsr/util/IOs.java index 611a9d6..57f7cf4 100644 --- a/bval-jsr/src/main/java/org/apache/bval/jsr/util/IOs.java +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/util/IOs.java @@ -33,25 +33,20 @@ public class IOs { if (stream == null) { return null; } - - // force ByteArrayOutputStream since we close the stream ATM - /*if (stream.markSupported()) { - return stream; - } else {*/ - try { + try (InputStream in = stream) { final ByteArrayOutputStream baos = new ByteArrayOutputStream(); final byte[] buffer = new byte[1024]; int length; - while ((length = stream.read(buffer)) != -1) { + while ((length = in.read(buffer)) != -1) { baos.write(buffer, 0, length); } return new ByteArrayInputStream(baos.toByteArray()); } catch (final IOException e) { throw new RuntimeException(e); } - /*}*/ } + //TODO see if needed public static void closeQuietly(Closeable closeable) { if (closeable != null) { try { http://git-wip-us.apache.org/repos/asf/bval/blob/3f287a7a/bval-jsr/src/main/java/org/apache/bval/jsr/util/LRUCache.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/util/LRUCache.java b/bval-jsr/src/main/java/org/apache/bval/jsr/util/LRUCache.java new file mode 100644 index 0000000..48fcd7d --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/util/LRUCache.java @@ -0,0 +1,41 @@ +/* + * 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.util; + +import java.util.LinkedHashMap; +import java.util.Map; + +public class LRUCache<K, V> extends LinkedHashMap<K, V> { + private static final long serialVersionUID = 1L; + + private final int maximumCapacity; + + public LRUCache(int maximumCapacity) { + super(16, 0.75f, true); + if (maximumCapacity < 1) { + throw new IllegalArgumentException("maximumCapacity must be > 0"); + } + this.maximumCapacity = maximumCapacity; + } + + @Override + protected boolean removeEldestEntry(Map.Entry<K, V> eldest) { + return super.removeEldestEntry(eldest) || size() >= maximumCapacity; + } +} http://git-wip-us.apache.org/repos/asf/bval/blob/3f287a7a/bval-jsr/src/main/java/org/apache/bval/jsr/util/LeafNodeBuilderCustomizableContextImpl.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/util/LeafNodeBuilderCustomizableContextImpl.java b/bval-jsr/src/main/java/org/apache/bval/jsr/util/LeafNodeBuilderCustomizableContextImpl.java index efa9aeb..99305be 100644 --- a/bval-jsr/src/main/java/org/apache/bval/jsr/util/LeafNodeBuilderCustomizableContextImpl.java +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/util/LeafNodeBuilderCustomizableContextImpl.java @@ -18,9 +18,10 @@ */ package org.apache.bval.jsr.util; -import org.apache.bval.jsr.ConstraintValidatorContextImpl; +import org.apache.bval.jsr.job.ConstraintValidatorContextImpl; import javax.validation.ConstraintValidatorContext; +import javax.validation.ConstraintValidatorContext.ConstraintViolationBuilder.LeafNodeBuilderCustomizableContext; import javax.validation.ConstraintValidatorContext.ConstraintViolationBuilder.LeafNodeBuilderDefinedContext; import javax.validation.ConstraintValidatorContext.ConstraintViolationBuilder.LeafNodeContextBuilder; @@ -43,8 +44,7 @@ public class LeafNodeBuilderCustomizableContextImpl } @Override - public LeafNodeBuilderDefinedContext atIndex( - Integer index) { + public LeafNodeBuilderDefinedContext atIndex(Integer index) { node.setIndex(index); return definedContext; } @@ -55,16 +55,16 @@ public class LeafNodeBuilderCustomizableContextImpl } } - private final ConstraintValidatorContextImpl context; + private final ConstraintValidatorContextImpl<?> context; private final PathImpl path; private final String template; private final NodeImpl node; - public LeafNodeBuilderCustomizableContextImpl(final ConstraintValidatorContextImpl parent, String messageTemplate, - PathImpl propertyPath) { - context = parent; - template = messageTemplate; - path = propertyPath; + public LeafNodeBuilderCustomizableContextImpl(final ConstraintValidatorContextImpl<?> context, String template, + PathImpl path) { + this.context = context; + this.template = template; + this.path = path; node = new NodeImpl.BeanNodeImpl(); } @@ -81,4 +81,9 @@ public class LeafNodeBuilderCustomizableContextImpl return context; } + @Override + public LeafNodeBuilderCustomizableContext inContainer(Class<?> containerType, Integer typeArgumentIndex) { + node.inContainer(containerType, typeArgumentIndex); + return this; + } } http://git-wip-us.apache.org/repos/asf/bval/blob/3f287a7a/bval-jsr/src/main/java/org/apache/bval/jsr/util/Methods.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/util/Methods.java b/bval-jsr/src/main/java/org/apache/bval/jsr/util/Methods.java new file mode 100644 index 0000000..9f98311 --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/util/Methods.java @@ -0,0 +1,45 @@ +/* + * 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.util; + +import java.beans.Introspector; +import java.lang.reflect.Method; + +import org.apache.bval.util.Validate; + +public final class Methods { + public static boolean isGetter(Method m) { + if (m.getParameterCount() > 0) { + return false; + } + // TODO look for capital letter after verb? + if (Boolean.TYPE.equals(m.getReturnType()) && m.getName().startsWith("is")) { + return true; + } + return !Void.TYPE.equals(m.getReturnType()) && m.getName().startsWith("get"); + } + + public static String propertyName(Method getter) { + Validate.isTrue(isGetter(getter), "%s is not a getter", getter); + final String name = getter.getName(); + final String suffix = name.startsWith("is") ? name.substring(2) : name.substring(3); + return Introspector.decapitalize(suffix); + } + + private Methods() { + } +} http://git-wip-us.apache.org/repos/asf/bval/blob/3f287a7a/bval-jsr/src/main/java/org/apache/bval/jsr/util/NodeBuilderCustomizableContextImpl.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/util/NodeBuilderCustomizableContextImpl.java b/bval-jsr/src/main/java/org/apache/bval/jsr/util/NodeBuilderCustomizableContextImpl.java index ca058fc..6ec977c 100644 --- a/bval-jsr/src/main/java/org/apache/bval/jsr/util/NodeBuilderCustomizableContextImpl.java +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/util/NodeBuilderCustomizableContextImpl.java @@ -18,38 +18,40 @@ */ package org.apache.bval.jsr.util; -import org.apache.bval.jsr.ConstraintValidatorContextImpl; - import javax.validation.ConstraintValidatorContext; +import javax.validation.ConstraintValidatorContext.ConstraintViolationBuilder.ContainerElementNodeBuilderCustomizableContext; +import javax.validation.ConstraintValidatorContext.ConstraintViolationBuilder.NodeBuilderCustomizableContext; import javax.validation.ElementKind; +import org.apache.bval.jsr.job.ConstraintValidatorContextImpl; + /** * Description: implementation of {@link javax.validation.ConstraintValidatorContext.ConstraintViolationBuilder.NodeBuilderCustomizableContext}.<br/> */ public final class NodeBuilderCustomizableContextImpl implements ConstraintValidatorContext.ConstraintViolationBuilder.NodeBuilderCustomizableContext { - private final ConstraintValidatorContextImpl parent; - private final String messageTemplate; - private final PathImpl propertyPath; + private final ConstraintValidatorContextImpl<?> context; + private final String template; + private final PathImpl path; private NodeImpl node; /** * Create a new NodeBuilderCustomizableContextImpl instance. - * @param contextImpl + * @param context * @param template * @param path * @param name */ - public NodeBuilderCustomizableContextImpl(ConstraintValidatorContextImpl contextImpl, String template, PathImpl path, + public NodeBuilderCustomizableContextImpl(ConstraintValidatorContextImpl<?> context, String template, PathImpl path, String name) { - parent = contextImpl; - messageTemplate = template; - propertyPath = path; + this.context = context; + this.template = template; + this.path = path; - if (propertyPath.isRootPath() || propertyPath.getLeafNode().getKind() != null) { + if (path.isRootPath() || path.getLeafNode().getKind() != null) { node = new NodeImpl.PropertyNodeImpl(name); } else { - node = propertyPath.removeLeafNode(); + node = path.removeLeafNode(); node.setName(name); node.setKind(ElementKind.PROPERTY); // enforce it } @@ -61,7 +63,7 @@ public final class NodeBuilderCustomizableContextImpl @Override public ConstraintValidatorContext.ConstraintViolationBuilder.NodeContextBuilder inIterable() { node.setInIterable(true); - return new NodeContextBuilderImpl(parent, messageTemplate, propertyPath, node); + return new NodeContextBuilderImpl(context, template, path, node); } /** @@ -75,15 +77,15 @@ public final class NodeBuilderCustomizableContextImpl @Override public ConstraintValidatorContext.ConstraintViolationBuilder.NodeBuilderCustomizableContext addPropertyNode( String name) { - propertyPath.addNode(node); + path.addNode(node); node = new NodeImpl.PropertyNodeImpl(name); return this; } @Override public ConstraintValidatorContext.ConstraintViolationBuilder.LeafNodeBuilderCustomizableContext addBeanNode() { - propertyPath.addNode(node); - return new LeafNodeBuilderCustomizableContextImpl(parent, messageTemplate, propertyPath); + path.addNode(node); + return new LeafNodeBuilderCustomizableContextImpl(context, template, path); } /** @@ -91,10 +93,25 @@ public final class NodeBuilderCustomizableContextImpl */ @Override public ConstraintValidatorContext addConstraintViolation() { - propertyPath.addNode(node); + path.addNode(node); node = null; - parent.addError(messageTemplate, propertyPath); - return parent; + context.addError(template, path); + return context; + } + + @Override + public NodeBuilderCustomizableContext inContainer(Class<?> containerClass, Integer typeArgumentIndex) { + path.getLeafNode().inContainer(containerClass, typeArgumentIndex); + return this; + } + + @Override + public ContainerElementNodeBuilderCustomizableContext addContainerElementNode(String name, Class<?> containerType, + Integer typeArgumentIndex) { + path.addNode(node); + node = new NodeImpl.ContainerElementNodeImpl(name, containerType, typeArgumentIndex); + return new ContainerElementNodeBuilderCustomizableContextImpl(context, template, path, name, containerType, + typeArgumentIndex); } } http://git-wip-us.apache.org/repos/asf/bval/blob/3f287a7a/bval-jsr/src/main/java/org/apache/bval/jsr/util/NodeBuilderDefinedContextImpl.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/util/NodeBuilderDefinedContextImpl.java b/bval-jsr/src/main/java/org/apache/bval/jsr/util/NodeBuilderDefinedContextImpl.java index 5ce20b5..f695e84 100644 --- a/bval-jsr/src/main/java/org/apache/bval/jsr/util/NodeBuilderDefinedContextImpl.java +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/util/NodeBuilderDefinedContextImpl.java @@ -18,18 +18,19 @@ */ package org.apache.bval.jsr.util; -import org.apache.bval.jsr.ConstraintValidatorContextImpl; - import javax.validation.ConstraintValidatorContext; +import javax.validation.ConstraintValidatorContext.ConstraintViolationBuilder.ContainerElementNodeBuilderCustomizableContext; + +import org.apache.bval.jsr.job.ConstraintValidatorContextImpl; /** * Description: Implementation of {@link NodeBuilderDefinedContext}.<br/> */ public final class NodeBuilderDefinedContextImpl implements ConstraintValidatorContext.ConstraintViolationBuilder.NodeBuilderDefinedContext { - private final ConstraintValidatorContextImpl parent; - private final String messageTemplate; - private final PathImpl propertyPath; + private final ConstraintValidatorContextImpl context; + private final String template; + private final PathImpl path; /** * Create a new NodeBuilderDefinedContextImpl instance. @@ -38,9 +39,9 @@ public final class NodeBuilderDefinedContextImpl * @param path */ public NodeBuilderDefinedContextImpl(ConstraintValidatorContextImpl contextImpl, String template, PathImpl path) { - parent = contextImpl; - messageTemplate = template; - propertyPath = path; + this.context = contextImpl; + this.template = template; + this.path = path; } /** @@ -54,12 +55,12 @@ public final class NodeBuilderDefinedContextImpl @Override public ConstraintValidatorContext.ConstraintViolationBuilder.NodeBuilderCustomizableContext addPropertyNode( String name) { - return new NodeBuilderCustomizableContextImpl(parent, messageTemplate, propertyPath, name); + return new NodeBuilderCustomizableContextImpl(context, template, path, name); } @Override public ConstraintValidatorContext.ConstraintViolationBuilder.LeafNodeBuilderCustomizableContext addBeanNode() { - return new LeafNodeBuilderCustomizableContextImpl(parent, messageTemplate, propertyPath); + return new LeafNodeBuilderCustomizableContextImpl(context, template, path); } /** @@ -67,7 +68,14 @@ public final class NodeBuilderDefinedContextImpl */ @Override public ConstraintValidatorContext addConstraintViolation() { - parent.addError(messageTemplate, propertyPath); - return parent; + context.addError(template, path); + return context; + } + + @Override + public ContainerElementNodeBuilderCustomizableContext addContainerElementNode(String name, Class<?> containerType, + Integer typeArgumentIndex) { + return new ContainerElementNodeBuilderCustomizableContextImpl(context, template, path, name, containerType, + typeArgumentIndex); } }
