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.time.Duration;
020import java.util.LinkedHashMap;
021import java.util.Locale;
022import java.util.Map;
023
024import org.springframework.boot.context.properties.ConfigurationProperties;
025import org.springframework.http.MediaType;
026import org.springframework.util.Assert;
027import org.springframework.validation.DefaultMessageCodesResolver;
028
029/**
030 * {@link ConfigurationProperties properties} for Spring MVC.
031 *
032 * @author Phillip Webb
033 * @author Sébastien Deleuze
034 * @author Stephane Nicoll
035 * @author Eddú Meléndez
036 * @author Brian Clozel
037 * @since 1.1
038 */
039@ConfigurationProperties(prefix = "spring.mvc")
040public class WebMvcProperties {
041
042        /**
043         * Formatting strategy for message codes. For instance, `PREFIX_ERROR_CODE`.
044         */
045        private DefaultMessageCodesResolver.Format messageCodesResolverFormat;
046
047        /**
048         * Locale to use. By default, this locale is overridden by the "Accept-Language"
049         * header.
050         */
051        private Locale locale;
052
053        /**
054         * Define how the locale should be resolved.
055         */
056        private LocaleResolver localeResolver = LocaleResolver.ACCEPT_HEADER;
057
058        /**
059         * Date format to use. For instance, `dd/MM/yyyy`.
060         */
061        private String dateFormat;
062
063        /**
064         * Whether to dispatch TRACE requests to the FrameworkServlet doService method.
065         */
066        private boolean dispatchTraceRequest = false;
067
068        /**
069         * Whether to dispatch OPTIONS requests to the FrameworkServlet doService method.
070         */
071        private boolean dispatchOptionsRequest = true;
072
073        /**
074         * Whether the content of the "default" model should be ignored during redirect
075         * scenarios.
076         */
077        private boolean ignoreDefaultModelOnRedirect = true;
078
079        /**
080         * Whether a "NoHandlerFoundException" should be thrown if no Handler was found to
081         * process a request.
082         */
083        private boolean throwExceptionIfNoHandlerFound = false;
084
085        /**
086         * Whether to enable warn logging of exceptions resolved by a
087         * "HandlerExceptionResolver", except for "DefaultHandlerExceptionResolver".
088         */
089        private boolean logResolvedException = false;
090
091        /**
092         * Path pattern used for static resources.
093         */
094        private String staticPathPattern = "/**";
095
096        private final Async async = new Async();
097
098        private final Servlet servlet = new Servlet();
099
100        private final View view = new View();
101
102        private final Contentnegotiation contentnegotiation = new Contentnegotiation();
103
104        private final Pathmatch pathmatch = new Pathmatch();
105
106        public DefaultMessageCodesResolver.Format getMessageCodesResolverFormat() {
107                return this.messageCodesResolverFormat;
108        }
109
110        public void setMessageCodesResolverFormat(
111                        DefaultMessageCodesResolver.Format messageCodesResolverFormat) {
112                this.messageCodesResolverFormat = messageCodesResolverFormat;
113        }
114
115        public Locale getLocale() {
116                return this.locale;
117        }
118
119        public void setLocale(Locale locale) {
120                this.locale = locale;
121        }
122
123        public LocaleResolver getLocaleResolver() {
124                return this.localeResolver;
125        }
126
127        public void setLocaleResolver(LocaleResolver localeResolver) {
128                this.localeResolver = localeResolver;
129        }
130
131        public String getDateFormat() {
132                return this.dateFormat;
133        }
134
135        public void setDateFormat(String dateFormat) {
136                this.dateFormat = dateFormat;
137        }
138
139        public boolean isIgnoreDefaultModelOnRedirect() {
140                return this.ignoreDefaultModelOnRedirect;
141        }
142
143        public void setIgnoreDefaultModelOnRedirect(boolean ignoreDefaultModelOnRedirect) {
144                this.ignoreDefaultModelOnRedirect = ignoreDefaultModelOnRedirect;
145        }
146
147        public boolean isThrowExceptionIfNoHandlerFound() {
148                return this.throwExceptionIfNoHandlerFound;
149        }
150
151        public void setThrowExceptionIfNoHandlerFound(
152                        boolean throwExceptionIfNoHandlerFound) {
153                this.throwExceptionIfNoHandlerFound = throwExceptionIfNoHandlerFound;
154        }
155
156        public boolean isLogResolvedException() {
157                return this.logResolvedException;
158        }
159
160        public void setLogResolvedException(boolean logResolvedException) {
161                this.logResolvedException = logResolvedException;
162        }
163
164        public boolean isDispatchOptionsRequest() {
165                return this.dispatchOptionsRequest;
166        }
167
168        public void setDispatchOptionsRequest(boolean dispatchOptionsRequest) {
169                this.dispatchOptionsRequest = dispatchOptionsRequest;
170        }
171
172        public boolean isDispatchTraceRequest() {
173                return this.dispatchTraceRequest;
174        }
175
176        public void setDispatchTraceRequest(boolean dispatchTraceRequest) {
177                this.dispatchTraceRequest = dispatchTraceRequest;
178        }
179
180        public String getStaticPathPattern() {
181                return this.staticPathPattern;
182        }
183
184        public void setStaticPathPattern(String staticPathPattern) {
185                this.staticPathPattern = staticPathPattern;
186        }
187
188        public Async getAsync() {
189                return this.async;
190        }
191
192        public Servlet getServlet() {
193                return this.servlet;
194        }
195
196        public View getView() {
197                return this.view;
198        }
199
200        public Contentnegotiation getContentnegotiation() {
201                return this.contentnegotiation;
202        }
203
204        public Pathmatch getPathmatch() {
205                return this.pathmatch;
206        }
207
208        public static class Async {
209
210                /**
211                 * Amount of time before asynchronous request handling times out. If this value is
212                 * not set, the default timeout of the underlying implementation is used, e.g. 10
213                 * seconds on Tomcat with Servlet 3.
214                 */
215                private Duration requestTimeout;
216
217                public Duration getRequestTimeout() {
218                        return this.requestTimeout;
219                }
220
221                public void setRequestTimeout(Duration requestTimeout) {
222                        this.requestTimeout = requestTimeout;
223                }
224
225        }
226
227        public static class Servlet {
228
229                /**
230                 * Path of the dispatcher servlet.
231                 */
232                private String path = "/";
233
234                /**
235                 * Load on startup priority of the dispatcher servlet.
236                 */
237                private int loadOnStartup = -1;
238
239                public String getPath() {
240                        return this.path;
241                }
242
243                public void setPath(String path) {
244                        Assert.notNull(path, "Path must not be null");
245                        Assert.isTrue(!path.contains("*"), "Path must not contain wildcards");
246                        this.path = path;
247                }
248
249                public int getLoadOnStartup() {
250                        return this.loadOnStartup;
251                }
252
253                public void setLoadOnStartup(int loadOnStartup) {
254                        this.loadOnStartup = loadOnStartup;
255                }
256
257                public String getServletMapping() {
258                        if (this.path.equals("") || this.path.equals("/")) {
259                                return "/";
260                        }
261                        if (this.path.endsWith("/")) {
262                                return this.path + "*";
263                        }
264                        return this.path + "/*";
265                }
266
267                public String getPath(String path) {
268                        String prefix = getServletPrefix();
269                        if (!path.startsWith("/")) {
270                                path = "/" + path;
271                        }
272                        return prefix + path;
273                }
274
275                public String getServletPrefix() {
276                        String result = this.path;
277                        int index = result.indexOf('*');
278                        if (index != -1) {
279                                result = result.substring(0, index);
280                        }
281                        if (result.endsWith("/")) {
282                                result = result.substring(0, result.length() - 1);
283                        }
284                        return result;
285                }
286
287        }
288
289        public static class View {
290
291                /**
292                 * Spring MVC view prefix.
293                 */
294                private String prefix;
295
296                /**
297                 * Spring MVC view suffix.
298                 */
299                private String suffix;
300
301                public String getPrefix() {
302                        return this.prefix;
303                }
304
305                public void setPrefix(String prefix) {
306                        this.prefix = prefix;
307                }
308
309                public String getSuffix() {
310                        return this.suffix;
311                }
312
313                public void setSuffix(String suffix) {
314                        this.suffix = suffix;
315                }
316
317        }
318
319        public static class Contentnegotiation {
320
321                /**
322                 * Whether the path extension in the URL path should be used to determine the
323                 * requested media type. If enabled a request "/users.pdf" will be interpreted as
324                 * a request for "application/pdf" regardless of the 'Accept' header.
325                 */
326                private boolean favorPathExtension = false;
327
328                /**
329                 * Whether a request parameter ("format" by default) should be used to determine
330                 * the requested media type.
331                 */
332                private boolean favorParameter = false;
333
334                /**
335                 * Map file extensions to media types for content negotiation. For instance, yml
336                 * to text/yaml.
337                 */
338                private Map<String, MediaType> mediaTypes = new LinkedHashMap<>();
339
340                /**
341                 * Query parameter name to use when "favor-parameter" is enabled.
342                 */
343                private String parameterName;
344
345                public boolean isFavorPathExtension() {
346                        return this.favorPathExtension;
347                }
348
349                public void setFavorPathExtension(boolean favorPathExtension) {
350                        this.favorPathExtension = favorPathExtension;
351                }
352
353                public boolean isFavorParameter() {
354                        return this.favorParameter;
355                }
356
357                public void setFavorParameter(boolean favorParameter) {
358                        this.favorParameter = favorParameter;
359                }
360
361                public Map<String, MediaType> getMediaTypes() {
362                        return this.mediaTypes;
363                }
364
365                public void setMediaTypes(Map<String, MediaType> mediaTypes) {
366                        this.mediaTypes = mediaTypes;
367                }
368
369                public String getParameterName() {
370                        return this.parameterName;
371                }
372
373                public void setParameterName(String parameterName) {
374                        this.parameterName = parameterName;
375                }
376
377        }
378
379        public static class Pathmatch {
380
381                /**
382                 * Whether to use suffix pattern match (".*") when matching patterns to requests.
383                 * If enabled a method mapped to "/users" also matches to "/users.*".
384                 */
385                private boolean useSuffixPattern = false;
386
387                /**
388                 * Whether suffix pattern matching should work only against extensions registered
389                 * with "spring.mvc.contentnegotiation.media-types.*". This is generally
390                 * recommended to reduce ambiguity and to avoid issues such as when a "." appears
391                 * in the path for other reasons.
392                 */
393                private boolean useRegisteredSuffixPattern = false;
394
395                public boolean isUseSuffixPattern() {
396                        return this.useSuffixPattern;
397                }
398
399                public void setUseSuffixPattern(boolean useSuffixPattern) {
400                        this.useSuffixPattern = useSuffixPattern;
401                }
402
403                public boolean isUseRegisteredSuffixPattern() {
404                        return this.useRegisteredSuffixPattern;
405                }
406
407                public void setUseRegisteredSuffixPattern(boolean useRegisteredSuffixPattern) {
408                        this.useRegisteredSuffixPattern = useRegisteredSuffixPattern;
409                }
410
411        }
412
413        public enum LocaleResolver {
414
415                /**
416                 * Always use the configured locale.
417                 */
418                FIXED,
419
420                /**
421                 * Use the "Accept-Language" header or the configured locale if the header is not
422                 * set.
423                 */
424                ACCEPT_HEADER
425
426        }
427
428}