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