From a287e67992b06eb32a5dfa87ca9cdde49968d819 Mon Sep 17 00:00:00 2001 From: Arjen Poutsma Date: Thu, 23 Mar 2017 14:56:43 +0100 Subject: [PATCH] Introduce 'useRegisteredExtensionsOnly' property in classes using MediaTypeFactory This commit introduces a `useRegisteredExtensionsOnly` property that indicates whether classes that use the `MediaTypeFactory` for supplying default media types can do so. - In classes that were introduced in Spring 5.0, the `useRegisteredExtensionsOnly` property takes the place of the `useJaf` property that was removed in 0aaa652 - In classes that existed before Spring 5.0, the `useRegisteredExtensionsOnly` property is added in addition to the deprecated `useJaf`, the latter delegating to the former, but with flipped behavior. Issue: SPR-14908 --- .../http/MediaTypeFactory.java | 11 +++----- .../web/accept/ContentNegotiationManager.java | 4 ++- .../ContentNegotiationManagerFactoryBean.java | 26 +++++++++++++++++-- ...thExtensionContentNegotiationStrategy.java | 22 +++++++++++++--- ...thExtensionContentNegotiationStrategy.java | 1 + .../PathExtensionContentTypeResolver.java | 25 +++++++++++++----- .../RequestedContentTypeResolverBuilder.java | 17 ++++++++++++ .../ContentNegotiationConfigurer.java | 17 ++++++++++-- 8 files changed, 101 insertions(+), 22 deletions(-) diff --git a/spring-web/src/main/java/org/springframework/http/MediaTypeFactory.java b/spring-web/src/main/java/org/springframework/http/MediaTypeFactory.java index 5dfe8c6aa2..bc9ebecaba 100644 --- a/spring-web/src/main/java/org/springframework/http/MediaTypeFactory.java +++ b/spring-web/src/main/java/org/springframework/http/MediaTypeFactory.java @@ -104,11 +104,9 @@ public class MediaTypeFactory { * @return the corresponding media type, or {@code null} if none found */ public static Optional getMediaType(Resource resource) { - if (resource == null) { - return Optional.empty(); - } - String filename = resource.getFilename(); - return (filename != null ? getMediaType(filename) : Optional.empty()); + return Optional.ofNullable(resource) + .map(Resource::getFilename) + .flatMap(MediaTypeFactory::getMediaType); } /** @@ -117,8 +115,7 @@ public class MediaTypeFactory { * @return the corresponding media type, or {@code null} if none found */ public static Optional getMediaType(String filename) { - List mediaTypes = getMediaTypes(filename); - return (!mediaTypes.isEmpty() ? Optional.of(mediaTypes.get(0)) : Optional.empty()); + return getMediaTypes(filename).stream().findFirst(); } /** diff --git a/spring-web/src/main/java/org/springframework/web/accept/ContentNegotiationManager.java b/spring-web/src/main/java/org/springframework/web/accept/ContentNegotiationManager.java index 7a3763d5e8..04184f5bb5 100644 --- a/spring-web/src/main/java/org/springframework/web/accept/ContentNegotiationManager.java +++ b/spring-web/src/main/java/org/springframework/web/accept/ContentNegotiationManager.java @@ -143,7 +143,9 @@ public class ContentNegotiationManager implements ContentNegotiationStrategy, Me *

At startup this method returns extensions explicitly registered with * either {@link PathExtensionContentNegotiationStrategy} or * {@link ParameterContentNegotiationStrategy}. At runtime if there is a - * "path extension" strategy, the list of extensions may + * "path extension" strategy and its + * {@link PathExtensionContentNegotiationStrategy#setUseRegisteredExtensionsOnly(boolean) + * useRegisteredExtensionsOnly} property is set to "false", the list of extensions may * increase as file extensions are resolved via * {@link org.springframework.http.MediaTypeFactory} and cached. */ diff --git a/spring-web/src/main/java/org/springframework/web/accept/ContentNegotiationManagerFactoryBean.java b/spring-web/src/main/java/org/springframework/web/accept/ContentNegotiationManagerFactoryBean.java index 5bc8a65966..2a42e7623f 100644 --- a/spring-web/src/main/java/org/springframework/web/accept/ContentNegotiationManagerFactoryBean.java +++ b/spring-web/src/main/java/org/springframework/web/accept/ContentNegotiationManagerFactoryBean.java @@ -100,6 +100,8 @@ public class ContentNegotiationManagerFactoryBean private boolean ignoreUnknownPathExtensions = true; + private Boolean useRegisteredExtensionsOnly; + private String parameterName = "format"; private ContentNegotiationStrategy defaultNegotiationStrategy; @@ -175,10 +177,27 @@ public class ContentNegotiationManagerFactoryBean } /** - * @deprecated as 5.0, in favor of {@link MediaTypeFactory}, which has no JAF dependency. + * @deprecated as of 5.0, in favor of {@link #setUseRegisteredExtensionsOnly(boolean)}, which + * has reverse behavior. */ @Deprecated public void setUseJaf(boolean useJaf) { + setUseRegisteredExtensionsOnly(!useJaf); + } + + /** + * When {@link #setFavorPathExtension favorPathExtension} is set, this + * property determines whether to use only registered {@code MediaType} mappings + * to resolve a path extension to a specific MediaType. + *

By default this is not set in which case + * {@code PathExtensionContentNegotiationStrategy} will use defaults if available. + */ + public void setUseRegisteredExtensionsOnly(boolean useRegisteredExtensionsOnly) { + this.useRegisteredExtensionsOnly = useRegisteredExtensionsOnly; + } + + private boolean useRegisteredExtensionsOnly() { + return (this.useRegisteredExtensionsOnly != null && this.useRegisteredExtensionsOnly); } /** @@ -244,7 +263,7 @@ public class ContentNegotiationManagerFactoryBean if (this.favorPathExtension) { PathExtensionContentNegotiationStrategy strategy; - if (this.servletContext != null) { + if (this.servletContext != null && !useRegisteredExtensionsOnly()) { strategy = new ServletPathExtensionContentNegotiationStrategy( this.servletContext, this.mediaTypes); } @@ -252,6 +271,9 @@ public class ContentNegotiationManagerFactoryBean strategy = new PathExtensionContentNegotiationStrategy(this.mediaTypes); } strategy.setIgnoreUnknownExtensions(this.ignoreUnknownPathExtensions); + if (this.useRegisteredExtensionsOnly != null) { + strategy.setUseRegisteredExtensionsOnly(this.useRegisteredExtensionsOnly); + } strategies.add(strategy); } diff --git a/spring-web/src/main/java/org/springframework/web/accept/PathExtensionContentNegotiationStrategy.java b/spring-web/src/main/java/org/springframework/web/accept/PathExtensionContentNegotiationStrategy.java index 2795db7447..8e0a1b7ecb 100644 --- a/spring-web/src/main/java/org/springframework/web/accept/PathExtensionContentNegotiationStrategy.java +++ b/spring-web/src/main/java/org/springframework/web/accept/PathExtensionContentNegotiationStrategy.java @@ -51,6 +51,8 @@ public class PathExtensionContentNegotiationStrategy extends AbstractMappingCont private UrlPathHelper urlPathHelper = new UrlPathHelper(); + private boolean useRegisteredExtensionsOnly = false; + private boolean ignoreUnknownExtensions = true; @@ -81,10 +83,20 @@ public class PathExtensionContentNegotiationStrategy extends AbstractMappingCont } /** - * @deprecated as 5.0, in favor of {@link MediaTypeFactory}, which has no JAF dependency. + * @deprecated as of 5.0, in favor of {@link #setUseRegisteredExtensionsOnly(boolean)}. */ @Deprecated public void setUseJaf(boolean useJaf) { + setUseRegisteredExtensionsOnly(!useJaf); + } + + /** + * Whether to only use the registered mappings to look up file extensions, or also refer to + * defaults. + *

By default this is set to {@code false}, meaning that defaults are used. + */ + public void setUseRegisteredExtensionsOnly(boolean useRegisteredExtensionsOnly) { + this.useRegisteredExtensionsOnly = useRegisteredExtensionsOnly; } /** @@ -113,9 +125,11 @@ public class PathExtensionContentNegotiationStrategy extends AbstractMappingCont protected MediaType handleNoMatch(NativeWebRequest webRequest, String extension) throws HttpMediaTypeNotAcceptableException { - Optional mediaType = MediaTypeFactory.getMediaType("file." + extension); - if (mediaType.isPresent()) { - return mediaType.get(); + if (!this.useRegisteredExtensionsOnly) { + Optional mediaType = MediaTypeFactory.getMediaType("file." + extension); + if (mediaType.isPresent()) { + return mediaType.get(); + } } if (this.ignoreUnknownExtensions) { return null; diff --git a/spring-web/src/main/java/org/springframework/web/accept/ServletPathExtensionContentNegotiationStrategy.java b/spring-web/src/main/java/org/springframework/web/accept/ServletPathExtensionContentNegotiationStrategy.java index 05a27a66ae..86a212e50a 100644 --- a/spring-web/src/main/java/org/springframework/web/accept/ServletPathExtensionContentNegotiationStrategy.java +++ b/spring-web/src/main/java/org/springframework/web/accept/ServletPathExtensionContentNegotiationStrategy.java @@ -93,6 +93,7 @@ public class ServletPathExtensionContentNegotiationStrategy extends PathExtensio * @return the MediaType for the extension or {@code null}. * @since 4.3 */ + @Override public MediaType getMediaTypeForResource(Resource resource) { MediaType mediaType = null; if (this.servletContext != null) { diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/accept/PathExtensionContentTypeResolver.java b/spring-webflux/src/main/java/org/springframework/web/reactive/accept/PathExtensionContentTypeResolver.java index a7944b41b9..7ad79e0383 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/accept/PathExtensionContentTypeResolver.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/accept/PathExtensionContentTypeResolver.java @@ -42,6 +42,8 @@ import org.springframework.web.util.UriUtils; */ public class PathExtensionContentTypeResolver extends AbstractMappingContentTypeResolver { + private boolean useRegisteredExtensionsOnly = false; + private boolean ignoreUnknownExtensions = true; @@ -61,6 +63,15 @@ public class PathExtensionContentTypeResolver extends AbstractMappingContentType } + /** + * Whether to only use the registered mappings to look up file extensions, or also refer to + * defaults. + *

By default this is set to {@code false}, meaning that defaults are used. + */ + public void setUseRegisteredExtensionsOnly(boolean useRegisteredExtensionsOnly) { + this.useRegisteredExtensionsOnly = useRegisteredExtensionsOnly; + } + /** * Whether to ignore requests with unknown file extension. Setting this to * {@code false} results in {@code HttpMediaTypeNotAcceptableException}. @@ -80,14 +91,16 @@ public class PathExtensionContentTypeResolver extends AbstractMappingContentType @Override protected MediaType handleNoMatch(String key) throws NotAcceptableStatusException { - Optional mediaType = MediaTypeFactory.getMediaType("file." + key); - if (mediaType.isPresent()) { - return mediaType.get(); + if (!this.useRegisteredExtensionsOnly) { + Optional mediaType = MediaTypeFactory.getMediaType("file." + key); + if (mediaType.isPresent()) { + return mediaType.get(); + } } - if (!this.ignoreUnknownExtensions) { - throw new NotAcceptableStatusException(getAllMediaTypes()); + if (this.ignoreUnknownExtensions) { + return null; } - return null; + throw new NotAcceptableStatusException(getAllMediaTypes()); } /** diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/accept/RequestedContentTypeResolverBuilder.java b/spring-webflux/src/main/java/org/springframework/web/reactive/accept/RequestedContentTypeResolverBuilder.java index 101b864e17..893ed225e8 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/accept/RequestedContentTypeResolverBuilder.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/accept/RequestedContentTypeResolverBuilder.java @@ -93,6 +93,8 @@ public class RequestedContentTypeResolverBuilder { private boolean ignoreUnknownPathExtensions = true; + private Boolean useRegisteredExtensionsOnly; + private String parameterName = "format"; private RequestedContentTypeResolver contentTypeResolver; @@ -152,6 +154,18 @@ public class RequestedContentTypeResolverBuilder { return this; } + /** + * When {@link #favorPathExtension favorPathExtension} is set, this + * property determines whether to use only registered {@code MediaType} mappings + * to resolve a path extension to a specific MediaType. + *

By default this is not set in which case + * {@code PathExtensionContentNegotiationStrategy} will use defaults if available. + */ + public RequestedContentTypeResolverBuilder useRegisteredExtensionsOnly(boolean useRegisteredExtensionsOnly) { + this.useRegisteredExtensionsOnly = useRegisteredExtensionsOnly; + return this; + } + /** * Whether a request parameter ("format" by default) should be used to * determine the requested media type. For this option to work you must @@ -211,6 +225,9 @@ public class RequestedContentTypeResolverBuilder { if (this.favorPathExtension) { PathExtensionContentTypeResolver resolver = new PathExtensionContentTypeResolver(this.mediaTypes); resolver.setIgnoreUnknownExtensions(this.ignoreUnknownPathExtensions); + if (this.useRegisteredExtensionsOnly != null) { + resolver.setUseRegisteredExtensionsOnly(this.useRegisteredExtensionsOnly); + } resolvers.add(resolver); } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/ContentNegotiationConfigurer.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/ContentNegotiationConfigurer.java index 27f22f766a..c085e62a29 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/ContentNegotiationConfigurer.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/ContentNegotiationConfigurer.java @@ -120,7 +120,7 @@ public class ContentNegotiationConfigurer { * attack protection). *

The path extension strategy will also try to use * {@link ServletContext#getMimeType} and {@link MediaTypeFactory} to resolve path - * extensions. + * extensions. To change this behavior see the {@link #useRegisteredExtensionsOnly} property. * @param extension the key to look up * @param mediaType the media type * @see #mediaTypes(Map) @@ -166,10 +166,23 @@ public class ContentNegotiationConfigurer { } /** - * @deprecated as 5.0, in favor of {@link MediaTypeFactory}, which has no JAF dependency. + * @deprecated as of 5.0, in favor of {@link #useRegisteredExtensionsOnly(boolean)}, which + * has reverse behavior. */ @Deprecated public ContentNegotiationConfigurer useJaf(boolean useJaf) { + return this.useRegisteredExtensionsOnly(!useJaf); + } + + /** + * When {@link #favorPathExtension favorPathExtension} is set, this + * property determines whether to use only registered {@code MediaType} mappings + * to resolve a path extension to a specific MediaType. + *

By default this is not set in which case + * {@code PathExtensionContentNegotiationStrategy} will use defaults if available. + */ + public ContentNegotiationConfigurer useRegisteredExtensionsOnly(boolean useRegisteredExtensionsOnly) { + this.factory.setUseRegisteredExtensionsOnly(useRegisteredExtensionsOnly); return this; }