apply constraints cache size to constraint attributes cache used for annotation proxies
Project: http://git-wip-us.apache.org/repos/asf/bval/repo Commit: http://git-wip-us.apache.org/repos/asf/bval/commit/1d54c14c Tree: http://git-wip-us.apache.org/repos/asf/bval/tree/1d54c14c Diff: http://git-wip-us.apache.org/repos/asf/bval/diff/1d54c14c Branch: refs/heads/master Commit: 1d54c14c920188f09abfe8aad294824f9d9f0a66 Parents: 3735154 Author: Matt Benson <[email protected]> Authored: Wed Oct 17 17:17:55 2018 -0500 Committer: Matt Benson <[email protected]> Committed: Wed Oct 17 17:17:55 2018 -0500 ---------------------------------------------------------------------- .../apache/bval/jsr/ApacheValidatorFactory.java | 8 +- .../bval/jsr/descriptor/MetadataReader.java | 5 +- .../bval/jsr/metadata/MetadataSource.java | 4 + .../apache/bval/jsr/metadata/XmlBuilder.java | 16 +- .../apache/bval/jsr/util/AnnotationProxy.java | 169 ++++++++++++ .../bval/jsr/util/AnnotationProxyBuilder.java | 235 +++++++++++++++++ .../bval/jsr/util/AnnotationsManager.java | 24 +- .../apache/bval/jsr/xml/AnnotationProxy.java | 169 ------------ .../bval/jsr/xml/AnnotationProxyBuilder.java | 257 ------------------- .../bval/jsr/xml/ValidationMappingParser.java | 17 +- 10 files changed, 461 insertions(+), 443 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/bval/blob/1d54c14c/bval-jsr/src/main/java/org/apache/bval/jsr/ApacheValidatorFactory.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/ApacheValidatorFactory.java b/bval-jsr/src/main/java/org/apache/bval/jsr/ApacheValidatorFactory.java index 481b501..89caf63 100644 --- a/bval-jsr/src/main/java/org/apache/bval/jsr/ApacheValidatorFactory.java +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/ApacheValidatorFactory.java @@ -24,6 +24,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Map; +import java.util.Optional; import java.util.function.BiConsumer; import javax.validation.ClockProvider; @@ -368,6 +369,11 @@ public class ApacheValidatorFactory implements ValidatorFactory, Cloneable { getMetadataBuilders().registerCustomBuilder((Class) t, (MetadataBuilder.ForBean) b); }; participantFactory.loadServices(MetadataSource.class) - .forEach(ms -> ms.process(configuration, getConstraintsCache()::add, addBuilder)); + .forEach(ms -> { + Optional.of(ms).filter(MetadataSource.FactoryDependent.class::isInstance) + .map(MetadataSource.FactoryDependent.class::cast).ifPresent(fd -> fd.setFactory(this)); + + ms.process(configuration, getConstraintsCache()::add, addBuilder); + }); } } http://git-wip-us.apache.org/repos/asf/bval/blob/1d54c14c/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/MetadataReader.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/MetadataReader.java b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/MetadataReader.java index 0828933..6ef1a86 100644 --- a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/MetadataReader.java +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/MetadataReader.java @@ -65,10 +65,10 @@ import org.apache.bval.jsr.metadata.EmptyBuilder; import org.apache.bval.jsr.metadata.Meta; import org.apache.bval.jsr.metadata.MetadataBuilder; import org.apache.bval.jsr.metadata.Signature; +import org.apache.bval.jsr.util.AnnotationProxyBuilder; import org.apache.bval.jsr.util.AnnotationsManager; import org.apache.bval.jsr.util.Methods; import org.apache.bval.jsr.util.ToUnmodifiable; -import org.apache.bval.jsr.xml.AnnotationProxyBuilder; import org.apache.bval.util.Exceptions; import org.apache.bval.util.ObjectUtils; import org.apache.bval.util.Validate; @@ -123,7 +123,8 @@ class MetadataReader { } } if (mustRewrite) { - final AnnotationProxyBuilder<A> builder = new AnnotationProxyBuilder<A>(constraint); + final AnnotationProxyBuilder<A> builder = + validatorFactory.getAnnotationsManager().buildProxyFor(constraint); builder.setGroups(groups); return builder.createAnnotation(); } http://git-wip-us.apache.org/repos/asf/bval/blob/1d54c14c/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/MetadataSource.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/MetadataSource.java b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/MetadataSource.java index 44cca43..29cc32b 100644 --- a/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/MetadataSource.java +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/MetadataSource.java @@ -20,12 +20,16 @@ import java.util.function.BiConsumer; import java.util.function.Consumer; import javax.validation.ConstraintValidator; +import javax.validation.ValidatorFactory; import javax.validation.spi.ConfigurationState; /** * Service interface for user metadata customizations. */ public interface MetadataSource { + interface FactoryDependent extends MetadataSource { + void setFactory(ValidatorFactory validatorFactory); + } /** * Add {@link ConstraintValidator} mappings and/or metadata builders. http://git-wip-us.apache.org/repos/asf/bval/blob/1d54c14c/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/XmlBuilder.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/XmlBuilder.java b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/XmlBuilder.java index be23e7c..42ce439 100644 --- a/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/XmlBuilder.java +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/XmlBuilder.java @@ -45,10 +45,11 @@ import javax.validation.ValidationException; import javax.validation.groups.Default; import javax.xml.bind.JAXBElement; +import org.apache.bval.jsr.ApacheValidatorFactory; import org.apache.bval.jsr.ConstraintAnnotationAttributes; import org.apache.bval.jsr.groups.GroupConversion; +import org.apache.bval.jsr.util.AnnotationProxyBuilder; import org.apache.bval.jsr.util.ToUnmodifiable; -import org.apache.bval.jsr.xml.AnnotationProxyBuilder; import org.apache.bval.jsr.xml.AnnotationType; import org.apache.bval.jsr.xml.BeanType; import org.apache.bval.jsr.xml.ClassType; @@ -475,13 +476,14 @@ public class XmlBuilder { return lazy.get(); } + private final ApacheValidatorFactory validatorFactory; private final ConstraintMappingsType constraintMappings; private final Version version; - public XmlBuilder(ConstraintMappingsType constraintMappings) { + public XmlBuilder(ApacheValidatorFactory validatorFactory, ConstraintMappingsType constraintMappings) { super(); - this.constraintMappings = constraintMappings; - Validate.notNull(constraintMappings, "constraintMappings"); + this.validatorFactory = Validate.notNull(validatorFactory, "validatorFactory"); + this.constraintMappings = Validate.notNull(constraintMappings, "constraintMappings"); this.version = Version.of(constraintMappings); new MappingValidator(constraintMappings, this::resolveClass).validateMappings(); } @@ -544,7 +546,8 @@ public class XmlBuilder { private <A extends Annotation, T> A createConstraint(final ConstraintType constraint, ConstraintTarget target) { final Class<A> annotationClass = this.<A> loadClass(toQualifiedClassName(constraint.getAnnotation())); - final AnnotationProxyBuilder<A> annoBuilder = new AnnotationProxyBuilder<A>(annotationClass); + final AnnotationProxyBuilder<A> annoBuilder = + validatorFactory.getAnnotationsManager().buildProxyFor(annotationClass); if (constraint.getMessage() != null) { annoBuilder.setMessage(constraint.getMessage()); @@ -676,7 +679,8 @@ public class XmlBuilder { } private <A extends Annotation> Annotation createAnnotation(AnnotationType annotationType, Class<A> returnType) { - final AnnotationProxyBuilder<A> metaAnnotation = new AnnotationProxyBuilder<>(returnType); + final AnnotationProxyBuilder<A> metaAnnotation = + validatorFactory.getAnnotationsManager().buildProxyFor(returnType); for (ElementType elementType : annotationType.getElement()) { final String name = elementType.getName(); metaAnnotation.setValue(name, getElementValue(elementType, getAnnotationParameterType(returnType, name))); http://git-wip-us.apache.org/repos/asf/bval/blob/1d54c14c/bval-jsr/src/main/java/org/apache/bval/jsr/util/AnnotationProxy.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/util/AnnotationProxy.java b/bval-jsr/src/main/java/org/apache/bval/jsr/util/AnnotationProxy.java new file mode 100644 index 0000000..1d21189 --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/util/AnnotationProxy.java @@ -0,0 +1,169 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.bval.jsr.util; + +import java.io.Serializable; +import java.lang.annotation.Annotation; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.SortedMap; +import java.util.TreeMap; +import java.util.stream.Collectors; + +import javax.validation.Valid; + +import org.apache.bval.jsr.metadata.Signature; +import org.apache.bval.util.Exceptions; +import org.apache.bval.util.ObjectUtils; +import org.apache.bval.util.StringUtils; +import org.apache.bval.util.reflection.Reflection; + +/** + * Description: <br/> + * InvocationHandler implementation of <code>Annotation</code> that pretends it is a "real" source code annotation. + * <p/> + */ +class AnnotationProxy implements Annotation, InvocationHandler, Serializable { + + /** Serialization version */ + private static final long serialVersionUID = 1L; + + private Signature EQUALS = new Signature("equals", Object.class); + + private final Class<? extends Annotation> annotationType; + private final SortedMap<String, Object> values; + + /** + * Create a new AnnotationProxy instance. + * + * @param <A> + * @param descriptor + */ + <A extends Annotation> AnnotationProxy(AnnotationProxyBuilder<A> descriptor) { + this.annotationType = descriptor.getType(); + values = new TreeMap<>(); + int processedValuesFromDescriptor = 0; + for (final Method m : descriptor.getMethods()) { + if (descriptor.contains(m.getName())) { + values.put(m.getName(), descriptor.getValue(m.getName())); + processedValuesFromDescriptor++; + } else { + if (m.getDefaultValue() == null) { + Exceptions.raise(IllegalArgumentException::new, "No value provided for %s", m.getName()); + } + values.put(m.getName(), m.getDefaultValue()); + } + } + Exceptions.raiseUnless(processedValuesFromDescriptor == descriptor.size() || Valid.class.equals(annotationType), + IllegalArgumentException::new, "Trying to instantiate %s with unknown parameters.", + f -> f.args(annotationType.getName())); + } + + /** + * {@inheritDoc} + */ + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + if (values.containsKey(method.getName())) { + return values.get(method.getName()); + } + if (EQUALS.equals(Signature.of(method))) { + return equalTo(args[0]); + } + return method.invoke(this, args); + } + + /** + * {@inheritDoc} + */ + @Override + public Class<? extends Annotation> annotationType() { + return annotationType; + } + + /** + * {@inheritDoc} + */ + @Override + public String toString() { + return values.entrySet().stream() + .map(e -> String.format("%s=%s", e.getKey(), StringUtils.valueOf(e.getValue()))) + .collect(Collectors.joining(", ", String.format("@%s(", annotationType().getName()), ")")); + } + + @Override + public int hashCode() { + return values.entrySet().stream().mapToInt(e -> { + return (127 * e.getKey().hashCode()) ^ ObjectUtils.hashCode(e.getValue()); + }).sum(); + } + + private boolean equalTo(Object obj) { + if (obj == this) { + return true; + } + if (obj instanceof Annotation) { + final Annotation other = (Annotation) obj; + return other.annotationType().equals(annotationType) + && values.entrySet().stream().allMatch(e -> memberEquals(other, e.getKey(), e.getValue())); + } + return false; + } + + private boolean memberEquals(Annotation other, String name, Object value) { + final Method member = Reflection.getDeclaredMethod(annotationType, name); + final Object otherValue; + try { + otherValue = member.invoke(other); + } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { + throw new IllegalStateException(e); + } + Exceptions.raiseIf(otherValue == null || !otherValue.getClass().equals(value.getClass()), + IllegalStateException::new, "Unexpected value %s for member %s of %s", otherValue, name, other); + + if (value instanceof Object[]) { + return Arrays.equals((Object[]) value, (Object[]) otherValue); + } + if (value instanceof byte[]) { + return Arrays.equals((byte[]) value, (byte[]) otherValue); + } + if (value instanceof short[]) { + return Arrays.equals((short[]) value, (short[]) otherValue); + } + if (value instanceof int[]) { + return Arrays.equals((int[]) value, (int[]) otherValue); + } + if (value instanceof char[]) { + return Arrays.equals((char[]) value, (char[]) otherValue); + } + if (value instanceof long[]) { + return Arrays.equals((long[]) value, (long[]) otherValue); + } + if (value instanceof float[]) { + return Arrays.equals((float[]) value, (float[]) otherValue); + } + if (value instanceof double[]) { + return Arrays.equals((double[]) value, (double[]) otherValue); + } + if (value instanceof boolean[]) { + return Arrays.equals((boolean[]) value, (boolean[]) otherValue); + } + return value.equals(otherValue); + } +} http://git-wip-us.apache.org/repos/asf/bval/blob/1d54c14c/bval-jsr/src/main/java/org/apache/bval/jsr/util/AnnotationProxyBuilder.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/util/AnnotationProxyBuilder.java b/bval-jsr/src/main/java/org/apache/bval/jsr/util/AnnotationProxyBuilder.java new file mode 100644 index 0000000..ee9a139 --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/util/AnnotationProxyBuilder.java @@ -0,0 +1,235 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.bval.jsr.util; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +import javax.enterprise.util.AnnotationLiteral; +import javax.validation.ConstraintTarget; +import javax.validation.Payload; +import javax.validation.Valid; +import javax.validation.ValidationException; +import javax.validation.groups.ConvertGroup; + +import org.apache.bval.cdi.EmptyAnnotationLiteral; +import org.apache.bval.jsr.ConstraintAnnotationAttributes; +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; + +/** + * Description: Holds the information and creates an annotation proxy during xml + * parsing of validation mapping constraints. <br/> + */ +@Privilizing(@CallTo(Reflection.class)) +public final class AnnotationProxyBuilder<A extends Annotation> { + private final Class<A> type; + private final Map<String, Object> elements = new HashMap<>(); + private final Method[] methods; + private boolean changed; + + /** + * Create a new AnnotationProxyBuilder instance. + * + * @param annotationType + * @param cache + */ + AnnotationProxyBuilder(final Class<A> annotationType, Map<Class<?>, Method[]> cache) { + this.type = Validate.notNull(annotationType, "annotationType"); + this.methods = Validate.notNull(cache, "cache").computeIfAbsent(annotationType, Reflection::getDeclaredMethods); + } + + /** + * Create a builder initially configured to create an annotation equivalent + * to {@code annot}. + * + * @param annot + * Annotation to be replicated. + * @param cache + */ + @SuppressWarnings("unchecked") + AnnotationProxyBuilder(A annot, Map<Class<?>, Method[]> cache) { + this((Class<A>) annot.annotationType(), cache); + elements.putAll(AnnotationsManager.readAttributes(annot)); + } + + public Method[] getMethods() { + return methods; + } + + /** + * 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; + } + + /** + * Get the specified element value from the current configuration. + * + * @param elementName + * @return Object value + */ + public Object getValue(String elementName) { + return elements.get(elementName); + } + + /** + * Learn whether a given element has been configured. + * + * @param elementName + * @return <code>true</code> if an <code>elementName</code> element is found + * on this annotation + */ + public boolean contains(String elementName) { + return elements.containsKey(elementName); + } + + /** + * Get the number of configured elements. + * + * @return int + */ + public int size() { + return elements.size(); + } + + /** + * Get the configured Annotation type. + * + * @return Class<A> + */ + public Class<A> getType() { + return type; + } + + /** + * Configure the well-known JSR303 "message" element. + * + * @param message + * @return + */ + public boolean setMessage(String message) { + return setValue(ConstraintAnnotationAttributes.MESSAGE.getAttributeName(), message); + } + + /** + * Configure the well-known JSR303 "groups" element. + * + * @param groups + * @return + */ + 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 boolean setValidationAppliesTo(ConstraintTarget constraintTarget) { + return setValue(ConstraintAnnotationAttributes.VALIDATION_APPLIES_TO.getAttributeName(), constraintTarget); + } + + public boolean isChanged() { + return changed; + } + + /** + * Create the annotation represented by this builder. + * + * @return {@link Annotation} + */ + public A createAnnotation() { + final ClassLoader classLoader = Reflection.loaderFromClassOrThread(getType()); + @SuppressWarnings("unchecked") + final Class<A> proxyClass = (Class<A>) Proxy.getProxyClass(classLoader, getType()); + return doCreateAnnotation(proxyClass, new AnnotationProxy(this)); + } + + @Privileged + private A doCreateAnnotation(final Class<A> proxyClass, final InvocationHandler handler) { + try { + 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); + } + } + + public static final class ValidAnnotation extends EmptyAnnotationLiteral<Valid> implements Valid { + private static final long serialVersionUID = 1L; + + public static final ValidAnnotation INSTANCE = new ValidAnnotation(); + } + + public static final class ConvertGroupAnnotation extends AnnotationLiteral<ConvertGroup> implements ConvertGroup { + private static final long serialVersionUID = 1L; + + private final Class<?> from; + private final Class<?> to; + + public ConvertGroupAnnotation(final Class<?> from, final Class<?> to) { + this.from = from; + this.to = to; + } + + @Override + public Class<?> from() { + return from; + } + + @Override + public Class<?> to() { + return to; + } + } +} http://git-wip-us.apache.org/repos/asf/bval/blob/1d54c14c/bval-jsr/src/main/java/org/apache/bval/jsr/util/AnnotationsManager.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/util/AnnotationsManager.java b/bval-jsr/src/main/java/org/apache/bval/jsr/util/AnnotationsManager.java index ead0a3b..dd9c28b 100644 --- a/bval-jsr/src/main/java/org/apache/bval/jsr/util/AnnotationsManager.java +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/util/AnnotationsManager.java @@ -56,7 +56,6 @@ import org.apache.bval.jsr.ConstraintAnnotationAttributes; import org.apache.bval.jsr.ConstraintAnnotationAttributes.Worker; import org.apache.bval.jsr.ConstraintCached.ConstraintValidatorInfo; import org.apache.bval.jsr.metadata.Meta; -import org.apache.bval.jsr.xml.AnnotationProxyBuilder; import org.apache.bval.util.Exceptions; import org.apache.bval.util.Lazy; import org.apache.bval.util.ObjectUtils; @@ -107,8 +106,8 @@ public class AnnotationsManager { } } - private static class Composition { - static <A extends Annotation> Optional<ConstraintAnnotationAttributes.Worker<A>> validWorker( + private class Composition { + <A extends Annotation> Optional<ConstraintAnnotationAttributes.Worker<A>> validWorker( ConstraintAnnotationAttributes attr, Class<A> type) { return Optional.of(type).map(attr::analyze).filter(Worker::isValid); } @@ -179,7 +178,7 @@ public class AnnotationsManager { final int index = constraintCounts.computeIfAbsent(c.annotationType(), k -> new AtomicInteger()).getAndIncrement(); - final AnnotationProxyBuilder<Annotation> proxyBuilder = new AnnotationProxyBuilder<>(c); + final AnnotationProxyBuilder<Annotation> proxyBuilder = buildProxyFor(c); proxyBuilder.setGroups(groups); proxyBuilder.setPayload(payload); @@ -281,7 +280,6 @@ public class AnnotationsManager { } return constraints.toArray(Annotation[]::new); } - private static Optional<AnnotatedElement> substitute(AnnotatedElement e) { if (e instanceof Parameter) { @@ -310,19 +308,23 @@ public class AnnotationsManager { private final ApacheValidatorFactory validatorFactory; private final LRUCache<Class<? extends Annotation>, Composition> compositions; + private final LRUCache<Class<? extends Annotation>, Method[]> constraintAttributes; public AnnotationsManager(ApacheValidatorFactory validatorFactory) { super(); this.validatorFactory = Validate.notNull(validatorFactory); final String cacheSize = validatorFactory.getProperties().get(ConfigurationImpl.Properties.CONSTRAINTS_CACHE_SIZE); + final int sz; try { - compositions = new LRUCache<>(Integer.parseInt(cacheSize)); + sz = Integer.parseInt(cacheSize); } catch (NumberFormatException e) { throw Exceptions.create(IllegalStateException::new, e, "Cannot parse value %s for configuration property %s", cacheSize, ConfigurationImpl.Properties.CONSTRAINTS_CACHE_SIZE); } + compositions = new LRUCache<>(sz); + constraintAttributes = new LRUCache<>(sz); } public void validateConstraintDefinition(Class<? extends Annotation> type) { @@ -414,6 +416,16 @@ public class AnnotationsManager { .collect(Collectors.toCollection(() -> EnumSet.noneOf(ValidationTarget.class))); } + @SuppressWarnings({ "unchecked", "rawtypes" }) + public <A extends Annotation> AnnotationProxyBuilder<A> buildProxyFor(Class<A> type) { + return new AnnotationProxyBuilder<>(type, (Map) constraintAttributes); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + public <A extends Annotation> AnnotationProxyBuilder<A> buildProxyFor(A instance) { + return new AnnotationProxyBuilder<>(instance, (Map) constraintAttributes); + } + private Composition getComposition(Class<? extends Annotation> annotationType) { return compositions.computeIfAbsent(annotationType, ct -> { final Set<ValidationTarget> composedTargets = supportedTargets(annotationType); http://git-wip-us.apache.org/repos/asf/bval/blob/1d54c14c/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 deleted file mode 100644 index 44d67b8..0000000 --- a/bval-jsr/src/main/java/org/apache/bval/jsr/xml/AnnotationProxy.java +++ /dev/null @@ -1,169 +0,0 @@ -/* - * 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.io.Serializable; -import java.lang.annotation.Annotation; -import java.lang.reflect.InvocationHandler; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.Arrays; -import java.util.SortedMap; -import java.util.TreeMap; -import java.util.stream.Collectors; - -import javax.validation.Valid; - -import org.apache.bval.jsr.metadata.Signature; -import org.apache.bval.util.Exceptions; -import org.apache.bval.util.ObjectUtils; -import org.apache.bval.util.StringUtils; -import org.apache.bval.util.reflection.Reflection; - -/** - * Description: <br/> - * InvocationHandler implementation of <code>Annotation</code> that pretends it is a "real" source code annotation. - * <p/> - */ -class AnnotationProxy implements Annotation, InvocationHandler, Serializable { - - /** Serialization version */ - private static final long serialVersionUID = 1L; - - private Signature EQUALS = new Signature("equals", Object.class); - - private final Class<? extends Annotation> annotationType; - private final SortedMap<String, Object> values; - - /** - * Create a new AnnotationProxy instance. - * - * @param <A> - * @param descriptor - */ - <A extends Annotation> AnnotationProxy(AnnotationProxyBuilder<A> descriptor) { - this.annotationType = descriptor.getType(); - values = new TreeMap<>(); - int processedValuesFromDescriptor = 0; - for (final Method m : descriptor.getMethods()) { - if (descriptor.contains(m.getName())) { - values.put(m.getName(), descriptor.getValue(m.getName())); - processedValuesFromDescriptor++; - } else { - if (m.getDefaultValue() == null) { - Exceptions.raise(IllegalArgumentException::new, "No value provided for %s", m.getName()); - } - values.put(m.getName(), m.getDefaultValue()); - } - } - Exceptions.raiseUnless(processedValuesFromDescriptor == descriptor.size() || Valid.class.equals(annotationType), - IllegalArgumentException::new, "Trying to instantiate %s with unknown parameters.", - f -> f.args(annotationType.getName())); - } - - /** - * {@inheritDoc} - */ - @Override - public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - if (values.containsKey(method.getName())) { - return values.get(method.getName()); - } - if (EQUALS.equals(Signature.of(method))) { - return equalTo(args[0]); - } - return method.invoke(this, args); - } - - /** - * {@inheritDoc} - */ - @Override - public Class<? extends Annotation> annotationType() { - return annotationType; - } - - /** - * {@inheritDoc} - */ - @Override - public String toString() { - return values.entrySet().stream() - .map(e -> String.format("%s=%s", e.getKey(), StringUtils.valueOf(e.getValue()))) - .collect(Collectors.joining(", ", String.format("@%s(", annotationType().getName()), ")")); - } - - @Override - public int hashCode() { - return values.entrySet().stream().mapToInt(e -> { - return (127 * e.getKey().hashCode()) ^ ObjectUtils.hashCode(e.getValue()); - }).sum(); - } - - private boolean equalTo(Object obj) { - if (obj == this) { - return true; - } - if (obj instanceof Annotation) { - final Annotation other = (Annotation) obj; - return other.annotationType().equals(annotationType) - && values.entrySet().stream().allMatch(e -> memberEquals(other, e.getKey(), e.getValue())); - } - return false; - } - - private boolean memberEquals(Annotation other, String name, Object value) { - final Method member = Reflection.getDeclaredMethod(annotationType, name); - final Object otherValue; - try { - otherValue = member.invoke(other); - } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { - throw new IllegalStateException(e); - } - Exceptions.raiseIf(otherValue == null || !otherValue.getClass().equals(value.getClass()), - IllegalStateException::new, "Unexpected value %s for member %s of %s", otherValue, name, other); - - if (value instanceof Object[]) { - return Arrays.equals((Object[]) value, (Object[]) otherValue); - } - if (value instanceof byte[]) { - return Arrays.equals((byte[]) value, (byte[]) otherValue); - } - if (value instanceof short[]) { - return Arrays.equals((short[]) value, (short[]) otherValue); - } - if (value instanceof int[]) { - return Arrays.equals((int[]) value, (int[]) otherValue); - } - if (value instanceof char[]) { - return Arrays.equals((char[]) value, (char[]) otherValue); - } - if (value instanceof long[]) { - return Arrays.equals((long[]) value, (long[]) otherValue); - } - if (value instanceof float[]) { - return Arrays.equals((float[]) value, (float[]) otherValue); - } - if (value instanceof double[]) { - return Arrays.equals((double[]) value, (double[]) otherValue); - } - if (value instanceof boolean[]) { - return Arrays.equals((boolean[]) value, (boolean[]) otherValue); - } - return value.equals(otherValue); - } -} http://git-wip-us.apache.org/repos/asf/bval/blob/1d54c14c/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 deleted file mode 100644 index dd27560..0000000 --- a/bval-jsr/src/main/java/org/apache/bval/jsr/xml/AnnotationProxyBuilder.java +++ /dev/null @@ -1,257 +0,0 @@ -/* - * 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.lang.annotation.Annotation; -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationHandler; -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; -import javax.validation.groups.ConvertGroup; - -import org.apache.bval.cdi.EmptyAnnotationLiteral; -import org.apache.bval.jsr.ConstraintAnnotationAttributes; -import org.apache.bval.jsr.util.AnnotationsManager; -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; - -/** - * Description: Holds the information and creates an annotation proxy during xml - * parsing of validation mapping constraints. <br/> - */ -@Privilizing(@CallTo(Reflection.class)) -public final class AnnotationProxyBuilder<A extends Annotation> { - 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<>(); - private final Method[] methods; - private boolean changed; - - /** - * Create a new AnnotationProxyBuilder instance. - * - * @param annotationType - */ - public AnnotationProxyBuilder(final Class<A> annotationType) { - this.type = annotationType; - this.methods = findMethods(annotationType); - } - - /** - * Create a new AnnotationProxyBuilder instance. - * - * @param annotationType - * @param elements - */ - public AnnotationProxyBuilder(Class<A> annotationType, Map<String, Object> elements) { - this(annotationType); - elements.forEach(this.elements::put); - } - - /** - * Create a builder initially configured to create an annotation equivalent - * to {@code annot}. - * - * @param annot - * Annotation to be replicated. - */ - @SuppressWarnings("unchecked") - public AnnotationProxyBuilder(A annot) { - this((Class<A>) annot.annotationType()); - elements.putAll(AnnotationsManager.readAttributes(annot)); - } - - public Method[] getMethods() { - return methods; - } - - /** - * 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; - } - - /** - * Get the specified element value from the current configuration. - * - * @param elementName - * @return Object value - */ - public Object getValue(String elementName) { - return elements.get(elementName); - } - - /** - * Learn whether a given element has been configured. - * - * @param elementName - * @return <code>true</code> if an <code>elementName</code> element is found - * on this annotation - */ - public boolean contains(String elementName) { - return elements.containsKey(elementName); - } - - /** - * Get the number of configured elements. - * - * @return int - */ - public int size() { - return elements.size(); - } - - /** - * Get the configured Annotation type. - * - * @return Class<A> - */ - public Class<A> getType() { - return type; - } - - /** - * Configure the well-known JSR303 "message" element. - * - * @param message - * @return - */ - public boolean setMessage(String message) { - return setValue(ConstraintAnnotationAttributes.MESSAGE.getAttributeName(), message); - } - - /** - * Configure the well-known JSR303 "groups" element. - * - * @param groups - * @return - */ - 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 boolean setValidationAppliesTo(ConstraintTarget constraintTarget) { - return setValue(ConstraintAnnotationAttributes.VALIDATION_APPLIES_TO.getAttributeName(), constraintTarget); - } - - public boolean isChanged() { - return changed; - } - - /** - * Create the annotation represented by this builder. - * - * @return {@link Annotation} - */ - public A createAnnotation() { - final ClassLoader classLoader = Reflection.loaderFromClassOrThread(getType()); - @SuppressWarnings("unchecked") - final Class<A> proxyClass = (Class<A>) Proxy.getProxyClass(classLoader, getType()); - return doCreateAnnotation(proxyClass, new AnnotationProxy(this)); - } - - @Privileged - private A doCreateAnnotation(final Class<A> proxyClass, final InvocationHandler handler) { - try { - 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); - } - } - - public static final class ValidAnnotation extends EmptyAnnotationLiteral<Valid> implements Valid { - private static final long serialVersionUID = 1L; - - public static final ValidAnnotation INSTANCE = new ValidAnnotation(); - } - - public static final class ConvertGroupAnnotation extends AnnotationLiteral<ConvertGroup> implements ConvertGroup { - private static final long serialVersionUID = 1L; - - private final Class<?> from; - private final Class<?> to; - - public ConvertGroupAnnotation(final Class<?> from, final Class<?> to) { - this.from = from; - this.to = to; - } - - @Override - public Class<?> from() { - return from; - } - - @Override - public Class<?> to() { - return to; - } - } -} http://git-wip-us.apache.org/repos/asf/bval/blob/1d54c14c/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 59cef76..af80d00 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 @@ -30,8 +30,10 @@ import java.util.function.Consumer; import java.util.stream.Collectors; import javax.validation.ValidationException; +import javax.validation.ValidatorFactory; import javax.validation.spi.ConfigurationState; +import org.apache.bval.jsr.ApacheValidatorFactory; import org.apache.bval.jsr.metadata.MetadataBuilder; import org.apache.bval.jsr.metadata.MetadataBuilder.ForBean; import org.apache.bval.jsr.metadata.MetadataSource; @@ -39,6 +41,7 @@ import org.apache.bval.jsr.metadata.ValidatorMappingProvider; 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.Privilizing; import org.apache.commons.weaver.privilizer.Privilizing.CallTo; @@ -48,7 +51,7 @@ import org.xml.sax.InputSource; * Uses JAXB to parse constraints.xml based on the validation-mapping XML schema. */ @Privilizing(@CallTo(Reflection.class)) -public class ValidationMappingParser implements MetadataSource { +public class ValidationMappingParser implements MetadataSource.FactoryDependent { private static final SchemaManager SCHEMA_MANAGER = new SchemaManager.Builder() .add(XmlBuilder.Version.v10.getId(), "http://jboss.org/xml/ns/javax/validation/mapping", "META-INF/validation-mapping-1.0.xsd") @@ -58,9 +61,18 @@ public class ValidationMappingParser implements MetadataSource { "META-INF/validation-mapping-2.0.xsd") .build(); + private ApacheValidatorFactory validatorFactory; + + @Override + public void setFactory(ValidatorFactory validatorFactory) { + this.validatorFactory = Validate.notNull(validatorFactory).unwrap(ApacheValidatorFactory.class); + } + @Override public void process(ConfigurationState configurationState, Consumer<ValidatorMappingProvider> addValidatorMappingProvider, BiConsumer<Class<?>, ForBean<?>> addBuilder) { + Validate.validState(validatorFactory != null, "validatorFactory unknown"); + if (configurationState.isIgnoreXmlConfiguration()) { return; } @@ -70,7 +82,8 @@ public class ValidationMappingParser implements MetadataSource { Optional.of(mapping).map(this::toMappingProvider).ifPresent(addValidatorMappingProvider); - final Map<Class<?>, MetadataBuilder.ForBean<?>> builders = new XmlBuilder(mapping).forBeans(); + final Map<Class<?>, MetadataBuilder.ForBean<?>> builders = + new XmlBuilder(validatorFactory, mapping).forBeans(); if (Collections.disjoint(beanTypes, builders.keySet())) { builders.forEach(addBuilder::accept); beanTypes.addAll(builders.keySet());
