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

com.freemanan.starter.grpc.extensions.jsontranscoder.webflux.ControllerMethodResolver Maven / Gradle / Ivy

/*
 * Copyright 2002-2022 the original author or 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 com.freemanan.starter.grpc.extensions.jsontranscoder.webflux;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.KotlinDetector;
import org.springframework.core.ReactiveAdapterRegistry;
import org.springframework.http.codec.HttpMessageReader;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.web.method.ControllerAdviceBean;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.method.annotation.ExceptionHandlerMethodResolver;
import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver;
import org.springframework.web.reactive.result.method.InvocableHandlerMethod;
import org.springframework.web.reactive.result.method.annotation.ContinuationHandlerMethodArgumentResolver;
import org.springframework.web.reactive.result.method.annotation.CookieValueMethodArgumentResolver;
import org.springframework.web.reactive.result.method.annotation.ErrorsMethodArgumentResolver;
import org.springframework.web.reactive.result.method.annotation.ExpressionValueMethodArgumentResolver;
import org.springframework.web.reactive.result.method.annotation.HttpEntityMethodArgumentResolver;
import org.springframework.web.reactive.result.method.annotation.MatrixVariableMapMethodArgumentResolver;
import org.springframework.web.reactive.result.method.annotation.MatrixVariableMethodArgumentResolver;
import org.springframework.web.reactive.result.method.annotation.ModelAttributeMethodArgumentResolver;
import org.springframework.web.reactive.result.method.annotation.ModelMethodArgumentResolver;
import org.springframework.web.reactive.result.method.annotation.PathVariableMapMethodArgumentResolver;
import org.springframework.web.reactive.result.method.annotation.PathVariableMethodArgumentResolver;
import org.springframework.web.reactive.result.method.annotation.PrincipalMethodArgumentResolver;
import org.springframework.web.reactive.result.method.annotation.RequestAttributeMethodArgumentResolver;
import org.springframework.web.reactive.result.method.annotation.RequestBodyMethodArgumentResolver;
import org.springframework.web.reactive.result.method.annotation.RequestHeaderMapMethodArgumentResolver;
import org.springframework.web.reactive.result.method.annotation.RequestHeaderMethodArgumentResolver;
import org.springframework.web.reactive.result.method.annotation.RequestParamMapMethodArgumentResolver;
import org.springframework.web.reactive.result.method.annotation.RequestParamMethodArgumentResolver;
import org.springframework.web.reactive.result.method.annotation.RequestPartMethodArgumentResolver;
import org.springframework.web.reactive.result.method.annotation.ServerWebExchangeMethodArgumentResolver;
import org.springframework.web.reactive.result.method.annotation.SessionAttributeMethodArgumentResolver;
import org.springframework.web.reactive.result.method.annotation.SessionStatusMethodArgumentResolver;
import org.springframework.web.reactive.result.method.annotation.WebSessionMethodArgumentResolver;

/**
 * Copy form {@link org.springframework.web.reactive.result.method.annotation.ControllerMethodResolver}.
 *
 * @author Freeman
 */
class ControllerMethodResolver {

    private final List exceptionHandlerResolvers;

    private final Map, ExceptionHandlerMethodResolver> exceptionHandlerCache = new ConcurrentHashMap<>(64);

    private final Map exceptionHandlerAdviceCache =
            new LinkedHashMap<>(64);

    ControllerMethodResolver(
            ReactiveAdapterRegistry adapterRegistry,
            ConfigurableApplicationContext context,
            List> readers) {

        Assert.notNull(context, "ApplicationContext is required");
        Assert.notNull(readers, "HttpMessageReader List is required");

        this.exceptionHandlerResolvers = exceptionHandlerResolvers(adapterRegistry, context);

        initControllerAdviceCaches(context);
    }

    private static List exceptionHandlerResolvers(
            ReactiveAdapterRegistry adapterRegistry, ConfigurableApplicationContext context) {

        return initResolvers(adapterRegistry, context, false, Collections.emptyList());
    }

    private static List initResolvers(
            ReactiveAdapterRegistry adapterRegistry,
            ConfigurableApplicationContext context,
            boolean supportDataBinding,
            List> readers) {

        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
        boolean requestMappingMethod = !readers.isEmpty() && supportDataBinding;

        // Annotation-based...
        List result = new ArrayList<>(30);
        result.add(new RequestParamMethodArgumentResolver(beanFactory, adapterRegistry, false));
        result.add(new RequestParamMapMethodArgumentResolver(adapterRegistry));
        result.add(new PathVariableMethodArgumentResolver(beanFactory, adapterRegistry));
        result.add(new PathVariableMapMethodArgumentResolver(adapterRegistry));
        result.add(new MatrixVariableMethodArgumentResolver(beanFactory, adapterRegistry));
        result.add(new MatrixVariableMapMethodArgumentResolver(adapterRegistry));
        if (!readers.isEmpty()) {
            result.add(new RequestBodyMethodArgumentResolver(readers, adapterRegistry));
            result.add(new RequestPartMethodArgumentResolver(readers, adapterRegistry));
        }
        if (supportDataBinding) {
            result.add(new ModelAttributeMethodArgumentResolver(adapterRegistry, false));
        }
        result.add(new RequestHeaderMethodArgumentResolver(beanFactory, adapterRegistry));
        result.add(new RequestHeaderMapMethodArgumentResolver(adapterRegistry));
        result.add(new CookieValueMethodArgumentResolver(beanFactory, adapterRegistry));
        result.add(new ExpressionValueMethodArgumentResolver(beanFactory, adapterRegistry));
        result.add(new SessionAttributeMethodArgumentResolver(beanFactory, adapterRegistry));
        result.add(new RequestAttributeMethodArgumentResolver(beanFactory, adapterRegistry));

        // Type-based...
        if (!readers.isEmpty()) {
            result.add(new HttpEntityMethodArgumentResolver(readers, adapterRegistry));
        }
        result.add(new ModelMethodArgumentResolver(adapterRegistry));
        if (supportDataBinding) {
            result.add(new ErrorsMethodArgumentResolver(adapterRegistry));
        }
        result.add(new ServerWebExchangeMethodArgumentResolver(adapterRegistry));
        result.add(new PrincipalMethodArgumentResolver(adapterRegistry));
        if (requestMappingMethod) {
            result.add(new SessionStatusMethodArgumentResolver());
        }
        result.add(new WebSessionMethodArgumentResolver(adapterRegistry));
        if (KotlinDetector.isKotlinPresent()) {
            result.add(new ContinuationHandlerMethodArgumentResolver());
        }

        // Custom...
        // result.addAll(customResolvers.getCustomResolvers());

        // Catch-all...
        result.add(new RequestParamMethodArgumentResolver(beanFactory, adapterRegistry, true));
        if (supportDataBinding) {
            result.add(new ModelAttributeMethodArgumentResolver(adapterRegistry, true));
        }

        return result;
    }

    private void initControllerAdviceCaches(ApplicationContext applicationContext) {
        List beans = ControllerAdviceBean.findAnnotatedBeans(applicationContext);
        for (ControllerAdviceBean bean : beans) {
            Class beanType = bean.getBeanType();
            if (beanType != null) {
                ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType);
                if (resolver.hasExceptionMappings()) {
                    this.exceptionHandlerAdviceCache.put(bean, resolver);
                }
            }
        }
    }

    /**
     * Look for an {@code @ExceptionHandler} method within the class of the given
     * controller method, and also within {@code @ControllerAdvice} classes that
     * are applicable to the class of the given controller method.
     *
     * @param ex            the exception to find a handler for
     * @param handlerMethod the controller method that raised the exception, or
     *                      if {@code null}, check only {@code @ControllerAdvice} classes.
     */
    @Nullable
    public InvocableHandlerMethod getExceptionHandlerMethod(Throwable ex, @Nullable HandlerMethod handlerMethod) {

        Class handlerType = (handlerMethod != null ? handlerMethod.getBeanType() : null);
        Object exceptionHandlerObject = null;
        Method exceptionHandlerMethod = null;

        if (handlerType != null) {
            // Controller-local first...
            exceptionHandlerObject = handlerMethod.getBean();
            exceptionHandlerMethod = this.exceptionHandlerCache
                    .computeIfAbsent(handlerType, ExceptionHandlerMethodResolver::new)
                    .resolveMethodByThrowable(ex);
        }

        if (exceptionHandlerMethod == null) {
            // Global exception handlers...
            for (Map.Entry entry :
                    this.exceptionHandlerAdviceCache.entrySet()) {
                ControllerAdviceBean advice = entry.getKey();
                if (advice.isApplicableToBeanType(handlerType)) {
                    exceptionHandlerMethod = entry.getValue().resolveMethodByThrowable(ex);
                    if (exceptionHandlerMethod != null) {
                        exceptionHandlerObject = advice.resolveBean();
                        break;
                    }
                }
            }
        }

        if (exceptionHandlerObject == null || exceptionHandlerMethod == null) {
            return null;
        }

        InvocableHandlerMethod invocable = new InvocableHandlerMethod(exceptionHandlerObject, exceptionHandlerMethod);
        invocable.setArgumentResolvers(this.exceptionHandlerResolvers);
        return invocable;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy