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}