001/*
002 * Copyright 2002-2020 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.web.reactive.result.method.annotation;
018
019import java.util.Collections;
020import java.util.List;
021import java.util.function.Function;
022
023import org.apache.commons.logging.Log;
024import org.apache.commons.logging.LogFactory;
025import reactor.core.publisher.Mono;
026
027import org.springframework.beans.factory.InitializingBean;
028import org.springframework.context.ApplicationContext;
029import org.springframework.context.ApplicationContextAware;
030import org.springframework.context.ConfigurableApplicationContext;
031import org.springframework.core.ReactiveAdapterRegistry;
032import org.springframework.http.codec.HttpMessageReader;
033import org.springframework.http.codec.ServerCodecConfigurer;
034import org.springframework.lang.Nullable;
035import org.springframework.util.Assert;
036import org.springframework.util.CollectionUtils;
037import org.springframework.web.bind.support.WebBindingInitializer;
038import org.springframework.web.method.HandlerMethod;
039import org.springframework.web.reactive.BindingContext;
040import org.springframework.web.reactive.HandlerAdapter;
041import org.springframework.web.reactive.HandlerMapping;
042import org.springframework.web.reactive.HandlerResult;
043import org.springframework.web.reactive.result.method.InvocableHandlerMethod;
044import org.springframework.web.server.ServerWebExchange;
045
046/**
047 * Supports the invocation of
048 * {@link org.springframework.web.bind.annotation.RequestMapping @RequestMapping}
049 * handler methods.
050 *
051 * @author Rossen Stoyanchev
052 * @since 5.0
053 */
054public class RequestMappingHandlerAdapter implements HandlerAdapter, ApplicationContextAware, InitializingBean {
055
056        private static final Log logger = LogFactory.getLog(RequestMappingHandlerAdapter.class);
057
058
059        private List<HttpMessageReader<?>> messageReaders = Collections.emptyList();
060
061        @Nullable
062        private WebBindingInitializer webBindingInitializer;
063
064        @Nullable
065        private ArgumentResolverConfigurer argumentResolverConfigurer;
066
067        @Nullable
068        private ReactiveAdapterRegistry reactiveAdapterRegistry;
069
070        @Nullable
071        private ConfigurableApplicationContext applicationContext;
072
073        @Nullable
074        private ControllerMethodResolver methodResolver;
075
076        @Nullable
077        private ModelInitializer modelInitializer;
078
079
080        /**
081         * Configure HTTP message readers to de-serialize the request body with.
082         * <p>By default this is set to {@link ServerCodecConfigurer}'s readers with defaults.
083         */
084        public void setMessageReaders(List<HttpMessageReader<?>> messageReaders) {
085                Assert.notNull(messageReaders, "'messageReaders' must not be null");
086                this.messageReaders = messageReaders;
087        }
088
089        /**
090         * Return the configurer for HTTP message readers.
091         */
092        public List<HttpMessageReader<?>> getMessageReaders() {
093                return this.messageReaders;
094        }
095
096        /**
097         * Provide a WebBindingInitializer with "global" initialization to apply
098         * to every DataBinder instance.
099         */
100        public void setWebBindingInitializer(@Nullable WebBindingInitializer webBindingInitializer) {
101                this.webBindingInitializer = webBindingInitializer;
102        }
103
104        /**
105         * Return the configured WebBindingInitializer, or {@code null} if none.
106         */
107        @Nullable
108        public WebBindingInitializer getWebBindingInitializer() {
109                return this.webBindingInitializer;
110        }
111
112        /**
113         * Configure resolvers for controller method arguments.
114         */
115        public void setArgumentResolverConfigurer(@Nullable ArgumentResolverConfigurer configurer) {
116                this.argumentResolverConfigurer = configurer;
117        }
118
119        /**
120         * Return the configured resolvers for controller method arguments.
121         */
122        @Nullable
123        public ArgumentResolverConfigurer getArgumentResolverConfigurer() {
124                return this.argumentResolverConfigurer;
125        }
126
127        /**
128         * Configure the registry for adapting various reactive types.
129         * <p>By default this is an instance of {@link ReactiveAdapterRegistry} with
130         * default settings.
131         */
132        public void setReactiveAdapterRegistry(@Nullable ReactiveAdapterRegistry registry) {
133                this.reactiveAdapterRegistry = registry;
134        }
135
136        /**
137         * Return the configured registry for adapting reactive types.
138         */
139        @Nullable
140        public ReactiveAdapterRegistry getReactiveAdapterRegistry() {
141                return this.reactiveAdapterRegistry;
142        }
143
144        /**
145         * A {@link ConfigurableApplicationContext} is expected for resolving
146         * expressions in method argument default values as well as for
147         * detecting {@code @ControllerAdvice} beans.
148         */
149        @Override
150        public void setApplicationContext(ApplicationContext applicationContext) {
151                if (applicationContext instanceof ConfigurableApplicationContext) {
152                        this.applicationContext = (ConfigurableApplicationContext) applicationContext;
153                }
154        }
155
156
157        @Override
158        public void afterPropertiesSet() throws Exception {
159                Assert.notNull(this.applicationContext, "ApplicationContext is required");
160
161                if (CollectionUtils.isEmpty(this.messageReaders)) {
162                        ServerCodecConfigurer codecConfigurer = ServerCodecConfigurer.create();
163                        this.messageReaders = codecConfigurer.getReaders();
164                }
165                if (this.argumentResolverConfigurer == null) {
166                        this.argumentResolverConfigurer = new ArgumentResolverConfigurer();
167                }
168                if (this.reactiveAdapterRegistry == null) {
169                        this.reactiveAdapterRegistry = ReactiveAdapterRegistry.getSharedInstance();
170                }
171
172                this.methodResolver = new ControllerMethodResolver(this.argumentResolverConfigurer,
173                                this.reactiveAdapterRegistry, this.applicationContext, this.messageReaders);
174
175                this.modelInitializer = new ModelInitializer(this.methodResolver, this.reactiveAdapterRegistry);
176        }
177
178
179        @Override
180        public boolean supports(Object handler) {
181                return handler instanceof HandlerMethod;
182        }
183
184        @Override
185        public Mono<HandlerResult> handle(ServerWebExchange exchange, Object handler) {
186                HandlerMethod handlerMethod = (HandlerMethod) handler;
187                Assert.state(this.methodResolver != null && this.modelInitializer != null, "Not initialized");
188
189                InitBinderBindingContext bindingContext = new InitBinderBindingContext(
190                                getWebBindingInitializer(), this.methodResolver.getInitBinderMethods(handlerMethod));
191
192                InvocableHandlerMethod invocableMethod = this.methodResolver.getRequestMappingMethod(handlerMethod);
193
194                Function<Throwable, Mono<HandlerResult>> exceptionHandler =
195                                ex -> handleException(ex, handlerMethod, bindingContext, exchange);
196
197                return this.modelInitializer
198                                .initModel(handlerMethod, bindingContext, exchange)
199                                .then(Mono.defer(() -> invocableMethod.invoke(exchange, bindingContext)))
200                                .doOnNext(result -> result.setExceptionHandler(exceptionHandler))
201                                .doOnNext(result -> bindingContext.saveModel())
202                                .onErrorResume(exceptionHandler);
203        }
204
205        private Mono<HandlerResult> handleException(Throwable exception, HandlerMethod handlerMethod,
206                        BindingContext bindingContext, ServerWebExchange exchange) {
207
208                Assert.state(this.methodResolver != null, "Not initialized");
209
210                // Success and error responses may use different content types
211                exchange.getAttributes().remove(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
212                exchange.getResponse().getHeaders().clearContentHeaders();
213
214                InvocableHandlerMethod invocable = this.methodResolver.getExceptionHandlerMethod(exception, handlerMethod);
215                if (invocable != null) {
216                        try {
217                                if (logger.isDebugEnabled()) {
218                                        logger.debug(exchange.getLogPrefix() + "Using @ExceptionHandler " + invocable);
219                                }
220                                bindingContext.getModel().asMap().clear();
221                                Throwable cause = exception.getCause();
222                                if (cause != null) {
223                                        return invocable.invoke(exchange, bindingContext, exception, cause, handlerMethod);
224                                }
225                                else {
226                                        return invocable.invoke(exchange, bindingContext, exception, handlerMethod);
227                                }
228                        }
229                        catch (Throwable invocationEx) {
230                                if (logger.isWarnEnabled()) {
231                                        logger.warn(exchange.getLogPrefix() + "Failure in @ExceptionHandler " + invocable, invocationEx);
232                                }
233                        }
234                }
235                return Mono.error(exception);
236        }
237
238}