11/*
2- * Copyright 2012-2020 the original author or authors.
2+ * Copyright 2012-2021 the original author or authors.
33 *
44 * Licensed under the Apache License, Version 2.0 (the "License");
55 * you may not use this file except in compliance with the License.
1717package org .springframework .boot .autoconfigure .web .servlet ;
1818
1919import java .time .Duration ;
20- import java .util .Arrays ;
20+ import java .util .HashSet ;
2121import java .util .List ;
2222import java .util .ListIterator ;
2323import java .util .Map ;
24- import java .util .Optional ;
24+ import java .util .Set ;
2525
2626import javax .servlet .Servlet ;
27+ import javax .servlet .ServletContext ;
2728
2829import org .apache .commons .logging .Log ;
2930import org .apache .commons .logging .LogFactory ;
7374import org .springframework .core .task .AsyncTaskExecutor ;
7475import org .springframework .format .FormatterRegistry ;
7576import org .springframework .format .support .FormattingConversionService ;
76- import org .springframework .http .CacheControl ;
7777import org .springframework .http .MediaType ;
7878import org .springframework .http .converter .HttpMessageConverter ;
7979import org .springframework .util .ClassUtils ;
80+ import org .springframework .util .PathMatcher ;
8081import org .springframework .validation .DefaultMessageCodesResolver ;
8182import org .springframework .validation .MessageCodesResolver ;
8283import org .springframework .validation .Validator ;
8889import org .springframework .web .context .request .NativeWebRequest ;
8990import org .springframework .web .context .request .RequestAttributes ;
9091import org .springframework .web .context .request .RequestContextListener ;
92+ import org .springframework .web .context .support .ServletContextResource ;
9193import org .springframework .web .filter .FormContentFilter ;
9294import org .springframework .web .filter .HiddenHttpMethodFilter ;
9395import org .springframework .web .filter .RequestContextFilter ;
9496import org .springframework .web .servlet .DispatcherServlet ;
9597import org .springframework .web .servlet .HandlerExceptionResolver ;
98+ import org .springframework .web .servlet .HandlerMapping ;
9699import org .springframework .web .servlet .LocaleResolver ;
97100import org .springframework .web .servlet .View ;
98101import org .springframework .web .servlet .ViewResolver ;
107110import org .springframework .web .servlet .config .annotation .WebMvcConfigurationSupport ;
108111import org .springframework .web .servlet .config .annotation .WebMvcConfigurer ;
109112import org .springframework .web .servlet .handler .AbstractHandlerExceptionResolver ;
113+ import org .springframework .web .servlet .handler .SimpleUrlHandlerMapping ;
110114import org .springframework .web .servlet .i18n .AcceptHeaderLocaleResolver ;
111115import org .springframework .web .servlet .i18n .FixedLocaleResolver ;
112116import org .springframework .web .servlet .mvc .method .annotation .ExceptionHandlerExceptionResolver ;
113117import org .springframework .web .servlet .mvc .method .annotation .RequestMappingHandlerAdapter ;
114118import org .springframework .web .servlet .mvc .method .annotation .RequestMappingHandlerMapping ;
115119import org .springframework .web .servlet .resource .AppCacheManifestTransformer ;
116120import org .springframework .web .servlet .resource .EncodedResourceResolver ;
121+ import org .springframework .web .servlet .resource .ResourceHttpRequestHandler ;
117122import org .springframework .web .servlet .resource .ResourceResolver ;
118123import org .springframework .web .servlet .resource .ResourceUrlProvider ;
119124import org .springframework .web .servlet .resource .VersionResourceResolver ;
@@ -155,7 +160,7 @@ public class WebMvcAutoConfiguration {
155160 */
156161 public static final String DEFAULT_SUFFIX = "" ;
157162
158- private static final String [] SERVLET_LOCATIONS = { "/" } ;
163+ private static final String SERVLET_LOCATION = "/" ;
159164
160165 @ Bean
161166 @ ConditionalOnMissingBean (HiddenHttpMethodFilter .class )
@@ -171,13 +176,6 @@ public OrderedFormContentFilter formContentFilter() {
171176 return new OrderedFormContentFilter ();
172177 }
173178
174- static String [] getResourceLocations (String [] staticLocations ) {
175- String [] locations = new String [staticLocations .length + SERVLET_LOCATIONS .length ];
176- System .arraycopy (staticLocations , 0 , locations , 0 , staticLocations .length );
177- System .arraycopy (SERVLET_LOCATIONS , 0 , locations , staticLocations .length , SERVLET_LOCATIONS .length );
178- return locations ;
179- }
180-
181179 // Defined as a nested config to ensure WebMvcConfigurer is not read when not
182180 // on the classpath
183181 @ Configuration (proxyBeanMethods = false )
@@ -186,10 +184,6 @@ static String[] getResourceLocations(String[] staticLocations) {
186184 @ Order (0 )
187185 public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {
188186
189- private static final Log logger = LogFactory .getLog (WebMvcConfigurer .class );
190-
191- private final ResourceProperties resourceProperties ;
192-
193187 private final WebMvcProperties mvcProperties ;
194188
195189 private final ListableBeanFactory beanFactory ;
@@ -202,12 +196,11 @@ public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {
202196
203197 final ResourceHandlerRegistrationCustomizer resourceHandlerRegistrationCustomizer ;
204198
205- public WebMvcAutoConfigurationAdapter (ResourceProperties resourceProperties , WebMvcProperties mvcProperties ,
206- ListableBeanFactory beanFactory , ObjectProvider <HttpMessageConverters > messageConvertersProvider ,
199+ public WebMvcAutoConfigurationAdapter (WebMvcProperties mvcProperties , ListableBeanFactory beanFactory ,
200+ ObjectProvider <HttpMessageConverters > messageConvertersProvider ,
207201 ObjectProvider <ResourceHandlerRegistrationCustomizer > resourceHandlerRegistrationCustomizerProvider ,
208202 ObjectProvider <DispatcherServletPath > dispatcherServletPath ,
209203 ObjectProvider <ServletRegistrationBean <?>> servletRegistrations ) {
210- this .resourceProperties = resourceProperties ;
211204 this .mvcProperties = mvcProperties ;
212205 this .beanFactory = beanFactory ;
213206 this .messageConvertersProvider = messageConvertersProvider ;
@@ -328,37 +321,6 @@ public void addFormatters(FormatterRegistry registry) {
328321 ApplicationConversionService .addBeans (registry , this .beanFactory );
329322 }
330323
331- @ Override
332- public void addResourceHandlers (ResourceHandlerRegistry registry ) {
333- if (!this .resourceProperties .isAddMappings ()) {
334- logger .debug ("Default resource handling disabled" );
335- return ;
336- }
337- Duration cachePeriod = this .resourceProperties .getCache ().getPeriod ();
338- CacheControl cacheControl = this .resourceProperties .getCache ().getCachecontrol ().toHttpCacheControl ();
339- if (!registry .hasMappingForPattern ("/webjars/**" )) {
340- customizeResourceHandlerRegistration (registry .addResourceHandler ("/webjars/**" )
341- .addResourceLocations ("classpath:/META-INF/resources/webjars/" )
342- .setCachePeriod (getSeconds (cachePeriod )).setCacheControl (cacheControl ));
343- }
344- String staticPathPattern = this .mvcProperties .getStaticPathPattern ();
345- if (!registry .hasMappingForPattern (staticPathPattern )) {
346- customizeResourceHandlerRegistration (registry .addResourceHandler (staticPathPattern )
347- .addResourceLocations (getResourceLocations (this .resourceProperties .getStaticLocations ()))
348- .setCachePeriod (getSeconds (cachePeriod )).setCacheControl (cacheControl ));
349- }
350- }
351-
352- private Integer getSeconds (Duration cachePeriod ) {
353- return (cachePeriod != null ) ? (int ) cachePeriod .getSeconds () : null ;
354- }
355-
356- private void customizeResourceHandlerRegistration (ResourceHandlerRegistration registration ) {
357- if (this .resourceHandlerRegistrationCustomizer != null ) {
358- this .resourceHandlerRegistrationCustomizer .customize (registration );
359- }
360- }
361-
362324 @ Bean
363325 @ ConditionalOnMissingBean ({ RequestContextListener .class , RequestContextFilter .class })
364326 @ ConditionalOnMissingFilterBean (RequestContextFilter .class )
@@ -374,22 +336,31 @@ public static RequestContextFilter requestContextFilter() {
374336 @ Configuration (proxyBeanMethods = false )
375337 public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware {
376338
339+ private static final Log logger = LogFactory .getLog (WebMvcConfigurer .class );
340+
377341 private final ResourceProperties resourceProperties ;
378342
379343 private final WebMvcProperties mvcProperties ;
380344
381- private final ListableBeanFactory beanFactory ;
382-
383345 private final WebMvcRegistrations mvcRegistrations ;
384346
347+ private final ResourceHandlerRegistrationCustomizer resourceHandlerRegistrationCustomizer ;
348+
385349 private ResourceLoader resourceLoader ;
386350
351+ private final ListableBeanFactory beanFactory ;
352+
353+ private final Set <String > autoConfiguredResourceHandlers = new HashSet <>();
354+
387355 public EnableWebMvcConfiguration (ResourceProperties resourceProperties ,
388356 ObjectProvider <WebMvcProperties > mvcPropertiesProvider ,
389- ObjectProvider <WebMvcRegistrations > mvcRegistrationsProvider , ListableBeanFactory beanFactory ) {
357+ ObjectProvider <WebMvcRegistrations > mvcRegistrationsProvider ,
358+ ObjectProvider <ResourceHandlerRegistrationCustomizer > resourceHandlerRegistrationCustomizerProvider ,
359+ ListableBeanFactory beanFactory ) {
390360 this .resourceProperties = resourceProperties ;
391361 this .mvcProperties = mvcPropertiesProvider .getIfAvailable ();
392362 this .mvcRegistrations = mvcRegistrationsProvider .getIfUnique ();
363+ this .resourceHandlerRegistrationCustomizer = resourceHandlerRegistrationCustomizerProvider .getIfAvailable ();
393364 this .beanFactory = beanFactory ;
394365 }
395366
@@ -429,6 +400,72 @@ public RequestMappingHandlerMapping requestMappingHandlerMapping(
429400 resourceUrlProvider );
430401 }
431402
403+ @ Bean
404+ @ Override
405+ public HandlerMapping resourceHandlerMapping (UrlPathHelper urlPathHelper , PathMatcher pathMatcher ,
406+ ContentNegotiationManager contentNegotiationManager , FormattingConversionService conversionService ,
407+ ResourceUrlProvider resourceUrlProvider ) {
408+ HandlerMapping mapping = super .resourceHandlerMapping (urlPathHelper , pathMatcher , contentNegotiationManager ,
409+ conversionService , resourceUrlProvider );
410+ if (mapping instanceof SimpleUrlHandlerMapping ) {
411+ addServletContextResourceHandlerMapping ((SimpleUrlHandlerMapping ) mapping );
412+ }
413+ return mapping ;
414+ }
415+
416+ private void addServletContextResourceHandlerMapping (SimpleUrlHandlerMapping mapping ) {
417+ Map <String , ?> urlMap = mapping .getUrlMap ();
418+ String pattern = this .mvcProperties .getStaticPathPattern ();
419+ Object handler = urlMap .get (pattern );
420+ if (handler instanceof ResourceHttpRequestHandler
421+ && this .autoConfiguredResourceHandlers .contains (pattern )) {
422+ addServletContextResourceHandlerMapping ((ResourceHttpRequestHandler ) handler );
423+ }
424+ }
425+
426+ private void addServletContextResourceHandlerMapping (ResourceHttpRequestHandler handler ) {
427+ ServletContext servletContext = getServletContext ();
428+ if (servletContext != null ) {
429+ List <Resource > locations = handler .getLocations ();
430+ locations .add (new ServletContextResource (servletContext , SERVLET_LOCATION ));
431+ }
432+ }
433+
434+ @ Override
435+ protected void addResourceHandlers (ResourceHandlerRegistry registry ) {
436+ super .addResourceHandlers (registry );
437+ if (!this .resourceProperties .isAddMappings ()) {
438+ logger .debug ("Default resource handling disabled" );
439+ return ;
440+ }
441+ addResourceHandler (registry , "/webjars/**" , "classpath:/META-INF/resources/webjars/" );
442+ addResourceHandler (registry , this .mvcProperties .getStaticPathPattern (),
443+ this .resourceProperties .getStaticLocations ());
444+
445+ }
446+
447+ private void addResourceHandler (ResourceHandlerRegistry registry , String pattern , String ... locations ) {
448+ if (registry .hasMappingForPattern (pattern )) {
449+ return ;
450+ }
451+ ResourceHandlerRegistration registration = registry .addResourceHandler (pattern );
452+ registration .addResourceLocations (locations );
453+ registration .setCachePeriod (getSeconds (this .resourceProperties .getCache ().getPeriod ()));
454+ registration .setCacheControl (this .resourceProperties .getCache ().getCachecontrol ().toHttpCacheControl ());
455+ customizeResourceHandlerRegistration (registration );
456+ this .autoConfiguredResourceHandlers .add (pattern );
457+ }
458+
459+ private Integer getSeconds (Duration cachePeriod ) {
460+ return (cachePeriod != null ) ? (int ) cachePeriod .getSeconds () : null ;
461+ }
462+
463+ private void customizeResourceHandlerRegistration (ResourceHandlerRegistration registration ) {
464+ if (this .resourceHandlerRegistrationCustomizer != null ) {
465+ this .resourceHandlerRegistrationCustomizer .customize (registration );
466+ }
467+ }
468+
432469 @ Bean
433470 public WelcomePageHandlerMapping welcomePageHandlerMapping (ApplicationContext applicationContext ,
434471 FormattingConversionService mvcConversionService , ResourceUrlProvider mvcResourceUrlProvider ) {
@@ -440,22 +477,34 @@ public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext ap
440477 return welcomePageHandlerMapping ;
441478 }
442479
443- private Optional <Resource > getWelcomePage () {
444- String [] locations = getResourceLocations (this .resourceProperties .getStaticLocations ());
445- return Arrays .stream (locations ).map (this ::getIndexHtml ).filter (this ::isReadable ).findFirst ();
480+ private Resource getWelcomePage () {
481+ for (String location : this .resourceProperties .getStaticLocations ()) {
482+ Resource indexHtml = getIndexHtml (location );
483+ if (indexHtml != null ) {
484+ return indexHtml ;
485+ }
486+ }
487+ ServletContext servletContext = getServletContext ();
488+ if (servletContext != null ) {
489+ return getIndexHtml (new ServletContextResource (servletContext , SERVLET_LOCATION ));
490+ }
491+ return null ;
446492 }
447493
448494 private Resource getIndexHtml (String location ) {
449- return this .resourceLoader .getResource (location + "index.html" );
495+ return getIndexHtml ( this .resourceLoader .getResource (location ) );
450496 }
451497
452- private boolean isReadable (Resource resource ) {
498+ private Resource getIndexHtml (Resource location ) {
453499 try {
454- return resource .exists () && (resource .getURL () != null );
500+ Resource resource = location .createRelative ("index.html" );
501+ if (resource .exists () && (resource .getURL () != null )) {
502+ return resource ;
503+ }
455504 }
456505 catch (Exception ex ) {
457- return false ;
458506 }
507+ return null ;
459508 }
460509
461510 @ Bean
0 commit comments