diff --git a/spring-web/src/main/java/org/springframework/http/RequestEntity.java b/spring-web/src/main/java/org/springframework/http/RequestEntity.java index 7e9ab88b2fe..aa08a0dae6a 100644 --- a/spring-web/src/main/java/org/springframework/http/RequestEntity.java +++ b/spring-web/src/main/java/org/springframework/http/RequestEntity.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 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.http; +import java.lang.reflect.Type; import java.net.URI; import java.nio.charset.Charset; import java.util.Arrays; @@ -54,6 +55,7 @@ import org.springframework.util.ObjectUtils; * * * @author Arjen Poutsma + * @author Sebastien Deleuze * @since 4.1 * @see #getMethod() * @see #getUrl() @@ -64,6 +66,8 @@ public class RequestEntity extends HttpEntity { private final URI url; + private final Type type; + /** * Constructor with method and URL but without body nor headers. @@ -81,7 +85,19 @@ public class RequestEntity extends HttpEntity { * @param url the URL */ public RequestEntity(T body, HttpMethod method, URI url) { - this(body, null, method, url); + this(body, null, method, url, null); + } + + /** + * Constructor with method, URL, body and type but without headers. + * @param body the body + * @param method the method + * @param url the URL + * @param type the type used for generic type resolution + * @since 4.3 + */ + public RequestEntity(T body, HttpMethod method, URI url, Type type) { + this(body, null, method, url, type); } /** @@ -91,7 +107,7 @@ public class RequestEntity extends HttpEntity { * @param url the URL */ public RequestEntity(MultiValueMap headers, HttpMethod method, URI url) { - this(null, headers, method, url); + this(null, headers, method, url, null); } /** @@ -102,9 +118,23 @@ public class RequestEntity extends HttpEntity { * @param url the URL */ public RequestEntity(T body, MultiValueMap headers, HttpMethod method, URI url) { + this(body, headers, method, url, null); + } + + /** + * Constructor with method, URL, headers, body and type. + * @param body the body + * @param headers the headers + * @param method the method + * @param url the URL + * @param type the type used for generic type resolution + * @since 4.3 + */ + public RequestEntity(T body, MultiValueMap headers, HttpMethod method, URI url, Type type) { super(body, headers); this.method = method; this.url = url; + this.type = type; } @@ -124,6 +154,14 @@ public class RequestEntity extends HttpEntity { return this.url; } + /** + * Return the type of the request's body. + * @return the request's body type + * @since 4.3 + */ + public Type getType() { + return (this.type == null && this.getBody() != null ? this.getBody().getClass() : this.type ); + } @Override public boolean equals(Object other) { @@ -327,6 +365,16 @@ public class RequestEntity extends HttpEntity { * @return the built request entity */ RequestEntity body(T body); + + /** + * Set the body and type of the request entity and build the RequestEntity. + * @param the type of the body + * @param body the body of the request entity + * @param type the type of the body, useful for generic type resolution + * @return the built request entity + * @since 4.3 + */ + RequestEntity body(T body, Type type); } @@ -396,6 +444,11 @@ public class RequestEntity extends HttpEntity { public RequestEntity body(T body) { return new RequestEntity(body, this.headers, this.method, this.url); } + + @Override + public RequestEntity body(T body, Type type) { + return new RequestEntity(body, this.headers, this.method, this.url, type); + } } } diff --git a/spring-web/src/main/java/org/springframework/web/client/RestTemplate.java b/spring-web/src/main/java/org/springframework/web/client/RestTemplate.java index b8391b59e90..85b1d985cd8 100644 --- a/spring-web/src/main/java/org/springframework/web/client/RestTemplate.java +++ b/spring-web/src/main/java/org/springframework/web/client/RestTemplate.java @@ -782,11 +782,34 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat } else { Object requestBody = this.requestEntity.getBody(); - Class requestType = requestBody.getClass(); + Class requestBodyClass = requestBody.getClass(); + Type requestBodyType = (this.requestEntity instanceof RequestEntity ? + ((RequestEntity)this.requestEntity).getType() : requestBodyClass); HttpHeaders requestHeaders = this.requestEntity.getHeaders(); MediaType requestContentType = requestHeaders.getContentType(); for (HttpMessageConverter messageConverter : getMessageConverters()) { - if (messageConverter.canWrite(requestType, requestContentType)) { + if (messageConverter instanceof GenericHttpMessageConverter) { + GenericHttpMessageConverter genericMessageConverter = (GenericHttpMessageConverter) messageConverter; + if (genericMessageConverter.canWrite(requestBodyType, requestBodyClass, requestContentType)) { + if (!requestHeaders.isEmpty()) { + httpRequest.getHeaders().putAll(requestHeaders); + } + if (logger.isDebugEnabled()) { + if (requestContentType != null) { + logger.debug("Writing [" + requestBody + "] as \"" + requestContentType + + "\" using [" + messageConverter + "]"); + } + else { + logger.debug("Writing [" + requestBody + "] using [" + messageConverter + "]"); + } + + } + genericMessageConverter.write( + requestBody, requestBodyType, requestContentType, httpRequest); + return; + } + } + else if (messageConverter.canWrite(requestBodyClass, requestContentType)) { if (!requestHeaders.isEmpty()) { httpRequest.getHeaders().putAll(requestHeaders); } @@ -806,7 +829,7 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat } } String message = "Could not write request: no suitable HttpMessageConverter found for request type [" + - requestType.getName() + "]"; + requestBodyClass.getName() + "]"; if (requestContentType != null) { message += " and content type [" + requestContentType + "]"; } diff --git a/spring-web/src/test/java/org/springframework/http/RequestEntityTests.java b/spring-web/src/test/java/org/springframework/http/RequestEntityTests.java index de235366bad..9b8268d5a3a 100644 --- a/spring-web/src/test/java/org/springframework/http/RequestEntityTests.java +++ b/spring-web/src/test/java/org/springframework/http/RequestEntityTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 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. @@ -19,11 +19,14 @@ package org.springframework.http; import java.net.URI; import java.net.URISyntaxException; import java.nio.charset.Charset; +import java.util.Arrays; import java.util.HashMap; +import java.util.List; import java.util.Map; import org.junit.Test; +import org.springframework.core.ParameterizedTypeReference; import org.springframework.web.util.UriTemplate; import static org.junit.Assert.*; @@ -148,4 +151,14 @@ public class RequestEntityTests { } + @Test // SPR-13154 + public void types() throws URISyntaxException { + URI url = new URI("http://example.com"); + List body = Arrays.asList("foo", "bar"); + ParameterizedTypeReference typeReference = new ParameterizedTypeReference>() {}; + + RequestEntity entity = RequestEntity.post(url).body(body, typeReference.getType()); + assertEquals(typeReference.getType(), entity.getType()); + } + } diff --git a/spring-web/src/test/java/org/springframework/web/client/RestTemplateIntegrationTests.java b/spring-web/src/test/java/org/springframework/web/client/RestTemplateIntegrationTests.java index 149340b6f9e..d5f00fc2548 100644 --- a/spring-web/src/test/java/org/springframework/web/client/RestTemplateIntegrationTests.java +++ b/spring-web/src/test/java/org/springframework/web/client/RestTemplateIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 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. @@ -20,11 +20,16 @@ import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URISyntaxException; import java.nio.charset.Charset; +import java.util.ArrayList; import java.util.EnumSet; +import java.util.List; import java.util.Set; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.annotation.JsonTypeName; import org.junit.Test; +import org.springframework.core.ParameterizedTypeReference; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; import org.springframework.http.HttpEntity; @@ -32,6 +37,7 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; +import org.springframework.http.RequestEntity; import org.springframework.http.ResponseEntity; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.springframework.http.converter.json.MappingJacksonValue; @@ -215,7 +221,7 @@ public class RestTemplateIntegrationTests extends AbstractJettyServerTestCase { bean.setWith2("with"); bean.setWithout("without"); HttpEntity entity = new HttpEntity(bean, entityHeaders); - String s = template.postForObject(baseUrl + "/jsonpost", entity, String.class, "post"); + String s = template.postForObject(baseUrl + "/jsonpost", entity, String.class); assertTrue(s.contains("\"with1\":\"with\"")); assertTrue(s.contains("\"with2\":\"with\"")); assertTrue(s.contains("\"without\":\"without\"")); @@ -229,7 +235,7 @@ public class RestTemplateIntegrationTests extends AbstractJettyServerTestCase { MappingJacksonValue jacksonValue = new MappingJacksonValue(bean); jacksonValue.setSerializationView(MyJacksonView1.class); HttpEntity entity = new HttpEntity(jacksonValue, entityHeaders); - String s = template.postForObject(baseUrl + "/jsonpost", entity, String.class, "post"); + String s = template.postForObject(baseUrl + "/jsonpost", entity, String.class); assertTrue(s.contains("\"with1\":\"with\"")); assertFalse(s.contains("\"with2\":\"with\"")); assertFalse(s.contains("\"without\":\"without\"")); @@ -243,6 +249,21 @@ public class RestTemplateIntegrationTests extends AbstractJettyServerTestCase { assertEquals("Invalid content", helloWorld, s); } + @Test // SPR-13154 + public void jsonPostForObjectWithJacksonTypeInfoList() throws URISyntaxException { + List list = new ArrayList<>(); + list.add(new Foo("foo")); + list.add(new Bar("bar")); + ParameterizedTypeReference typeReference = new ParameterizedTypeReference>() {}; + RequestEntity> entity = RequestEntity + .post(new URI(baseUrl + "/jsonpost")) + .contentType(new MediaType("application", "json", Charset.forName("UTF-8"))) + .body(list, typeReference.getType()); + String content = template.exchange(entity, String.class).getBody(); + assertTrue(content.contains("\"type\":\"foo\"")); + assertTrue(content.contains("\"type\":\"bar\"")); + } + public interface MyJacksonView1 {}; public interface MyJacksonView2 {}; @@ -290,4 +311,47 @@ public class RestTemplateIntegrationTests extends AbstractJettyServerTestCase { } } + @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type") + public static class ParentClass { + + private String parentProperty; + + public ParentClass() { + } + + public ParentClass(String parentProperty) { + this.parentProperty = parentProperty; + } + + public String getParentProperty() { + return parentProperty; + } + + public void setParentProperty(String parentProperty) { + this.parentProperty = parentProperty; + } + } + + @JsonTypeName("foo") + public static class Foo extends ParentClass { + + public Foo() { + } + + public Foo(String parentProperty) { + super(parentProperty); + } + } + + @JsonTypeName("bar") + public static class Bar extends ParentClass { + + public Bar() { + } + + public Bar(String parentProperty) { + super(parentProperty); + } + } + } diff --git a/spring-web/src/test/java/org/springframework/web/client/RestTemplateTests.java b/spring-web/src/test/java/org/springframework/web/client/RestTemplateTests.java index 62ab3cbd36d..5674ad55fe9 100644 --- a/spring-web/src/test/java/org/springframework/web/client/RestTemplateTests.java +++ b/spring-web/src/test/java/org/springframework/web/client/RestTemplateTests.java @@ -699,9 +699,9 @@ public class RestTemplateTests { given(requestFactory.createRequest(new URI("http://example.com"), HttpMethod.POST)).willReturn(this.request); HttpHeaders requestHeaders = new HttpHeaders(); given(this.request.getHeaders()).willReturn(requestHeaders); - given(converter.canWrite(String.class, null)).willReturn(true); + given(converter.canWrite(String.class, String.class, null)).willReturn(true); String requestBody = "Hello World"; - converter.write(requestBody, null, this.request); + converter.write(requestBody, String.class, null, this.request); given(this.request.execute()).willReturn(response); given(errorHandler.hasError(response)).willReturn(false); List expected = Collections.singletonList(42);