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}