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}