BV2: xml processing changes
Project: http://git-wip-us.apache.org/repos/asf/bval/repo Commit: http://git-wip-us.apache.org/repos/asf/bval/commit/5c09f0dd Tree: http://git-wip-us.apache.org/repos/asf/bval/tree/5c09f0dd Diff: http://git-wip-us.apache.org/repos/asf/bval/diff/5c09f0dd Branch: refs/heads/bv2 Commit: 5c09f0dd1bfa62aa444287fbf2c1882f539b02d6 Parents: af6ee90 Author: Matt Benson <[email protected]> Authored: Wed Feb 21 14:29:48 2018 -0600 Committer: Matt Benson <[email protected]> Committed: Wed Feb 21 14:44:27 2018 -0600 ---------------------------------------------------------------------- .../apache/bval/jsr/xml/AnnotationProxy.java | 59 +- .../bval/jsr/xml/AnnotationProxyBuilder.java | 104 ++- .../org/apache/bval/jsr/xml/SchemaManager.java | 286 +++++++ .../bval/jsr/xml/ValidationMappingParser.java | 749 ++----------------- .../apache/bval/jsr/xml/ValidationParser.java | 330 ++++---- .../java/org/apache/bval/jsr/xml/XmlUtils.java | 67 ++ bval-jsr/src/main/xjb/binding-customization.xjb | 4 +- .../main/xsd/validation-configuration-2.0.xsd | 75 ++ .../src/main/xsd/validation-mapping-2.0.xsd | 297 ++++++++ .../test/java/org/apache/bval/jsr/xml/Demo.java | 40 + .../bval/jsr/xml/ValidationParserTest.java | 18 +- .../src/test/resources/sample-validation11.xml | 30 + .../src/test/resources/sample-validation2.xml | 30 + 13 files changed, 1116 insertions(+), 973 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/bval/blob/5c09f0dd/bval-jsr/src/main/java/org/apache/bval/jsr/xml/AnnotationProxy.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/xml/AnnotationProxy.java b/bval-jsr/src/main/java/org/apache/bval/jsr/xml/AnnotationProxy.java index 77aed70..96a2b46 100644 --- a/bval-jsr/src/main/java/org/apache/bval/jsr/xml/AnnotationProxy.java +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/xml/AnnotationProxy.java @@ -16,20 +16,21 @@ */ package org.apache.bval.jsr.xml; -import javax.validation.Valid; import java.io.Serializable; import java.lang.annotation.Annotation; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; -import java.util.HashMap; -import java.util.Map; -import java.util.SortedSet; -import java.util.TreeSet; +import java.util.SortedMap; +import java.util.TreeMap; +import java.util.stream.Collectors; + +import javax.validation.Valid; + +import org.apache.bval.util.Exceptions; /** * Description: <br/> - * InvocationHandler implementation of <code>Annotation</code> that pretends it - * is a "real" source code annotation. + * InvocationHandler implementation of <code>Annotation</code> that pretends it is a "real" source code annotation. * <p/> */ class AnnotationProxy implements Annotation, InvocationHandler, Serializable { @@ -38,7 +39,7 @@ class AnnotationProxy implements Annotation, InvocationHandler, Serializable { private static final long serialVersionUID = 1L; private final Class<? extends Annotation> annotationType; - private final Map<String, Object> values; + private final SortedMap<String, Object> values; /** * Create a new AnnotationProxy instance. @@ -46,28 +47,23 @@ class AnnotationProxy implements Annotation, InvocationHandler, Serializable { * @param <A> * @param descriptor */ - public <A extends Annotation> AnnotationProxy(AnnotationProxyBuilder<A> descriptor) { + <A extends Annotation> AnnotationProxy(AnnotationProxyBuilder<A> descriptor) { this.annotationType = descriptor.getType(); - values = getAnnotationValues(descriptor); - } - - private <A extends Annotation> Map<String, Object> getAnnotationValues(AnnotationProxyBuilder<A> descriptor) { - final Map<String, Object> result = new HashMap<String, Object>(); + values = new TreeMap<>(); int processedValuesFromDescriptor = 0; for (final Method m : descriptor.getMethods()) { if (descriptor.contains(m.getName())) { - result.put(m.getName(), descriptor.getValue(m.getName())); + values.put(m.getName(), descriptor.getValue(m.getName())); processedValuesFromDescriptor++; - } else if (m.getDefaultValue() != null) { - result.put(m.getName(), m.getDefaultValue()); } else { - throw new IllegalArgumentException("No value provided for " + m.getName()); + Exceptions.raiseIf(m.getDefaultValue() == null, IllegalArgumentException::new, + "No value provided for %s", m.getName()); + values.put(m.getName(), m.getDefaultValue()); } } - if (processedValuesFromDescriptor != descriptor.size() && !Valid.class.equals(annotationType)) { - throw new RuntimeException("Trying to instanciate " + annotationType + " with unknown paramters."); - } - return result; + Exceptions.raiseUnless(processedValuesFromDescriptor == descriptor.size() || Valid.class.equals(annotationType), + IllegalArgumentException::new, "Trying to instantiate %s with unknown parameters.", + annotationType.getName()); } /** @@ -94,22 +90,7 @@ class AnnotationProxy implements Annotation, InvocationHandler, Serializable { */ @Override public String toString() { - StringBuilder result = new StringBuilder(); - result.append('@').append(annotationType().getName()).append('('); - boolean comma = false; - for (String m : getMethodsSorted()) { - if (comma) - result.append(", "); - result.append(m).append('=').append(values.get(m)); - comma = true; - } - result.append(")"); - return result.toString(); - } - - private SortedSet<String> getMethodsSorted() { - SortedSet<String> result = new TreeSet<String>(); - result.addAll(values.keySet()); - return result; + return values.entrySet().stream().map(e -> String.format("%s=%s", e.getKey(), e.getValue())) + .collect(Collectors.joining(", ", String.format("@%s(", annotationType().getName()), ")")); } } http://git-wip-us.apache.org/repos/asf/bval/blob/5c09f0dd/bval-jsr/src/main/java/org/apache/bval/jsr/xml/AnnotationProxyBuilder.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/xml/AnnotationProxyBuilder.java b/bval-jsr/src/main/java/org/apache/bval/jsr/xml/AnnotationProxyBuilder.java index dedfabc..02383cb 100644 --- a/bval-jsr/src/main/java/org/apache/bval/jsr/xml/AnnotationProxyBuilder.java +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/xml/AnnotationProxyBuilder.java @@ -30,10 +30,12 @@ import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.HashMap; import java.util.Map; +import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import javax.enterprise.util.AnnotationLiteral; +import javax.validation.ConstraintTarget; import javax.validation.Payload; import javax.validation.Valid; import javax.validation.ValidationException; @@ -45,11 +47,21 @@ import javax.validation.groups.ConvertGroup; */ @Privilizing(@CallTo(Reflection.class)) public final class AnnotationProxyBuilder<A extends Annotation> { - private static final ConcurrentMap<Class<?>, Method[]> METHODS_CACHE = new ConcurrentHashMap<Class<?>, Method[]>(); + private static final ConcurrentMap<Class<?>, Method[]> METHODS_CACHE = new ConcurrentHashMap<>(); + + public static <A> Method[] findMethods(final Class<A> annotationType) { + // cache only built-in constraints to avoid memory leaks: + // TODO use configurable cache size property? + if (annotationType.getName().startsWith("javax.validation.constraints.")) { + return METHODS_CACHE.computeIfAbsent(annotationType, Reflection::getDeclaredMethods); + } + return Reflection.getDeclaredMethods(annotationType); + } private final Class<A> type; - private final Map<String, Object> elements = new HashMap<String, Object>(); + private final Map<String, Object> elements = new HashMap<>(); private final Method[] methods; + private boolean changed; /** * Create a new AnnotationProxyBuilder instance. @@ -61,21 +73,6 @@ public final class AnnotationProxyBuilder<A extends Annotation> { this.methods = findMethods(annotationType); } - public static <A> Method[] findMethods(final Class<A> annotationType) { - if (annotationType.getName().startsWith("javax.validation.constraints.")) { // cache built-in constraints only to avoid mem leaks - Method[] mtd = METHODS_CACHE.get(annotationType); - if (mtd == null) { - final Method[] value = Reflection.getDeclaredMethods(annotationType); - mtd = METHODS_CACHE.putIfAbsent(annotationType, value); - if (mtd == null) { - mtd = value; - } - } - return mtd; - } - return Reflection.getDeclaredMethods(annotationType); - } - /** * Create a new AnnotationProxyBuilder instance. * @@ -84,16 +81,15 @@ public final class AnnotationProxyBuilder<A extends Annotation> { */ public AnnotationProxyBuilder(Class<A> annotationType, Map<String, Object> elements) { this(annotationType); - for (Map.Entry<String, Object> entry : elements.entrySet()) { - this.elements.put(entry.getKey(), entry.getValue()); - } + elements.forEach(this.elements::put); } /** * Create a builder initially configured to create an annotation equivalent - * to <code>annot</code>. + * to {@code annot}. * - * @param annot Annotation to be replicated. + * @param annot + * Annotation to be replicated. */ @SuppressWarnings("unchecked") public AnnotationProxyBuilder(A annot) { @@ -102,8 +98,7 @@ public final class AnnotationProxyBuilder<A extends Annotation> { for (Method m : methods) { final boolean mustUnset = Reflection.setAccessible(m, true); try { - Object value = m.invoke(annot); - this.elements.put(m.getName(), value); + this.elements.put(m.getName(), m.invoke(annot)); } catch (Exception e) { throw new ValidationException("Cannot access annotation " + annot + " element: " + m.getName(), e); } finally { @@ -124,8 +119,22 @@ public final class AnnotationProxyBuilder<A extends Annotation> { * @param elementName * @param value */ - public void putValue(String elementName, Object value) { - elements.put(elementName, value); + @Deprecated + public Object putValue(String elementName, Object value) { + return elements.put(elementName, value); + } + + /** + * Add an element to the configuration. + * + * @param elementName + * @param value + * @return whether any change occurred + */ + public boolean setValue(String elementName, Object value) { + final boolean result = !Objects.equals(elements.put(elementName, value), value); + changed |= result; + return result; } /** @@ -171,27 +180,42 @@ public final class AnnotationProxyBuilder<A extends Annotation> { * Configure the well-known JSR303 "message" element. * * @param message + * @return */ - public void setMessage(String message) { - ConstraintAnnotationAttributes.MESSAGE.put(elements, message); + public boolean setMessage(String message) { + return setValue(ConstraintAnnotationAttributes.MESSAGE.getAttributeName(), message); } /** * Configure the well-known JSR303 "groups" element. * * @param groups + * @return */ - public void setGroups(Class<?>[] groups) { - ConstraintAnnotationAttributes.GROUPS.put(elements, groups); + public boolean setGroups(Class<?>[] groups) { + return setValue(ConstraintAnnotationAttributes.GROUPS.getAttributeName(), groups); } /** * Configure the well-known JSR303 "payload" element. * * @param payload + * @return + */ + public boolean setPayload(Class<? extends Payload>[] payload) { + return setValue(ConstraintAnnotationAttributes.PAYLOAD.getAttributeName(), payload); + } + + /** + * Configure the well-known "validationAppliesTo" element. + * + * @param constraintTarget */ - public void setPayload(Class<? extends Payload>[] payload) { - ConstraintAnnotationAttributes.PAYLOAD.put(elements, payload); + public boolean setValidationAppliesTo(ConstraintTarget constraintTarget) { + return setValue(ConstraintAnnotationAttributes.VALIDATION_APPLIES_TO.getAttributeName(), constraintTarget); + } + public boolean isChanged() { + return changed; } /** @@ -203,16 +227,22 @@ public final class AnnotationProxyBuilder<A extends Annotation> { final ClassLoader classLoader = Reflection.getClassLoader(getType()); @SuppressWarnings("unchecked") final Class<A> proxyClass = (Class<A>) Proxy.getProxyClass(classLoader, getType()); - final InvocationHandler handler = new AnnotationProxy(this); - return doCreateAnnotation(proxyClass, handler); + return doCreateAnnotation(proxyClass, new AnnotationProxy(this)); } @Privileged private A doCreateAnnotation(final Class<A> proxyClass, final InvocationHandler handler) { try { - Constructor<A> constructor = proxyClass.getConstructor(InvocationHandler.class); - Reflection.setAccessible(constructor, true); // java 8 - return constructor.newInstance(handler); + final Constructor<A> constructor = proxyClass.getConstructor(InvocationHandler.class); + final boolean mustUnset = Reflection.setAccessible(constructor, true); // java + // 8 + try { + return constructor.newInstance(handler); + } finally { + if (mustUnset) { + Reflection.setAccessible(constructor, false); + } + } } catch (Exception e) { throw new ValidationException("Unable to create annotation for configured constraint", e); } http://git-wip-us.apache.org/repos/asf/bval/blob/5c09f0dd/bval-jsr/src/main/java/org/apache/bval/jsr/xml/SchemaManager.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/xml/SchemaManager.java b/bval-jsr/src/main/java/org/apache/bval/jsr/xml/SchemaManager.java new file mode 100644 index 0000000..a502b7e --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/xml/SchemaManager.java @@ -0,0 +1,286 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.bval.jsr.xml; + +import java.net.URL; +import java.util.Collections; +import java.util.Comparator; +import java.util.Objects; +import java.util.Optional; +import java.util.SortedMap; +import java.util.TreeMap; +import java.util.function.Function; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.xml.XMLConstants; +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBElement; +import javax.xml.bind.UnmarshallerHandler; +import javax.xml.parsers.SAXParserFactory; +import javax.xml.validation.Schema; +import javax.xml.validation.SchemaFactory; +import javax.xml.validation.ValidatorHandler; + +import org.apache.bval.util.Lazy; +import org.apache.bval.util.reflection.Reflection; +import org.w3c.dom.Document; +import org.xml.sax.Attributes; +import org.xml.sax.ContentHandler; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; +import org.xml.sax.XMLReader; +import org.xml.sax.helpers.AttributesImpl; +import org.xml.sax.helpers.XMLFilterImpl; + +/** + * Unmarshals XML converging on latest schema version. Presumes backward compatiblity between schemae. + */ +public class SchemaManager { + public static class Builder { + private final SortedMap<Key, Lazy<Schema>> data = new TreeMap<>(); + + public Builder add(String version, String ns, String resource) { + data.put(new Key(version, ns), new Lazy<>(() -> SchemaManager.loadSchema(resource))); + return this; + } + + public SchemaManager build() { + return new SchemaManager(new TreeMap<>(data)); + } + } + + private static class Key implements Comparable<Key> { + private static final Comparator<Key> CMP = Comparator.comparing(Key::getVersion).thenComparing(Key::getNs); + + final String version; + final String ns; + + Key(String version, String ns) { + super(); + this.version = Objects.toString(version, ""); + this.ns = Objects.toString(ns, ""); + } + + public String getVersion() { + return version; + } + + public String getNs() { + return ns; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + return Optional.ofNullable(obj).filter(SchemaManager.Key.class::isInstance) + .map(SchemaManager.Key.class::cast) + .filter(k -> Objects.equals(this.version, k.version) && Objects.equals(this.ns, k.ns)).isPresent(); + } + + @Override + public int hashCode() { + return Objects.hash(version, ns); + } + + @Override + public String toString() { + return String.format("%s:%s", version, ns); + } + + @Override + public int compareTo(Key o) { + return CMP.compare(this, o); + } + } + + private class DynamicValidatorHandler extends XMLFilterImpl { + ContentHandler ch; + SAXParseException e; + + @Override + public void setContentHandler(ContentHandler handler) { + super.setContentHandler(handler); + this.ch = handler; + } + + @Override + public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException { + if (getContentHandler() == ch) { + final Key schemaKey = new Key(Objects.toString(atts.getValue("version"), ""), uri); + if (data.containsKey(schemaKey)) { + final Schema schema = data.get(schemaKey).get(); + final ValidatorHandler vh = schema.newValidatorHandler(); + vh.startDocument(); + vh.setContentHandler(ch); + super.setContentHandler(vh); + } + } + try { + super.startElement(uri, localName, qName, atts); + } catch (SAXParseException e) { + this.e = e; + } + } + + @Override + public void error(SAXParseException e) throws SAXException { + this.e = e; + super.error(e); + } + + @Override + public void fatalError(SAXParseException e) throws SAXException { + this.e = e; + super.fatalError(e); + } + + void validate() throws SAXParseException { + if (e != null) { + throw e; + } + } + } + + //@formatter:off + private enum XmlAttributeType { + CDATA, ID, IDREF, IDREFS, NMTOKEN, NMTOKENS, ENTITY, ENTITIES, NOTATION; + //@formatter:on + } + + private class SchemaRewriter extends XMLFilterImpl { + private boolean root = true; + + @Override + public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException { + final Key schemaKey = new Key(Objects.toString(atts.getValue("version"), ""), uri); + + if (!target.equals(schemaKey) && data.containsKey(schemaKey)) { + uri = target.ns; + if (root) { + atts = rewrite(atts); + root = false; + } + } + super.startElement(uri, localName, qName, atts); + } + + private Attributes rewrite(Attributes atts) { + final AttributesImpl result; + if (atts instanceof AttributesImpl) { + result = (AttributesImpl) atts; + } else { + result = new AttributesImpl(atts); + } + set(result, "", VERSION_ATTRIBUTE, "", XmlAttributeType.CDATA, target.version); + return result; + } + + private void set(AttributesImpl attrs, String uri, String localName, String qName, XmlAttributeType type, + String value) { + for (int i = 0, sz = attrs.getLength(); i < sz; i++) { + if (Objects.equals(qName, attrs.getQName(i)) + || Objects.equals(uri, attrs.getURI(i)) && Objects.equals(localName, attrs.getLocalName(i))) { + attrs.setAttribute(i, uri, localName, qName, type.name(), value); + return; + } + } + attrs.addAttribute(uri, localName, qName, type.name(), value); + } + } + + public static final String VERSION_ATTRIBUTE = "version"; + + private static final Logger log = Logger.getLogger(SchemaManager.class.getName()); + private static final SchemaFactory SCHEMA_FACTORY = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); + private static final SAXParserFactory SAX_PARSER_FACTORY; + + static { + SAX_PARSER_FACTORY = SAXParserFactory.newInstance(); + SAX_PARSER_FACTORY.setNamespaceAware(true); + } + + static Schema loadSchema(String resource) { + final URL schemaUrl = Reflection.getClassLoader(XmlUtils.class).getResource(resource); + try { + return SCHEMA_FACTORY.newSchema(schemaUrl); + } catch (SAXException e) { + log.log(Level.WARNING, String.format("Unable to parse schema: %s", resource), e); + return null; + } + } + + private static Class<?> getObjectFactory(Class<?> type) throws ClassNotFoundException { + final String className = String.format("%s.%s", type.getPackage().getName(), "ObjectFactory"); + return Reflection.toClass(className, type.getClassLoader()); + } + + private final Key target; + private final SortedMap<Key, Lazy<Schema>> data; + private final String description; + + private SchemaManager(SortedMap<Key, Lazy<Schema>> data) { + super(); + this.data = Collections.unmodifiableSortedMap(data); + this.target = data.lastKey(); + this.description = target.ns.substring(target.ns.lastIndexOf('/') + 1); + } + + public Optional<Schema> getSchema(String ns, String version) { + return Optional.of(new Key(version, ns)).map(data::get).map(Lazy::get); + } + + public Optional<Schema> getSchema(Document document) { + return Optional.ofNullable(document).map(Document::getDocumentElement) + .map(e -> getSchema(e.getAttribute(XMLConstants.XMLNS_ATTRIBUTE), e.getAttribute(VERSION_ATTRIBUTE))).get(); + } + + public <E extends Exception> Schema requireSchema(Document document, Function<String, E> exc) throws E { + return getSchema(document).orElseThrow(() -> Objects.requireNonNull(exc, "exc") + .apply(String.format("Unknown %s schema", Objects.toString(description, "")))); + } + + public <T> T unmarshal(InputSource input, Class<T> type) throws Exception { + final XMLReader xmlReader = SAX_PARSER_FACTORY.newSAXParser().getXMLReader(); + + // validate specified schema: + final DynamicValidatorHandler schemaValidator = new DynamicValidatorHandler(); + xmlReader.setContentHandler(schemaValidator); + + // rewrite to latest schema, if required: + final SchemaRewriter schemaRewriter = new SchemaRewriter(); + schemaValidator.setContentHandler(schemaRewriter); + + JAXBContext jc = JAXBContext.newInstance(getObjectFactory(type)); + // unmarshal: + final UnmarshallerHandler unmarshallerHandler = jc.createUnmarshaller().getUnmarshallerHandler(); + schemaRewriter.setContentHandler(unmarshallerHandler); + + xmlReader.parse(input); + + schemaValidator.validate(); + + @SuppressWarnings("unchecked") + final JAXBElement<T> result = (JAXBElement<T>) unmarshallerHandler.getResult(); + return result.getValue(); + } +} http://git-wip-us.apache.org/repos/asf/bval/blob/5c09f0dd/bval-jsr/src/main/java/org/apache/bval/jsr/xml/ValidationMappingParser.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/xml/ValidationMappingParser.java b/bval-jsr/src/main/java/org/apache/bval/jsr/xml/ValidationMappingParser.java index b260a9e..3a86142 100644 --- a/bval-jsr/src/main/java/org/apache/bval/jsr/xml/ValidationMappingParser.java +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/xml/ValidationMappingParser.java @@ -18,730 +18,100 @@ package org.apache.bval.jsr.xml; import java.io.IOException; import java.io.InputStream; -import java.io.Serializable; import java.lang.annotation.Annotation; -import java.lang.reflect.Array; -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; -import java.lang.reflect.Member; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.EnumSet; -import java.util.HashSet; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Set; -import javax.validation.Constraint; -import javax.validation.ConstraintValidator; -import javax.validation.Payload; import javax.validation.ValidationException; -import javax.xml.bind.JAXBContext; -import javax.xml.bind.JAXBElement; -import javax.xml.bind.JAXBException; -import javax.xml.bind.Unmarshaller; -import javax.xml.transform.stream.StreamSource; -import javax.xml.validation.Schema; import org.apache.bval.jsr.ApacheValidatorFactory; -import org.apache.bval.jsr.ConstraintAnnotationAttributes; -import org.apache.bval.jsr.util.IOs; -import org.apache.bval.util.FieldAccess; -import org.apache.bval.util.MethodAccess; -import org.apache.bval.util.ObjectUtils; -import org.apache.bval.util.StringUtils; +import org.apache.bval.jsr.metadata.XmlBuilder; +import org.apache.bval.jsr.metadata.XmlValidationMappingProvider; +import org.apache.bval.util.Exceptions; +import org.apache.bval.util.Validate; import org.apache.bval.util.reflection.Reflection; -import org.apache.commons.weaver.privilizer.Privileged; import org.apache.commons.weaver.privilizer.Privilizing; import org.apache.commons.weaver.privilizer.Privilizing.CallTo; +import org.xml.sax.InputSource; /** - * Uses JAXB to parse constraints.xml based on validation-mapping-1.0.xsd.<br> + * Uses JAXB to parse constraints.xml based on the validation-mapping XML + * schema. */ @Privilizing(@CallTo(Reflection.class)) public class ValidationMappingParser { - private static final String VALIDATION_MAPPING_XSD = "META-INF/validation-mapping-1.1.xsd"; + private static final SchemaManager SCHEMA_MANAGER = new SchemaManager.Builder() + .add(null, "http://jboss.org/xml/ns/javax/validation/mapping", "META-INF/validation-mapping-1.0.xsd") + .add(XmlBuilder.Version.v11.getId(), "http://jboss.org/xml/ns/javax/validation/mapping", + "META-INF/validation-mapping-1.1.xsd") + .add(XmlBuilder.Version.v20.getId(), "http://xmlns.jcp.org/xml/ns/javax/validation/mapping", + "META-INF/validation-mapping-2.0.xsd") + .build(); - private static final Set<ConstraintAnnotationAttributes> RESERVED_PARAMS = Collections - .unmodifiableSet(EnumSet.of(ConstraintAnnotationAttributes.GROUPS, ConstraintAnnotationAttributes.MESSAGE, - ConstraintAnnotationAttributes.PAYLOAD, ConstraintAnnotationAttributes.VALIDATION_APPLIES_TO)); - - private final Set<Class<?>> processedClasses; private final ApacheValidatorFactory factory; public ValidationMappingParser(ApacheValidatorFactory factory) { - this.factory = factory; - this.processedClasses = new HashSet<Class<?>>(); + this.factory = Validate.notNull(factory, "factory"); } /** - * Parse files with constraint mappings and collect information in the factory. - * - * @param xmlStreams - one or more contraints.xml file streams to parse + * Parse files with constraint mappings and collect information in the + * factory. + * + * @param xmlStreams + * - one or more contraints.xml file streams to parse */ public void processMappingConfig(Set<InputStream> xmlStreams) throws ValidationException { for (final InputStream xmlStream : xmlStreams) { - ConstraintMappingsType mapping = parseXmlMappings(xmlStream); - - final String defaultPackage = mapping.getDefaultPackage(); - processConstraintDefinitions(mapping.getConstraintDefinition(), defaultPackage); - for (final BeanType bean : mapping.getBean()) { - Class<?> beanClass = loadClass(bean.getClazz(), defaultPackage); - if (!processedClasses.add(beanClass)) { - // spec: A given class must not be described more than once amongst all - // the XML mapping descriptors. - throw new ValidationException(beanClass.getName() + " has already be configured in xml."); - } - - boolean ignoreAnnotations = bean.getIgnoreAnnotations() == null ? true : bean.getIgnoreAnnotations(); - factory.getAnnotationIgnores().setDefaultIgnoreAnnotation(beanClass, ignoreAnnotations); - processClassLevel(bean.getClassType(), beanClass, defaultPackage); - processConstructorLevel(bean.getConstructor(), beanClass, defaultPackage, ignoreAnnotations); - processFieldLevel(bean.getField(), beanClass, defaultPackage, ignoreAnnotations); - final Collection<String> potentialMethodName = - processPropertyLevel(bean.getGetter(), beanClass, defaultPackage, ignoreAnnotations); - processMethodLevel(bean.getMethod(), beanClass, defaultPackage, ignoreAnnotations, potentialMethodName); - processedClasses.add(beanClass); - } + final ConstraintMappingsType mapping = parseXmlMappings(xmlStream); + processConstraintDefinitions(mapping.getConstraintDefinition(), mapping.getDefaultPackage()); + new XmlBuilder(mapping).forBeans().forEach(factory.getMetadataBuilders()::registerCustomBuilder); } } - /** @param in XML stream to parse using the validation-mapping-1.0.xsd */ + /** + * @param in + * XML stream to parse using the validation-mapping-1.0.xsd + */ private ConstraintMappingsType parseXmlMappings(final InputStream in) { - ConstraintMappingsType mappings; try { - final JAXBContext jc = JAXBContext.newInstance(ConstraintMappingsType.class); - final Unmarshaller unmarshaller = jc.createUnmarshaller(); - unmarshaller.setSchema(getSchema()); - final StreamSource stream = new StreamSource(in); - final JAXBElement<ConstraintMappingsType> root = - unmarshaller.unmarshal(stream, ConstraintMappingsType.class); - mappings = root.getValue(); - } catch (final JAXBException e) { + return SCHEMA_MANAGER.unmarshal(new InputSource(in), ConstraintMappingsType.class); + } catch (Exception e) { throw new ValidationException("Failed to parse XML deployment descriptor file.", e); } finally { - IOs.closeQuietly(in); try { - in.reset(); // can be read several times + we ensured it was re-readable in addMapping() + in.reset(); // can be read several times + we ensured it was + // re-readable in addMapping() } catch (final IOException e) { // no-op } } - return mappings; - } - - /** @return validation-mapping-1.0.xsd based schema */ - private Schema getSchema() { - return ValidationParser.getSchema(VALIDATION_MAPPING_XSD); - } - - private void processClassLevel(ClassType classType, Class<?> beanClass, String defaultPackage) { - if (classType == null) { - return; - } - - // ignore annotation - if (classType.getIgnoreAnnotations() != null) { - factory.getAnnotationIgnores().setIgnoreAnnotationsOnClass(beanClass, classType.getIgnoreAnnotations()); - } - - // group sequence - Class<?>[] groupSequence = createGroupSequence(classType.getGroupSequence(), defaultPackage); - if (groupSequence != null) { - factory.addDefaultSequence(beanClass, groupSequence); - } - - // constraints - for (ConstraintType constraint : classType.getConstraint()) { - MetaConstraint<?, ?> metaConstraint = createConstraint(constraint, beanClass, null, defaultPackage); - factory.addMetaConstraint(beanClass, metaConstraint); - } - } - - @SuppressWarnings("unchecked") - private <A extends Annotation, T> MetaConstraint<?, ?> createConstraint(final ConstraintType constraint, - final Class<T> beanClass, final Member member, final String defaultPackage) { - - final Class<A> annotationClass = (Class<A>) loadClass(constraint.getAnnotation(), defaultPackage); - final AnnotationProxyBuilder<A> annoBuilder = new AnnotationProxyBuilder<A>(annotationClass); - - if (constraint.getMessage() != null) { - annoBuilder.setMessage(constraint.getMessage()); - } - annoBuilder.setGroups(getGroups(constraint.getGroups(), defaultPackage)); - annoBuilder.setPayload(getPayload(constraint.getPayload(), defaultPackage)); - - 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, defaultPackage); - annoBuilder.putValue(name, elementValue); - } - return new MetaConstraint<T, A>(beanClass, member, annoBuilder.createAnnotation()); - } - - private void checkValidName(String name) { - for (ConstraintAnnotationAttributes attr : RESERVED_PARAMS) { - if (attr.getAttributeName().equals(name)) { - throw new ValidationException(name + " is a reserved parameter name."); - } - } - } - - private <A extends Annotation> Class<?> getAnnotationParameterType(final Class<A> annotationClass, - final String name) { - final Method m = Reflection.getPublicMethod(annotationClass, name); - if (m == null) { - throw new ValidationException( - "Annotation of type " + annotationClass.getName() + " does not contain a parameter " + name + "."); - } - return m.getReturnType(); - } - - private Object getElementValue(ElementType elementType, Class<?> returnType, String defaultPackage) { - removeEmptyContentElements(elementType); - - boolean isArray = returnType.isArray(); - if (!isArray) { - if (elementType.getContent().size() != 1) { - throw new ValidationException("Attempt to specify an array where single value is expected."); - } - return getSingleValue(elementType.getContent().get(0), returnType, defaultPackage); - } - List<Object> values = new ArrayList<Object>(); - for (Serializable s : elementType.getContent()) { - values.add(getSingleValue(s, returnType.getComponentType(), defaultPackage)); - } - return values.toArray((Object[]) Array.newInstance(returnType.getComponentType(), values.size())); - } - - private void removeEmptyContentElements(ElementType elementType) { - List<Serializable> contentToDelete = new ArrayList<Serializable>(); - for (Serializable content : elementType.getContent()) { - if (content instanceof String && ((String) content).matches("[\\n ].*")) { - contentToDelete.add(content); - } - } - elementType.getContent().removeAll(contentToDelete); - } - - @SuppressWarnings("unchecked") - private Object getSingleValue(Serializable serializable, Class<?> returnType, String defaultPackage) { - if (serializable instanceof String) { - String value = (String) serializable; - return convertToResultType(returnType, value, defaultPackage); - } - if (serializable instanceof JAXBElement<?>) { - JAXBElement<?> elem = (JAXBElement<?>) serializable; - if (String.class.equals(elem.getDeclaredType())) { - String value = (String) elem.getValue(); - return convertToResultType(returnType, value, defaultPackage); - } - if (AnnotationType.class.equals(elem.getDeclaredType())) { - AnnotationType annotationType = (AnnotationType) elem.getValue(); - try { - Class<? extends Annotation> annotationClass = (Class<? extends Annotation>) returnType; - return createAnnotation(annotationType, annotationClass, defaultPackage); - } catch (ClassCastException e) { - throw new ValidationException("Unexpected parameter value"); - } - } - } - throw new ValidationException("Unexpected parameter value"); } - private Object convertToResultType(Class<?> returnType, String value, String defaultPackage) { - /** - * 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 (returnType.equals(String.class)) { - return value; - } - if (returnType.equals(Class.class)) { - ClassLoader cl = Reflection.getClassLoader(ValidationMappingParser.class); - try { - return Reflection.toClass(toQualifiedClassName(value, defaultPackage), cl); - } catch (Exception e) { - throw new ValidationException(e); - } - } - if (returnType.isEnum()) { - try { - @SuppressWarnings({ "rawtypes", "unchecked" }) - final Enum e = Enum.valueOf(returnType.asSubclass(Enum.class), value); - return e; - } catch (IllegalArgumentException e) { - throw new ValidationException(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)) { - if (value.length() > 1) { - throw new IllegalArgumentException("a char has a length of 1"); - } - return value.charAt(0); - } - throw new ValidationException(String.format("Unknown annotation value type %s", returnType.getName())); - } - - private <A extends Annotation> Annotation createAnnotation(AnnotationType annotationType, Class<A> returnType, + private void processConstraintDefinitions(List<ConstraintDefinitionType> constraintDefinitionList, String defaultPackage) { - AnnotationProxyBuilder<A> metaAnnotation = new AnnotationProxyBuilder<A>(returnType); - for (ElementType elementType : annotationType.getElement()) { - String name = elementType.getName(); - Class<?> parameterType = getAnnotationParameterType(returnType, name); - Object elementValue = getElementValue(elementType, parameterType, defaultPackage); - metaAnnotation.putValue(name, elementValue); - } - return metaAnnotation.createAnnotation(); - } - private Class<?>[] getGroups(GroupsType groupsType, String defaultPackage) { - if (groupsType == null) { - return ObjectUtils.EMPTY_CLASS_ARRAY; - } + final Map<Class<? extends Annotation>, ValidatedByType> validatorMappings = new HashMap<>(); - List<Class<?>> groupList = new ArrayList<Class<?>>(); - for (String groupClass : groupsType.getValue()) { - groupList.add(loadClass(groupClass, defaultPackage)); - } - return groupList.toArray(new Class[groupList.size()]); - } - - @SuppressWarnings("unchecked") - private Class<? extends Payload>[] getPayload(PayloadType payloadType, String defaultPackage) { - if (payloadType == null) { - return new Class[] {}; - } - - List<Class<? extends Payload>> payloadList = new ArrayList<Class<? extends Payload>>(); - for (String groupClass : payloadType.getValue()) { - Class<?> payload = loadClass(groupClass, defaultPackage); - if (!Payload.class.isAssignableFrom(payload)) { - throw new ValidationException( - "Specified payload class " + payload.getName() + " does not implement javax.validation.Payload"); - } - payloadList.add((Class<? extends Payload>) payload); - } - return payloadList.toArray(new Class[payloadList.size()]); - } - - private Class<?>[] createGroupSequence(GroupSequenceType groupSequenceType, String defaultPackage) { - if (groupSequenceType != null) { - Class<?>[] groupSequence = new Class<?>[groupSequenceType.getValue().size()]; - int i = 0; - for (String groupName : groupSequenceType.getValue()) { - Class<?> group = loadClass(groupName, defaultPackage); - groupSequence[i++] = group; - } - return groupSequence; - } - return null; - } - - private <A> void processMethodLevel(final List<MethodType> methods, final Class<A> beanClass, - final String defaultPackage, final boolean parentIgnoreAnn, final Collection<String> getters) { - final List<String> methodNames = new ArrayList<String>(); - for (final MethodType methodType : methods) { - final String methodName = methodType.getName(); - if (methodNames.contains(methodName) || getters.contains(methodName)) { - throw new ValidationException( - methodName + " is defined more than once in mapping xml for bean " + beanClass.getName()); - } - methodNames.add(methodName); - - final Method method = - Reflection.getDeclaredMethod(beanClass, methodName, toTypes(methodType.getParameter(), defaultPackage)); - if (method == null) { - throw new ValidationException(beanClass.getName() + " does not contain the method " + methodName); - } - - // ignore annotations - final boolean ignoreMethodAnnotation = - methodType.getIgnoreAnnotations() == null ? parentIgnoreAnn : methodType.getIgnoreAnnotations(); - factory.getAnnotationIgnores().setIgnoreAnnotationsOnMember(method, ignoreMethodAnnotation); - - final boolean ignoreAnn; - if (methodType.getIgnoreAnnotations() == null) { - ignoreAnn = parentIgnoreAnn; - } else { - ignoreAnn = methodType.getIgnoreAnnotations(); - } - - // constraints - int i = 0; - for (final ParameterType p : methodType.getParameter()) { - for (final ConstraintType constraintType : p.getConstraint()) { - final MetaConstraint<?, ?> constraint = - createConstraint(constraintType, beanClass, method, defaultPackage); - constraint.setIndex(i); - factory.addMetaConstraint(beanClass, constraint); - } - if (p.getValid() != null) { - final MetaConstraint<?, ?> constraint = new MetaConstraint<A, Annotation>(beanClass, method, - AnnotationProxyBuilder.ValidAnnotation.INSTANCE); - constraint.setIndex(i); - factory.addMetaConstraint(beanClass, constraint); - } - - if (p.getConvertGroup() != null) { - for (final GroupConversionType groupConversion : p.getConvertGroup()) { - final Class<?> from = loadClass(groupConversion.getFrom(), defaultPackage); - final Class<?> to = loadClass(groupConversion.getTo(), defaultPackage); - final MetaConstraint<?, ?> constraint = new MetaConstraint<A, Annotation>(beanClass, method, - new AnnotationProxyBuilder.ConvertGroupAnnotation(from, to)); - constraint.setIndex(i); - factory.addMetaConstraint(beanClass, constraint); - } - } - - boolean ignoreParametersAnnotation = - p.getIgnoreAnnotations() == null ? ignoreMethodAnnotation : p.getIgnoreAnnotations(); - factory.getAnnotationIgnores().setIgnoreAnnotationsOnParameter(method, i, ignoreParametersAnnotation); - - i++; - } - - final ReturnValueType returnValue = methodType.getReturnValue(); - if (returnValue != null) { - for (final ConstraintType constraintType : returnValue.getConstraint()) { - final MetaConstraint<?, ?> constraint = - createConstraint(constraintType, beanClass, method, defaultPackage); - factory.addMetaConstraint(beanClass, constraint); - } - if (returnValue.getValid() != null) { - final MetaConstraint<?, ?> constraint = new MetaConstraint<A, Annotation>(beanClass, method, - AnnotationProxyBuilder.ValidAnnotation.INSTANCE); - factory.addMetaConstraint(beanClass, constraint); - } - - if (returnValue.getConvertGroup() != null) { - for (final GroupConversionType groupConversion : returnValue.getConvertGroup()) { - final Class<?> from = loadClass(groupConversion.getFrom(), defaultPackage); - final Class<?> to = loadClass(groupConversion.getTo(), defaultPackage); - final MetaConstraint<?, ?> constraint = new MetaConstraint<A, Annotation>(beanClass, method, - new AnnotationProxyBuilder.ConvertGroupAnnotation(from, to)); - factory.addMetaConstraint(beanClass, constraint); - } - } - factory.getAnnotationIgnores().setIgnoreAnnotationOnReturn(method, - returnValue.getIgnoreAnnotations() == null ? ignoreAnn : returnValue.getIgnoreAnnotations()); - } - - final CrossParameterType crossParameter = methodType.getCrossParameter(); - if (crossParameter != null) { - for (final ConstraintType constraintType : crossParameter.getConstraint()) { - final MetaConstraint<?, ?> constraint = - createConstraint(constraintType, beanClass, method, defaultPackage); - factory.addMetaConstraint(beanClass, constraint); - } - factory.getAnnotationIgnores().setIgnoreAnnotationOnCrossParameter(method, - crossParameter.getIgnoreAnnotations() != null ? crossParameter.getIgnoreAnnotations() : ignoreAnn); - } - } - } - - private <A> void processConstructorLevel(final List<ConstructorType> constructors, final Class<A> beanClass, - final String defaultPackage, final boolean parentIgnore) { - for (final ConstructorType constructorType : constructors) { - final Constructor<?> constructor = - Reflection.getDeclaredConstructor(beanClass, toTypes(constructorType.getParameter(), defaultPackage)); - if (constructor == null) { - throw new ValidationException( - beanClass.getName() + " does not contain the constructor " + constructorType); - } - - // ignore annotations - final boolean ignoreMethodAnnotation = - constructorType.getIgnoreAnnotations() == null ? parentIgnore : constructorType.getIgnoreAnnotations(); - factory.getAnnotationIgnores().setIgnoreAnnotationsOnMember(constructor, ignoreMethodAnnotation); - - final boolean ignoreAnn; - if (constructorType.getIgnoreAnnotations() == null) { - ignoreAnn = parentIgnore; - } else { - ignoreAnn = constructorType.getIgnoreAnnotations(); - } - - // constraints - int i = 0; - for (final ParameterType p : constructorType.getParameter()) { - for (final ConstraintType constraintType : p.getConstraint()) { - final MetaConstraint<?, ?> constraint = - createConstraint(constraintType, beanClass, constructor, defaultPackage); - constraint.setIndex(i); - factory.addMetaConstraint(beanClass, constraint); - } - if (p.getValid() != null) { - final MetaConstraint<?, ?> constraint = new MetaConstraint<A, Annotation>(beanClass, constructor, - AnnotationProxyBuilder.ValidAnnotation.INSTANCE); - constraint.setIndex(i); - factory.addMetaConstraint(beanClass, constraint); - } - - if (p.getConvertGroup() != null) { - for (final GroupConversionType groupConversion : p.getConvertGroup()) { - final Class<?> from = loadClass(groupConversion.getFrom(), defaultPackage); - final Class<?> to = loadClass(groupConversion.getTo(), defaultPackage); - final MetaConstraint<?, ?> constraint = new MetaConstraint<A, Annotation>(beanClass, - constructor, new AnnotationProxyBuilder.ConvertGroupAnnotation(from, to)); - constraint.setIndex(i); - factory.addMetaConstraint(beanClass, constraint); - } - } - - boolean ignoreParametersAnnotation = - p.getIgnoreAnnotations() == null ? ignoreMethodAnnotation : p.getIgnoreAnnotations(); - if (ignoreParametersAnnotation || (ignoreMethodAnnotation && p.getIgnoreAnnotations() == null)) { - // TODO what ? - } - factory.getAnnotationIgnores().setIgnoreAnnotationsOnParameter(constructor, i, - p.getIgnoreAnnotations() != null ? p.getIgnoreAnnotations() : ignoreAnn); - - i++; - } - - final ReturnValueType returnValue = constructorType.getReturnValue(); - if (returnValue != null) { - for (final ConstraintType constraintType : returnValue.getConstraint()) { - final MetaConstraint<?, ?> constraint = - createConstraint(constraintType, beanClass, constructor, defaultPackage); - constraint.setIndex(-1); - factory.addMetaConstraint(beanClass, constraint); - } - if (returnValue.getValid() != null) { - final MetaConstraint<?, ?> constraint = new MetaConstraint<A, Annotation>(beanClass, constructor, - AnnotationProxyBuilder.ValidAnnotation.INSTANCE); - constraint.setIndex(-1); - factory.addMetaConstraint(beanClass, constraint); - } - - if (returnValue.getConvertGroup() != null) { - for (final GroupConversionType groupConversion : returnValue.getConvertGroup()) { - final Class<?> from = loadClass(groupConversion.getFrom(), defaultPackage); - final Class<?> to = loadClass(groupConversion.getTo(), defaultPackage); - final MetaConstraint<?, ?> constraint = new MetaConstraint<A, Annotation>(beanClass, - constructor, new AnnotationProxyBuilder.ConvertGroupAnnotation(from, to)); - constraint.setIndex(-1); - factory.addMetaConstraint(beanClass, constraint); - } - } - factory.getAnnotationIgnores().setIgnoreAnnotationOnReturn(constructor, - returnValue.getIgnoreAnnotations() != null ? returnValue.getIgnoreAnnotations() : ignoreAnn); - } - - final CrossParameterType crossParameter = constructorType.getCrossParameter(); - if (crossParameter != null) { - for (final ConstraintType constraintType : crossParameter.getConstraint()) { - final MetaConstraint<?, ?> constraint = - createConstraint(constraintType, beanClass, constructor, defaultPackage); - factory.addMetaConstraint(beanClass, constraint); - } - factory.getAnnotationIgnores().setIgnoreAnnotationOnCrossParameter(constructor, - crossParameter.getIgnoreAnnotations() != null ? crossParameter.getIgnoreAnnotations() : ignoreAnn); - } - } - } - - private Class<?>[] toTypes(final List<ParameterType> parameter, final String defaultPck) { - if (parameter == null) { - return null; - } - final Class<?>[] types = new Class<?>[parameter.size()]; - int i = 0; - for (final ParameterType type : parameter) { - types[i++] = loadClass(type.getType(), defaultPck); - } - return types; - } - - private <A> void processFieldLevel(List<FieldType> fields, Class<A> beanClass, String defaultPackage, - boolean ignoreAnnotations) { - final List<String> fieldNames = new ArrayList<String>(); - for (FieldType fieldType : fields) { - String fieldName = fieldType.getName(); - if (fieldNames.contains(fieldName)) { - throw new ValidationException( - fieldName + " is defined more than once in mapping xml for bean " + beanClass.getName()); - } - fieldNames.add(fieldName); - - final Field field = Reflection.getDeclaredField(beanClass, fieldName); - if (field == null) { - throw new ValidationException(beanClass.getName() + " does not contain the fieldType " + fieldName); - } - - // ignore annotations - final boolean ignoreFieldAnnotation = - fieldType.getIgnoreAnnotations() == null ? ignoreAnnotations : fieldType.getIgnoreAnnotations(); - factory.getAnnotationIgnores().setIgnoreAnnotationsOnMember(field, ignoreFieldAnnotation); - - // valid - if (fieldType.getValid() != null) { - factory.addValid(beanClass, new FieldAccess(field)); - } - - for (final GroupConversionType conversion : fieldType.getConvertGroup()) { - final Class<?> from = loadClass(conversion.getFrom(), defaultPackage); - final Class<?> to = loadClass(conversion.getTo(), defaultPackage); - final MetaConstraint<?, ?> constraint = new MetaConstraint<A, Annotation>(beanClass, field, - new AnnotationProxyBuilder.ConvertGroupAnnotation(from, to)); - factory.addMetaConstraint(beanClass, constraint); - } - - // constraints - for (ConstraintType constraintType : fieldType.getConstraint()) { - MetaConstraint<?, ?> constraint = createConstraint(constraintType, beanClass, field, defaultPackage); - factory.addMetaConstraint(beanClass, constraint); - } - } - } - - private <A> Collection<String> processPropertyLevel(List<GetterType> getters, Class<A> beanClass, - String defaultPackage, boolean ignoreAnnotatino) { - List<String> getterNames = new ArrayList<String>(); - for (GetterType getterType : getters) { - final String getterName = getterType.getName(); - final String methodName = "get" + StringUtils.capitalize(getterType.getName()); - if (getterNames.contains(methodName)) { - throw new ValidationException( - getterName + " is defined more than once in mapping xml for bean " + beanClass.getName()); - } - getterNames.add(methodName); - - final Method method = getGetter(beanClass, getterName); - if (method == null) { - throw new ValidationException(beanClass.getName() + " does not contain the property " + getterName); - } - - // ignore annotations - final boolean ignoreGetterAnnotation = - getterType.getIgnoreAnnotations() == null ? ignoreAnnotatino : getterType.getIgnoreAnnotations(); - factory.getAnnotationIgnores().setIgnoreAnnotationsOnMember(method, ignoreGetterAnnotation); - - // valid - if (getterType.getValid() != null) { - factory.addValid(beanClass, new MethodAccess(getterName, method)); - } - - // ConvertGroup - for (final GroupConversionType conversion : getterType.getConvertGroup()) { - final Class<?> from = loadClass(conversion.getFrom(), defaultPackage); - final Class<?> to = loadClass(conversion.getTo(), defaultPackage); - final MetaConstraint<?, ?> constraint = new MetaConstraint<A, Annotation>(beanClass, method, - new AnnotationProxyBuilder.ConvertGroupAnnotation(from, to)); - factory.addMetaConstraint(beanClass, constraint); - } - - // constraints - for (ConstraintType constraintType : getterType.getConstraint()) { - MetaConstraint<?, ?> metaConstraint = - createConstraint(constraintType, beanClass, method, defaultPackage); - factory.addMetaConstraint(beanClass, metaConstraint); - } - } - - return getterNames; - } - - @SuppressWarnings("unchecked") - private void processConstraintDefinitions(List<ConstraintDefinitionType> constraintDefinitionList, - String defaultPackage) { for (ConstraintDefinitionType constraintDefinition : constraintDefinitionList) { - String annotationClassName = constraintDefinition.getAnnotation(); + final String annotationClassName = constraintDefinition.getAnnotation(); - Class<?> clazz = loadClass(annotationClassName, defaultPackage); - if (!clazz.isAnnotation()) { - throw new ValidationException(annotationClassName + " is not an annotation"); - } - Class<? extends Annotation> annotationClass = (Class<? extends Annotation>) clazz; + final Class<?> clazz = loadClass(annotationClassName, defaultPackage); - ValidatedByType validatedByType = constraintDefinition.getValidatedBy(); - List<Class<? extends ConstraintValidator<?, ?>>> classes = - new ArrayList<Class<? extends ConstraintValidator<?, ?>>>(); - /* - If include-existing-validator is set to false, - ConstraintValidator defined on the constraint annotation are ignored. - */ - if (validatedByType.getIncludeExistingValidators() != null - && validatedByType.getIncludeExistingValidators()) { - /* - If set to true, the list of ConstraintValidators described in XML - are concatenated to the list of ConstraintValidator described on the - annotation to form a new array of ConstraintValidator evaluated. - */ - classes.addAll(findConstraintValidatorClasses(annotationClass)); - } - for (String validatorClassName : validatedByType.getValue()) { - Class<? extends ConstraintValidator<?, ?>> validatorClass; - validatorClass = (Class<? extends ConstraintValidator<?, ?>>) loadClass(validatorClassName); + Exceptions.raiseUnless(clazz.isAnnotation(), ValidationException::new, "%s is not an annotation", + annotationClassName); - if (!ConstraintValidator.class.isAssignableFrom(validatorClass)) { - throw new ValidationException(validatorClass + " is not a constraint validator class"); - } + final Class<? extends Annotation> annotationClass = clazz.asSubclass(Annotation.class); - /* - Annotation based ConstraintValidator come before XML based - ConstraintValidator in the array. The new list is returned - by ConstraintDescriptor.getConstraintValidatorClasses(). - */ - if (!classes.contains(validatorClass)) - classes.add(validatorClass); - } - if (factory.getConstraintsCache().containsConstraintValidator(annotationClass)) { - throw new ValidationException( - "Constraint validator for " + annotationClass.getName() + " already configured."); - } else { - factory.getConstraintsCache().putConstraintValidator(annotationClass, - classes.toArray(new Class[classes.size()])); - } - } - } + Exceptions.raiseIf(validatorMappings.containsKey(annotationClass), ValidationException::new, + "Constraint validator for %s already configured.", annotationClass); - private List<Class<? extends ConstraintValidator<? extends Annotation, ?>>> findConstraintValidatorClasses( - Class<? extends Annotation> annotationType) { - List<Class<? extends ConstraintValidator<? extends Annotation, ?>>> classes = - new ArrayList<Class<? extends ConstraintValidator<? extends Annotation, ?>>>(); - - Class<? extends ConstraintValidator<?, ?>>[] validator = - factory.getDefaultConstraints().getValidatorClasses(annotationType); - if (validator == null) { - /* Collections.addAll() would be more straightforward here, but there is an Oracle compiler bug of some sort - * that precludes this: - */ - Class<? extends ConstraintValidator<?, ?>>[] validatedBy = - annotationType.getAnnotation(Constraint.class).validatedBy(); - classes.addAll(Arrays.asList(validatedBy)); - } else { - Collections.addAll(classes, validator); + validatorMappings.put(annotationClass, constraintDefinition.getValidatedBy()); } - return classes; + factory.getConstraintsCache() + .add(new XmlValidationMappingProvider(validatorMappings, cn -> toQualifiedClassName(cn, defaultPackage))); } private Class<?> loadClass(String className, String defaultPackage) { @@ -751,42 +121,27 @@ public class ValidationMappingParser { private String toQualifiedClassName(String className, String defaultPackage) { if (!isQualifiedClass(className)) { if (className.startsWith("[L") && className.endsWith(";")) { - className = "[L" + defaultPackage + "." + className.substring(2); + className = "[L" + defaultPackage + '.' + className.substring(2); } else { - className = defaultPackage + "." + className; + className = defaultPackage + '.' + className; } } return className; } private boolean isQualifiedClass(String clazz) { - return clazz.contains("."); - } - - @Privileged - private static Method getGetter(Class<?> clazz, String propertyName) { - try { - final String p = StringUtils.capitalize(propertyName); - try { - return clazz.getMethod("get" + p); - } catch (NoSuchMethodException e) { - return clazz.getMethod("is" + p); - } - } catch (NoSuchMethodException e) { - return null; - } + return clazz.indexOf('.') >= 0; } private Class<?> loadClass(final String className) { ClassLoader loader = Reflection.getClassLoader(ValidationMappingParser.class); - if (loader == null) + if (loader == null) { loader = getClass().getClassLoader(); - + } try { - return Class.forName(className, true, loader); + return Reflection.toClass(className, loader); } catch (ClassNotFoundException ex) { - throw new ValidationException("Unable to load class: " + className, ex); + throw Exceptions.create(ValidationException::new, ex, "Unable to load class: %s", className); } } - }
