Add abstractions for content negotiation
Introduced ContentNeogtiationStrategy for resolving the requested media types from an incoming request. The available implementations are based on path extension, request parameter, 'Accept' header, and a fixed default content type. The logic for these implementations is based on equivalent options, previously available only in the ContentNegotiatingViewResolver. Also in this commit is ContentNegotiationManager, the central class to use when configuring content negotiation options. It accepts one or more ContentNeogtiationStrategy instances and delegates to them. The ContentNeogiationManager can now be used to configure the following classes: - RequestMappingHandlerMappingm - RequestMappingHandlerAdapter - ExceptionHandlerExceptionResolver - ContentNegotiatingViewResolver Issue: SPR-8410, SPR-8417, SPR-8418,SPR-8416, SPR-8419,SPR-7722
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright 2002-2012 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.accept;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.util.StringUtils;
|
||||
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".
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 3.2
|
||||
*/
|
||||
public abstract class AbstractMappingContentNegotiationStrategy extends MappingMediaTypeExtensionsResolver
|
||||
implements ContentNegotiationStrategy, MediaTypeExtensionsResolver {
|
||||
|
||||
/**
|
||||
* Create an instance with the given extension-to-MediaType lookup.
|
||||
* @throws IllegalArgumentException if a media type string cannot be parsed
|
||||
*/
|
||||
public AbstractMappingContentNegotiationStrategy(Map<String, String> mediaTypes) {
|
||||
super(mediaTypes);
|
||||
}
|
||||
|
||||
public List<MediaType> resolveMediaTypes(NativeWebRequest webRequest) {
|
||||
String key = getMediaTypeKey(webRequest);
|
||||
if (StringUtils.hasText(key)) {
|
||||
MediaType mediaType = lookupMediaType(key);
|
||||
if (mediaType != null) {
|
||||
handleMatch(key, mediaType);
|
||||
return Collections.singletonList(mediaType);
|
||||
}
|
||||
mediaType = handleNoMatch(webRequest, key);
|
||||
if (mediaType != null) {
|
||||
addMapping(key, mediaType);
|
||||
return Collections.singletonList(mediaType);
|
||||
}
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
protected abstract String getMediaTypeKey(NativeWebRequest request);
|
||||
|
||||
/**
|
||||
* Invoked when a matching media type is found in the lookup map.
|
||||
*/
|
||||
protected void handleMatch(String mappingKey, 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.
|
||||
*/
|
||||
protected MediaType handleNoMatch(NativeWebRequest request, String mappingKey) {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright 2002-2012 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.accept;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.HttpMediaTypeNotAcceptableException;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
|
||||
/**
|
||||
* This class is used to determine the requested {@linkplain MediaType media types}
|
||||
* in a request by delegating to a list of {@link ContentNegotiationStrategy} instances.
|
||||
*
|
||||
* <p>It may also be used to determine the extensions associated with a MediaType by
|
||||
* delegating to a list of {@link MediaTypeExtensionsResolver} instances.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 3.2
|
||||
*/
|
||||
public class ContentNegotiationManager implements ContentNegotiationStrategy, MediaTypeExtensionsResolver {
|
||||
|
||||
private final List<ContentNegotiationStrategy> contentNegotiationStrategies = new ArrayList<ContentNegotiationStrategy>();
|
||||
|
||||
private final Set<MediaTypeExtensionsResolver> extensionResolvers = new LinkedHashSet<MediaTypeExtensionsResolver>();
|
||||
|
||||
/**
|
||||
* Create an instance with the given ContentNegotiationStrategy instances.
|
||||
* <p>Each instance is checked to see if it is also an implementation of
|
||||
* MediaTypeExtensionsResolver, and if so it is registered as such.
|
||||
*/
|
||||
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 MediaTypeExtensionsResolver) {
|
||||
this.extensionResolvers.add((MediaTypeExtensionsResolver) strategy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance with a {@link HeaderContentNegotiationStrategy}.
|
||||
*/
|
||||
public ContentNegotiationManager() {
|
||||
this(new HeaderContentNegotiationStrategy());
|
||||
}
|
||||
|
||||
/**
|
||||
* Add MediaTypeExtensionsResolver instances.
|
||||
*/
|
||||
public void addExtensionsResolver(MediaTypeExtensionsResolver... resolvers) {
|
||||
this.extensionResolvers.addAll(Arrays.asList(resolvers));
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegate to all configured ContentNegotiationStrategy instances until one
|
||||
* returns a non-empty list.
|
||||
* @param request 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
|
||||
*/
|
||||
public List<MediaType> resolveMediaTypes(NativeWebRequest webRequest) throws HttpMediaTypeNotAcceptableException {
|
||||
for (ContentNegotiationStrategy strategy : this.contentNegotiationStrategies) {
|
||||
List<MediaType> mediaTypes = strategy.resolveMediaTypes(webRequest);
|
||||
if (!mediaTypes.isEmpty()) {
|
||||
return mediaTypes;
|
||||
}
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegate to all configured MediaTypeExtensionsResolver instances and aggregate
|
||||
* the list of all extensions found.
|
||||
*/
|
||||
public List<String> resolveExtensions(MediaType mediaType) {
|
||||
Set<String> extensions = new LinkedHashSet<String>();
|
||||
for (MediaTypeExtensionsResolver resolver : this.extensionResolvers) {
|
||||
extensions.addAll(resolver.resolveExtensions(mediaType));
|
||||
}
|
||||
return new ArrayList<String>(extensions);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2002-2012 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.accept;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.HttpMediaTypeNotAcceptableException;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
|
||||
/**
|
||||
* A strategy for resolving the requested media types in a request.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 3.2
|
||||
*/
|
||||
public interface ContentNegotiationStrategy {
|
||||
|
||||
/**
|
||||
* Resolve the given request to a list of media types. The returned list is
|
||||
* ordered by specificity first and by quality parameter second.
|
||||
*
|
||||
* @param request 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
|
||||
*/
|
||||
List<MediaType> resolveMediaTypes(NativeWebRequest webRequest) throws HttpMediaTypeNotAcceptableException;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright 2002-2012 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.accept;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
|
||||
/**
|
||||
* A 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 final MediaType defaultContentType;
|
||||
|
||||
/**
|
||||
* Create an instance that always returns the given content type.
|
||||
*/
|
||||
public FixedContentNegotiationStrategy(MediaType defaultContentType) {
|
||||
this.defaultContentType = defaultContentType;
|
||||
}
|
||||
|
||||
public List<MediaType> resolveMediaTypes(NativeWebRequest webRequest) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Requested media types is " + this.defaultContentType + " (based on default MediaType)");
|
||||
}
|
||||
return Collections.singletonList(this.defaultContentType);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright 2002-2012 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.accept;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.HttpMediaTypeNotAcceptableException;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
|
||||
/**
|
||||
* A ContentNegotiationStrategy that parses the 'Accept' header of the request.
|
||||
*
|
||||
* @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.
|
||||
*/
|
||||
public List<MediaType> resolveMediaTypes(NativeWebRequest webRequest) throws HttpMediaTypeNotAcceptableException {
|
||||
String acceptHeader = webRequest.getHeader(ACCEPT_HEADER);
|
||||
try {
|
||||
if (StringUtils.hasText(acceptHeader)) {
|
||||
List<MediaType> mediaTypes = MediaType.parseMediaTypes(acceptHeader);
|
||||
MediaType.sortBySpecificityAndQuality(mediaTypes);
|
||||
return mediaTypes;
|
||||
}
|
||||
}
|
||||
catch (IllegalArgumentException ex) {
|
||||
throw new HttpMediaTypeNotAcceptableException(
|
||||
"Could not parse accept header [" + acceptHeader + "]: " + ex.getMessage());
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright 2002-2012 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.accept;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
|
||||
/**
|
||||
* An implementation of {@link MediaTypeExtensionsResolver} that maintains a lookup
|
||||
* from extension to MediaType.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 3.2
|
||||
*/
|
||||
public class MappingMediaTypeExtensionsResolver implements MediaTypeExtensionsResolver {
|
||||
|
||||
private ConcurrentMap<String, MediaType> mediaTypes = new ConcurrentHashMap<String, MediaType>();
|
||||
|
||||
/**
|
||||
* Create an instance with the given mappings between extensions and media types.
|
||||
* @throws IllegalArgumentException if a media type string cannot be parsed
|
||||
*/
|
||||
public MappingMediaTypeExtensionsResolver(Map<String, String> mediaTypes) {
|
||||
if (mediaTypes != null) {
|
||||
for (Map.Entry<String, String> entry : mediaTypes.entrySet()) {
|
||||
String extension = entry.getKey().toLowerCase(Locale.ENGLISH);
|
||||
MediaType mediaType = MediaType.parseMediaType(entry.getValue());
|
||||
this.mediaTypes.put(extension, mediaType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the extensions applicable to the given MediaType.
|
||||
* @return 0 or more extensions, never {@code null}
|
||||
*/
|
||||
public List<String> resolveExtensions(MediaType mediaType) {
|
||||
List<String> result = new ArrayList<String>();
|
||||
for (Entry<String, MediaType> entry : this.mediaTypes.entrySet()) {
|
||||
if (mediaType.includes(entry.getValue())) {
|
||||
result.add(entry.getKey());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the MediaType mapped to the given extension.
|
||||
* @return a MediaType for the key or {@code null}
|
||||
*/
|
||||
public 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) {
|
||||
this.mediaTypes.putIfAbsent(extension, mediaType);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2002-2012 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.accept;
|
||||
|
||||
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".
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 3.2
|
||||
*/
|
||||
public interface MediaTypeExtensionsResolver {
|
||||
|
||||
/**
|
||||
* Resolve the given media type to a list of path extensions.
|
||||
*
|
||||
* @param mediaType the media type to resolve
|
||||
* @return a list of extensions or an empty list, never {@code null}
|
||||
*/
|
||||
List<String> resolveExtensions(MediaType mediaType);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright 2002-2012 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.accept;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.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.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 3.2
|
||||
*/
|
||||
public class ParameterContentNegotiationStrategy extends AbstractMappingContentNegotiationStrategy {
|
||||
|
||||
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
|
||||
*/
|
||||
public ParameterContentNegotiationStrategy(Map<String, String> mediaTypes) {
|
||||
super(mediaTypes);
|
||||
Assert.notEmpty(mediaTypes, "Cannot look up media types without any mappings");
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the parameter name that can be used to determine the requested media type.
|
||||
* <p>The default parameter name is {@code format}.
|
||||
*/
|
||||
public void setParameterName(String parameterName) {
|
||||
this.parameterName = parameterName;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getMediaTypeKey(NativeWebRequest webRequest) {
|
||||
return webRequest.getParameter(this.parameterName);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleMatch(String mediaTypeKey, MediaType mediaType) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Requested media type is '" + mediaType + "' (based on parameter '" +
|
||||
this.parameterName + "'='" + mediaTypeKey + "')");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
/*
|
||||
* Copyright 2002-2012 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.accept;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.activation.FileTypeMap;
|
||||
import javax.activation.MimetypesFileTypeMap;
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
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 used as follows:
|
||||
*
|
||||
* <ol>
|
||||
* <li>Look upin the map of media types provided to the constructor
|
||||
* <li>Call to {@link ServletContext#getMimeType(String)}
|
||||
* <li>Use the Java Activation framework
|
||||
* </ol>
|
||||
*
|
||||
* <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.
|
||||
*
|
||||
* @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());
|
||||
|
||||
private static final Log logger = LogFactory.getLog(PathExtensionContentNegotiationStrategy.class);
|
||||
|
||||
private static final UrlPathHelper urlPathHelper = new UrlPathHelper();
|
||||
|
||||
static {
|
||||
urlPathHelper.setUrlDecode(false);
|
||||
}
|
||||
|
||||
private boolean useJaf = JAF_PRESENT;
|
||||
|
||||
/**
|
||||
* Create an instance with the given extension-to-MediaType lookup.
|
||||
* @throws IllegalArgumentException if a media type string cannot be parsed
|
||||
*/
|
||||
public PathExtensionContentNegotiationStrategy(Map<String, String> mediaTypes) {
|
||||
super(mediaTypes);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public PathExtensionContentNegotiationStrategy() {
|
||||
super(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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).
|
||||
*/
|
||||
public void setUseJaf(boolean useJaf) {
|
||||
this.useJaf = useJaf;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getMediaTypeKey(NativeWebRequest webRequest) {
|
||||
HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
|
||||
if (servletRequest == null) {
|
||||
logger.warn("An HttpServletRequest is required to determine the media type key");
|
||||
return null;
|
||||
}
|
||||
String path = urlPathHelper.getLookupPathForRequest(servletRequest);
|
||||
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) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Requested media type is '" + mediaType + "' (based on file extension '" + extension + "')");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected MediaType handleNoMatch(NativeWebRequest webRequest, String extension) {
|
||||
MediaType mediaType = null;
|
||||
HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
|
||||
if (servletRequest != null) {
|
||||
String mimeType = servletRequest.getServletContext().getMimeType("file." + extension);
|
||||
if (StringUtils.hasText(mimeType)) {
|
||||
mediaType = MediaType.parseMediaType(mimeType);
|
||||
}
|
||||
}
|
||||
if ((mediaType == null || MediaType.APPLICATION_OCTET_STREAM.equals(mediaType)) && this.useJaf) {
|
||||
MediaType jafMediaType = JafMediaTypeFactory.getMediaType("file." + extension);
|
||||
if (jafMediaType != null && !MediaType.APPLICATION_OCTET_STREAM.equals(jafMediaType)) {
|
||||
mediaType = jafMediaType;
|
||||
}
|
||||
}
|
||||
return mediaType;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Inner class to avoid hard-coded dependency on JAF.
|
||||
*/
|
||||
private static class JafMediaTypeFactory {
|
||||
|
||||
private static final FileTypeMap fileTypeMap;
|
||||
|
||||
static {
|
||||
fileTypeMap = initFileTypeMap();
|
||||
}
|
||||
|
||||
/**
|
||||
* Find extended mime.types from the spring-context-support module.
|
||||
*/
|
||||
private static FileTypeMap initFileTypeMap() {
|
||||
Resource resource = new ClassPathResource("org/springframework/mail/javamail/mime.types");
|
||||
if (resource.exists()) {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Loading Java Activation Framework FileTypeMap from " + resource);
|
||||
}
|
||||
InputStream inputStream = null;
|
||||
try {
|
||||
inputStream = resource.getInputStream();
|
||||
return new MimetypesFileTypeMap(inputStream);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
// ignore
|
||||
}
|
||||
finally {
|
||||
if (inputStream != null) {
|
||||
try {
|
||||
inputStream.close();
|
||||
}
|
||||
catch (IOException ex) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Loading default Java Activation Framework FileTypeMap");
|
||||
}
|
||||
return FileTypeMap.getDefaultFileTypeMap();
|
||||
}
|
||||
|
||||
public static MediaType getMediaType(String filename) {
|
||||
String mediaType = fileTypeMap.getContentType(filename);
|
||||
return (StringUtils.hasText(mediaType) ? MediaType.parseMediaType(mediaType) : null);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
|
||||
/**
|
||||
* This package contains classes used to determine the requested the media types in a request.
|
||||
*
|
||||
* <p>{@link org.springframework.web.accept.ContentNegotiationStrategy} is the main
|
||||
* abstraction for determining requested {@linkplain org.springframework.http.MediaType media types}
|
||||
* with implementations based on
|
||||
* {@linkplain org.springframework.web.accept.PathExtensionContentNegotiationStrategy path extensions}, a
|
||||
* {@linkplain org.springframework.web.accept.ParameterContentNegotiationStrategy a request parameter}, the
|
||||
* {@linkplain org.springframework.web.accept.HeaderContentNegotiationStrategy 'Accept' header}, or a
|
||||
* {@linkplain org.springframework.web.accept.FixedContentNegotiationStrategy default content type}.
|
||||
*
|
||||
* <p>{@link org.springframework.web.accept.ContentNegotiationManager} is used to delegate to one
|
||||
* ore more of the above strategies in a specific order.
|
||||
*/
|
||||
package org.springframework.web.accept;
|
||||
|
||||
Reference in New Issue
Block a user