From 1f283acb9820f19baded69fe9cb797dce071197d Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Mon, 18 Apr 2016 16:38:33 -0400 Subject: [PATCH] Add path extension and parameter ContentTypeResolver's --- .../AbstractMappingContentTypeResolver.java | 159 ++++++++++++++ .../accept/ParameterContentTypeResolver.java | 82 ++++++++ .../PathExtensionContentTypeResolver.java | 196 ++++++++++++++++++ .../MappingContentTypeResolverTests.java | 122 +++++++++++ ...ensionContentNegotiationStrategyTests.java | 119 +++++++++++ 5 files changed, 678 insertions(+) create mode 100644 spring-web-reactive/src/main/java/org/springframework/web/reactive/accept/AbstractMappingContentTypeResolver.java create mode 100644 spring-web-reactive/src/main/java/org/springframework/web/reactive/accept/ParameterContentTypeResolver.java create mode 100644 spring-web-reactive/src/main/java/org/springframework/web/reactive/accept/PathExtensionContentTypeResolver.java create mode 100644 spring-web-reactive/src/test/java/org/springframework/web/reactive/accept/MappingContentTypeResolverTests.java create mode 100644 spring-web-reactive/src/test/java/org/springframework/web/reactive/accept/PathExtensionContentNegotiationStrategyTests.java diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/accept/AbstractMappingContentTypeResolver.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/accept/AbstractMappingContentTypeResolver.java new file mode 100644 index 00000000000..3e89fe8a736 --- /dev/null +++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/accept/AbstractMappingContentTypeResolver.java @@ -0,0 +1,159 @@ +/* + * Copyright 2002-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.web.reactive.accept; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import org.springframework.http.MediaType; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.util.StringUtils; +import org.springframework.web.HttpMediaTypeNotAcceptableException; +import org.springframework.web.server.ServerWebExchange; + +/** + * Abstract base class for {@link MappingContentTypeResolver} implementations. + * Maintains the actual mappings and pre-implements the overall algorithm with + * sub-classes left to provide a way to extract the lookup key (e.g. file + * extension, query parameter, etc) for a given exchange. + * + * @author Rossen Stoyanchev + */ +public abstract class AbstractMappingContentTypeResolver implements MappingContentTypeResolver { + + /** Primary lookup for media types by key (e.g. "json" -> "application/json") */ + private final ConcurrentMap mediaTypeLookup = new ConcurrentHashMap<>(64); + + /** Reverse lookup for keys associated with a media type */ + private final MultiValueMap keyLookup = new LinkedMultiValueMap<>(64); + + + /** + * Create an instance with the given map of file extensions and media types. + */ + public AbstractMappingContentTypeResolver(Map mediaTypes) { + if (mediaTypes != null) { + for (Map.Entry entry : mediaTypes.entrySet()) { + String extension = entry.getKey().toLowerCase(Locale.ENGLISH); + MediaType mediaType = entry.getValue(); + this.mediaTypeLookup.put(extension, mediaType); + this.keyLookup.add(mediaType, extension); + } + } + } + + + /** + * Sub-classes can use this method to look up a MediaType by key. + * @param key the key converted to lower case + * @return a MediaType or {@code null} + */ + protected MediaType getMediaType(String key) { + return this.mediaTypeLookup.get(key.toLowerCase(Locale.ENGLISH)); + } + + /** + * Sub-classes can use this method get all mapped media types. + */ + protected List getMediaTypes() { + return new ArrayList<>(this.mediaTypeLookup.values()); + } + + + // ContentTypeResolver implementation + + @Override + public List resolveMediaTypes(ServerWebExchange exchange) + throws HttpMediaTypeNotAcceptableException { + + String key = extractKey(exchange); + return resolveMediaTypes(key); + } + + /** + * An overloaded resolve method with a pre-resolved lookup key. + * @param key the key for looking up media types + * @return a list of resolved media types or an empty list + * @throws HttpMediaTypeNotAcceptableException + */ + public List resolveMediaTypes(String key) + throws HttpMediaTypeNotAcceptableException { + + if (StringUtils.hasText(key)) { + MediaType mediaType = getMediaType(key); + if (mediaType != null) { + handleMatch(key, mediaType); + return Collections.singletonList(mediaType); + } + mediaType = handleNoMatch(key); + if (mediaType != null) { + MediaType previous = this.mediaTypeLookup.putIfAbsent(key, mediaType); + if (previous == null) { + this.keyLookup.add(mediaType, key); + } + return Collections.singletonList(mediaType); + } + } + return Collections.emptyList(); + } + + /** + * Extract the key to use to look up a media type from the given exchange, + * e.g. file extension, query parameter, etc. + * @return the key or {@code null} + */ + protected abstract String extractKey(ServerWebExchange exchange); + + /** + * Override to provide handling when a key is successfully resolved via + * {@link #getMediaType(String)}. + */ + @SuppressWarnings("UnusedParameters") + protected void handleMatch(String key, MediaType mediaType) { + } + + /** + * Override to provide handling when a key is not resolved via. + * {@link #getMediaType(String)}. If a MediaType is returned from + * this method it will be added to the mappings. + */ + @SuppressWarnings("UnusedParameters") + protected MediaType handleNoMatch(String key) throws HttpMediaTypeNotAcceptableException { + return null; + } + + // MappingContentTypeResolver implementation + + @Override + public Set getKeysFor(MediaType mediaType) { + List keys = this.keyLookup.get(mediaType); + return (keys != null ? new HashSet<>(keys) : Collections.emptySet()); + } + + @Override + public Set getKeys() { + return new HashSet<>(this.mediaTypeLookup.keySet()); + } + +} diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/accept/ParameterContentTypeResolver.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/accept/ParameterContentTypeResolver.java new file mode 100644 index 00000000000..4b6ed63678b --- /dev/null +++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/accept/ParameterContentTypeResolver.java @@ -0,0 +1,82 @@ +/* + * Copyright 2002-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.web.reactive.accept; + +import java.util.Map; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.http.MediaType; +import org.springframework.util.Assert; +import org.springframework.web.HttpMediaTypeNotAcceptableException; +import org.springframework.web.server.ServerWebExchange; + +/** + * A {@link ContentTypeResolver} that extracts the media type lookup key from a + * known query parameter named "format" by default. + *s + * @author Rossen Stoyanchev + */ +public class ParameterContentTypeResolver extends AbstractMappingContentTypeResolver { + + private static final Log logger = LogFactory.getLog(ParameterContentTypeResolver.class); + + private String parameterName = "format"; + + + /** + * Create an instance with the given map of file extensions and media types. + */ + public ParameterContentTypeResolver(Map mediaTypes) { + super(mediaTypes); + } + + + /** + * Set the name of the parameter to use to determine requested media types. + *

By default this is set to {@code "format"}. + */ + public void setParameterName(String parameterName) { + Assert.notNull(parameterName, "parameterName is required"); + this.parameterName = parameterName; + } + + public String getParameterName() { + return this.parameterName; + } + + + @Override + protected String extractKey(ServerWebExchange exchange) { + return exchange.getRequest().getQueryParams().getFirst(getParameterName()); + } + + @Override + protected void handleMatch(String mediaTypeKey, MediaType mediaType) { + if (logger.isDebugEnabled()) { + logger.debug("Requested media type is '" + mediaType + + "' based on '" + getParameterName() + "'='" + mediaTypeKey + "'."); + } + } + + @Override + protected MediaType handleNoMatch(String key) throws HttpMediaTypeNotAcceptableException { + throw new HttpMediaTypeNotAcceptableException(getMediaTypes()); + } + +} diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/accept/PathExtensionContentTypeResolver.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/accept/PathExtensionContentTypeResolver.java new file mode 100644 index 00000000000..f8ea0747c8d --- /dev/null +++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/accept/PathExtensionContentTypeResolver.java @@ -0,0 +1,196 @@ +/* + * Copyright 2002-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.web.reactive.accept; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Locale; +import java.util.Map; +import javax.activation.FileTypeMap; +import javax.activation.MimetypesFileTypeMap; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; +import org.springframework.http.MediaType; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; +import org.springframework.util.StringUtils; +import org.springframework.web.HttpMediaTypeNotAcceptableException; +import org.springframework.web.accept.PathExtensionContentNegotiationStrategy; +import org.springframework.web.server.ServerWebExchange; +import org.springframework.web.util.WebUtils; + +/** + * A {@link ContentTypeResolver} that extracts the file extension from the + * request path and uses that as the media type lookup key. + * + *

If the file extension is not found in the explicit registrations provided + * to the constructor, the Java Activation Framework (JAF) is used as a fallback + * mechanism. The presence of the JAF is detected and enabled automatically but + * the {@link #setUseJaf(boolean)} property may be set to false. + * + * @author Rossen Stoyanchev + */ +public class PathExtensionContentTypeResolver extends AbstractMappingContentTypeResolver { + + private static final Log logger = LogFactory.getLog(PathExtensionContentNegotiationStrategy.class); + + private static final boolean JAF_PRESENT = ClassUtils.isPresent( + "javax.activation.FileTypeMap", + PathExtensionContentNegotiationStrategy.class.getClassLoader()); + + + private boolean useJaf = true; + + private boolean ignoreUnknownExtensions = true; + + + /** + * Create an instance with the given map of file extensions and media types. + */ + public PathExtensionContentTypeResolver(Map mediaTypes) { + super(mediaTypes); + } + + /** + * Create an instance without any mappings to start with. Mappings may be added + * later on if any extensions are resolved through the Java Activation framework. + */ + public PathExtensionContentTypeResolver() { + super(null); + } + + + /** + * Whether to use the Java Activation Framework to look up file extensions. + *

By default this is set to "true" but depends on JAF being present. + */ + public void setUseJaf(boolean useJaf) { + this.useJaf = useJaf; + } + + /** + * Whether to ignore requests with unknown file extension. Setting this to + * {@code false} results in {@code HttpMediaTypeNotAcceptableException}. + *

By default this is set to {@code true}. + */ + public void setIgnoreUnknownExtensions(boolean ignoreUnknownExtensions) { + this.ignoreUnknownExtensions = ignoreUnknownExtensions; + } + + + @Override + protected String extractKey(ServerWebExchange exchange) { + String path = exchange.getRequest().getURI().getRawPath(); + String filename = WebUtils.extractFullFilenameFromUrlPath(path); + String extension = StringUtils.getFilenameExtension(filename); + return (StringUtils.hasText(extension)) ? extension.toLowerCase(Locale.ENGLISH) : null; + } + + @Override + protected MediaType handleNoMatch(String key) throws HttpMediaTypeNotAcceptableException { + if (this.useJaf && JAF_PRESENT) { + MediaType mediaType = JafMediaTypeFactory.getMediaType("file." + key); + if (mediaType != null && !MediaType.APPLICATION_OCTET_STREAM.equals(mediaType)) { + return mediaType; + } + } + if (!this.ignoreUnknownExtensions) { + throw new HttpMediaTypeNotAcceptableException(getMediaTypes()); + } + return null; + } + + /** + * A public method exposing the knowledge of the path extension resolver to + * determine the media type for a given {@link Resource}. First it checks + * the explicitly registered mappings and then falls back on JAF. + * @param resource the resource + * @return the MediaType for the extension or {@code null}. + */ + public MediaType resolveMediaTypeForResource(Resource resource) { + Assert.notNull(resource); + MediaType mediaType = null; + String filename = resource.getFilename(); + String extension = StringUtils.getFilenameExtension(filename); + if (extension != null) { + mediaType = getMediaType(extension); + } + if (mediaType == null && JAF_PRESENT) { + mediaType = JafMediaTypeFactory.getMediaType(filename); + } + if (MediaType.APPLICATION_OCTET_STREAM.equals(mediaType)) { + mediaType = null; + } + return mediaType; + } + + + /** + * Inner class to avoid hard-coded dependency on JAF. + */ + private static class JafMediaTypeFactory { + + private static final FileTypeMap fileTypeMap; + + static { + fileTypeMap = initFileTypeMap(); + } + + /** + * Find extended mime.types from the spring-context-support module. + */ + private static FileTypeMap initFileTypeMap() { + Resource resource = new ClassPathResource("org/springframework/mail/javamail/mime.types"); + if (resource.exists()) { + if (logger.isTraceEnabled()) { + logger.trace("Loading JAF FileTypeMap from " + resource); + } + InputStream inputStream = null; + try { + inputStream = resource.getInputStream(); + return new MimetypesFileTypeMap(inputStream); + } + catch (IOException ex) { + // ignore + } + finally { + if (inputStream != null) { + try { + inputStream.close(); + } + catch (IOException ex) { + // ignore + } + } + } + } + if (logger.isTraceEnabled()) { + logger.trace("Loading default Java Activation Framework FileTypeMap"); + } + return FileTypeMap.getDefaultFileTypeMap(); + } + + public static MediaType getMediaType(String filename) { + String mediaType = fileTypeMap.getContentType(filename); + return (StringUtils.hasText(mediaType) ? MediaType.parseMediaType(mediaType) : null); + } + } + +} diff --git a/spring-web-reactive/src/test/java/org/springframework/web/reactive/accept/MappingContentTypeResolverTests.java b/spring-web-reactive/src/test/java/org/springframework/web/reactive/accept/MappingContentTypeResolverTests.java new file mode 100644 index 00000000000..6c2cd5a30a9 --- /dev/null +++ b/spring-web-reactive/src/test/java/org/springframework/web/reactive/accept/MappingContentTypeResolverTests.java @@ -0,0 +1,122 @@ +/* + * Copyright 2002-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.web.reactive.accept; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.junit.Test; + +import org.springframework.http.MediaType; +import org.springframework.web.server.ServerWebExchange; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** + * Unit tests for {@link AbstractMappingContentTypeResolver}. + * @author Rossen Stoyanchev + */ +public class MappingContentTypeResolverTests { + + @Test + public void resolveExtensions() { + Map mapping = Collections.singletonMap("json", MediaType.APPLICATION_JSON); + TestMappingContentTypeResolver resolver = new TestMappingContentTypeResolver("", mapping); + Set keys = resolver.getKeysFor(MediaType.APPLICATION_JSON); + + assertEquals(1, keys.size()); + assertEquals("json", keys.iterator().next()); + } + + @Test + public void resolveExtensionsNoMatch() { + Map mapping = Collections.singletonMap("json", MediaType.APPLICATION_JSON); + TestMappingContentTypeResolver resolver = new TestMappingContentTypeResolver("", mapping); + Set keys = resolver.getKeysFor(MediaType.TEXT_HTML); + + assertTrue(keys.isEmpty()); + } + + @Test // SPR-13747 + public void lookupMediaTypeCaseInsensitive() { + Map mapping = Collections.singletonMap("json", MediaType.APPLICATION_JSON); + TestMappingContentTypeResolver resolver = new TestMappingContentTypeResolver("", mapping); + MediaType mediaType = resolver.getMediaType("JSoN"); + + assertEquals(mediaType, MediaType.APPLICATION_JSON); + } + + @Test + public void resolveMediaTypes() throws Exception { + Map mapping = Collections.singletonMap("json", MediaType.APPLICATION_JSON); + TestMappingContentTypeResolver resolver = new TestMappingContentTypeResolver("json", mapping); + List mediaTypes = resolver.resolveMediaTypes((ServerWebExchange) null); + + assertEquals(1, mediaTypes.size()); + assertEquals("application/json", mediaTypes.get(0).toString()); + } + + @Test + public void resolveMediaTypesNoMatch() throws Exception { + TestMappingContentTypeResolver resolver = new TestMappingContentTypeResolver("blah", null); + List mediaTypes = resolver.resolveMediaTypes((ServerWebExchange) null); + + assertEquals(0, mediaTypes.size()); + } + + @Test + public void resolveMediaTypesNoKey() throws Exception { + Map mapping = Collections.singletonMap("json", MediaType.APPLICATION_JSON); + TestMappingContentTypeResolver resolver = new TestMappingContentTypeResolver(null, mapping); + List mediaTypes = resolver.resolveMediaTypes((ServerWebExchange) null); + + assertEquals(0, mediaTypes.size()); + } + + @Test + public void resolveMediaTypesHandleNoMatch() throws Exception { + TestMappingContentTypeResolver resolver = new TestMappingContentTypeResolver("xml", null); + List mediaTypes = resolver.resolveMediaTypes((ServerWebExchange) null); + + assertEquals(1, mediaTypes.size()); + assertEquals("application/xml", mediaTypes.get(0).toString()); + } + + + private static class TestMappingContentTypeResolver extends AbstractMappingContentTypeResolver { + + private final String key; + + public TestMappingContentTypeResolver(String key, Map mapping) { + super(mapping); + this.key = key; + } + + @Override + protected String extractKey(ServerWebExchange exchange) { + return this.key; + } + + @Override + protected MediaType handleNoMatch(String mappingKey) { + return "xml".equals(mappingKey) ? MediaType.APPLICATION_XML : null; + } + } + +} diff --git a/spring-web-reactive/src/test/java/org/springframework/web/reactive/accept/PathExtensionContentNegotiationStrategyTests.java b/spring-web-reactive/src/test/java/org/springframework/web/reactive/accept/PathExtensionContentNegotiationStrategyTests.java new file mode 100644 index 00000000000..4399f7c56e7 --- /dev/null +++ b/spring-web-reactive/src/test/java/org/springframework/web/reactive/accept/PathExtensionContentNegotiationStrategyTests.java @@ -0,0 +1,119 @@ +/* + * Copyright 2002-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.web.reactive.accept; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import org.junit.Test; + +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.http.server.reactive.MockServerHttpRequest; +import org.springframework.http.server.reactive.MockServerHttpResponse; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.web.HttpMediaTypeNotAcceptableException; +import org.springframework.web.server.ServerWebExchange; +import org.springframework.web.server.adapter.DefaultServerWebExchange; +import org.springframework.web.server.session.WebSessionManager; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; + +/** + * Unit tests for {@link PathExtensionContentTypeResolver}. + * + * @author Rossen Stoyanchev + */ +public class PathExtensionContentNegotiationStrategyTests { + + @Test + public void resolveMediaTypesFromMapping() throws Exception { + ServerWebExchange exchange = createExchange("/test.html"); + PathExtensionContentTypeResolver resolver = new PathExtensionContentTypeResolver(); + List mediaTypes = resolver.resolveMediaTypes(exchange); + + assertEquals(Collections.singletonList(new MediaType("text", "html")), mediaTypes); + + Map mapping = Collections.singletonMap("HTML", MediaType.APPLICATION_XHTML_XML); + resolver = new PathExtensionContentTypeResolver(mapping); + mediaTypes = resolver.resolveMediaTypes(exchange); + + assertEquals(Collections.singletonList(new MediaType("application", "xhtml+xml")), mediaTypes); + } + + @Test + public void resolveMediaTypesFromJaf() throws Exception { + ServerWebExchange exchange = createExchange("test.xls"); + PathExtensionContentTypeResolver resolver = new PathExtensionContentTypeResolver(); + List mediaTypes = resolver.resolveMediaTypes(exchange); + + assertEquals(Collections.singletonList(new MediaType("application", "vnd.ms-excel")), mediaTypes); + } + + // SPR-10334 + + @Test + public void getMediaTypeFromFilenameNoJaf() throws Exception { + ServerWebExchange exchange = createExchange("test.json"); + PathExtensionContentTypeResolver resolver = new PathExtensionContentTypeResolver(); + resolver.setUseJaf(false); + List mediaTypes = resolver.resolveMediaTypes(exchange); + + assertEquals(Collections.emptyList(), mediaTypes); + } + + // SPR-9390 + + @Test + public void getMediaTypeFilenameWithEncodedURI() throws Exception { + ServerWebExchange exchange = createExchange("/quo%20vadis%3f.html"); + PathExtensionContentTypeResolver resolver = new PathExtensionContentTypeResolver(); + List result = resolver.resolveMediaTypes(exchange); + + assertEquals("Invalid content type", Collections.singletonList(new MediaType("text", "html")), result); + } + + // SPR-10170 + + @Test + public void resolveMediaTypesIgnoreUnknownExtension() throws Exception { + ServerWebExchange exchange = createExchange("test.xyz"); + PathExtensionContentTypeResolver resolver = new PathExtensionContentTypeResolver(); + List mediaTypes = resolver.resolveMediaTypes(exchange); + + assertEquals(Collections.emptyList(), mediaTypes); + } + + @Test(expected = HttpMediaTypeNotAcceptableException.class) + public void resolveMediaTypesDoNotIgnoreUnknownExtension() throws Exception { + ServerWebExchange exchange = createExchange("test.xyz"); + PathExtensionContentTypeResolver resolver = new PathExtensionContentTypeResolver(); + resolver.setIgnoreUnknownExtensions(false); + resolver.resolveMediaTypes(exchange); + } + + + private ServerWebExchange createExchange(String path) throws URISyntaxException { + ServerHttpRequest request = new MockServerHttpRequest(HttpMethod.GET, new URI(path)); + WebSessionManager sessionManager = mock(WebSessionManager.class); + return new DefaultServerWebExchange(request, new MockServerHttpResponse(), sessionManager); + } + +}