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);