001/*
002 * Copyright 2002-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 *      https://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.http.converter;
018
019import java.io.IOException;
020import java.io.OutputStream;
021import java.nio.charset.Charset;
022import java.util.ArrayList;
023import java.util.Arrays;
024import java.util.Collections;
025import java.util.List;
026
027import org.apache.commons.logging.Log;
028import org.apache.commons.logging.LogFactory;
029
030import org.springframework.http.HttpHeaders;
031import org.springframework.http.HttpInputMessage;
032import org.springframework.http.HttpOutputMessage;
033import org.springframework.http.MediaType;
034import org.springframework.http.StreamingHttpOutputMessage;
035import org.springframework.util.Assert;
036
037/**
038 * Abstract base class for most {@link HttpMessageConverter} implementations.
039 *
040 * <p>This base class adds support for setting supported {@code MediaTypes}, through the
041 * {@link #setSupportedMediaTypes(List) supportedMediaTypes} bean property. It also adds
042 * support for {@code Content-Type} and {@code Content-Length} when writing to output messages.
043 *
044 * @author Arjen Poutsma
045 * @author Juergen Hoeller
046 * @author Sebastien Deleuze
047 * @since 3.0
048 */
049public abstract class AbstractHttpMessageConverter<T> implements HttpMessageConverter<T> {
050
051        /** Logger available to subclasses */
052        protected final Log logger = LogFactory.getLog(getClass());
053
054        private List<MediaType> supportedMediaTypes = Collections.emptyList();
055
056        private Charset defaultCharset;
057
058
059        /**
060         * Construct an {@code AbstractHttpMessageConverter} with no supported media types.
061         * @see #setSupportedMediaTypes
062         */
063        protected AbstractHttpMessageConverter() {
064        }
065
066        /**
067         * Construct an {@code AbstractHttpMessageConverter} with one supported media type.
068         * @param supportedMediaType the supported media type
069         */
070        protected AbstractHttpMessageConverter(MediaType supportedMediaType) {
071                setSupportedMediaTypes(Collections.singletonList(supportedMediaType));
072        }
073
074        /**
075         * Construct an {@code AbstractHttpMessageConverter} with multiple supported media types.
076         * @param supportedMediaTypes the supported media types
077         */
078        protected AbstractHttpMessageConverter(MediaType... supportedMediaTypes) {
079                setSupportedMediaTypes(Arrays.asList(supportedMediaTypes));
080        }
081
082        /**
083         * Construct an {@code AbstractHttpMessageConverter} with a default charset and
084         * multiple supported media types.
085         * @param defaultCharset the default character set
086         * @param supportedMediaTypes the supported media types
087         * @since 4.3
088         */
089        protected AbstractHttpMessageConverter(Charset defaultCharset, MediaType... supportedMediaTypes) {
090                this.defaultCharset = defaultCharset;
091                setSupportedMediaTypes(Arrays.asList(supportedMediaTypes));
092        }
093
094
095        /**
096         * Set the list of {@link MediaType} objects supported by this converter.
097         */
098        public void setSupportedMediaTypes(List<MediaType> supportedMediaTypes) {
099                Assert.notEmpty(supportedMediaTypes, "MediaType List must not be empty");
100                this.supportedMediaTypes = new ArrayList<MediaType>(supportedMediaTypes);
101        }
102
103        @Override
104        public List<MediaType> getSupportedMediaTypes() {
105                return Collections.unmodifiableList(this.supportedMediaTypes);
106        }
107
108        /**
109         * Set the default character set, if any.
110         * @since 4.3
111         */
112        public void setDefaultCharset(Charset defaultCharset) {
113                this.defaultCharset = defaultCharset;
114        }
115
116        /**
117         * Return the default character set, if any.
118         * @since 4.3
119         */
120        public Charset getDefaultCharset() {
121                return this.defaultCharset;
122        }
123
124
125        /**
126         * This implementation checks if the given class is {@linkplain #supports(Class) supported},
127         * and if the {@linkplain #getSupportedMediaTypes() supported media types}
128         * {@linkplain MediaType#includes(MediaType) include} the given media type.
129         */
130        @Override
131        public boolean canRead(Class<?> clazz, MediaType mediaType) {
132                return supports(clazz) && canRead(mediaType);
133        }
134
135        /**
136         * Returns {@code true} if any of the {@linkplain #setSupportedMediaTypes(List)
137         * supported} media types {@link MediaType#includes(MediaType) include} the
138         * given media type.
139         * @param mediaType the media type to read, can be {@code null} if not specified.
140         * Typically the value of a {@code Content-Type} header.
141         * @return {@code true} if the supported media types include the media type,
142         * or if the media type is {@code null}
143         */
144        protected boolean canRead(MediaType mediaType) {
145                if (mediaType == null) {
146                        return true;
147                }
148                for (MediaType supportedMediaType : getSupportedMediaTypes()) {
149                        if (supportedMediaType.includes(mediaType)) {
150                                return true;
151                        }
152                }
153                return false;
154        }
155
156        /**
157         * This implementation checks if the given class is
158         * {@linkplain #supports(Class) supported}, and if the
159         * {@linkplain #getSupportedMediaTypes() supported} media types
160         * {@linkplain MediaType#includes(MediaType) include} the given media type.
161         */
162        @Override
163        public boolean canWrite(Class<?> clazz, MediaType mediaType) {
164                return supports(clazz) && canWrite(mediaType);
165        }
166
167        /**
168         * Returns {@code true} if the given media type includes any of the
169         * {@linkplain #setSupportedMediaTypes(List) supported media types}.
170         * @param mediaType the media type to write, can be {@code null} if not specified.
171         * Typically the value of an {@code Accept} header.
172         * @return {@code true} if the supported media types are compatible with the media type,
173         * or if the media type is {@code null}
174         */
175        protected boolean canWrite(MediaType mediaType) {
176                if (mediaType == null || MediaType.ALL.equals(mediaType)) {
177                        return true;
178                }
179                for (MediaType supportedMediaType : getSupportedMediaTypes()) {
180                        if (supportedMediaType.isCompatibleWith(mediaType)) {
181                                return true;
182                        }
183                }
184                return false;
185        }
186
187        /**
188         * This implementation simple delegates to {@link #readInternal(Class, HttpInputMessage)}.
189         * Future implementations might add some default behavior, however.
190         */
191        @Override
192        public final T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
193                        throws IOException, HttpMessageNotReadableException {
194
195                return readInternal(clazz, inputMessage);
196        }
197
198        /**
199         * This implementation sets the default headers by calling {@link #addDefaultHeaders},
200         * and then calls {@link #writeInternal}.
201         */
202        @Override
203        public final void write(final T t, MediaType contentType, HttpOutputMessage outputMessage)
204                        throws IOException, HttpMessageNotWritableException {
205
206                final HttpHeaders headers = outputMessage.getHeaders();
207                addDefaultHeaders(headers, t, contentType);
208
209                if (outputMessage instanceof StreamingHttpOutputMessage) {
210                        StreamingHttpOutputMessage streamingOutputMessage = (StreamingHttpOutputMessage) outputMessage;
211                        streamingOutputMessage.setBody(new StreamingHttpOutputMessage.Body() {
212                                @Override
213                                public void writeTo(final OutputStream outputStream) throws IOException {
214                                        writeInternal(t, new HttpOutputMessage() {
215                                                @Override
216                                                public OutputStream getBody() throws IOException {
217                                                        return outputStream;
218                                                }
219                                                @Override
220                                                public HttpHeaders getHeaders() {
221                                                        return headers;
222                                                }
223                                        });
224                                }
225                        });
226                }
227                else {
228                        writeInternal(t, outputMessage);
229                        outputMessage.getBody().flush();
230                }
231        }
232
233        /**
234         * Add default headers to the output message.
235         * <p>This implementation delegates to {@link #getDefaultContentType(Object)} if a
236         * content type was not provided, set if necessary the default character set, calls
237         * {@link #getContentLength}, and sets the corresponding headers.
238         * @since 4.2
239         */
240        protected void addDefaultHeaders(HttpHeaders headers, T t, MediaType contentType) throws IOException {
241                if (headers.getContentType() == null) {
242                        MediaType contentTypeToUse = contentType;
243                        if (contentType == null || contentType.isWildcardType() || contentType.isWildcardSubtype()) {
244                                contentTypeToUse = getDefaultContentType(t);
245                        }
246                        else if (MediaType.APPLICATION_OCTET_STREAM.equals(contentType)) {
247                                MediaType mediaType = getDefaultContentType(t);
248                                contentTypeToUse = (mediaType != null ? mediaType : contentTypeToUse);
249                        }
250                        if (contentTypeToUse != null) {
251                                if (contentTypeToUse.getCharset() == null) {
252                                        Charset defaultCharset = getDefaultCharset();
253                                        if (defaultCharset != null) {
254                                                contentTypeToUse = new MediaType(contentTypeToUse, defaultCharset);
255                                        }
256                                }
257                                headers.setContentType(contentTypeToUse);
258                        }
259                }
260                if (headers.getContentLength() < 0 && !headers.containsKey(HttpHeaders.TRANSFER_ENCODING)) {
261                        Long contentLength = getContentLength(t, headers.getContentType());
262                        if (contentLength != null) {
263                                headers.setContentLength(contentLength);
264                        }
265                }
266        }
267
268        /**
269         * Returns the default content type for the given type. Called when {@link #write}
270         * is invoked without a specified content type parameter.
271         * <p>By default, this returns the first element of the
272         * {@link #setSupportedMediaTypes(List) supportedMediaTypes} property, if any.
273         * Can be overridden in subclasses.
274         * @param t the type to return the content type for
275         * @return the content type, or {@code null} if not known
276         */
277        protected MediaType getDefaultContentType(T t) throws IOException {
278                List<MediaType> mediaTypes = getSupportedMediaTypes();
279                return (!mediaTypes.isEmpty() ? mediaTypes.get(0) : null);
280        }
281
282        /**
283         * Returns the content length for the given type.
284         * <p>By default, this returns {@code null}, meaning that the content length is unknown.
285         * Can be overridden in subclasses.
286         * @param t the type to return the content length for
287         * @return the content length, or {@code null} if not known
288         */
289        protected Long getContentLength(T t, MediaType contentType) throws IOException {
290                return null;
291        }
292
293
294        /**
295         * Indicates whether the given class is supported by this converter.
296         * @param clazz the class to test for support
297         * @return {@code true} if supported; {@code false} otherwise
298         */
299        protected abstract boolean supports(Class<?> clazz);
300
301        /**
302         * Abstract template method that reads the actual object. Invoked from {@link #read}.
303         * @param clazz the type of object to return
304         * @param inputMessage the HTTP input message to read from
305         * @return the converted object
306         * @throws IOException in case of I/O errors
307         * @throws HttpMessageNotReadableException in case of conversion errors
308         */
309        protected abstract T readInternal(Class<? extends T> clazz, HttpInputMessage inputMessage)
310                        throws IOException, HttpMessageNotReadableException;
311
312        /**
313         * Abstract template method that writes the actual body. Invoked from {@link #write}.
314         * @param t the object to write to the output message
315         * @param outputMessage the HTTP output message to write to
316         * @throws IOException in case of I/O errors
317         * @throws HttpMessageNotWritableException in case of conversion errors
318         */
319        protected abstract void writeInternal(T t, HttpOutputMessage outputMessage)
320                        throws IOException, HttpMessageNotWritableException;
321
322}