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.nio.charset.Charset; 021 022import org.springframework.core.convert.ConversionService; 023import org.springframework.http.HttpInputMessage; 024import org.springframework.http.HttpOutputMessage; 025import org.springframework.http.MediaType; 026import org.springframework.lang.Nullable; 027import org.springframework.util.Assert; 028 029/** 030 * An {@code HttpMessageConverter} that uses {@link StringHttpMessageConverter} 031 * for reading and writing content and a {@link ConversionService} for converting 032 * the String content to and from the target object type. 033 * 034 * <p>By default, this converter supports the media type {@code text/plain} only. 035 * This can be overridden through the {@link #setSupportedMediaTypes supportedMediaTypes} 036 * property. 037 * 038 * <p>A usage example: 039 * 040 * <pre class="code"> 041 * <bean class="org.springframework.http.converter.ObjectToStringHttpMessageConverter"> 042 * <constructor-arg> 043 * <bean class="org.springframework.context.support.ConversionServiceFactoryBean"/> 044 * </constructor-arg> 045 * </bean> 046 * </pre> 047 * 048 * @author <a href="mailto:dmitry.katsubo@gmail.com">Dmitry Katsubo</a> 049 * @author Rossen Stoyanchev 050 * @since 3.2 051 */ 052public class ObjectToStringHttpMessageConverter extends AbstractHttpMessageConverter<Object> { 053 054 private final ConversionService conversionService; 055 056 private final StringHttpMessageConverter stringHttpMessageConverter; 057 058 059 /** 060 * A constructor accepting a {@code ConversionService} to use to convert the 061 * (String) message body to/from the target class type. This constructor uses 062 * {@link StringHttpMessageConverter#DEFAULT_CHARSET} as the default charset. 063 * @param conversionService the conversion service 064 */ 065 public ObjectToStringHttpMessageConverter(ConversionService conversionService) { 066 this(conversionService, StringHttpMessageConverter.DEFAULT_CHARSET); 067 } 068 069 /** 070 * A constructor accepting a {@code ConversionService} as well as a default charset. 071 * @param conversionService the conversion service 072 * @param defaultCharset the default charset 073 */ 074 public ObjectToStringHttpMessageConverter(ConversionService conversionService, Charset defaultCharset) { 075 super(defaultCharset, MediaType.TEXT_PLAIN); 076 077 Assert.notNull(conversionService, "ConversionService is required"); 078 this.conversionService = conversionService; 079 this.stringHttpMessageConverter = new StringHttpMessageConverter(defaultCharset); 080 } 081 082 083 /** 084 * Delegates to {@link StringHttpMessageConverter#setWriteAcceptCharset(boolean)}. 085 */ 086 public void setWriteAcceptCharset(boolean writeAcceptCharset) { 087 this.stringHttpMessageConverter.setWriteAcceptCharset(writeAcceptCharset); 088 } 089 090 091 @Override 092 public boolean canRead(Class<?> clazz, @Nullable MediaType mediaType) { 093 return canRead(mediaType) && this.conversionService.canConvert(String.class, clazz); 094 } 095 096 @Override 097 public boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType) { 098 return canWrite(mediaType) && this.conversionService.canConvert(clazz, String.class); 099 } 100 101 @Override 102 protected boolean supports(Class<?> clazz) { 103 // should not be called, since we override canRead/Write 104 throw new UnsupportedOperationException(); 105 } 106 107 @Override 108 protected Object readInternal(Class<?> clazz, HttpInputMessage inputMessage) 109 throws IOException, HttpMessageNotReadableException { 110 111 String value = this.stringHttpMessageConverter.readInternal(String.class, inputMessage); 112 Object result = this.conversionService.convert(value, clazz); 113 if (result == null) { 114 throw new HttpMessageNotReadableException( 115 "Unexpected null conversion result for '" + value + "' to " + clazz, 116 inputMessage); 117 } 118 return result; 119 } 120 121 @Override 122 protected void writeInternal(Object obj, HttpOutputMessage outputMessage) throws IOException { 123 String value = this.conversionService.convert(obj, String.class); 124 if (value != null) { 125 this.stringHttpMessageConverter.writeInternal(value, outputMessage); 126 } 127 } 128 129 @Override 130 protected Long getContentLength(Object obj, @Nullable MediaType contentType) { 131 String value = this.conversionService.convert(obj, String.class); 132 if (value == null) { 133 return 0L; 134 } 135 return this.stringHttpMessageConverter.getContentLength(value, contentType); 136 } 137 138}