From 56476cdd5f806831b1619de91d00354458de3857 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Thu, 27 Feb 2014 15:47:42 +0100 Subject: [PATCH] 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 --- .../converter/AbstractMessageConverter.java | 72 +++++++++++++++---- .../converter/ContentTypeResolver.java | 13 +++- .../converter/DefaultContentTypeResolver.java | 1 + ...rTests.java => MessageConverterTests.java} | 27 ++++++- 4 files changed, 98 insertions(+), 15 deletions(-) rename spring-messaging/src/test/java/org/springframework/messaging/converter/{AbstractMessageConverterTests.java => MessageConverterTests.java} (78%) diff --git a/spring-messaging/src/main/java/org/springframework/messaging/converter/AbstractMessageConverter.java b/spring-messaging/src/main/java/org/springframework/messaging/converter/AbstractMessageConverter.java index 58bee4e199..74a316e17d 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/converter/AbstractMessageConverter.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/converter/AbstractMessageConverter.java @@ -43,23 +43,27 @@ public abstract class AbstractMessageConverter implements MessageConverter { protected final Log logger = LogFactory.getLog(getClass()); - private final List supportedMimeTypes; - private Class serializedPayloadClass = byte[].class; + private final List 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.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 supportedMimeTypes) { @@ -69,28 +73,64 @@ public abstract class AbstractMessageConverter implements MessageConverter { /** - * Return the configured supported MIME types. + * Return the supported MIME types. */ public List getSupportedMimeTypes() { return Collections.unmodifiableList(this.supportedMimeTypes); } /** - * Configure the {@link ContentTypeResolver} to use. - *

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. + *

+ * 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())) { diff --git a/spring-messaging/src/main/java/org/springframework/messaging/converter/ContentTypeResolver.java b/spring-messaging/src/main/java/org/springframework/messaging/converter/ContentTypeResolver.java index 283e1ca05f..77a77ead2c 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/converter/ContentTypeResolver.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/converter/ContentTypeResolver.java @@ -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); } diff --git a/spring-messaging/src/main/java/org/springframework/messaging/converter/DefaultContentTypeResolver.java b/spring-messaging/src/main/java/org/springframework/messaging/converter/DefaultContentTypeResolver.java index 73c013ac22..2f0956b5b2 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/converter/DefaultContentTypeResolver.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/converter/DefaultContentTypeResolver.java @@ -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) { diff --git a/spring-messaging/src/test/java/org/springframework/messaging/converter/AbstractMessageConverterTests.java b/spring-messaging/src/test/java/org/springframework/messaging/converter/MessageConverterTests.java similarity index 78% rename from spring-messaging/src/test/java/org/springframework/messaging/converter/AbstractMessageConverterTests.java rename to spring-messaging/src/test/java/org/springframework/messaging/converter/MessageConverterTests.java index 463f1657bd..6d4ae23483 100644 --- a/spring-messaging/src/test/java/org/springframework/messaging/converter/AbstractMessageConverterTests.java +++ b/spring-messaging/src/test/java/org/springframework/messaging/converter/MessageConverterTests.java @@ -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 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 message = MessageBuilder.withPayload("ABC").build(); + this.converter = new TestMessageConverter(Collections.emptyList()); + this.converter.setContentTypeResolver(new DefaultContentTypeResolver()); + this.converter.setStrictContentTypeMatch(true); + } + @Test public void toMessageHeadersCopied() { Map map = new HashMap();