/*
* Copyright 2002-2016 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
*
* 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.springframework.web.reactive.result.method.annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import reactor.core.publisher.Mono;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.core.codec.ByteBufferDecoder;
import org.springframework.core.codec.StringDecoder;
import org.springframework.core.convert.ConversionService;
import org.springframework.format.support.DefaultFormattingConversionService;
import org.springframework.http.converter.reactive.CodecHttpMessageConverter;
import org.springframework.http.converter.reactive.HttpMessageConverter;
import org.springframework.ui.ExtendedModelMap;
import org.springframework.ui.ModelMap;
import org.springframework.validation.Validator;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.method.annotation.ExceptionHandlerMethodResolver;
import org.springframework.web.reactive.HandlerAdapter;
import org.springframework.web.reactive.HandlerResult;
import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver;
import org.springframework.web.reactive.result.method.InvocableHandlerMethod;
import org.springframework.web.server.ServerWebExchange;
/**
* Supports the invocation of {@code @RequestMapping} methods.
*
* @author Rossen Stoyanchev
*/
public class RequestMappingHandlerAdapter implements HandlerAdapter, BeanFactoryAware, InitializingBean {
private static Log logger = LogFactory.getLog(RequestMappingHandlerAdapter.class);
private List<HandlerMethodArgumentResolver> customArgumentResolvers;
private List<HandlerMethodArgumentResolver> argumentResolvers;
private final List<HttpMessageConverter<?>> messageConverters = new ArrayList<>(10);
private ConversionService conversionService = new DefaultFormattingConversionService();
private Validator validator;
private ConfigurableBeanFactory beanFactory;
private final Map<Class<?>, ExceptionHandlerMethodResolver> exceptionHandlerCache = new ConcurrentHashMap<>(64);
public RequestMappingHandlerAdapter() {
this.messageConverters.add(new CodecHttpMessageConverter<>(new ByteBufferDecoder()));
this.messageConverters.add(new CodecHttpMessageConverter<>(new StringDecoder()));
}
/**
* Provide custom argument resolvers without overriding the built-in ones.
*/
public void setCustomArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
this.customArgumentResolvers = argumentResolvers;
}
/**
* Return the custom argument resolvers.
*/
public List<HandlerMethodArgumentResolver> getCustomArgumentResolvers() {
return this.customArgumentResolvers;
}
/**
* Configure the complete list of supported argument types thus overriding
* the resolvers that would otherwise be configured by default.
*/
public void setArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
this.argumentResolvers = new ArrayList<>(resolvers);
}
/**
* Return the configured argument resolvers.
*/
public List<HandlerMethodArgumentResolver> getArgumentResolvers() {
return this.argumentResolvers;
}
/**
* Configure message converters to read the request body with.
*/
public void setMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
this.messageConverters.clear();
this.messageConverters.addAll(messageConverters);
}
/**
* Return the configured message converters.
*/
public List<HttpMessageConverter<?>> getMessageConverters() {
return this.messageConverters;
}
/**
* Configure a ConversionService for type conversion of controller method
* arguments as well as for converting from different async types to
* {@code Flux} and {@code Mono}.
*
* TODO: this may be replaced by DataBinder
*/
public void setConversionService(ConversionService conversionService) {
this.conversionService = conversionService;
}
/**
* Return the configured ConversionService.
*/
public ConversionService getConversionService() {
return this.conversionService;
}
/**
* Configure a Validator for validation of controller method arguments such
* as {@code @RequestBody}.
*
* TODO: this may be replaced by DataBinder
*/
public void setValidator(Validator validator) {
this.validator = validator;
}
/**
* Return the configured Validator.
*/
public Validator getValidator() {
return this.validator;
}
/**
* A {@link ConfigurableBeanFactory} is expected for resolving expressions
* in method argument default values.
*/
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
if (beanFactory instanceof ConfigurableBeanFactory) {
this.beanFactory = (ConfigurableBeanFactory) beanFactory;
}
}
public ConfigurableBeanFactory getBeanFactory() {
return this.beanFactory;
}
@Override
public void afterPropertiesSet() throws Exception {
if (this.argumentResolvers == null) {
this.argumentResolvers = initArgumentResolvers();
}
}
protected List<HandlerMethodArgumentResolver> initArgumentResolvers() {
List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>();
// Annotation-based argument resolution
ConversionService cs = getConversionService();
resolvers.add(new RequestParamMethodArgumentResolver(cs, getBeanFactory(), false));
resolvers.add(new RequestParamMapMethodArgumentResolver());
resolvers.add(new PathVariableMethodArgumentResolver(cs, getBeanFactory()));
resolvers.add(new PathVariableMapMethodArgumentResolver());
resolvers.add(new RequestBodyArgumentResolver(getMessageConverters(), cs, getValidator()));
resolvers.add(new RequestHeaderMethodArgumentResolver(cs, getBeanFactory()));
resolvers.add(new RequestHeaderMapMethodArgumentResolver());
resolvers.add(new CookieValueMethodArgumentResolver(cs, getBeanFactory()));
resolvers.add(new ExpressionValueMethodArgumentResolver(cs, getBeanFactory()));
resolvers.add(new SessionAttributeMethodArgumentResolver(cs, getBeanFactory()));
resolvers.add(new RequestAttributeMethodArgumentResolver(cs , getBeanFactory()));
// Type-based argument resolution
resolvers.add(new ModelArgumentResolver());
// Custom resolvers
if (getCustomArgumentResolvers() != null) {
resolvers.addAll(getCustomArgumentResolvers());
}
// Catch-all
resolvers.add(new RequestParamMethodArgumentResolver(cs, getBeanFactory(), true));
return resolvers;
}
@Override
public boolean supports(Object handler) {
return HandlerMethod.class.equals(handler.getClass());
}
@Override
public Mono<HandlerResult> handle(ServerWebExchange exchange, Object handler) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
InvocableHandlerMethod invocable = new InvocableHandlerMethod(handlerMethod);
invocable.setHandlerMethodArgumentResolvers(getArgumentResolvers());
ModelMap model = new ExtendedModelMap();
return invocable.invokeForRequest(exchange, model)
.map(result -> result.setExceptionHandler(ex -> handleException(ex, handlerMethod, exchange)))
.otherwise(ex -> handleException(ex, handlerMethod, exchange));
}
private Mono<HandlerResult> handleException(Throwable ex, HandlerMethod handlerMethod,
ServerWebExchange exchange) {
if (ex instanceof Exception) {
InvocableHandlerMethod invocable = findExceptionHandler(handlerMethod, (Exception) ex);
if (invocable != null) {
try {
if (logger.isDebugEnabled()) {
logger.debug("Invoking @ExceptionHandler method: " + invocable);
}
invocable.setHandlerMethodArgumentResolvers(getArgumentResolvers());
ExtendedModelMap errorModel = new ExtendedModelMap();
return invocable.invokeForRequest(exchange, errorModel, ex);
}
catch (Exception invocationEx) {
if (logger.isErrorEnabled()) {
logger.error("Failed to invoke @ExceptionHandler method: " + invocable, invocationEx);
}
}
}
}
return Mono.error(ex);
}
protected InvocableHandlerMethod findExceptionHandler(HandlerMethod handlerMethod, Exception exception) {
if (handlerMethod == null) {
return null;
}
Class<?> handlerType = handlerMethod.getBeanType();
ExceptionHandlerMethodResolver resolver = this.exceptionHandlerCache.get(handlerType);
if (resolver == null) {
resolver = new ExceptionHandlerMethodResolver(handlerType);
this.exceptionHandlerCache.put(handlerType, resolver);
}
Method method = resolver.resolveMethod(exception);
return (method != null ? new InvocableHandlerMethod(handlerMethod.getBean(), method) : null);
}
}