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
master
Rossen Stoyanchev 11 years ago
parent 2b69c1f15b
commit 56476cdd5f
  1. 72
      spring-messaging/src/main/java/org/springframework/messaging/converter/AbstractMessageConverter.java
  2. 13
      spring-messaging/src/main/java/org/springframework/messaging/converter/ContentTypeResolver.java
  3. 1
      spring-messaging/src/main/java/org/springframework/messaging/converter/DefaultContentTypeResolver.java
  4. 27
      spring-messaging/src/test/java/org/springframework/messaging/converter/MessageConverterTests.java

@ -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…
Cancel
Save