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

io.micronaut.http.bind.binders.RequestBeanAnnotationBinder Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2017-2020 original authors
 *
 * Licensed 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
 *
 * https://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 io.micronaut.http.bind.binders;

import io.micronaut.core.annotation.AnnotationMetadata;
import io.micronaut.core.beans.BeanIntrospection;
import io.micronaut.core.beans.BeanProperty;
import io.micronaut.core.bind.ArgumentBinder;
import io.micronaut.core.bind.exceptions.UnsatisfiedArgumentException;
import io.micronaut.core.convert.ArgumentConversionContext;
import io.micronaut.core.convert.ConversionContext;
import io.micronaut.core.convert.ConversionError;
import io.micronaut.core.convert.exceptions.ConversionErrorException;
import io.micronaut.core.naming.Named;
import io.micronaut.core.type.Argument;
import io.micronaut.http.HttpRequest;
import io.micronaut.http.annotation.RequestBean;
import io.micronaut.http.bind.RequestBinderRegistry;

import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * Used to bind Bindable parameters to a Bean object.
 * NOTE: The binder is annotating as postponed to allow injecting values added by filters.

 * @author Anze Sodja
 * @author graemerocher
 * @since 2.0
 * @see RequestBean
 * @param 
 */
public class RequestBeanAnnotationBinder implements AnnotatedRequestArgumentBinder, PostponedRequestArgumentBinder {

    private final RequestBinderRegistry requestBinderRegistry;

    /**
     * @param requestBinderRegistry Original request binder registry
     */
    public RequestBeanAnnotationBinder(RequestBinderRegistry requestBinderRegistry) {
        this.requestBinderRegistry = requestBinderRegistry;
    }

    @Override
    public Class getAnnotationType() {
        return RequestBean.class;
    }

    @Override
    public BindingResult bind(ArgumentConversionContext context, HttpRequest source) {
        Argument argument = context.getArgument();
        AnnotationMetadata annotationMetadata = argument.getAnnotationMetadata();
        boolean hasAnnotation = annotationMetadata.hasAnnotation(RequestBean.class);

        if (hasAnnotation) {
            BeanIntrospection introspection = BeanIntrospection.getIntrospection(context.getArgument().getType());
            Map> beanProperties = introspection.getBeanProperties().stream()
                    .collect(Collectors.toMap(Named::getName, p -> p));

            if (introspection.getConstructorArguments().length > 0) {
                // Handle injection with Constructor or @Creator
                Argument[] constructorArguments = introspection.getConstructorArguments();
                Object[] argumentValues = new Object[constructorArguments.length];
                for (int i = 0; i < constructorArguments.length; i++) {
                    @SuppressWarnings("unchecked")
                    Argument constructorArgument = (Argument) constructorArguments[i];
                    BeanProperty bp = beanProperties.get(constructorArgument.getName());
                    Argument argumentToBind;
                    if (bp != null) {
                        argumentToBind = bp.asArgument();
                    } else {
                        argumentToBind = constructorArgument;
                    }
                    Optional bindableResult = getBindableResult(source, argumentToBind);
                    argumentValues[i] = constructorArgument.isOptional() ? bindableResult : bindableResult.orElse(null);
                }
                return () -> Optional.of(introspection.instantiate(false, argumentValues));
            } else {
                // Handle injection with setters, we checked that all values are writable at compile time
                T bean = introspection.instantiate();
                for (BeanProperty property : beanProperties.values()) {
                    Argument propertyArgument = property.asArgument();
                    Optional bindableResult = getBindableResult(source, propertyArgument);
                    property.set(bean, propertyArgument.isOptional()
                            ? bindableResult
                            : bindableResult.orElse(null));
                }
                return () -> Optional.of(bean);
            }
        } else {
            //noinspection unchecked
            return BindingResult.EMPTY;
        }
    }

    private Optional getBindableResult(HttpRequest source, Argument argument) {
        ArgumentConversionContext conversionContext = ConversionContext.of(
                argument,
                source.getLocale().orElse(Locale.getDefault()),
                source.getCharacterEncoding()
        );
        return getBindableResult(conversionContext, source);
    }

    private Optional getBindableResult(ArgumentConversionContext conversionContext, HttpRequest source) {
        Argument argument = conversionContext.getArgument();
        Optional>> binder = requestBinderRegistry.findArgumentBinder(argument);
        if (binder.isEmpty()) {
            throw new UnsatisfiedArgumentException(argument);
        }
        BindingResult result = binder.get().bind(conversionContext, source);
        if (!result.isSatisfied() || !result.getConversionErrors().isEmpty()) {
            List errors = result.getConversionErrors();
            if (!errors.isEmpty()) {
                throw new ConversionErrorException(argument, errors.iterator().next());
            }
        }
        if (!result.isPresentAndSatisfied() && !argument.isNullable() && !argument.getType().isAssignableFrom(Optional.class)) {
            throw new UnsatisfiedArgumentException(argument);
        }
        return result.getValue();
    }

}