/* * Copyright 2012-2017 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.boot.autoconfigure.http; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; import org.springframework.http.converter.FormHttpMessageConverter; 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; /** * 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. * <p> * An instance of this bean can be registered with specific * {@link #HttpMessageConverters(HttpMessageConverter...) additional converters} if * needed, otherwise default converters will be used. * <p> * 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 * @author Andy Wilkinson * @see #HttpMessageConverters(HttpMessageConverter...) * @see #HttpMessageConverters(Collection) * @see #getConverters() */ public class HttpMessageConverters implements Iterable<HttpMessageConverter<?>> { private static final List<Class<?>> NON_REPLACING_CONVERTERS; static { List<Class<?>> nonReplacingConverters = new ArrayList<>(); addClassIfExists(nonReplacingConverters, "org.springframework.hateoas.mvc." + "TypeConstrainedMappingJackson2HttpMessageConverter"); NON_REPLACING_CONVERTERS = Collections.unmodifiableList(nonReplacingConverters); } private final List<HttpMessageConverter<?>> 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(HttpMessageConverter<?>... additionalConverters) { this(Arrays.asList(additionalConverters)); } /** * Create a new {@link HttpMessageConverters} instance with the specified additional * converters. * @param additionalConverters additional converters to be added. Items are added just * before any default converter of the same type (or at the front of the list if no * default converter is found) The {@link #postProcessConverters(List)} method can be * used for further converter manipulation. */ public HttpMessageConverters( Collection<HttpMessageConverter<?>> additionalConverters) { this(true, additionalConverters); } /** * Create a new {@link HttpMessageConverters} instance with the specified converters. * @param addDefaultConverters if default converters should be added * @param converters converters to be added. Items are added just before any default * converter of the same type (or at the front of the list if no default converter is * found) The {@link #postProcessConverters(List)} method can be used for further * converter manipulation. */ public HttpMessageConverters(boolean addDefaultConverters, Collection<HttpMessageConverter<?>> converters) { List<HttpMessageConverter<?>> combined = getCombinedConverters(converters, addDefaultConverters ? getDefaultConverters() : Collections.<HttpMessageConverter<?>>emptyList()); combined = postProcessConverters(combined); this.converters = Collections.unmodifiableList(combined); } private List<HttpMessageConverter<?>> getCombinedConverters( Collection<HttpMessageConverter<?>> converters, List<HttpMessageConverter<?>> defaultConverters) { List<HttpMessageConverter<?>> combined = new ArrayList<>(); List<HttpMessageConverter<?>> processing = new ArrayList<>(converters); for (HttpMessageConverter<?> defaultConverter : defaultConverters) { Iterator<HttpMessageConverter<?>> iterator = processing.iterator(); while (iterator.hasNext()) { HttpMessageConverter<?> candidate = iterator.next(); if (isReplacement(defaultConverter, candidate)) { combined.add(candidate); iterator.remove(); } } combined.add(defaultConverter); if (defaultConverter instanceof AllEncompassingFormHttpMessageConverter) { configurePartConverters( (AllEncompassingFormHttpMessageConverter) defaultConverter, converters); } } combined.addAll(0, processing); return combined; } private boolean isReplacement(HttpMessageConverter<?> defaultConverter, HttpMessageConverter<?> candidate) { for (Class<?> nonReplacingConverter : NON_REPLACING_CONVERTERS) { if (nonReplacingConverter.isInstance(candidate)) { return false; } } return ClassUtils.isAssignableValue(defaultConverter.getClass(), candidate); } private void configurePartConverters( AllEncompassingFormHttpMessageConverter formConverter, Collection<HttpMessageConverter<?>> converters) { List<HttpMessageConverter<?>> partConverters = extractPartConverters( formConverter); List<HttpMessageConverter<?>> combinedConverters = getCombinedConverters( converters, partConverters); combinedConverters = postProcessPartConverters(combinedConverters); formConverter.setPartConverters(combinedConverters); } @SuppressWarnings("unchecked") private List<HttpMessageConverter<?>> extractPartConverters( FormHttpMessageConverter formConverter) { Field field = ReflectionUtils.findField(FormHttpMessageConverter.class, "partConverters"); ReflectionUtils.makeAccessible(field); return (List<HttpMessageConverter<?>>) ReflectionUtils.getField(field, formConverter); } /** * Method that can be used to post-process the {@link HttpMessageConverter} list * before it is used. * @param converters a mutable list of the converters that will be used. * @return the final converts list to use */ protected List<HttpMessageConverter<?>> postProcessConverters( List<HttpMessageConverter<?>> converters) { 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<HttpMessageConverter<?>> postProcessPartConverters( List<HttpMessageConverter<?>> converters) { return converters; } private List<HttpMessageConverter<?>> getDefaultConverters() { List<HttpMessageConverter<?>> converters = new ArrayList<>(); if (ClassUtils.isPresent("org.springframework.web.servlet.config.annotation." + "WebMvcConfigurationSupport", null)) { converters.addAll(new WebMvcConfigurationSupport() { public List<HttpMessageConverter<?>> defaultMessageConverters() { return super.getMessageConverters(); } }.defaultMessageConverters()); } else { converters.addAll(new RestTemplate().getMessageConverters()); } reorderXmlConvertersToEnd(converters); return converters; } private void reorderXmlConvertersToEnd(List<HttpMessageConverter<?>> converters) { List<HttpMessageConverter<?>> xml = new ArrayList<>(); for (Iterator<HttpMessageConverter<?>> iterator = converters.iterator(); iterator .hasNext();) { HttpMessageConverter<?> converter = iterator.next(); if ((converter instanceof AbstractXmlHttpMessageConverter) || (converter instanceof MappingJackson2XmlHttpMessageConverter)) { xml.add(converter); iterator.remove(); } } converters.addAll(xml); } @Override public Iterator<HttpMessageConverter<?>> iterator() { return getConverters().iterator(); } /** * Return an immutable list of the converters in the order that they will be * registered. * @return the converters */ public List<HttpMessageConverter<?>> getConverters() { return this.converters; } private static void addClassIfExists(List<Class<?>> list, String className) { try { list.add(Class.forName(className)); } catch (ClassNotFoundException ex) { // Ignore } catch (NoClassDefFoundError ex) { // Ignore } } }