001/* 002 * Copyright 2012-2016 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.web; 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<Class<?>>(); 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. New converters will 074 * be added to the front of the list, overrides will replace existing items without 075 * changing the order. The {@link #getConverters()} methods can be used for further 076 * 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() 107 : Collections.<HttpMessageConverter<?>>emptyList()); 108 combined = postProcessConverters(combined); 109 this.converters = Collections.unmodifiableList(combined); 110 } 111 112 private List<HttpMessageConverter<?>> getCombinedConverters( 113 Collection<HttpMessageConverter<?>> converters, 114 List<HttpMessageConverter<?>> defaultConverters) { 115 List<HttpMessageConverter<?>> combined = new ArrayList<HttpMessageConverter<?>>(); 116 List<HttpMessageConverter<?>> processing = new ArrayList<HttpMessageConverter<?>>( 117 converters); 118 for (HttpMessageConverter<?> defaultConverter : defaultConverters) { 119 Iterator<HttpMessageConverter<?>> iterator = processing.iterator(); 120 while (iterator.hasNext()) { 121 HttpMessageConverter<?> candidate = iterator.next(); 122 if (isReplacement(defaultConverter, candidate)) { 123 combined.add(candidate); 124 iterator.remove(); 125 } 126 } 127 combined.add(defaultConverter); 128 if (defaultConverter instanceof AllEncompassingFormHttpMessageConverter) { 129 configurePartConverters( 130 (AllEncompassingFormHttpMessageConverter) defaultConverter, 131 converters); 132 } 133 } 134 combined.addAll(0, processing); 135 return combined; 136 } 137 138 private boolean isReplacement(HttpMessageConverter<?> defaultConverter, 139 HttpMessageConverter<?> candidate) { 140 for (Class<?> nonReplacingConverter : NON_REPLACING_CONVERTERS) { 141 if (nonReplacingConverter.isInstance(candidate)) { 142 return false; 143 } 144 } 145 return ClassUtils.isAssignableValue(defaultConverter.getClass(), candidate); 146 } 147 148 private void configurePartConverters( 149 AllEncompassingFormHttpMessageConverter formConverter, 150 Collection<HttpMessageConverter<?>> converters) { 151 List<HttpMessageConverter<?>> partConverters = extractPartConverters( 152 formConverter); 153 List<HttpMessageConverter<?>> combinedConverters = getCombinedConverters( 154 converters, partConverters); 155 combinedConverters = postProcessPartConverters(combinedConverters); 156 formConverter.setPartConverters(combinedConverters); 157 } 158 159 @SuppressWarnings("unchecked") 160 private List<HttpMessageConverter<?>> extractPartConverters( 161 FormHttpMessageConverter formConverter) { 162 Field field = ReflectionUtils.findField(FormHttpMessageConverter.class, 163 "partConverters"); 164 ReflectionUtils.makeAccessible(field); 165 return (List<HttpMessageConverter<?>>) ReflectionUtils.getField(field, 166 formConverter); 167 } 168 169 /** 170 * Method that can be used to post-process the {@link HttpMessageConverter} list 171 * before it is used. 172 * @param converters a mutable list of the converters that will be used. 173 * @return the final converts list to use 174 */ 175 protected List<HttpMessageConverter<?>> postProcessConverters( 176 List<HttpMessageConverter<?>> converters) { 177 return converters; 178 } 179 180 /** 181 * Method that can be used to post-process the {@link HttpMessageConverter} list 182 * before it is used to configure the part converters of 183 * {@link AllEncompassingFormHttpMessageConverter}. 184 * @param converters a mutable list of the converters that will be used. 185 * @return the final converts list to use 186 * @since 1.3.0 187 */ 188 protected List<HttpMessageConverter<?>> postProcessPartConverters( 189 List<HttpMessageConverter<?>> converters) { 190 return converters; 191 } 192 193 private List<HttpMessageConverter<?>> getDefaultConverters() { 194 List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>(); 195 if (ClassUtils.isPresent("org.springframework.web.servlet.config.annotation." 196 + "WebMvcConfigurationSupport", null)) { 197 converters.addAll(new WebMvcConfigurationSupport() { 198 public List<HttpMessageConverter<?>> defaultMessageConverters() { 199 return super.getMessageConverters(); 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<HttpMessageConverter<?>>(); 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 ex) { 243 // Ignore 244 } 245 catch (NoClassDefFoundError ex) { 246 // Ignore 247 } 248 } 249 250}