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 * &lt;bean class="org.springframework.http.converter.ObjectToStringHttpMessageConverter">
042 *   &lt;constructor-arg>
043 *     &lt;bean class="org.springframework.context.support.ConversionServiceFactoryBean"/>
044 *   &lt;/constructor-arg>
045 * &lt;/bean>
046 * </pre>
047 *
048 * @author <a href="mailto:[email protected]">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}