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.servlet;
018
019import java.util.Arrays;
020import java.util.List;
021
022import javax.servlet.MultipartConfigElement;
023import javax.servlet.ServletRegistration;
024
025import org.springframework.beans.factory.ObjectProvider;
026import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
027import org.springframework.boot.autoconfigure.AutoConfigureAfter;
028import org.springframework.boot.autoconfigure.AutoConfigureOrder;
029import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
030import org.springframework.boot.autoconfigure.condition.ConditionMessage;
031import org.springframework.boot.autoconfigure.condition.ConditionMessage.Style;
032import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
033import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
034import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
035import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
036import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
037import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
038import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
039import org.springframework.boot.autoconfigure.http.HttpProperties;
040import org.springframework.boot.context.properties.EnableConfigurationProperties;
041import org.springframework.boot.web.servlet.ServletRegistrationBean;
042import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
043import org.springframework.context.annotation.Bean;
044import org.springframework.context.annotation.ConditionContext;
045import org.springframework.context.annotation.Conditional;
046import org.springframework.context.annotation.Configuration;
047import org.springframework.context.annotation.Import;
048import org.springframework.core.Ordered;
049import org.springframework.core.annotation.Order;
050import org.springframework.core.type.AnnotatedTypeMetadata;
051import org.springframework.web.multipart.MultipartResolver;
052import org.springframework.web.servlet.DispatcherServlet;
053
054/**
055 * {@link EnableAutoConfiguration Auto-configuration} for the Spring
056 * {@link DispatcherServlet}. Should work for a standalone application where an embedded
057 * web server is already present and also for a deployable application using
058 * {@link SpringBootServletInitializer}.
059 *
060 * @author Phillip Webb
061 * @author Dave Syer
062 * @author Stephane Nicoll
063 * @author Brian Clozel
064 */
065@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
066@Configuration
067@ConditionalOnWebApplication(type = Type.SERVLET)
068@ConditionalOnClass(DispatcherServlet.class)
069@AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class)
070public class DispatcherServletAutoConfiguration {
071
072        /*
073         * The bean name for a DispatcherServlet that will be mapped to the root URL "/"
074         */
075        public static final String DEFAULT_DISPATCHER_SERVLET_BEAN_NAME = "dispatcherServlet";
076
077        /*
078         * The bean name for a ServletRegistrationBean for the DispatcherServlet "/"
079         */
080        public static final String DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME = "dispatcherServletRegistration";
081
082        @Configuration
083        @Conditional(DefaultDispatcherServletCondition.class)
084        @ConditionalOnClass(ServletRegistration.class)
085        @EnableConfigurationProperties({ HttpProperties.class, WebMvcProperties.class })
086        protected static class DispatcherServletConfiguration {
087
088                private final HttpProperties httpProperties;
089
090                private final WebMvcProperties webMvcProperties;
091
092                public DispatcherServletConfiguration(HttpProperties httpProperties,
093                                WebMvcProperties webMvcProperties) {
094                        this.httpProperties = httpProperties;
095                        this.webMvcProperties = webMvcProperties;
096                }
097
098                @Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
099                public DispatcherServlet dispatcherServlet() {
100                        DispatcherServlet dispatcherServlet = new DispatcherServlet();
101                        dispatcherServlet.setDispatchOptionsRequest(
102                                        this.webMvcProperties.isDispatchOptionsRequest());
103                        dispatcherServlet.setDispatchTraceRequest(
104                                        this.webMvcProperties.isDispatchTraceRequest());
105                        dispatcherServlet.setThrowExceptionIfNoHandlerFound(
106                                        this.webMvcProperties.isThrowExceptionIfNoHandlerFound());
107                        dispatcherServlet.setEnableLoggingRequestDetails(
108                                        this.httpProperties.isLogRequestDetails());
109                        return dispatcherServlet;
110                }
111
112                @Bean
113                @ConditionalOnBean(MultipartResolver.class)
114                @ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)
115                public MultipartResolver multipartResolver(MultipartResolver resolver) {
116                        // Detect if the user has created a MultipartResolver but named it incorrectly
117                        return resolver;
118                }
119
120        }
121
122        @Configuration
123        @Conditional(DispatcherServletRegistrationCondition.class)
124        @ConditionalOnClass(ServletRegistration.class)
125        @EnableConfigurationProperties(WebMvcProperties.class)
126        @Import(DispatcherServletConfiguration.class)
127        protected static class DispatcherServletRegistrationConfiguration {
128
129                private final WebMvcProperties webMvcProperties;
130
131                private final MultipartConfigElement multipartConfig;
132
133                public DispatcherServletRegistrationConfiguration(
134                                WebMvcProperties webMvcProperties,
135                                ObjectProvider<MultipartConfigElement> multipartConfigProvider) {
136                        this.webMvcProperties = webMvcProperties;
137                        this.multipartConfig = multipartConfigProvider.getIfAvailable();
138                }
139
140                @Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
141                @ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
142                public DispatcherServletRegistrationBean dispatcherServletRegistration(
143                                DispatcherServlet dispatcherServlet) {
144                        DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(
145                                        dispatcherServlet, this.webMvcProperties.getServlet().getPath());
146                        registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
147                        registration.setLoadOnStartup(
148                                        this.webMvcProperties.getServlet().getLoadOnStartup());
149                        if (this.multipartConfig != null) {
150                                registration.setMultipartConfig(this.multipartConfig);
151                        }
152                        return registration;
153                }
154
155        }
156
157        @Order(Ordered.LOWEST_PRECEDENCE - 10)
158        private static class DefaultDispatcherServletCondition extends SpringBootCondition {
159
160                @Override
161                public ConditionOutcome getMatchOutcome(ConditionContext context,
162                                AnnotatedTypeMetadata metadata) {
163                        ConditionMessage.Builder message = ConditionMessage
164                                        .forCondition("Default DispatcherServlet");
165                        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
166                        List<String> dispatchServletBeans = Arrays.asList(beanFactory
167                                        .getBeanNamesForType(DispatcherServlet.class, false, false));
168                        if (dispatchServletBeans.contains(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)) {
169                                return ConditionOutcome.noMatch(message.found("dispatcher servlet bean")
170                                                .items(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME));
171                        }
172                        if (beanFactory.containsBean(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)) {
173                                return ConditionOutcome
174                                                .noMatch(message.found("non dispatcher servlet bean")
175                                                                .items(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME));
176                        }
177                        if (dispatchServletBeans.isEmpty()) {
178                                return ConditionOutcome
179                                                .match(message.didNotFind("dispatcher servlet beans").atAll());
180                        }
181                        return ConditionOutcome.match(message
182                                        .found("dispatcher servlet bean", "dispatcher servlet beans")
183                                        .items(Style.QUOTE, dispatchServletBeans)
184                                        .append("and none is named " + DEFAULT_DISPATCHER_SERVLET_BEAN_NAME));
185                }
186
187        }
188
189        @Order(Ordered.LOWEST_PRECEDENCE - 10)
190        private static class DispatcherServletRegistrationCondition
191                        extends SpringBootCondition {
192
193                @Override
194                public ConditionOutcome getMatchOutcome(ConditionContext context,
195                                AnnotatedTypeMetadata metadata) {
196                        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
197                        ConditionOutcome outcome = checkDefaultDispatcherName(beanFactory);
198                        if (!outcome.isMatch()) {
199                                return outcome;
200                        }
201                        return checkServletRegistration(beanFactory);
202                }
203
204                private ConditionOutcome checkDefaultDispatcherName(
205                                ConfigurableListableBeanFactory beanFactory) {
206                        List<String> servlets = Arrays.asList(beanFactory
207                                        .getBeanNamesForType(DispatcherServlet.class, false, false));
208                        boolean containsDispatcherBean = beanFactory
209                                        .containsBean(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
210                        if (containsDispatcherBean
211                                        && !servlets.contains(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)) {
212                                return ConditionOutcome
213                                                .noMatch(startMessage().found("non dispatcher servlet")
214                                                                .items(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME));
215                        }
216                        return ConditionOutcome.match();
217                }
218
219                private ConditionOutcome checkServletRegistration(
220                                ConfigurableListableBeanFactory beanFactory) {
221                        ConditionMessage.Builder message = startMessage();
222                        List<String> registrations = Arrays.asList(beanFactory
223                                        .getBeanNamesForType(ServletRegistrationBean.class, false, false));
224                        boolean containsDispatcherRegistrationBean = beanFactory
225                                        .containsBean(DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME);
226                        if (registrations.isEmpty()) {
227                                if (containsDispatcherRegistrationBean) {
228                                        return ConditionOutcome
229                                                        .noMatch(message.found("non servlet registration bean").items(
230                                                                        DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME));
231                                }
232                                return ConditionOutcome
233                                                .match(message.didNotFind("servlet registration bean").atAll());
234                        }
235                        if (registrations
236                                        .contains(DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)) {
237                                return ConditionOutcome.noMatch(message.found("servlet registration bean")
238                                                .items(DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME));
239                        }
240                        if (containsDispatcherRegistrationBean) {
241                                return ConditionOutcome
242                                                .noMatch(message.found("non servlet registration bean").items(
243                                                                DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME));
244                        }
245                        return ConditionOutcome.match(message.found("servlet registration beans")
246                                        .items(Style.QUOTE, registrations).append("and none is named "
247                                                        + DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME));
248                }
249
250                private ConditionMessage.Builder startMessage() {
251                        return ConditionMessage.forCondition("DispatcherServlet Registration");
252                }
253
254        }
255
256}