mirror of
https://github.com/spring-projects/spring-framework.git
synced 2026-09-17 08:24:13 +00:00
Fix wildcard MIME type support in messaging converters
Prior to this commit, the "application/*+json" wildcard MIME type was added to the list of supported MIME types in the JSON messaging converter. This change wasn't fully reflected in the `AbstractMessageConverter`, because only strict matching of type and subtybe were considered. This commit updates the `AbstractMessageConverter` to not only check the type and subtype, but also check whether the supported MIME type includes the one given as a parameter. Fixes gh-36285
This commit is contained in:
+1
-1
@@ -236,7 +236,7 @@ public abstract class AbstractMessageConverter implements SmartMessageConverter
|
||||
return !isStrictContentTypeMatch();
|
||||
}
|
||||
for (MimeType current : getSupportedMimeTypes()) {
|
||||
if (current.getType().equals(mimeType.getType()) && current.getSubtype().equals(mimeType.getSubtype())) {
|
||||
if (current.includes(mimeType)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
+379
@@ -0,0 +1,379 @@
|
||||
/*
|
||||
* Copyright 2002-present 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
|
||||
*
|
||||
* https://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.lang.reflect.Method;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonView;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import tools.jackson.databind.DeserializationFeature;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageHeaders;
|
||||
import org.springframework.messaging.support.MessageBuilder;
|
||||
import org.springframework.util.MimeType;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.assertj.core.api.Assertions.within;
|
||||
|
||||
/**
|
||||
* Tests for {@link JacksonJsonMessageConverter}.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @author Rossen Stoyanchev
|
||||
* @author Sebastien Deleuze
|
||||
*/
|
||||
class JacksonJsonMessageConverterTests {
|
||||
|
||||
@Test
|
||||
void defaultConstructor() {
|
||||
JacksonJsonMessageConverter converter = new JacksonJsonMessageConverter();
|
||||
assertThat(converter.getSupportedMimeTypes()).contains(new MimeType("application", "json"));
|
||||
assertThat(converter.getJsonMapper().deserializationConfig()
|
||||
.isEnabled(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)).isFalse();
|
||||
}
|
||||
|
||||
@Test // SPR-12724
|
||||
void mimetypeParametrizedConstructor() {
|
||||
MimeType mimetype = new MimeType("application", "xml", StandardCharsets.UTF_8);
|
||||
JacksonJsonMessageConverter converter = new JacksonJsonMessageConverter(mimetype);
|
||||
assertThat(converter.getSupportedMimeTypes()).contains(mimetype);
|
||||
assertThat(converter.getJsonMapper().deserializationConfig()
|
||||
.isEnabled(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)).isFalse();
|
||||
}
|
||||
|
||||
@Test // SPR-12724
|
||||
void mimetypesParametrizedConstructor() {
|
||||
MimeType jsonMimetype = new MimeType("application", "json", StandardCharsets.UTF_8);
|
||||
MimeType xmlMimetype = new MimeType("application", "xml", StandardCharsets.UTF_8);
|
||||
JacksonJsonMessageConverter converter = new JacksonJsonMessageConverter(jsonMimetype, xmlMimetype);
|
||||
assertThat(converter.getSupportedMimeTypes()).contains(jsonMimetype, xmlMimetype);
|
||||
assertThat(converter.getJsonMapper().deserializationConfig()
|
||||
.isEnabled(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void supportJsonMimeType() {
|
||||
JacksonJsonMessageConverter converter = new JacksonJsonMessageConverter();
|
||||
Message<String> message = MessageBuilder.withPayload("foo")
|
||||
.setHeader(MessageHeaders.CONTENT_TYPE, "application/json").build();
|
||||
assertThat(converter.supportsMimeType(message.getHeaders())).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void supportVendorJsonMimeTypes() {
|
||||
JacksonJsonMessageConverter converter = new JacksonJsonMessageConverter();
|
||||
Message<String> message = MessageBuilder.withPayload("foo")
|
||||
.setHeader(MessageHeaders.CONTENT_TYPE, "application/vnd.springframework.type+json").build();
|
||||
assertThat(converter.supportsMimeType(message.getHeaders())).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void fromMessage() {
|
||||
JacksonJsonMessageConverter converter = new JacksonJsonMessageConverter();
|
||||
String payload = "{\"bytes\":\"AQI=\",\"array\":[\"Foo\",\"Bar\"]," +
|
||||
"\"number\":42,\"string\":\"Foo\",\"bool\":true,\"fraction\":42.0}";
|
||||
Message<?> message = MessageBuilder.withPayload(payload.getBytes(StandardCharsets.UTF_8)).build();
|
||||
MyBean actual = (MyBean) converter.fromMessage(message, MyBean.class);
|
||||
|
||||
assertThat(actual.getString()).isEqualTo("Foo");
|
||||
assertThat(actual.getNumber()).isEqualTo(42);
|
||||
assertThat(actual.getFraction()).isCloseTo(42F, within(0F));
|
||||
assertThat(actual.getArray()).isEqualTo(new String[]{"Foo", "Bar"});
|
||||
assertThat(actual.isBool()).isTrue();
|
||||
assertThat(actual.getBytes()).isEqualTo(new byte[]{0x1, 0x2});
|
||||
}
|
||||
|
||||
@Test
|
||||
void fromMessageUntyped() {
|
||||
JacksonJsonMessageConverter converter = new JacksonJsonMessageConverter();
|
||||
String payload = "{\"bytes\":\"AQI=\",\"array\":[\"Foo\",\"Bar\"]," +
|
||||
"\"number\":42,\"string\":\"Foo\",\"bool\":true,\"fraction\":42.0}";
|
||||
Message<?> message = MessageBuilder.withPayload(payload.getBytes(StandardCharsets.UTF_8)).build();
|
||||
@SuppressWarnings("unchecked")
|
||||
HashMap<String, Object> actual = (HashMap<String, Object>) converter.fromMessage(message, HashMap.class);
|
||||
|
||||
assertThat(actual.get("string")).isEqualTo("Foo");
|
||||
assertThat(actual.get("number")).isEqualTo(42);
|
||||
assertThat((Double) actual.get("fraction")).isCloseTo(42D, within(0D));
|
||||
assertThat(actual.get("array")).isEqualTo(Arrays.asList("Foo", "Bar"));
|
||||
assertThat(actual.get("bool")).isEqualTo(Boolean.TRUE);
|
||||
assertThat(actual.get("bytes")).isEqualTo("AQI=");
|
||||
}
|
||||
|
||||
@Test // gh-22386
|
||||
public void fromMessageMatchingInstance() {
|
||||
MyBean myBean = new MyBean();
|
||||
JacksonJsonMessageConverter converter = new JacksonJsonMessageConverter();
|
||||
Message<?> message = MessageBuilder.withPayload(myBean).build();
|
||||
assertThat(converter.fromMessage(message, MyBean.class)).isSameAs(myBean);
|
||||
}
|
||||
|
||||
@Test
|
||||
void fromMessageInvalidJson() {
|
||||
JacksonJsonMessageConverter converter = new JacksonJsonMessageConverter();
|
||||
String payload = "FooBar";
|
||||
Message<?> message = MessageBuilder.withPayload(payload.getBytes(StandardCharsets.UTF_8)).build();
|
||||
assertThatExceptionOfType(MessageConversionException.class).isThrownBy(() ->
|
||||
converter.fromMessage(message, MyBean.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void fromMessageValidJsonWithUnknownProperty() {
|
||||
JacksonJsonMessageConverter converter = new JacksonJsonMessageConverter();
|
||||
String payload = "{\"string\":\"string\",\"unknownProperty\":\"value\"}";
|
||||
Message<?> message = MessageBuilder.withPayload(payload.getBytes(StandardCharsets.UTF_8)).build();
|
||||
MyBean myBean = (MyBean)converter.fromMessage(message, MyBean.class);
|
||||
assertThat(myBean.getString()).isEqualTo("string");
|
||||
}
|
||||
|
||||
@Test // SPR-16252
|
||||
public void fromMessageToList() throws Exception {
|
||||
JacksonJsonMessageConverter converter = new JacksonJsonMessageConverter();
|
||||
String payload = "[1, 2, 3, 4, 5, 6, 7, 8, 9]";
|
||||
Message<?> message = MessageBuilder.withPayload(payload.getBytes(StandardCharsets.UTF_8)).build();
|
||||
|
||||
Method method = getClass().getDeclaredMethod("handleList", List.class);
|
||||
MethodParameter param = new MethodParameter(method, 0);
|
||||
Object actual = converter.fromMessage(message, List.class, param);
|
||||
|
||||
assertThat(actual).isNotNull();
|
||||
assertThat(actual).isEqualTo(Arrays.asList(1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L, 9L));
|
||||
}
|
||||
|
||||
@Test // SPR-16486
|
||||
public void fromMessageToMessageWithPojo() throws Exception {
|
||||
JacksonJsonMessageConverter converter = new JacksonJsonMessageConverter();
|
||||
String payload = "{\"string\":\"foo\"}";
|
||||
Message<?> message = MessageBuilder.withPayload(payload.getBytes(StandardCharsets.UTF_8)).build();
|
||||
|
||||
Method method = getClass().getDeclaredMethod("handleMessage", Message.class);
|
||||
MethodParameter param = new MethodParameter(method, 0);
|
||||
Object actual = converter.fromMessage(message, MyBean.class, param);
|
||||
|
||||
assertThat(actual).isInstanceOf(MyBean.class);
|
||||
assertThat(((MyBean) actual).getString()).isEqualTo("foo");
|
||||
}
|
||||
|
||||
@Test
|
||||
void toMessage() {
|
||||
JacksonJsonMessageConverter converter = new JacksonJsonMessageConverter();
|
||||
MyBean payload = new MyBean();
|
||||
payload.setString("Foo");
|
||||
payload.setNumber(42);
|
||||
payload.setFraction(42F);
|
||||
payload.setArray(new String[]{"Foo", "Bar"});
|
||||
payload.setBool(true);
|
||||
payload.setBytes(new byte[]{0x1, 0x2});
|
||||
|
||||
Message<?> message = converter.toMessage(payload, null);
|
||||
String actual = new String((byte[]) message.getPayload(), StandardCharsets.UTF_8);
|
||||
|
||||
assertThat(actual).contains("\"string\":\"Foo\"");
|
||||
assertThat(actual).contains("\"number\":42");
|
||||
assertThat(actual).contains("fraction\":42.0");
|
||||
assertThat(actual).contains("\"array\":[\"Foo\",\"Bar\"]");
|
||||
assertThat(actual).contains("\"bool\":true");
|
||||
assertThat(actual).contains("\"bytes\":\"AQI=\"");
|
||||
assertThat(message.getHeaders().get(MessageHeaders.CONTENT_TYPE, MimeType.class)).as("Invalid content-type").isEqualTo(new MimeType("application", "json"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void toMessageUtf16() {
|
||||
JacksonJsonMessageConverter converter = new JacksonJsonMessageConverter();
|
||||
MimeType contentType = new MimeType("application", "json", StandardCharsets.UTF_16BE);
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put(MessageHeaders.CONTENT_TYPE, contentType);
|
||||
MessageHeaders headers = new MessageHeaders(map);
|
||||
String payload = "H\u00e9llo W\u00f6rld";
|
||||
Message<?> message = converter.toMessage(payload, headers);
|
||||
|
||||
assertThat(new String((byte[]) message.getPayload(), StandardCharsets.UTF_16BE)).isEqualTo("\"" + payload + "\"");
|
||||
assertThat(message.getHeaders().get(MessageHeaders.CONTENT_TYPE)).isEqualTo(contentType);
|
||||
}
|
||||
|
||||
@Test
|
||||
void toMessageUtf16String() {
|
||||
JacksonJsonMessageConverter converter = new JacksonJsonMessageConverter();
|
||||
converter.setSerializedPayloadClass(String.class);
|
||||
|
||||
MimeType contentType = new MimeType("application", "json", StandardCharsets.UTF_16BE);
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put(MessageHeaders.CONTENT_TYPE, contentType);
|
||||
MessageHeaders headers = new MessageHeaders(map);
|
||||
String payload = "H\u00e9llo W\u00f6rld";
|
||||
Message<?> message = converter.toMessage(payload, headers);
|
||||
|
||||
assertThat(message.getPayload()).isEqualTo("\"" + payload + "\"");
|
||||
assertThat(message.getHeaders().get(MessageHeaders.CONTENT_TYPE)).isEqualTo(contentType);
|
||||
}
|
||||
|
||||
@Test
|
||||
void toMessageJsonView() throws Exception {
|
||||
JacksonJsonMessageConverter converter = new JacksonJsonMessageConverter();
|
||||
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
Method method = getClass().getDeclaredMethod("jsonViewResponse");
|
||||
MethodParameter returnType = new MethodParameter(method, -1);
|
||||
Message<?> message = converter.toMessage(jsonViewResponse(), new MessageHeaders(map), returnType);
|
||||
String actual = new String((byte[]) message.getPayload(), StandardCharsets.UTF_8);
|
||||
|
||||
assertThat(actual).contains("\"withView1\":\"with\"");
|
||||
assertThat(actual).contains("\"withView2\":\"with\"");
|
||||
assertThat(actual).doesNotContain("\"withoutView\":\"with\"");
|
||||
|
||||
method = getClass().getDeclaredMethod("jsonViewPayload", JacksonViewBean.class);
|
||||
MethodParameter param = new MethodParameter(method, 0);
|
||||
JacksonViewBean back = (JacksonViewBean) converter.fromMessage(message, JacksonViewBean.class, param);
|
||||
assertThat(back.getWithView1()).isNull();
|
||||
assertThat(back.getWithView2()).isEqualTo("with");
|
||||
assertThat(back.getWithoutView()).isNull();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@JsonView(MyJacksonView1.class)
|
||||
public JacksonViewBean jsonViewResponse() {
|
||||
JacksonViewBean bean = new JacksonViewBean();
|
||||
bean.setWithView1("with");
|
||||
bean.setWithView2("with");
|
||||
bean.setWithoutView("with");
|
||||
return bean;
|
||||
}
|
||||
|
||||
public void jsonViewPayload(@JsonView(MyJacksonView2.class) JacksonViewBean payload) {
|
||||
}
|
||||
|
||||
void handleList(List<Long> payload) {
|
||||
}
|
||||
|
||||
void handleMessage(Message<MyBean> message) {
|
||||
}
|
||||
|
||||
|
||||
public static class MyBean {
|
||||
|
||||
private String string;
|
||||
|
||||
private int number;
|
||||
|
||||
private float fraction;
|
||||
|
||||
private String[] array;
|
||||
|
||||
private boolean bool;
|
||||
|
||||
private byte[] bytes;
|
||||
|
||||
public byte[] getBytes() {
|
||||
return bytes;
|
||||
}
|
||||
|
||||
public void setBytes(byte[] bytes) {
|
||||
this.bytes = bytes;
|
||||
}
|
||||
|
||||
public boolean isBool() {
|
||||
return bool;
|
||||
}
|
||||
|
||||
public void setBool(boolean bool) {
|
||||
this.bool = bool;
|
||||
}
|
||||
|
||||
public String getString() {
|
||||
return string;
|
||||
}
|
||||
|
||||
public void setString(String string) {
|
||||
this.string = string;
|
||||
}
|
||||
|
||||
public int getNumber() {
|
||||
return number;
|
||||
}
|
||||
|
||||
public void setNumber(int number) {
|
||||
this.number = number;
|
||||
}
|
||||
|
||||
public float getFraction() {
|
||||
return fraction;
|
||||
}
|
||||
|
||||
public void setFraction(float fraction) {
|
||||
this.fraction = fraction;
|
||||
}
|
||||
|
||||
public String[] getArray() {
|
||||
return array;
|
||||
}
|
||||
|
||||
public void setArray(String[] array) {
|
||||
this.array = array;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public interface MyJacksonView1 {}
|
||||
|
||||
public interface MyJacksonView2 {}
|
||||
|
||||
|
||||
public static class JacksonViewBean {
|
||||
|
||||
@JsonView(MyJacksonView1.class)
|
||||
private String withView1;
|
||||
|
||||
@JsonView({MyJacksonView1.class, MyJacksonView2.class})
|
||||
private String withView2;
|
||||
|
||||
private String withoutView;
|
||||
|
||||
public String getWithView1() {
|
||||
return withView1;
|
||||
}
|
||||
|
||||
public void setWithView1(String withView1) {
|
||||
this.withView1 = withView1;
|
||||
}
|
||||
|
||||
public String getWithView2() {
|
||||
return withView2;
|
||||
}
|
||||
|
||||
public void setWithView2(String withView2) {
|
||||
this.withView2 = withView2;
|
||||
}
|
||||
|
||||
public String getWithoutView() {
|
||||
return withoutView;
|
||||
}
|
||||
|
||||
public void setWithoutView(String withoutView) {
|
||||
this.withoutView = withoutView;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+18
-2
@@ -55,7 +55,7 @@ class MappingJackson2MessageConverterTests {
|
||||
}
|
||||
|
||||
@Test // SPR-12724
|
||||
public void mimetypeParametrizedConstructor() {
|
||||
void mimetypeParametrizedConstructor() {
|
||||
MimeType mimetype = new MimeType("application", "xml", StandardCharsets.UTF_8);
|
||||
MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter(mimetype);
|
||||
assertThat(converter.getSupportedMimeTypes()).contains(mimetype);
|
||||
@@ -64,7 +64,7 @@ class MappingJackson2MessageConverterTests {
|
||||
}
|
||||
|
||||
@Test // SPR-12724
|
||||
public void mimetypesParametrizedConstructor() {
|
||||
void mimetypesParametrizedConstructor() {
|
||||
MimeType jsonMimetype = new MimeType("application", "json", StandardCharsets.UTF_8);
|
||||
MimeType xmlMimetype = new MimeType("application", "xml", StandardCharsets.UTF_8);
|
||||
MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter(jsonMimetype, xmlMimetype);
|
||||
@@ -73,6 +73,22 @@ class MappingJackson2MessageConverterTests {
|
||||
.isEnabled(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void supportJsonMimeType() {
|
||||
MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
|
||||
Message<String> message = MessageBuilder.withPayload("foo")
|
||||
.setHeader(MessageHeaders.CONTENT_TYPE, "application/json").build();
|
||||
assertThat(converter.supportsMimeType(message.getHeaders())).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void supportVendorJsonMimeTypes() {
|
||||
MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
|
||||
Message<String> message = MessageBuilder.withPayload("foo")
|
||||
.setHeader(MessageHeaders.CONTENT_TYPE, "application/vnd.springframework.type+json").build();
|
||||
assertThat(converter.supportsMimeType(message.getHeaders())).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void fromMessage() {
|
||||
MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
|
||||
|
||||
Reference in New Issue
Block a user