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