mirror of
https://github.com/spring-projects/spring-framework.git
synced 2026-05-03 04:19:47 +01:00
Polish content negotiation
This commit is contained in:
+33
-15
@@ -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.
|
||||
*
|
||||
* <p>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.
|
||||
*
|
||||
* <p>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<String, MediaType> mediaTypes) {
|
||||
super(mediaTypes);
|
||||
@@ -46,7 +57,9 @@ public abstract class AbstractMappingContentNegotiationStrategy extends MappingM
|
||||
|
||||
|
||||
@Override
|
||||
public List<MediaType> resolveMediaTypes(NativeWebRequest webRequest) throws HttpMediaTypeNotAcceptableException {
|
||||
public List<MediaType> 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;
|
||||
}
|
||||
|
||||
|
||||
+36
-63
@@ -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.
|
||||
*
|
||||
* <p>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...)}.
|
||||
* <p>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<MediaType> MEDIA_TYPE_ALL = Arrays.asList(MediaType.ALL);
|
||||
private static final List<MediaType> MEDIA_TYPE_ALL =
|
||||
Collections.<MediaType>singletonList(MediaType.ALL);
|
||||
|
||||
private final List<ContentNegotiationStrategy> contentNegotiationStrategies =
|
||||
|
||||
private final List<ContentNegotiationStrategy> strategies =
|
||||
new ArrayList<ContentNegotiationStrategy>();
|
||||
|
||||
private final Set<MediaTypeFileExtensionResolver> fileExtensionResolvers =
|
||||
private final Set<MediaTypeFileExtensionResolver> resolvers =
|
||||
new LinkedHashSet<MediaTypeFileExtensionResolver>();
|
||||
|
||||
|
||||
/**
|
||||
* Create an instance with the given ContentNegotiationStrategy instances.
|
||||
* <p>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.
|
||||
* <p>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<ContentNegotiationStrategy> 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.
|
||||
* <p>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<MediaType> resolveMediaTypes(NativeWebRequest webRequest) throws HttpMediaTypeNotAcceptableException {
|
||||
for (ContentNegotiationStrategy strategy : this.contentNegotiationStrategies) {
|
||||
List<MediaType> mediaTypes = strategy.resolveMediaTypes(webRequest);
|
||||
public List<MediaType> resolveMediaTypes(NativeWebRequest request)
|
||||
throws HttpMediaTypeNotAcceptableException {
|
||||
|
||||
for (ContentNegotiationStrategy strategy : this.strategies) {
|
||||
List<MediaType> 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<String> resolveFileExtensions(MediaType mediaType) {
|
||||
Set<String> result = new LinkedHashSet<String>();
|
||||
for (MediaTypeFileExtensionResolver resolver : this.fileExtensionResolvers) {
|
||||
for (MediaTypeFileExtensionResolver resolver : this.resolvers) {
|
||||
result.addAll(resolver.resolveFileExtensions(mediaType));
|
||||
}
|
||||
return new ArrayList<String>(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegate to all configured MediaTypeFileExtensionResolver instances and aggregate
|
||||
* the list of all known file extensions.
|
||||
*/
|
||||
@Override
|
||||
public List<String> getAllFileExtensions() {
|
||||
Set<String> result = new LinkedHashSet<String>();
|
||||
for (MediaTypeFileExtensionResolver resolver : this.fileExtensionResolvers) {
|
||||
for (MediaTypeFileExtensionResolver resolver : this.resolvers) {
|
||||
result.addAll(resolver.getAllFileExtensions());
|
||||
}
|
||||
return new ArrayList<String>(result);
|
||||
|
||||
+9
-7
@@ -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}.
|
||||
* <p>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);
|
||||
}
|
||||
|
||||
+6
-4
@@ -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<MediaType> resolveMediaTypes(NativeWebRequest webRequest) throws HttpMediaTypeNotAcceptableException;
|
||||
List<MediaType> resolveMediaTypes(NativeWebRequest webRequest)
|
||||
throws HttpMediaTypeNotAcceptableException;
|
||||
|
||||
}
|
||||
|
||||
+13
-10
@@ -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<MediaType> 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<MediaType> resolveMediaTypes(NativeWebRequest webRequest) {
|
||||
public List<MediaType> 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+16
-13
@@ -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<MediaType> resolveMediaTypes(NativeWebRequest webRequest) throws HttpMediaTypeNotAcceptableException {
|
||||
String acceptHeader = webRequest.getHeader(ACCEPT_HEADER);
|
||||
public List<MediaType> resolveMediaTypes(NativeWebRequest request)
|
||||
throws HttpMediaTypeNotAcceptableException {
|
||||
|
||||
String header = request.getHeader(HttpHeaders.ACCEPT);
|
||||
if (!StringUtils.hasText(header)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
try {
|
||||
if (StringUtils.hasText(acceptHeader)) {
|
||||
List<MediaType> mediaTypes = MediaType.parseMediaTypes(acceptHeader);
|
||||
MediaType.sortBySpecificityAndQuality(mediaTypes);
|
||||
return mediaTypes;
|
||||
}
|
||||
List<MediaType> 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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+29
-26
@@ -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.
|
||||
*
|
||||
* <p>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<String, MediaType> mediaTypes = new ConcurrentHashMap<String, MediaType>(64);
|
||||
private final ConcurrentMap<String, MediaType> mediaTypes =
|
||||
new ConcurrentHashMap<String, MediaType>(64);
|
||||
|
||||
private final MultiValueMap<MediaType, String> fileExtensions = new LinkedMultiValueMap<MediaType, String>();
|
||||
private final MultiValueMap<MediaType, String> fileExtensions =
|
||||
new LinkedMultiValueMap<MediaType, String>();
|
||||
|
||||
private final List<String> allFileExtensions = new LinkedList<String>();
|
||||
|
||||
|
||||
/**
|
||||
* 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<String, MediaType> mediaTypes) {
|
||||
if (mediaTypes != null) {
|
||||
for (Entry<String, MediaType> 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<MediaType> getAllMediaTypes() {
|
||||
return new ArrayList<MediaType>(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<String> resolveFileExtensions(MediaType mediaType) {
|
||||
List<String> fileExtensions = this.fileExtensions.get(mediaType);
|
||||
@@ -76,27 +94,12 @@ public class MappingMediaTypeFileExtensionResolver implements MediaTypeFileExten
|
||||
return Collections.unmodifiableList(this.allFileExtensions);
|
||||
}
|
||||
|
||||
protected List<MediaType> getAllMediaTypes() {
|
||||
return new ArrayList<MediaType>(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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+4
-4
@@ -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<String> 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<String> getAllFileExtensions();
|
||||
|
||||
+26
-16
@@ -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<String, MediaType> mediaTypes) {
|
||||
super(mediaTypes);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the parameter name that can be used to determine the requested media type.
|
||||
* <p>The default parameter name is {@code format}.
|
||||
* Set the name of the parameter to use to determine requested media types.
|
||||
* <p>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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+33
-40
@@ -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.
|
||||
*
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>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.
|
||||
*
|
||||
* <p>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<String, MediaType> 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.
|
||||
*
|
||||
* <p>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.
|
||||
* <p>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}.
|
||||
* <p>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 {
|
||||
|
||||
+16
-17
@@ -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<String, MediaType> mediaTypes) {
|
||||
public ServletPathExtensionContentNegotiationStrategy(ServletContext context,
|
||||
Map<String, MediaType> 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)
|
||||
|
||||
+4
-3
@@ -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}.
|
||||
*
|
||||
* <p>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<String, MediaType> mediaTypes = new HashMap<String, MediaType>();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user