Add options to configure content negotiation
The MVC Java config and the MVC namespace now support options to configure content negotiation. By default both support checking path extensions first and the "Accept" header second. For path extensions .json, .xml, .atom, and .rss are recognized out of the box if the Jackson, JAXB2, or Rome libraries are available. The ServletContext and the Java Activation Framework may be used as fallback options for path extension lookups. Issue: SPR-8420
This commit is contained in:
parent
92759ed1f8
commit
028e15faa3
|
@ -30,10 +30,19 @@ 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.
|
||||
* of a request by delegating to a list of ContentNegotiationStrategy instances.
|
||||
* The strategies must be provided at instantiation or alternatively if using
|
||||
* the default constructor, an instance of {@link HeaderContentNegotiationStrategy}
|
||||
* will be configured by default.
|
||||
*
|
||||
* <p>It may also be used to determine the extensions associated with a MediaType by
|
||||
* delegating to a list of {@link MediaTypeFileExtensionResolver} instances.
|
||||
* <p>This class may also be used to look up file extensions associated with a
|
||||
* MediaType. This is done by consulting the list of configured
|
||||
* {@link MediaTypeFileExtensionResolver} instances. Note that some
|
||||
* ContentNegotiationStrategy implementations also implement
|
||||
* MediaTypeFileExtensionResolver and the class constructor accepting the former
|
||||
* will also detect if they implement the latter. If you need to register additional
|
||||
* resolvers, you can use the method
|
||||
* {@link #addFileExtensionResolvers(MediaTypeFileExtensionResolver...)}.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 3.2
|
||||
|
@ -50,6 +59,7 @@ public class ContentNegotiationManager implements ContentNegotiationStrategy, Me
|
|||
* Create an instance with the given ContentNegotiationStrategy instances.
|
||||
* <p>Each instance is checked to see if it is also an implementation of
|
||||
* MediaTypeFileExtensionResolver, and if so it is registered as such.
|
||||
* @param strategies one more more ContentNegotiationStrategy instances
|
||||
*/
|
||||
public ContentNegotiationManager(ContentNegotiationStrategy... strategies) {
|
||||
Assert.notEmpty(strategies, "At least one ContentNegotiationStrategy is expected");
|
||||
|
@ -70,6 +80,11 @@ public class ContentNegotiationManager implements ContentNegotiationStrategy, Me
|
|||
|
||||
/**
|
||||
* Add MediaTypeFileExtensionResolver instances.
|
||||
* <p>Note that some {@link ContentNegotiationStrategy} implementations also
|
||||
* implement {@link MediaTypeFileExtensionResolver} and the class constructor
|
||||
* accepting the former will also detect implementations of the latter. Therefore
|
||||
* you only need to use this method to register additional instances.
|
||||
* @param one more resolvers
|
||||
*/
|
||||
public void addFileExtensionResolvers(MediaTypeFileExtensionResolver... resolvers) {
|
||||
this.fileExtensionResolvers.addAll(Arrays.asList(resolvers));
|
||||
|
|
|
@ -16,7 +16,10 @@
|
|||
|
||||
package org.springframework.web.servlet.config;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.BeanDefinitionHolder;
|
||||
|
@ -29,6 +32,7 @@ import org.springframework.beans.factory.xml.BeanDefinitionParser;
|
|||
import org.springframework.beans.factory.xml.ParserContext;
|
||||
import org.springframework.format.support.DefaultFormattingConversionService;
|
||||
import org.springframework.format.support.FormattingConversionServiceFactoryBean;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.ByteArrayHttpMessageConverter;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.converter.ResourceHttpMessageConverter;
|
||||
|
@ -44,6 +48,9 @@ import org.springframework.util.ClassUtils;
|
|||
import org.springframework.util.xml.DomUtils;
|
||||
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
|
||||
import org.springframework.web.HttpRequestHandler;
|
||||
import org.springframework.web.accept.ContentNegotiationManager;
|
||||
import org.springframework.web.accept.HeaderContentNegotiationStrategy;
|
||||
import org.springframework.web.accept.PathExtensionContentNegotiationStrategy;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import org.springframework.web.bind.support.ConfigurableWebBindingInitializer;
|
||||
|
@ -102,9 +109,10 @@ import org.w3c.dom.Element;
|
|||
* </ul>
|
||||
*
|
||||
* <p>Both the {@link RequestMappingHandlerAdapter} and the
|
||||
* {@link ExceptionHandlerExceptionResolver} are configured with default
|
||||
* instances of the following kind, unless custom instances are provided:
|
||||
* {@link ExceptionHandlerExceptionResolver} are configured with instances of
|
||||
* the following by default:
|
||||
* <ul>
|
||||
* <li>A {@link ContentNegotiationManager}
|
||||
* <li>A {@link DefaultFormattingConversionService}
|
||||
* <li>A {@link LocalValidatorFactoryBean} if a JSR-303 implementation is
|
||||
* available on the classpath
|
||||
|
@ -143,11 +151,14 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
|
|||
CompositeComponentDefinition compDefinition = new CompositeComponentDefinition(element.getTagName(), source);
|
||||
parserContext.pushContainingComponent(compDefinition);
|
||||
|
||||
RootBeanDefinition methodMappingDef = new RootBeanDefinition(RequestMappingHandlerMapping.class);
|
||||
methodMappingDef.setSource(source);
|
||||
methodMappingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
|
||||
methodMappingDef.getPropertyValues().add("order", 0);
|
||||
String methodMappingName = parserContext.getReaderContext().registerWithGeneratedName(methodMappingDef);
|
||||
RuntimeBeanReference contentNegotiationManager = getContentNegotiationManager(element, source, parserContext);
|
||||
|
||||
RootBeanDefinition handlerMappingDef = new RootBeanDefinition(RequestMappingHandlerMapping.class);
|
||||
handlerMappingDef.setSource(source);
|
||||
handlerMappingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
|
||||
handlerMappingDef.getPropertyValues().add("order", 0);
|
||||
handlerMappingDef.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager);
|
||||
String methodMappingName = parserContext.getReaderContext().registerWithGeneratedName(handlerMappingDef);
|
||||
|
||||
RuntimeBeanReference conversionService = getConversionService(element, source, parserContext);
|
||||
RuntimeBeanReference validator = getValidator(element, source, parserContext);
|
||||
|
@ -164,22 +175,23 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
|
|||
ManagedList<?> argumentResolvers = getArgumentResolvers(element, source, parserContext);
|
||||
ManagedList<?> returnValueHandlers = getReturnValueHandlers(element, source, parserContext);
|
||||
|
||||
RootBeanDefinition methodAdapterDef = new RootBeanDefinition(RequestMappingHandlerAdapter.class);
|
||||
methodAdapterDef.setSource(source);
|
||||
methodAdapterDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
|
||||
methodAdapterDef.getPropertyValues().add("webBindingInitializer", bindingDef);
|
||||
methodAdapterDef.getPropertyValues().add("messageConverters", messageConverters);
|
||||
RootBeanDefinition handlerAdapterDef = new RootBeanDefinition(RequestMappingHandlerAdapter.class);
|
||||
handlerAdapterDef.setSource(source);
|
||||
handlerAdapterDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
|
||||
handlerAdapterDef.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager);
|
||||
handlerAdapterDef.getPropertyValues().add("webBindingInitializer", bindingDef);
|
||||
handlerAdapterDef.getPropertyValues().add("messageConverters", messageConverters);
|
||||
if (element.hasAttribute("ignoreDefaultModelOnRedirect")) {
|
||||
Boolean ignoreDefaultModel = Boolean.valueOf(element.getAttribute("ignoreDefaultModelOnRedirect"));
|
||||
methodAdapterDef.getPropertyValues().add("ignoreDefaultModelOnRedirect", ignoreDefaultModel);
|
||||
handlerAdapterDef.getPropertyValues().add("ignoreDefaultModelOnRedirect", ignoreDefaultModel);
|
||||
}
|
||||
if (argumentResolvers != null) {
|
||||
methodAdapterDef.getPropertyValues().add("customArgumentResolvers", argumentResolvers);
|
||||
handlerAdapterDef.getPropertyValues().add("customArgumentResolvers", argumentResolvers);
|
||||
}
|
||||
if (returnValueHandlers != null) {
|
||||
methodAdapterDef.getPropertyValues().add("customReturnValueHandlers", returnValueHandlers);
|
||||
handlerAdapterDef.getPropertyValues().add("customReturnValueHandlers", returnValueHandlers);
|
||||
}
|
||||
String methodAdapterName = parserContext.getReaderContext().registerWithGeneratedName(methodAdapterDef);
|
||||
String handlerAdapterName = parserContext.getReaderContext().registerWithGeneratedName(handlerAdapterDef);
|
||||
|
||||
RootBeanDefinition csInterceptorDef = new RootBeanDefinition(ConversionServiceExposingInterceptor.class);
|
||||
csInterceptorDef.setSource(source);
|
||||
|
@ -191,13 +203,14 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
|
|||
mappedCsInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(1, csInterceptorDef);
|
||||
String mappedInterceptorName = parserContext.getReaderContext().registerWithGeneratedName(mappedCsInterceptorDef);
|
||||
|
||||
RootBeanDefinition methodExceptionResolver = new RootBeanDefinition(ExceptionHandlerExceptionResolver.class);
|
||||
methodExceptionResolver.setSource(source);
|
||||
methodExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
|
||||
methodExceptionResolver.getPropertyValues().add("messageConverters", messageConverters);
|
||||
methodExceptionResolver.getPropertyValues().add("order", 0);
|
||||
RootBeanDefinition exceptionHandlerExceptionResolver = new RootBeanDefinition(ExceptionHandlerExceptionResolver.class);
|
||||
exceptionHandlerExceptionResolver.setSource(source);
|
||||
exceptionHandlerExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
|
||||
exceptionHandlerExceptionResolver.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager);
|
||||
exceptionHandlerExceptionResolver.getPropertyValues().add("messageConverters", messageConverters);
|
||||
exceptionHandlerExceptionResolver.getPropertyValues().add("order", 0);
|
||||
String methodExceptionResolverName =
|
||||
parserContext.getReaderContext().registerWithGeneratedName(methodExceptionResolver);
|
||||
parserContext.getReaderContext().registerWithGeneratedName(exceptionHandlerExceptionResolver);
|
||||
|
||||
RootBeanDefinition responseStatusExceptionResolver = new RootBeanDefinition(ResponseStatusExceptionResolver.class);
|
||||
responseStatusExceptionResolver.setSource(source);
|
||||
|
@ -213,9 +226,9 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
|
|||
String defaultExceptionResolverName =
|
||||
parserContext.getReaderContext().registerWithGeneratedName(defaultExceptionResolver);
|
||||
|
||||
parserContext.registerComponent(new BeanComponentDefinition(methodMappingDef, methodMappingName));
|
||||
parserContext.registerComponent(new BeanComponentDefinition(methodAdapterDef, methodAdapterName));
|
||||
parserContext.registerComponent(new BeanComponentDefinition(methodExceptionResolver, methodExceptionResolverName));
|
||||
parserContext.registerComponent(new BeanComponentDefinition(handlerMappingDef, methodMappingName));
|
||||
parserContext.registerComponent(new BeanComponentDefinition(handlerAdapterDef, handlerAdapterName));
|
||||
parserContext.registerComponent(new BeanComponentDefinition(exceptionHandlerExceptionResolver, methodExceptionResolverName));
|
||||
parserContext.registerComponent(new BeanComponentDefinition(responseStatusExceptionResolver, responseStatusExceptionResolverName));
|
||||
parserContext.registerComponent(new BeanComponentDefinition(defaultExceptionResolver, defaultExceptionResolverName));
|
||||
parserContext.registerComponent(new BeanComponentDefinition(mappedCsInterceptorDef, mappedInterceptorName));
|
||||
|
@ -261,6 +274,42 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
|
|||
}
|
||||
}
|
||||
|
||||
private RuntimeBeanReference getContentNegotiationManager(Element element, Object source, ParserContext parserContext) {
|
||||
RuntimeBeanReference contentNegotiationManagerRef;
|
||||
if (element.hasAttribute("content-negotiation-manager")) {
|
||||
contentNegotiationManagerRef = new RuntimeBeanReference(element.getAttribute("content-negotiation-manager"));
|
||||
}
|
||||
else {
|
||||
RootBeanDefinition managerDef = new RootBeanDefinition(ContentNegotiationManager.class);
|
||||
managerDef.setSource(source);
|
||||
managerDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
|
||||
PathExtensionContentNegotiationStrategy strategy1 = new PathExtensionContentNegotiationStrategy(getDefaultMediaTypes());
|
||||
HeaderContentNegotiationStrategy strategy2 = new HeaderContentNegotiationStrategy();
|
||||
managerDef.getConstructorArgumentValues().addIndexedArgumentValue(0, Arrays.asList(strategy1,strategy2));
|
||||
|
||||
String beanName = "mvcContentNegotiationManager";
|
||||
parserContext.getReaderContext().getRegistry().registerBeanDefinition(beanName , managerDef);
|
||||
parserContext.registerComponent(new BeanComponentDefinition(managerDef, beanName));
|
||||
contentNegotiationManagerRef = new RuntimeBeanReference(beanName);
|
||||
}
|
||||
return contentNegotiationManagerRef;
|
||||
}
|
||||
|
||||
private Map<String, MediaType> getDefaultMediaTypes() {
|
||||
Map<String, MediaType> map = new HashMap<String, MediaType>();
|
||||
if (romePresent) {
|
||||
map.put("atom", MediaType.APPLICATION_ATOM_XML);
|
||||
map.put("rss", MediaType.valueOf("application/rss+xml"));
|
||||
}
|
||||
if (jackson2Present || jacksonPresent) {
|
||||
map.put("json", MediaType.APPLICATION_JSON);
|
||||
}
|
||||
if (jaxb2Present) {
|
||||
map.put("xml", MediaType.APPLICATION_XML);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
private RuntimeBeanReference getMessageCodesResolver(Element element, Object source, ParserContext parserContext) {
|
||||
if (element.hasAttribute("message-codes-resolver")) {
|
||||
return new RuntimeBeanReference(element.getAttribute("message-codes-resolver"));
|
||||
|
|
|
@ -0,0 +1,195 @@
|
|||
/*
|
||||
* 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.servlet.config.annotation;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.servlet.ServletContext;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.web.accept.ContentNegotiationManager;
|
||||
import org.springframework.web.accept.ContentNegotiationStrategy;
|
||||
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}.
|
||||
*
|
||||
* <p>By default the extension of the request path extension is checked first and
|
||||
* the {@code Accept} is checked second. The path extension check will perform a
|
||||
* look up in the media types configured via {@link #setMediaTypes(Map)} and
|
||||
* will also fall back to {@link ServletContext} and the Java Activation Framework
|
||||
* (if present).
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 3.2
|
||||
*/
|
||||
public class ContentNegotiationConfigurer {
|
||||
|
||||
private boolean favorPathExtension = true;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* Indicate whether the extension of the request path should be used to determine
|
||||
* the requested media type with the <em>highest priority</em>.
|
||||
* <p>By default this value is set to {@code true} in which case a request
|
||||
* for {@code /hotels.pdf} will be interpreted as a request for
|
||||
* {@code "application/pdf"} regardless of the {@code Accept} header.
|
||||
*/
|
||||
public ContentNegotiationConfigurer setFavorPathExtension(boolean favorPathExtension) {
|
||||
this.favorPathExtension = favorPathExtension;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add mappings from file extensions to media types.
|
||||
* <p>If this property is not set, the Java Action Framework, if available, may
|
||||
* still be used in conjunction with {@link #setFavorPathExtension(boolean)}.
|
||||
*/
|
||||
public ContentNegotiationConfigurer addMediaTypes(Map<String, MediaType> mediaTypes) {
|
||||
if (!CollectionUtils.isEmpty(mediaTypes)) {
|
||||
for (Map.Entry<String, MediaType> entry : mediaTypes.entrySet()) {
|
||||
String extension = entry.getKey().toLowerCase(Locale.ENGLISH);
|
||||
this.mediaTypes.put(extension, entry.getValue());
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* still be used in conjunction with {@link #setFavorPathExtension(boolean)}.
|
||||
*/
|
||||
public ContentNegotiationConfigurer replaceMediaTypes(Map<String, MediaType> mediaTypes) {
|
||||
this.mediaTypes.clear();
|
||||
if (!CollectionUtils.isEmpty(mediaTypes)) {
|
||||
for (Map.Entry<String, MediaType> entry : mediaTypes.entrySet()) {
|
||||
String extension = entry.getKey().toLowerCase(Locale.ENGLISH);
|
||||
this.mediaTypes.put(extension, entry.getValue());
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* {@link #setFavorPathExtension(boolean)} is set to {@code true}.
|
||||
* <p>The default value is {@code true}.
|
||||
* @see #parameterName
|
||||
* @see #setMediaTypes(Map)
|
||||
*/
|
||||
public ContentNegotiationConfigurer setUseJaf(boolean useJaf) {
|
||||
this.useJaf = useJaf;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate whether a request parameter should be used to determine the
|
||||
* requested media type with the <em>2nd highest priority</em>, i.e.
|
||||
* after path extensions but before the {@code Accept} header.
|
||||
* <p>The default value is {@code false}. If set to to {@code true}, a request
|
||||
* for {@code /hotels?format=pdf} will be interpreted as a request for
|
||||
* {@code "application/pdf"} regardless of the {@code Accept} header.
|
||||
* <p>To use this option effectively you must also configure the MediaType
|
||||
* type mappings via {@link #setMediaTypes(Map)}.
|
||||
* @see #setParameterName(String)
|
||||
*/
|
||||
public ContentNegotiationConfigurer setFavorParameter(boolean favorParameter) {
|
||||
this.favorParameter = favorParameter;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the parameter name that can be used to determine the requested media type
|
||||
* if the {@link #setFavorParameter} property is {@code true}.
|
||||
* <p>The default parameter name is {@code "format"}.
|
||||
*/
|
||||
public ContentNegotiationConfigurer setParameterName(String parameterName) {
|
||||
this.parameterName = parameterName;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate whether the HTTP {@code Accept} header should be ignored altogether.
|
||||
* If set the {@code Accept} header is checked at the
|
||||
* <em>3rd highest priority</em>, i.e. after the request path extension and
|
||||
* possibly a request parameter if configured.
|
||||
* <p>By default this value is set to {@code false}.
|
||||
*/
|
||||
public ContentNegotiationConfigurer setIgnoreAcceptHeader(boolean ignoreAcceptHeader) {
|
||||
this.ignoreAcceptHeader = ignoreAcceptHeader;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the default content type.
|
||||
* <p>This content type will be used when neither the request path extension,
|
||||
* nor a request parameter, nor the {@code Accept} header could help determine
|
||||
* the requested content type.
|
||||
*/
|
||||
public ContentNegotiationConfigurer setDefaultContentType(MediaType defaultContentType) {
|
||||
this.defaultContentType = defaultContentType;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the configured {@link ContentNegotiationManager} instance
|
||||
*/
|
||||
protected ContentNegotiationManager getContentNegotiationManager() {
|
||||
List<ContentNegotiationStrategy> strategies = new ArrayList<ContentNegotiationStrategy>();
|
||||
if (this.favorPathExtension) {
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
|
@ -55,6 +55,11 @@ public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
|
|||
this.configurers.addInterceptors(registry);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
|
||||
this.configurers.configureContentNegotiation(configurer);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addViewControllers(ViewControllerRegistry registry) {
|
||||
this.configurers.addViewControllers(registry);
|
||||
|
|
|
@ -24,8 +24,7 @@ import org.springframework.web.servlet.HandlerInterceptor;
|
|||
import org.springframework.web.servlet.handler.WebRequestHandlerInterceptorAdapter;
|
||||
|
||||
/**
|
||||
* Stores and provides access to a list of interceptors. For each interceptor you can optionally
|
||||
* specify one or more URL patterns it applies to.
|
||||
* Helps with configuring a list of mapped interceptors.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @author Keith Donald
|
||||
|
|
|
@ -17,7 +17,9 @@
|
|||
package org.springframework.web.servlet.config.annotation;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
@ -35,6 +37,7 @@ import org.springframework.format.Formatter;
|
|||
import org.springframework.format.FormatterRegistry;
|
||||
import org.springframework.format.support.DefaultFormattingConversionService;
|
||||
import org.springframework.format.support.FormattingConversionService;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.ByteArrayHttpMessageConverter;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.converter.ResourceHttpMessageConverter;
|
||||
|
@ -52,6 +55,7 @@ import org.springframework.validation.MessageCodesResolver;
|
|||
import org.springframework.validation.Validator;
|
||||
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
|
||||
import org.springframework.web.HttpRequestHandler;
|
||||
import org.springframework.web.accept.ContentNegotiationManager;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import org.springframework.web.bind.support.ConfigurableWebBindingInitializer;
|
||||
|
@ -120,8 +124,9 @@ import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolv
|
|||
*
|
||||
* <p>Both the {@link RequestMappingHandlerAdapter} and the
|
||||
* {@link ExceptionHandlerExceptionResolver} are configured with default
|
||||
* instances of the following kind, unless custom instances are provided:
|
||||
* instances of the following by default:
|
||||
* <ul>
|
||||
* <li>A {@link ContentNegotiationManager}
|
||||
* <li>A {@link DefaultFormattingConversionService}
|
||||
* <li>A {@link LocalValidatorFactoryBean} if a JSR-303 implementation is
|
||||
* available on the classpath
|
||||
|
@ -158,6 +163,8 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
|
|||
|
||||
private List<Object> interceptors;
|
||||
|
||||
private ContentNegotiationManager contentNegotiationManager;
|
||||
|
||||
private List<HttpMessageConverter<?>> messageConverters;
|
||||
|
||||
public void setServletContext(ServletContext servletContext) {
|
||||
|
@ -177,6 +184,7 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
|
|||
RequestMappingHandlerMapping handlerMapping = new RequestMappingHandlerMapping();
|
||||
handlerMapping.setOrder(0);
|
||||
handlerMapping.setInterceptors(getInterceptors());
|
||||
handlerMapping.setContentNegotiationManager(mvcContentNegotiationManager());
|
||||
return handlerMapping;
|
||||
}
|
||||
|
||||
|
@ -203,6 +211,43 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
|
|||
protected void addInterceptors(InterceptorRegistry registry) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a {@link ContentNegotiationManager} instance to use to determine
|
||||
* requested {@linkplain MediaType media types} in a given request.
|
||||
*/
|
||||
@Bean
|
||||
public ContentNegotiationManager mvcContentNegotiationManager() {
|
||||
if (this.contentNegotiationManager == null) {
|
||||
ContentNegotiationConfigurer configurer = new ContentNegotiationConfigurer();
|
||||
configurer.addMediaTypes(getDefaultMediaTypes());
|
||||
configureContentNegotiation(configurer);
|
||||
this.contentNegotiationManager = configurer.getContentNegotiationManager();
|
||||
}
|
||||
return this.contentNegotiationManager;
|
||||
}
|
||||
|
||||
protected Map<String, MediaType> getDefaultMediaTypes() {
|
||||
Map<String, MediaType> map = new HashMap<String, MediaType>();
|
||||
if (romePresent) {
|
||||
map.put("atom", MediaType.APPLICATION_ATOM_XML);
|
||||
map.put("rss", MediaType.valueOf("application/rss+xml"));
|
||||
}
|
||||
if (jackson2Present || jacksonPresent) {
|
||||
map.put("json", MediaType.APPLICATION_JSON);
|
||||
}
|
||||
if (jaxb2Present) {
|
||||
map.put("xml", MediaType.APPLICATION_XML);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override this method to configure content negotiation.
|
||||
* @see DefaultServletHandlerConfigurer
|
||||
*/
|
||||
protected void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a handler mapping ordered at 1 to map URL paths directly to
|
||||
* view names. To configure view controllers, override
|
||||
|
@ -304,6 +349,7 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
|
|||
addReturnValueHandlers(returnValueHandlers);
|
||||
|
||||
RequestMappingHandlerAdapter adapter = new RequestMappingHandlerAdapter();
|
||||
adapter.setContentNegotiationManager(mvcContentNegotiationManager());
|
||||
adapter.setMessageConverters(getMessageConverters());
|
||||
adapter.setWebBindingInitializer(webBindingInitializer);
|
||||
adapter.setCustomArgumentResolvers(argumentResolvers);
|
||||
|
@ -526,8 +572,7 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
|
|||
}
|
||||
|
||||
/**
|
||||
* A method available to subclasses for adding default
|
||||
* {@link HandlerExceptionResolver}s.
|
||||
* A method available to subclasses for adding default {@link HandlerExceptionResolver}s.
|
||||
* <p>Adds the following exception resolvers:
|
||||
* <ul>
|
||||
* <li>{@link ExceptionHandlerExceptionResolver}
|
||||
|
@ -540,6 +585,7 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
|
|||
*/
|
||||
protected final void addDefaultHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
|
||||
ExceptionHandlerExceptionResolver exceptionHandlerExceptionResolver = new ExceptionHandlerExceptionResolver();
|
||||
exceptionHandlerExceptionResolver.setContentNegotiationManager(mvcContentNegotiationManager());
|
||||
exceptionHandlerExceptionResolver.setMessageConverters(getMessageConverters());
|
||||
exceptionHandlerExceptionResolver.afterPropertiesSet();
|
||||
|
||||
|
|
|
@ -69,6 +69,11 @@ public interface WebMvcConfigurer {
|
|||
*/
|
||||
Validator getValidator();
|
||||
|
||||
/**
|
||||
* Configure content negotiation options.
|
||||
*/
|
||||
void configureContentNegotiation(ContentNegotiationConfigurer configurer);
|
||||
|
||||
/**
|
||||
* Add resolvers to support custom controller method argument types.
|
||||
* <p>This does not override the built-in support for resolving handler
|
||||
|
|
|
@ -57,6 +57,13 @@ public abstract class WebMvcConfigurerAdapter implements WebMvcConfigurer {
|
|||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* <p>This implementation is empty.
|
||||
*/
|
||||
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* <p>This implementation is empty.
|
||||
|
|
|
@ -49,6 +49,12 @@ class WebMvcConfigurerComposite implements WebMvcConfigurer {
|
|||
}
|
||||
}
|
||||
|
||||
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
|
||||
for (WebMvcConfigurer delegate : this.delegates) {
|
||||
delegate.configureContentNegotiation(configurer);
|
||||
}
|
||||
}
|
||||
|
||||
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
|
||||
for (WebMvcConfigurer delegate : this.delegates) {
|
||||
delegate.configureMessageConverters(converters);
|
||||
|
|
|
@ -120,6 +120,22 @@
|
|||
</xsd:appinfo>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="content-negotiation-manager" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation source="java:org.springframework.web.accept.ContentNegotiationManager"><![CDATA[
|
||||
The bean name of a ContentNegotiationManager that is to be used to determine requested media types. If not specified,
|
||||
a default ContentNegotiationManager is configured that checks the request path extension first and the "Accept" header
|
||||
second where path extensions such as ".json", ".xml", ".atom", and ".rss" are recognized if Jackson, JAXB2, or the
|
||||
Rome libraries are available. As a fallback option, the path extension is also used to perform a lookup through
|
||||
the ServletContext and the Java Activation Framework (if available).
|
||||
]]></xsd:documentation>
|
||||
<xsd:appinfo>
|
||||
<tool:annotation kind="ref">
|
||||
<tool:expected-type type="java:org.springframework.web.accept.ContentNegotiationManager" />
|
||||
</tool:annotation>
|
||||
</xsd:appinfo>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="message-codes-resolver" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
|
|
|
@ -19,6 +19,7 @@ package org.springframework.web.servlet.config;
|
|||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
@ -38,6 +39,7 @@ import org.springframework.core.io.ClassPathResource;
|
|||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
import org.springframework.format.annotation.DateTimeFormat.ISO;
|
||||
import org.springframework.format.support.FormattingConversionServiceFactoryBean;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
|
@ -49,8 +51,11 @@ import org.springframework.validation.Errors;
|
|||
import org.springframework.validation.Validator;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
|
||||
import org.springframework.web.accept.ContentNegotiationManager;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
import org.springframework.web.context.request.ServletWebRequest;
|
||||
import org.springframework.web.context.support.GenericWebApplicationContext;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
import org.springframework.web.method.support.InvocableHandlerMethod;
|
||||
|
@ -98,13 +103,18 @@ public class MvcNamespaceTests {
|
|||
|
||||
@Test
|
||||
public void testDefaultConfig() throws Exception {
|
||||
loadBeanDefinitions("mvc-config.xml", 11);
|
||||
loadBeanDefinitions("mvc-config.xml", 12);
|
||||
|
||||
RequestMappingHandlerMapping mapping = appContext.getBean(RequestMappingHandlerMapping.class);
|
||||
assertNotNull(mapping);
|
||||
assertEquals(0, mapping.getOrder());
|
||||
mapping.setDefaultHandler(handlerMethod);
|
||||
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo.json");
|
||||
NativeWebRequest webRequest = new ServletWebRequest(request);
|
||||
ContentNegotiationManager manager = mapping.getContentNegotiationManager();
|
||||
assertEquals(Arrays.asList(MediaType.APPLICATION_JSON), manager.resolveMediaTypes(webRequest));
|
||||
|
||||
RequestMappingHandlerAdapter adapter = appContext.getBean(RequestMappingHandlerAdapter.class);
|
||||
assertNotNull(adapter);
|
||||
assertEquals(false, new DirectFieldAccessor(adapter).getPropertyValue("ignoreDefaultModelOnRedirect"));
|
||||
|
@ -118,7 +128,7 @@ public class MvcNamespaceTests {
|
|||
assertNotNull(appContext.getBean(Validator.class));
|
||||
|
||||
// default web binding initializer behavior test
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/");
|
||||
request = new MockHttpServletRequest("GET", "/");
|
||||
request.addParameter("date", "2009-10-31");
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
|
||||
|
@ -135,7 +145,7 @@ public class MvcNamespaceTests {
|
|||
|
||||
@Test(expected=TypeMismatchException.class)
|
||||
public void testCustomConversionService() throws Exception {
|
||||
loadBeanDefinitions("mvc-config-custom-conversion-service.xml", 11);
|
||||
loadBeanDefinitions("mvc-config-custom-conversion-service.xml", 12);
|
||||
|
||||
RequestMappingHandlerMapping mapping = appContext.getBean(RequestMappingHandlerMapping.class);
|
||||
assertNotNull(mapping);
|
||||
|
@ -161,7 +171,7 @@ public class MvcNamespaceTests {
|
|||
|
||||
@Test
|
||||
public void testCustomValidator() throws Exception {
|
||||
loadBeanDefinitions("mvc-config-custom-validator.xml", 11);
|
||||
loadBeanDefinitions("mvc-config-custom-validator.xml", 12);
|
||||
|
||||
RequestMappingHandlerAdapter adapter = appContext.getBean(RequestMappingHandlerAdapter.class);
|
||||
assertNotNull(adapter);
|
||||
|
@ -179,7 +189,7 @@ public class MvcNamespaceTests {
|
|||
|
||||
@Test
|
||||
public void testInterceptors() throws Exception {
|
||||
loadBeanDefinitions("mvc-config-interceptors.xml", 16);
|
||||
loadBeanDefinitions("mvc-config-interceptors.xml", 17);
|
||||
|
||||
RequestMappingHandlerMapping mapping = appContext.getBean(RequestMappingHandlerMapping.class);
|
||||
assertNotNull(mapping);
|
||||
|
@ -308,7 +318,7 @@ public class MvcNamespaceTests {
|
|||
|
||||
@Test
|
||||
public void testBeanDecoration() throws Exception {
|
||||
loadBeanDefinitions("mvc-config-bean-decoration.xml", 13);
|
||||
loadBeanDefinitions("mvc-config-bean-decoration.xml", 14);
|
||||
|
||||
RequestMappingHandlerMapping mapping = appContext.getBean(RequestMappingHandlerMapping.class);
|
||||
assertNotNull(mapping);
|
||||
|
@ -329,7 +339,7 @@ public class MvcNamespaceTests {
|
|||
|
||||
@Test
|
||||
public void testViewControllers() throws Exception {
|
||||
loadBeanDefinitions("mvc-config-view-controllers.xml", 14);
|
||||
loadBeanDefinitions("mvc-config-view-controllers.xml", 15);
|
||||
|
||||
RequestMappingHandlerMapping mapping = appContext.getBean(RequestMappingHandlerMapping.class);
|
||||
assertNotNull(mapping);
|
||||
|
@ -389,7 +399,7 @@ public class MvcNamespaceTests {
|
|||
/** WebSphere gives trailing servlet path slashes by default!! */
|
||||
@Test
|
||||
public void testViewControllersOnWebSphere() throws Exception {
|
||||
loadBeanDefinitions("mvc-config-view-controllers.xml", 14);
|
||||
loadBeanDefinitions("mvc-config-view-controllers.xml", 15);
|
||||
|
||||
SimpleUrlHandlerMapping mapping2 = appContext.getBean(SimpleUrlHandlerMapping.class);
|
||||
SimpleControllerHandlerAdapter adapter = appContext.getBean(SimpleControllerHandlerAdapter.class);
|
||||
|
@ -440,6 +450,19 @@ public class MvcNamespaceTests {
|
|||
assertEquals(2, beanNameMapping.getOrder());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCustomContentNegotiationManager() throws Exception {
|
||||
loadBeanDefinitions("mvc-config-content-negotiation-manager.xml", 14);
|
||||
|
||||
RequestMappingHandlerMapping mapping = appContext.getBean(RequestMappingHandlerMapping.class);
|
||||
ContentNegotiationManager manager = mapping.getContentNegotiationManager();
|
||||
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo.xml");
|
||||
NativeWebRequest webRequest = new ServletWebRequest(request);
|
||||
assertEquals(Arrays.asList(MediaType.valueOf("application/rss+xml")), manager.resolveMediaTypes(webRequest));
|
||||
}
|
||||
|
||||
|
||||
private void loadBeanDefinitions(String fileName, int expectedBeanCount) {
|
||||
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(appContext);
|
||||
ClassPathResource resource = new ClassPathResource(fileName, AnnotationDrivenBeanDefinitionParserTests.class);
|
||||
|
@ -448,6 +471,7 @@ public class MvcNamespaceTests {
|
|||
appContext.refresh();
|
||||
}
|
||||
|
||||
|
||||
@Controller
|
||||
public static class TestController {
|
||||
|
||||
|
|
|
@ -0,0 +1,112 @@
|
|||
/*
|
||||
* 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.servlet.config.annotation;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.web.accept.ContentNegotiationManager;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
import org.springframework.web.context.request.ServletWebRequest;
|
||||
|
||||
/**
|
||||
* Test fixture for {@link ContentNegotiationConfigurer} tests.
|
||||
* @author Rossen Stoyanchev
|
||||
*/
|
||||
public class ContentNegotiationConfigurerTests {
|
||||
|
||||
private ContentNegotiationConfigurer configurer;
|
||||
|
||||
private NativeWebRequest webRequest;
|
||||
|
||||
private MockHttpServletRequest servletRequest;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
this.configurer = new ContentNegotiationConfigurer();
|
||||
this.servletRequest = new MockHttpServletRequest();
|
||||
this.webRequest = new ServletWebRequest(this.servletRequest);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void defaultSettings() throws Exception {
|
||||
ContentNegotiationManager manager = this.configurer.getContentNegotiationManager();
|
||||
|
||||
this.servletRequest.setRequestURI("/flower.gif");
|
||||
|
||||
assertEquals("Should be able to resolve file extensions by default",
|
||||
Arrays.asList(MediaType.IMAGE_GIF), manager.resolveMediaTypes(this.webRequest));
|
||||
|
||||
this.servletRequest.setRequestURI("/flower?format=gif");
|
||||
this.servletRequest.addParameter("format", "gif");
|
||||
|
||||
assertEquals("Should not resolve request parameters by default",
|
||||
Collections.emptyList(), manager.resolveMediaTypes(this.webRequest));
|
||||
|
||||
this.servletRequest.setRequestURI("/flower");
|
||||
this.servletRequest.addHeader("Accept", MediaType.IMAGE_GIF_VALUE);
|
||||
|
||||
assertEquals("Should resolve Accept header by default",
|
||||
Arrays.asList(MediaType.IMAGE_GIF), manager.resolveMediaTypes(this.webRequest));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addMediaTypes() throws Exception {
|
||||
this.configurer.addMediaTypes(Collections.singletonMap("json", MediaType.APPLICATION_JSON));
|
||||
ContentNegotiationManager manager = this.configurer.getContentNegotiationManager();
|
||||
|
||||
this.servletRequest.setRequestURI("/flower.json");
|
||||
assertEquals(Arrays.asList(MediaType.APPLICATION_JSON), manager.resolveMediaTypes(this.webRequest));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void favorParameter() throws Exception {
|
||||
this.configurer.setFavorParameter(true);
|
||||
this.configurer.setParameterName("f");
|
||||
this.configurer.addMediaTypes(Collections.singletonMap("json", MediaType.APPLICATION_JSON));
|
||||
ContentNegotiationManager manager = this.configurer.getContentNegotiationManager();
|
||||
|
||||
this.servletRequest.setRequestURI("/flower");
|
||||
this.servletRequest.addParameter("f", "json");
|
||||
|
||||
assertEquals(Arrays.asList(MediaType.APPLICATION_JSON), manager.resolveMediaTypes(this.webRequest));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ignoreAcceptHeader() throws Exception {
|
||||
this.configurer.setIgnoreAcceptHeader(true);
|
||||
ContentNegotiationManager manager = this.configurer.getContentNegotiationManager();
|
||||
|
||||
this.servletRequest.setRequestURI("/flower");
|
||||
this.servletRequest.addHeader("Accept", MediaType.IMAGE_GIF_VALUE);
|
||||
|
||||
assertEquals(Collections.emptyList(), manager.resolveMediaTypes(this.webRequest));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setDefaultContentType() throws Exception {
|
||||
this.configurer.setDefaultContentType(MediaType.APPLICATION_JSON);
|
||||
ContentNegotiationManager manager = this.configurer.getContentNegotiationManager();
|
||||
|
||||
assertEquals(Arrays.asList(MediaType.APPLICATION_JSON), manager.resolveMediaTypes(this.webRequest));
|
||||
}
|
||||
}
|
|
@ -67,11 +67,13 @@ public class DelegatingWebMvcConfigurationTests {
|
|||
@Test
|
||||
public void requestMappingHandlerAdapter() throws Exception {
|
||||
Capture<List<HttpMessageConverter<?>>> converters = new Capture<List<HttpMessageConverter<?>>>();
|
||||
Capture<ContentNegotiationConfigurer> contentNegotiationConfigurer = new Capture<ContentNegotiationConfigurer>();
|
||||
Capture<FormattingConversionService> conversionService = new Capture<FormattingConversionService>();
|
||||
Capture<List<HandlerMethodArgumentResolver>> resolvers = new Capture<List<HandlerMethodArgumentResolver>>();
|
||||
Capture<List<HandlerMethodReturnValueHandler>> handlers = new Capture<List<HandlerMethodReturnValueHandler>>();
|
||||
|
||||
configurer.configureMessageConverters(capture(converters));
|
||||
configurer.configureContentNegotiation(capture(contentNegotiationConfigurer));
|
||||
expect(configurer.getValidator()).andReturn(null);
|
||||
expect(configurer.getMessageCodesResolver()).andReturn(null);
|
||||
configurer.addFormatters(capture(conversionService));
|
||||
|
@ -135,8 +137,10 @@ public class DelegatingWebMvcConfigurationTests {
|
|||
public void handlerExceptionResolver() throws Exception {
|
||||
Capture<List<HttpMessageConverter<?>>> converters = new Capture<List<HttpMessageConverter<?>>>();
|
||||
Capture<List<HandlerExceptionResolver>> exceptionResolvers = new Capture<List<HandlerExceptionResolver>>();
|
||||
Capture<ContentNegotiationConfigurer> contentNegotiationConfigurer = new Capture<ContentNegotiationConfigurer>();
|
||||
|
||||
configurer.configureMessageConverters(capture(converters));
|
||||
configurer.configureContentNegotiation(capture(contentNegotiationConfigurer));
|
||||
configurer.configureHandlerExceptionResolvers(capture(exceptionResolvers));
|
||||
replay(configurer);
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ import static org.junit.Assert.assertNotNull;
|
|||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
@ -34,6 +35,7 @@ import org.springframework.core.convert.converter.Converter;
|
|||
import org.springframework.core.io.FileSystemResourceLoader;
|
||||
import org.springframework.format.FormatterRegistry;
|
||||
import org.springframework.format.support.FormattingConversionService;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
|
@ -45,8 +47,11 @@ import org.springframework.validation.Errors;
|
|||
import org.springframework.validation.MessageCodesResolver;
|
||||
import org.springframework.validation.Validator;
|
||||
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
|
||||
import org.springframework.web.accept.ContentNegotiationManager;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.support.ConfigurableWebBindingInitializer;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
import org.springframework.web.context.request.ServletWebRequest;
|
||||
import org.springframework.web.context.support.StaticWebApplicationContext;
|
||||
import org.springframework.web.method.annotation.ModelAttributeMethodProcessor;
|
||||
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
|
||||
|
@ -182,6 +187,24 @@ public class WebMvcConfigurationSupportTests {
|
|||
String actual = webConfig.mvcConversionService().convert(new TestBean(), String.class);
|
||||
assertEquals("converted", actual);
|
||||
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo.json");
|
||||
NativeWebRequest webRequest = new ServletWebRequest(request);
|
||||
ContentNegotiationManager manager = webConfig.requestMappingHandlerMapping().getContentNegotiationManager();
|
||||
assertEquals(Arrays.asList(MediaType.APPLICATION_JSON), manager.resolveMediaTypes(webRequest));
|
||||
|
||||
request.setRequestURI("/foo.xml");
|
||||
assertEquals(Arrays.asList(MediaType.APPLICATION_XML), manager.resolveMediaTypes(webRequest));
|
||||
|
||||
request.setRequestURI("/foo.rss");
|
||||
assertEquals(Arrays.asList(MediaType.valueOf("application/rss+xml")), manager.resolveMediaTypes(webRequest));
|
||||
|
||||
request.setRequestURI("/foo.atom");
|
||||
assertEquals(Arrays.asList(MediaType.APPLICATION_ATOM_XML), manager.resolveMediaTypes(webRequest));
|
||||
|
||||
request.setRequestURI("/foo");
|
||||
request.setParameter("f", "json");
|
||||
assertEquals(Arrays.asList(MediaType.APPLICATION_JSON), manager.resolveMediaTypes(webRequest));
|
||||
|
||||
RequestMappingHandlerAdapter adapter = webConfig.requestMappingHandlerAdapter();
|
||||
assertEquals(1, adapter.getMessageConverters().size());
|
||||
|
||||
|
@ -242,7 +265,6 @@ public class WebMvcConfigurationSupportTests {
|
|||
@Controller
|
||||
private static class TestController {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@RequestMapping("/")
|
||||
public void handle() {
|
||||
}
|
||||
|
@ -287,6 +309,11 @@ public class WebMvcConfigurationSupportTests {
|
|||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
|
||||
configurer.setFavorParameter(true).setParameterName("f");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
|
||||
argumentResolvers.add(new ModelAttributeMethodProcessor(true));
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:mvc="http://www.springframework.org/schema/mvc"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd
|
||||
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd">
|
||||
|
||||
<mvc:annotation-driven content-negotiation-manager="contentNegotiationManager" />
|
||||
|
||||
<bean id="contentNegotiationManager" class="org.springframework.web.accept.ContentNegotiationManager">
|
||||
<constructor-arg>
|
||||
<list>
|
||||
<ref bean="pathExtensionStrategy" />
|
||||
<ref bean="headerStrategy" />
|
||||
</list>
|
||||
</constructor-arg>
|
||||
</bean>
|
||||
|
||||
<bean id="pathExtensionStrategy" class="org.springframework.web.accept.PathExtensionContentNegotiationStrategy">
|
||||
<constructor-arg>
|
||||
<map>
|
||||
<entry key="xml" value="application/rss+xml" />
|
||||
</map>
|
||||
</constructor-arg>
|
||||
</bean>
|
||||
|
||||
<bean id="headerStrategy" class="org.springframework.web.accept.HeaderContentNegotiationStrategy" />
|
||||
|
||||
</beans>
|
|
@ -26,6 +26,7 @@ Changes in version 3.2 M2 (2012-08-xx)
|
|||
* use reflection to instantiate StandardServletAsyncWebRequest
|
||||
* fix issue with forward before async request processing
|
||||
* add exclude patterns for mapped interceptors in MVC namespace and MVC Java config
|
||||
* support content negotiation options in MVC namespace and MVC Java config
|
||||
|
||||
|
||||
Changes in version 3.2 M1 (2012-05-28)
|
||||
|
|
|
@ -4478,6 +4478,54 @@ public class WebConfig extends WebMvcConfigurerAdapter {
|
|||
|
||||
</section>
|
||||
|
||||
<section id="mvc-config-content-negotiation">
|
||||
<title>Configuring Content Negotiation</title>
|
||||
|
||||
<para>You can configure how Spring MVC determines requested media types for content negotiation purposes.
|
||||
The available strategies are to check the request path extension, a
|
||||
request parameter, the "Accept" header, and also to use a default content type.</para>
|
||||
|
||||
<para>By default the path extension is checked first and the "Accept"
|
||||
header is checked second. The path extension check recognizes ".json" if Jackson is available,
|
||||
".xml" if JAXB2 is available, and ".atom" and ".rss" if the Rome library is available.
|
||||
It also uses the <classname>ServletContext</classname> and the <emphasis>Java Activation Framework</emphasis>
|
||||
to perform lookups as a fallback option.</para>
|
||||
|
||||
<para>An example of customizing content negotiation in Java:</para>
|
||||
|
||||
<programlisting language="java">@EnableWebMvc
|
||||
@Configuration
|
||||
public class WebConfig extends WebMvcConfigurerAdapter {
|
||||
|
||||
@Override
|
||||
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
|
||||
configurer.setFavorPathExtension(false).setFavorParameter(true);
|
||||
}
|
||||
}</programlisting>
|
||||
|
||||
<para>In XML you'll need to use the <code>content-negotiation-manager</code> property:</para>
|
||||
|
||||
<programlisting language="xml"><mvc:annotation-driven content-negotiation-manager="contentNegotiationManager" />
|
||||
|
||||
<bean id="contentNegotiationManager" class="org.springframework.web.accept.ContentNegotiationManager">
|
||||
<constructor-arg>
|
||||
<list>
|
||||
<ref bean="pathExtensionStrategy" />
|
||||
<bean id="headerStrategy" class="org.springframework.web.accept.HeaderContentNegotiationStrategy"/>
|
||||
</list>
|
||||
</constructor-arg>
|
||||
</bean>
|
||||
|
||||
<bean id="pathExtensionStrategy" class="org.springframework.web.accept.PathExtensionContentNegotiationStrategy">
|
||||
<constructor-arg>
|
||||
<map>
|
||||
<entry key="xml" value="application/rss+xml" />
|
||||
</map>
|
||||
</constructor-arg>
|
||||
</bean></programlisting>
|
||||
|
||||
</section>
|
||||
|
||||
<section id="mvc-config-view-controller">
|
||||
<title>Configuring View Controllers</title>
|
||||
|
||||
|
|
Loading…
Reference in New Issue