From 0e9ea2c94d9e2ca136d93e1abbd40d58901eb5e8 Mon Sep 17 00:00:00 2001 From: Arjen Poutsma Date: Wed, 13 Mar 2019 15:30:50 +0100 Subject: [PATCH] Fix review remarks on Servlet.fn This commit incoporates the remarks made during the Servlet.fn review. See gh-21490 --- .../DefaultEntityResponseBuilder.java | 118 ++++++++++-------- .../function/DefaultServerRequest.java | 42 ++++--- .../function/DefaultServerRequestBuilder.java | 27 ++-- .../DefaultServerResponseBuilder.java | 16 +-- .../web/servlet/function/EntityResponse.java | 24 ++-- .../web/servlet/function/ServerResponse.java | 22 ++-- .../DefaultEntityResponseBuilderTests.java | 11 ++ .../function/DefaultServerRequestTests.java | 13 +- .../DefaultServerResponseBuilderTests.java | 28 ++++- 9 files changed, 163 insertions(+), 138 deletions(-) diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/function/DefaultEntityResponseBuilder.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/function/DefaultEntityResponseBuilder.java index 1011a0e2f5..d6acce370f 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/function/DefaultEntityResponseBuilder.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/function/DefaultEntityResponseBuilder.java @@ -17,6 +17,7 @@ package org.springframework.web.servlet.function; import java.io.IOException; +import java.lang.reflect.Type; import java.net.URI; import java.time.Instant; import java.time.ZonedDateTime; @@ -39,12 +40,14 @@ import org.reactivestreams.Publisher; import org.reactivestreams.Subscriber; import org.reactivestreams.Subscription; +import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.CacheControl; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.InvalidMediaTypeException; import org.springframework.http.MediaType; +import org.springframework.http.converter.GenericHttpMessageConverter; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.server.ServletServerHttpResponse; import org.springframework.lang.Nullable; @@ -64,7 +67,7 @@ class DefaultEntityResponseBuilder implements EntityResponse.Builder { private final T entity; - private final BuilderFunction builderFunction; + private final Type entityType; private int status = HttpStatus.OK.value(); @@ -73,9 +76,9 @@ class DefaultEntityResponseBuilder implements EntityResponse.Builder { private final MultiValueMap cookies = new LinkedMultiValueMap<>(); - private DefaultEntityResponseBuilder(T entity, BuilderFunction builderFunction) { + private DefaultEntityResponseBuilder(T entity, @Nullable Type entityType) { this.entity = entity; - this.builderFunction = builderFunction; + this.entityType = (entityType != null) ? entityType : entity.getClass(); } @Override @@ -185,9 +188,23 @@ class DefaultEntityResponseBuilder implements EntityResponse.Builder { return this; } + @SuppressWarnings({"rawtypes", "unchecked"}) @Override public EntityResponse build() { - return this.builderFunction.build(this.status, this.headers, this.cookies, this.entity); + if (this.entity instanceof CompletionStage) { + CompletionStage completionStage = (CompletionStage) this.entity; + return new CompletionStageEntityResponse(this.status, this.headers, this.cookies, + completionStage, this.entityType); + } + else if (this.entity instanceof Publisher) { + Publisher publisher = (Publisher) this.entity; + return new PublisherEntityResponse(this.status, this.headers, this.cookies, publisher, + this.entityType); + } + else { + return new DefaultEntityResponse<>(this.status, this.headers, this.cookies, this.entity, + this.entityType); + } } @@ -195,42 +212,16 @@ class DefaultEntityResponseBuilder implements EntityResponse.Builder { * Return a new {@link EntityResponse.Builder} from the given object. */ public static EntityResponse.Builder fromObject(T t) { - return new DefaultEntityResponseBuilder<>(t, DefaultEntityResponse::new); - } - - /** - * Return a new {@link EntityResponse.Builder} from the given completion stage. - */ - public static EntityResponse.Builder> fromCompletionStage( - CompletionStage completionStage) { - return new DefaultEntityResponseBuilder<>(completionStage, - CompletionStageEntityResponse::new); + return new DefaultEntityResponseBuilder<>(t, null); } /** - * Return a new {@link EntityResponse.Builder} from the given Reactive Streams publisher. + * Return a new {@link EntityResponse.Builder} from the given object and type reference. */ - public static EntityResponse.Builder> fromPublisher(Publisher publisher) { - return new DefaultEntityResponseBuilder<>(publisher, PublisherEntityResponse::new); + public static EntityResponse.Builder fromObject(T t, ParameterizedTypeReference bodyType) { + return new DefaultEntityResponseBuilder<>(t, bodyType.getType()); } - @SuppressWarnings("unchecked") - private static HttpMessageConverter cast(HttpMessageConverter messageConverter) { - return (HttpMessageConverter) messageConverter; - } - - - /** - * Defines contract for building {@link EntityResponse} instances. - */ - private interface BuilderFunction { - - EntityResponse build(int statusCode, HttpHeaders headers, - MultiValueMap cookies, T entity); - - } - - /** * Default {@link EntityResponse} implementation for synchronous bodies. */ @@ -240,12 +231,15 @@ class DefaultEntityResponseBuilder implements EntityResponse.Builder { private final T entity; + private final Type entityType; + public DefaultEntityResponse(int statusCode, HttpHeaders headers, - MultiValueMap cookies, T entity) { + MultiValueMap cookies, T entity, Type entityType) { super(statusCode, headers, cookies); this.entity = entity; + this.entityType = entityType; } @Override @@ -258,11 +252,12 @@ class DefaultEntityResponseBuilder implements EntityResponse.Builder { HttpServletResponse servletResponse, Context context) throws ServletException, IOException { - writeEntityWithMessageConverters(this.entity, servletRequest, servletResponse, context); + writeEntityWithMessageConverters(this.entity, servletRequest,servletResponse, context); return null; } + @SuppressWarnings("unchecked") protected void writeEntityWithMessageConverters(Object entity, HttpServletRequest request, HttpServletResponse response, ServerResponse.Context context) @@ -271,30 +266,39 @@ class DefaultEntityResponseBuilder implements EntityResponse.Builder { ServletServerHttpResponse serverResponse = new ServletServerHttpResponse(response); MediaType contentType = getContentType(response); - Class entityType = entity.getClass(); - - HttpMessageConverter messageConverter = context.messageConverters().stream() - .filter(converter -> converter.canWrite(entityType, contentType)) - .findFirst() - .map(DefaultEntityResponseBuilder::cast) - .orElseThrow(() -> new HttpMediaTypeNotAcceptableException( - producibleMediaTypes(context.messageConverters(), entityType))); + Class entityClass = entity.getClass(); + + for (HttpMessageConverter messageConverter : context.messageConverters()) { + if (messageConverter instanceof GenericHttpMessageConverter) { + GenericHttpMessageConverter genericMessageConverter = + (GenericHttpMessageConverter) messageConverter; + if (genericMessageConverter.canWrite(this.entityType, entityClass, contentType)) { + genericMessageConverter.write(entity, this.entityType, contentType, serverResponse); + return; + } + } + if (messageConverter.canWrite(entityClass, contentType)) { + ((HttpMessageConverter)messageConverter).write(entity, contentType, serverResponse); + return; + } + } - messageConverter.write(entity, contentType, serverResponse); + List producibleMediaTypes = producibleMediaTypes(context.messageConverters(), entityClass); + throw new HttpMediaTypeNotAcceptableException(producibleMediaTypes); } @Nullable - private MediaType getContentType(HttpServletResponse response) { + private static MediaType getContentType(HttpServletResponse response) { try { - return MediaType.parseMediaType(response.getContentType()); + return MediaType.parseMediaType(response.getContentType()).removeQualityValue(); } catch (InvalidMediaTypeException ex) { return null; } } - protected final void tryWriteEntityWithMessageConverters(Object entity, - HttpServletRequest request, HttpServletResponse response, + protected void tryWriteEntityWithMessageConverters(Object entity, + HttpServletRequest request, HttpServletResponse response, ServerResponse.Context context) { try { writeEntityWithMessageConverters(entity, request, response, context); @@ -323,10 +327,10 @@ class DefaultEntityResponseBuilder implements EntityResponse.Builder { private static class CompletionStageEntityResponse extends DefaultEntityResponse> { - public CompletionStageEntityResponse(int statusCode, - HttpHeaders headers, - MultiValueMap cookies, CompletionStage entity) { - super(statusCode, headers, cookies, entity); + public CompletionStageEntityResponse(int statusCode, HttpHeaders headers, + MultiValueMap cookies, CompletionStage entity, + Type entityType) { + super(statusCode, headers, cookies, entity, entityType); } @Override @@ -338,6 +342,7 @@ class DefaultEntityResponseBuilder implements EntityResponse.Builder { entity().whenComplete((entity, throwable) -> { try { if (entity != null) { + tryWriteEntityWithMessageConverters(entity, (HttpServletRequest) asyncContext.getRequest(), (HttpServletResponse) asyncContext.getResponse(), @@ -358,8 +363,9 @@ class DefaultEntityResponseBuilder implements EntityResponse.Builder { private static class PublisherEntityResponse extends DefaultEntityResponse> { public PublisherEntityResponse(int statusCode, HttpHeaders headers, - MultiValueMap cookies, Publisher entity) { - super(statusCode, headers, cookies, entity); + MultiValueMap cookies, Publisher entity, + Type entityType) { + super(statusCode, headers, cookies, entity, entityType); } @Override @@ -425,6 +431,8 @@ class DefaultEntityResponseBuilder implements EntityResponse.Builder { (HttpServletRequest) this.asyncContext.getRequest(), (HttpServletResponse) this.asyncContext.getResponse(), this.context); + + this.asyncContext.complete(); } @Override diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/function/DefaultServerRequest.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/function/DefaultServerRequest.java index 179e4447ef..0803863d1c 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/function/DefaultServerRequest.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/function/DefaultServerRequest.java @@ -17,6 +17,7 @@ package org.springframework.web.servlet.function; import java.io.IOException; +import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.net.InetSocketAddress; import java.net.URI; @@ -48,7 +49,6 @@ import org.springframework.http.MediaType; import org.springframework.http.converter.GenericHttpMessageConverter; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.server.ServletServerHttpRequest; -import org.springframework.lang.Nullable; import org.springframework.util.CollectionUtils; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; @@ -152,35 +152,42 @@ class DefaultServerRequest implements ServerRequest { @Override public T body(ParameterizedTypeReference bodyType) throws IOException, ServletException { Type type = bodyType.getType(); - Class contextClass = null; + return bodyInternal(type, bodyClass(type)); + } + + static Class bodyClass(Type type) { if (type instanceof Class) { - contextClass = (Class) type; + return (Class) type; } - return bodyInternal(type, contextClass); + if (type instanceof ParameterizedType) { + ParameterizedType parameterizedType = (ParameterizedType) type; + if (parameterizedType.getRawType() instanceof Class) { + return (Class) parameterizedType.getRawType(); + } + } + return Object.class; } @SuppressWarnings("unchecked") - private T bodyInternal(Type type, @Nullable Class contextClass) + private T bodyInternal(Type bodyType, Class bodyClass) throws ServletException, IOException { MediaType contentType = this.headers.contentType().orElse(MediaType.APPLICATION_OCTET_STREAM); for (HttpMessageConverter messageConverter : this.messageConverters) { - if (messageConverter instanceof GenericHttpMessageConverter) { + if (messageConverter instanceof GenericHttpMessageConverter) { GenericHttpMessageConverter genericMessageConverter = (GenericHttpMessageConverter) messageConverter; - if (genericMessageConverter.canRead(type, contextClass, contentType)) { - return genericMessageConverter.read(type, contextClass, this.serverHttpRequest); + if (genericMessageConverter.canRead(bodyType, bodyClass, contentType)) { + return genericMessageConverter.read(bodyType, bodyClass, this.serverHttpRequest); } } - else { - if (messageConverter.canRead(contextClass, contentType)) { - HttpMessageConverter theConverter = - (HttpMessageConverter) messageConverter; - Class clazz = (Class) contextClass; - return theConverter.read(clazz, this.serverHttpRequest); - } + if (messageConverter.canRead(bodyClass, contentType)) { + HttpMessageConverter theConverter = + (HttpMessageConverter) messageConverter; + Class clazz = (Class) bodyClass; + return theConverter.read(clazz, this.serverHttpRequest); } } throw new HttpMediaTypeNotSupportedException(contentType, this.allSupportedMediaTypes); @@ -196,6 +203,11 @@ class DefaultServerRequest implements ServerRequest { return this.attributes; } + @Override + public Optional param(String name) { + return Optional.ofNullable(servletRequest().getParameter(name)); + } + @Override public MultiValueMap params() { return this.params; diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/function/DefaultServerRequestBuilder.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/function/DefaultServerRequestBuilder.java index 196fd12f63..31eba479cf 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/function/DefaultServerRequestBuilder.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/function/DefaultServerRequestBuilder.java @@ -46,7 +46,6 @@ import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; import org.springframework.http.converter.GenericHttpMessageConverter; import org.springframework.http.converter.HttpMessageConverter; -import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; @@ -239,35 +238,29 @@ class DefaultServerRequestBuilder implements ServerRequest.Builder { @Override public T body(ParameterizedTypeReference bodyType) throws IOException, ServletException { Type type = bodyType.getType(); - Class contextClass = null; - if (type instanceof Class) { - contextClass = (Class) type; - } - return bodyInternal(type, contextClass); + return bodyInternal(type, DefaultServerRequest.bodyClass(type)); } @SuppressWarnings("unchecked") - private T bodyInternal(Type type, @Nullable Class contextClass) + private T bodyInternal(Type bodyType, Class bodyClass) throws ServletException, IOException { HttpInputMessage inputMessage = new BuiltInputMessage(); MediaType contentType = headers().contentType().orElse(MediaType.APPLICATION_OCTET_STREAM); for (HttpMessageConverter messageConverter : this.messageConverters) { - if (messageConverter instanceof GenericHttpMessageConverter) { + if (messageConverter instanceof GenericHttpMessageConverter) { GenericHttpMessageConverter genericMessageConverter = (GenericHttpMessageConverter) messageConverter; - if (genericMessageConverter.canRead(type, contextClass, contentType)) { - return genericMessageConverter.read(type, contextClass, inputMessage); + if (genericMessageConverter.canRead(bodyType, bodyClass, contentType)) { + return genericMessageConverter.read(bodyType, bodyClass, inputMessage); } } - else { - if (messageConverter.canRead(contextClass, contentType)) { - HttpMessageConverter theConverter = - (HttpMessageConverter) messageConverter; - Class clazz = (Class) contextClass; - return theConverter.read(clazz, inputMessage); - } + if (messageConverter.canRead(bodyClass, contentType)) { + HttpMessageConverter theConverter = + (HttpMessageConverter) messageConverter; + Class clazz = (Class) bodyClass; + return theConverter.read(clazz, inputMessage); } } throw new HttpMediaTypeNotSupportedException(contentType, Collections.emptyList()); diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/function/DefaultServerResponseBuilder.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/function/DefaultServerResponseBuilder.java index fae04122c7..1c40459443 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/function/DefaultServerResponseBuilder.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/function/DefaultServerResponseBuilder.java @@ -29,7 +29,6 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.concurrent.CompletionStage; import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.function.Predicate; @@ -38,8 +37,7 @@ import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import org.reactivestreams.Publisher; - +import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.CacheControl; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; @@ -196,16 +194,8 @@ class DefaultServerResponseBuilder implements ServerResponse.BodyBuilder { } @Override - public ServerResponse asyncBody(CompletionStage asyncBody) { - return DefaultEntityResponseBuilder.fromCompletionStage(asyncBody) - .headers(this.headers) - .status(this.statusCode) - .build(); - } - - @Override - public ServerResponse asyncBody(Publisher futureBody) { - return DefaultEntityResponseBuilder.fromPublisher(futureBody) + public ServerResponse body(T body, ParameterizedTypeReference bodyType) { + return DefaultEntityResponseBuilder.fromObject(body, bodyType) .headers(this.headers) .status(this.statusCode) .build(); diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/function/EntityResponse.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/function/EntityResponse.java index 87339a5831..7e859a1dd1 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/function/EntityResponse.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/function/EntityResponse.java @@ -20,12 +20,10 @@ import java.net.URI; import java.time.Instant; import java.time.ZonedDateTime; import java.util.Set; -import java.util.concurrent.CompletionStage; import java.util.function.Consumer; import javax.servlet.http.Cookie; -import org.reactivestreams.Publisher; - +import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.CacheControl; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; @@ -61,24 +59,16 @@ public interface EntityResponse extends ServerResponse { } /** - * Create a builder for an asynchronous body supplied by the given {@link CompletionStage}. - * @param completionStage the supplier of the response body - * @param the type of the elements contained in the publisher + * Create a builder with the given object and type reference. + * @param t the object that represents the body of the response + * @param entityType the type of the entity, used to capture the generic type + * @param the type of element contained in the publisher * @return the created builder */ - static Builder> fromCompletionStage(CompletionStage completionStage) { - return DefaultEntityResponseBuilder.fromCompletionStage(completionStage); + static Builder fromObject(T t, ParameterizedTypeReference entityType) { + return DefaultEntityResponseBuilder.fromObject(t, entityType); } - /** - * Create a builder for an asynchronous body supplied by the given {@link Publisher}. - * @param publisher the supplier of the response body - * @param the type of the elements contained in the publisher - * @return the created builder - */ - static Builder> fromPublisher(Publisher publisher) { - return DefaultEntityResponseBuilder.fromPublisher(publisher); - } /** * Defines a builder for {@code EntityResponse}. diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/function/ServerResponse.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/function/ServerResponse.java index 20a5cd8bf7..f931698110 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/function/ServerResponse.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/function/ServerResponse.java @@ -34,6 +34,7 @@ import javax.servlet.http.HttpServletResponse; import org.reactivestreams.Publisher; +import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.CacheControl; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; @@ -362,26 +363,23 @@ public interface ServerResponse { /** * Set the body of the response to the given {@code Object} and return it. + * + *

Asynchronous response bodies are supported by providing a {@link CompletionStage} or + * {@link Publisher} as body. * @param body the body of the response * @return the built response */ ServerResponse body(Object body); /** - * Set the asynchronous body of the response to the given {@link CompletionStage} and - * return it. - * @param asyncBody the body of the response - * @return the built response - */ - ServerResponse asyncBody(CompletionStage asyncBody); - - /** - * Set the asynchronous body of the response to the given {@link Publisher} and - * return it. - * @param asyncBody the body of the response + * Set the body of the response to the given {@code Object} and return it. The parameter + * {@code bodyType} is used to capture the generic type. + * + * @param body the body of the response + * @param bodyType the type of the body, used to capture the generic type * @return the built response */ - ServerResponse asyncBody(Publisher asyncBody); + ServerResponse body(T body, ParameterizedTypeReference bodyType); /** * Render the template with the given {@code name} using the given {@code modelAttributes}. diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/function/DefaultEntityResponseBuilderTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/function/DefaultEntityResponseBuilderTests.java index 130c0c5014..5134f79a13 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/function/DefaultEntityResponseBuilderTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/function/DefaultEntityResponseBuilderTests.java @@ -29,6 +29,7 @@ import javax.servlet.http.Cookie; import org.junit.Test; +import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.CacheControl; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; @@ -63,6 +64,16 @@ public class DefaultEntityResponseBuilderTests { assertSame(body, response.entity()); } + @Test + public void fromObjectTypeReference() { + String body = "foo"; + EntityResponse response = EntityResponse.fromObject(body, + new ParameterizedTypeReference() {}) + .build(); + + assertSame(body, response.entity()); + } + @Test public void status() { String body = "foo"; diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/function/DefaultServerRequestTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/function/DefaultServerRequestTests.java index 04cdc228a5..fbbf1cce2e 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/function/DefaultServerRequestTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/function/DefaultServerRequestTests.java @@ -36,6 +36,7 @@ import org.springframework.http.HttpRange; import org.springframework.http.MediaType; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.StringHttpMessageConverter; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.mock.web.test.MockHttpServletRequest; import org.springframework.mock.web.test.MockHttpSession; import org.springframework.util.LinkedMultiValueMap; @@ -240,14 +241,16 @@ public class DefaultServerRequestTests { @Test public void bodyParameterizedTypeReference() throws Exception { MockHttpServletRequest servletRequest = new MockHttpServletRequest("GET", "/"); - servletRequest.setContentType(MediaType.TEXT_PLAIN_VALUE); - servletRequest.setContent("foo".getBytes(UTF_8)); + servletRequest.setContentType(MediaType.APPLICATION_JSON_VALUE); + servletRequest.setContent("[\"foo\",\"bar\"]".getBytes(UTF_8)); DefaultServerRequest request = new DefaultServerRequest(servletRequest, - this.messageConverters); + Collections.singletonList(new MappingJackson2HttpMessageConverter())); - String result = request.body(new ParameterizedTypeReference() {}); - assertEquals("foo", result); + List result = request.body(new ParameterizedTypeReference>() {}); + assertEquals(2, result.size()); + assertEquals("foo", result.get(0)); + assertEquals("bar", result.get(1)); } @Test(expected = HttpMediaTypeNotSupportedException.class) diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/function/DefaultServerResponseBuilderTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/function/DefaultServerResponseBuilderTests.java index a388b61169..e00595a21d 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/function/DefaultServerResponseBuilderTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/function/DefaultServerResponseBuilderTests.java @@ -20,6 +20,7 @@ import java.net.URI; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.time.temporal.ChronoUnit; +import java.util.ArrayList; import java.util.Collections; import java.util.EnumSet; import java.util.List; @@ -31,6 +32,7 @@ import org.junit.Test; import org.reactivestreams.Publisher; import reactor.core.publisher.Mono; +import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.CacheControl; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; @@ -38,6 +40,7 @@ import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.StringHttpMessageConverter; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.mock.web.test.MockHttpServletRequest; import org.springframework.mock.web.test.MockHttpServletResponse; import org.springframework.util.LinkedMultiValueMap; @@ -288,10 +291,27 @@ public class DefaultServerResponseBuilderTests { } @Test - public void asyncBodyCompletionStage() throws Exception { + public void bodyWithParameterizedTypeReference() throws Exception { + List body = new ArrayList<>(); + body.add("foo"); + body.add("bar"); + ServerResponse response = ServerResponse.ok().body(body, new ParameterizedTypeReference>() {}); + + MockHttpServletRequest mockRequest = new MockHttpServletRequest("GET", "http://example.com"); + MockHttpServletResponse mockResponse = new MockHttpServletResponse(); + ServerResponse.Context context = () -> Collections.singletonList(new MappingJackson2HttpMessageConverter()); + + ModelAndView mav = response.writeTo(mockRequest, mockResponse, context); + assertNull(mav); + + assertEquals("[\"foo\",\"bar\"]", mockResponse.getContentAsString()); + } + + @Test + public void bodyCompletionStage() throws Exception { String body = "foo"; CompletionStage completionStage = CompletableFuture.completedFuture(body); - ServerResponse response = ServerResponse.ok().asyncBody(completionStage); + ServerResponse response = ServerResponse.ok().body(completionStage); MockHttpServletRequest mockRequest = new MockHttpServletRequest("GET", "http://example.com"); MockHttpServletResponse mockResponse = new MockHttpServletResponse(); @@ -307,10 +327,10 @@ public class DefaultServerResponseBuilderTests { } @Test - public void asyncBodyPublisher() throws Exception { + public void bodyPublisher() throws Exception { String body = "foo"; Publisher publisher = Mono.just(body); - ServerResponse response = ServerResponse.ok().asyncBody(publisher); + ServerResponse response = ServerResponse.ok().body(publisher); MockHttpServletRequest mockRequest = new MockHttpServletRequest("GET", "http://example.com"); MockHttpServletResponse mockResponse = new MockHttpServletResponse(); -- GitLab