001/*
002 * Copyright 2012-2018 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 *      http://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.boot.autoconfigure.web.reactive;
018
019import java.time.Duration;
020import java.util.Collection;
021
022import org.apache.commons.logging.Log;
023import org.apache.commons.logging.LogFactory;
024
025import org.springframework.beans.factory.ListableBeanFactory;
026import org.springframework.beans.factory.ObjectProvider;
027import org.springframework.boot.autoconfigure.AutoConfigureAfter;
028import org.springframework.boot.autoconfigure.AutoConfigureOrder;
029import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
030import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
031import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
032import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
033import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
034import org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration;
035import org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration;
036import org.springframework.boot.autoconfigure.validation.ValidatorAdapter;
037import org.springframework.boot.autoconfigure.web.ConditionalOnEnabledResourceChain;
038import org.springframework.boot.autoconfigure.web.ResourceProperties;
039import org.springframework.boot.autoconfigure.web.format.WebConversionService;
040import org.springframework.boot.context.properties.EnableConfigurationProperties;
041import org.springframework.boot.web.codec.CodecCustomizer;
042import org.springframework.boot.web.reactive.filter.OrderedHiddenHttpMethodFilter;
043import org.springframework.context.annotation.Bean;
044import org.springframework.context.annotation.Configuration;
045import org.springframework.context.annotation.Import;
046import org.springframework.core.Ordered;
047import org.springframework.core.convert.converter.Converter;
048import org.springframework.core.convert.converter.GenericConverter;
049import org.springframework.format.Formatter;
050import org.springframework.format.FormatterRegistry;
051import org.springframework.format.support.FormattingConversionService;
052import org.springframework.http.codec.ServerCodecConfigurer;
053import org.springframework.util.ClassUtils;
054import org.springframework.validation.Validator;
055import org.springframework.web.filter.reactive.HiddenHttpMethodFilter;
056import org.springframework.web.reactive.config.DelegatingWebFluxConfiguration;
057import org.springframework.web.reactive.config.EnableWebFlux;
058import org.springframework.web.reactive.config.ResourceHandlerRegistration;
059import org.springframework.web.reactive.config.ResourceHandlerRegistry;
060import org.springframework.web.reactive.config.ViewResolverRegistry;
061import org.springframework.web.reactive.config.WebFluxConfigurationSupport;
062import org.springframework.web.reactive.config.WebFluxConfigurer;
063import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver;
064import org.springframework.web.reactive.result.method.annotation.ArgumentResolverConfigurer;
065import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerAdapter;
066import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerMapping;
067import org.springframework.web.reactive.result.view.ViewResolver;
068
069/**
070 * {@link EnableAutoConfiguration Auto-configuration} for {@link EnableWebFlux WebFlux}.
071 *
072 * @author Brian Clozel
073 * @author Rob Winch
074 * @author Stephane Nicoll
075 * @author Andy Wilkinson
076 * @author Phillip Webb
077 * @author EddĂș MelĂ©ndez
078 * @author Artsiom Yudovin
079 * @since 2.0.0
080 */
081@Configuration
082@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
083@ConditionalOnClass(WebFluxConfigurer.class)
084@ConditionalOnMissingBean({ WebFluxConfigurationSupport.class })
085@AutoConfigureAfter({ ReactiveWebServerFactoryAutoConfiguration.class,
086                CodecsAutoConfiguration.class, ValidationAutoConfiguration.class })
087@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
088public class WebFluxAutoConfiguration {
089
090        @Bean
091        @ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
092        @ConditionalOnProperty(prefix = "spring.webflux.hiddenmethod.filter", name = "enabled", matchIfMissing = true)
093        public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
094                return new OrderedHiddenHttpMethodFilter();
095        }
096
097        @Configuration
098        @EnableConfigurationProperties({ ResourceProperties.class, WebFluxProperties.class })
099        @Import({ EnableWebFluxConfiguration.class })
100        public static class WebFluxConfig implements WebFluxConfigurer {
101
102                private static final Log logger = LogFactory.getLog(WebFluxConfig.class);
103
104                private final ResourceProperties resourceProperties;
105
106                private final WebFluxProperties webFluxProperties;
107
108                private final ListableBeanFactory beanFactory;
109
110                private final ObjectProvider<HandlerMethodArgumentResolver> argumentResolvers;
111
112                private final ObjectProvider<CodecCustomizer> codecCustomizers;
113
114                private final ResourceHandlerRegistrationCustomizer resourceHandlerRegistrationCustomizer;
115
116                private final ObjectProvider<ViewResolver> viewResolvers;
117
118                public WebFluxConfig(ResourceProperties resourceProperties,
119                                WebFluxProperties webFluxProperties, ListableBeanFactory beanFactory,
120                                ObjectProvider<HandlerMethodArgumentResolver> resolvers,
121                                ObjectProvider<CodecCustomizer> codecCustomizers,
122                                ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizer,
123                                ObjectProvider<ViewResolver> viewResolvers) {
124                        this.resourceProperties = resourceProperties;
125                        this.webFluxProperties = webFluxProperties;
126                        this.beanFactory = beanFactory;
127                        this.argumentResolvers = resolvers;
128                        this.codecCustomizers = codecCustomizers;
129                        this.resourceHandlerRegistrationCustomizer = resourceHandlerRegistrationCustomizer
130                                        .getIfAvailable();
131                        this.viewResolvers = viewResolvers;
132                }
133
134                @Override
135                public void configureArgumentResolvers(ArgumentResolverConfigurer configurer) {
136                        this.argumentResolvers.orderedStream().forEach(configurer::addCustomResolver);
137                }
138
139                @Override
140                public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {
141                        this.codecCustomizers.orderedStream()
142                                        .forEach((customizer) -> customizer.customize(configurer));
143                }
144
145                @Override
146                public void addResourceHandlers(ResourceHandlerRegistry registry) {
147                        if (!this.resourceProperties.isAddMappings()) {
148                                logger.debug("Default resource handling disabled");
149                                return;
150                        }
151                        if (!registry.hasMappingForPattern("/webjars/**")) {
152                                ResourceHandlerRegistration registration = registry
153                                                .addResourceHandler("/webjars/**")
154                                                .addResourceLocations("classpath:/META-INF/resources/webjars/");
155                                configureResourceCaching(registration);
156                                customizeResourceHandlerRegistration(registration);
157                        }
158                        String staticPathPattern = this.webFluxProperties.getStaticPathPattern();
159                        if (!registry.hasMappingForPattern(staticPathPattern)) {
160                                ResourceHandlerRegistration registration = registry
161                                                .addResourceHandler(staticPathPattern).addResourceLocations(
162                                                                this.resourceProperties.getStaticLocations());
163                                configureResourceCaching(registration);
164                                customizeResourceHandlerRegistration(registration);
165                        }
166                }
167
168                private void configureResourceCaching(ResourceHandlerRegistration registration) {
169                        Duration cachePeriod = this.resourceProperties.getCache().getPeriod();
170                        ResourceProperties.Cache.Cachecontrol cacheControl = this.resourceProperties
171                                        .getCache().getCachecontrol();
172                        if (cachePeriod != null && cacheControl.getMaxAge() == null) {
173                                cacheControl.setMaxAge(cachePeriod);
174                        }
175                        registration.setCacheControl(cacheControl.toHttpCacheControl());
176                }
177
178                @Override
179                public void configureViewResolvers(ViewResolverRegistry registry) {
180                        this.viewResolvers.orderedStream().forEach(registry::viewResolver);
181                }
182
183                @Override
184                public void addFormatters(FormatterRegistry registry) {
185                        for (Converter<?, ?> converter : getBeansOfType(Converter.class)) {
186                                registry.addConverter(converter);
187                        }
188                        for (GenericConverter converter : getBeansOfType(GenericConverter.class)) {
189                                registry.addConverter(converter);
190                        }
191                        for (Formatter<?> formatter : getBeansOfType(Formatter.class)) {
192                                registry.addFormatter(formatter);
193                        }
194                }
195
196                private <T> Collection<T> getBeansOfType(Class<T> type) {
197                        return this.beanFactory.getBeansOfType(type).values();
198                }
199
200                private void customizeResourceHandlerRegistration(
201                                ResourceHandlerRegistration registration) {
202                        if (this.resourceHandlerRegistrationCustomizer != null) {
203                                this.resourceHandlerRegistrationCustomizer.customize(registration);
204                        }
205
206                }
207
208        }
209
210        /**
211         * Configuration equivalent to {@code @EnableWebFlux}.
212         */
213        @Configuration
214        public static class EnableWebFluxConfiguration
215                        extends DelegatingWebFluxConfiguration {
216
217                private final WebFluxProperties webFluxProperties;
218
219                private final WebFluxRegistrations webFluxRegistrations;
220
221                public EnableWebFluxConfiguration(WebFluxProperties webFluxProperties,
222                                ObjectProvider<WebFluxRegistrations> webFluxRegistrations) {
223                        this.webFluxProperties = webFluxProperties;
224                        this.webFluxRegistrations = webFluxRegistrations.getIfUnique();
225                }
226
227                @Bean
228                @Override
229                public FormattingConversionService webFluxConversionService() {
230                        WebConversionService conversionService = new WebConversionService(
231                                        this.webFluxProperties.getDateFormat());
232                        addFormatters(conversionService);
233                        return conversionService;
234                }
235
236                @Bean
237                @Override
238                public Validator webFluxValidator() {
239                        if (!ClassUtils.isPresent("javax.validation.Validator",
240                                        getClass().getClassLoader())) {
241                                return super.webFluxValidator();
242                        }
243                        return ValidatorAdapter.get(getApplicationContext(), getValidator());
244                }
245
246                @Override
247                protected RequestMappingHandlerAdapter createRequestMappingHandlerAdapter() {
248                        if (this.webFluxRegistrations != null && this.webFluxRegistrations
249                                        .getRequestMappingHandlerAdapter() != null) {
250                                return this.webFluxRegistrations.getRequestMappingHandlerAdapter();
251                        }
252                        return super.createRequestMappingHandlerAdapter();
253                }
254
255                @Override
256                protected RequestMappingHandlerMapping createRequestMappingHandlerMapping() {
257                        if (this.webFluxRegistrations != null && this.webFluxRegistrations
258                                        .getRequestMappingHandlerMapping() != null) {
259                                return this.webFluxRegistrations.getRequestMappingHandlerMapping();
260                        }
261                        return super.createRequestMappingHandlerMapping();
262                }
263
264        }
265
266        @Configuration
267        @ConditionalOnEnabledResourceChain
268        static class ResourceChainCustomizerConfiguration {
269
270                @Bean
271                public ResourceChainResourceHandlerRegistrationCustomizer resourceHandlerRegistrationCustomizer() {
272                        return new ResourceChainResourceHandlerRegistrationCustomizer();
273                }
274
275        }
276
277}