Add strictContentTypeMatching converter option
AbstractMessageConverter now supports a strictContentTypeMatching mode in which the content type of a message must be resolved to a (non-null) value that matches one of the configured supported MIME types in order for the converter to be used. Issue: SPR-11463
This commit is contained in:
parent
2b69c1f15b
commit
56476cdd5f
|
@ -43,23 +43,27 @@ public abstract class AbstractMessageConverter implements MessageConverter {
|
|||
|
||||
protected final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
private final List<MimeType> supportedMimeTypes;
|
||||
|
||||
private Class<?> serializedPayloadClass = byte[].class;
|
||||
private final List<MimeType> supportedMimeTypes;
|
||||
|
||||
private ContentTypeResolver contentTypeResolver;
|
||||
|
||||
private boolean strictContentTypeMatch = false;
|
||||
|
||||
private Class<?> serializedPayloadClass = byte[].class;
|
||||
|
||||
|
||||
/**
|
||||
* Construct an {@code AbstractMessageConverter} with one supported MIME type.
|
||||
* 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.<MimeType>singletonList(supportedMimeType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an {@code AbstractMessageConverter} with multiple supported MIME type.
|
||||
* Construct an {@code AbstractMessageConverter} supporting multiple MIME types.
|
||||
* @param supportedMimeTypes the supported MIME types
|
||||
*/
|
||||
protected AbstractMessageConverter(Collection<MimeType> supportedMimeTypes) {
|
||||
|
@ -69,28 +73,64 @@ public abstract class AbstractMessageConverter implements MessageConverter {
|
|||
|
||||
|
||||
/**
|
||||
* Return the configured supported MIME types.
|
||||
* Return the supported MIME types.
|
||||
*/
|
||||
public List<MimeType> getSupportedMimeTypes() {
|
||||
return Collections.unmodifiableList(this.supportedMimeTypes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the {@link ContentTypeResolver} to use.
|
||||
* <p>The default value is {@code null}. However when {@link CompositeMessageConverter}
|
||||
* is used it configures all of its delegates with a default resolver.
|
||||
* Configure the {@link ContentTypeResolver} to use to resolve the content
|
||||
* type of an input message.
|
||||
* <p>
|
||||
* By default, no {@code ContentTypeResolver} is configured. When a resolver
|
||||
* is not configured, then {@link #setStrictContentTypeMatch(boolean)} should
|
||||
* be left {@code false} (the default) or otherwise this converter will ignore
|
||||
* all input messages.
|
||||
*/
|
||||
public void setContentTypeResolver(ContentTypeResolver resolver) {
|
||||
this.contentTypeResolver = resolver;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the default {@link ContentTypeResolver}.
|
||||
* 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) contentTypeResolver}
|
||||
* is configured and the list of {@link #getSupportedMimeTypes() supportedMimeTypes}
|
||||
* is not be empty.
|
||||
*
|
||||
* then requires the content type of a message to be resolved
|
||||
*
|
||||
* When set to true, #supportsMimeType(MessageHeaders) will return false if the
|
||||
* contentTypeResolver is not defined or if no content-type header is present.
|
||||
*/
|
||||
public void setStrictContentTypeMatch(boolean strictContentTypeMatch) {
|
||||
if (strictContentTypeMatch) {
|
||||
Assert.notEmpty(getSupportedMimeTypes(),
|
||||
"A strict converter requires a non-empty list of supported mime types");
|
||||
Assert.notNull(getContentTypeResolver(),
|
||||
"A strict converter requires a 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}.
|
||||
|
@ -110,6 +150,7 @@ public abstract class AbstractMessageConverter implements MessageConverter {
|
|||
return this.serializedPayloadClass;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the default content type for the payload. Called when
|
||||
* {@link #toMessage(Object, MessageHeaders)} is invoked without message headers or
|
||||
|
@ -177,13 +218,18 @@ public abstract class AbstractMessageConverter implements MessageConverter {
|
|||
public abstract Object convertToInternal(Object payload, MessageHeaders headers);
|
||||
|
||||
protected boolean supportsMimeType(MessageHeaders headers) {
|
||||
MimeType mimeType = getMimeType(headers);
|
||||
if (mimeType == null) {
|
||||
return true;
|
||||
}
|
||||
if (getSupportedMimeTypes().isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
MimeType mimeType = getMimeType(headers);
|
||||
if (mimeType == null) {
|
||||
if (isStrictContentTypeMatch()) {
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
for (MimeType supported : getSupportedMimeTypes()) {
|
||||
if (supported.getType().equals(mimeType.getType()) &&
|
||||
supported.getSubtype().equals(mimeType.getSubtype())) {
|
||||
|
|
|
@ -20,13 +20,24 @@ import org.springframework.messaging.MessageHeaders;
|
|||
import org.springframework.util.MimeType;
|
||||
|
||||
/**
|
||||
* Resolve the content type for a message given a set of {@link MessageHeaders}.
|
||||
* Resolve the content type for a message.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 4.0
|
||||
*/
|
||||
public interface ContentTypeResolver {
|
||||
|
||||
/**
|
||||
* Determine the {@link MimeType} of a message from the given MessageHeaders.
|
||||
*
|
||||
* @param headers the headers to use for the resolution
|
||||
* @return the resolved {@code MimeType} of {@code null} if none found
|
||||
*
|
||||
* @throws org.springframework.util.InvalidMimeTypeException if the content type
|
||||
* is a String that cannot be parsed
|
||||
* @throws java.lang.IllegalArgumentException if there is a content type but
|
||||
* its type is unknown
|
||||
*/
|
||||
MimeType resolve(MessageHeaders headers);
|
||||
|
||||
}
|
||||
|
|
|
@ -51,6 +51,7 @@ public class DefaultContentTypeResolver implements ContentTypeResolver {
|
|||
return this.defaultMimeType;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public MimeType resolve(MessageHeaders headers) {
|
||||
if (headers == null || headers.get(MessageHeaders.CONTENT_TYPE) == null) {
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
package org.springframework.messaging.converter;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
|
@ -30,13 +31,14 @@ import org.springframework.util.MimeType;
|
|||
import org.springframework.util.MimeTypeUtils;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* Test fixture for {@link org.springframework.messaging.converter.AbstractMessageConverter}.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
*/
|
||||
public class AbstractMessageConverterTests {
|
||||
public class MessageConverterTests {
|
||||
|
||||
private TestMessageConverter converter;
|
||||
|
||||
|
@ -89,6 +91,29 @@ public class AbstractMessageConverterTests {
|
|||
assertEquals("success-from", this.converter.fromMessage(message, String.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canConvertFromStrictContentTypeMatch() {
|
||||
this.converter = new TestMessageConverter(Arrays.asList(MimeTypeUtils.TEXT_PLAIN));
|
||||
this.converter.setContentTypeResolver(new DefaultContentTypeResolver());
|
||||
this.converter.setStrictContentTypeMatch(true);
|
||||
|
||||
Message<String> message = MessageBuilder.withPayload("ABC").build();
|
||||
assertFalse(this.converter.canConvertFrom(message, String.class));
|
||||
|
||||
message = MessageBuilder.withPayload("ABC")
|
||||
.setHeader(MessageHeaders.CONTENT_TYPE, MimeTypeUtils.TEXT_PLAIN).build();
|
||||
assertTrue(this.converter.canConvertFrom(message, String.class));
|
||||
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void setStrictContentTypeMatchWithNoSupportedMimeTypes() {
|
||||
Message<String> message = MessageBuilder.withPayload("ABC").build();
|
||||
this.converter = new TestMessageConverter(Collections.<MimeType>emptyList());
|
||||
this.converter.setContentTypeResolver(new DefaultContentTypeResolver());
|
||||
this.converter.setStrictContentTypeMatch(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toMessageHeadersCopied() {
|
||||
Map<String, Object> map = new HashMap<String, Object>();
|
Loading…
Reference in New Issue