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 94d972add6..81d035b489 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2014 the original author or authors. + * Copyright 2012-2015 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. @@ -16,6 +16,7 @@ package org.springframework.boot.autoconfigure.web; +import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -24,9 +25,11 @@ import java.util.Iterator; import java.util.List; import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter; import org.springframework.http.converter.xml.AbstractXmlHttpMessageConverter; import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter; import org.springframework.util.ClassUtils; +import org.springframework.util.ReflectionUtils; import org.springframework.web.client.RestTemplate; import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport; @@ -45,6 +48,7 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupp * * @author Dave Syer * @author Phillip Webb + * @author Andy Wilkinson * @see #HttpMessageConverters(HttpMessageConverter...) * @see #HttpMessageConverters(Collection) * @see #getConverters() @@ -97,25 +101,37 @@ public class HttpMessageConverters implements Iterable> */ public HttpMessageConverters(boolean addDefaultConverters, Collection> converters) { + List> combined = getCombinedConverters(converters, + addDefaultConverters ? getDefaultConverters() + : Collections.>emptyList()); + combined = postProcessConverters(combined); + this.converters = Collections.unmodifiableList(combined); + } + + private List> getCombinedConverters( + Collection> converters, + List> defaultConverters) { List> combined = new ArrayList>(); List> processing = new ArrayList>( converters); - if (addDefaultConverters) { - for (HttpMessageConverter defaultConverter : getDefaultConverters()) { - Iterator> iterator = processing.iterator(); - while (iterator.hasNext()) { - HttpMessageConverter candidate = iterator.next(); - if (isReplacement(defaultConverter, candidate)) { - combined.add(candidate); - iterator.remove(); - } + for (HttpMessageConverter defaultConverter : defaultConverters) { + Iterator> iterator = processing.iterator(); + while (iterator.hasNext()) { + HttpMessageConverter candidate = iterator.next(); + if (isReplacement(defaultConverter, candidate)) { + combined.add(candidate); + iterator.remove(); } - combined.add(defaultConverter); + } + combined.add(defaultConverter); + if (defaultConverter instanceof AllEncompassingFormHttpMessageConverter) { + configurePartConverters( + (AllEncompassingFormHttpMessageConverter) defaultConverter, + converters); } } combined.addAll(0, processing); - combined = postProcessConverters(combined); - this.converters = Collections.unmodifiableList(combined); + return combined; } private boolean isReplacement(HttpMessageConverter defaultConverter, @@ -128,6 +144,28 @@ public class HttpMessageConverters implements Iterable> return ClassUtils.isAssignableValue(defaultConverter.getClass(), candidate); } + private void configurePartConverters( + AllEncompassingFormHttpMessageConverter formConverter, + Collection> converters) { + List> partConverters = extractPartConverters( + formConverter); + List> combinedConverters = getCombinedConverters( + converters, partConverters); + combinedConverters = postProcessPartConverters(combinedConverters); + formConverter.setPartConverters(combinedConverters); + } + + private List> extractPartConverters( + AllEncompassingFormHttpMessageConverter formConverter) { + Field field = ReflectionUtils.findField( + AllEncompassingFormHttpMessageConverter.class, "partConverters"); + ReflectionUtils.makeAccessible(field); + @SuppressWarnings("unchecked") + List> partConverters = (List>) ReflectionUtils + .getField(field, formConverter); + return partConverters; + } + /** * Method that can be used to post-process the {@link HttpMessageConverter} list * before it is used. @@ -139,6 +177,19 @@ public class HttpMessageConverters implements Iterable> return converters; } + /** + * Method that can be used to post-process the {@link HttpMessageConverter} list + * before it is used to configure the part converters of + * {@link AllEncompassingFormHttpMessageConverter}. + * @param converters a mutable list of the converters that will be used. + * @return the final converts list to use + * @since 1.3.0 + */ + protected List> postProcessPartConverters( + List> converters) { + return converters; + } + private List> getDefaultConverters() { List> converters = new ArrayList>(); if (ClassUtils.isPresent("org.springframework.web.servlet.config.annotation." 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 57c29fbb6a..e0a84676d7 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 @@ -18,6 +18,7 @@ package org.springframework.boot.autoconfigure.web; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Iterator; import java.util.List; @@ -32,6 +33,7 @@ import org.springframework.http.converter.json.MappingJackson2HttpMessageConvert import org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter; import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter; import org.springframework.http.converter.xml.SourceHttpMessageConverter; +import org.springframework.test.util.ReflectionTestUtils; import static org.hamcrest.Matchers.equalTo; import static org.junit.Assert.assertEquals; @@ -100,6 +102,19 @@ public class HttpMessageConvertersTests { assertEquals(converter2, converters.getConverters().get(1)); } + @Test + public void convertersAreAddedToFormPartConverter() { + HttpMessageConverter converter1 = mock(HttpMessageConverter.class); + HttpMessageConverter converter2 = mock(HttpMessageConverter.class); + List> converters = new HttpMessageConverters(converter1, + converter2).getConverters(); + List> partConverters = extractFormPartConverters( + converters); + assertTrue(partConverters.contains(converter1)); + assertEquals(converter1, partConverters.get(0)); + assertEquals(converter2, partConverters.get(1)); + } + @Test public void postProcessConverters() throws Exception { HttpMessageConverters converters = new HttpMessageConverters() { @@ -129,4 +144,52 @@ public class HttpMessageConvertersTests { MappingJackson2HttpMessageConverter.class))); } + @Test + public void postProcessPartConverters() throws Exception { + HttpMessageConverters converters = new HttpMessageConverters() { + @Override + protected List> postProcessPartConverters( + List> converters) { + for (Iterator> iterator = converters + .iterator(); iterator.hasNext();) { + if (iterator + .next() instanceof MappingJackson2XmlHttpMessageConverter) { + iterator.remove(); + } + } + return converters; + }; + }; + List> converterClasses = new ArrayList>(); + for (HttpMessageConverter converter : extractFormPartConverters( + converters.getConverters())) { + converterClasses.add(converter.getClass()); + } + assertThat(converterClasses, + equalTo(Arrays.>asList(ByteArrayHttpMessageConverter.class, + StringHttpMessageConverter.class, + ResourceHttpMessageConverter.class, + SourceHttpMessageConverter.class, + MappingJackson2HttpMessageConverter.class))); + } + + @SuppressWarnings("unchecked") + private List> extractFormPartConverters( + List> converters) { + AllEncompassingFormHttpMessageConverter formConverter = findFormConverter( + converters); + return (List>) ReflectionTestUtils.getField(formConverter, + "partConverters"); + } + + private AllEncompassingFormHttpMessageConverter findFormConverter( + Collection> converters) { + for (HttpMessageConverter converter : converters) { + if (converter instanceof AllEncompassingFormHttpMessageConverter) { + return (AllEncompassingFormHttpMessageConverter) converter; + } + } + return null; + } + }