From 214bc407b49f5a6e1351d0ac9295afe99c61ea59 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Fri, 25 Sep 2020 10:48:25 +0200 Subject: [PATCH] Provide Gson/JSON-B MessageConverter for spring-messaging (aligned with spring-web) Closes gh-21496 --- spring-messaging/spring-messaging.gradle | 6 +- .../AbstractJsonMessageConverter.java | 131 +++++++++ .../converter/AbstractMessageConverter.java | 62 +++-- .../converter/GsonMessageConverter.java | 110 ++++++++ .../converter/JsonbMessageConverter.java | 122 +++++++++ .../MappingJackson2MessageConverter.java | 24 +- .../AbstractMessageBrokerConfiguration.java | 26 +- .../converter/GsonMessageConverterTests.java | 249 ++++++++++++++++++ .../converter/JsonbMessageConverterTests.java | 249 ++++++++++++++++++ .../MappingJackson2MessageConverterTests.java | 41 ++- .../MessageBrokerBeanDefinitionParser.java | 19 +- 11 files changed, 971 insertions(+), 68 deletions(-) create mode 100644 spring-messaging/src/main/java/org/springframework/messaging/converter/AbstractJsonMessageConverter.java create mode 100644 spring-messaging/src/main/java/org/springframework/messaging/converter/GsonMessageConverter.java create mode 100644 spring-messaging/src/main/java/org/springframework/messaging/converter/JsonbMessageConverter.java create mode 100644 spring-messaging/src/test/java/org/springframework/messaging/converter/GsonMessageConverterTests.java create mode 100644 spring-messaging/src/test/java/org/springframework/messaging/converter/JsonbMessageConverterTests.java diff --git a/spring-messaging/spring-messaging.gradle b/spring-messaging/spring-messaging.gradle index ee8d1810603..a619cd3384b 100644 --- a/spring-messaging/spring-messaging.gradle +++ b/spring-messaging/spring-messaging.gradle @@ -12,6 +12,8 @@ dependencies { optional("io.rsocket:rsocket-core") optional("io.rsocket:rsocket-transport-netty") optional("com.fasterxml.jackson.core:jackson-databind") + optional("com.google.code.gson:gson") + optional("javax.json.bind:javax.json.bind-api") optional("javax.xml.bind:jaxb-api") optional("com.google.protobuf:protobuf-java-util") optional("org.jetbrains.kotlinx:kotlinx-coroutines-core") @@ -31,8 +33,10 @@ dependencies { testCompile("org.jetbrains.kotlin:kotlin-stdlib") testCompile("org.xmlunit:xmlunit-assertj") testCompile("org.xmlunit:xmlunit-matchers") + testRuntime("com.sun.activation:javax.activation") testRuntime("com.sun.xml.bind:jaxb-core") testRuntime("com.sun.xml.bind:jaxb-impl") - testRuntime("com.sun.activation:javax.activation") + testRuntime("javax.json:javax.json-api") + testRuntime("org.apache.johnzon:johnzon-jsonb") testRuntime(project(":spring-context")) } diff --git a/spring-messaging/src/main/java/org/springframework/messaging/converter/AbstractJsonMessageConverter.java b/spring-messaging/src/main/java/org/springframework/messaging/converter/AbstractJsonMessageConverter.java new file mode 100644 index 00000000000..a7a9597104f --- /dev/null +++ b/spring-messaging/src/main/java/org/springframework/messaging/converter/AbstractJsonMessageConverter.java @@ -0,0 +1,131 @@ +/* + * Copyright 2002-2020 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.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.io.Reader; +import java.io.Writer; +import java.lang.reflect.Type; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; + +import org.springframework.lang.Nullable; +import org.springframework.messaging.Message; +import org.springframework.messaging.MessageHeaders; +import org.springframework.util.ClassUtils; +import org.springframework.util.MimeType; + +/** + * Common base class for plain JSON converters, e.g. Gson and JSON-B. + * + * @author Juergen Hoeller + * @since 5.3 + * @see GsonMessageConverter + * @see JsonbMessageConverter + * @see #fromJson(Reader, Type) + * @see #fromJson(String, Type) + * @see #toJson(Object, Type) + * @see #toJson(Object, Type, Writer) + */ +public abstract class AbstractJsonMessageConverter extends AbstractMessageConverter { + + private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8; + + + protected AbstractJsonMessageConverter() { + super(new MimeType("application", "json")); + } + + + @Override + protected boolean supports(Class clazz) { + return true; + } + + @Override + @Nullable + protected Object convertFromInternal(Message message, Class targetClass, @Nullable Object conversionHint) { + try { + Type resolvedType = getResolvedType(targetClass, conversionHint); + Object payload = message.getPayload(); + if (ClassUtils.isAssignableValue(targetClass, payload)) { + return payload; + } + else if (payload instanceof byte[]) { + return fromJson(getReader((byte[]) payload, message.getHeaders()), resolvedType); + } + else { + // Assuming a text-based source payload + return fromJson(payload.toString(), resolvedType); + } + } + catch (Exception ex) { + throw new MessageConversionException(message, "Could not read JSON: " + ex.getMessage(), ex); + } + } + + @Override + @Nullable + protected Object convertToInternal(Object payload, @Nullable MessageHeaders headers, @Nullable Object conversionHint) { + try { + Type resolvedType = getResolvedType(payload.getClass(), conversionHint); + if (byte[].class == getSerializedPayloadClass()) { + ByteArrayOutputStream out = new ByteArrayOutputStream(1024); + Writer writer = getWriter(out, headers); + toJson(payload, resolvedType, writer); + writer.flush(); + return out.toByteArray(); + } + else { + // Assuming a text-based target payload + return toJson(payload, resolvedType); + } + } + catch (Exception ex) { + throw new MessageConversionException("Could not write JSON: " + ex.getMessage(), ex); + } + } + + + private Reader getReader(byte[] payload, @Nullable MessageHeaders headers) { + InputStream in = new ByteArrayInputStream(payload); + return new InputStreamReader(in, getCharsetToUse(headers)); + } + + private Writer getWriter(ByteArrayOutputStream out, @Nullable MessageHeaders headers) { + return new OutputStreamWriter(out, getCharsetToUse(headers)); + } + + private Charset getCharsetToUse(@Nullable MessageHeaders headers) { + MimeType mimeType = getMimeType(headers); + return (mimeType != null && mimeType.getCharset() != null ? mimeType.getCharset() : DEFAULT_CHARSET); + } + + + protected abstract Object fromJson(Reader reader, Type resolvedType); + + protected abstract Object fromJson(String payload, Type resolvedType); + + protected abstract void toJson(Object payload, Type resolvedType, Writer writer); + + protected abstract String toJson(Object payload, Type resolvedType); + +} 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 0dd92e12064..b8400e046c8 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2020 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. @@ -16,6 +16,7 @@ package org.springframework.messaging.converter; +import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -25,6 +26,8 @@ import java.util.List; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.core.GenericTypeResolver; +import org.springframework.core.MethodParameter; import org.springframework.lang.Nullable; import org.springframework.messaging.Message; import org.springframework.messaging.MessageHeaders; @@ -92,7 +95,7 @@ public abstract class AbstractMessageConverter implements SmartMessageConverter } /** - * Allows sub-classes to add more supported mime types. + * Allows subclasses to add more supported mime types. * @since 5.2.2 */ protected void addSupportedMimeTypes(MimeType... supportedMimeTypes) { @@ -167,21 +170,6 @@ public abstract class AbstractMessageConverter implements SmartMessageConverter } - /** - * 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 - */ - @Nullable - protected MimeType getDefaultContentType(Object payload) { - List mimeTypes = getSupportedMimeTypes(); - return (!mimeTypes.isEmpty() ? mimeTypes.get(0) : null); - } - @Override @Nullable public final Object fromMessage(Message message, Class targetClass) { @@ -197,10 +185,6 @@ public abstract class AbstractMessageConverter implements SmartMessageConverter return convertFromInternal(message, targetClass, conversionHint); } - protected boolean canConvertFrom(Message message, Class targetClass) { - return (supports(targetClass) && supportsMimeType(message.getHeaders())); - } - @Override @Nullable public final Message toMessage(Object payload, @Nullable MessageHeaders headers) { @@ -240,6 +224,11 @@ public abstract class AbstractMessageConverter implements SmartMessageConverter return builder.build(); } + + protected boolean canConvertFrom(Message message, Class targetClass) { + return (supports(targetClass) && supportsMimeType(message.getHeaders())); + } + protected boolean canConvertTo(Object payload, @Nullable MessageHeaders headers) { return (supports(payload.getClass()) && supportsMimeType(headers)); } @@ -265,6 +254,22 @@ public abstract class AbstractMessageConverter implements SmartMessageConverter return (headers != null && this.contentTypeResolver != null ? this.contentTypeResolver.resolve(headers) : null); } + /** + * Return 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 subclasses. + * @param payload the payload being converted to a message + * @return the content type, or {@code null} if not known + */ + @Nullable + protected MimeType getDefaultContentType(Object payload) { + List mimeTypes = getSupportedMimeTypes(); + return (!mimeTypes.isEmpty() ? mimeTypes.get(0) : null); + } + /** * Whether the given class is supported by this converter. @@ -307,4 +312,19 @@ public abstract class AbstractMessageConverter implements SmartMessageConverter return null; } + + static Type getResolvedType(Class targetClass, @Nullable Object conversionHint) { + if (conversionHint instanceof MethodParameter) { + MethodParameter param = (MethodParameter) conversionHint; + param = param.nestedIfOptional(); + if (Message.class.isAssignableFrom(param.getParameterType())) { + param = param.nested(); + } + Type genericParameterType = param.getNestedGenericParameterType(); + Class contextClass = param.getContainingClass(); + return GenericTypeResolver.resolveType(genericParameterType, contextClass); + } + return targetClass; + } + } diff --git a/spring-messaging/src/main/java/org/springframework/messaging/converter/GsonMessageConverter.java b/spring-messaging/src/main/java/org/springframework/messaging/converter/GsonMessageConverter.java new file mode 100644 index 00000000000..95de58ede26 --- /dev/null +++ b/spring-messaging/src/main/java/org/springframework/messaging/converter/GsonMessageConverter.java @@ -0,0 +1,110 @@ +/* + * Copyright 2002-2020 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.io.Reader; +import java.io.Writer; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; + +import com.google.gson.Gson; + +import org.springframework.util.Assert; + +/** + * Implementation of {@link MessageConverter} that can read and write JSON + * using Google Gson. + * + * @author Juergen Hoeller + * @since 5.3 + * @see com.google.gson.Gson + * @see com.google.gson.GsonBuilder + * @see #setGson + */ +public class GsonMessageConverter extends AbstractJsonMessageConverter { + + private Gson gson; + + + /** + * Construct a new {@code GsonMessageConverter} with default configuration. + */ + public GsonMessageConverter() { + this.gson = new Gson(); + } + + /** + * Construct a new {@code GsonMessageConverter} with the given delegate. + * @param gson the Gson instance to use + */ + public GsonMessageConverter(Gson gson) { + Assert.notNull(gson, "A Gson instance is required"); + this.gson = gson; + } + + + /** + * Set the {@code Gson} instance to use. + * If not set, a default {@link Gson#Gson() Gson} instance will be used. + *

Setting a custom-configured {@code Gson} is one way to take further + * control of the JSON serialization process. + * @see #GsonMessageConverter(Gson) + */ + public void setGson(Gson gson) { + Assert.notNull(gson, "A Gson instance is required"); + this.gson = gson; + } + + /** + * Return the configured {@code Gson} instance for this converter. + */ + public Gson getGson() { + return this.gson; + } + + + @Override + protected Object fromJson(Reader reader, Type resolvedType) { + return getGson().fromJson(reader, resolvedType); + } + + @Override + protected Object fromJson(String payload, Type resolvedType) { + return getGson().fromJson(payload, resolvedType); + } + + @Override + protected void toJson(Object payload, Type resolvedType, Writer writer) { + if (resolvedType instanceof ParameterizedType) { + getGson().toJson(payload, resolvedType, writer); + } + else { + getGson().toJson(payload, writer); + } + } + + @Override + protected String toJson(Object payload, Type resolvedType) { + if (resolvedType instanceof ParameterizedType) { + return getGson().toJson(payload, resolvedType); + } + else { + return getGson().toJson(payload); + } + } + +} diff --git a/spring-messaging/src/main/java/org/springframework/messaging/converter/JsonbMessageConverter.java b/spring-messaging/src/main/java/org/springframework/messaging/converter/JsonbMessageConverter.java new file mode 100644 index 00000000000..e327edbc2fc --- /dev/null +++ b/spring-messaging/src/main/java/org/springframework/messaging/converter/JsonbMessageConverter.java @@ -0,0 +1,122 @@ +/* + * Copyright 2002-2020 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.io.Reader; +import java.io.Writer; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; + +import javax.json.bind.Jsonb; +import javax.json.bind.JsonbBuilder; +import javax.json.bind.JsonbConfig; + +import org.springframework.util.Assert; + +/** + * Implementation of {@link MessageConverter} that can read and write JSON + * using the JSON Binding API. + * + * @author Juergen Hoeller + * @since 5.3 + * @see javax.json.bind.Jsonb + * @see javax.json.bind.JsonbBuilder + * @see #setJsonb + */ +public class JsonbMessageConverter extends AbstractJsonMessageConverter { + + private Jsonb jsonb; + + + /** + * Construct a new {@code JsonbMessageConverter} with default configuration. + */ + public JsonbMessageConverter() { + this.jsonb = JsonbBuilder.create(); + } + + /** + * Construct a new {@code JsonbMessageConverter} with the given configuration. + * @param config the {@code JsonbConfig} for the underlying delegate + */ + public JsonbMessageConverter(JsonbConfig config) { + this.jsonb = JsonbBuilder.create(config); + } + + /** + * Construct a new {@code JsonbMessageConverter} with the given delegate. + * @param jsonb the Jsonb instance to use + */ + public JsonbMessageConverter(Jsonb jsonb) { + Assert.notNull(jsonb, "A Jsonb instance is required"); + this.jsonb = jsonb; + } + + + /** + * Set the {@code Jsonb} instance to use. + * If not set, a default {@code Jsonb} instance will be created. + *

Setting a custom-configured {@code Jsonb} is one way to take further + * control of the JSON serialization process. + * @see #JsonbMessageConverter(Jsonb) + * @see #JsonbMessageConverter(JsonbConfig) + * @see JsonbBuilder + */ + public void setJsonb(Jsonb jsonb) { + Assert.notNull(jsonb, "A Jsonb instance is required"); + this.jsonb = jsonb; + } + + /** + * Return the configured {@code Jsonb} instance for this converter. + */ + public Jsonb getJsonb() { + return this.jsonb; + } + + + @Override + protected Object fromJson(Reader reader, Type resolvedType) { + return getJsonb().fromJson(reader, resolvedType); + } + + @Override + protected Object fromJson(String payload, Type resolvedType) { + return getJsonb().fromJson(payload, resolvedType); + } + + @Override + protected void toJson(Object payload, Type resolvedType, Writer writer) { + if (resolvedType instanceof ParameterizedType) { + getJsonb().toJson(payload, resolvedType, writer); + } + else { + getJsonb().toJson(payload, writer); + } + } + + @Override + protected String toJson(Object payload, Type resolvedType) { + if (resolvedType instanceof ParameterizedType) { + return getJsonb().toJson(payload, resolvedType); + } + else { + return getJsonb().toJson(payload); + } + } + +} diff --git a/spring-messaging/src/main/java/org/springframework/messaging/converter/MappingJackson2MessageConverter.java b/spring-messaging/src/main/java/org/springframework/messaging/converter/MappingJackson2MessageConverter.java index fe4a194f0ae..935657a2ed7 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/converter/MappingJackson2MessageConverter.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/converter/MappingJackson2MessageConverter.java @@ -35,12 +35,12 @@ import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; -import org.springframework.core.GenericTypeResolver; import org.springframework.core.MethodParameter; import org.springframework.lang.Nullable; import org.springframework.messaging.Message; import org.springframework.messaging.MessageHeaders; import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; import org.springframework.util.MimeType; /** @@ -139,6 +139,7 @@ public class MappingJackson2MessageConverter extends AbstractMessageConverter { } } + @Override protected boolean canConvertFrom(Message message, @Nullable Class targetClass) { if (targetClass == null || !supportsMimeType(message.getHeaders())) { @@ -206,11 +207,11 @@ public class MappingJackson2MessageConverter extends AbstractMessageConverter { @Override @Nullable protected Object convertFromInternal(Message message, Class targetClass, @Nullable Object conversionHint) { - JavaType javaType = getJavaType(targetClass, conversionHint); + JavaType javaType = this.objectMapper.constructType(getResolvedType(targetClass, conversionHint)); Object payload = message.getPayload(); Class view = getSerializationView(conversionHint); try { - if (targetClass.isInstance(payload)) { + if (ClassUtils.isAssignableValue(targetClass, payload)) { return payload; } else if (payload instanceof byte[]) { @@ -236,21 +237,6 @@ public class MappingJackson2MessageConverter extends AbstractMessageConverter { } } - private JavaType getJavaType(Class targetClass, @Nullable Object conversionHint) { - if (conversionHint instanceof MethodParameter) { - MethodParameter param = (MethodParameter) conversionHint; - param = param.nestedIfOptional(); - if (Message.class.isAssignableFrom(param.getParameterType())) { - param = param.nested(); - } - Type genericParameterType = param.getNestedGenericParameterType(); - Class contextClass = param.getContainingClass(); - Type type = GenericTypeResolver.resolveType(genericParameterType, contextClass); - return this.objectMapper.getTypeFactory().constructType(type); - } - return this.objectMapper.constructType(targetClass); - } - @Override @Nullable protected Object convertToInternal(Object payload, @Nullable MessageHeaders headers, @@ -331,7 +317,7 @@ public class MappingJackson2MessageConverter extends AbstractMessageConverter { * @return the JSON encoding to use (never {@code null}) */ protected JsonEncoding getJsonEncoding(@Nullable MimeType contentType) { - if (contentType != null && (contentType.getCharset() != null)) { + if (contentType != null && contentType.getCharset() != null) { Charset charset = contentType.getCharset(); for (JsonEncoding encoding : JsonEncoding.values()) { if (charset.name().equals(encoding.getJavaName())) { diff --git a/spring-messaging/src/main/java/org/springframework/messaging/simp/config/AbstractMessageBrokerConfiguration.java b/spring-messaging/src/main/java/org/springframework/messaging/simp/config/AbstractMessageBrokerConfiguration.java index a3cc4f10c15..86398a82ef1 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/simp/config/AbstractMessageBrokerConfiguration.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/simp/config/AbstractMessageBrokerConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2020 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. @@ -34,6 +34,8 @@ import org.springframework.messaging.MessageHandler; import org.springframework.messaging.converter.ByteArrayMessageConverter; import org.springframework.messaging.converter.CompositeMessageConverter; import org.springframework.messaging.converter.DefaultContentTypeResolver; +import org.springframework.messaging.converter.GsonMessageConverter; +import org.springframework.messaging.converter.JsonbMessageConverter; import org.springframework.messaging.converter.MappingJackson2MessageConverter; import org.springframework.messaging.converter.MessageConverter; import org.springframework.messaging.converter.StringMessageConverter; @@ -93,8 +95,20 @@ public abstract class AbstractMessageBrokerConfiguration implements ApplicationC private static final String MVC_VALIDATOR_NAME = "mvcValidator"; - private static final boolean jackson2Present = ClassUtils.isPresent( - "com.fasterxml.jackson.databind.ObjectMapper", AbstractMessageBrokerConfiguration.class.getClassLoader()); + private static final boolean jackson2Present; + + private static final boolean gsonPresent; + + private static final boolean jsonbPresent; + + + static { + ClassLoader classLoader = AbstractMessageBrokerConfiguration.class.getClassLoader(); + jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader) && + ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader); + gsonPresent = ClassUtils.isPresent("com.google.gson.Gson", classLoader); + jsonbPresent = ClassUtils.isPresent("javax.json.bind.Jsonb", classLoader); + } @Nullable @@ -391,6 +405,12 @@ public abstract class AbstractMessageBrokerConfiguration implements ApplicationC if (jackson2Present) { converters.add(createJacksonConverter()); } + else if (gsonPresent) { + converters.add(new GsonMessageConverter()); + } + else if (jsonbPresent) { + converters.add(new JsonbMessageConverter()); + } } return new CompositeMessageConverter(converters); } diff --git a/spring-messaging/src/test/java/org/springframework/messaging/converter/GsonMessageConverterTests.java b/spring-messaging/src/test/java/org/springframework/messaging/converter/GsonMessageConverterTests.java new file mode 100644 index 00000000000..c5c100449e3 --- /dev/null +++ b/spring-messaging/src/test/java/org/springframework/messaging/converter/GsonMessageConverterTests.java @@ -0,0 +1,249 @@ +/* + * Copyright 2002-2020 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 org.junit.jupiter.api.Test; + +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; + +/** + * Test fixture for {@link GsonMessageConverter}. + * + * @author Rossen Stoyanchev + * @author Sebastien Deleuze + */ +public class GsonMessageConverterTests { + + @Test + public void defaultConstructor() { + GsonMessageConverter converter = new GsonMessageConverter(); + assertThat(converter.getSupportedMimeTypes()).contains(new MimeType("application", "json")); + } + + @Test + public void fromMessage() { + GsonMessageConverter converter = new GsonMessageConverter(); + String payload = "{\"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(); + } + + @Test + public void fromMessageUntyped() { + GsonMessageConverter converter = new GsonMessageConverter(); + String payload = "{\"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 actual = (HashMap) converter.fromMessage(message, HashMap.class); + + assertThat(actual.get("string")).isEqualTo("Foo"); + assertThat(actual.get("number")).isEqualTo(42.0); + 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); + } + + @Test + public void fromMessageMatchingInstance() { + MyBean myBean = new MyBean(); + GsonMessageConverter converter = new GsonMessageConverter(); + Message message = MessageBuilder.withPayload(myBean).build(); + assertThat(converter.fromMessage(message, MyBean.class)).isSameAs(myBean); + } + + @Test + public void fromMessageInvalidJson() { + GsonMessageConverter converter = new GsonMessageConverter(); + String payload = "FooBar"; + Message message = MessageBuilder.withPayload(payload.getBytes(StandardCharsets.UTF_8)).build(); + assertThatExceptionOfType(MessageConversionException.class).isThrownBy(() -> + converter.fromMessage(message, MyBean.class)); + } + + @Test + public void fromMessageValidJsonWithUnknownProperty() { + GsonMessageConverter converter = new GsonMessageConverter(); + 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 + public void fromMessageToList() throws Exception { + GsonMessageConverter converter = new GsonMessageConverter(); + 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 + public void fromMessageToMessageWithPojo() throws Exception { + GsonMessageConverter converter = new GsonMessageConverter(); + 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 instanceof MyBean).isTrue(); + assertThat(((MyBean) actual).getString()).isEqualTo("foo"); + } + + @Test + public void toMessage() { + GsonMessageConverter converter = new GsonMessageConverter(); + MyBean payload = new MyBean(); + payload.setString("Foo"); + payload.setNumber(42); + payload.setFraction(42F); + payload.setArray(new String[]{"Foo", "Bar"}); + payload.setBool(true); + + Message message = converter.toMessage(payload, null); + String actual = new String((byte[]) message.getPayload(), StandardCharsets.UTF_8); + + assertThat(actual.contains("\"string\":\"Foo\"")).isTrue(); + assertThat(actual.contains("\"number\":42")).isTrue(); + assertThat(actual.contains("fraction\":42.0")).isTrue(); + assertThat(actual.contains("\"array\":[\"Foo\",\"Bar\"]")).isTrue(); + assertThat(actual.contains("\"bool\":true")).isTrue(); + assertThat(message.getHeaders().get(MessageHeaders.CONTENT_TYPE, MimeType.class)).as("Invalid content-type").isEqualTo(new MimeType("application", "json")); + } + + @Test + public void toMessageUtf16() { + GsonMessageConverter converter = new GsonMessageConverter(); + MimeType contentType = new MimeType("application", "json", StandardCharsets.UTF_16BE); + Map 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 + public void toMessageUtf16String() { + GsonMessageConverter converter = new GsonMessageConverter(); + converter.setSerializedPayloadClass(String.class); + + MimeType contentType = new MimeType("application", "json", StandardCharsets.UTF_16BE); + Map 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); + } + + + void handleList(List payload) { + } + + void handleMessage(Message message) { + } + + + public static class MyBean { + + private String string; + + private int number; + + private float fraction; + + private String[] array; + + private boolean bool; + + 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; + } + } + +} diff --git a/spring-messaging/src/test/java/org/springframework/messaging/converter/JsonbMessageConverterTests.java b/spring-messaging/src/test/java/org/springframework/messaging/converter/JsonbMessageConverterTests.java new file mode 100644 index 00000000000..63380ea4ebc --- /dev/null +++ b/spring-messaging/src/test/java/org/springframework/messaging/converter/JsonbMessageConverterTests.java @@ -0,0 +1,249 @@ +/* + * Copyright 2002-2020 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 org.junit.jupiter.api.Test; + +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; + +/** + * Test fixture for {@link JsonbMessageConverter}. + * + * @author Rossen Stoyanchev + * @author Sebastien Deleuze + */ +public class JsonbMessageConverterTests { + + @Test + public void defaultConstructor() { + JsonbMessageConverter converter = new JsonbMessageConverter(); + assertThat(converter.getSupportedMimeTypes()).contains(new MimeType("application", "json")); + } + + @Test + public void fromMessage() { + JsonbMessageConverter converter = new JsonbMessageConverter(); + String payload = "{\"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(); + } + + @Test + public void fromMessageUntyped() { + JsonbMessageConverter converter = new JsonbMessageConverter(); + String payload = "{\"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 actual = (HashMap) 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); + } + + @Test + public void fromMessageMatchingInstance() { + MyBean myBean = new MyBean(); + JsonbMessageConverter converter = new JsonbMessageConverter(); + Message message = MessageBuilder.withPayload(myBean).build(); + assertThat(converter.fromMessage(message, MyBean.class)).isSameAs(myBean); + } + + @Test + public void fromMessageInvalidJson() { + JsonbMessageConverter converter = new JsonbMessageConverter(); + String payload = "FooBar"; + Message message = MessageBuilder.withPayload(payload.getBytes(StandardCharsets.UTF_8)).build(); + assertThatExceptionOfType(MessageConversionException.class).isThrownBy(() -> + converter.fromMessage(message, MyBean.class)); + } + + @Test + public void fromMessageValidJsonWithUnknownProperty() { + JsonbMessageConverter converter = new JsonbMessageConverter(); + 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 + public void fromMessageToList() throws Exception { + JsonbMessageConverter converter = new JsonbMessageConverter(); + 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 + public void fromMessageToMessageWithPojo() throws Exception { + JsonbMessageConverter converter = new JsonbMessageConverter(); + 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 instanceof MyBean).isTrue(); + assertThat(((MyBean) actual).getString()).isEqualTo("foo"); + } + + @Test + public void toMessage() { + JsonbMessageConverter converter = new JsonbMessageConverter(); + MyBean payload = new MyBean(); + payload.setString("Foo"); + payload.setNumber(42); + payload.setFraction(42F); + payload.setArray(new String[]{"Foo", "Bar"}); + payload.setBool(true); + + Message message = converter.toMessage(payload, null); + String actual = new String((byte[]) message.getPayload(), StandardCharsets.UTF_8); + + assertThat(actual.contains("\"string\":\"Foo\"")).isTrue(); + assertThat(actual.contains("\"number\":42")).isTrue(); + assertThat(actual.contains("fraction\":42.0")).isTrue(); + assertThat(actual.contains("\"array\":[\"Foo\",\"Bar\"]")).isTrue(); + assertThat(actual.contains("\"bool\":true")).isTrue(); + assertThat(message.getHeaders().get(MessageHeaders.CONTENT_TYPE, MimeType.class)).as("Invalid content-type").isEqualTo(new MimeType("application", "json")); + } + + @Test + public void toMessageUtf16() { + JsonbMessageConverter converter = new JsonbMessageConverter(); + MimeType contentType = new MimeType("application", "json", StandardCharsets.UTF_16BE); + Map 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 + public void toMessageUtf16String() { + JsonbMessageConverter converter = new JsonbMessageConverter(); + converter.setSerializedPayloadClass(String.class); + + MimeType contentType = new MimeType("application", "json", StandardCharsets.UTF_16BE); + Map 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); + } + + + void handleList(List payload) { + } + + void handleMessage(Message message) { + } + + + public static class MyBean { + + private String string; + + private int number; + + private float fraction; + + private String[] array; + + private boolean bool; + + 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; + } + } + +} diff --git a/spring-messaging/src/test/java/org/springframework/messaging/converter/MappingJackson2MessageConverterTests.java b/spring-messaging/src/test/java/org/springframework/messaging/converter/MappingJackson2MessageConverterTests.java index efca6b26fec..1e69ff4137f 100644 --- a/spring-messaging/src/test/java/org/springframework/messaging/converter/MappingJackson2MessageConverterTests.java +++ b/spring-messaging/src/test/java/org/springframework/messaging/converter/MappingJackson2MessageConverterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2020 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. @@ -48,10 +48,9 @@ public class MappingJackson2MessageConverterTests { @Test public void defaultConstructor() { MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter(); - assertThat(converter.getSupportedMimeTypes()) - .contains(new MimeType("application", "json")); + assertThat(converter.getSupportedMimeTypes()).contains(new MimeType("application", "json")); assertThat(converter.getObjectMapper().getDeserializationConfig() - .isEnabled(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)).isFalse(); + .isEnabled(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)).isFalse(); } @Test // SPR-12724 @@ -60,7 +59,7 @@ public class MappingJackson2MessageConverterTests { MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter(mimetype); assertThat(converter.getSupportedMimeTypes()).contains(mimetype); assertThat(converter.getObjectMapper().getDeserializationConfig() - .isEnabled(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)).isFalse(); + .isEnabled(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)).isFalse(); } @Test // SPR-12724 @@ -70,19 +69,14 @@ public class MappingJackson2MessageConverterTests { MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter(jsonMimetype, xmlMimetype); assertThat(converter.getSupportedMimeTypes()).contains(jsonMimetype, xmlMimetype); assertThat(converter.getObjectMapper().getDeserializationConfig() - .isEnabled(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)).isFalse(); + .isEnabled(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)).isFalse(); } @Test public void fromMessage() { MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter(); - String payload = "{" + - "\"bytes\":\"AQI=\"," + - "\"array\":[\"Foo\",\"Bar\"]," + - "\"number\":42," + - "\"string\":\"Foo\"," + - "\"bool\":true," + - "\"fraction\":42.0}"; + 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); @@ -97,8 +91,8 @@ public class MappingJackson2MessageConverterTests { @Test public void fromMessageUntyped() { MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter(); - String payload = "{\"bytes\":\"AQI=\",\"array\":[\"Foo\",\"Bar\"]," - + "\"number\":42,\"string\":\"Foo\",\"bool\":true,\"fraction\":42.0}"; + 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 actual = (HashMap) converter.fromMessage(message, HashMap.class); @@ -111,7 +105,7 @@ public class MappingJackson2MessageConverterTests { assertThat(actual.get("bytes")).isEqualTo("AQI="); } - @Test // gh-22386 + @Test // gh-22386 public void fromMessageMatchingInstance() { MyBean myBean = new MyBean(); MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter(); @@ -137,7 +131,7 @@ public class MappingJackson2MessageConverterTests { assertThat(myBean.getString()).isEqualTo("string"); } - @Test // SPR-16252 + @Test // SPR-16252 public void fromMessageToList() throws Exception { MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter(); String payload = "[1, 2, 3, 4, 5, 6, 7, 8, 9]"; @@ -151,7 +145,7 @@ public class MappingJackson2MessageConverterTests { assertThat(actual).isEqualTo(Arrays.asList(1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L, 9L)); } - @Test // SPR-16486 + @Test // SPR-16486 public void fromMessageToMessageWithPojo() throws Exception { MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter(); String payload = "{\"string\":\"foo\"}"; @@ -198,7 +192,7 @@ public class MappingJackson2MessageConverterTests { String payload = "H\u00e9llo W\u00f6rld"; Message message = converter.toMessage(payload, headers); - assertThat(new String((byte[]) message.getPayload(), StandardCharsets.UTF_16BE)).isEqualTo(("\"" + payload + "\"")); + assertThat(new String((byte[]) message.getPayload(), StandardCharsets.UTF_16BE)).isEqualTo("\"" + payload + "\""); assertThat(message.getHeaders().get(MessageHeaders.CONTENT_TYPE)).isEqualTo(contentType); } @@ -214,7 +208,7 @@ public class MappingJackson2MessageConverterTests { String payload = "H\u00e9llo W\u00f6rld"; Message message = converter.toMessage(payload, headers); - assertThat(message.getPayload()).isEqualTo(("\"" + payload + "\"")); + assertThat(message.getPayload()).isEqualTo("\"" + payload + "\""); assertThat(message.getHeaders().get(MessageHeaders.CONTENT_TYPE)).isEqualTo(contentType); } @@ -254,9 +248,12 @@ public class MappingJackson2MessageConverterTests { public void jsonViewPayload(@JsonView(MyJacksonView2.class) JacksonViewBean payload) { } - void handleList(List payload) {} + void handleList(List payload) { + } + + void handleMessage(Message message) { + } - void handleMessage(Message message) {} public static class MyBean { diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/config/MessageBrokerBeanDefinitionParser.java b/spring-websocket/src/main/java/org/springframework/web/socket/config/MessageBrokerBeanDefinitionParser.java index 31294cb5794..9e9b5b4c8b0 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/config/MessageBrokerBeanDefinitionParser.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/config/MessageBrokerBeanDefinitionParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2020 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. @@ -42,6 +42,8 @@ import org.springframework.lang.Nullable; import org.springframework.messaging.converter.ByteArrayMessageConverter; import org.springframework.messaging.converter.CompositeMessageConverter; import org.springframework.messaging.converter.DefaultContentTypeResolver; +import org.springframework.messaging.converter.GsonMessageConverter; +import org.springframework.messaging.converter.JsonbMessageConverter; import org.springframework.messaging.converter.MappingJackson2MessageConverter; import org.springframework.messaging.converter.StringMessageConverter; import org.springframework.messaging.simp.SimpMessagingTemplate; @@ -113,11 +115,18 @@ class MessageBrokerBeanDefinitionParser implements BeanDefinitionParser { private static final boolean jackson2Present; + private static final boolean gsonPresent; + + private static final boolean jsonbPresent; + private static final boolean javaxValidationPresent; static { ClassLoader classLoader = MessageBrokerBeanDefinitionParser.class.getClassLoader(); - jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader); + jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader) && + ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader); + gsonPresent = ClassUtils.isPresent("com.google.gson.Gson", classLoader); + jsonbPresent = ClassUtils.isPresent("javax.json.bind.Jsonb", classLoader); javaxValidationPresent = ClassUtils.isPresent("javax.validation.Validator", classLoader); } @@ -502,6 +511,12 @@ class MessageBrokerBeanDefinitionParser implements BeanDefinitionParser { jacksonConverterDef.getPropertyValues().add("objectMapper", jacksonFactoryDef); converters.add(jacksonConverterDef); } + else if (gsonPresent) { + converters.add(new RootBeanDefinition(GsonMessageConverter.class)); + } + else if (jsonbPresent) { + converters.add(new RootBeanDefinition(JsonbMessageConverter.class)); + } } ConstructorArgumentValues cargs = new ConstructorArgumentValues(); cargs.addIndexedArgumentValue(0, converters);