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

se.fortnox.reactivewizard.jaxrs.params.annotated.BeanParamResolver Maven / Gradle / Ivy

There is a newer version: 24.6.0
Show newest version
package se.fortnox.reactivewizard.jaxrs.params.annotated;

import com.fasterxml.jackson.core.type.TypeReference;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import se.fortnox.reactivewizard.jaxrs.JaxRsRequest;
import se.fortnox.reactivewizard.jaxrs.params.ParamResolver;
import se.fortnox.reactivewizard.jaxrs.params.ParamResolverFactories;
import se.fortnox.reactivewizard.json.Types;
import se.fortnox.reactivewizard.util.ReflectionUtil;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;

import static java.util.Arrays.asList;

public class BeanParamResolver extends AnnotatedParamResolver {

    private static final Object NULL_VALUE = new Object();

    private final Function> resolver;

    public BeanParamResolver(Function> resolver) {
        super(null, null, null);
        this.resolver = resolver;
    }

    @Override
    protected String getValue(JaxRsRequest request) {
        return null;
    }

    @Override
    public Mono resolve(JaxRsRequest request) {
        return resolver.apply(request);
    }

    public static class Factory implements AnnotatedParamResolverFactory {

        private final AnnotatedParamResolverFactories annotatedParamResolverFactories;

        public Factory(AnnotatedParamResolverFactories annotatedParamResolverFactories) {
            this.annotatedParamResolverFactories = annotatedParamResolverFactories;
        }

        @Override
        public  ParamResolver create(TypeReference paramType, Annotation annotation, String defaultValue) {
            //noinspection unchecked
            Class beanParamCls = (Class)paramType.getType();

            if (beanParamCls.isRecord()) {
                return createForRecord(beanParamCls);
            } else {
                return createForClass(beanParamCls);
            }
        }

        private  BeanParamResolver createForClass(Class beanParamCls) {
            Supplier instantiator = ReflectionUtil.instantiator(beanParamCls);

            List>> fieldSetters = new ArrayList<>();

            for (Field field : getAllDeclaredFields(beanParamCls)) {
                Annotation[] fieldAnnotations = field.getAnnotations();
                for (Annotation fieldAnnotation : fieldAnnotations) {
                    AnnotatedParamResolverFactory paramResolverFactory = annotatedParamResolverFactories.get(fieldAnnotation.annotationType());
                    if (paramResolverFactory != null) {
                        TypeReference fieldType = Types.toReference(field.getGenericType());
                        String defaultFieldValue = ParamResolverFactories.findDefaultValue(asList(fieldAnnotations));
                        ParamResolver fieldResolver = paramResolverFactory.create(fieldType, fieldAnnotation, defaultFieldValue);

                        Optional> setterOptional = ReflectionUtil.setter(beanParamCls, field.getName());
                        if (setterOptional.isEmpty()) {
                            continue;
                        }
                        BiConsumer setter = setterOptional.get();
                        fieldSetters.add((instance, request) -> {
                            Mono fieldValue = fieldResolver.resolve(request);
                            return fieldValue.map(value -> {
                                setter.accept(instance, value);
                                return (T)instance;
                            });
                        });
                    }
                }
            }

            Function> resolver = (JaxRsRequest request) -> {
                T instance = instantiator.get();
                List> runSetters = new ArrayList<>(fieldSetters.size());
                for (var setter : fieldSetters) {
                    runSetters.add(setter.apply(instance, request));
                }
                return Flux.merge(runSetters).count().map(count -> instance);
            };

            return new BeanParamResolver<>(resolver);
        }

        private  BeanParamResolver createForRecord(Class beanParamCls) {
            var constructors = beanParamCls.getDeclaredConstructors();
            if (constructors.length != 1) {
                throw new IllegalArgumentException("A @BeanParam record may only have a single constructor");
            }

            var constructor = constructors[0];

            var constructorParams = constructor.getParameters();
            var constructorArgumentResolvers = new ArrayList>(constructorParams.length);

            for (var constructorParam : constructorParams) {
                Annotation annotation = null;
                AnnotatedParamResolverFactory paramResolverFactory = null;

                var parameterAnnotations = asList(constructorParam.getAnnotations());

                for (var ann : parameterAnnotations) {
                    paramResolverFactory = annotatedParamResolverFactories.get(ann.annotationType());
                    if (paramResolverFactory != null) {
                        annotation = ann;
                        break;
                    }
                }

                if (paramResolverFactory == null) {
                    throw new IllegalArgumentException("Missing Param annotation for @BeanParam record parameter");
                }

                var paramType = Types.toReference(constructorParam.getParameterizedType());
                var defaultValue = ParamResolverFactories.findDefaultValue(parameterAnnotations);
                var paramResolver = paramResolverFactory.create(paramType, annotation, defaultValue);

                constructorArgumentResolvers.add(paramResolver);
            }

            Function> resolver = (JaxRsRequest request) -> {
                var argsFlux = Flux.fromIterable(constructorArgumentResolvers)
                    .map(it -> it.resolve(request).defaultIfEmpty(NULL_VALUE));

                return Flux.concat(argsFlux)
                    .reduce(new ArrayList<>(), (acc, next) -> {
                        if (next == NULL_VALUE) {
                            acc.add(null);
                        } else {
                            acc.add(next);
                        }
                        return acc;
                    })
                    .map(args -> {
                        try {
                            //noinspection unchecked
                            return (T)(constructor.newInstance(args.toArray()));
                        } catch (Throwable e) {
                            throw new RuntimeException(e);
                        }
                    });
            };

            return new BeanParamResolver<>(resolver);
        }

        private static List getAllDeclaredFields(Class type) {
            List fields = new ArrayList<>();
            for (Class c = type; c != null; c = c.getSuperclass()) {
                fields.addAll(asList(c.getDeclaredFields()));
            }
            return fields;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy