From 6dbc828aa1301db30015ad0095be69b3aa0f9879 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Thu, 4 Jan 2018 13:17:34 -0500 Subject: [PATCH] MappingJackson2MessageConverter uses generic type This is a backport of the #583201 minus use of GenericTypeResolver which in 5.0 has been refactored to provide a getJavaType method. Issue: SPR-16252 --- .../MappingJackson2MessageConverter.java | 81 ++++++++++++++++++- .../MappingJackson2MessageConverterTests.java | 21 ++++- 2 files changed, 99 insertions(+), 3 deletions(-) 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 76c4362cca9..b92661bb928 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 @@ -20,7 +20,9 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.StringWriter; import java.io.Writer; +import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; import java.nio.charset.Charset; import java.util.Arrays; import java.util.concurrent.atomic.AtomicReference; @@ -35,8 +37,10 @@ import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.type.TypeFactory; import org.springframework.core.MethodParameter; +import org.springframework.core.ResolvableType; import org.springframework.messaging.Message; import org.springframework.messaging.MessageHeaders; import org.springframework.util.Assert; @@ -202,7 +206,7 @@ public class MappingJackson2MessageConverter extends AbstractMessageConverter { @Override protected Object convertFromInternal(Message message, Class targetClass, Object conversionHint) { - JavaType javaType = this.objectMapper.constructType(targetClass); + JavaType javaType = getJavaType(targetClass, conversionHint); Object payload = message.getPayload(); Class view = getSerializationView(conversionHint); // Note: in the view case, calling withType instead of forType for compatibility with Jackson <2.5 @@ -229,6 +233,81 @@ public class MappingJackson2MessageConverter extends AbstractMessageConverter { } } + private JavaType getJavaType(Class targetClass, Object conversionHint) { + if (conversionHint instanceof MethodParameter) { + MethodParameter param = (MethodParameter) conversionHint; + param = param.nestedIfOptional(); + Type genericParameterType = param.getNestedGenericParameterType(); + Class contextClass = param.getContainingClass(); + Type type = getJavaType(genericParameterType, contextClass); + return this.objectMapper.getTypeFactory().constructType(type); + } + return this.objectMapper.constructType(targetClass); + } + + private JavaType getJavaType(Type type, Class contextClass) { + TypeFactory typeFactory = this.objectMapper.getTypeFactory(); + if (contextClass != null) { + ResolvableType resolvedType = ResolvableType.forType(type); + if (type instanceof TypeVariable) { + ResolvableType resolvedTypeVariable = resolveVariable( + (TypeVariable) type, ResolvableType.forClass(contextClass)); + if (resolvedTypeVariable != ResolvableType.NONE) { + return typeFactory.constructType(resolvedTypeVariable.resolve()); + } + } + else if (type instanceof ParameterizedType && resolvedType.hasUnresolvableGenerics()) { + ParameterizedType parameterizedType = (ParameterizedType) type; + Class[] generics = new Class[parameterizedType.getActualTypeArguments().length]; + Type[] typeArguments = parameterizedType.getActualTypeArguments(); + for (int i = 0; i < typeArguments.length; i++) { + Type typeArgument = typeArguments[i]; + if (typeArgument instanceof TypeVariable) { + ResolvableType resolvedTypeArgument = resolveVariable( + (TypeVariable) typeArgument, ResolvableType.forClass(contextClass)); + if (resolvedTypeArgument != ResolvableType.NONE) { + generics[i] = resolvedTypeArgument.resolve(); + } + else { + generics[i] = ResolvableType.forType(typeArgument).resolve(); + } + } + else { + generics[i] = ResolvableType.forType(typeArgument).resolve(); + } + } + return typeFactory.constructType(ResolvableType. + forClassWithGenerics(resolvedType.getRawClass(), generics).getType()); + } + } + return typeFactory.constructType(type); + } + + private ResolvableType resolveVariable(TypeVariable typeVariable, ResolvableType contextType) { + ResolvableType resolvedType; + if (contextType.hasGenerics()) { + resolvedType = ResolvableType.forType(typeVariable, contextType); + if (resolvedType.resolve() != null) { + return resolvedType; + } + } + + ResolvableType superType = contextType.getSuperType(); + if (superType != ResolvableType.NONE) { + resolvedType = resolveVariable(typeVariable, superType); + if (resolvedType.resolve() != null) { + return resolvedType; + } + } + for (ResolvableType ifc : contextType.getInterfaces()) { + resolvedType = resolveVariable(typeVariable, ifc); + if (resolvedType.resolve() != null) { + return resolvedType; + } + } + return ResolvableType.NONE; + } + @Override protected Object convertToInternal(Object payload, MessageHeaders headers, Object conversionHint) { try { 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 59f18e304cc..da735a64838 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 @@ -19,8 +19,10 @@ package org.springframework.messaging.converter; import java.io.IOException; import java.lang.reflect.Method; import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.HashMap; +import java.util.List; import java.util.Map; import com.fasterxml.jackson.annotation.JsonView; @@ -120,6 +122,20 @@ public class MappingJackson2MessageConverterTests { assertEquals("string", myBean.getString()); } + @Test // SPR-16252 + public void fromMessageToList() throws Exception { + MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter(); + 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); + + assertNotNull(actual); + assertEquals(Arrays.asList(1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L, 9L), actual); + } + @Test public void toMessage() throws Exception { MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter(); @@ -209,8 +225,9 @@ public class MappingJackson2MessageConverterTests { return bean; } - public void jsonViewPayload(@JsonView(MyJacksonView2.class) JacksonViewBean payload) { - } + public void jsonViewPayload(@JsonView(MyJacksonView2.class) JacksonViewBean payload) {} + + void handleList(List payload) {} public static class MyBean {