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