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:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user