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.config;
018
019import java.util.List;
020import java.util.Map;
021import java.util.function.Predicate;
022
023import reactor.core.publisher.Mono;
024
025import org.springframework.beans.BeanUtils;
026import org.springframework.beans.factory.BeanInitializationException;
027import org.springframework.beans.factory.annotation.Qualifier;
028import org.springframework.context.ApplicationContext;
029import org.springframework.context.ApplicationContextAware;
030import org.springframework.context.annotation.Bean;
031import org.springframework.core.ReactiveAdapterRegistry;
032import org.springframework.core.annotation.Order;
033import org.springframework.core.convert.converter.Converter;
034import org.springframework.core.io.DefaultResourceLoader;
035import org.springframework.core.io.ResourceLoader;
036import org.springframework.format.Formatter;
037import org.springframework.format.FormatterRegistry;
038import org.springframework.format.support.DefaultFormattingConversionService;
039import org.springframework.format.support.FormattingConversionService;
040import org.springframework.http.codec.ServerCodecConfigurer;
041import org.springframework.lang.Nullable;
042import org.springframework.util.Assert;
043import org.springframework.util.ClassUtils;
044import org.springframework.validation.Errors;
045import org.springframework.validation.MessageCodesResolver;
046import org.springframework.validation.Validator;
047import org.springframework.web.bind.WebDataBinder;
048import org.springframework.web.bind.support.ConfigurableWebBindingInitializer;
049import org.springframework.web.cors.CorsConfiguration;
050import org.springframework.web.reactive.DispatcherHandler;
051import org.springframework.web.reactive.HandlerMapping;
052import org.springframework.web.reactive.accept.RequestedContentTypeResolver;
053import org.springframework.web.reactive.accept.RequestedContentTypeResolverBuilder;
054import org.springframework.web.reactive.function.server.support.HandlerFunctionAdapter;
055import org.springframework.web.reactive.function.server.support.RouterFunctionMapping;
056import org.springframework.web.reactive.function.server.support.ServerResponseResultHandler;
057import org.springframework.web.reactive.handler.AbstractHandlerMapping;
058import org.springframework.web.reactive.handler.WebFluxResponseStatusExceptionHandler;
059import org.springframework.web.reactive.resource.ResourceUrlProvider;
060import org.springframework.web.reactive.result.SimpleHandlerAdapter;
061import org.springframework.web.reactive.result.method.annotation.ArgumentResolverConfigurer;
062import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerAdapter;
063import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerMapping;
064import org.springframework.web.reactive.result.method.annotation.ResponseBodyResultHandler;
065import org.springframework.web.reactive.result.method.annotation.ResponseEntityResultHandler;
066import org.springframework.web.reactive.result.view.ViewResolutionResultHandler;
067import org.springframework.web.reactive.result.view.ViewResolver;
068import org.springframework.web.server.ServerWebExchange;
069import org.springframework.web.server.WebExceptionHandler;
070import org.springframework.web.server.i18n.AcceptHeaderLocaleContextResolver;
071import org.springframework.web.server.i18n.LocaleContextResolver;
072
073/**
074 * The main class for Spring WebFlux configuration.
075 *
076 * <p>Import directly or extend and override protected methods to customize.
077 *
078 * @author Rossen Stoyanchev
079 * @author Brian Clozel
080 * @since 5.0
081 */
082public class WebFluxConfigurationSupport implements ApplicationContextAware {
083
084        @Nullable
085        private Map<String, CorsConfiguration> corsConfigurations;
086
087        @Nullable
088        private PathMatchConfigurer pathMatchConfigurer;
089
090        @Nullable
091        private ViewResolverRegistry viewResolverRegistry;
092
093        @Nullable
094        private ApplicationContext applicationContext;
095
096
097        @Override
098        public void setApplicationContext(@Nullable ApplicationContext applicationContext) {
099                this.applicationContext = applicationContext;
100                if (applicationContext != null) {
101                                Assert.state(!applicationContext.containsBean("mvcContentNegotiationManager"),
102                                                "The Java/XML config for Spring MVC and Spring WebFlux cannot both be enabled, " +
103                                                "e.g. via @EnableWebMvc and @EnableWebFlux, in the same application.");
104                }
105        }
106
107        @Nullable
108        public final ApplicationContext getApplicationContext() {
109                return this.applicationContext;
110        }
111
112
113        @Bean
114        public DispatcherHandler webHandler() {
115                return new DispatcherHandler();
116        }
117
118        @Bean
119        @Order(0)
120        public WebExceptionHandler responseStatusExceptionHandler() {
121                return new WebFluxResponseStatusExceptionHandler();
122        }
123
124        @Bean
125        public RequestMappingHandlerMapping requestMappingHandlerMapping(
126                        @Qualifier("webFluxContentTypeResolver") RequestedContentTypeResolver contentTypeResolver) {
127
128                RequestMappingHandlerMapping mapping = createRequestMappingHandlerMapping();
129                mapping.setOrder(0);
130                mapping.setContentTypeResolver(contentTypeResolver);
131                mapping.setCorsConfigurations(getCorsConfigurations());
132
133                PathMatchConfigurer configurer = getPathMatchConfigurer();
134                Boolean useTrailingSlashMatch = configurer.isUseTrailingSlashMatch();
135                if (useTrailingSlashMatch != null) {
136                        mapping.setUseTrailingSlashMatch(useTrailingSlashMatch);
137                }
138                Boolean useCaseSensitiveMatch = configurer.isUseCaseSensitiveMatch();
139                if (useCaseSensitiveMatch != null) {
140                        mapping.setUseCaseSensitiveMatch(useCaseSensitiveMatch);
141                }
142                Map<String, Predicate<Class<?>>> pathPrefixes = configurer.getPathPrefixes();
143                if (pathPrefixes != null) {
144                        mapping.setPathPrefixes(pathPrefixes);
145                }
146
147                return mapping;
148        }
149
150        /**
151         * Override to plug a sub-class of {@link RequestMappingHandlerMapping}.
152         */
153        protected RequestMappingHandlerMapping createRequestMappingHandlerMapping() {
154                return new RequestMappingHandlerMapping();
155        }
156
157        @Bean
158        public RequestedContentTypeResolver webFluxContentTypeResolver() {
159                RequestedContentTypeResolverBuilder builder = new RequestedContentTypeResolverBuilder();
160                configureContentTypeResolver(builder);
161                return builder.build();
162        }
163
164        /**
165         * Override to configure how the requested content type is resolved.
166         */
167        protected void configureContentTypeResolver(RequestedContentTypeResolverBuilder builder) {
168        }
169
170        /**
171         * Callback for building the global CORS configuration. This method is final.
172         * Use {@link #addCorsMappings(CorsRegistry)} to customize the CORS conifg.
173         */
174        protected final Map<String, CorsConfiguration> getCorsConfigurations() {
175                if (this.corsConfigurations == null) {
176                        CorsRegistry registry = new CorsRegistry();
177                        addCorsMappings(registry);
178                        this.corsConfigurations = registry.getCorsConfigurations();
179                }
180                return this.corsConfigurations;
181        }
182
183        /**
184         * Override this method to configure cross origin requests processing.
185         * @see CorsRegistry
186         */
187        protected void addCorsMappings(CorsRegistry registry) {
188        }
189
190        /**
191         * Callback for building the {@link PathMatchConfigurer}. This method is
192         * final, use {@link #configurePathMatching} to customize path matching.
193         */
194        protected final PathMatchConfigurer getPathMatchConfigurer() {
195                if (this.pathMatchConfigurer == null) {
196                        this.pathMatchConfigurer = new PathMatchConfigurer();
197                        configurePathMatching(this.pathMatchConfigurer);
198                }
199                return this.pathMatchConfigurer;
200        }
201
202        /**
203         * Override to configure path matching options.
204         */
205        public void configurePathMatching(PathMatchConfigurer configurer) {
206        }
207
208        @Bean
209        public RouterFunctionMapping routerFunctionMapping(ServerCodecConfigurer serverCodecConfigurer) {
210                RouterFunctionMapping mapping = createRouterFunctionMapping();
211                mapping.setOrder(-1);  // go before RequestMappingHandlerMapping
212                mapping.setMessageReaders(serverCodecConfigurer.getReaders());
213                mapping.setCorsConfigurations(getCorsConfigurations());
214
215                return mapping;
216        }
217
218        /**
219         * Override to plug a sub-class of {@link RouterFunctionMapping}.
220         */
221        protected RouterFunctionMapping createRouterFunctionMapping() {
222                return new RouterFunctionMapping();
223        }
224
225        /**
226         * Return a handler mapping ordered at Integer.MAX_VALUE-1 with mapped
227         * resource handlers. To configure resource handling, override
228         * {@link #addResourceHandlers}.
229         */
230        @Bean
231        public HandlerMapping resourceHandlerMapping(ResourceUrlProvider resourceUrlProvider) {
232                ResourceLoader resourceLoader = this.applicationContext;
233                if (resourceLoader == null) {
234                        resourceLoader = new DefaultResourceLoader();
235                }
236                ResourceHandlerRegistry registry = new ResourceHandlerRegistry(resourceLoader);
237                registry.setResourceUrlProvider(resourceUrlProvider);
238                addResourceHandlers(registry);
239
240                AbstractHandlerMapping handlerMapping = registry.getHandlerMapping();
241                if (handlerMapping != null) {
242                        PathMatchConfigurer configurer = getPathMatchConfigurer();
243                        Boolean useTrailingSlashMatch = configurer.isUseTrailingSlashMatch();
244                        Boolean useCaseSensitiveMatch = configurer.isUseCaseSensitiveMatch();
245                        if (useTrailingSlashMatch != null) {
246                                handlerMapping.setUseTrailingSlashMatch(useTrailingSlashMatch);
247                        }
248                        if (useCaseSensitiveMatch != null) {
249                                handlerMapping.setUseCaseSensitiveMatch(useCaseSensitiveMatch);
250                        }
251                }
252                else {
253                        handlerMapping = new EmptyHandlerMapping();
254                }
255                return handlerMapping;
256        }
257
258        @Bean
259        public ResourceUrlProvider resourceUrlProvider() {
260                return new ResourceUrlProvider();
261        }
262
263        /**
264         * Override this method to add resource handlers for serving static resources.
265         * @see ResourceHandlerRegistry
266         */
267        protected void addResourceHandlers(ResourceHandlerRegistry registry) {
268        }
269
270        @Bean
271        public RequestMappingHandlerAdapter requestMappingHandlerAdapter(
272                        @Qualifier("webFluxAdapterRegistry") ReactiveAdapterRegistry reactiveAdapterRegistry,
273                        ServerCodecConfigurer serverCodecConfigurer,
274                        @Qualifier("webFluxConversionService") FormattingConversionService conversionService,
275                        @Qualifier("webFluxValidator") Validator validator) {
276
277                RequestMappingHandlerAdapter adapter = createRequestMappingHandlerAdapter();
278                adapter.setMessageReaders(serverCodecConfigurer.getReaders());
279                adapter.setWebBindingInitializer(getConfigurableWebBindingInitializer(conversionService, validator));
280                adapter.setReactiveAdapterRegistry(reactiveAdapterRegistry);
281
282                ArgumentResolverConfigurer configurer = new ArgumentResolverConfigurer();
283                configureArgumentResolvers(configurer);
284                adapter.setArgumentResolverConfigurer(configurer);
285
286                return adapter;
287        }
288
289        /**
290         * Override to plug a sub-class of {@link RequestMappingHandlerAdapter}.
291         */
292        protected RequestMappingHandlerAdapter createRequestMappingHandlerAdapter() {
293                return new RequestMappingHandlerAdapter();
294        }
295
296        /**
297         * Configure resolvers for custom controller method arguments.
298         */
299        protected void configureArgumentResolvers(ArgumentResolverConfigurer configurer) {
300        }
301
302        /**
303         * Return the configurer for HTTP message readers and writers.
304         * <p>Use {@link #configureHttpMessageCodecs(ServerCodecConfigurer)} to
305         * configure the readers and writers.
306         */
307        @Bean
308        public ServerCodecConfigurer serverCodecConfigurer() {
309                ServerCodecConfigurer serverCodecConfigurer = ServerCodecConfigurer.create();
310                configureHttpMessageCodecs(serverCodecConfigurer);
311                return serverCodecConfigurer;
312        }
313
314        /**
315         * Override to plug a sub-class of {@link LocaleContextResolver}.
316         */
317        protected LocaleContextResolver createLocaleContextResolver() {
318                return new AcceptHeaderLocaleContextResolver();
319        }
320
321        @Bean
322        public LocaleContextResolver localeContextResolver() {
323                return createLocaleContextResolver();
324        }
325
326        /**
327         * Override to configure the HTTP message readers and writers to use.
328         */
329        protected void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {
330        }
331
332        /**
333         * Return the {@link ConfigurableWebBindingInitializer} to use for
334         * initializing all {@link WebDataBinder} instances.
335         */
336        protected ConfigurableWebBindingInitializer getConfigurableWebBindingInitializer(
337                        FormattingConversionService webFluxConversionService, Validator webFluxValidator) {
338
339                ConfigurableWebBindingInitializer initializer = new ConfigurableWebBindingInitializer();
340                initializer.setConversionService(webFluxConversionService);
341                initializer.setValidator(webFluxValidator);
342                MessageCodesResolver messageCodesResolver = getMessageCodesResolver();
343                if (messageCodesResolver != null) {
344                        initializer.setMessageCodesResolver(messageCodesResolver);
345                }
346                return initializer;
347        }
348
349        /**
350         * Return a {@link FormattingConversionService} for use with annotated controllers.
351         * <p>See {@link #addFormatters} as an alternative to overriding this method.
352         */
353        @Bean
354        public FormattingConversionService webFluxConversionService() {
355                FormattingConversionService service = new DefaultFormattingConversionService();
356                addFormatters(service);
357                return service;
358        }
359
360        /**
361         * Override this method to add custom {@link Converter} and/or {@link Formatter}
362         * delegates to the common {@link FormattingConversionService}.
363         * @see #webFluxConversionService()
364         */
365        protected void addFormatters(FormatterRegistry registry) {
366        }
367
368        /**
369         * Return a {@link ReactiveAdapterRegistry} to adapting reactive types.
370         */
371        @Bean
372        public ReactiveAdapterRegistry webFluxAdapterRegistry() {
373                return new ReactiveAdapterRegistry();
374        }
375
376        /**
377         * Return a global {@link Validator} instance for example for validating
378         * {@code @RequestBody} method arguments.
379         * <p>Delegates to {@link #getValidator()} first. If that returns {@code null}
380         * checks the classpath for the presence of a JSR-303 implementations
381         * before creating a {@code OptionalValidatorFactoryBean}. If a JSR-303
382         * implementation is not available, a "no-op" {@link Validator} is returned.
383         */
384        @Bean
385        public Validator webFluxValidator() {
386                Validator validator = getValidator();
387                if (validator == null) {
388                        if (ClassUtils.isPresent("javax.validation.Validator", getClass().getClassLoader())) {
389                                Class<?> clazz;
390                                try {
391                                        String name = "org.springframework.validation.beanvalidation.OptionalValidatorFactoryBean";
392                                        clazz = ClassUtils.forName(name, getClass().getClassLoader());
393                                }
394                                catch (ClassNotFoundException | LinkageError ex) {
395                                        throw new BeanInitializationException("Failed to resolve default validator class", ex);
396                                }
397                                validator = (Validator) BeanUtils.instantiateClass(clazz);
398                        }
399                        else {
400                                validator = new NoOpValidator();
401                        }
402                }
403                return validator;
404        }
405
406        /**
407         * Override this method to provide a custom {@link Validator}.
408         */
409        @Nullable
410        protected Validator getValidator() {
411                return null;
412        }
413
414        /**
415         * Override this method to provide a custom {@link MessageCodesResolver}.
416         */
417        @Nullable
418        protected MessageCodesResolver getMessageCodesResolver() {
419                return null;
420        }
421
422        @Bean
423        public HandlerFunctionAdapter handlerFunctionAdapter() {
424                return new HandlerFunctionAdapter();
425        }
426
427        @Bean
428        public SimpleHandlerAdapter simpleHandlerAdapter() {
429                return new SimpleHandlerAdapter();
430        }
431
432        @Bean
433        public ResponseEntityResultHandler responseEntityResultHandler(
434                        @Qualifier("webFluxAdapterRegistry") ReactiveAdapterRegistry reactiveAdapterRegistry,
435                        ServerCodecConfigurer serverCodecConfigurer,
436                        @Qualifier("webFluxContentTypeResolver") RequestedContentTypeResolver contentTypeResolver) {
437
438                return new ResponseEntityResultHandler(serverCodecConfigurer.getWriters(),
439                                contentTypeResolver, reactiveAdapterRegistry);
440        }
441
442        @Bean
443        public ResponseBodyResultHandler responseBodyResultHandler(
444                        @Qualifier("webFluxAdapterRegistry") ReactiveAdapterRegistry reactiveAdapterRegistry,
445                        ServerCodecConfigurer serverCodecConfigurer,
446                        @Qualifier("webFluxContentTypeResolver") RequestedContentTypeResolver contentTypeResolver) {
447
448                return new ResponseBodyResultHandler(serverCodecConfigurer.getWriters(),
449                                contentTypeResolver, reactiveAdapterRegistry);
450        }
451
452        @Bean
453        public ViewResolutionResultHandler viewResolutionResultHandler(
454                        @Qualifier("webFluxAdapterRegistry") ReactiveAdapterRegistry reactiveAdapterRegistry,
455                        @Qualifier("webFluxContentTypeResolver") RequestedContentTypeResolver contentTypeResolver) {
456
457                ViewResolverRegistry registry = getViewResolverRegistry();
458                List<ViewResolver> resolvers = registry.getViewResolvers();
459                ViewResolutionResultHandler handler = new ViewResolutionResultHandler(
460                                resolvers, contentTypeResolver, reactiveAdapterRegistry);
461                handler.setDefaultViews(registry.getDefaultViews());
462                handler.setOrder(registry.getOrder());
463                return handler;
464        }
465
466        @Bean
467        public ServerResponseResultHandler serverResponseResultHandler(ServerCodecConfigurer serverCodecConfigurer) {
468                List<ViewResolver> resolvers = getViewResolverRegistry().getViewResolvers();
469                ServerResponseResultHandler handler = new ServerResponseResultHandler();
470                handler.setMessageWriters(serverCodecConfigurer.getWriters());
471                handler.setViewResolvers(resolvers);
472                return handler;
473        }
474
475        /**
476         * Callback for building the {@link ViewResolverRegistry}. This method is final,
477         * use {@link #configureViewResolvers} to customize view resolvers.
478         */
479        protected final ViewResolverRegistry getViewResolverRegistry() {
480                if (this.viewResolverRegistry == null) {
481                        this.viewResolverRegistry = new ViewResolverRegistry(this.applicationContext);
482                        configureViewResolvers(this.viewResolverRegistry);
483                }
484                return this.viewResolverRegistry;
485        }
486
487        /**
488         * Configure view resolution for supporting template engines.
489         * @see ViewResolverRegistry
490         */
491        protected void configureViewResolvers(ViewResolverRegistry registry) {
492        }
493
494
495        private static final class EmptyHandlerMapping extends AbstractHandlerMapping {
496
497                @Override
498                public Mono<Object> getHandlerInternal(ServerWebExchange exchange) {
499                        return Mono.empty();
500                }
501        }
502
503
504        private static final class NoOpValidator implements Validator {
505
506                @Override
507                public boolean supports(Class<?> clazz) {
508                        return false;
509                }
510
511                @Override
512                public void validate(@Nullable Object target, Errors errors) {
513                }
514        }
515
516}