From 01a92517bdb657193f2aabc99c20113dada232fa Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Thu, 8 Jun 2017 20:11:41 -0400 Subject: [PATCH] Refactor RequestedContentTypeResolverBuilder The revised builder emphasizes creating a list of resolvers either built-in or custom with each top-level builder method resulting in adding a resolver. By default only the Header resolver is configured. The path extension resolver is removed altogether to discourage its use but is trivial to create manually with the helpf of UriUtils#extractFileExtension + MediaTypeFactory. Issue: SPR-15639 --- .../AbstractMappingContentTypeResolver.java | 82 ------ .../accept/CompositeContentTypeResolver.java | 55 ---- .../accept/FixedContentTypeResolver.java | 5 +- .../accept/HeaderContentTypeResolver.java | 2 +- .../accept/ParameterContentTypeResolver.java | 46 +++- .../PathExtensionContentTypeResolver.java | 45 ---- .../accept/RequestedContentTypeResolver.java | 8 +- .../RequestedContentTypeResolverBuilder.java | 234 +++++++----------- .../config/WebFluxConfigurationSupport.java | 19 +- .../reactive/config/WebFluxConfigurer.java | 8 +- .../condition/ProducesRequestCondition.java | 7 +- ...positeContentTypeResolverBuilderTests.java | 132 ---------- .../MappingContentTypeResolverTests.java | 103 -------- .../ParameterContentTypeResolverTests.java | 92 +++++++ ...PathExtensionContentTypeResolverTests.java | 83 ------- ...uestedContentTypeResolverBuilderTests.java | 100 ++++++++ .../WebFluxConfigurationSupportTests.java | 10 +- .../MessageWriterResultHandlerTests.java | 12 +- .../ResponseBodyResultHandlerTests.java | 2 +- .../ResponseEntityResultHandlerTests.java | 2 +- 20 files changed, 340 insertions(+), 707 deletions(-) delete mode 100644 spring-webflux/src/main/java/org/springframework/web/reactive/accept/AbstractMappingContentTypeResolver.java delete mode 100644 spring-webflux/src/main/java/org/springframework/web/reactive/accept/CompositeContentTypeResolver.java delete mode 100644 spring-webflux/src/main/java/org/springframework/web/reactive/accept/PathExtensionContentTypeResolver.java delete mode 100644 spring-webflux/src/test/java/org/springframework/web/reactive/accept/CompositeContentTypeResolverBuilderTests.java delete mode 100644 spring-webflux/src/test/java/org/springframework/web/reactive/accept/MappingContentTypeResolverTests.java create mode 100644 spring-webflux/src/test/java/org/springframework/web/reactive/accept/ParameterContentTypeResolverTests.java delete mode 100644 spring-webflux/src/test/java/org/springframework/web/reactive/accept/PathExtensionContentTypeResolverTests.java create mode 100644 spring-webflux/src/test/java/org/springframework/web/reactive/accept/RequestedContentTypeResolverBuilderTests.java diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/accept/AbstractMappingContentTypeResolver.java b/spring-webflux/src/main/java/org/springframework/web/reactive/accept/AbstractMappingContentTypeResolver.java deleted file mode 100644 index c987bcce54..0000000000 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/accept/AbstractMappingContentTypeResolver.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2002-2017 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.Locale; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -import org.springframework.http.MediaType; -import org.springframework.http.MediaTypeFactory; -import org.springframework.lang.Nullable; -import org.springframework.util.StringUtils; -import org.springframework.web.server.ServerWebExchange; - -/** - * Base class for resolvers that extract a key from the request and look up a - * mapping to a MediaType. The use case is URI-based content negotiation for - * example based on query parameter or file extension in the request path. - * - * @author Rossen Stoyanchev - * @since 5.0 - */ -public abstract class AbstractMappingContentTypeResolver implements RequestedContentTypeResolver { - - /** Primary lookup for media types by key (e.g. "json" -> "application/json") */ - private final Map mediaTypeLookup = new ConcurrentHashMap<>(64); - - - public AbstractMappingContentTypeResolver(Map mediaTypes) { - mediaTypes.forEach((key, mediaType) -> - this.mediaTypeLookup.put(key.toLowerCase(Locale.ENGLISH), mediaType)); - } - - - @Override - public List resolveMediaTypes(ServerWebExchange exchange) { - String key = getKey(exchange); - if (StringUtils.hasText(key)) { - MediaType mediaType = getMediaType(key); - if (mediaType != null) { - this.mediaTypeLookup.putIfAbsent(key, mediaType); - return Collections.singletonList(mediaType); - } - } - return Collections.emptyList(); - } - - /** - * Get the key to look up a MediaType with. - */ - @Nullable - protected abstract String getKey(ServerWebExchange exchange); - - /** - * Get the MediaType for the given key. - */ - @Nullable - protected MediaType getMediaType(String key) { - key = key.toLowerCase(Locale.ENGLISH); - MediaType mediaType = this.mediaTypeLookup.get(key); - if (mediaType == null) { - mediaType = MediaTypeFactory.getMediaType("filename." + key).orElse(null); - } - return mediaType; - } - -} diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/accept/CompositeContentTypeResolver.java b/spring-webflux/src/main/java/org/springframework/web/reactive/accept/CompositeContentTypeResolver.java deleted file mode 100644 index bc2f8c0e60..0000000000 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/accept/CompositeContentTypeResolver.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2002-2017 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.List; - -import org.springframework.http.MediaType; -import org.springframework.util.Assert; -import org.springframework.web.server.ServerWebExchange; - -/** - * Contains and delegates to other {@link RequestedContentTypeResolver}. - * - * @author Rossen Stoyanchev - * @since 5.0 - */ -public class CompositeContentTypeResolver implements RequestedContentTypeResolver { - - private final List resolvers = new ArrayList<>(); - - - public CompositeContentTypeResolver(List resolvers) { - Assert.notEmpty(resolvers, "At least one resolver is expected."); - this.resolvers.addAll(resolvers); - } - - - @Override - public List resolveMediaTypes(ServerWebExchange exchange) { - for (RequestedContentTypeResolver resolver : this.resolvers) { - List mediaTypes = resolver.resolveMediaTypes(exchange); - if (mediaTypes.isEmpty() || (mediaTypes.size() == 1 && mediaTypes.contains(MediaType.ALL))) { - continue; - } - return mediaTypes; - } - return Collections.emptyList(); - } - -} diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/accept/FixedContentTypeResolver.java b/spring-webflux/src/main/java/org/springframework/web/reactive/accept/FixedContentTypeResolver.java index 7f6795d266..e834c14288 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/accept/FixedContentTypeResolver.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/accept/FixedContentTypeResolver.java @@ -26,7 +26,9 @@ import org.springframework.http.MediaType; import org.springframework.web.server.ServerWebExchange; /** - * {@code RequestedContentTypeResolver} with a fixed list of media types. + * Resolver that always resolves to a fixed list of media types. This can be + * used as the "last in line" strategy providing a fallback for when the client + * has not requested any media types. * * @author Rossen Stoyanchev * @since 5.0 @@ -65,7 +67,6 @@ public class FixedContentTypeResolver implements RequestedContentTypeResolver { } - @Override public List resolveMediaTypes(ServerWebExchange exchange) { if (logger.isDebugEnabled()) { diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/accept/HeaderContentTypeResolver.java b/spring-webflux/src/main/java/org/springframework/web/reactive/accept/HeaderContentTypeResolver.java index 04e22bcce4..58d10e85a8 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/accept/HeaderContentTypeResolver.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/accept/HeaderContentTypeResolver.java @@ -23,7 +23,7 @@ import org.springframework.web.server.NotAcceptableStatusException; import org.springframework.web.server.ServerWebExchange; /** - * A {@link RequestedContentTypeResolver} that checks the 'Accept' request header. + * Resolver that looks at the 'Accept' header of the request. * * @author Rossen Stoyanchev * @since 5.0 diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/accept/ParameterContentTypeResolver.java b/spring-webflux/src/main/java/org/springframework/web/reactive/accept/ParameterContentTypeResolver.java index 6c294ef691..29a71761f3 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/accept/ParameterContentTypeResolver.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/accept/ParameterContentTypeResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2017 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. @@ -13,34 +13,50 @@ * 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.List; +import java.util.Locale; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import org.springframework.http.MediaType; +import org.springframework.http.MediaTypeFactory; import org.springframework.util.Assert; +import org.springframework.util.StringUtils; +import org.springframework.web.server.NotAcceptableStatusException; import org.springframework.web.server.ServerWebExchange; /** - * Query parameter based {@link AbstractMappingContentTypeResolver}. + * Resolver that checks a query parameter and uses it to lookup a matching + * MediaType. Lookup keys can be registered or as a fallback + * {@link MediaTypeFactory} can be used to perform a lookup. * * @author Rossen Stoyanchev * @since 5.0 */ -public class ParameterContentTypeResolver extends AbstractMappingContentTypeResolver { +public class ParameterContentTypeResolver implements RequestedContentTypeResolver { + + /** Primary lookup for media types by key (e.g. "json" -> "application/json") */ + private final Map mediaTypes = new ConcurrentHashMap<>(64); private String parameterName = "format"; public ParameterContentTypeResolver(Map mediaTypes) { - super(mediaTypes); + mediaTypes.forEach((key, value) -> this.mediaTypes.put(formatKey(key), value)); + } + + private static String formatKey(String key) { + return key.toLowerCase(Locale.ENGLISH); } /** * Set the name of the parameter to use to determine requested media types. - *

By default this is set to {@code "format"}. + *

By default this is set to {@literal "format"}. */ public void setParameterName(String parameterName) { Assert.notNull(parameterName, "'parameterName' is required"); @@ -53,8 +69,22 @@ public class ParameterContentTypeResolver extends AbstractMappingContentTypeReso @Override - protected String getKey(ServerWebExchange exchange) { - return exchange.getRequest().getQueryParams().getFirst(getParameterName()); + public List resolveMediaTypes(ServerWebExchange exchange) throws NotAcceptableStatusException { + String key = exchange.getRequest().getQueryParams().getFirst(getParameterName()); + if (!StringUtils.hasText(key)) { + return Collections.emptyList(); + } + key = formatKey(key); + MediaType match = this.mediaTypes.get(key); + if (match == null) { + match = MediaTypeFactory.getMediaType("filename." + key) + .orElseThrow(() -> { + List supported = new ArrayList<>(this.mediaTypes.values()); + return new NotAcceptableStatusException(supported); + }); + } + this.mediaTypes.putIfAbsent(key, match); + return Collections.singletonList(match); } } 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 deleted file mode 100644 index 41b0359eb7..0000000000 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/accept/PathExtensionContentTypeResolver.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2002-2017 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.springframework.http.MediaType; -import org.springframework.web.server.ServerWebExchange; -import org.springframework.web.util.UriUtils; - -/** - * Path file extension sub-class of {@link AbstractMappingContentTypeResolver}. - * - * @author Rossen Stoyanchev - * @since 5.0 - */ -public class PathExtensionContentTypeResolver extends AbstractMappingContentTypeResolver { - - - public PathExtensionContentTypeResolver(Map mediaTypes) { - super(mediaTypes); - } - - - @Override - protected String getKey(ServerWebExchange exchange) { - String path = exchange.getRequest().getURI().getRawPath(); - return UriUtils.extractFileExtension(path); - } - -} diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/accept/RequestedContentTypeResolver.java b/spring-webflux/src/main/java/org/springframework/web/reactive/accept/RequestedContentTypeResolver.java index 87e8ce2372..d4479708c6 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/accept/RequestedContentTypeResolver.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/accept/RequestedContentTypeResolver.java @@ -23,8 +23,10 @@ import org.springframework.web.server.NotAcceptableStatusException; import org.springframework.web.server.ServerWebExchange; /** - * Strategy for resolving the requested media types for a - * {@code ServerWebExchange}. + * Strategy to resolve the requested media types for a {@code ServerWebExchange}. + * + *

See {@link RequestedContentTypeResolverBuilder} to create a sequence of + * strategies. * * @author Rossen Stoyanchev * @since 5.0 @@ -36,7 +38,7 @@ public interface RequestedContentTypeResolver { * list is ordered by specificity first and by quality parameter second. * @param exchange the current exchange * @return the requested media types or an empty list - * @throws NotAcceptableStatusException if the requested media types is invalid + * @throws NotAcceptableStatusException if the requested media type is invalid */ List resolveMediaTypes(ServerWebExchange exchange); 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 09eff60a04..5a88cc4a97 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 @@ -17,209 +17,141 @@ package org.springframework.web.reactive.accept; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.List; -import java.util.Locale; import java.util.Map; +import java.util.function.Supplier; +import java.util.stream.Collectors; import org.springframework.http.MediaType; -import org.springframework.util.Assert; -import org.springframework.util.CollectionUtils; /** - * Factory to create a {@link CompositeContentTypeResolver} and configure it with - * one or more {@link RequestedContentTypeResolver} instances with build style - * methods. The following table shows methods, resulting strategy instances, and - * if in use by default: + * Builder for a composite {@link RequestedContentTypeResolver} that delegates + * to one or more other resolvers each implementing a different strategy to + * determine the requested content type(s), e.g. from the Accept header, + * through a query parameter, or other custom strategy. * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
Property SetterUnderlying StrategyDefault Setting
{@link #favorPathExtension}{@link PathExtensionContentTypeResolver Path Extension resolver}On
{@link #favorParameter}{@link ParameterContentTypeResolver Parameter resolver}Off
{@link #ignoreAcceptHeader}{@link HeaderContentTypeResolver Header resolver}Off
{@link #defaultContentType}{@link FixedContentTypeResolver Fixed content resolver}Not set
{@link #defaultContentTypeResolver}{@link RequestedContentTypeResolver}Not set
+ *

Use methods of this builder to add resolvers in the desired order. + * The result of the first resolver to return a non-empty list of media types + * is used. * - *

The order in which resolvers are configured is fixed. Config methods may - * only turn individual resolvers on or off. If you need a custom order for any - * reason simply instantiate {@code {@link CompositeContentTypeResolver}} - * directly. - * - *

For the path extension and parameter resolvers you may explicitly add - * {@link #mediaTypes(Map)}. This will be used to resolve path extensions or a - * parameter value such as "json" to a media type such as "application/json". - * - *

The path extension strategy will also use - * {@link org.springframework.http.MediaTypeFactory} to resolve a path extension - * to a MediaType. + *

If no resolvers are configured, by default the builder will configure + * {@link HeaderContentTypeResolver} only. * * @author Rossen Stoyanchev * @since 5.0 */ public class RequestedContentTypeResolverBuilder { - private boolean favorPathExtension = true; - - private boolean favorParameter = false; - - private boolean ignoreAcceptHeader = false; - - private Map mediaTypes = new HashMap<>(); - - private String parameterName = "format"; - - private RequestedContentTypeResolver contentTypeResolver; + private final List> candidates = new ArrayList<>(); /** - * Whether the path extension in the URL path should be used to determine - * the requested media type. - *

By default this is set to {@code true} in which case a request - * for {@code /hotels.pdf} will be interpreted as a request for - * {@code "application/pdf"} regardless of the 'Accept' header. + * Add resolver extracting the requested content type from a query parameter. + * By default the expected query parameter name is {@code "format"}. */ - public RequestedContentTypeResolverBuilder favorPathExtension(boolean favorPathExtension) { - this.favorPathExtension = favorPathExtension; - return this; + public ParameterResolverConfigurer parameterResolver() { + ParameterResolverConfigurer parameterBuilder = new ParameterResolverConfigurer(); + this.candidates.add(parameterBuilder::createResolver); + return parameterBuilder; } /** - * Add a mapping from a key, extracted from a path extension or a query - * parameter, to a MediaType. This is required in order for the parameter - * strategy to work. Any extensions explicitly registered here are also - * whitelisted for the purpose of Reflected File Download attack detection - * (see Spring Framework reference documentation for more details on RFD - * attack protection). - *

The path extension strategy will also use the - * {@link org.springframework.http.MediaTypeFactory} to resolve path - * extensions. - * @param mediaTypes media type mappings + * Add resolver extracting the requested content type from the + * {@literal "Accept"} header. */ - public RequestedContentTypeResolverBuilder mediaTypes(Map mediaTypes) { - if (!CollectionUtils.isEmpty(mediaTypes)) { - for (Map.Entry entry : mediaTypes.entrySet()) { - String extension = entry.getKey().toLowerCase(Locale.ENGLISH); - this.mediaTypes.put(extension, entry.getValue()); - } - } - return this; + public void headerResolver() { + this.candidates.add(HeaderContentTypeResolver::new); } /** - * Alternative to {@link #mediaTypes} to add a single mapping. + * Add resolver that always returns a fixed set of media types. + * @param mediaTypes the media types to use */ - public RequestedContentTypeResolverBuilder mediaType(String key, MediaType mediaType) { - this.mediaTypes.put(key, mediaType); - return this; + public void fixedResolver(MediaType... mediaTypes) { + this.candidates.add(() -> new FixedContentTypeResolver(Arrays.asList(mediaTypes))); } /** - * Whether a request parameter ("format" by default) should be used to - * determine the requested media type. For this option to work you must - * register {@link #mediaTypes media type mappings}. - *

By default this is set to {@code false}. - * @see #parameterName + * Add a custom resolver. + * @param resolver the resolver to add */ - public RequestedContentTypeResolverBuilder favorParameter(boolean favorParameter) { - this.favorParameter = favorParameter; - return this; + public void resolver(RequestedContentTypeResolver resolver) { + this.candidates.add(() -> resolver); } /** - * Set the query parameter name to use when {@link #favorParameter} is on. - *

The default parameter name is {@code "format"}. + * Build a {@link RequestedContentTypeResolver} that delegates to the list + * of resolvers configured through this builder. */ - public RequestedContentTypeResolverBuilder parameterName(String parameterName) { - Assert.notNull(parameterName, "parameterName is required"); - this.parameterName = parameterName; - return this; + public RequestedContentTypeResolver build() { + + List resolvers = + this.candidates.isEmpty() ? + Collections.singletonList(new HeaderContentTypeResolver()) : + this.candidates.stream().map(Supplier::get).collect(Collectors.toList()); + + return exchange -> { + for (RequestedContentTypeResolver resolver : resolvers) { + List type = resolver.resolveMediaTypes(exchange); + if (type.isEmpty() || (type.size() == 1 && type.contains(MediaType.ALL))) { + continue; + } + return type; + } + return Collections.emptyList(); + }; } - /** - * Whether to disable checking the 'Accept' request header. - *

By default this value is set to {@code false}. - */ - public RequestedContentTypeResolverBuilder ignoreAcceptHeader(boolean ignoreAcceptHeader) { - this.ignoreAcceptHeader = ignoreAcceptHeader; - return this; - } /** - * Set the default content type(s) to use when no content type is requested - * in order of priority. - * - *

If destinations are present that do not support any of the given media - * types, consider appending {@link MediaType#ALL} at the end. - * - *

By default this is not set. - * - * @see #defaultContentTypeResolver + * Helps to create a {@link ParameterContentTypeResolver}. */ - public RequestedContentTypeResolverBuilder defaultContentType(MediaType... contentTypes) { - this.contentTypeResolver = new FixedContentTypeResolver(Arrays.asList(contentTypes)); - return this; - } + public static class ParameterResolverConfigurer { - /** - * Set a custom {@link RequestedContentTypeResolver} to use to determine - * the content type to use when no content type is requested. - *

By default this is not set. - * @see #defaultContentType - */ - public RequestedContentTypeResolverBuilder defaultContentTypeResolver(RequestedContentTypeResolver resolver) { - this.contentTypeResolver = resolver; - return this; - } + private final Map mediaTypes = new HashMap<>(); + private String parameterName; - public CompositeContentTypeResolver build() { - List resolvers = new ArrayList<>(); - if (this.favorPathExtension) { - resolvers.add(new PathExtensionContentTypeResolver(this.mediaTypes)); + /** + * Configure a mapping between a lookup key (extracted from a query + * parameter value) and a corresponding {@code MediaType}. + * @param key the lookup key + * @param mediaType the MediaType for that key + */ + public ParameterResolverConfigurer mediaType(String key, MediaType mediaType) { + this.mediaTypes.put(key, mediaType); + return this; } - if (this.favorParameter) { - ParameterContentTypeResolver resolver = new ParameterContentTypeResolver(this.mediaTypes); - resolver.setParameterName(this.parameterName); - resolvers.add(resolver); + /** + * Map-based variant of {@link #mediaType(String, MediaType)}. + * @param mediaTypes the mappings to copy + */ + public ParameterResolverConfigurer mediaType(Map mediaTypes) { + this.mediaTypes.putAll(mediaTypes); + return this; } - if (!this.ignoreAcceptHeader) { - resolvers.add(new HeaderContentTypeResolver()); + /** + * Set the name of the parameter to use to determine requested media types. + *

By default this is set to {@literal "format"}. + */ + public ParameterResolverConfigurer parameterName(String parameterName) { + this.parameterName = parameterName; + return this; } - if (this.contentTypeResolver != null) { - resolvers.add(this.contentTypeResolver); + RequestedContentTypeResolver createResolver() { + ParameterContentTypeResolver resolver = new ParameterContentTypeResolver(this.mediaTypes); + if (this.parameterName != null) { + resolver.setParameterName(this.parameterName); + } + return resolver; } - - return new CompositeContentTypeResolver(resolvers); } } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/config/WebFluxConfigurationSupport.java b/spring-webflux/src/main/java/org/springframework/web/reactive/config/WebFluxConfigurationSupport.java index 294d49c446..50ebeca86e 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/config/WebFluxConfigurationSupport.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/config/WebFluxConfigurationSupport.java @@ -16,7 +16,6 @@ package org.springframework.web.reactive.config; -import java.util.HashMap; import java.util.List; import java.util.Map; @@ -34,7 +33,6 @@ import org.springframework.format.Formatter; import org.springframework.format.FormatterRegistry; import org.springframework.format.support.DefaultFormattingConversionService; import org.springframework.format.support.FormattingConversionService; -import org.springframework.http.MediaType; import org.springframework.http.codec.ServerCodecConfigurer; import org.springframework.lang.Nullable; import org.springframework.util.ClassUtils; @@ -47,7 +45,7 @@ import org.springframework.web.bind.support.ConfigurableWebBindingInitializer; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.reactive.DispatcherHandler; import org.springframework.web.reactive.HandlerMapping; -import org.springframework.web.reactive.accept.CompositeContentTypeResolver; +import org.springframework.web.reactive.accept.RequestedContentTypeResolver; import org.springframework.web.reactive.accept.RequestedContentTypeResolverBuilder; import org.springframework.web.reactive.function.server.support.HandlerFunctionAdapter; import org.springframework.web.reactive.function.server.support.RouterFunctionMapping; @@ -147,25 +145,12 @@ public class WebFluxConfigurationSupport implements ApplicationContextAware { } @Bean - public CompositeContentTypeResolver webFluxContentTypeResolver() { + public RequestedContentTypeResolver webFluxContentTypeResolver() { RequestedContentTypeResolverBuilder builder = new RequestedContentTypeResolverBuilder(); - builder.mediaTypes(getDefaultMediaTypeMappings()); configureContentTypeResolver(builder); return builder.build(); } - /** - * Override to configure media type mappings. - * @see RequestedContentTypeResolverBuilder#mediaTypes(Map) - */ - protected Map getDefaultMediaTypeMappings() { - Map map = new HashMap<>(); - if (jackson2Present) { - map.put("json", MediaType.APPLICATION_JSON); - } - return map; - } - /** * Override to configure how the requested content type is resolved. */ diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/config/WebFluxConfigurer.java b/spring-webflux/src/main/java/org/springframework/web/reactive/config/WebFluxConfigurer.java index 3560202e78..bca49f3a4b 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/config/WebFluxConfigurer.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/config/WebFluxConfigurer.java @@ -23,8 +23,6 @@ import org.springframework.http.codec.ServerCodecConfigurer; import org.springframework.lang.Nullable; import org.springframework.validation.MessageCodesResolver; import org.springframework.validation.Validator; -import org.springframework.web.reactive.accept.CompositeContentTypeResolver; -import org.springframework.web.reactive.accept.RequestedContentTypeResolver; import org.springframework.web.reactive.accept.RequestedContentTypeResolverBuilder; import org.springframework.web.reactive.result.method.annotation.ArgumentResolverConfigurer; @@ -45,11 +43,7 @@ public interface WebFluxConfigurer { /** * Configure how the content type requested for the response is resolved. - *

The given builder will create a composite of multiple - * {@link RequestedContentTypeResolver}s, each defining a way to resolve - * the requested content type (accept HTTP header, path extension, - * parameter, etc). - * @param builder factory that creates a {@link CompositeContentTypeResolver} + * @param builder for configuring the resolvers to use */ default void configureContentTypeResolver(RequestedContentTypeResolverBuilder builder) { } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/condition/ProducesRequestCondition.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/condition/ProducesRequestCondition.java index 5a1bcac27a..887ab36065 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/condition/ProducesRequestCondition.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/condition/ProducesRequestCondition.java @@ -28,9 +28,9 @@ import org.springframework.http.MediaType; import org.springframework.web.accept.ContentNegotiationManager; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.cors.reactive.CorsUtils; -import org.springframework.web.reactive.accept.RequestedContentTypeResolverBuilder; -import org.springframework.web.reactive.accept.RequestedContentTypeResolver; import org.springframework.web.reactive.accept.HeaderContentTypeResolver; +import org.springframework.web.reactive.accept.RequestedContentTypeResolver; +import org.springframework.web.reactive.accept.RequestedContentTypeResolverBuilder; import org.springframework.web.server.NotAcceptableStatusException; import org.springframework.web.server.ServerWebExchange; @@ -99,8 +99,7 @@ public final class ProducesRequestCondition extends AbstractRequestCondition(expressions); Collections.sort(this.expressions); - this.contentTypeResolver = (resolver != null ? - resolver : new RequestedContentTypeResolverBuilder().build()); + this.contentTypeResolver = (resolver != null ? resolver : new RequestedContentTypeResolverBuilder().build()); } diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/accept/CompositeContentTypeResolverBuilderTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/accept/CompositeContentTypeResolverBuilderTests.java deleted file mode 100644 index 9996fb3668..0000000000 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/accept/CompositeContentTypeResolverBuilderTests.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright 2002-2017 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 org.junit.Test; - -import org.springframework.http.MediaType; -import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest; -import org.springframework.mock.http.server.reactive.test.MockServerWebExchange; -import org.springframework.web.server.ServerWebExchange; - -import static org.junit.Assert.assertEquals; - -/** - * Unit tests for {@link RequestedContentTypeResolverBuilder}. - * - * @author Rossen Stoyanchev - */ -public class CompositeContentTypeResolverBuilderTests { - - @Test - public void defaultSettings() throws Exception { - RequestedContentTypeResolver resolver = new RequestedContentTypeResolverBuilder().build(); - - MockServerWebExchange exchange = MockServerHttpRequest.get("/flower.gif").toExchange(); - - assertEquals("Should be able to resolve file extensions by default", - Collections.singletonList(MediaType.IMAGE_GIF), resolver.resolveMediaTypes(exchange)); - - exchange = MockServerHttpRequest.get("/flower.foobar").toExchange(); - - assertEquals("Should ignore unknown extensions by default", - Collections.emptyList(), resolver.resolveMediaTypes(exchange)); - - exchange = MockServerHttpRequest.get("/flower?format=gif").toExchange(); - - assertEquals("Should not resolve request parameters by default", - Collections.emptyList(), resolver.resolveMediaTypes(exchange)); - - exchange = MockServerHttpRequest.get("/flower").accept(MediaType.IMAGE_GIF).toExchange(); - - assertEquals("Should resolve Accept header by default", - Collections.singletonList(MediaType.IMAGE_GIF), resolver.resolveMediaTypes(exchange)); - } - - @Test - public void favorPath() throws Exception { - RequestedContentTypeResolver resolver = new RequestedContentTypeResolverBuilder() - .favorPathExtension(true) - .mediaType("foo", new MediaType("application", "foo")) - .mediaType("bar", new MediaType("application", "bar")) - .build(); - - ServerWebExchange exchange = MockServerHttpRequest.get("/flower.foo").toExchange(); - assertEquals(Collections.singletonList(new MediaType("application", "foo")), - resolver.resolveMediaTypes(exchange)); - - exchange = MockServerHttpRequest.get("/flower.bar").toExchange(); - assertEquals(Collections.singletonList(new MediaType("application", "bar")), - resolver.resolveMediaTypes(exchange)); - - exchange = MockServerHttpRequest.get("/flower.gif").toExchange(); - assertEquals(Collections.singletonList(MediaType.IMAGE_GIF), resolver.resolveMediaTypes(exchange)); - } - - @Test - public void favorParameter() throws Exception { - RequestedContentTypeResolver resolver = new RequestedContentTypeResolverBuilder() - .favorParameter(true) - .mediaType("json", MediaType.APPLICATION_JSON) - .build(); - - ServerWebExchange exchange = MockServerHttpRequest.get("/flower?format=json").toExchange(); - - assertEquals(Collections.singletonList(MediaType.APPLICATION_JSON), resolver.resolveMediaTypes(exchange)); - } - - @Test - public void ignoreAcceptHeader() throws Exception { - RequestedContentTypeResolver resolver = new RequestedContentTypeResolverBuilder() - .ignoreAcceptHeader(true) - .build(); - - ServerWebExchange exchange = MockServerHttpRequest.get("/flower").accept(MediaType.IMAGE_GIF).toExchange(); - - assertEquals(Collections.emptyList(), resolver.resolveMediaTypes(exchange)); - } - - @Test // SPR-10513 - public void setDefaultContentType() throws Exception { - RequestedContentTypeResolver resolver = new RequestedContentTypeResolverBuilder() - .defaultContentType(MediaType.APPLICATION_JSON) - .build(); - - ServerWebExchange exchange = MockServerHttpRequest.get("/").accept(MediaType.ALL).toExchange(); - - assertEquals(Collections.singletonList(MediaType.APPLICATION_JSON), resolver.resolveMediaTypes(exchange)); - } - - @Test // SPR-12286 - public void setDefaultContentTypeWithStrategy() throws Exception { - RequestedContentTypeResolver resolver = new RequestedContentTypeResolverBuilder() - .defaultContentTypeResolver(new FixedContentTypeResolver(MediaType.APPLICATION_JSON)) - .build(); - - List expected = Collections.singletonList(MediaType.APPLICATION_JSON); - - ServerWebExchange exchange = MockServerHttpRequest.get("/").toExchange(); - assertEquals(expected, resolver.resolveMediaTypes(exchange)); - - exchange = MockServerHttpRequest.get("/").accept(MediaType.ALL).toExchange(); - assertEquals(expected, resolver.resolveMediaTypes(exchange)); - } - - -} diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/accept/MappingContentTypeResolverTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/accept/MappingContentTypeResolverTests.java deleted file mode 100644 index 241d30dc05..0000000000 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/accept/MappingContentTypeResolverTests.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright 2002-2017 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 org.junit.Test; - -import org.springframework.http.MediaType; -import org.springframework.lang.Nullable; -import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest; -import org.springframework.web.server.NotAcceptableStatusException; -import org.springframework.web.server.ServerWebExchange; - -import static org.junit.Assert.assertEquals; - -/** - * Unit tests for {@link AbstractMappingContentTypeResolver}. - * @author Rossen Stoyanchev - */ -public class MappingContentTypeResolverTests { - - @Test // SPR-13747 - public void resolveCaseInsensitive() { - Map mapping = Collections.singletonMap("json", MediaType.APPLICATION_JSON); - TestMappingContentTypeResolver resolver = new TestMappingContentTypeResolver("JSoN", mapping); - List mediaTypes = resolver.resolve(); - - assertEquals(Collections.singletonList(MediaType.APPLICATION_JSON), mediaTypes); - } - - @Test - public void resolveMediaTypes() throws Exception { - Map mapping = Collections.singletonMap("json", MediaType.APPLICATION_JSON); - TestMappingContentTypeResolver resolver = new TestMappingContentTypeResolver("json", mapping); - List mediaTypes = resolver.resolve(); - - assertEquals(1, mediaTypes.size()); - assertEquals("application/json", mediaTypes.get(0).toString()); - } - - @Test - public void resolveNoMatch() throws Exception { - TestMappingContentTypeResolver resolver = new TestMappingContentTypeResolver("blah", Collections.emptyMap()); - List mediaTypes = resolver.resolve(); - - assertEquals(0, mediaTypes.size()); - } - - @Test - public void resolveNoKey() throws Exception { - Map mapping = Collections.singletonMap("json", MediaType.APPLICATION_JSON); - TestMappingContentTypeResolver resolver = new TestMappingContentTypeResolver(null, mapping); - List mediaTypes = resolver.resolve(); - - assertEquals(0, mediaTypes.size()); - } - - @Test - public void resolveMediaTypesHandleNoMatch() throws Exception { - TestMappingContentTypeResolver resolver = new TestMappingContentTypeResolver("xml", Collections.emptyMap()); - List mediaTypes = resolver.resolve(); - - assertEquals(1, mediaTypes.size()); - assertEquals("application/xml", mediaTypes.get(0).toString()); - } - - - private static class TestMappingContentTypeResolver extends AbstractMappingContentTypeResolver { - - private final String key; - - TestMappingContentTypeResolver(@Nullable String key, Map mapping) { - super(mapping); - this.key = key; - } - - public List resolve() throws NotAcceptableStatusException { - return super.resolveMediaTypes(MockServerHttpRequest.get("/").toExchange()); - } - - @Override - protected String getKey(ServerWebExchange exchange) { - return this.key; - } - } - -} diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/accept/ParameterContentTypeResolverTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/accept/ParameterContentTypeResolverTests.java new file mode 100644 index 0000000000..77e8b46a21 --- /dev/null +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/accept/ParameterContentTypeResolverTests.java @@ -0,0 +1,92 @@ +/* + * Copyright 2002-2017 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 org.junit.Test; + +import org.springframework.http.MediaType; +import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest; +import org.springframework.mock.http.server.reactive.test.MockServerWebExchange; +import org.springframework.web.server.NotAcceptableStatusException; +import org.springframework.web.server.ServerWebExchange; + +import static org.junit.Assert.assertEquals; + +/** + * Unit tests for {@link ParameterContentTypeResolver}. + * @author Rossen Stoyanchev + */ +public class ParameterContentTypeResolverTests { + + @Test + public void noKey() throws Exception { + ParameterContentTypeResolver resolver = new ParameterContentTypeResolver(Collections.emptyMap()); + List mediaTypes = resolver.resolveMediaTypes(MockServerHttpRequest.get("/").toExchange()); + + assertEquals(0, mediaTypes.size()); + } + + @Test(expected = NotAcceptableStatusException.class) + public void noMatchForKey() throws Exception { + ParameterContentTypeResolver resolver = new ParameterContentTypeResolver(Collections.emptyMap()); + List mediaTypes = resolver.resolveMediaTypes(createExchange("blah")); + + assertEquals(0, mediaTypes.size()); + } + + @Test + public void resolveKeyFromRegistrations() throws Exception { + ServerWebExchange exchange = createExchange("html"); + + Map mapping = Collections.emptyMap(); + RequestedContentTypeResolver resolver = new ParameterContentTypeResolver(mapping); + List mediaTypes = resolver.resolveMediaTypes(exchange); + assertEquals(Collections.singletonList(new MediaType("text", "html")), mediaTypes); + + mapping = Collections.singletonMap("HTML", MediaType.APPLICATION_XHTML_XML); + resolver = new ParameterContentTypeResolver(mapping); + mediaTypes = resolver.resolveMediaTypes(exchange); + assertEquals(Collections.singletonList(new MediaType("application", "xhtml+xml")), mediaTypes); + } + + @Test + public void resolveKeyThroughMediaTypeFactory() throws Exception { + ServerWebExchange exchange = createExchange("xls"); + RequestedContentTypeResolver resolver = new ParameterContentTypeResolver(Collections.emptyMap()); + List mediaTypes = resolver.resolveMediaTypes(exchange); + + assertEquals(Collections.singletonList(new MediaType("application", "vnd.ms-excel")), mediaTypes); + } + + @Test // SPR-13747 + public void resolveKeyIsCaseInsensitive() { + ServerWebExchange exchange = createExchange("JSoN"); + Map mapping = Collections.singletonMap("json", MediaType.APPLICATION_JSON); + ParameterContentTypeResolver resolver = new ParameterContentTypeResolver(mapping); + List mediaTypes = resolver.resolveMediaTypes(exchange); + + assertEquals(Collections.singletonList(MediaType.APPLICATION_JSON), mediaTypes); + } + + private MockServerWebExchange createExchange(String format) { + return MockServerHttpRequest.get("/path?format=" + format).toExchange(); + } + +} diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/accept/PathExtensionContentTypeResolverTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/accept/PathExtensionContentTypeResolverTests.java deleted file mode 100644 index db2c5a474b..0000000000 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/accept/PathExtensionContentTypeResolverTests.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2002-2017 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 org.junit.Test; - -import org.springframework.http.MediaType; -import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest; -import org.springframework.web.server.ServerWebExchange; - -import static org.junit.Assert.assertEquals; - -/** - * Unit tests for {@link PathExtensionContentTypeResolver}. - * - * @author Rossen Stoyanchev - */ -public class PathExtensionContentTypeResolverTests { - - @Test - public void resolveFromRegistrations() throws Exception { - ServerWebExchange exchange = MockServerHttpRequest.get("/test.html").toExchange(); - PathExtensionContentTypeResolver resolver = createResolver(); - 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 resolveFromMediaTypeFactory() throws Exception { - ServerWebExchange exchange = MockServerHttpRequest.get("test.xls").toExchange(); - PathExtensionContentTypeResolver resolver = createResolver(); - List mediaTypes = resolver.resolveMediaTypes(exchange); - - assertEquals(Collections.singletonList(new MediaType("application", "vnd.ms-excel")), mediaTypes); - } - - @Test // SPR-9390 - public void resolveFromFilenameWithEncodedURI() throws Exception { - ServerWebExchange exchange = MockServerHttpRequest.get("/quo%20vadis%3f.html").toExchange(); - PathExtensionContentTypeResolver resolver = createResolver(); - List result = resolver.resolveMediaTypes(exchange); - - assertEquals("Invalid content type", Collections.singletonList(new MediaType("text", "html")), result); - } - - @Test // SPR-10170 - public void resolveAndIgnoreUnknownExtension() throws Exception { - ServerWebExchange exchange = MockServerHttpRequest.get("test.foobar").toExchange(); - PathExtensionContentTypeResolver resolver = createResolver(); - List mediaTypes = resolver.resolveMediaTypes(exchange); - - assertEquals(Collections.emptyList(), mediaTypes); - } - - private PathExtensionContentTypeResolver createResolver() { - return new PathExtensionContentTypeResolver(Collections.emptyMap()); - } - -} diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/accept/RequestedContentTypeResolverBuilderTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/accept/RequestedContentTypeResolverBuilderTests.java new file mode 100644 index 0000000000..73f5429725 --- /dev/null +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/accept/RequestedContentTypeResolverBuilderTests.java @@ -0,0 +1,100 @@ +/* + * Copyright 2002-2017 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 org.junit.Test; + +import org.springframework.http.MediaType; +import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest; +import org.springframework.web.server.ServerWebExchange; + +import static org.junit.Assert.assertEquals; + +/** + * Unit tests for {@link RequestedContentTypeResolverBuilder}. + * @author Rossen Stoyanchev + */ +public class RequestedContentTypeResolverBuilderTests { + + @Test + public void defaultSettings() throws Exception { + + RequestedContentTypeResolver resolver = new RequestedContentTypeResolverBuilder().build(); + ServerWebExchange exchange = MockServerHttpRequest.get("/flower").accept(MediaType.IMAGE_GIF).toExchange(); + List mediaTypes = resolver.resolveMediaTypes(exchange); + + assertEquals(Collections.singletonList(MediaType.IMAGE_GIF), mediaTypes); + } + + @Test + public void parameterResolver() throws Exception { + + RequestedContentTypeResolverBuilder builder = new RequestedContentTypeResolverBuilder(); + builder.parameterResolver().mediaType("json", MediaType.APPLICATION_JSON); + RequestedContentTypeResolver resolver = builder.build(); + + ServerWebExchange exchange = MockServerHttpRequest.get("/flower?format=json").toExchange(); + List mediaTypes = resolver.resolveMediaTypes(exchange); + + assertEquals(Collections.singletonList(MediaType.APPLICATION_JSON), mediaTypes); + } + + @Test + public void parameterResolverWithCustomParamName() throws Exception { + + RequestedContentTypeResolverBuilder builder = new RequestedContentTypeResolverBuilder(); + builder.parameterResolver().mediaType("json", MediaType.APPLICATION_JSON).parameterName("s"); + RequestedContentTypeResolver resolver = builder.build(); + + ServerWebExchange exchange = MockServerHttpRequest.get("/flower?s=json").toExchange(); + List mediaTypes = resolver.resolveMediaTypes(exchange); + + assertEquals(Collections.singletonList(MediaType.APPLICATION_JSON), mediaTypes); + } + + @Test // SPR-10513 + public void fixedResolver() throws Exception { + + RequestedContentTypeResolverBuilder builder = new RequestedContentTypeResolverBuilder(); + builder.fixedResolver(MediaType.APPLICATION_JSON); + RequestedContentTypeResolver resolver = builder.build(); + + ServerWebExchange exchange = MockServerHttpRequest.get("/").accept(MediaType.ALL).toExchange(); + List mediaTypes = resolver.resolveMediaTypes(exchange); + + assertEquals(Collections.singletonList(MediaType.APPLICATION_JSON), mediaTypes); + } + + @Test // SPR-12286 + public void resolver() throws Exception { + + RequestedContentTypeResolverBuilder builder = new RequestedContentTypeResolverBuilder(); + builder.resolver(new FixedContentTypeResolver(MediaType.APPLICATION_JSON)); + RequestedContentTypeResolver resolver = builder.build(); + + ServerWebExchange exchange = MockServerHttpRequest.get("/").toExchange(); + List mediaTypes = resolver.resolveMediaTypes(exchange); + assertEquals(Collections.singletonList(MediaType.APPLICATION_JSON), mediaTypes); + + exchange = MockServerHttpRequest.get("/").accept(MediaType.ALL).toExchange(); + mediaTypes = resolver.resolveMediaTypes(exchange); + assertEquals(Collections.singletonList(MediaType.APPLICATION_JSON), mediaTypes); + } + +} diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/config/WebFluxConfigurationSupportTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/config/WebFluxConfigurationSupportTests.java index 15bbb63981..563f005743 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/config/WebFluxConfigurationSupportTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/config/WebFluxConfigurationSupportTests.java @@ -40,7 +40,6 @@ import org.springframework.http.codec.ServerCodecConfigurer; import org.springframework.http.codec.json.Jackson2JsonEncoder; import org.springframework.http.codec.xml.Jaxb2XmlDecoder; import org.springframework.http.codec.xml.Jaxb2XmlEncoder; -import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest; import org.springframework.util.MimeType; import org.springframework.util.MimeTypeUtils; import org.springframework.util.MultiValueMap; @@ -76,6 +75,7 @@ import static org.springframework.http.MediaType.APPLICATION_OCTET_STREAM; import static org.springframework.http.MediaType.APPLICATION_XML; import static org.springframework.http.MediaType.IMAGE_PNG; import static org.springframework.http.MediaType.TEXT_PLAIN; +import static org.springframework.mock.http.server.reactive.test.MockServerHttpRequest.get; /** * Unit tests for {@link WebFluxConfigurationSupport}. @@ -102,12 +102,8 @@ public class WebFluxConfigurationSupportTests { RequestedContentTypeResolver resolver = context.getBean(name, RequestedContentTypeResolver.class); assertSame(resolver, mapping.getContentTypeResolver()); - ServerWebExchange exchange = MockServerHttpRequest.get("/path.json").toExchange(); - List list = Collections.singletonList(MediaType.APPLICATION_JSON); - assertEquals(list, resolver.resolveMediaTypes(exchange)); - - exchange = MockServerHttpRequest.get("/path.foobar").toExchange(); - assertEquals(Collections.emptyList(), resolver.resolveMediaTypes(exchange)); + ServerWebExchange exchange = get("/path").accept(MediaType.APPLICATION_JSON).toExchange(); + assertEquals(Collections.singletonList(MediaType.APPLICATION_JSON), resolver.resolveMediaTypes(exchange)); } @Test diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/MessageWriterResultHandlerTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/MessageWriterResultHandlerTests.java index d001750984..715609848e 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/MessageWriterResultHandlerTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/MessageWriterResultHandlerTests.java @@ -52,11 +52,13 @@ import org.springframework.util.ObjectUtils; import org.springframework.web.reactive.accept.RequestedContentTypeResolver; import org.springframework.web.reactive.accept.RequestedContentTypeResolverBuilder; -import static org.junit.Assert.*; -import static org.springframework.core.io.buffer.support.DataBufferTestUtils.*; -import static org.springframework.http.MediaType.*; -import static org.springframework.web.method.ResolvableMethod.*; -import static org.springframework.web.reactive.HandlerMapping.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.springframework.core.io.buffer.support.DataBufferTestUtils.dumpString; +import static org.springframework.http.MediaType.APPLICATION_JSON; +import static org.springframework.http.MediaType.APPLICATION_JSON_UTF8; +import static org.springframework.web.method.ResolvableMethod.on; +import static org.springframework.web.reactive.HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE; /** * Unit tests for {@link AbstractMessageWriterResultHandler}. diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ResponseBodyResultHandlerTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ResponseBodyResultHandlerTests.java index c8bb97cba8..4833d68549 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ResponseBodyResultHandlerTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ResponseBodyResultHandlerTests.java @@ -29,8 +29,8 @@ import rx.Single; import org.springframework.core.codec.ByteBufferEncoder; import org.springframework.core.codec.CharSequenceEncoder; import org.springframework.http.codec.EncoderHttpMessageWriter; -import org.springframework.http.codec.ResourceHttpMessageWriter; import org.springframework.http.codec.HttpMessageWriter; +import org.springframework.http.codec.ResourceHttpMessageWriter; import org.springframework.http.codec.json.Jackson2JsonEncoder; import org.springframework.http.codec.xml.Jaxb2XmlEncoder; import org.springframework.stereotype.Controller; diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityResultHandlerTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityResultHandlerTests.java index 704a7d34ed..948e696013 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityResultHandlerTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityResultHandlerTests.java @@ -44,8 +44,8 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.http.codec.EncoderHttpMessageWriter; -import org.springframework.http.codec.ResourceHttpMessageWriter; import org.springframework.http.codec.HttpMessageWriter; +import org.springframework.http.codec.ResourceHttpMessageWriter; import org.springframework.http.codec.json.Jackson2JsonEncoder; import org.springframework.http.codec.xml.Jaxb2XmlEncoder; import org.springframework.mock.http.server.reactive.test.MockServerWebExchange;