From 24a91b43ccc76ba0bd0ce7ed6a5d8006cbed946c Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Tue, 29 Sep 2015 17:08:37 -0400 Subject: [PATCH] Polish content negotiation --- ...ractMappingContentNegotiationStrategy.java | 48 ++++++--- .../web/accept/ContentNegotiationManager.java | 99 +++++++------------ .../ContentNegotiationManagerFactoryBean.java | 16 +-- .../accept/ContentNegotiationStrategy.java | 10 +- .../FixedContentNegotiationStrategy.java | 23 +++-- .../HeaderContentNegotiationStrategy.java | 29 +++--- ...MappingMediaTypeFileExtensionResolver.java | 55 ++++++----- .../MediaTypeFileExtensionResolver.java | 8 +- .../ParameterContentNegotiationStrategy.java | 42 +++++--- ...thExtensionContentNegotiationStrategy.java | 73 +++++++------- ...thExtensionContentNegotiationStrategy.java | 33 +++---- .../ContentNegotiationConfigurer.java | 7 +- 12 files changed, 225 insertions(+), 218 deletions(-) diff --git a/spring-web/src/main/java/org/springframework/web/accept/AbstractMappingContentNegotiationStrategy.java b/spring-web/src/main/java/org/springframework/web/accept/AbstractMappingContentNegotiationStrategy.java index 1133b060f5..a299784a6a 100644 --- a/spring-web/src/main/java/org/springframework/web/accept/AbstractMappingContentNegotiationStrategy.java +++ b/spring-web/src/main/java/org/springframework/web/accept/AbstractMappingContentNegotiationStrategy.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2015 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. @@ -26,19 +26,30 @@ import org.springframework.web.HttpMediaTypeNotAcceptableException; import org.springframework.web.context.request.NativeWebRequest; /** - * A base class for ContentNegotiationStrategy types that maintain a map with keys - * such as "json" and media types such as "application/json". + * Base class for {@code ContentNegotiationStrategy} implementations with the + * steps to resolve a request to media types. + * + *

First a key (e.g. "json", "pdf") must be extracted from the request (e.g. + * file extension, query param). The key must then be resolved to media type(s) + * through the base class {@link MappingMediaTypeFileExtensionResolver} which + * stores such mappings. + * + *

The method {@link #handleNoMatch} allow sub-classes to plug in additional + * ways of looking up media types (e.g. through the Java Activation framework, + * or {@link javax.servlet.ServletContext#getMimeType}. Media types resolved + * via base classes are then added to the base class + * {@link MappingMediaTypeFileExtensionResolver}, i.e. cached for new lookups. * * @author Rossen Stoyanchev * @since 3.2 */ -public abstract class AbstractMappingContentNegotiationStrategy extends MappingMediaTypeFileExtensionResolver - implements ContentNegotiationStrategy, MediaTypeFileExtensionResolver { +public abstract class AbstractMappingContentNegotiationStrategy + extends MappingMediaTypeFileExtensionResolver + implements ContentNegotiationStrategy { /** - * Create an instance with the given extension-to-MediaType lookup. - * @throws IllegalArgumentException if a media type string cannot be parsed + * Create an instance with the given map of file extensions and media types. */ public AbstractMappingContentNegotiationStrategy(Map mediaTypes) { super(mediaTypes); @@ -46,7 +57,9 @@ public abstract class AbstractMappingContentNegotiationStrategy extends MappingM @Override - public List resolveMediaTypes(NativeWebRequest webRequest) throws HttpMediaTypeNotAcceptableException { + public List resolveMediaTypes(NativeWebRequest webRequest) + throws HttpMediaTypeNotAcceptableException { + String key = getMediaTypeKey(webRequest); if (StringUtils.hasText(key)) { MediaType mediaType = lookupMediaType(key); @@ -64,22 +77,27 @@ public abstract class AbstractMappingContentNegotiationStrategy extends MappingM } /** - * Sub-classes must extract the key to use to look up a media type. - * @return the lookup key or {@code null} if the key cannot be derived + * Extract a key from the request to use to look up media types. + * @return the lookup key or {@code null}. */ protected abstract String getMediaTypeKey(NativeWebRequest request); /** - * Invoked when a matching media type is found in the lookup map. + * Override to provide handling when a key is successfully resolved via + * {@link #lookupMediaType}. */ - protected void handleMatch(String mappingKey, MediaType mediaType) { + protected void handleMatch(String key, MediaType mediaType) { } /** - * Invoked when no matching media type is found in the lookup map. - * Sub-classes can take further steps to determine the media type. + * Override to provide handling when a key is not resolved via. + * {@link #lookupMediaType}. Sub-classes can take further steps to + * determine the media type(s). If a MediaType is returned from + * this method it will be added to the cache in the base class. */ - protected MediaType handleNoMatch(NativeWebRequest request, String key) throws HttpMediaTypeNotAcceptableException { + protected MediaType handleNoMatch(NativeWebRequest request, String key) + throws HttpMediaTypeNotAcceptableException { + return null; } 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 9120a52fae..babf45c68e 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2015 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. @@ -30,63 +30,52 @@ import org.springframework.web.HttpMediaTypeNotAcceptableException; import org.springframework.web.context.request.NativeWebRequest; /** - * This class is used to determine the requested {@linkplain MediaType media types} - * of a request by delegating to a list of ContentNegotiationStrategy instances. - * The strategies must be provided at instantiation or alternatively if using - * the default constructor, an instance of {@link HeaderContentNegotiationStrategy} - * will be configured by default. + * Central class to determine requested {@linkplain MediaType media types} + * for a request. This is done by delegating to a list of configured + * {@code ContentNegotiationStrategy} instances. * - *

This class may also be used to look up file extensions associated with a - * MediaType. This is done by consulting the list of configured - * {@link MediaTypeFileExtensionResolver} instances. Note that some - * ContentNegotiationStrategy implementations also implement - * MediaTypeFileExtensionResolver and the class constructor accepting the former - * will also detect if they implement the latter. If you need to register additional - * resolvers, you can use the method - * {@link #addFileExtensionResolvers(MediaTypeFileExtensionResolver...)}. + *

Also provides methods to look up file extensions for a media type. + * This is done by delegating to the list of configured + * {@code MediaTypeFileExtensionResolver} instances. * * @author Rossen Stoyanchev * @since 3.2 */ -public class ContentNegotiationManager implements ContentNegotiationStrategy, MediaTypeFileExtensionResolver { +public class ContentNegotiationManager implements ContentNegotiationStrategy, + MediaTypeFileExtensionResolver { - private static final List MEDIA_TYPE_ALL = Arrays.asList(MediaType.ALL); + private static final List MEDIA_TYPE_ALL = + Collections.singletonList(MediaType.ALL); - private final List contentNegotiationStrategies = + + private final List strategies = new ArrayList(); - private final Set fileExtensionResolvers = + private final Set resolvers = new LinkedHashSet(); /** - * Create an instance with the given ContentNegotiationStrategy instances. - *

Each instance is checked to see if it is also an implementation of - * MediaTypeFileExtensionResolver, and if so it is registered as such. - * @param strategies one more more ContentNegotiationStrategy instances + * Create an instance with the given list of + * {@code ContentNegotiationStrategy} strategies each of which may also be + * an instance of {@code MediaTypeFileExtensionResolver}. + * @param strategies the strategies to use */ public ContentNegotiationManager(ContentNegotiationStrategy... strategies) { - Assert.notEmpty(strategies, "At least one ContentNegotiationStrategy is expected"); - this.contentNegotiationStrategies.addAll(Arrays.asList(strategies)); - for (ContentNegotiationStrategy strategy : this.contentNegotiationStrategies) { - if (strategy instanceof MediaTypeFileExtensionResolver) { - this.fileExtensionResolvers.add((MediaTypeFileExtensionResolver) strategy); - } - } + this(Arrays.asList(strategies)); } /** - * Create an instance with the given ContentNegotiationStrategy instances. - *

Each instance is checked to see if it is also an implementation of - * MediaTypeFileExtensionResolver, and if so it is registered as such. - * @param strategies one more more ContentNegotiationStrategy instances + * A collection-based alternative to + * {@link #ContentNegotiationManager(ContentNegotiationStrategy...)}. + * @param strategies the strategies to use */ public ContentNegotiationManager(Collection strategies) { Assert.notEmpty(strategies, "At least one ContentNegotiationStrategy is expected"); - this.contentNegotiationStrategies.addAll(strategies); - for (ContentNegotiationStrategy strategy : this.contentNegotiationStrategies) { + this.strategies.addAll(strategies); + for (ContentNegotiationStrategy strategy : this.strategies) { if (strategy instanceof MediaTypeFileExtensionResolver) { - this.fileExtensionResolvers.add((MediaTypeFileExtensionResolver) strategy); + this.resolvers.add((MediaTypeFileExtensionResolver) strategy); } } } @@ -100,28 +89,20 @@ public class ContentNegotiationManager implements ContentNegotiationStrategy, Me /** - * Add MediaTypeFileExtensionResolver instances. - *

Note that some {@link ContentNegotiationStrategy} implementations also - * implement {@link MediaTypeFileExtensionResolver} and the class constructor - * accepting the former will also detect implementations of the latter. Therefore - * you only need to use this method to register additional instances. - * @param resolvers one or more resolvers + * Register more {@code MediaTypeFileExtensionResolver} instances in addition + * to those detected at construction. + * @param resolvers the resolvers to add */ public void addFileExtensionResolvers(MediaTypeFileExtensionResolver... resolvers) { - this.fileExtensionResolvers.addAll(Arrays.asList(resolvers)); + this.resolvers.addAll(Arrays.asList(resolvers)); } - /** - * Delegate to all configured ContentNegotiationStrategy instances until one - * returns a non-empty list. - * @param webRequest the current request - * @return the requested media types or an empty list, never {@code null} - * @throws HttpMediaTypeNotAcceptableException if the requested media types cannot be parsed - */ @Override - public List resolveMediaTypes(NativeWebRequest webRequest) throws HttpMediaTypeNotAcceptableException { - for (ContentNegotiationStrategy strategy : this.contentNegotiationStrategies) { - List mediaTypes = strategy.resolveMediaTypes(webRequest); + public List resolveMediaTypes(NativeWebRequest request) + throws HttpMediaTypeNotAcceptableException { + + for (ContentNegotiationStrategy strategy : this.strategies) { + List mediaTypes = strategy.resolveMediaTypes(request); if (mediaTypes.isEmpty() || mediaTypes.equals(MEDIA_TYPE_ALL)) { continue; } @@ -130,27 +111,19 @@ public class ContentNegotiationManager implements ContentNegotiationStrategy, Me return Collections.emptyList(); } - /** - * Delegate to all configured MediaTypeFileExtensionResolver instances and aggregate - * the list of all file extensions found. - */ @Override public List resolveFileExtensions(MediaType mediaType) { Set result = new LinkedHashSet(); - for (MediaTypeFileExtensionResolver resolver : this.fileExtensionResolvers) { + for (MediaTypeFileExtensionResolver resolver : this.resolvers) { result.addAll(resolver.resolveFileExtensions(mediaType)); } return new ArrayList(result); } - /** - * Delegate to all configured MediaTypeFileExtensionResolver instances and aggregate - * the list of all known file extensions. - */ @Override public List getAllFileExtensions() { Set result = new LinkedHashSet(); - for (MediaTypeFileExtensionResolver resolver : this.fileExtensionResolvers) { + for (MediaTypeFileExtensionResolver resolver : this.resolvers) { result.addAll(resolver.getAllFileExtensions()); } return new ArrayList(result); 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 0c6e6ed1df..289b7b3e71 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 @@ -130,7 +130,7 @@ public class ContentNegotiationManagerFactoryBean } /** - * Indicate whether to use the Java Activation Framework as a fallback option + * Whether to use the Java Activation Framework as a fallback option * to map from file extensions to media types. This is used only when * {@link #setFavorPathExtension(boolean)} is set to {@code true}. *

The default value is {@code true}. @@ -184,8 +184,8 @@ public class ContentNegotiationManagerFactoryBean * FixedContentNegotiationStrategy}. Alternatively you can also provide a * custom strategy via {@link #setDefaultContentTypeStrategy}. */ - public void setDefaultContentType(MediaType defaultContentType) { - this.defaultNegotiationStrategy = new FixedContentNegotiationStrategy(defaultContentType); + public void setDefaultContentType(MediaType contentType) { + this.defaultNegotiationStrategy = new FixedContentNegotiationStrategy(contentType); } /** @@ -195,8 +195,8 @@ public class ContentNegotiationManagerFactoryBean * provides a simpler alternative to doing the same. * @since 4.1.2 */ - public void setDefaultContentTypeStrategy(ContentNegotiationStrategy defaultStrategy) { - this.defaultNegotiationStrategy = defaultStrategy; + public void setDefaultContentTypeStrategy(ContentNegotiationStrategy strategy) { + this.defaultNegotiationStrategy = strategy; } @Override @@ -212,7 +212,8 @@ public class ContentNegotiationManagerFactoryBean if (this.favorPathExtension) { PathExtensionContentNegotiationStrategy strategy; if (this.servletContext != null) { - strategy = new ServletPathExtensionContentNegotiationStrategy(this.servletContext, this.mediaTypes); + strategy = new ServletPathExtensionContentNegotiationStrategy( + this.servletContext, this.mediaTypes); } else { strategy = new PathExtensionContentNegotiationStrategy(this.mediaTypes); @@ -225,7 +226,8 @@ public class ContentNegotiationManagerFactoryBean } if (this.favorParameter) { - ParameterContentNegotiationStrategy strategy = new ParameterContentNegotiationStrategy(this.mediaTypes); + ParameterContentNegotiationStrategy strategy = + new ParameterContentNegotiationStrategy(this.mediaTypes); strategy.setParameterName(this.parameterName); strategies.add(strategy); } diff --git a/spring-web/src/main/java/org/springframework/web/accept/ContentNegotiationStrategy.java b/spring-web/src/main/java/org/springframework/web/accept/ContentNegotiationStrategy.java index 1b1c4b9c04..127f90cef4 100644 --- a/spring-web/src/main/java/org/springframework/web/accept/ContentNegotiationStrategy.java +++ b/spring-web/src/main/java/org/springframework/web/accept/ContentNegotiationStrategy.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2015 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. @@ -23,7 +23,7 @@ import org.springframework.web.HttpMediaTypeNotAcceptableException; import org.springframework.web.context.request.NativeWebRequest; /** - * A strategy for resolving the requested media types in a request. + * A strategy for resolving the requested media types for a request. * * @author Rossen Stoyanchev * @since 3.2 @@ -37,8 +37,10 @@ public interface ContentNegotiationStrategy { * @param webRequest the current request * @return the requested media types or an empty list, never {@code null} * - * @throws HttpMediaTypeNotAcceptableException if the requested media types cannot be parsed + * @throws HttpMediaTypeNotAcceptableException if the requested media + * types cannot be parsed */ - List resolveMediaTypes(NativeWebRequest webRequest) throws HttpMediaTypeNotAcceptableException; + List resolveMediaTypes(NativeWebRequest webRequest) + throws HttpMediaTypeNotAcceptableException; } diff --git a/spring-web/src/main/java/org/springframework/web/accept/FixedContentNegotiationStrategy.java b/spring-web/src/main/java/org/springframework/web/accept/FixedContentNegotiationStrategy.java index f43668c5e8..547ccef791 100644 --- a/spring-web/src/main/java/org/springframework/web/accept/FixedContentNegotiationStrategy.java +++ b/spring-web/src/main/java/org/springframework/web/accept/FixedContentNegotiationStrategy.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2015 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. @@ -26,30 +26,33 @@ import org.springframework.http.MediaType; import org.springframework.web.context.request.NativeWebRequest; /** - * A ContentNegotiationStrategy that returns a fixed content type. + * A {@code ContentNegotiationStrategy} that returns a fixed content type. * * @author Rossen Stoyanchev * @since 3.2 */ public class FixedContentNegotiationStrategy implements ContentNegotiationStrategy { - private static final Log logger = LogFactory.getLog(FixedContentNegotiationStrategy.class); + private static final Log logger = LogFactory.getLog( + FixedContentNegotiationStrategy.class); + + private final List contentType; - private final MediaType defaultContentType; /** - * Create an instance that always returns the given content type. + * Create an instance with the given content type. */ - public FixedContentNegotiationStrategy(MediaType defaultContentType) { - this.defaultContentType = defaultContentType; + public FixedContentNegotiationStrategy(MediaType contentType) { + this.contentType = Collections.singletonList(contentType); } + @Override - public List resolveMediaTypes(NativeWebRequest webRequest) { + public List resolveMediaTypes(NativeWebRequest request) { if (logger.isDebugEnabled()) { - logger.debug("Requested media types is " + this.defaultContentType + " (based on default MediaType)"); + logger.debug("Requested media types is " + this.contentType + "."); } - return Collections.singletonList(this.defaultContentType); + return this.contentType; } } diff --git a/spring-web/src/main/java/org/springframework/web/accept/HeaderContentNegotiationStrategy.java b/spring-web/src/main/java/org/springframework/web/accept/HeaderContentNegotiationStrategy.java index 7ed825846e..bcefd7279e 100644 --- a/spring-web/src/main/java/org/springframework/web/accept/HeaderContentNegotiationStrategy.java +++ b/spring-web/src/main/java/org/springframework/web/accept/HeaderContentNegotiationStrategy.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2015 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. @@ -19,6 +19,7 @@ package org.springframework.web.accept; import java.util.Collections; import java.util.List; +import org.springframework.http.HttpHeaders; import org.springframework.http.InvalidMediaTypeException; import org.springframework.http.MediaType; import org.springframework.util.StringUtils; @@ -26,34 +27,36 @@ import org.springframework.web.HttpMediaTypeNotAcceptableException; import org.springframework.web.context.request.NativeWebRequest; /** - * A ContentNegotiationStrategy that parses the 'Accept' header of the request. + * A {@code ContentNegotiationStrategy} that checks the 'Accept' request header. * * @author Rossen Stoyanchev * @since 3.2 */ public class HeaderContentNegotiationStrategy implements ContentNegotiationStrategy { - private static final String ACCEPT_HEADER = "Accept"; /** * {@inheritDoc} - * @throws HttpMediaTypeNotAcceptableException if the 'Accept' header cannot be parsed. + * @throws HttpMediaTypeNotAcceptableException if the 'Accept' header + * cannot be parsed. */ @Override - public List resolveMediaTypes(NativeWebRequest webRequest) throws HttpMediaTypeNotAcceptableException { - String acceptHeader = webRequest.getHeader(ACCEPT_HEADER); + public List resolveMediaTypes(NativeWebRequest request) + throws HttpMediaTypeNotAcceptableException { + + String header = request.getHeader(HttpHeaders.ACCEPT); + if (!StringUtils.hasText(header)) { + return Collections.emptyList(); + } try { - if (StringUtils.hasText(acceptHeader)) { - List mediaTypes = MediaType.parseMediaTypes(acceptHeader); - MediaType.sortBySpecificityAndQuality(mediaTypes); - return mediaTypes; - } + List mediaTypes = MediaType.parseMediaTypes(header); + MediaType.sortBySpecificityAndQuality(mediaTypes); + return mediaTypes; } catch (InvalidMediaTypeException ex) { throw new HttpMediaTypeNotAcceptableException( - "Could not parse accept header [" + acceptHeader + "]: " + ex.getMessage()); + "Could not parse 'Accept' header [" + header + "]: " + ex.getMessage()); } - return Collections.emptyList(); } } diff --git a/spring-web/src/main/java/org/springframework/web/accept/MappingMediaTypeFileExtensionResolver.java b/spring-web/src/main/java/org/springframework/web/accept/MappingMediaTypeFileExtensionResolver.java index 3fee152081..59125b5ea0 100644 --- a/spring-web/src/main/java/org/springframework/web/accept/MappingMediaTypeFileExtensionResolver.java +++ b/spring-web/src/main/java/org/springframework/web/accept/MappingMediaTypeFileExtensionResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2015 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. @@ -31,40 +31,58 @@ import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; /** - * An implementation of {@link MediaTypeFileExtensionResolver} that maintains a lookup - * from extension to MediaType. + * An implementation of {@code MediaTypeFileExtensionResolver} that maintains + * lookups between file extensions and MediaTypes in both directions. + * + *

Initially created with a map of file extensions and media types. + * Subsequently sub-classes can use {@link #addMapping} to add more mappings. * * @author Rossen Stoyanchev * @since 3.2 */ public class MappingMediaTypeFileExtensionResolver implements MediaTypeFileExtensionResolver { - private final ConcurrentMap mediaTypes = new ConcurrentHashMap(64); + private final ConcurrentMap mediaTypes = + new ConcurrentHashMap(64); - private final MultiValueMap fileExtensions = new LinkedMultiValueMap(); + private final MultiValueMap fileExtensions = + new LinkedMultiValueMap(); private final List allFileExtensions = new LinkedList(); /** - * Create an instance with the given mappings between extensions and media types. - * @throws IllegalArgumentException if a media type string cannot be parsed + * Create an instance with the given map of file extensions and media types. */ public MappingMediaTypeFileExtensionResolver(Map mediaTypes) { if (mediaTypes != null) { for (Entry entries : mediaTypes.entrySet()) { String extension = entries.getKey().toLowerCase(Locale.ENGLISH); MediaType mediaType = entries.getValue(); - addMapping(extension, mediaType); + this.mediaTypes.put(extension, mediaType); + this.fileExtensions.add(mediaType, extension); + this.allFileExtensions.add(extension); } } } + protected List getAllMediaTypes() { + return new ArrayList(this.mediaTypes.values()); + } + /** - * Find the file extensions mapped to the given MediaType. - * @return 0 or more extensions, never {@code null} + * Map an extension to a MediaType. Ignore if extension already mapped. */ + protected void addMapping(String extension, MediaType mediaType) { + MediaType previous = this.mediaTypes.putIfAbsent(extension, mediaType); + if (previous == null) { + this.fileExtensions.add(mediaType, extension); + this.allFileExtensions.add(extension); + } + } + + @Override public List resolveFileExtensions(MediaType mediaType) { List fileExtensions = this.fileExtensions.get(mediaType); @@ -76,27 +94,12 @@ public class MappingMediaTypeFileExtensionResolver implements MediaTypeFileExten return Collections.unmodifiableList(this.allFileExtensions); } - protected List getAllMediaTypes() { - return new ArrayList(this.mediaTypes.values()); - } - /** - * Return the MediaType mapped to the given extension. + * Use this method for a reverse lookup from extension to MediaType. * @return a MediaType for the key or {@code null} */ protected MediaType lookupMediaType(String extension) { return this.mediaTypes.get(extension); } - /** - * Map a MediaType to an extension or ignore if the extensions is already mapped. - */ - protected void addMapping(String extension, MediaType mediaType) { - MediaType previous = this.mediaTypes.putIfAbsent(extension, mediaType); - if (previous == null) { - this.fileExtensions.add(mediaType, extension); - this.allFileExtensions.add(extension); - } - } - } diff --git a/spring-web/src/main/java/org/springframework/web/accept/MediaTypeFileExtensionResolver.java b/spring-web/src/main/java/org/springframework/web/accept/MediaTypeFileExtensionResolver.java index 218a07fe6c..c063e5c50b 100644 --- a/spring-web/src/main/java/org/springframework/web/accept/MediaTypeFileExtensionResolver.java +++ b/spring-web/src/main/java/org/springframework/web/accept/MediaTypeFileExtensionResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2015 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. @@ -21,8 +21,8 @@ import java.util.List; import org.springframework.http.MediaType; /** - * A strategy for resolving a {@link MediaType} to one or more path extensions. - * For example "application/json" to "json". + * Strategy to resolve {@link MediaType} to a list of file extensions. + * For example resolve "application/json" to "json". * * @author Rossen Stoyanchev * @since 3.2 @@ -38,7 +38,7 @@ public interface MediaTypeFileExtensionResolver { List resolveFileExtensions(MediaType mediaType); /** - * Return all known file extensions. + * Return all registered file extensions. * @return a list of extensions or an empty list, never {@code null} */ List getAllFileExtensions(); diff --git a/spring-web/src/main/java/org/springframework/web/accept/ParameterContentNegotiationStrategy.java b/spring-web/src/main/java/org/springframework/web/accept/ParameterContentNegotiationStrategy.java index cd70681276..a2520d27fd 100644 --- a/spring-web/src/main/java/org/springframework/web/accept/ParameterContentNegotiationStrategy.java +++ b/spring-web/src/main/java/org/springframework/web/accept/ParameterContentNegotiationStrategy.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2015 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. @@ -27,51 +27,61 @@ import org.springframework.web.HttpMediaTypeNotAcceptableException; import org.springframework.web.context.request.NativeWebRequest; /** - * A ContentNegotiationStrategy that uses a request parameter to determine what - * media types are requested. The default parameter name is {@code format}. - * Its value is used to look up the media type in the map given to the constructor. - * + * A {@code ContentNegotiationStrategy} that resolves a query parameter to a + * key to be used to look up a media type. The default parameter name is + * {@code format}. + *s * @author Rossen Stoyanchev * @since 3.2 */ -public class ParameterContentNegotiationStrategy extends AbstractMappingContentNegotiationStrategy { +public class ParameterContentNegotiationStrategy + extends AbstractMappingContentNegotiationStrategy { - private static final Log logger = LogFactory.getLog(ParameterContentNegotiationStrategy.class); + private static final Log logger = LogFactory.getLog( + ParameterContentNegotiationStrategy.class); private String parameterName = "format"; + /** - * Create an instance with the given extension-to-MediaType lookup. - * @throws IllegalArgumentException if a media type string cannot be parsed + * Create an instance with the given map of file extensions and media types. */ public ParameterContentNegotiationStrategy(Map mediaTypes) { super(mediaTypes); } + /** - * Set the parameter name that can be used to determine the requested media type. - *

The default parameter name is {@code format}. + * 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 getMediaTypeKey(NativeWebRequest webRequest) { - return webRequest.getParameter(this.parameterName); + protected String getMediaTypeKey(NativeWebRequest request) { + return request.getParameter(getParameterName()); } @Override protected void handleMatch(String mediaTypeKey, MediaType mediaType) { if (logger.isDebugEnabled()) { - logger.debug("Requested media type is '" + mediaType + "' (based on parameter '" + - this.parameterName + "'='" + mediaTypeKey + "')"); + logger.debug("Requested media type is '" + mediaType + + "' based on '" + getParameterName() + "'='" + mediaTypeKey + "'."); } } @Override - protected MediaType handleNoMatch(NativeWebRequest request, String key) throws HttpMediaTypeNotAcceptableException { + protected MediaType handleNoMatch(NativeWebRequest request, String key) + throws HttpMediaTypeNotAcceptableException { + throw new HttpMediaTypeNotAcceptableException(getAllMediaTypes()); } + } 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 de98ae2ec3..a64292823a 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2015 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. @@ -38,30 +38,32 @@ import org.springframework.web.util.UrlPathHelper; import org.springframework.web.util.WebUtils; /** - * A ContentNegotiationStrategy that uses the path extension of the URL to - * determine what media types are requested. The path extension is first looked - * up in the map of media types provided to the constructor. If that fails, the - * Java Activation framework is used as a fallback mechanism. + * A {@code ContentNegotiationStrategy} that resolves the file extension in the + * request path to a key to be used to look up a media type. * - *

- * The presence of the Java Activation framework is detected and enabled - * automatically but the {@link #setUseJaf(boolean)} property may be used to - * override that setting. + *

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 * @since 3.2 */ -public class PathExtensionContentNegotiationStrategy extends AbstractMappingContentNegotiationStrategy { - - private static final boolean JAF_PRESENT = ClassUtils.isPresent("javax.activation.FileTypeMap", - PathExtensionContentNegotiationStrategy.class.getClassLoader()); +public class PathExtensionContentNegotiationStrategy + extends AbstractMappingContentNegotiationStrategy { private static final Log logger = LogFactory.getLog(PathExtensionContentNegotiationStrategy.class); - private static final UrlPathHelper urlPathHelper = new UrlPathHelper(); + private static final boolean JAF_PRESENT = ClassUtils.isPresent( + "javax.activation.FileTypeMap", + PathExtensionContentNegotiationStrategy.class.getClassLoader()); + + private static final UrlPathHelper PATH_HELPER = new UrlPathHelper(); static { - urlPathHelper.setUrlDecode(false); + PATH_HELPER.setUrlDecode(false); } private boolean useJaf = JAF_PRESENT; @@ -70,8 +72,7 @@ public class PathExtensionContentNegotiationStrategy extends AbstractMappingCont /** - * Create an instance with the given extension-to-MediaType lookup. - * @throws IllegalArgumentException if a media type string cannot be parsed + * Create an instance with the given map of file extensions and media types. */ public PathExtensionContentNegotiationStrategy(Map mediaTypes) { super(mediaTypes); @@ -87,21 +88,17 @@ public class PathExtensionContentNegotiationStrategy extends AbstractMappingCont /** - * Indicate whether to use the Java Activation Framework to map from file - * extensions to media types. - * - *

Default is {@code true}, i.e. the Java Activation Framework is used - * (if available). + * Whether to use the Java Activation Framework to look up file extensions. + *

By default if this property is not set JAF is present on the + * classpath it will be used. */ public void setUseJaf(boolean useJaf) { this.useJaf = useJaf; } /** - * Whether to ignore requests that have a file extension that does not match - * any mapped media types. Setting this to {@code false} will result in a - * {@code HttpMediaTypeNotAcceptableException}. - * + * 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) { @@ -111,35 +108,31 @@ public class PathExtensionContentNegotiationStrategy extends AbstractMappingCont @Override protected String getMediaTypeKey(NativeWebRequest webRequest) { - HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class); - if (servletRequest == null) { + HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class); + if (request == null) { logger.warn("An HttpServletRequest is required to determine the media type key"); return null; } - String path = urlPathHelper.getLookupPathForRequest(servletRequest); + String path = PATH_HELPER.getLookupPathForRequest(request); String filename = WebUtils.extractFullFilenameFromUrlPath(path); String extension = StringUtils.getFilenameExtension(filename); return (StringUtils.hasText(extension)) ? extension.toLowerCase(Locale.ENGLISH) : null; } - @Override - protected void handleMatch(String extension, MediaType mediaType) { - } - @Override protected MediaType handleNoMatch(NativeWebRequest webRequest, String extension) throws HttpMediaTypeNotAcceptableException { if (this.useJaf) { - MediaType jafMediaType = JafMediaTypeFactory.getMediaType("file." + extension); - if (jafMediaType != null && !MediaType.APPLICATION_OCTET_STREAM.equals(jafMediaType)) { - return jafMediaType; + MediaType mediaType = JafMediaTypeFactory.getMediaType("file." + extension); + if (mediaType != null && !MediaType.APPLICATION_OCTET_STREAM.equals(mediaType)) { + return mediaType; } } - if (!this.ignoreUnknownExtensions) { - throw new HttpMediaTypeNotAcceptableException(getAllMediaTypes()); + if (this.ignoreUnknownExtensions) { + return null; } - return null; + throw new HttpMediaTypeNotAcceptableException(getAllMediaTypes()); } @@ -161,7 +154,7 @@ public class PathExtensionContentNegotiationStrategy extends AbstractMappingCont Resource resource = new ClassPathResource("org/springframework/mail/javamail/mime.types"); if (resource.exists()) { if (logger.isTraceEnabled()) { - logger.trace("Loading Java Activation Framework FileTypeMap from " + resource); + logger.trace("Loading JAF FileTypeMap from " + resource); } InputStream inputStream = null; try { 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 01aa7cda5d..203387105c 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2015 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. @@ -25,43 +25,42 @@ import org.springframework.web.HttpMediaTypeNotAcceptableException; import org.springframework.web.context.request.NativeWebRequest; /** - * An extension of {@code PathExtensionContentNegotiationStrategy} that uses - * {@link ServletContext#getMimeType(String)} as a fallback mechanism when - * matching a path extension to a media type. + * Extends {@code PathExtensionContentNegotiationStrategy} that also uses + * {@link ServletContext#getMimeType(String)} to resolve file extensions. * * @author Rossen Stoyanchev * @since 3.2 */ -public class ServletPathExtensionContentNegotiationStrategy extends PathExtensionContentNegotiationStrategy { +public class ServletPathExtensionContentNegotiationStrategy + extends PathExtensionContentNegotiationStrategy { private final ServletContext servletContext; /** * Create an instance with the given extension-to-MediaType lookup. - * @throws IllegalArgumentException if a media type string cannot be parsed */ - public ServletPathExtensionContentNegotiationStrategy( - ServletContext servletContext, Map mediaTypes) { + public ServletPathExtensionContentNegotiationStrategy(ServletContext context, + Map mediaTypes) { super(mediaTypes); - Assert.notNull(servletContext, "ServletContext is required!"); - this.servletContext = servletContext; + Assert.notNull(context, "ServletContext is required!"); + this.servletContext = context; } /** * Create an instance without any mappings to start with. Mappings may be - * added later on if any extensions are resolved through - * {@link ServletContext#getMimeType(String)} or through the Java Activation - * framework. + * added later when extensions are resolved through + * {@link ServletContext#getMimeType(String)} or via JAF. */ - public ServletPathExtensionContentNegotiationStrategy(ServletContext servletContext) { - this(servletContext, null); + public ServletPathExtensionContentNegotiationStrategy(ServletContext context) { + this(context, null); } + /** - * Look up the given extension via {@link ServletContext#getMimeType(String)} - * and if that doesn't help, delegate to the parent implementation. + * Resolve file extension via {@link ServletContext#getMimeType(String)} + * and also delegate to base class for a potential JAF lookup. */ @Override protected MediaType handleNoMatch(NativeWebRequest webRequest, String extension) 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 ff34257ada..35a928e2b9 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2015 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. @@ -25,7 +25,7 @@ import org.springframework.web.accept.ContentNegotiationManagerFactoryBean; import org.springframework.web.accept.ContentNegotiationStrategy; /** - * Helps with configuring a {@link ContentNegotiationManager}. + * Help to create and configure a {@link ContentNegotiationManager}. * *

By default strategies for checking the extension of the request path and * the {@code Accept} header are registered. The path extension check will perform @@ -37,7 +37,8 @@ import org.springframework.web.accept.ContentNegotiationStrategy; */ public class ContentNegotiationConfigurer { - private final ContentNegotiationManagerFactoryBean factoryBean = new ContentNegotiationManagerFactoryBean(); + private final ContentNegotiationManagerFactoryBean factoryBean = + new ContentNegotiationManagerFactoryBean(); private final Map mediaTypes = new HashMap();