001/* 002 * Copyright 2012-2018 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.boot.autoconfigure.http; 018 019import java.lang.reflect.Field; 020import java.util.ArrayList; 021import java.util.Arrays; 022import java.util.Collection; 023import java.util.Collections; 024import java.util.Iterator; 025import java.util.List; 026 027import org.springframework.http.converter.FormHttpMessageConverter; 028import org.springframework.http.converter.HttpMessageConverter; 029import org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter; 030import org.springframework.http.converter.xml.AbstractXmlHttpMessageConverter; 031import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter; 032import org.springframework.util.ClassUtils; 033import org.springframework.util.ReflectionUtils; 034import org.springframework.web.client.RestTemplate; 035import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport; 036 037/** 038 * Bean used to manage the {@link HttpMessageConverter}s used in a Spring Boot 039 * application. Provides a convenient way to add and merge additional 040 * {@link HttpMessageConverter}s to a web application. 041 * <p> 042 * An instance of this bean can be registered with specific 043 * {@link #HttpMessageConverters(HttpMessageConverter...) additional converters} if 044 * needed, otherwise default converters will be used. 045 * <p> 046 * NOTE: The default converters used are the same as standard Spring MVC (see 047 * {@link WebMvcConfigurationSupport#getMessageConverters} with some slight re-ordering to 048 * put XML converters at the back of the list. 049 * 050 * @author Dave Syer 051 * @author Phillip Webb 052 * @author Andy Wilkinson 053 * @see #HttpMessageConverters(HttpMessageConverter...) 054 * @see #HttpMessageConverters(Collection) 055 * @see #getConverters() 056 */ 057public class HttpMessageConverters implements Iterable<HttpMessageConverter<?>> { 058 059 private static final List<Class<?>> NON_REPLACING_CONVERTERS; 060 061 static { 062 List<Class<?>> nonReplacingConverters = new ArrayList<>(); 063 addClassIfExists(nonReplacingConverters, "org.springframework.hateoas.mvc." 064 + "TypeConstrainedMappingJackson2HttpMessageConverter"); 065 NON_REPLACING_CONVERTERS = Collections.unmodifiableList(nonReplacingConverters); 066 } 067 068 private final List<HttpMessageConverter<?>> converters; 069 070 /** 071 * Create a new {@link HttpMessageConverters} instance with the specified additional 072 * converters. 073 * @param additionalConverters additional converters to be added. Items are added just 074 * before any default converter of the same type (or at the front of the list if no 075 * default converter is found) The {@link #postProcessConverters(List)} method can be 076 * used for further converter manipulation. 077 */ 078 public HttpMessageConverters(HttpMessageConverter<?>... additionalConverters) { 079 this(Arrays.asList(additionalConverters)); 080 } 081 082 /** 083 * Create a new {@link HttpMessageConverters} instance with the specified additional 084 * converters. 085 * @param additionalConverters additional converters to be added. Items are added just 086 * before any default converter of the same type (or at the front of the list if no 087 * default converter is found) The {@link #postProcessConverters(List)} method can be 088 * used for further converter manipulation. 089 */ 090 public HttpMessageConverters( 091 Collection<HttpMessageConverter<?>> additionalConverters) { 092 this(true, additionalConverters); 093 } 094 095 /** 096 * Create a new {@link HttpMessageConverters} instance with the specified converters. 097 * @param addDefaultConverters if default converters should be added 098 * @param converters converters to be added. Items are added just before any default 099 * converter of the same type (or at the front of the list if no default converter is 100 * found) The {@link #postProcessConverters(List)} method can be used for further 101 * converter manipulation. 102 */ 103 public HttpMessageConverters(boolean addDefaultConverters, 104 Collection<HttpMessageConverter<?>> converters) { 105 List<HttpMessageConverter<?>> combined = getCombinedConverters(converters, 106 addDefaultConverters ? getDefaultConverters() : Collections.emptyList()); 107 combined = postProcessConverters(combined); 108 this.converters = Collections.unmodifiableList(combined); 109 } 110 111 private List<HttpMessageConverter<?>> getCombinedConverters( 112 Collection<HttpMessageConverter<?>> converters, 113 List<HttpMessageConverter<?>> defaultConverters) { 114 List<HttpMessageConverter<?>> combined = new ArrayList<>(); 115 List<HttpMessageConverter<?>> processing = new ArrayList<>(converters); 116 for (HttpMessageConverter<?> defaultConverter : defaultConverters) { 117 Iterator<HttpMessageConverter<?>> iterator = processing.iterator(); 118 while (iterator.hasNext()) { 119 HttpMessageConverter<?> candidate = iterator.next(); 120 if (isReplacement(defaultConverter, candidate)) { 121 combined.add(candidate); 122 iterator.remove(); 123 } 124 } 125 combined.add(defaultConverter); 126 if (defaultConverter instanceof AllEncompassingFormHttpMessageConverter) { 127 configurePartConverters( 128 (AllEncompassingFormHttpMessageConverter) defaultConverter, 129 converters); 130 } 131 } 132 combined.addAll(0, processing); 133 return combined; 134 } 135 136 private boolean isReplacement(HttpMessageConverter<?> defaultConverter, 137 HttpMessageConverter<?> candidate) { 138 for (Class<?> nonReplacingConverter : NON_REPLACING_CONVERTERS) { 139 if (nonReplacingConverter.isInstance(candidate)) { 140 return false; 141 } 142 } 143 return ClassUtils.isAssignableValue(defaultConverter.getClass(), candidate); 144 } 145 146 private void configurePartConverters( 147 AllEncompassingFormHttpMessageConverter formConverter, 148 Collection<HttpMessageConverter<?>> converters) { 149 List<HttpMessageConverter<?>> partConverters = extractPartConverters( 150 formConverter); 151 List<HttpMessageConverter<?>> combinedConverters = getCombinedConverters( 152 converters, partConverters); 153 combinedConverters = postProcessPartConverters(combinedConverters); 154 formConverter.setPartConverters(combinedConverters); 155 } 156 157 @SuppressWarnings("unchecked") 158 private List<HttpMessageConverter<?>> extractPartConverters( 159 FormHttpMessageConverter formConverter) { 160 Field field = ReflectionUtils.findField(FormHttpMessageConverter.class, 161 "partConverters"); 162 ReflectionUtils.makeAccessible(field); 163 return (List<HttpMessageConverter<?>>) ReflectionUtils.getField(field, 164 formConverter); 165 } 166 167 /** 168 * Method that can be used to post-process the {@link HttpMessageConverter} list 169 * before it is used. 170 * @param converters a mutable list of the converters that will be used. 171 * @return the final converts list to use 172 */ 173 protected List<HttpMessageConverter<?>> postProcessConverters( 174 List<HttpMessageConverter<?>> converters) { 175 return converters; 176 } 177 178 /** 179 * Method that can be used to post-process the {@link HttpMessageConverter} list 180 * before it is used to configure the part converters of 181 * {@link AllEncompassingFormHttpMessageConverter}. 182 * @param converters a mutable list of the converters that will be used. 183 * @return the final converts list to use 184 * @since 1.3.0 185 */ 186 protected List<HttpMessageConverter<?>> postProcessPartConverters( 187 List<HttpMessageConverter<?>> converters) { 188 return converters; 189 } 190 191 private List<HttpMessageConverter<?>> getDefaultConverters() { 192 List<HttpMessageConverter<?>> converters = new ArrayList<>(); 193 if (ClassUtils.isPresent("org.springframework.web.servlet.config.annotation." 194 + "WebMvcConfigurationSupport", null)) { 195 converters.addAll(new WebMvcConfigurationSupport() { 196 197 public List<HttpMessageConverter<?>> defaultMessageConverters() { 198 return super.getMessageConverters(); 199 } 200 201 }.defaultMessageConverters()); 202 } 203 else { 204 converters.addAll(new RestTemplate().getMessageConverters()); 205 } 206 reorderXmlConvertersToEnd(converters); 207 return converters; 208 } 209 210 private void reorderXmlConvertersToEnd(List<HttpMessageConverter<?>> converters) { 211 List<HttpMessageConverter<?>> xml = new ArrayList<>(); 212 for (Iterator<HttpMessageConverter<?>> iterator = converters.iterator(); iterator 213 .hasNext();) { 214 HttpMessageConverter<?> converter = iterator.next(); 215 if ((converter instanceof AbstractXmlHttpMessageConverter) 216 || (converter instanceof MappingJackson2XmlHttpMessageConverter)) { 217 xml.add(converter); 218 iterator.remove(); 219 } 220 } 221 converters.addAll(xml); 222 } 223 224 @Override 225 public Iterator<HttpMessageConverter<?>> iterator() { 226 return getConverters().iterator(); 227 } 228 229 /** 230 * Return an immutable list of the converters in the order that they will be 231 * registered. 232 * @return the converters 233 */ 234 public List<HttpMessageConverter<?>> getConverters() { 235 return this.converters; 236 } 237 238 private static void addClassIfExists(List<Class<?>> list, String className) { 239 try { 240 list.add(Class.forName(className)); 241 } 242 catch (ClassNotFoundException | NoClassDefFoundError ex) { 243 // Ignore 244 } 245 } 246 247}