blob: 6ef1a8695ac91fe205305ee8d840733443607bf8 [file] [log] [blame]
/*
* 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.descriptor;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.AnnotatedType;
import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Deque;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.TreeSet;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import javax.validation.ConstraintDeclarationException;
import javax.validation.GroupDefinitionException;
import javax.validation.GroupSequence;
import javax.validation.ParameterNameProvider;
import javax.validation.Valid;
import javax.validation.groups.ConvertGroup;
import javax.validation.groups.Default;
import javax.validation.metadata.PropertyDescriptor;
import javax.validation.metadata.Scope;
import org.apache.bval.jsr.ApacheValidatorFactory;
import org.apache.bval.jsr.ConstraintAnnotationAttributes;
import org.apache.bval.jsr.groups.Group;
import org.apache.bval.jsr.groups.GroupConversion;
import org.apache.bval.jsr.groups.GroupStrategy;
import org.apache.bval.jsr.groups.GroupsComputer;
import org.apache.bval.jsr.metadata.ContainerElementKey;
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.util.Exceptions;
import org.apache.bval.util.ObjectUtils;
import org.apache.bval.util.Validate;
import org.apache.bval.util.reflection.Reflection;
import org.apache.bval.util.reflection.Reflection.Interfaces;
import org.apache.commons.weaver.privilizer.Privilizing;
import org.apache.commons.weaver.privilizer.Privilizing.CallTo;
@Privilizing(@CallTo(Reflection.class))
class MetadataReader {
class ForElement<E extends AnnotatedElement, B extends MetadataBuilder.ForElement<E>> {
final Meta<E> meta;
protected final B builder;
ForElement(Meta<E> meta, B builder) {
super();
this.meta = Validate.notNull(meta, "meta");
this.builder = Validate.notNull(builder, "builder");
}
Set<ConstraintD<?>> getConstraints() {
return builder.getConstraintDeclarationMap(meta).entrySet().stream().filter(e -> e.getValue().length > 0)
.flatMap(e -> {
final Meta<E> m = e.getKey();
final Class<?> declaredBy = m.getDeclaringClass();
final Scope scope = declaredBy.equals(beanClass) ? Scope.LOCAL_ELEMENT : Scope.HIERARCHY;
return Stream.of(e.getValue()).peek(
c -> validatorFactory.getAnnotationsManager().validateConstraintDefinition(c.annotationType()))
.map(c -> rewriteConstraint(c, declaredBy))
.map(c -> new ConstraintD<>(c, scope, m, validatorFactory));
}).collect(ToUnmodifiable.set());
}
ApacheValidatorFactory getValidatorFactory() {
return validatorFactory;
}
private <A extends Annotation> A rewriteConstraint(A constraint, Class<?> declaredBy) {
boolean mustRewrite = false;
Class<?>[] groups =
ConstraintAnnotationAttributes.GROUPS.analyze(constraint.annotationType()).read(constraint);
if (groups.length == 0) {
mustRewrite = true;
groups = GroupsComputer.DEFAULT_GROUP;
}
if (!(declaredBy.equals(beanClass) || beanClass.isInterface())) {
if (ObjectUtils.arrayContains(groups, Default.class)
&& !ObjectUtils.arrayContains(groups, declaredBy)) {
mustRewrite = true;
groups = ObjectUtils.arrayAdd(groups, declaredBy);
}
}
if (mustRewrite) {
final AnnotationProxyBuilder<A> builder =
validatorFactory.getAnnotationsManager().buildProxyFor(constraint);
builder.setGroups(groups);
return builder.createAnnotation();
}
return constraint;
}
}
class ForBean<T> extends MetadataReader.ForElement<Class<T>, MetadataBuilder.ForClass<T>> {
private final MetadataBuilder.ForBean<T> beanBuilder;
ForBean(Meta<Class<T>> meta, MetadataBuilder.ForBean<T> builder) {
super(meta, Validate.notNull(builder, "builder").getClass(meta));
this.beanBuilder = builder;
}
Map<String, PropertyDescriptor> getProperties(BeanD<T> parent) {
final Map<String, List<PropertyD<?>>> properties = new LinkedHashMap<>();
final Function<? super String, ? extends List<PropertyD<?>>> descriptorList = k -> new ArrayList<>();
beanBuilder.getFields(meta).forEach((f, builder) -> {
final Field fld = Reflection.find(meta.getHost(), t -> Reflection.getDeclaredField(t, f));
properties.computeIfAbsent(f, descriptorList).add(
new PropertyD.ForField(new MetadataReader.ForContainer<>(new Meta.ForField(fld), builder), parent));
});
beanBuilder.getGetters(meta).forEach((g, builder) -> {
final Method getter = Methods.getter(meta.getHost(), g);
if (getter == null) {
Exceptions.raise(IllegalStateException::new, "Getter method for property %s not found", g);
}
properties.computeIfAbsent(g, descriptorList).add(new PropertyD.ForMethod(
new MetadataReader.ForContainer<>(new Meta.ForMethod(getter), builder), parent));
});
return properties.entrySet().stream().collect(ToUnmodifiable.map(Map.Entry::getKey, e -> {
final List<PropertyD<?>> delegates = e.getValue();
if (delegates.size() == 1) {
return delegates.get(0);
}
final Set<PropertyD<?>> constrained =
delegates.stream().filter(DescriptorManager::isConstrained).collect(Collectors.toSet());
if (constrained.isEmpty()) {
return delegates.get(0);
}
if (constrained.size() == 1) {
return constrained.iterator().next();
}
return new ComposedD.ForProperty(delegates);
}));
}
Map<Signature, MethodD> getMethods(BeanD<T> parent) {
final Map<Signature, MetadataBuilder.ForExecutable<Method>> methodBuilders = beanBuilder.getMethods(meta);
if (methodBuilders.isEmpty()) {
return Collections.emptyMap();
}
final Map<Signature, MethodD> result = new LinkedHashMap<>();
methodBuilders.forEach((sig, builder) -> {
final Method m = Reflection.find(meta.getHost(),
t -> Reflection.getDeclaredMethod(t, sig.getName(), sig.getParameterTypes()));
final MethodD descriptor =
new MethodD(new MetadataReader.ForMethod(new Meta.ForMethod(m), builder), parent);
if (DescriptorManager.isConstrained(descriptor)) {
result.put(sig, descriptor);
}
});
return Collections.unmodifiableMap(result);
}
Map<Signature, ConstructorD<T>> getConstructors(BeanD<T> parent) {
final Map<Signature, MetadataBuilder.ForExecutable<Constructor<? extends T>>> ctorBuilders =
beanBuilder.getConstructors(meta);
if (ctorBuilders.isEmpty()) {
return Collections.emptyMap();
}
final Map<Signature, ConstructorD<T>> result = new LinkedHashMap<>();
ctorBuilders.forEach((sig, builder) -> {
final Constructor<?> c = Reflection.getDeclaredConstructor(meta.getHost(), sig.getParameterTypes());
@SuppressWarnings({ "unchecked", "rawtypes" })
final Meta.ForConstructor<T> metaCtor = (Meta.ForConstructor) new Meta.ForConstructor<>(c);
final ConstructorD<T> descriptor =
new ConstructorD<>(new MetadataReader.ForConstructor<T>(metaCtor, builder), parent);
if (DescriptorManager.isConstrained(descriptor)) {
result.put(sig, descriptor);
}
});
return Collections.unmodifiableMap(result);
}
GroupStrategy getGroupStrategy() {
final Class<T> host = meta.getHost();
if (host.isInterface()) {
return validatorFactory.getGroupsComputer().computeGroups(host).asStrategy();
}
final GroupStrategy parentStrategy = Optional.ofNullable(host.getSuperclass()).filter(JDK.negate())
.map(validatorFactory.getDescriptorManager()::getBeanDescriptor).map(BeanD.class::cast)
.map(BeanD::getGroupStrategy).orElse(null);
final List<Class<?>> groupSequence = builder.getGroupSequence(meta);
final Set<Group> parentGroups = parentStrategy == null ? null : parentStrategy.getGroups();
Group localGroup = Group.of(host);
if (groupSequence == null) {
final Set<Group> groups = new HashSet<>();
groups.add(localGroup);
for (Class<?> t : Reflection.hierarchy(host, Interfaces.INCLUDE)) {
if (JDK.test(t)) {
continue;
}
if (!t.isInterface()) {
continue;
}
if (AnnotationsManager.isAnnotationDirectlyPresent(t, GroupSequence.class)) {
continue;
}
final Group g = Group.of(t);
if (parentGroups != null && parentGroups.contains(g)) {
continue;
}
groups.add(g);
}
final GroupStrategy strategy = GroupStrategy.simple(groups);
return parentStrategy == null ? strategy : GroupStrategy.composite(strategy, parentStrategy);
}
if (groupSequence.contains(Default.class)) {
Exceptions.raise(GroupDefinitionException::new, "@%s for %s must not contain %s",
GroupSequence.class.getSimpleName(), host, Default.class.getName());
}
if (!groupSequence.contains(host)) {
Exceptions.raise(GroupDefinitionException::new, "@%s for %s must contain %<s",
GroupSequence.class.getSimpleName(), host);
}
final Group.Sequence result =
Group.sequence(groupSequence.stream().map(Group::of).collect(Collectors.toList()));
final Deque<Group> expanded = new ArrayDeque<>();
for (Class<?> t : Reflection.hierarchy(host, Interfaces.INCLUDE)) {
if (JDK.test(t)) {
continue;
}
if (t.isInterface() && AnnotationsManager.isAnnotationDirectlyPresent(t, GroupSequence.class)) {
continue;
}
expanded.push(Group.of(t));
}
if (expanded.size() == 1) {
return result;
}
return result.redefining(Collections.singletonMap(localGroup, GroupStrategy.simple(expanded)));
}
}
class ForContainer<E extends AnnotatedElement> extends ForElement<E, MetadataBuilder.ForContainer<E>> {
ForContainer(Meta<E> meta, MetadataBuilder.ForContainer<E> builder) {
super(meta, builder);
}
boolean isCascaded() {
return builder.isCascade(meta);
}
Set<GroupConversion> getGroupConversions() {
final Set<GroupConversion> groupConversions = builder.getGroupConversions(meta);
if (!groupConversions.isEmpty()) {
if (!isCascaded()) {
Exceptions.raise(ConstraintDeclarationException::new, "@%s declared without @%s on %s",
ConvertGroup.class.getSimpleName(), Valid.class.getSimpleName(), meta.describeHost());
}
if (groupConversions.stream().map(GroupConversion::getFrom).distinct().count() < groupConversions
.size()) {
Exceptions.raise(ConstraintDeclarationException::new, "%s has duplicate 'from' group conversions",
meta.describeHost());
}
groupConversions.stream().map(GroupConversion::getFrom)
.forEach(from -> Exceptions.raiseIf(from.isAnnotationPresent(GroupSequence.class),
ConstraintDeclarationException::new,
"Invalid group conversion declared on %s from group sequence %s",
f -> f.args(meta.describeHost(), from)));
}
return groupConversions;
}
Set<ContainerElementTypeD> getContainerElementTypes(CascadableContainerD<?, ?> parent) {
final Map<ContainerElementKey, MetadataBuilder.ForContainer<AnnotatedType>> containerElementTypes =
builder.getContainerElementTypes(meta);
if (containerElementTypes.isEmpty()) {
return Collections.emptySet();
}
final Set<ContainerElementTypeD> result =
new TreeSet<>(Comparator.comparing(ContainerElementTypeD::getKey));
containerElementTypes.forEach((k, builder) -> {
result.add(new ContainerElementTypeD(k,
new MetadataReader.ForContainer<>(new Meta.ForContainerElement(meta, k), builder), parent));
});
return Collections.unmodifiableSet(result);
}
}
abstract class ForExecutable<E extends Executable, SELF extends ForExecutable<E, SELF>>
extends ForElement<E, MetadataBuilder.ForElement<E>> {
private final MetadataBuilder.ForExecutable<E> executableBuilder;
ForExecutable(Meta<E> meta, MetadataBuilder.ForExecutable<E> executableBuilder) {
super(meta, EmptyBuilder.instance().forElement());
this.executableBuilder = Validate.notNull(executableBuilder, "executableBuilder");
}
<X extends ExecutableD<E, SELF, X>> List<ParameterD<X>> getParameterDescriptors(X parent) {
final Parameter[] parameters = meta.getHost().getParameters();
final List<String> parameterNames =
getParameterNames(validatorFactory.getParameterNameProvider(), meta.getHost());
final List<MetadataBuilder.ForContainer<Parameter>> builders = executableBuilder.getParameters(meta);
return IntStream.range(0, parameters.length).mapToObj(i -> {
final Meta.ForParameter param = new Meta.ForParameter(parameters[i], parameterNames.get(i));
final MetadataBuilder.ForContainer<Parameter> parameterBuilder =
builders.size() > i ? builders.get(i) : EmptyBuilder.instance().forContainer();
return new ParameterD<>(param, i, new MetadataReader.ForContainer<>(param, parameterBuilder), parent);
}).collect(ToUnmodifiable.list());
}
<X extends ExecutableD<E, SELF, X>> CrossParameterD<X, E> getCrossParameterDescriptor(X parent) {
final Meta.ForCrossParameter<E> cp = new Meta.ForCrossParameter<>(meta);
return new CrossParameterD<>(new MetadataReader.ForElement<>(cp, executableBuilder.getCrossParameter(cp)),
parent);
}
<X extends ExecutableD<E, SELF, X>> ReturnValueD<X, E> getReturnValueDescriptor(X parent) {
return new ReturnValueD<>(new MetadataReader.ForContainer<>(meta, executableBuilder.getReturnValue(meta)),
parent);
}
abstract List<String> getParameterNames(ParameterNameProvider parameterNameProvider, E host);
}
class ForMethod extends ForExecutable<Method, ForMethod> {
ForMethod(Meta<Method> meta, MetadataBuilder.ForExecutable<Method> builder) {
super(meta, builder);
}
@Override
List<String> getParameterNames(ParameterNameProvider parameterNameProvider, Method host) {
return parameterNameProvider.getParameterNames(host);
}
}
class ForConstructor<T> extends ForExecutable<Constructor<? extends T>, ForConstructor<T>> {
ForConstructor(Meta<Constructor<? extends T>> meta,
MetadataBuilder.ForExecutable<Constructor<? extends T>> builder) {
super(meta, builder);
}
@Override
List<String> getParameterNames(ParameterNameProvider parameterNameProvider, Constructor<? extends T> host) {
return parameterNameProvider.getParameterNames(host);
}
}
private static final Predicate<Class<?>> JDK = t -> t.getName().startsWith("java.");
private final ApacheValidatorFactory validatorFactory;
private final Class<?> beanClass;
MetadataReader(ApacheValidatorFactory validatorFactory, Class<?> beanClass) {
super();
this.validatorFactory = Validate.notNull(validatorFactory, "validatorFactory");
this.beanClass = Validate.notNull(beanClass, "beanClass");
}
@SuppressWarnings("unchecked")
<T> MetadataReader.ForBean<T> forBean(MetadataBuilder.ForBean<T> builder) {
return new MetadataReader.ForBean<>(new Meta.ForClass<>((Class<T>) beanClass), builder);
}
}