diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/KotlinRequestBodyAdvice.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/KotlinRequestBodyAdvice.java index 0bcaa562034..fdd0f5e29c2 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/KotlinRequestBodyAdvice.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/KotlinRequestBodyAdvice.java @@ -28,6 +28,7 @@ import kotlin.reflect.jvm.ReflectJvmMapping; import org.jspecify.annotations.Nullable; import org.springframework.core.MethodParameter; +import org.springframework.http.HttpEntity; import org.springframework.http.converter.AbstractKotlinSerializationHttpMessageConverter; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.SmartHttpMessageConverter; @@ -61,6 +62,10 @@ public class KotlinRequestBodyAdvice extends RequestBodyAdviceAdapter { for (KParameter p : Objects.requireNonNull(function).getParameters()) { if (KParameter.Kind.VALUE.equals(p.getKind())) { if (index == i++) { + if (HttpEntity.class.isAssignableFrom(parameter.getParameterType())) { + return Collections.singletonMap(KType.class.getName(), + Objects.requireNonNull(p.getType().getArguments().get(0).getType())); + } return Collections.singletonMap(KType.class.getName(), p.getType()); } } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/KotlinResponseBodyAdvice.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/KotlinResponseBodyAdvice.java index fb527bc1f85..760c9dcdc9e 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/KotlinResponseBodyAdvice.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/KotlinResponseBodyAdvice.java @@ -26,6 +26,7 @@ import kotlin.reflect.jvm.ReflectJvmMapping; import org.jspecify.annotations.Nullable; import org.springframework.core.MethodParameter; +import org.springframework.http.HttpEntity; import org.springframework.http.MediaType; import org.springframework.http.converter.AbstractKotlinSerializationHttpMessageConverter; import org.springframework.http.converter.HttpMessageConverter; @@ -61,6 +62,9 @@ public class KotlinResponseBodyAdvice implements ResponseBodyAdvice { KFunction function = ReflectJvmMapping.getKotlinFunction(Objects.requireNonNull(returnType.getMethod())); KType type = Objects.requireNonNull(function).getReturnType(); + if (HttpEntity.class.isAssignableFrom(returnType.getParameterType())) { + return Collections.singletonMap(KType.class.getName(), Objects.requireNonNull(type.getArguments().get(0).getType())); + } return Collections.singletonMap(KType.class.getName(), type); } diff --git a/spring-webmvc/src/test/kotlin/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyMethodProcessorKotlinTests.kt b/spring-webmvc/src/test/kotlin/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyMethodProcessorKotlinTests.kt index 6e87030c251..d81e97844ba 100644 --- a/spring-webmvc/src/test/kotlin/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyMethodProcessorKotlinTests.kt +++ b/spring-webmvc/src/test/kotlin/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyMethodProcessorKotlinTests.kt @@ -20,6 +20,8 @@ import kotlinx.serialization.Serializable import org.assertj.core.api.Assertions import org.junit.jupiter.api.Test import org.springframework.core.MethodParameter +import org.springframework.http.RequestEntity +import org.springframework.http.ResponseEntity import org.springframework.http.converter.StringHttpMessageConverter import org.springframework.http.converter.json.KotlinSerializationJsonHttpMessageConverter import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean @@ -68,6 +70,22 @@ class RequestResponseBodyMethodProcessorKotlinTests { .contains("\"value\":\"foo\"") } + @Test + fun writeEntityWithKotlinSerializationJsonMessageConverter() { + val method = SampleController::writeMessageEntity::javaMethod.get()!! + val handlerMethod = HandlerMethod(SampleController(), method) + val methodReturnType = handlerMethod.returnType + + val converters = listOf(KotlinSerializationJsonHttpMessageConverter()) + val processor = RequestResponseBodyMethodProcessor(converters, null, listOf(KotlinResponseBodyAdvice())) + + val returnValue: Any? = SampleController().writeMessageEntity().body + processor.handleReturnValue(returnValue, methodReturnType, this.container, this.request) + + Assertions.assertThat(this.servletResponse.contentAsString) + .contains("\"value\":\"foo\"") + } + @Test fun writeGenericTypeWithKotlinSerializationJsonMessageConverter() { val method = SampleController::writeMessages::javaMethod.get()!! @@ -118,6 +136,24 @@ class RequestResponseBodyMethodProcessorKotlinTests { Assertions.assertThat(result).isEqualTo(Message("foo")) } + @Test + @Suppress("UNCHECKED_CAST") + fun readEntityWithKotlinSerializationJsonMessageConverter() { + val content = "{\"value\" : \"foo\"}" + this.servletRequest.setContent(content.toByteArray(StandardCharsets.UTF_8)) + this.servletRequest.setContentType("application/json") + + val converters = listOf(StringHttpMessageConverter(), KotlinSerializationJsonHttpMessageConverter()) + val processor = RequestResponseBodyMethodProcessor(converters, null, listOf(KotlinRequestBodyAdvice())) + + val method = SampleController::readMessageEntity::javaMethod.get()!! + val methodParameter = MethodParameter(method, 0) + + val result = processor.resolveArgument(methodParameter, container, request, factory) as Message + + Assertions.assertThat(result).isEqualTo(Message("foo")) + } + @Suppress("UNCHECKED_CAST") @Test fun readGenericTypeWithKotlinSerializationJsonMessageConverter() { @@ -161,6 +197,10 @@ class RequestResponseBodyMethodProcessorKotlinTests { @ResponseBody fun writeMessage() = Message("foo") + @RequestMapping + @ResponseBody + fun writeMessageEntity() = ResponseEntity.ok(Message("foo")) + @RequestMapping @ResponseBody fun writeMessages() = listOf(Message("foo"), Message("bar")) @@ -169,6 +209,10 @@ class RequestResponseBodyMethodProcessorKotlinTests { @ResponseBody fun readMessage(message: Message) = message.value + @RequestMapping + @ResponseBody + fun readMessageEntity(entity: RequestEntity) = entity.body!!.value + @RequestMapping @ResponseBody fun readMessages(messages: List) = messages.map { it.value }.reduce { acc, string -> "$acc $string" }