All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.bval.jsr.descriptor.MetadataReader Maven / Gradle / Ivy

/*
 * 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 jakarta.validation.ConstraintDeclarationException;
import jakarta.validation.GroupDefinitionException;
import jakarta.validation.GroupSequence;
import jakarta.validation.ParameterNameProvider;
import jakarta.validation.Valid;
import jakarta.validation.groups.ConvertGroup;
import jakarta.validation.groups.Default;
import jakarta.validation.metadata.PropertyDescriptor;
import jakarta.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> {
        final Meta meta;
        protected final B builder;

        ForElement(Meta meta, B builder) {
            super();
            this.meta = Validate.notNull(meta, "meta");
            this.builder = Validate.notNull(builder, "builder");
        }

        Set> getConstraints() {
            return builder.getConstraintDeclarationMap(meta).entrySet().stream().filter(e -> e.getValue().length > 0)
                .flatMap(e -> {
                    final Meta 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 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 builder =
                    validatorFactory.getAnnotationsManager().buildProxyFor(constraint);
                builder.setGroups(groups);
                return builder.createAnnotation();
            }
            return constraint;
        }
    }

    class ForBean extends MetadataReader.ForElement, MetadataBuilder.ForClass> {
        private final MetadataBuilder.ForBean beanBuilder;

        ForBean(Meta> meta, MetadataBuilder.ForBean builder) {
            super(meta, Validate.notNull(builder, "builder").getClass(meta));
            this.beanBuilder = builder;
        }

        Map getProperties(BeanD parent) {
            final Map>> properties = new LinkedHashMap<>();
            final Function>> 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> delegates = e.getValue();

                if (delegates.size() == 1) {
                    return delegates.get(0);
                }
                final Set> 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 getMethods(BeanD parent) {
            final Map> methodBuilders = beanBuilder.getMethods(meta);
            if (methodBuilders.isEmpty()) {
                return Collections.emptyMap();
            }
            final Map 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> getConstructors(BeanD parent) {
            final Map>> ctorBuilders =
                beanBuilder.getConstructors(meta);

            if (ctorBuilders.isEmpty()) {
                return Collections.emptyMap();
            }
            final Map> result = new LinkedHashMap<>();

            ctorBuilders.forEach((sig, builder) -> {
                final Constructor c = Reflection.getDeclaredConstructor(meta.getHost(), sig.getParameterTypes());
                @SuppressWarnings({ "unchecked", "rawtypes" })
                final Meta.ForConstructor metaCtor = (Meta.ForConstructor) new Meta.ForConstructor<>(c);
                final ConstructorD descriptor =
                    new ConstructorD<>(new MetadataReader.ForConstructor(metaCtor, builder), parent);
                if (DescriptorManager.isConstrained(descriptor)) {
                    result.put(sig, descriptor);
                }
            });
            return Collections.unmodifiableMap(result);
        }

        GroupStrategy getGroupStrategy() {
            final Class 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> groupSequence = builder.getGroupSequence(meta);

            final Set parentGroups = parentStrategy == null ? null : parentStrategy.getGroups();

            Group localGroup = Group.of(host);
            if (groupSequence == null) {
                final Set 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 % 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 extends ForElement> {

        ForContainer(Meta meta, MetadataBuilder.ForContainer builder) {
            super(meta, builder);
        }

        boolean isCascaded() {
            return builder.isCascade(meta);
        }

        Set getGroupConversions() {
            final Set 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 getContainerElementTypes(CascadableContainerD parent) {
            final Map> containerElementTypes =
                builder.getContainerElementTypes(meta);

            if (containerElementTypes.isEmpty()) {
                return Collections.emptySet();
            }
            final Set 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>
        extends ForElement> {
        private final MetadataBuilder.ForExecutable executableBuilder;

        ForExecutable(Meta meta, MetadataBuilder.ForExecutable executableBuilder) {
            super(meta, EmptyBuilder.instance().forElement());
            this.executableBuilder = Validate.notNull(executableBuilder, "executableBuilder");
        }

        > List> getParameterDescriptors(X parent) {
            final Parameter[] parameters = meta.getHost().getParameters();

            final List parameterNames =
                getParameterNames(validatorFactory.getParameterNameProvider(), meta.getHost());

            final List> 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 parameterBuilder =
                    builders.size() > i ? builders.get(i) : EmptyBuilder.instance().forContainer();
                return new ParameterD<>(param, i, new MetadataReader.ForContainer<>(param, parameterBuilder), parent);
            }).collect(ToUnmodifiable.list());
        }

        > CrossParameterD getCrossParameterDescriptor(X parent) {
            final Meta.ForCrossParameter cp = new Meta.ForCrossParameter<>(meta);
            return new CrossParameterD<>(new MetadataReader.ForElement<>(cp, executableBuilder.getCrossParameter(cp)),
                parent);
        }

        > ReturnValueD getReturnValueDescriptor(X parent) {
            return new ReturnValueD<>(new MetadataReader.ForContainer<>(meta, executableBuilder.getReturnValue(meta)),
                parent);
        }

        abstract List getParameterNames(ParameterNameProvider parameterNameProvider, E host);
    }

    class ForMethod extends ForExecutable {
        ForMethod(Meta meta, MetadataBuilder.ForExecutable builder) {
            super(meta, builder);
        }

        @Override
        List getParameterNames(ParameterNameProvider parameterNameProvider, Method host) {
            return parameterNameProvider.getParameterNames(host);
        }
    }

    class ForConstructor extends ForExecutable, ForConstructor> {

        ForConstructor(Meta> meta,
            MetadataBuilder.ForExecutable> builder) {
            super(meta, builder);
        }

        @Override
        List getParameterNames(ParameterNameProvider parameterNameProvider, Constructor host) {
            return parameterNameProvider.getParameterNames(host);
        }
    }

    private static final Predicate> 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")
     MetadataReader.ForBean forBean(MetadataBuilder.ForBean builder) {
        return new MetadataReader.ForBean<>(new Meta.ForClass<>((Class) beanClass), builder);
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy