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}