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.messaging.converter;
018
019import java.util.ArrayList;
020import java.util.Collection;
021import java.util.Collections;
022import java.util.List;
023
024import org.apache.commons.logging.Log;
025import org.apache.commons.logging.LogFactory;
026
027import org.springframework.messaging.Message;
028import org.springframework.messaging.MessageHeaders;
029import org.springframework.messaging.support.MessageBuilder;
030import org.springframework.messaging.support.MessageHeaderAccessor;
031import org.springframework.util.Assert;
032import org.springframework.util.MimeType;
033
034/**
035 * Abstract base class for {@link SmartMessageConverter} implementations including
036 * support for common properties and a partial implementation of the conversion methods,
037 * mainly to check if the converter supports the conversion based on the payload class
038 * and MIME type.
039 *
040 * @author Rossen Stoyanchev
041 * @author Sebastien Deleuze
042 * @author Juergen Hoeller
043 * @since 4.0
044 */
045public abstract class AbstractMessageConverter implements SmartMessageConverter {
046
047        protected final Log logger = LogFactory.getLog(getClass());
048
049        private final List<MimeType> supportedMimeTypes;
050
051        private ContentTypeResolver contentTypeResolver = new DefaultContentTypeResolver();
052
053        private boolean strictContentTypeMatch = false;
054
055        private Class<?> serializedPayloadClass = byte[].class;
056
057
058        /**
059         * Construct an {@code AbstractMessageConverter} supporting a single MIME type.
060         * @param supportedMimeType the supported MIME type
061         */
062        protected AbstractMessageConverter(MimeType supportedMimeType) {
063                Assert.notNull(supportedMimeType, "supportedMimeType is required");
064                this.supportedMimeTypes = Collections.<MimeType>singletonList(supportedMimeType);
065        }
066
067        /**
068         * Construct an {@code AbstractMessageConverter} supporting multiple MIME types.
069         * @param supportedMimeTypes the supported MIME types
070         */
071        protected AbstractMessageConverter(Collection<MimeType> supportedMimeTypes) {
072                Assert.notNull(supportedMimeTypes, "supportedMimeTypes must not be null");
073                this.supportedMimeTypes = new ArrayList<MimeType>(supportedMimeTypes);
074        }
075
076
077        /**
078         * Return the supported MIME types.
079         */
080        public List<MimeType> getSupportedMimeTypes() {
081                return Collections.unmodifiableList(this.supportedMimeTypes);
082        }
083
084        /**
085         * Configure the {@link ContentTypeResolver} to use to resolve the content
086         * type of an input message.
087         * <p>Note that if no resolver is configured, then
088         * {@link #setStrictContentTypeMatch(boolean) strictContentTypeMatch} should
089         * be left as {@code false} (the default) or otherwise this converter will
090         * ignore all messages.
091         * <p>By default, a {@code DefaultContentTypeResolver} instance is used.
092         */
093        public void setContentTypeResolver(ContentTypeResolver resolver) {
094                this.contentTypeResolver = resolver;
095        }
096
097        /**
098         * Return the configured {@link ContentTypeResolver}.
099         */
100        public ContentTypeResolver getContentTypeResolver() {
101                return this.contentTypeResolver;
102        }
103
104        /**
105         * Whether this converter should convert messages for which no content type
106         * could be resolved through the configured
107         * {@link org.springframework.messaging.converter.ContentTypeResolver}.
108         * <p>A converter can configured to be strict only when a
109         * {@link #setContentTypeResolver contentTypeResolver} is configured and the
110         * list of {@link #getSupportedMimeTypes() supportedMimeTypes} is not be empty.
111         * <p>When this flag is set to {@code true}, {@link #supportsMimeType(MessageHeaders)}
112         * will return {@code false} if the {@link #setContentTypeResolver contentTypeResolver}
113         * is not defined or if no content-type header is present.
114         */
115        public void setStrictContentTypeMatch(boolean strictContentTypeMatch) {
116                if (strictContentTypeMatch) {
117                        Assert.notEmpty(getSupportedMimeTypes(), "Strict match requires non-empty list of supported mime types");
118                        Assert.notNull(getContentTypeResolver(), "Strict match requires ContentTypeResolver");
119                }
120                this.strictContentTypeMatch = strictContentTypeMatch;
121        }
122
123        /**
124         * Whether content type resolution must produce a value that matches one of
125         * the supported MIME types.
126         */
127        public boolean isStrictContentTypeMatch() {
128                return this.strictContentTypeMatch;
129        }
130
131        /**
132         * Configure the preferred serialization class to use (byte[] or String) when
133         * converting an Object payload to a {@link Message}.
134         * <p>The default value is byte[].
135         * @param payloadClass either byte[] or String
136         */
137        public void setSerializedPayloadClass(Class<?> payloadClass) {
138                if (!(byte[].class == payloadClass || String.class == payloadClass)) {
139                        throw new IllegalArgumentException("Payload class must be byte[] or String: " + payloadClass);
140                }
141                this.serializedPayloadClass = payloadClass;
142        }
143
144        /**
145         * Return the configured preferred serialization payload class.
146         */
147        public Class<?> getSerializedPayloadClass() {
148                return this.serializedPayloadClass;
149        }
150
151
152        @Override
153        public final Object fromMessage(Message<?> message, Class<?> targetClass) {
154                return fromMessage(message, targetClass, null);
155        }
156
157        @Override
158        public final Object fromMessage(Message<?> message, Class<?> targetClass, Object conversionHint) {
159                if (!canConvertFrom(message, targetClass)) {
160                        return null;
161                }
162                return convertFromInternal(message, targetClass, conversionHint);
163        }
164
165        @Override
166        public final Message<?> toMessage(Object payload, MessageHeaders headers) {
167                return toMessage(payload, headers, null);
168        }
169
170        @Override
171        public final Message<?> toMessage(Object payload, MessageHeaders headers, Object conversionHint) {
172                if (!canConvertTo(payload, headers)) {
173                        return null;
174                }
175
176                payload = convertToInternal(payload, headers, conversionHint);
177                if (payload == null) {
178                        return null;
179                }
180
181                MimeType mimeType = getDefaultContentType(payload);
182                if (headers != null) {
183                        MessageHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(headers, MessageHeaderAccessor.class);
184                        if (accessor != null && accessor.isMutable()) {
185                                accessor.setHeaderIfAbsent(MessageHeaders.CONTENT_TYPE, mimeType);
186                                return MessageBuilder.createMessage(payload, accessor.getMessageHeaders());
187                        }
188                }
189
190                MessageBuilder<?> builder = MessageBuilder.withPayload(payload);
191                if (headers != null) {
192                        builder.copyHeaders(headers);
193                }
194                builder.setHeaderIfAbsent(MessageHeaders.CONTENT_TYPE, mimeType);
195                return builder.build();
196        }
197
198
199        protected boolean canConvertFrom(Message<?> message, Class<?> targetClass) {
200                return (supports(targetClass) && supportsMimeType(message.getHeaders()));
201        }
202
203        protected boolean canConvertTo(Object payload, MessageHeaders headers) {
204                Class<?> clazz = (payload != null ? payload.getClass() : null);
205                return (supports(clazz) && supportsMimeType(headers));
206        }
207
208        protected boolean supportsMimeType(MessageHeaders headers) {
209                if (getSupportedMimeTypes().isEmpty()) {
210                        return true;
211                }
212                MimeType mimeType = getMimeType(headers);
213                if (mimeType == null) {
214                        return !isStrictContentTypeMatch();
215                }
216                for (MimeType current : getSupportedMimeTypes()) {
217                        if (current.getType().equals(mimeType.getType()) && current.getSubtype().equals(mimeType.getSubtype())) {
218                                return true;
219                        }
220                }
221                return false;
222        }
223
224        protected MimeType getMimeType(MessageHeaders headers) {
225                return (this.contentTypeResolver != null ? this.contentTypeResolver.resolve(headers) : null);
226        }
227
228        /**
229         * Return the default content type for the payload. Called when
230         * {@link #toMessage(Object, MessageHeaders)} is invoked without
231         * message headers or without a content type header.
232         * <p>By default, this returns the first element of the
233         * {@link #getSupportedMimeTypes() supportedMimeTypes}, if any.
234         * Can be overridden in subclasses.
235         * @param payload the payload being converted to a message
236         * @return the content type, or {@code null} if not known
237         */
238        protected MimeType getDefaultContentType(Object payload) {
239                List<MimeType> mimeTypes = getSupportedMimeTypes();
240                return (!mimeTypes.isEmpty() ? mimeTypes.get(0) : null);
241        }
242
243
244        /**
245         * Whether the given class is supported by this converter.
246         * @param clazz the class to test for support
247         * @return {@code true} if supported; {@code false} otherwise
248         */
249        protected abstract boolean supports(Class<?> clazz);
250
251        /**
252         * Convert the message payload from serialized form to an Object.
253         * @param message the input message
254         * @param targetClass the target class for the conversion
255         * @param conversionHint an extra object passed to the {@link MessageConverter},
256         * e.g. the associated {@code MethodParameter} (may be {@code null}}
257         * @return the result of the conversion, or {@code null} if the converter cannot
258         * perform the conversion
259         * @since 4.2
260         */
261        @SuppressWarnings("deprecation")
262        protected Object convertFromInternal(Message<?> message, Class<?> targetClass, Object conversionHint) {
263                return convertFromInternal(message, targetClass);
264        }
265
266        /**
267         * Convert the payload object to serialized form.
268         * @param payload the Object to convert
269         * @param headers optional headers for the message (may be {@code null})
270         * @param conversionHint an extra object passed to the {@link MessageConverter},
271         * e.g. the associated {@code MethodParameter} (may be {@code null}}
272         * @return the resulting payload for the message, or {@code null} if the converter
273         * cannot perform the conversion
274         * @since 4.2
275         */
276        @SuppressWarnings("deprecation")
277        protected Object convertToInternal(Object payload, MessageHeaders headers, Object conversionHint) {
278                return convertToInternal(payload, headers);
279        }
280
281        /**
282         * Convert the message payload from serialized form to an Object.
283         * @deprecated as of Spring 4.2, in favor of {@link #convertFromInternal(Message, Class, Object)}
284         * (which is also protected instead of public)
285         */
286        @Deprecated
287        public Object convertFromInternal(Message<?> message, Class<?> targetClass) {
288                return null;
289        }
290
291        /**
292         * Convert the payload object to serialized form.
293         * @deprecated as of Spring 4.2, in favor of {@link #convertFromInternal(Message, Class, Object)}
294         * (which is also protected instead of public)
295         */
296        @Deprecated
297        public Object convertToInternal(Object payload, MessageHeaders headers) {
298                return null;
299        }
300
301}