Skip to content

Commit c69703f

Browse files
committed
Deprecate path extension strategies
This commit deprecates PathExtensionContentNegotiationStrategy and ServletPathExtensionContentNegotiationStrategy and also updates code that depends on them internally to remove that dependence. See gh-24179
1 parent 542e187 commit c69703f

File tree

8 files changed

+138
-73
lines changed

8 files changed

+138
-73
lines changed

spring-test/src/main/java/org/springframework/test/web/servlet/setup/StandaloneMockMvcBuilder.java

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2020 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.
@@ -310,7 +310,11 @@ public StandaloneMockMvcBuilder setFlashMapManager(FlashMapManager flashMapManag
310310
* Whether to use suffix pattern match (".*") when matching patterns to
311311
* requests. If enabled a method mapped to "/users" also matches to "/users.*".
312312
* <p>The default value is {@code true}.
313+
* @deprecated as of 5.2.4. See class-level note in
314+
* {@link RequestMappingHandlerMapping} on the deprecation of path extension
315+
* config options.
313316
*/
317+
@Deprecated
314318
public StandaloneMockMvcBuilder setUseSuffixPatternMatch(boolean useSuffixPatternMatch) {
315319
this.useSuffixPatternMatch = useSuffixPatternMatch;
316320
return this;
@@ -442,6 +446,7 @@ protected Map<String, Object> extendMvcSingletons(@Nullable ServletContext servl
442446
/** Using the MVC Java configuration as the starting point for the "standalone" setup. */
443447
private class StandaloneConfiguration extends WebMvcConfigurationSupport {
444448

449+
@SuppressWarnings("deprecation")
445450
public RequestMappingHandlerMapping getHandlerMapping(
446451
FormattingConversionService mvcConversionService,
447452
ResourceUrlProvider mvcResourceUrlProvider) {

spring-web/src/main/java/org/springframework/web/accept/PathExtensionContentNegotiationStrategy.java

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2020 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.
@@ -41,7 +41,11 @@
4141
*
4242
* @author Rossen Stoyanchev
4343
* @since 3.2
44+
* @deprecated as of 5.2.4. See class-level note in
45+
* {@link ContentNegotiationManagerFactoryBean} on the deprecation of path
46+
* extension config options.
4447
*/
48+
@Deprecated
4549
public class PathExtensionContentNegotiationStrategy extends AbstractMappingContentNegotiationStrategy {
4650

4751
private UrlPathHelper urlPathHelper = new UrlPathHelper();

spring-web/src/main/java/org/springframework/web/accept/ServletPathExtensionContentNegotiationStrategy.java

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2017 the original author or authors.
2+
* Copyright 2002-2020 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.
@@ -34,7 +34,11 @@
3434
*
3535
* @author Rossen Stoyanchev
3636
* @since 3.2
37+
* @deprecated as of 5.2.4. See class-level note in
38+
* {@link ContentNegotiationManagerFactoryBean} on the deprecation of path
39+
* extension config options.
3740
*/
41+
@Deprecated
3842
public class ServletPathExtensionContentNegotiationStrategy extends PathExtensionContentNegotiationStrategy {
3943

4044
private final ServletContext servletContext;

spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/ResourceHandlerRegistry.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2020 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.
@@ -151,6 +151,7 @@ public ResourceHandlerRegistry setOrder(int order) {
151151
* of no registrations.
152152
*/
153153
@Nullable
154+
@SuppressWarnings("deprecation")
154155
protected AbstractHandlerMapping getHandlerMapping() {
155156
if (this.registrations.isEmpty()) {
156157
return null;

spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodProcessor.java

+13-26
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import java.util.Locale;
2727
import java.util.Set;
2828

29+
import javax.servlet.ServletRequest;
2930
import javax.servlet.http.HttpServletRequest;
3031
import javax.servlet.http.HttpServletResponse;
3132

@@ -43,6 +44,7 @@
4344
import org.springframework.http.HttpRange;
4445
import org.springframework.http.HttpStatus;
4546
import org.springframework.http.MediaType;
47+
import org.springframework.http.MediaTypeFactory;
4648
import org.springframework.http.converter.GenericHttpMessageConverter;
4749
import org.springframework.http.converter.HttpMessageConverter;
4850
import org.springframework.http.converter.HttpMessageNotWritableException;
@@ -54,7 +56,6 @@
5456
import org.springframework.util.StringUtils;
5557
import org.springframework.web.HttpMediaTypeNotAcceptableException;
5658
import org.springframework.web.accept.ContentNegotiationManager;
57-
import org.springframework.web.accept.PathExtensionContentNegotiationStrategy;
5859
import org.springframework.web.context.request.NativeWebRequest;
5960
import org.springframework.web.context.request.ServletWebRequest;
6061
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
@@ -102,8 +103,6 @@ public abstract class AbstractMessageConverterMethodProcessor extends AbstractMe
102103

103104
private final ContentNegotiationManager contentNegotiationManager;
104105

105-
private final PathExtensionContentNegotiationStrategy pathStrategy;
106-
107106
private final Set<String> safeExtensions = new HashSet<>();
108107

109108

@@ -133,17 +132,10 @@ protected AbstractMessageConverterMethodProcessor(List<HttpMessageConverter<?>>
133132
super(converters, requestResponseBodyAdvice);
134133

135134
this.contentNegotiationManager = (manager != null ? manager : new ContentNegotiationManager());
136-
this.pathStrategy = initPathStrategy(this.contentNegotiationManager);
137135
this.safeExtensions.addAll(this.contentNegotiationManager.getAllFileExtensions());
138136
this.safeExtensions.addAll(WHITELISTED_EXTENSIONS);
139137
}
140138

141-
private static PathExtensionContentNegotiationStrategy initPathStrategy(ContentNegotiationManager manager) {
142-
Class<PathExtensionContentNegotiationStrategy> clazz = PathExtensionContentNegotiationStrategy.class;
143-
PathExtensionContentNegotiationStrategy strategy = manager.getStrategy(clazz);
144-
return (strategy != null ? strategy : new PathExtensionContentNegotiationStrategy());
145-
}
146-
147139

148140
/**
149141
* Creates a new {@link HttpOutputMessage} from the given {@link NativeWebRequest}.
@@ -481,26 +473,21 @@ private boolean safeExtension(HttpServletRequest request, @Nullable String exten
481473
return true;
482474
}
483475
}
484-
return safeMediaTypesForExtension(new ServletWebRequest(request), extension);
476+
MediaType mediaType = resolveMediaType(request, extension);
477+
return (mediaType != null && (safeMediaType(mediaType)));
485478
}
486479

487-
private boolean safeMediaTypesForExtension(NativeWebRequest request, String extension) {
488-
List<MediaType> mediaTypes = null;
489-
try {
490-
mediaTypes = this.pathStrategy.resolveMediaTypeKey(request, extension);
480+
@Nullable
481+
private MediaType resolveMediaType(ServletRequest request, String extension) {
482+
MediaType result = null;
483+
String rawMimeType = request.getServletContext().getMimeType("file." + extension);
484+
if (StringUtils.hasText(rawMimeType)) {
485+
result = MediaType.parseMediaType(rawMimeType);
491486
}
492-
catch (HttpMediaTypeNotAcceptableException ex) {
493-
// Ignore
494-
}
495-
if (CollectionUtils.isEmpty(mediaTypes)) {
496-
return false;
497-
}
498-
for (MediaType mediaType : mediaTypes) {
499-
if (!safeMediaType(mediaType)) {
500-
return false;
501-
}
487+
if (result == null || MediaType.APPLICATION_OCTET_STREAM.equals(result)) {
488+
result = MediaTypeFactory.getMediaType("file." + extension).orElse(null);
502489
}
503-
return true;
490+
return result;
504491
}
505492

506493
private boolean safeMediaType(MediaType mediaType) {

spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandler.java

+84-28
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2020 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.
@@ -24,6 +24,7 @@
2424
import java.util.Collections;
2525
import java.util.HashMap;
2626
import java.util.List;
27+
import java.util.Locale;
2728
import java.util.Map;
2829
import java.util.stream.Collectors;
2930

@@ -43,6 +44,7 @@
4344
import org.springframework.http.HttpMethod;
4445
import org.springframework.http.HttpRange;
4546
import org.springframework.http.MediaType;
47+
import org.springframework.http.MediaTypeFactory;
4648
import org.springframework.http.converter.ResourceHttpMessageConverter;
4749
import org.springframework.http.converter.ResourceRegionHttpMessageConverter;
4850
import org.springframework.http.server.ServletServerHttpRequest;
@@ -56,8 +58,6 @@
5658
import org.springframework.util.StringValueResolver;
5759
import org.springframework.web.HttpRequestHandler;
5860
import org.springframework.web.accept.ContentNegotiationManager;
59-
import org.springframework.web.accept.PathExtensionContentNegotiationStrategy;
60-
import org.springframework.web.accept.ServletPathExtensionContentNegotiationStrategy;
6161
import org.springframework.web.context.request.ServletWebRequest;
6262
import org.springframework.web.cors.CorsConfiguration;
6363
import org.springframework.web.cors.CorsConfigurationSource;
@@ -129,8 +129,7 @@ public class ResourceHttpRequestHandler extends WebContentGenerator
129129
@Nullable
130130
private ContentNegotiationManager contentNegotiationManager;
131131

132-
@Nullable
133-
private PathExtensionContentNegotiationStrategy contentNegotiationStrategy;
132+
private final Map<String, MediaType> mediaTypes = new HashMap<>(4);
134133

135134
@Nullable
136135
private CorsConfiguration corsConfiguration;
@@ -262,20 +261,50 @@ public ResourceRegionHttpMessageConverter getResourceRegionHttpMessageConverter(
262261
* media types for resources being served. If the manager contains a path
263262
* extension strategy it will be checked for registered file extension.
264263
* @since 4.3
264+
* @deprecated as of 5.2.4 in favor of using {@link #setMediaTypes(Map)}
265+
* with mappings possibly obtained from
266+
* {@link ContentNegotiationManager#getMediaTypeMappings()}.
265267
*/
268+
@Deprecated
266269
public void setContentNegotiationManager(@Nullable ContentNegotiationManager contentNegotiationManager) {
267270
this.contentNegotiationManager = contentNegotiationManager;
268271
}
269272

270273
/**
271274
* Return the configured content negotiation manager.
272275
* @since 4.3
276+
* @deprecated as of 5.2.4.
273277
*/
274278
@Nullable
279+
@Deprecated
275280
public ContentNegotiationManager getContentNegotiationManager() {
276281
return this.contentNegotiationManager;
277282
}
278283

284+
/**
285+
* Add mappings between file extensions, extracted from the filename of a
286+
* static {@link Resource}, and corresponding media type to set on the
287+
* response.
288+
* <p>Use of this method is typically not necessary since mappings are
289+
* otherwise determined via
290+
* {@link javax.servlet.ServletContext#getMimeType(String)} or via
291+
* {@link MediaTypeFactory#getMediaType(Resource)}.
292+
* @param mediaTypes media type mappings
293+
* @since 5.2.4
294+
*/
295+
public void setMediaTypes(Map<String, MediaType> mediaTypes) {
296+
mediaTypes.forEach((ext, mediaType) ->
297+
this.mediaTypes.put(ext.toLowerCase(Locale.ENGLISH), mediaType));
298+
}
299+
300+
/**
301+
* Return the {@link #setMediaTypes(Map) configured} media types.
302+
* @since 5.2.4
303+
*/
304+
public Map<String, MediaType> getMediaTypes() {
305+
return this.mediaTypes;
306+
}
307+
279308
/**
280309
* Specify the CORS configuration for resources served by this handler.
281310
* <p>By default this is not set in which allows cross-origin requests.
@@ -344,7 +373,17 @@ public void afterPropertiesSet() throws Exception {
344373
this.resourceRegionHttpMessageConverter = new ResourceRegionHttpMessageConverter();
345374
}
346375

347-
this.contentNegotiationStrategy = initContentNegotiationStrategy();
376+
ContentNegotiationManager manager = getContentNegotiationManager();
377+
if (manager != null) {
378+
setMediaTypes(manager.getMediaTypeMappings());
379+
}
380+
381+
@SuppressWarnings("deprecation")
382+
org.springframework.web.accept.PathExtensionContentNegotiationStrategy strategy =
383+
initContentNegotiationStrategy();
384+
if (strategy != null) {
385+
setMediaTypes(strategy.getMediaTypes());
386+
}
348387
}
349388

350389
private void resolveResourceLocations() {
@@ -412,26 +451,20 @@ protected void initAllowedLocations() {
412451
}
413452

414453
/**
415-
* Initialize the content negotiation strategy depending on the {@code ContentNegotiationManager}
416-
* setup and the availability of a {@code ServletContext}.
417-
* @see ServletPathExtensionContentNegotiationStrategy
418-
* @see PathExtensionContentNegotiationStrategy
454+
* Initialize the strategy to use to determine the media type for a resource.
455+
* @deprecated as of 5.2.4 this method returns {@code null}, and if a
456+
* sub-class returns an actual instance,the instance is used only as a
457+
* source of media type mappings, if it contains any. Please, use
458+
* {@link #setMediaTypes(Map)} instead, or if you need to change behavior,
459+
* you can override {@link #getMediaType(HttpServletRequest, Resource)}.
419460
*/
420-
protected PathExtensionContentNegotiationStrategy initContentNegotiationStrategy() {
421-
Map<String, MediaType> mediaTypes = null;
422-
if (getContentNegotiationManager() != null) {
423-
PathExtensionContentNegotiationStrategy strategy =
424-
getContentNegotiationManager().getStrategy(PathExtensionContentNegotiationStrategy.class);
425-
if (strategy != null) {
426-
mediaTypes = new HashMap<>(strategy.getMediaTypes());
427-
}
428-
}
429-
return (getServletContext() != null ?
430-
new ServletPathExtensionContentNegotiationStrategy(getServletContext(), mediaTypes) :
431-
new PathExtensionContentNegotiationStrategy(mediaTypes));
461+
@Nullable
462+
@Deprecated
463+
@SuppressWarnings("deprecation")
464+
protected org.springframework.web.accept.PathExtensionContentNegotiationStrategy initContentNegotiationStrategy() {
465+
return null;
432466
}
433467

434-
435468
/**
436469
* Processes a resource request.
437470
* <p>Checks for the existence of the requested resource in the configured list of locations.
@@ -659,17 +692,40 @@ protected boolean isInvalidPath(String path) {
659692

660693
/**
661694
* Determine the media type for the given request and the resource matched
662-
* to it. This implementation tries to determine the MediaType based on the
663-
* file extension of the Resource via
664-
* {@link ServletPathExtensionContentNegotiationStrategy#getMediaTypeForResource}.
695+
* to it. This implementation tries to determine the MediaType using one of
696+
* the following lookups based on the resource filename and its path
697+
* extension:
698+
* <ol>
699+
* <li>{@link javax.servlet.ServletContext#getMimeType(String)}
700+
* <li>{@link #getMediaTypes()}
701+
* <li>{@link MediaTypeFactory#getMediaType(String)}
702+
* </ol>
665703
* @param request the current request
666704
* @param resource the resource to check
667705
* @return the corresponding media type, or {@code null} if none found
668706
*/
669707
@Nullable
670708
protected MediaType getMediaType(HttpServletRequest request, Resource resource) {
671-
return (this.contentNegotiationStrategy != null ?
672-
this.contentNegotiationStrategy.getMediaTypeForResource(resource) : null);
709+
MediaType result = null;
710+
String mimeType = request.getServletContext().getMimeType(resource.getFilename());
711+
if (StringUtils.hasText(mimeType)) {
712+
result = MediaType.parseMediaType(mimeType);
713+
}
714+
if (result == null || MediaType.APPLICATION_OCTET_STREAM.equals(result)) {
715+
MediaType mediaType = null;
716+
String filename = resource.getFilename();
717+
String ext = StringUtils.getFilenameExtension(filename);
718+
if (ext != null) {
719+
mediaType = this.mediaTypes.get(ext.toLowerCase(Locale.ENGLISH));
720+
}
721+
if (mediaType == null) {
722+
mediaType = MediaTypeFactory.getMediaType(filename).orElse(null);
723+
}
724+
if (mediaType != null) {
725+
result = mediaType;
726+
}
727+
}
728+
return result;
673729
}
674730

675731
/**

0 commit comments

Comments
 (0)