You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@bval.apache.org by mb...@apache.org on 2018/02/21 21:01:55 UTC
[05/17] bval git commit: BV2: xml processing changes
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 <mb...@apache.org>
Authored: Wed Feb 21 14:29:48 2018 -0600
Committer: Matt Benson <mb...@apache.org>
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);
}
}
-
}