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.beans.factory.InitializingBean;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
import org.springframework.web.context.ServletContextAware;
/** /**
* A factory providing convenient access to a {@code ContentNegotiationManager} * A factory providing convenient access to a {@code ContentNegotiationManager}
@ -41,7 +42,8 @@ import org.springframework.util.CollectionUtils;
* @author Rossen Stoyanchev * @author Rossen Stoyanchev
* @since 3.2 * @since 3.2
*/ */
public class ContentNegotiationManagerFactoryBean implements FactoryBean<ContentNegotiationManager>, InitializingBean { public class ContentNegotiationManagerFactoryBean
implements FactoryBean<ContentNegotiationManager>, InitializingBean, ServletContextAware {
private boolean favorPathExtension = true; private boolean favorPathExtension = true;
@ -49,7 +51,7 @@ public class ContentNegotiationManagerFactoryBean implements FactoryBean<Content
private boolean ignoreAcceptHeader = false; private boolean ignoreAcceptHeader = false;
private Map<String, MediaType> mediaTypes = new HashMap<String, MediaType>(); private Properties mediaTypes = new Properties();
private Boolean useJaf; private Boolean useJaf;
@ -59,6 +61,9 @@ public class ContentNegotiationManagerFactoryBean implements FactoryBean<Content
private ContentNegotiationManager contentNegotiationManager; private ContentNegotiationManager contentNegotiationManager;
private ServletContext servletContext;
/** /**
* Indicate whether the extension of the request path should be used to determine * Indicate whether the extension of the request path should be used to determine
* the requested media type with the <em>highest priority</em>. * 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 * 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 * 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; this.defaultContentType = defaultContentType;
} }
public void setServletContext(ServletContext servletContext) {
this.servletContext = servletContext;
}
public void afterPropertiesSet() throws Exception { public void afterPropertiesSet() throws Exception {
List<ContentNegotiationStrategy> strategies = new ArrayList<ContentNegotiationStrategy>(); List<ContentNegotiationStrategy> strategies = new ArrayList<ContentNegotiationStrategy>();
Map<String, MediaType> mediaTypesMap = new HashMap<String, MediaType>();
CollectionUtils.mergePropertiesIntoMap(this.mediaTypes, mediaTypesMap);
if (this.favorPathExtension) { 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) { if (this.useJaf != null) {
strategy.setUseJaf(this.useJaf); strategy.setUseJaf(this.useJaf);
} }
@ -153,7 +175,7 @@ public class ContentNegotiationManagerFactoryBean implements FactoryBean<Content
} }
if (this.favorParameter) { if (this.favorParameter) {
ParameterContentNegotiationStrategy strategy = new ParameterContentNegotiationStrategy(this.mediaTypes); ParameterContentNegotiationStrategy strategy = new ParameterContentNegotiationStrategy(mediaTypesMap);
strategy.setParameterName(this.parameterName); strategy.setParameterName(this.parameterName);
strategies.add(strategy); strategies.add(strategy);
} }

View File

@ -23,7 +23,6 @@ import java.util.Map;
import javax.activation.FileTypeMap; import javax.activation.FileTypeMap;
import javax.activation.MimetypesFileTypeMap; import javax.activation.MimetypesFileTypeMap;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
@ -38,25 +37,23 @@ import org.springframework.web.util.UrlPathHelper;
import org.springframework.web.util.WebUtils; import org.springframework.web.util.WebUtils;
/** /**
* A ContentNegotiationStrategy that uses the path extension of the URL to determine * A ContentNegotiationStrategy that uses the path extension of the URL to
* what media types are requested. The path extension is used as follows: * 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> * <p>
* <li>Look upin the map of media types provided to the constructor * The presence of the Java Activation framework is detected and enabled
* <li>Call to {@link ServletContext#getMimeType(String)} * automatically but the {@link #setUseJaf(boolean)} property may be used to
* <li>Use the Java Activation framework * override that setting.
* </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 * @author Rossen Stoyanchev
* @since 3.2 * @since 3.2
*/ */
public class PathExtensionContentNegotiationStrategy extends AbstractMappingContentNegotiationStrategy { public class PathExtensionContentNegotiationStrategy extends AbstractMappingContentNegotiationStrategy {
private static final boolean JAF_PRESENT = private static final boolean JAF_PRESENT = ClassUtils.isPresent("javax.activation.FileTypeMap",
ClassUtils.isPresent("javax.activation.FileTypeMap", PathExtensionContentNegotiationStrategy.class.getClassLoader()); PathExtensionContentNegotiationStrategy.class.getClassLoader());
private static final Log logger = LogFactory.getLog(PathExtensionContentNegotiationStrategy.class); private static final Log logger = LogFactory.getLog(PathExtensionContentNegotiationStrategy.class);
@ -68,6 +65,7 @@ public class PathExtensionContentNegotiationStrategy extends AbstractMappingCont
private boolean useJaf = JAF_PRESENT; private boolean useJaf = JAF_PRESENT;
/** /**
* Create an instance with the given extension-to-MediaType lookup. * Create an instance with the given extension-to-MediaType lookup.
* @throws IllegalArgumentException if a media type string cannot be parsed * @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 * 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)} * later on if any extensions are resolved through the Java Activation framework.
* or through the Java Activation framework.
*/ */
public PathExtensionContentNegotiationStrategy() { public PathExtensionContentNegotiationStrategy() {
super(null); super(null);
@ -112,21 +109,13 @@ public class PathExtensionContentNegotiationStrategy extends AbstractMappingCont
@Override @Override
protected MediaType handleNoMatch(NativeWebRequest webRequest, String extension) { protected MediaType handleNoMatch(NativeWebRequest webRequest, String extension) {
MediaType mediaType = null; if (this.useJaf) {
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); MediaType jafMediaType = JafMediaTypeFactory.getMediaType("file." + extension);
if (jafMediaType != null && !MediaType.APPLICATION_OCTET_STREAM.equals(jafMediaType)) { 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;
}
}

View File

@ -42,9 +42,11 @@ public class ContentNegotiationManagerFactoryBeanTests {
@Before @Before
public void setup() { public void setup() {
this.factoryBean = new ContentNegotiationManagerFactoryBean();
this.servletRequest = new MockHttpServletRequest(); this.servletRequest = new MockHttpServletRequest();
this.webRequest = new ServletWebRequest(this.servletRequest); this.webRequest = new ServletWebRequest(this.servletRequest);
this.factoryBean = new ContentNegotiationManagerFactoryBean();
this.factoryBean.setServletContext(this.servletRequest.getServletContext());
} }
@Test @Test

View File

@ -22,6 +22,8 @@ import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import javax.servlet.ServletContext;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
@ -44,7 +46,7 @@ public class PathExtensionContentNegotiationStrategyTests {
@Before @Before
public void setup() { public void setup() {
this.servletRequest = new MockHttpServletRequest(); this.servletRequest = new MockHttpServletRequest();
this.webRequest = new ServletWebRequest(servletRequest ); this.webRequest = new ServletWebRequest(servletRequest);
} }
@Test @Test
@ -74,8 +76,12 @@ public class PathExtensionContentNegotiationStrategyTests {
@Test @Test
public void getMediaTypeFromFilenameNoJaf() { public void getMediaTypeFromFilenameNoJaf() {
this.servletRequest.setRequestURI("test.xls"); this.servletRequest.setRequestURI("test.xls");
PathExtensionContentNegotiationStrategy strategy = new PathExtensionContentNegotiationStrategy();
ServletContext servletContext = this.servletRequest.getServletContext();
PathExtensionContentNegotiationStrategy strategy =
new ServletPathExtensionContentNegotiationStrategy(servletContext);
strategy.setUseJaf(false); strategy.setUseJaf(false);
List<MediaType> mediaTypes = strategy.resolveMediaTypes(this.webRequest); List<MediaType> mediaTypes = strategy.resolveMediaTypes(this.webRequest);

View File

@ -16,10 +16,8 @@
package org.springframework.web.servlet.config; package org.springframework.web.servlet.config;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Properties;
import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder; import org.springframework.beans.factory.config.BeanDefinitionHolder;
@ -49,8 +47,7 @@ import org.springframework.util.xml.DomUtils;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.web.HttpRequestHandler; import org.springframework.web.HttpRequestHandler;
import org.springframework.web.accept.ContentNegotiationManager; import org.springframework.web.accept.ContentNegotiationManager;
import org.springframework.web.accept.HeaderContentNegotiationStrategy; import org.springframework.web.accept.ContentNegotiationManagerFactoryBean;
import org.springframework.web.accept.PathExtensionContentNegotiationStrategy;
import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.support.ConfigurableWebBindingInitializer; import org.springframework.web.bind.support.ConfigurableWebBindingInitializer;
@ -289,34 +286,32 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
contentNegotiationManagerRef = new RuntimeBeanReference(element.getAttribute("content-negotiation-manager")); contentNegotiationManagerRef = new RuntimeBeanReference(element.getAttribute("content-negotiation-manager"));
} }
else { else {
RootBeanDefinition managerDef = new RootBeanDefinition(ContentNegotiationManager.class); RootBeanDefinition factoryBeanDef = new RootBeanDefinition(ContentNegotiationManagerFactoryBean.class);
managerDef.setSource(source); factoryBeanDef.setSource(source);
managerDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); factoryBeanDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
PathExtensionContentNegotiationStrategy strategy1 = new PathExtensionContentNegotiationStrategy(getDefaultMediaTypes()); factoryBeanDef.getPropertyValues().add("mediaTypes", getDefaultMediaTypes());
HeaderContentNegotiationStrategy strategy2 = new HeaderContentNegotiationStrategy();
managerDef.getConstructorArgumentValues().addIndexedArgumentValue(0, Arrays.asList(strategy1,strategy2));
String beanName = "mvcContentNegotiationManager"; String beanName = "mvcContentNegotiationManager";
parserContext.getReaderContext().getRegistry().registerBeanDefinition(beanName , managerDef); parserContext.getReaderContext().getRegistry().registerBeanDefinition(beanName , factoryBeanDef);
parserContext.registerComponent(new BeanComponentDefinition(managerDef, beanName)); parserContext.registerComponent(new BeanComponentDefinition(factoryBeanDef, beanName));
contentNegotiationManagerRef = new RuntimeBeanReference(beanName); contentNegotiationManagerRef = new RuntimeBeanReference(beanName);
} }
return contentNegotiationManagerRef; return contentNegotiationManagerRef;
} }
private Map<String, MediaType> getDefaultMediaTypes() { private Properties getDefaultMediaTypes() {
Map<String, MediaType> map = new HashMap<String, MediaType>(); Properties props = new Properties();
if (romePresent) { if (romePresent) {
map.put("atom", MediaType.APPLICATION_ATOM_XML); props.put("atom", MediaType.APPLICATION_ATOM_XML_VALUE);
map.put("rss", MediaType.valueOf("application/rss+xml")); props.put("rss", "application/rss+xml");
} }
if (jackson2Present || jacksonPresent) { if (jackson2Present || jacksonPresent) {
map.put("json", MediaType.APPLICATION_JSON); props.put("json", MediaType.APPLICATION_JSON_VALUE);
} }
if (jaxb2Present) { if (jaxb2Present) {
map.put("xml", MediaType.APPLICATION_XML); props.put("xml", MediaType.APPLICATION_XML_VALUE);
} }
return map; return props;
} }
private RuntimeBeanReference getMessageCodesResolver(Element element, Object source, ParserContext parserContext) { private RuntimeBeanReference getMessageCodesResolver(Element element, Object source, ParserContext parserContext) {

View File

@ -15,22 +15,13 @@
*/ */
package org.springframework.web.servlet.config.annotation; package org.springframework.web.servlet.config.annotation;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map; import java.util.Map;
import javax.servlet.ServletContext; import javax.servlet.ServletContext;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.util.CollectionUtils;
import org.springframework.web.accept.ContentNegotiationManager; import org.springframework.web.accept.ContentNegotiationManager;
import org.springframework.web.accept.ContentNegotiationStrategy; import org.springframework.web.accept.ContentNegotiationManagerFactoryBean;
import org.springframework.web.accept.FixedContentNegotiationStrategy;
import org.springframework.web.accept.HeaderContentNegotiationStrategy;
import org.springframework.web.accept.ParameterContentNegotiationStrategy;
import org.springframework.web.accept.PathExtensionContentNegotiationStrategy;
/** /**
* Helps with configuring a {@link ContentNegotiationManager}. * Helps with configuring a {@link ContentNegotiationManager}.
@ -45,19 +36,15 @@ import org.springframework.web.accept.PathExtensionContentNegotiationStrategy;
*/ */
public class ContentNegotiationConfigurer { public class ContentNegotiationConfigurer {
private boolean favorPathExtension = true; private ContentNegotiationManagerFactoryBean factoryBean = new ContentNegotiationManagerFactoryBean();
private boolean favorParameter = false;
private boolean ignoreAcceptHeader = false; /**
* Class constructor with {@link javax.servlet.ServletContext}.
private Map<String, MediaType> mediaTypes = new HashMap<String, MediaType>(); */
public ContentNegotiationConfigurer(ServletContext servletContext) {
private Boolean useJaf; this.factoryBean.setServletContext(servletContext);
}
private String parameterName;
private MediaType defaultContentType;
/** /**
* Indicate whether the extension of the request path should be used to determine * Indicate whether the extension of the request path should be used to determine
@ -66,32 +53,29 @@ public class ContentNegotiationConfigurer {
* for {@code /hotels.pdf} will be interpreted as a request for * for {@code /hotels.pdf} will be interpreted as a request for
* {@code "application/pdf"} regardless of the {@code Accept} header. * {@code "application/pdf"} regardless of the {@code Accept} header.
*/ */
public ContentNegotiationConfigurer setFavorPathExtension(boolean favorPathExtension) { public ContentNegotiationConfigurer favorPathExtension(boolean favorPathExtension) {
this.favorPathExtension = favorPathExtension; this.factoryBean.setFavorPathExtension(favorPathExtension);
return this; return this;
} }
/** /**
* Add mappings from file extensions to media types. * Add mappings from file extensions to media types.
* <p>If this property is not set, the Java Action Framework, if available, may * <p>If this property is not set, the Java Action Framework, if available, may
* still be used in conjunction with {@link #setFavorPathExtension(boolean)}. * still be used in conjunction with {@link #favorPathExtension(boolean)}.
*/ */
public ContentNegotiationConfigurer addMediaType(String extension, MediaType mediaType) { public ContentNegotiationConfigurer mediaType(String extension, MediaType mediaType) {
this.mediaTypes.put(extension, mediaType); this.factoryBean.getMediaTypes().put(extension, mediaType);
return this; return this;
} }
/** /**
* Add mappings from file extensions to media types. * Add mappings from file extensions to media types.
* <p>If this property is not set, the Java Action Framework, if available, may * <p>If this property is not set, the Java Action Framework, if available, may
* still be used in conjunction with {@link #setFavorPathExtension(boolean)}. * still be used in conjunction with {@link #favorPathExtension(boolean)}.
*/ */
public ContentNegotiationConfigurer addMediaTypes(Map<String, MediaType> mediaTypes) { public ContentNegotiationConfigurer mediaTypes(Map<String, MediaType> mediaTypes) {
if (!CollectionUtils.isEmpty(mediaTypes)) { if (mediaTypes != null) {
for (Map.Entry<String, MediaType> entry : mediaTypes.entrySet()) { this.factoryBean.getMediaTypes().putAll(mediaTypes);
String extension = entry.getKey().toLowerCase(Locale.ENGLISH);
this.mediaTypes.put(extension, entry.getValue());
}
} }
return this; return this;
} }
@ -99,29 +83,24 @@ public class ContentNegotiationConfigurer {
/** /**
* Add mappings from file extensions to media types replacing any previous mappings. * Add mappings from file extensions to media types replacing any previous mappings.
* <p>If this property is not set, the Java Action Framework, if available, may * <p>If this property is not set, the Java Action Framework, if available, may
* still be used in conjunction with {@link #setFavorPathExtension(boolean)}. * still be used in conjunction with {@link #favorPathExtension(boolean)}.
*/ */
public ContentNegotiationConfigurer replaceMediaTypes(Map<String, MediaType> mediaTypes) { public ContentNegotiationConfigurer replaceMediaTypes(Map<String, MediaType> mediaTypes) {
this.mediaTypes.clear(); this.factoryBean.getMediaTypes().clear();
if (!CollectionUtils.isEmpty(mediaTypes)) { mediaTypes(mediaTypes);
for (Map.Entry<String, MediaType> entry : mediaTypes.entrySet()) {
String extension = entry.getKey().toLowerCase(Locale.ENGLISH);
this.mediaTypes.put(extension, entry.getValue());
}
}
return this; return this;
} }
/** /**
* Indicate whether to use the Java Activation Framework as a fallback option * 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 * to map from file extensions to media types. This is used only when
* {@link #setFavorPathExtension(boolean)} is set to {@code true}. * {@link #favorPathExtension(boolean)} is set to {@code true}.
* <p>The default value is {@code true}. * <p>The default value is {@code true}.
* @see #parameterName * @see #parameterName
* @see #setMediaTypes(Map) * @see #setMediaTypes(Map)
*/ */
public ContentNegotiationConfigurer setUseJaf(boolean useJaf) { public ContentNegotiationConfigurer useJaf(boolean useJaf) {
this.useJaf = useJaf; this.factoryBean.setUseJaf(useJaf);
return this; return this;
} }
@ -134,10 +113,10 @@ public class ContentNegotiationConfigurer {
* {@code "application/pdf"} regardless of the {@code Accept} header. * {@code "application/pdf"} regardless of the {@code Accept} header.
* <p>To use this option effectively you must also configure the MediaType * <p>To use this option effectively you must also configure the MediaType
* type mappings via {@link #setMediaTypes(Map)}. * type mappings via {@link #setMediaTypes(Map)}.
* @see #setParameterName(String) * @see #parameterName(String)
*/ */
public ContentNegotiationConfigurer setFavorParameter(boolean favorParameter) { public ContentNegotiationConfigurer favorParameter(boolean favorParameter) {
this.favorParameter = favorParameter; this.factoryBean.setFavorParameter(favorParameter);
return this; return this;
} }
@ -146,8 +125,8 @@ public class ContentNegotiationConfigurer {
* if the {@link #setFavorParameter} property is {@code true}. * if the {@link #setFavorParameter} property is {@code true}.
* <p>The default parameter name is {@code "format"}. * <p>The default parameter name is {@code "format"}.
*/ */
public ContentNegotiationConfigurer setParameterName(String parameterName) { public ContentNegotiationConfigurer parameterName(String parameterName) {
this.parameterName = parameterName; this.factoryBean.setParameterName(parameterName);
return this; return this;
} }
@ -158,8 +137,8 @@ public class ContentNegotiationConfigurer {
* possibly a request parameter if configured. * possibly a request parameter if configured.
* <p>By default this value is set to {@code false}. * <p>By default this value is set to {@code false}.
*/ */
public ContentNegotiationConfigurer setIgnoreAcceptHeader(boolean ignoreAcceptHeader) { public ContentNegotiationConfigurer ignoreAcceptHeader(boolean ignoreAcceptHeader) {
this.ignoreAcceptHeader = ignoreAcceptHeader; this.factoryBean.setIgnoreAcceptHeader(ignoreAcceptHeader);
return this; return this;
} }
@ -169,36 +148,17 @@ public class ContentNegotiationConfigurer {
* nor a request parameter, nor the {@code Accept} header could help determine * nor a request parameter, nor the {@code Accept} header could help determine
* the requested content type. * the requested content type.
*/ */
public ContentNegotiationConfigurer setDefaultContentType(MediaType defaultContentType) { public ContentNegotiationConfigurer defaultContentType(MediaType defaultContentType) {
this.defaultContentType = defaultContentType; this.factoryBean.setDefaultContentType(defaultContentType);
return this; return this;
} }
/** /**
* @return the configured {@link ContentNegotiationManager} instance * Return the configured {@link ContentNegotiationManager} instance
*/ */
protected ContentNegotiationManager getContentNegotiationManager() { protected ContentNegotiationManager getContentNegotiationManager() throws Exception {
List<ContentNegotiationStrategy> strategies = new ArrayList<ContentNegotiationStrategy>(); this.factoryBean.afterPropertiesSet();
if (this.favorPathExtension) { return this.factoryBean.getObject();
PathExtensionContentNegotiationStrategy strategy = new PathExtensionContentNegotiationStrategy(this.mediaTypes);
if (this.useJaf != null) {
strategy.setUseJaf(this.useJaf);
}
strategies.add(strategy);
}
if (this.favorParameter) {
ParameterContentNegotiationStrategy strategy = new ParameterContentNegotiationStrategy(this.mediaTypes);
strategy.setParameterName(this.parameterName);
strategies.add(strategy);
}
if (!this.ignoreAcceptHeader) {
strategies.add(new HeaderContentNegotiationStrategy());
}
if (this.defaultContentType != null) {
strategies.add(new FixedContentNegotiationStrategy(this.defaultContentType));
}
ContentNegotiationStrategy[] array = strategies.toArray(new ContentNegotiationStrategy[strategies.size()]);
return new ContentNegotiationManager(array);
} }
} }

View File

@ -167,10 +167,18 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
private List<HttpMessageConverter<?>> messageConverters; private List<HttpMessageConverter<?>> messageConverters;
/**
* Set the {@link javax.servlet.ServletContext}, e.g. for resource handling,
* looking up file extensions, etc.
*/
public void setServletContext(ServletContext servletContext) { public void setServletContext(ServletContext servletContext) {
this.servletContext = servletContext; this.servletContext = servletContext;
} }
/**
* Set the Spring {@link ApplicationContext}, e.g. for resource loading.
*/
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext; this.applicationContext = applicationContext;
} }
@ -219,10 +227,15 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
@Bean @Bean
public ContentNegotiationManager mvcContentNegotiationManager() { public ContentNegotiationManager mvcContentNegotiationManager() {
if (this.contentNegotiationManager == null) { if (this.contentNegotiationManager == null) {
ContentNegotiationConfigurer configurer = new ContentNegotiationConfigurer(); ContentNegotiationConfigurer configurer = new ContentNegotiationConfigurer(this.servletContext);
configurer.addMediaTypes(getDefaultMediaTypes()); configurer.mediaTypes(getDefaultMediaTypes());
configureContentNegotiation(configurer); configureContentNegotiation(configurer);
this.contentNegotiationManager = configurer.getContentNegotiationManager(); try {
this.contentNegotiationManager = configurer.getContentNegotiationManager();
}
catch (Exception e) {
throw new BeanInitializationException("Could not create ContentNegotiationManager", e);
}
} }
return this.contentNegotiationManager; return this.contentNegotiationManager;
} }
@ -398,9 +411,11 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
try { try {
String className = "org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"; String className = "org.springframework.validation.beanvalidation.LocalValidatorFactoryBean";
clazz = ClassUtils.forName(className, WebMvcConfigurationSupport.class.getClassLoader()); clazz = ClassUtils.forName(className, WebMvcConfigurationSupport.class.getClassLoader());
} catch (ClassNotFoundException e) { }
catch (ClassNotFoundException e) {
throw new BeanInitializationException("Could not find default validator", e); throw new BeanInitializationException("Could not find default validator", e);
} catch (LinkageError e) { }
catch (LinkageError e) {
throw new BeanInitializationException("Could not find default validator", e); throw new BeanInitializationException("Could not find default validator", e);
} }
validator = (Validator) BeanUtils.instantiate(clazz); validator = (Validator) BeanUtils.instantiate(clazz);

View File

@ -19,7 +19,6 @@ package org.springframework.web.servlet.view;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
@ -43,11 +42,7 @@ import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.springframework.web.HttpMediaTypeNotAcceptableException; import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.accept.ContentNegotiationManager; import org.springframework.web.accept.ContentNegotiationManager;
import org.springframework.web.accept.FixedContentNegotiationStrategy; import org.springframework.web.accept.ContentNegotiationManagerFactoryBean;
import org.springframework.web.accept.PathExtensionContentNegotiationStrategy;
import org.springframework.web.accept.HeaderContentNegotiationStrategy;
import org.springframework.web.accept.ParameterContentNegotiationStrategy;
import org.springframework.web.accept.ContentNegotiationStrategy;
import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes; import org.springframework.web.context.request.ServletRequestAttributes;
@ -99,13 +94,7 @@ public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport
private ContentNegotiationManager contentNegotiationManager; private ContentNegotiationManager contentNegotiationManager;
private boolean favorPathExtension = true; private ContentNegotiationManagerFactoryBean cnManagerFactoryBean = new ContentNegotiationManagerFactoryBean();
private boolean favorParameter = false;
private boolean ignoreAcceptHeader = false;
private Map<String, MediaType> mediaTypes = new HashMap<String, MediaType>();
private Boolean useJaf;
private String parameterName;
private MediaType defaultContentType;
private boolean useNotAcceptableStatusCode = false; private boolean useNotAcceptableStatusCode = false;
@ -144,7 +133,7 @@ public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport
* @deprecated use {@link #setContentNegotiationManager(ContentNegotiationManager)} * @deprecated use {@link #setContentNegotiationManager(ContentNegotiationManager)}
*/ */
public void setFavorPathExtension(boolean favorPathExtension) { public void setFavorPathExtension(boolean favorPathExtension) {
this.favorPathExtension = favorPathExtension; this.cnManagerFactoryBean.setFavorParameter(favorPathExtension);
} }
/** /**
@ -154,7 +143,7 @@ public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport
* @deprecated use {@link #setContentNegotiationManager(ContentNegotiationManager)} * @deprecated use {@link #setContentNegotiationManager(ContentNegotiationManager)}
*/ */
public void setUseJaf(boolean useJaf) { public void setUseJaf(boolean useJaf) {
this.useJaf = useJaf; this.cnManagerFactoryBean.setUseJaf(useJaf);
} }
/** /**
@ -167,7 +156,7 @@ public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport
* @deprecated use {@link #setContentNegotiationManager(ContentNegotiationManager)} * @deprecated use {@link #setContentNegotiationManager(ContentNegotiationManager)}
*/ */
public void setFavorParameter(boolean favorParameter) { public void setFavorParameter(boolean favorParameter) {
this.favorParameter = favorParameter; this.cnManagerFactoryBean.setFavorParameter(favorParameter);
} }
/** /**
@ -177,7 +166,7 @@ public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport
* @deprecated use {@link #setContentNegotiationManager(ContentNegotiationManager)} * @deprecated use {@link #setContentNegotiationManager(ContentNegotiationManager)}
*/ */
public void setParameterName(String parameterName) { public void setParameterName(String parameterName) {
this.parameterName = parameterName; this.cnManagerFactoryBean.setParameterName(parameterName);
} }
/** /**
@ -189,7 +178,7 @@ public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport
* @deprecated use {@link #setContentNegotiationManager(ContentNegotiationManager)} * @deprecated use {@link #setContentNegotiationManager(ContentNegotiationManager)}
*/ */
public void setIgnoreAcceptHeader(boolean ignoreAcceptHeader) { public void setIgnoreAcceptHeader(boolean ignoreAcceptHeader) {
this.ignoreAcceptHeader = ignoreAcceptHeader; this.cnManagerFactoryBean.setIgnoreAcceptHeader(ignoreAcceptHeader);
} }
/** /**
@ -201,11 +190,7 @@ public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport
*/ */
public void setMediaTypes(Map<String, String> mediaTypes) { public void setMediaTypes(Map<String, String> mediaTypes) {
if (mediaTypes != null) { if (mediaTypes != null) {
for (Map.Entry<String, String> entry : mediaTypes.entrySet()) { this.cnManagerFactoryBean.getMediaTypes().putAll(mediaTypes);
String extension = entry.getKey().toLowerCase(Locale.ENGLISH);
MediaType mediaType = MediaType.parseMediaType(entry.getValue());
this.mediaTypes.put(extension, mediaType);
}
} }
} }
@ -217,7 +202,7 @@ public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport
* @deprecated use {@link #setContentNegotiationManager(ContentNegotiationManager)} * @deprecated use {@link #setContentNegotiationManager(ContentNegotiationManager)}
*/ */
public void setDefaultContentType(MediaType defaultContentType) { public void setDefaultContentType(MediaType defaultContentType) {
this.defaultContentType = defaultContentType; this.cnManagerFactoryBean.setDefaultContentType(defaultContentType);
} }
/** /**
@ -277,31 +262,13 @@ public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport
"'viewResolvers' property on the ContentNegotiatingViewResolver"); "'viewResolvers' property on the ContentNegotiatingViewResolver");
} }
OrderComparator.sort(this.viewResolvers); OrderComparator.sort(this.viewResolvers);
this.cnManagerFactoryBean.setServletContext(servletContext);
} }
public void afterPropertiesSet() throws Exception { public void afterPropertiesSet() throws Exception {
if (this.contentNegotiationManager == null) { if (this.contentNegotiationManager == null) {
List<ContentNegotiationStrategy> strategies = new ArrayList<ContentNegotiationStrategy>(); this.cnManagerFactoryBean.afterPropertiesSet();
if (this.favorPathExtension) { this.contentNegotiationManager = this.cnManagerFactoryBean.getObject();
PathExtensionContentNegotiationStrategy strategy = new PathExtensionContentNegotiationStrategy(this.mediaTypes);
if (this.useJaf != null) {
strategy.setUseJaf(this.useJaf);
}
strategies.add(strategy);
}
if (this.favorParameter) {
ParameterContentNegotiationStrategy strategy = new ParameterContentNegotiationStrategy(this.mediaTypes);
strategy.setParameterName(this.parameterName);
strategies.add(strategy);
}
if (!this.ignoreAcceptHeader) {
strategies.add(new HeaderContentNegotiationStrategy());
}
if (this.defaultContentType != null) {
strategies.add(new FixedContentNegotiationStrategy(this.defaultContentType));
}
ContentNegotiationStrategy[] array = strategies.toArray(new ContentNegotiationStrategy[strategies.size()]);
this.contentNegotiationManager = new ContentNegotiationManager(array);
} }
} }

View File

@ -42,9 +42,9 @@ public class ContentNegotiationConfigurerTests {
@Before @Before
public void setup() { public void setup() {
this.configurer = new ContentNegotiationConfigurer();
this.servletRequest = new MockHttpServletRequest(); this.servletRequest = new MockHttpServletRequest();
this.webRequest = new ServletWebRequest(this.servletRequest); this.webRequest = new ServletWebRequest(this.servletRequest);
this.configurer = new ContentNegotiationConfigurer(this.servletRequest.getServletContext());
} }
@Test @Test
@ -71,7 +71,7 @@ public class ContentNegotiationConfigurerTests {
@Test @Test
public void addMediaTypes() throws Exception { public void addMediaTypes() throws Exception {
this.configurer.addMediaTypes(Collections.singletonMap("json", MediaType.APPLICATION_JSON)); this.configurer.mediaTypes(Collections.singletonMap("json", MediaType.APPLICATION_JSON));
ContentNegotiationManager manager = this.configurer.getContentNegotiationManager(); ContentNegotiationManager manager = this.configurer.getContentNegotiationManager();
this.servletRequest.setRequestURI("/flower.json"); this.servletRequest.setRequestURI("/flower.json");
@ -80,9 +80,9 @@ public class ContentNegotiationConfigurerTests {
@Test @Test
public void favorParameter() throws Exception { public void favorParameter() throws Exception {
this.configurer.setFavorParameter(true); this.configurer.favorParameter(true);
this.configurer.setParameterName("f"); this.configurer.parameterName("f");
this.configurer.addMediaTypes(Collections.singletonMap("json", MediaType.APPLICATION_JSON)); this.configurer.mediaTypes(Collections.singletonMap("json", MediaType.APPLICATION_JSON));
ContentNegotiationManager manager = this.configurer.getContentNegotiationManager(); ContentNegotiationManager manager = this.configurer.getContentNegotiationManager();
this.servletRequest.setRequestURI("/flower"); this.servletRequest.setRequestURI("/flower");
@ -93,7 +93,7 @@ public class ContentNegotiationConfigurerTests {
@Test @Test
public void ignoreAcceptHeader() throws Exception { public void ignoreAcceptHeader() throws Exception {
this.configurer.setIgnoreAcceptHeader(true); this.configurer.ignoreAcceptHeader(true);
ContentNegotiationManager manager = this.configurer.getContentNegotiationManager(); ContentNegotiationManager manager = this.configurer.getContentNegotiationManager();
this.servletRequest.setRequestURI("/flower"); this.servletRequest.setRequestURI("/flower");
@ -104,7 +104,7 @@ public class ContentNegotiationConfigurerTests {
@Test @Test
public void setDefaultContentType() throws Exception { public void setDefaultContentType() throws Exception {
this.configurer.setDefaultContentType(MediaType.APPLICATION_JSON); this.configurer.defaultContentType(MediaType.APPLICATION_JSON);
ContentNegotiationManager manager = this.configurer.getContentNegotiationManager(); ContentNegotiationManager manager = this.configurer.getContentNegotiationManager();
assertEquals(Arrays.asList(MediaType.APPLICATION_JSON), manager.resolveMediaTypes(this.webRequest)); assertEquals(Arrays.asList(MediaType.APPLICATION_JSON), manager.resolveMediaTypes(this.webRequest));

View File

@ -235,7 +235,7 @@ public class WebMvcConfigurationSupportExtensionTests {
@Override @Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) { public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.setFavorParameter(true).setParameterName("f"); configurer.favorParameter(true).parameterName("f");
} }
@Override @Override