Work around Servlet dependency in content negotiation

Before this change the PathExtensionContentNegotiationStrategy accessed
the ServletContext via request.getServletContext, which is Servlet 3
specific. To work around it, there is now a Servlet-specific sub-class
that accepts a ServletContext as a constructor argument.

The ContentNegotiationManagerFactoryBean is now ServletContextAware and
if it has a ServletContext it creates the Servlet-specific sub-class
of PathExtensionContentNegotiationStrategy.

The ContentNegotiationManagerFactoryBean is now also used in several
places internally -- MVC namespace, MVC Java config, and the
ContentNegotiatingViewResolver -- to reduce duplication.

Issue: SPR-9826
This commit is contained in:
Rossen Stoyanchev
2012-09-26 09:16:08 -04:00
parent 2bb0104556
commit 7b30ffd522
11 changed files with 227 additions and 187 deletions

View File

@@ -28,6 +28,7 @@ import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.http.MediaType;
import org.springframework.util.CollectionUtils;
import org.springframework.web.context.ServletContextAware;
/**
* A factory providing convenient access to a {@code ContentNegotiationManager}
@@ -41,7 +42,8 @@ import org.springframework.util.CollectionUtils;
* @author Rossen Stoyanchev
* @since 3.2
*/
public class ContentNegotiationManagerFactoryBean implements FactoryBean<ContentNegotiationManager>, InitializingBean {
public class ContentNegotiationManagerFactoryBean
implements FactoryBean<ContentNegotiationManager>, InitializingBean, ServletContextAware {
private boolean favorPathExtension = true;
@@ -49,7 +51,7 @@ public class ContentNegotiationManagerFactoryBean implements FactoryBean<Content
private boolean ignoreAcceptHeader = false;
private Map<String, MediaType> mediaTypes = new HashMap<String, MediaType>();
private Properties mediaTypes = new Properties();
private Boolean useJaf;
@@ -59,6 +61,9 @@ public class ContentNegotiationManagerFactoryBean implements FactoryBean<Content
private ContentNegotiationManager contentNegotiationManager;
private ServletContext servletContext;
/**
* Indicate whether the extension of the request path should be used to determine
* the requested media type with the <em>highest priority</em>.
@@ -84,6 +89,10 @@ public class ContentNegotiationManagerFactoryBean implements FactoryBean<Content
}
}
public Properties getMediaTypes() {
return this.mediaTypes;
}
/**
* Indicate whether to use the Java Activation Framework as a fallback option
* to map from file extensions to media types. This is used only when
@@ -141,11 +150,24 @@ public class ContentNegotiationManagerFactoryBean implements FactoryBean<Content
this.defaultContentType = defaultContentType;
}
public void setServletContext(ServletContext servletContext) {
this.servletContext = servletContext;
}
public void afterPropertiesSet() throws Exception {
List<ContentNegotiationStrategy> strategies = new ArrayList<ContentNegotiationStrategy>();
Map<String, MediaType> mediaTypesMap = new HashMap<String, MediaType>();
CollectionUtils.mergePropertiesIntoMap(this.mediaTypes, mediaTypesMap);
if (this.favorPathExtension) {
PathExtensionContentNegotiationStrategy strategy = new PathExtensionContentNegotiationStrategy(this.mediaTypes);
PathExtensionContentNegotiationStrategy strategy;
if (this.servletContext != null) {
strategy = new ServletPathExtensionContentNegotiationStrategy(this.servletContext, mediaTypesMap);
}
else {
strategy = new PathExtensionContentNegotiationStrategy(mediaTypesMap);
}
if (this.useJaf != null) {
strategy.setUseJaf(this.useJaf);
}
@@ -153,7 +175,7 @@ public class ContentNegotiationManagerFactoryBean implements FactoryBean<Content
}
if (this.favorParameter) {
ParameterContentNegotiationStrategy strategy = new ParameterContentNegotiationStrategy(this.mediaTypes);
ParameterContentNegotiationStrategy strategy = new ParameterContentNegotiationStrategy(mediaTypesMap);
strategy.setParameterName(this.parameterName);
strategies.add(strategy);
}

View File

@@ -23,7 +23,6 @@ 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;
@@ -38,25 +37,23 @@ 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:
* 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.
*
* <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.
* <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 boolean JAF_PRESENT = ClassUtils.isPresent("javax.activation.FileTypeMap",
PathExtensionContentNegotiationStrategy.class.getClassLoader());
private static final Log logger = LogFactory.getLog(PathExtensionContentNegotiationStrategy.class);
@@ -68,6 +65,7 @@ public class PathExtensionContentNegotiationStrategy extends AbstractMappingCont
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
@@ -78,8 +76,7 @@ public class PathExtensionContentNegotiationStrategy extends AbstractMappingCont
/**
* 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.
* later on if any extensions are resolved through the Java Activation framework.
*/
public PathExtensionContentNegotiationStrategy() {
super(null);
@@ -112,21 +109,13 @@ public class PathExtensionContentNegotiationStrategy extends AbstractMappingCont
@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) {
if (this.useJaf) {
MediaType jafMediaType = JafMediaTypeFactory.getMediaType("file." + extension);
if (jafMediaType != null && !MediaType.APPLICATION_OCTET_STREAM.equals(jafMediaType)) {
mediaType = jafMediaType;
return jafMediaType;
}
}
return mediaType;
return null;
}

View File

@@ -0,0 +1,84 @@
/*
* 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 javax.servlet.ServletContext;
import org.springframework.http.MediaType;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
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.
*
* @author Rossen Stoyanchev
* @since 3.2
*/
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) {
super(mediaTypes);
Assert.notNull(servletContext, "ServletContext is required!");
this.servletContext = servletContext;
}
/**
* 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 ServletPathExtensionContentNegotiationStrategy(ServletContext servletContext) {
this(servletContext, null);
}
/**
* Look up the given extension via {@link ServletContext#getMimeType(String)}
* and if that doesn't help, delegate to the parent implementation.
*/
@Override
protected MediaType handleNoMatch(NativeWebRequest webRequest, String extension) {
MediaType mediaType = null;
if (this.servletContext != null) {
String mimeType = this.servletContext.getMimeType("file." + extension);
if (StringUtils.hasText(mimeType)) {
mediaType = MediaType.parseMediaType(mimeType);
}
}
if (mediaType == null || MediaType.APPLICATION_OCTET_STREAM.equals(mediaType)) {
MediaType superMediaType = super.handleNoMatch(webRequest, extension);
if (superMediaType != null) {
mediaType = superMediaType;
}
}
return mediaType;
}
}