entry : mediaTypes.entrySet()) {
+ String extension = ((String) entry.getKey()).toLowerCase(Locale.ENGLISH);
+ this.mediaTypes.put(extension, MediaType.valueOf((String) entry.getValue()));
+ }
+ }
+ }
+
+ /**
+ * 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}.
+ * The default value is {@code true}.
+ * @see #parameterName
+ * @see #setMediaTypes(Map)
+ */
+ public void setUseJaf(boolean useJaf) {
+ this.useJaf = useJaf;
+ }
+
+ /**
+ * Indicate whether a request parameter should be used to determine the
+ * requested media type with the 2nd highest priority , i.e.
+ * after path extensions but before the {@code Accept} header.
+ *
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.
+ *
To use this option effectively you must also configure the MediaType
+ * type mappings via {@link #setMediaTypes(Map)}.
+ * @see #setParameterName(String)
+ */
+ public void setFavorParameter(boolean favorParameter) {
+ this.favorParameter = favorParameter;
+ }
+
+ /**
+ * Set the parameter name that can be used to determine the requested media type
+ * if the {@link #setFavorParameter} property is {@code true}.
+ *
The default parameter name is {@code "format"}.
+ */
+ public void setParameterName(String parameterName) {
+ this.parameterName = parameterName;
+ }
+
+ /**
+ * Indicate whether the HTTP {@code Accept} header should be ignored altogether.
+ * If set the {@code Accept} header is checked at the
+ * 3rd highest priority , i.e. after the request path extension and
+ * possibly a request parameter if configured.
+ *
By default this value is set to {@code false}.
+ */
+ public void setIgnoreAcceptHeader(boolean ignoreAcceptHeader) {
+ this.ignoreAcceptHeader = ignoreAcceptHeader;
+ }
+
+ /**
+ * Set the default content type.
+ *
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 void setDefaultContentType(MediaType defaultContentType) {
+ this.defaultContentType = defaultContentType;
+ }
+
+ public void afterPropertiesSet() throws Exception {
+ List strategies = new ArrayList();
+
+ 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()]);
+ this.contentNegotiationManager = new ContentNegotiationManager(array);
+ }
+
+ public Class> getObjectType() {
+ return ContentNegotiationManager.class;
+ }
+
+ public boolean isSingleton() {
+ return true;
+ }
+
+ public ContentNegotiationManager getObject() throws Exception {
+ return this.contentNegotiationManager;
+ }
+
+}
diff --git a/spring-web/src/test/java/org/springframework/web/accept/ContentNegotiationManagerFactoryBeanTests.java b/spring-web/src/test/java/org/springframework/web/accept/ContentNegotiationManagerFactoryBeanTests.java
new file mode 100644
index 0000000000..cbdb2c8ae7
--- /dev/null
+++ b/spring-web/src/test/java/org/springframework/web/accept/ContentNegotiationManagerFactoryBeanTests.java
@@ -0,0 +1,125 @@
+/*
+ * 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 static org.junit.Assert.assertEquals;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Properties;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.springframework.http.MediaType;
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.web.context.request.NativeWebRequest;
+import org.springframework.web.context.request.ServletWebRequest;
+
+/**
+ * Test fixture for {@link ContentNegotiationManagerFactoryBean} tests.
+ * @author Rossen Stoyanchev
+ */
+public class ContentNegotiationManagerFactoryBeanTests {
+
+ private ContentNegotiationManagerFactoryBean factoryBean;
+
+ private NativeWebRequest webRequest;
+
+ private MockHttpServletRequest servletRequest;
+
+ @Before
+ public void setup() {
+ this.factoryBean = new ContentNegotiationManagerFactoryBean();
+ this.servletRequest = new MockHttpServletRequest();
+ this.webRequest = new ServletWebRequest(this.servletRequest);
+ }
+
+ @Test
+ public void defaultSettings() throws Exception {
+ this.factoryBean.afterPropertiesSet();
+ ContentNegotiationManager manager = this.factoryBean.getObject();
+
+ 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 {
+ Properties mediaTypes = new Properties();
+ mediaTypes.put("json", MediaType.APPLICATION_JSON_VALUE);
+ this.factoryBean.setMediaTypes(mediaTypes);
+
+ this.factoryBean.afterPropertiesSet();
+ ContentNegotiationManager manager = this.factoryBean.getObject();
+
+ this.servletRequest.setRequestURI("/flower.json");
+ assertEquals(Arrays.asList(MediaType.APPLICATION_JSON), manager.resolveMediaTypes(this.webRequest));
+ }
+
+ @Test
+ public void favorParameter() throws Exception {
+ this.factoryBean.setFavorParameter(true);
+ this.factoryBean.setParameterName("f");
+
+ Properties mediaTypes = new Properties();
+ mediaTypes.put("json", MediaType.APPLICATION_JSON_VALUE);
+ this.factoryBean.setMediaTypes(mediaTypes);
+
+ this.factoryBean.afterPropertiesSet();
+ ContentNegotiationManager manager = this.factoryBean.getObject();
+
+ 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.factoryBean.setIgnoreAcceptHeader(true);
+ this.factoryBean.afterPropertiesSet();
+ ContentNegotiationManager manager = this.factoryBean.getObject();
+
+ 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.factoryBean.setDefaultContentType(MediaType.APPLICATION_JSON);
+ this.factoryBean.afterPropertiesSet();
+ ContentNegotiationManager manager = this.factoryBean.getObject();
+
+ assertEquals(Arrays.asList(MediaType.APPLICATION_JSON), manager.resolveMediaTypes(this.webRequest));
+ }
+
+}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/ContentNegotiationConfigurer.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/ContentNegotiationConfigurer.java
index 8468a365b3..2fb32c6bde 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/ContentNegotiationConfigurer.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/ContentNegotiationConfigurer.java
@@ -35,11 +35,10 @@ import org.springframework.web.accept.PathExtensionContentNegotiationStrategy;
/**
* Helps with configuring a {@link ContentNegotiationManager}.
*
- * 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).
+ *
By default strategies for checking the extension of the request path and
+ * the {@code Accept} header are registered. The path extension check will perform
+ * lookups through the {@link ServletContext} and the Java Activation Framework
+ * (if present) unless {@linkplain #setMediaTypes(Map) media types} are configured.
*
* @author Rossen Stoyanchev
* @since 3.2
@@ -72,6 +71,16 @@ public class ContentNegotiationConfigurer {
return this;
}
+ /**
+ * Add mappings from file extensions to media types.
+ *
If this property is not set, the Java Action Framework, if available, may
+ * still be used in conjunction with {@link #setFavorPathExtension(boolean)}.
+ */
+ public ContentNegotiationConfigurer addMediaType(String extension, MediaType mediaType) {
+ this.mediaTypes.put(extension, mediaType);
+ return this;
+ }
+
/**
* Add mappings from file extensions to media types.
*
If this property is not set, the Java Action Framework, if available, may
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java
index f5fa9469fe..bedc9ac477 100644
--- a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java
@@ -452,7 +452,7 @@ public class MvcNamespaceTests {
@Test
public void testCustomContentNegotiationManager() throws Exception {
- loadBeanDefinitions("mvc-config-content-negotiation-manager.xml", 14);
+ loadBeanDefinitions("mvc-config-content-negotiation-manager.xml", 12);
RequestMappingHandlerMapping mapping = appContext.getBean(RequestMappingHandlerMapping.class);
ContentNegotiationManager manager = mapping.getContentNegotiationManager();
diff --git a/spring-webmvc/src/test/resources/org/springframework/web/servlet/config/mvc-config-content-negotiation-manager.xml b/spring-webmvc/src/test/resources/org/springframework/web/servlet/config/mvc-config-content-negotiation-manager.xml
index 4fb4b48757..612999a52f 100644
--- a/spring-webmvc/src/test/resources/org/springframework/web/servlet/config/mvc-config-content-negotiation-manager.xml
+++ b/spring-webmvc/src/test/resources/org/springframework/web/servlet/config/mvc-config-content-negotiation-manager.xml
@@ -7,23 +7,12 @@
-
-
-
-
-
-
-
+
+
+
+ xml=application/rss+xml
+
+
-
-
-
-
-
-
-
-
-
-
diff --git a/src/reference/docbook/mvc.xml b/src/reference/docbook/mvc.xml
index d3d91a63d6..09cd747815 100644
--- a/src/reference/docbook/mvc.xml
+++ b/src/reference/docbook/mvc.xml
@@ -4503,25 +4503,20 @@ public class WebConfig extends WebMvcConfigurerAdapter {
}
}
- In XML you'll need to use the content-negotiation-manager property:
+ In XML you'll need to use the content-negotiation-manager
+ property of annotation-driven:
<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 id="contentNegotiationManager" class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean">
+ <property name="favorPathExtension" value="false" />
+ <property name="favorParameter" value="true" />
+ <property name="mediaTypes" >
+ <value>
+ json=application/json
+ xml=application/xml
+ </value>
+ </property>
</bean>