org.springframework.messaging.converter.AbstractMessageConverter Maven / Gradle / Ivy
/*
* Copyright 2002-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.messaging.converter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.messaging.support.MessageHeaderAccessor;
import org.springframework.util.Assert;
import org.springframework.util.MimeType;
/**
* Abstract base class for {@link SmartMessageConverter} implementations including
* support for common properties and a partial implementation of the conversion methods,
* mainly to check if the converter supports the conversion based on the payload class
* and MIME type.
*
* @author Rossen Stoyanchev
* @author Sebastien Deleuze
* @author Juergen Hoeller
* @since 4.0
*/
public abstract class AbstractMessageConverter implements SmartMessageConverter {
protected final Log logger = LogFactory.getLog(getClass());
private final List supportedMimeTypes;
private ContentTypeResolver contentTypeResolver = new DefaultContentTypeResolver();
private boolean strictContentTypeMatch = false;
private Class> serializedPayloadClass = byte[].class;
/**
* Construct an {@code AbstractMessageConverter} supporting a single MIME type.
* @param supportedMimeType the supported MIME type
*/
protected AbstractMessageConverter(MimeType supportedMimeType) {
Assert.notNull(supportedMimeType, "supportedMimeType is required");
this.supportedMimeTypes = Collections.singletonList(supportedMimeType);
}
/**
* Construct an {@code AbstractMessageConverter} supporting multiple MIME types.
* @param supportedMimeTypes the supported MIME types
*/
protected AbstractMessageConverter(Collection supportedMimeTypes) {
Assert.notNull(supportedMimeTypes, "supportedMimeTypes must not be null");
this.supportedMimeTypes = new ArrayList(supportedMimeTypes);
}
/**
* Return the supported MIME types.
*/
public List getSupportedMimeTypes() {
return Collections.unmodifiableList(this.supportedMimeTypes);
}
/**
* Configure the {@link ContentTypeResolver} to use to resolve the content
* type of an input message.
* Note that if no resolver is configured, then
* {@link #setStrictContentTypeMatch(boolean) strictContentTypeMatch} should
* be left as {@code false} (the default) or otherwise this converter will
* ignore all messages.
*
By default, a {@code DefaultContentTypeResolver} instance is used.
*/
public void setContentTypeResolver(ContentTypeResolver resolver) {
this.contentTypeResolver = resolver;
}
/**
* Return the configured {@link ContentTypeResolver}.
*/
public ContentTypeResolver getContentTypeResolver() {
return this.contentTypeResolver;
}
/**
* Whether this converter should convert messages for which no content type
* could be resolved through the configured
* {@link org.springframework.messaging.converter.ContentTypeResolver}.
*
A converter can configured to be strict only when a
* {@link #setContentTypeResolver contentTypeResolver} is configured and the
* list of {@link #getSupportedMimeTypes() supportedMimeTypes} is not be empty.
*
When this flag is set to {@code true}, {@link #supportsMimeType(MessageHeaders)}
* will return {@code false} if the {@link #setContentTypeResolver contentTypeResolver}
* is not defined or if no content-type header is present.
*/
public void setStrictContentTypeMatch(boolean strictContentTypeMatch) {
if (strictContentTypeMatch) {
Assert.notEmpty(getSupportedMimeTypes(), "Strict match requires non-empty list of supported mime types");
Assert.notNull(getContentTypeResolver(), "Strict match requires ContentTypeResolver");
}
this.strictContentTypeMatch = strictContentTypeMatch;
}
/**
* Whether content type resolution must produce a value that matches one of
* the supported MIME types.
*/
public boolean isStrictContentTypeMatch() {
return this.strictContentTypeMatch;
}
/**
* Configure the preferred serialization class to use (byte[] or String) when
* converting an Object payload to a {@link Message}.
*
The default value is byte[].
* @param payloadClass either byte[] or String
*/
public void setSerializedPayloadClass(Class> payloadClass) {
if (!(byte[].class == payloadClass || String.class == payloadClass)) {
throw new IllegalArgumentException("Payload class must be byte[] or String: " + payloadClass);
}
this.serializedPayloadClass = payloadClass;
}
/**
* Return the configured preferred serialization payload class.
*/
public Class> getSerializedPayloadClass() {
return this.serializedPayloadClass;
}
/**
* Returns the default content type for the payload. Called when
* {@link #toMessage(Object, MessageHeaders)} is invoked without message headers or
* without a content type header.
*
By default, this returns the first element of the {@link #getSupportedMimeTypes()
* supportedMimeTypes}, if any. Can be overridden in sub-classes.
* @param payload the payload being converted to message
* @return the content type, or {@code null} if not known
*/
protected MimeType getDefaultContentType(Object payload) {
List mimeTypes = getSupportedMimeTypes();
return (!mimeTypes.isEmpty() ? mimeTypes.get(0) : null);
}
@Override
public final Object fromMessage(Message> message, Class> targetClass) {
return fromMessage(message, targetClass, null);
}
@Override
public final Object fromMessage(Message> message, Class> targetClass, Object conversionHint) {
if (!canConvertFrom(message, targetClass)) {
return null;
}
return convertFromInternal(message, targetClass, conversionHint);
}
protected boolean canConvertFrom(Message> message, Class> targetClass) {
return (supports(targetClass) && supportsMimeType(message.getHeaders()));
}
@Override
public final Message> toMessage(Object payload, MessageHeaders headers) {
return toMessage(payload, headers, null);
}
@Override
public final Message> toMessage(Object payload, MessageHeaders headers, Object conversionHint) {
if (!canConvertTo(payload, headers)) {
return null;
}
payload = convertToInternal(payload, headers, conversionHint);
if (payload == null) {
return null;
}
MimeType mimeType = getDefaultContentType(payload);
if (headers != null) {
MessageHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(headers, MessageHeaderAccessor.class);
if (accessor != null && accessor.isMutable()) {
accessor.setHeaderIfAbsent(MessageHeaders.CONTENT_TYPE, mimeType);
return MessageBuilder.createMessage(payload, accessor.getMessageHeaders());
}
}
MessageBuilder> builder = MessageBuilder.withPayload(payload);
if (headers != null) {
builder.copyHeaders(headers);
}
builder.setHeaderIfAbsent(MessageHeaders.CONTENT_TYPE, mimeType);
return builder.build();
}
protected boolean canConvertTo(Object payload, MessageHeaders headers) {
Class> clazz = (payload != null ? payload.getClass() : null);
return (supports(clazz) && supportsMimeType(headers));
}
protected boolean supportsMimeType(MessageHeaders headers) {
if (getSupportedMimeTypes().isEmpty()) {
return true;
}
MimeType mimeType = getMimeType(headers);
if (mimeType == null) {
return !isStrictContentTypeMatch();
}
for (MimeType current : getSupportedMimeTypes()) {
if (current.getType().equals(mimeType.getType()) && current.getSubtype().equals(mimeType.getSubtype())) {
return true;
}
}
return false;
}
protected MimeType getMimeType(MessageHeaders headers) {
return (this.contentTypeResolver != null ? this.contentTypeResolver.resolve(headers) : null);
}
/**
* Whether the given class is supported by this converter.
* @param clazz the class to test for support
* @return {@code true} if supported; {@code false} otherwise
*/
protected abstract boolean supports(Class> clazz);
/**
* Convert the message payload from serialized form to an Object.
* @param message the input message
* @param targetClass the target class for the conversion
* @param conversionHint an extra object passed to the {@link MessageConverter},
* e.g. the associated {@code MethodParameter} (may be {@code null}}
* @return the result of the conversion, or {@code null} if the converter cannot
* perform the conversion
* @since 4.2
*/
@SuppressWarnings("deprecation")
protected Object convertFromInternal(Message> message, Class> targetClass, Object conversionHint) {
return convertFromInternal(message, targetClass);
}
/**
* Convert the payload object to serialized form.
* @param payload the Object to convert
* @param headers optional headers for the message (may be {@code null})
* @param conversionHint an extra object passed to the {@link MessageConverter},
* e.g. the associated {@code MethodParameter} (may be {@code null}}
* @return the resulting payload for the message, or {@code null} if the converter
* cannot perform the conversion
* @since 4.2
*/
@SuppressWarnings("deprecation")
protected Object convertToInternal(Object payload, MessageHeaders headers, Object conversionHint) {
return convertToInternal(payload, headers);
}
/**
* Convert the message payload from serialized form to an Object.
* @deprecated as of Spring 4.2, in favor of {@link #convertFromInternal(Message, Class, Object)}
* (which is also protected instead of public)
*/
@Deprecated
public Object convertFromInternal(Message> message, Class> targetClass) {
return null;
}
/**
* Convert the payload object to serialized form.
* @deprecated as of Spring 4.2, in favor of {@link #convertFromInternal(Message, Class, Object)}
* (which is also protected instead of public)
*/
@Deprecated
public Object convertToInternal(Object payload, MessageHeaders headers) {
return null;
}
}