diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcAutoConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcAutoConfiguration.java
index e009793f77..895f8f2a51 100644
--- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcAutoConfiguration.java
+++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcAutoConfiguration.java
@@ -96,7 +96,7 @@ public class EndpointWebMvcAutoConfiguration implements ApplicationContextAware,
public EndpointHandlerAdapter endpointHandlerAdapter(
final HttpMessageConverters messageConverters) {
EndpointHandlerAdapter adapter = new EndpointHandlerAdapter();
- adapter.setMessageConverters(messageConverters.getMessageConverters());
+ adapter.setMessageConverters(messageConverters.getConverters());
return adapter;
}
diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/HttpMessageConverters.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/HttpMessageConverters.java
index 275e8f0486..8aad3563a5 100644
--- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/HttpMessageConverters.java
+++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/HttpMessageConverters.java
@@ -17,95 +17,141 @@
package org.springframework.boot.autoconfigure.web;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
-import java.util.LinkedHashSet;
+import java.util.Iterator;
import java.util.List;
+import org.springframework.beans.factory.InitializingBean;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.xml.AbstractXmlHttpMessageConverter;
+import org.springframework.util.Assert;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
/**
- * Convenient utility for adding and merging additional {@link HttpMessageConverter} in an
- * application context. It also modifies the default converters a bit (putting XML
- * converters at the back of the list if they are present).
+ * Bean used to manage the {@link HttpMessageConverter}s used in a Spring Boot
+ * application. Provides a convenient way to add and merge additional
+ * {@link HttpMessageConverter}s to a web application.
+ *
+ * An instance of this bean can be registered with specific
+ * {@link #HttpMessageConverters(HttpMessageConverter...) additional converters} if
+ * needed, otherwise default converters will be used.
+ *
+ * NOTE: The default converters used are the same as standard Spring MVC (see
+ * {@link WebMvcConfigurationSupport#getMessageConverters} with some slight re-ordering to
+ * put XML converters at the back of the list.
*
* @author Dave Syer
+ * @author Phillip Webb
+ * @see #HttpMessageConverters(HttpMessageConverter...)
+ * @see #HttpMessageConverters(Collection)
+ * @see #getConverters()
+ * @see #setConverters(List)
*/
-public class HttpMessageConverters {
-
- private List> defaults;
-
- private List> overrides;
-
- private Object lock = new Object();
+public class HttpMessageConverters implements Iterable>,
+ InitializingBean {
private List> converters;
- public HttpMessageConverters() {
- this(Collections.> emptyList());
+ private boolean initialized;
+
+ /**
+ * Create a new {@link HttpMessageConverters} instance with the specified additional
+ * converters.
+ * @param additionalConverters additional converters to be added. New converters will
+ * be added to the front of the list, overrides will replace existing items without
+ * changing the order. The {@link #getConverters()} methods can be used for further
+ * converter manipulation.
+ */
+ public HttpMessageConverters(HttpMessageConverter>... additionalConverters) {
+ this(Arrays.asList(additionalConverters));
}
- public HttpMessageConverters(Collection> overrides) {
- this.overrides = new ArrayList>(overrides);
- }
-
- public List> getMessageConverters() {
- if (this.converters == null) {
- synchronized (this.lock) {
- if (this.converters == null) {
- getDefaultMessageConverters(); // ensure they are available
- Collection> fallbacks = new LinkedHashSet>();
- for (HttpMessageConverter> fallback : this.defaults) {
- boolean overridden = false;
- for (HttpMessageConverter> converter : this.overrides) {
- if (fallback.getClass()
- .isAssignableFrom(converter.getClass())) {
- if (!fallbacks.contains(converter)) {
- fallbacks.add(converter);
- overridden = true;
- }
- }
- }
- if (!overridden) {
- fallbacks.add(fallback);
- }
- }
- Collection> converters = new LinkedHashSet>(
- this.overrides);
- converters.addAll(fallbacks);
- this.converters = new ArrayList>(converters);
- }
+ /**
+ * Create a new {@link HttpMessageConverters} instance with the specified additional
+ * converters.
+ * @param additionalConverters additional converters to be added. New converters will
+ * be added to the front of the list, overrides will replace existing items without
+ * changing the order. The {@link #getConverters()} methods can be used for further
+ * converter manipulation.
+ */
+ public HttpMessageConverters(Collection> additionalConverters) {
+ this.converters = new ArrayList>();
+ List> defaultConverters = getDefaultConverters();
+ for (HttpMessageConverter> converter : additionalConverters) {
+ int defaultConverterIndex = indexOfItemClass(defaultConverters, converter);
+ if (defaultConverterIndex == -1) {
+ this.converters.add(converter);
+ }
+ else {
+ defaultConverters.set(defaultConverterIndex, converter);
}
}
+ this.converters.addAll(defaultConverters);
+ }
+
+ private List> getDefaultConverters() {
+ List> converters = new ArrayList>();
+ converters.addAll(new WebMvcConfigurationSupport() {
+ public List> defaultMessageConverters() {
+ return super.getMessageConverters();
+ }
+ }.defaultMessageConverters());
+ reorderXmlConvertersToEnd(converters);
+ return converters;
+ }
+
+ private void reorderXmlConvertersToEnd(List> converters) {
+ List> xml = new ArrayList>();
+ for (Iterator> iterator = converters.iterator(); iterator
+ .hasNext();) {
+ HttpMessageConverter> converter = iterator.next();
+ if (converter instanceof AbstractXmlHttpMessageConverter) {
+ xml.add(converter);
+ iterator.remove();
+ }
+ }
+ converters.addAll(xml);
+ }
+
+ private int indexOfItemClass(List list, E item) {
+ Class extends Object> itemClass = item.getClass();
+ for (int i = 0; i < list.size(); i++) {
+ if (list.get(i).getClass().isAssignableFrom(itemClass)) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ @Override
+ public Iterator> iterator() {
+ return getConverters().iterator();
+ }
+
+ /**
+ * Return a mutable list of the converters in the order that they will be registered.
+ * Values in the list cannot be modified once the bean has been initialized.
+ * @return the converters
+ */
+ public List> getConverters() {
return this.converters;
}
- public List> getDefaultMessageConverters() {
- if (this.defaults == null) {
- synchronized (this.lock) {
- if (this.defaults == null) {
- this.defaults = new ArrayList>();
- this.defaults.addAll(new WebMvcConfigurationSupport() {
- public List> defaultMessageConverters() {
- return super.getMessageConverters();
- }
- }.defaultMessageConverters());
- List> xmls = new ArrayList>();
- for (HttpMessageConverter> converter : this.defaults) {
- // Shift XML converters to the back of the list so they only get
- // used if nothing else works...
- if (converter instanceof AbstractXmlHttpMessageConverter) {
- xmls.add(converter);
- }
- }
- this.defaults.removeAll(xmls);
- this.defaults.addAll(xmls);
- }
- }
- }
- return Collections.unmodifiableList(this.defaults);
+ /**
+ * Set the converters to use, replacing any existing values. This method can only be
+ * called before the bean has been initialized.
+ * @param converters the converters to set
+ */
+ public void setConverters(List> converters) {
+ Assert.state(!this.initialized, "Unable to set converters once initialized");
+ this.converters = converters;
}
-}
\ No newline at end of file
+ @Override
+ public void afterPropertiesSet() throws Exception {
+ this.initialized = true;
+ this.converters = Collections.unmodifiableList(this.converters);
+ }
+}
diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/WebMvcAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/WebMvcAutoConfiguration.java
index 4f0b052d9d..5599fcb224 100644
--- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/WebMvcAutoConfiguration.java
+++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/WebMvcAutoConfiguration.java
@@ -133,7 +133,7 @@ public class WebMvcAutoConfiguration {
@Override
public void configureMessageConverters(List> converters) {
- converters.addAll(this.messageConverters.getMessageConverters());
+ converters.addAll(this.messageConverters.getConverters());
}
@Bean
diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/HttpMessageConvertersAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/HttpMessageConvertersAutoConfigurationTests.java
index 4ee137dbcd..46af4b2f95 100644
--- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/HttpMessageConvertersAutoConfigurationTests.java
+++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/HttpMessageConvertersAutoConfigurationTests.java
@@ -42,6 +42,8 @@ import static org.mockito.Matchers.argThat;
import static org.mockito.Mockito.verify;
/**
+ * Tests for {@link HttpMessageConvertersAutoConfiguration}.
+ *
* @author Dave Syer
*/
public class HttpMessageConvertersAutoConfigurationTests {
@@ -67,7 +69,7 @@ public class HttpMessageConvertersAutoConfigurationTests {
converter.getObjectMapper());
HttpMessageConverters converters = this.context
.getBean(HttpMessageConverters.class);
- assertTrue(converters.getMessageConverters().contains(converter));
+ assertTrue(converters.getConverters().contains(converter));
}
@Test
diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/HttpMessageConvertersTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/HttpMessageConvertersTests.java
index 2c760c630f..86472e49cf 100644
--- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/HttpMessageConvertersTests.java
+++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/HttpMessageConvertersTests.java
@@ -16,43 +16,96 @@
package org.springframework.boot.autoconfigure.web;
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.List;
+import org.junit.Rule;
import org.junit.Test;
-import org.mockito.Mockito;
+import org.junit.rules.ExpectedException;
+import org.springframework.http.converter.ByteArrayHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter;
+import org.springframework.http.converter.ResourceHttpMessageConverter;
+import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
+import org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter;
+import org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter;
+import org.springframework.http.converter.xml.SourceHttpMessageConverter;
+import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
/**
* @author Dave Syer
+ * @author Phillip Webb
*/
public class HttpMessageConvertersTests {
+ @Rule
+ public ExpectedException thrown = ExpectedException.none();
+
@Test
- public void defaultsCreated() {
- HttpMessageConverters messageConverters = new HttpMessageConverters();
- assertFalse(messageConverters.getDefaultMessageConverters().isEmpty());
+ public void containsDefaults() throws Exception {
+ HttpMessageConverters converters = new HttpMessageConverters();
+ List> converterClasses = new ArrayList>();
+ for (HttpMessageConverter> converter : converters) {
+ converterClasses.add(converter.getClass());
+ }
+ assertThat(converterClasses, equalTo(Arrays.> asList(
+ ByteArrayHttpMessageConverter.class, StringHttpMessageConverter.class,
+ ResourceHttpMessageConverter.class, SourceHttpMessageConverter.class,
+ AllEncompassingFormHttpMessageConverter.class,
+ MappingJackson2HttpMessageConverter.class,
+ Jaxb2RootElementHttpMessageConverter.class)));
+ }
+
+ @Test
+ public void canModifyBeforeInitialize() throws Exception {
+ HttpMessageConverters converters = new HttpMessageConverters();
+ HttpMessageConverter> converter = mock(HttpMessageConverter.class);
+ converters.getConverters().add(converter);
+ assertThat(converters.getConverters().contains(converter), equalTo(true));
+ }
+
+ @Test
+ public void cannotModifyAfterInitialize() throws Exception {
+ HttpMessageConverters converters = new HttpMessageConverters();
+ converters.afterPropertiesSet();
+ this.thrown.expect(UnsupportedOperationException.class);
+ converters.getConverters().add(mock(HttpMessageConverter.class));
+ }
+
+ @Test
+ public void canSetBeforeInitialize() throws Exception {
+ HttpMessageConverters converters = new HttpMessageConverters();
+ converters.setConverters(new ArrayList>());
+ assertThat(converters.getConverters().size(), equalTo(0));
+ }
+
+ @Test
+ public void cannotSetAfterInitailzie() throws Exception {
+ HttpMessageConverters converters = new HttpMessageConverters();
+ converters.afterPropertiesSet();
+ this.thrown.expect(IllegalStateException.class);
+ converters.setConverters(new ArrayList>());
}
@Test
public void overrideExistingConverter() {
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
- HttpMessageConverters messageConverters = new HttpMessageConverters(
- Arrays.> asList(converter));
- assertTrue(messageConverters.getMessageConverters().contains(converter));
+ HttpMessageConverters converters = new HttpMessageConverters(converter);
+ assertTrue(converters.getConverters().contains(converter));
}
@Test
- public void addNewOne() {
- HttpMessageConverter> converter = Mockito.mock(HttpMessageConverter.class);
- HttpMessageConverters messageConverters = new HttpMessageConverters(
- Arrays.> asList(converter));
- assertTrue(messageConverters.getMessageConverters().contains(converter));
- assertEquals(converter, messageConverters.getMessageConverters().get(0));
+ public void addNewConverter() {
+ HttpMessageConverter> converter = mock(HttpMessageConverter.class);
+ HttpMessageConverters converters = new HttpMessageConverters(converter);
+ assertTrue(converters.getConverters().contains(converter));
+ assertEquals(converter, converters.getConverters().get(0));
}
}
diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/WebMvcAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/WebMvcAutoConfigurationTests.java
index f3b33a073a..1a57aeb621 100644
--- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/WebMvcAutoConfigurationTests.java
+++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/WebMvcAutoConfigurationTests.java
@@ -83,8 +83,7 @@ public class WebMvcAutoConfigurationTests {
assertEquals(3, this.context.getBeanNamesForType(HandlerAdapter.class).length);
assertFalse(this.context.getBean(RequestMappingHandlerAdapter.class)
.getMessageConverters().isEmpty());
- assertEquals(this.context.getBean(HttpMessageConverters.class)
- .getMessageConverters(),
+ assertEquals(this.context.getBean(HttpMessageConverters.class).getConverters(),
this.context.getBean(RequestMappingHandlerAdapter.class)
.getMessageConverters());
}