diff --git a/org.springframework.web/src/main/java/org/springframework/http/HttpEntity.java b/org.springframework.web/src/main/java/org/springframework/http/HttpEntity.java new file mode 100644 index 0000000000000000000000000000000000000000..2baf302a74aa8d8928158960869077f24e576b72 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/http/HttpEntity.java @@ -0,0 +1,168 @@ +/* + * Copyright 2002-2010 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 + * + * http://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.http; + +import java.util.Map; + +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; + +/** + * Represents an HTTP request or response entity, consisting of headers and body. + * + *

Typically used in combination with the {@link org.springframework.web.client.RestTemplate RestTemplate}, like so: + *

+ * HttpEntity<String> entity = new HttpEntity<String>(helloWorld, MediaType.TEXT_PLAIN);
+ * URI location = template.postForLocation("http://example.com", entity);
+ * 
+ * or + *
+ * HttpEntity<String> entity = template.getForEntity("http://example.com", String.class);
+ * String body = entity.getBody();
+ * MediaType contentType = entity.getHeaders().getContentType();
+ * 
+ * + * @author Arjen Poutsma + * @see org.springframework.web.client.RestTemplate + * @see #getBody() + * @see #getHeaders() + * @since 3.0.2 + */ +public class HttpEntity { + + /** + * The empty {@code HttpEntity}, with no body or headers. + */ + public static final HttpEntity EMPTY = new HttpEntity(); + + private final HttpHeaders headers; + + private final T body; + + /** + * Create a new, empty {@code HttpEntity}. + */ + private HttpEntity() { + this(null, (MultiValueMap) null); + } + + /** + * Create a new {@code HttpEntity} with the given body and no headers. + * + * @param body the entity body + */ + public HttpEntity(T body) { + this(body, (MultiValueMap) null); + } + + /** + * Create a new {@code HttpEntity} with the given headers and no body. + * + * @param headers the entity headers + */ + public HttpEntity(Map headers) { + this(null, toMultiValueMap(headers)); + } + + /** + * Create a new {@code HttpEntity} with the given headers and no body. + * + * @param headers the entity headers + */ + public HttpEntity(MultiValueMap headers) { + this(null, headers); + } + + /** + * Create a new {@code HttpEntity} with the given body and {@code Content-Type} header value. + * + * @param body the entity body + * @param contentType the value of the {@code Content-Type header} + */ + public HttpEntity(T body, MediaType contentType) { + this(body, toMultiValueMap(contentType)); + } + + /** + * Create a new {@code HttpEntity} with the given body and headers. + * + * @param body the entity body + * @param headers the entity headers + */ + public HttpEntity(T body, Map headers) { + this(body, toMultiValueMap(headers)); + } + + /** + * Create a new {@code HttpEntity} with the given body and headers. + * + * @param body the entity body + * @param headers the entity headers + */ + public HttpEntity(T body, MultiValueMap headers) { + this.body = body; + HttpHeaders tempHeaders = new HttpHeaders(); + if (headers != null) { + tempHeaders.putAll(headers); + } + this.headers = HttpHeaders.readOnlyHttpHeaders(tempHeaders); + } + + private static MultiValueMap toMultiValueMap(Map map) { + if (map == null) { + return null; + } + else { + MultiValueMap result = new LinkedMultiValueMap(map.size()); + result.setAll(map); + return result; + } + } + + private static MultiValueMap toMultiValueMap(MediaType contentType) { + if (contentType == null) { + return null; + } + else { + HttpHeaders result = new HttpHeaders(); + result.setContentType(contentType); + return result; + } + } + + /** + * Returns the headers of this entity. + */ + public HttpHeaders getHeaders() { + return headers; + } + + /** + * Returns the body of this entity. + */ + public T getBody() { + return body; + } + + /** + * Indicates whether this entity has a body. + */ + public boolean hasBody() { + return body != null; + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/http/converter/AbstractHttpMessageConverter.java b/org.springframework.web/src/main/java/org/springframework/http/converter/AbstractHttpMessageConverter.java index 5b303a2a5163333d283ab08749f1d03d81898cba..4264803c6c132d40f433e1699c6246fd44e9f904 100644 --- a/org.springframework.web/src/main/java/org/springframework/http/converter/AbstractHttpMessageConverter.java +++ b/org.springframework.web/src/main/java/org/springframework/http/converter/AbstractHttpMessageConverter.java @@ -162,15 +162,19 @@ public abstract class AbstractHttpMessageConverter implements HttpMessageConv throws IOException, HttpMessageNotWritableException { HttpHeaders headers = outputMessage.getHeaders(); - if (contentType == null || contentType.isWildcardType() || contentType.isWildcardSubtype()) { - contentType = getDefaultContentType(t); - } - if (contentType != null) { - headers.setContentType(contentType); + if (headers.getContentType() == null) { + if (contentType == null || contentType.isWildcardType() || contentType.isWildcardSubtype()) { + contentType = getDefaultContentType(t); + } + if (contentType != null) { + headers.setContentType(contentType); + } } - Long contentLength = getContentLength(t, contentType); - if (contentLength != null) { - headers.setContentLength(contentLength); + if (headers.getContentLength() == -1) { + Long contentLength = getContentLength(t, contentType); + if (contentLength != null) { + headers.setContentLength(contentLength); + } } writeInternal(t, outputMessage); outputMessage.getBody().flush(); diff --git a/org.springframework.web/src/main/java/org/springframework/http/converter/FormHttpMessageConverter.java b/org.springframework.web/src/main/java/org/springframework/http/converter/FormHttpMessageConverter.java index 9a79c5c5b2f9b29df57a1d43739adf5d5a2b666b..a541e422918d4170d61cee636c369bdeeaca9198 100644 --- a/org.springframework.web/src/main/java/org/springframework/http/converter/FormHttpMessageConverter.java +++ b/org.springframework.web/src/main/java/org/springframework/http/converter/FormHttpMessageConverter.java @@ -33,6 +33,7 @@ import java.util.Map; import java.util.Random; import org.springframework.core.io.Resource; +import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpInputMessage; import org.springframework.http.HttpOutputMessage; @@ -241,7 +242,8 @@ public class FormHttpMessageConverter implements HttpMessageConverter partType = part.getClass(); + private HttpEntity getEntity(Object part) { + if (part instanceof HttpEntity) { + return (HttpEntity) part; + } + else { + return new HttpEntity(part); + } + } + + @SuppressWarnings("unchecked") + private void writePart(String name, HttpEntity partEntity, OutputStream os) throws IOException { + Object partBody = partEntity.getBody(); + Class partType = partBody.getClass(); + HttpHeaders partHeaders = partEntity.getHeaders(); + MediaType partContentType = partHeaders.getContentType(); for (HttpMessageConverter messageConverter : partConverters) { - if (messageConverter.canWrite(partType, null)) { + if (messageConverter.canWrite(partType, partContentType)) { HttpOutputMessage multipartOutputMessage = new MultipartHttpOutputMessage(os); - multipartOutputMessage.getHeaders().setContentDispositionFormData(name, getFilename(part)); - messageConverter.write(part, null, multipartOutputMessage); + multipartOutputMessage.getHeaders().setContentDispositionFormData(name, getFilename(partBody)); + if (!partHeaders.isEmpty()) { + multipartOutputMessage.getHeaders().putAll(partHeaders); + } + messageConverter.write(partBody, partContentType, multipartOutputMessage); return; } } diff --git a/org.springframework.web/src/main/java/org/springframework/web/client/RestOperations.java b/org.springframework.web/src/main/java/org/springframework/web/client/RestOperations.java index 656e8d6c32e360d20dade90928c53789e18e87ee..4107fec77bd8a7781017588a764cf203d95bc990 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/client/RestOperations.java +++ b/org.springframework.web/src/main/java/org/springframework/web/client/RestOperations.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 the original author or authors. + * Copyright 2002-2010 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,6 +20,7 @@ import java.net.URI; import java.util.Map; import java.util.Set; +import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; @@ -68,6 +69,40 @@ public interface RestOperations { */ T getForObject(URI url, Class responseType) throws RestClientException; + /** + * Retrieve an entity by doing a GET on the specified URL. + * The response is converted and stored in an {@link HttpEntity}. + *

URI Template variables are expanded using the given URI variables, if any. + * @param url the URL + * @param responseType the type of the return value + * @param uriVariables the variables to expand the template + * @return the entity + * @since 3.0.2 + */ + HttpEntity getForEntity(String url, Class responseType, Object... uriVariables) throws RestClientException; + + /** + * Retrieve a representation by doing a GET on the URI template. + * The response is converted and stored in an {@link HttpEntity}. + *

URI Template variables are expanded using the given map. + * @param url the URL + * @param responseType the type of the return value + * @param uriVariables the map containing variables for the URI template + * @return the converted object + * @since 3.0.2 + */ + HttpEntity getForEntity(String url, Class responseType, Map uriVariables) throws RestClientException; + + /** + * Retrieve a representation by doing a GET on the URL . + * The response is converted and stored in an {@link HttpEntity}. + * @param url the URL + * @param responseType the type of the return value + * @return the converted object + * @since 3.0.2 + */ + HttpEntity getForEntity(URI url, Class responseType) throws RestClientException; + // HEAD /** @@ -101,10 +136,13 @@ public interface RestOperations { * Create a new resource by POSTing the given object to the URI template, and returns the value of the * Location header. This header typically indicates where the new resource is stored. *

URI Template variables are expanded using the given URI variables, if any. + *

The {@code request} parameter can be a {@link HttpEntity} in order to + * add additional HTTP headers to the request. * @param url the URL * @param request the Object to be POSTed, may be null * @param uriVariables the variables to expand the template * @return the value for the Location header + * @see HttpEntity */ URI postForLocation(String url, Object request, Object... uriVariables) throws RestClientException; @@ -112,19 +150,25 @@ public interface RestOperations { * Create a new resource by POSTing the given object to the URI template, and returns the value of the * Location header. This header typically indicates where the new resource is stored. *

URI Template variables are expanded using the given map. + *

The {@code request} parameter can be a {@link HttpEntity} in order to + * add additional HTTP headers to the request. * @param url the URL * @param request the Object to be POSTed, may be null * @param uriVariables the variables to expand the template * @return the value for the Location header + * @see HttpEntity */ URI postForLocation(String url, Object request, Map uriVariables) throws RestClientException; /** * Create a new resource by POSTing the given object to the URL, and returns the value of the * Location header. This header typically indicates where the new resource is stored. + *

The {@code request} parameter can be a {@link HttpEntity} in order to + * add additional HTTP headers to the request. * @param url the URL * @param request the Object to be POSTed, may be null * @return the value for the Location header + * @see HttpEntity */ URI postForLocation(URI url, Object request) throws RestClientException; @@ -132,10 +176,13 @@ public interface RestOperations { * Create a new resource by POSTing the given object to the URI template, * and returns the representation found in the response. *

URI Template variables are expanded using the given URI variables, if any. + *

The {@code request} parameter can be a {@link HttpEntity} in order to + * add additional HTTP headers to the request. * @param url the URL * @param request the Object to be POSTed, may be null * @param uriVariables the variables to expand the template * @return the converted object + * @see HttpEntity */ T postForObject(String url, Object request, Class responseType, Object... uriVariables) throws RestClientException; @@ -144,10 +191,13 @@ public interface RestOperations { * Create a new resource by POSTing the given object to the URI template, * and returns the representation found in the response. *

URI Template variables are expanded using the given map. + *

The {@code request} parameter can be a {@link HttpEntity} in order to + * add additional HTTP headers to the request. * @param url the URL * @param request the Object to be POSTed, may be null * @param uriVariables the variables to expand the template * @return the converted object + * @see HttpEntity */ T postForObject(String url, Object request, Class responseType, Map uriVariables) throws RestClientException; @@ -155,36 +205,93 @@ public interface RestOperations { /** * Create a new resource by POSTing the given object to the URL, * and returns the representation found in the response. + *

The {@code request} parameter can be a {@link HttpEntity} in order to + * add additional HTTP headers to the request. * @param url the URL * @param request the Object to be POSTed, may be null * @return the converted object + * @see HttpEntity */ T postForObject(URI url, Object request, Class responseType) throws RestClientException; + /** + * Create a new resource by POSTing the given object to the URI template, + * and returns the response as {@link HttpEntity}. + *

URI Template variables are expanded using the given URI variables, if any. + *

The {@code request} parameter can be a {@link HttpEntity} in order to + * add additional HTTP headers to the request. + * @param url the URL + * @param request the Object to be POSTed, may be null + * @param uriVariables the variables to expand the template + * @return the converted object + * @see HttpEntity + * @since 3.0.2 + */ + HttpEntity postForEntity(String url, Object request, Class responseType, Object... uriVariables) + throws RestClientException; + + /** + * Create a new resource by POSTing the given object to the URI template, + * and returns the response as {@link HttpEntity}. + *

URI Template variables are expanded using the given map. + *

The {@code request} parameter can be a {@link HttpEntity} in order to + * add additional HTTP headers to the request. + * @param url the URL + * @param request the Object to be POSTed, may be null + * @param uriVariables the variables to expand the template + * @return the converted object + * @see HttpEntity + * @since 3.0.2 + */ + HttpEntity postForEntity(String url, Object request, Class responseType, Map uriVariables) + throws RestClientException; + + /** + * Create a new resource by POSTing the given object to the URL, + * and returns the response as {@link HttpEntity}. + *

The {@code request} parameter can be a {@link HttpEntity} in order to + * add additional HTTP headers to the request. + * @param url the URL + * @param request the Object to be POSTed, may be null + * @return the converted object + * @see HttpEntity + * @since 3.0.2 + */ + HttpEntity postForEntity(URI url, Object request, Class responseType) throws RestClientException; + // PUT /** * Create or update a resource by PUTting the given object to the URI. *

URI Template variables are expanded using the given URI variables, if any. + *

The {@code request} parameter can be a {@link HttpEntity} in order to + * add additional HTTP headers to the request. * @param url the URL * @param request the Object to be PUT, may be null * @param uriVariables the variables to expand the template + * @see HttpEntity */ void put(String url, Object request, Object... uriVariables) throws RestClientException; /** * Creates a new resource by PUTting the given object to URI template. *

URI Template variables are expanded using the given map. + *

The {@code request} parameter can be a {@link HttpEntity} in order to + * add additional HTTP headers to the request. * @param url the URL * @param request the Object to be PUT, may be null * @param uriVariables the variables to expand the template + * @see HttpEntity */ void put(String url, Object request, Map uriVariables) throws RestClientException; /** * Creates a new resource by PUTting the given object to URL. + *

The {@code request} parameter can be a {@link HttpEntity} in order to + * add additional HTTP headers to the request. * @param url the URL * @param request the Object to be PUT, may be null + * @see HttpEntity */ void put(URI url, Object request) throws RestClientException; diff --git a/org.springframework.web/src/main/java/org/springframework/web/client/RestTemplate.java b/org.springframework.web/src/main/java/org/springframework/web/client/RestTemplate.java index 72342e1aebd815425d3950ccb63e537133c94775..cc9ec8ad7b694ce395f9ca2cd15c4f4b60c08a66 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/client/RestTemplate.java +++ b/org.springframework.web/src/main/java/org/springframework/web/client/RestTemplate.java @@ -25,6 +25,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; @@ -184,26 +185,49 @@ public class RestTemplate extends HttpAccessor implements RestOperations { // GET public T getForObject(String url, Class responseType, Object... urlVariables) throws RestClientException { - AcceptHeaderRequestCallback requestCallback = new AcceptHeaderRequestCallback(responseType); + AcceptHeaderRequestCallback requestCallback = new AcceptHeaderRequestCallback(responseType); HttpMessageConverterExtractor responseExtractor = new HttpMessageConverterExtractor(responseType, getMessageConverters()); return execute(url, HttpMethod.GET, requestCallback, responseExtractor, urlVariables); } public T getForObject(String url, Class responseType, Map urlVariables) throws RestClientException { - AcceptHeaderRequestCallback requestCallback = new AcceptHeaderRequestCallback(responseType); + AcceptHeaderRequestCallback requestCallback = new AcceptHeaderRequestCallback(responseType); HttpMessageConverterExtractor responseExtractor = new HttpMessageConverterExtractor(responseType, getMessageConverters()); return execute(url, HttpMethod.GET, requestCallback, responseExtractor, urlVariables); } public T getForObject(URI url, Class responseType) throws RestClientException { - AcceptHeaderRequestCallback requestCallback = new AcceptHeaderRequestCallback(responseType); + AcceptHeaderRequestCallback requestCallback = new AcceptHeaderRequestCallback(responseType); HttpMessageConverterExtractor responseExtractor = new HttpMessageConverterExtractor(responseType, getMessageConverters()); return execute(url, HttpMethod.GET, requestCallback, responseExtractor); } + public HttpEntity getForEntity(String url, Class responseType, Object... urlVariables) + throws RestClientException { + AcceptHeaderRequestCallback requestCallback = new AcceptHeaderRequestCallback(responseType); + HttpEntityResponseExtractor responseExtractor = + new HttpEntityResponseExtractor(responseType); + return execute(url, HttpMethod.GET, requestCallback, responseExtractor, urlVariables); + } + + public HttpEntity getForEntity(String url, Class responseType, Map urlVariables) + throws RestClientException { + AcceptHeaderRequestCallback requestCallback = new AcceptHeaderRequestCallback(responseType); + HttpEntityResponseExtractor responseExtractor = + new HttpEntityResponseExtractor(responseType); + return execute(url, HttpMethod.GET, requestCallback, responseExtractor, urlVariables); + } + + public HttpEntity getForEntity(URI url, Class responseType) throws RestClientException { + AcceptHeaderRequestCallback requestCallback = new AcceptHeaderRequestCallback(responseType); + HttpEntityResponseExtractor responseExtractor = + new HttpEntityResponseExtractor(responseType); + return execute(url, HttpMethod.GET, requestCallback, responseExtractor); + } + // HEAD public HttpHeaders headForHeaders(String url, Object... urlVariables) throws RestClientException { @@ -221,27 +245,27 @@ public class RestTemplate extends HttpAccessor implements RestOperations { // POST public URI postForLocation(String url, Object request, Object... urlVariables) throws RestClientException { - PostPutCallback requestCallback = new PostPutCallback(request); + HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(request); HttpHeaders headers = execute(url, HttpMethod.POST, requestCallback, this.headersExtractor, urlVariables); return headers.getLocation(); } public URI postForLocation(String url, Object request, Map urlVariables) throws RestClientException { - PostPutCallback requestCallback = new PostPutCallback(request); + HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(request); HttpHeaders headers = execute(url, HttpMethod.POST, requestCallback, this.headersExtractor, urlVariables); return headers.getLocation(); } public URI postForLocation(URI url, Object request) throws RestClientException { - PostPutCallback requestCallback = new PostPutCallback(request); + HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(request); HttpHeaders headers = execute(url, HttpMethod.POST, requestCallback, this.headersExtractor); return headers.getLocation(); } public T postForObject(String url, Object request, Class responseType, Object... uriVariables) throws RestClientException { - PostPutCallback requestCallback = new PostPutCallback(request, responseType); + HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(request, responseType); HttpMessageConverterExtractor responseExtractor = new HttpMessageConverterExtractor(responseType, getMessageConverters()); return execute(url, HttpMethod.POST, requestCallback, responseExtractor, uriVariables); @@ -249,33 +273,59 @@ public class RestTemplate extends HttpAccessor implements RestOperations { public T postForObject(String url, Object request, Class responseType, Map uriVariables) throws RestClientException { - PostPutCallback requestCallback = new PostPutCallback(request, responseType); + HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(request, responseType); HttpMessageConverterExtractor responseExtractor = new HttpMessageConverterExtractor(responseType, getMessageConverters()); return execute(url, HttpMethod.POST, requestCallback, responseExtractor, uriVariables); } public T postForObject(URI url, Object request, Class responseType) throws RestClientException { - PostPutCallback requestCallback = new PostPutCallback(request, responseType); + HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(request, responseType); HttpMessageConverterExtractor responseExtractor = new HttpMessageConverterExtractor(responseType, getMessageConverters()); return execute(url, HttpMethod.POST, requestCallback, responseExtractor); } + public HttpEntity postForEntity(String url, Object request, Class responseType, Object... uriVariables) + throws RestClientException { + HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(request, responseType); + HttpEntityResponseExtractor responseExtractor = + new HttpEntityResponseExtractor(responseType); + return execute(url, HttpMethod.POST, requestCallback, responseExtractor, uriVariables); + } + + public HttpEntity postForEntity(String url, + Object request, + Class responseType, + Map uriVariables) + throws RestClientException { + HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(request, responseType); + HttpEntityResponseExtractor responseExtractor = + new HttpEntityResponseExtractor(responseType); + return execute(url, HttpMethod.POST, requestCallback, responseExtractor, uriVariables); + } + + public HttpEntity postForEntity(URI url, Object request, Class responseType) throws RestClientException { + HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(request, responseType); + HttpEntityResponseExtractor responseExtractor = + new HttpEntityResponseExtractor(responseType); + return execute(url, HttpMethod.POST, requestCallback, responseExtractor); + } + // PUT public void put(String url, Object request, Object... urlVariables) throws RestClientException { - PostPutCallback requestCallback = new PostPutCallback(request); + HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(request); execute(url, HttpMethod.PUT, requestCallback, null, urlVariables); } public void put(String url, Object request, Map urlVariables) throws RestClientException { - PostPutCallback requestCallback = new PostPutCallback(request); + HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(request); execute(url, HttpMethod.PUT, requestCallback, null, urlVariables); } public void put(URI url, Object request) throws RestClientException { - PostPutCallback requestCallback = new PostPutCallback(request); + HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(request); execute(url, HttpMethod.PUT, requestCallback, null); } @@ -409,15 +459,11 @@ public class RestTemplate extends HttpAccessor implements RestOperations { /** * Request callback implementation that prepares the request's accept headers. */ - private class AcceptHeaderRequestCallback implements RequestCallback { - - private final Class responseType; + private class AcceptHeaderRequestCallback implements RequestCallback { - private AcceptHeaderRequestCallback() { - responseType = null; - } + private final Class responseType; - private AcceptHeaderRequestCallback(Class responseType) { + private AcceptHeaderRequestCallback(Class responseType) { this.responseType = responseType; } @@ -449,31 +495,51 @@ public class RestTemplate extends HttpAccessor implements RestOperations { /** * Request callback implementation that writes the given object to the request stream. */ - private class PostPutCallback extends AcceptHeaderRequestCallback { - - private final Object requestBody; + private class HttpEntityRequestCallback extends AcceptHeaderRequestCallback { - private final MediaType requestContentType; + private final HttpEntity requestEntity; - private PostPutCallback(Object requestBody) { - this.requestBody = requestBody; - this.requestContentType = null; + private HttpEntityRequestCallback(Object requestBody) { + this(requestBody, null); } - private PostPutCallback(Object requestBody, Class responseType) { + @SuppressWarnings("unchecked") + private HttpEntityRequestCallback(Object requestBody, Class responseType) { super(responseType); - this.requestBody = requestBody; - this.requestContentType = null; + if (requestBody instanceof HttpEntity) { + this.requestEntity = (HttpEntity) requestBody; + } + else if (requestBody != null) { + this.requestEntity = new HttpEntity(requestBody); + } + else { + this.requestEntity = HttpEntity.EMPTY; + } } @Override @SuppressWarnings("unchecked") public void doWithRequest(ClientHttpRequest httpRequest) throws IOException { super.doWithRequest(httpRequest); - if (requestBody != null) { + if (!requestEntity.hasBody()) { + HttpHeaders requestHeaders = requestEntity.getHeaders(); + if (!requestHeaders.isEmpty()) { + httpRequest.getHeaders().putAll(requestHeaders); + } + if (httpRequest.getHeaders().getContentLength() == -1) { + httpRequest.getHeaders().setContentLength(0L); + } + } + else { + Object requestBody = requestEntity.getBody(); Class requestType = requestBody.getClass(); + HttpHeaders requestHeaders = requestEntity.getHeaders(); + MediaType requestContentType = requestHeaders.getContentType(); for (HttpMessageConverter messageConverter : getMessageConverters()) { if (messageConverter.canWrite(requestType, requestContentType)) { + if (!requestHeaders.isEmpty()) { + httpRequest.getHeaders().putAll(requestHeaders); + } messageConverter.write(requestBody, requestContentType, httpRequest); return; } @@ -485,9 +551,23 @@ public class RestTemplate extends HttpAccessor implements RestOperations { } throw new RestClientException(message); } - else { - httpRequest.getHeaders().setContentLength(0L); - } + } + } + + /** + * Response extractor for {@link HttpEntity}. + */ + private class HttpEntityResponseExtractor implements ResponseExtractor> { + + private final HttpMessageConverterExtractor delegate; + + public HttpEntityResponseExtractor(Class responseType) { + this.delegate = new HttpMessageConverterExtractor(responseType, getMessageConverters()); + } + + public HttpEntity extractData(ClientHttpResponse response) throws IOException { + T body = delegate.extractData(response); + return new HttpEntity(body, response.getHeaders()); } } diff --git a/org.springframework.web/src/test/java/org/springframework/http/HttpEntityTests.java b/org.springframework.web/src/test/java/org/springframework/http/HttpEntityTests.java new file mode 100644 index 0000000000000000000000000000000000000000..66dc4024226c58fc2f9cd867ed0883142a10fd88 --- /dev/null +++ b/org.springframework.web/src/test/java/org/springframework/http/HttpEntityTests.java @@ -0,0 +1,67 @@ +/* + * Copyright 2002-2010 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 + * + * http://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.http; + +import java.util.LinkedHashMap; +import java.util.Map; + +import static org.junit.Assert.*; +import org.junit.Test; + +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; + +/** + * @author Arjen Poutsma + */ +public class HttpEntityTests { + + @Test + public void noHeaders() { + String body = "foo"; + HttpEntity entity = new HttpEntity(body); + assertSame(body, entity.getBody()); + assertTrue(entity.getHeaders().isEmpty()); + } + + @Test + public void contentType() { + MediaType contentType = MediaType.TEXT_PLAIN; + HttpEntity entity = new HttpEntity("foo", contentType); + assertEquals(contentType, entity.getHeaders().getContentType()); + assertEquals("text/plain", entity.getHeaders().getFirst("Content-Type")); + } + + @Test + public void multiValueMap() { + MultiValueMap map = new LinkedMultiValueMap(); + map.set("Content-Type", "text/plain"); + HttpEntity entity = new HttpEntity("foo", map); + assertEquals(MediaType.TEXT_PLAIN, entity.getHeaders().getContentType()); + assertEquals("text/plain", entity.getHeaders().getFirst("Content-Type")); + } + + @Test + public void map() { + Map map = new LinkedHashMap(); + map.put("Content-Type", "text/plain"); + HttpEntity entity = new HttpEntity("foo", map); + assertEquals(MediaType.TEXT_PLAIN, entity.getHeaders().getContentType()); + assertEquals("text/plain", entity.getHeaders().getFirst("Content-Type")); + } + +} diff --git a/org.springframework.web/src/test/java/org/springframework/http/converter/FormHttpMessageConverterTests.java b/org.springframework.web/src/test/java/org/springframework/http/converter/FormHttpMessageConverterTests.java index ab8999b380aa975d06fd91a53be93f7a42c369f2..55dd963447e5e95bdff00477dc80d8c74c1c00d0 100644 --- a/org.springframework.web/src/test/java/org/springframework/http/converter/FormHttpMessageConverterTests.java +++ b/org.springframework.web/src/test/java/org/springframework/http/converter/FormHttpMessageConverterTests.java @@ -36,6 +36,7 @@ import org.junit.Test; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; +import org.springframework.http.HttpEntity; import org.springframework.http.MediaType; import org.springframework.http.MockHttpInputMessage; import org.springframework.http.MockHttpOutputMessage; @@ -108,7 +109,8 @@ public class FormHttpMessageConverterTests { Resource logo = new ClassPathResource("/org/springframework/http/converter/logo.jpg"); parts.add("logo", logo); Source xml = new StreamSource(new StringReader("")); - parts.add("xml", xml); + HttpEntity entity = new HttpEntity(xml, MediaType.TEXT_XML); + parts.add("xml", entity); MockHttpOutputMessage outputMessage = new MockHttpOutputMessage(); converter.write(parts, MediaType.MULTIPART_FORM_DATA, outputMessage); @@ -145,7 +147,7 @@ public class FormHttpMessageConverterTests { item = (FileItem) items.get(4); assertEquals("xml", item.getFieldName()); - assertEquals("application/xml", item.getContentType()); + assertEquals("text/xml", item.getContentType()); } private static class MockHttpOutputMessageRequestContext implements RequestContext { diff --git a/org.springframework.web/src/test/java/org/springframework/web/client/RestTemplateIntegrationTests.java b/org.springframework.web/src/test/java/org/springframework/web/client/RestTemplateIntegrationTests.java index e04604381a1adcf5566e52e6f4b63626cc976b4a..09054f116972d7e8093afb84b83109c2aec6f9d6 100644 --- a/org.springframework.web/src/test/java/org/springframework/web/client/RestTemplateIntegrationTests.java +++ b/org.springframework.web/src/test/java/org/springframework/web/client/RestTemplateIntegrationTests.java @@ -20,6 +20,8 @@ import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URISyntaxException; +import java.nio.charset.Charset; +import java.util.Collections; import java.util.EnumSet; import java.util.List; import java.util.Set; @@ -47,7 +49,9 @@ import org.mortbay.jetty.servlet.ServletHolder; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; +import org.springframework.http.HttpEntity; import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; import org.springframework.http.client.CommonsClientHttpRequestFactory; import org.springframework.util.FileCopyUtils; import org.springframework.util.LinkedMultiValueMap; @@ -64,12 +68,14 @@ public class RestTemplateIntegrationTests { private static final String URI = "http://localhost:8889"; + private static MediaType contentType; + @BeforeClass public static void startJettyServer() throws Exception { jettyServer = new Server(8889); Context jettyContext = new Context(jettyServer, "/"); byte[] bytes = helloWorld.getBytes("UTF-8"); - String contentType = "text/plain;charset=utf-8"; + contentType = new MediaType("text", "plain", Collections.singletonMap("charset", "utf-8")); jettyContext.addServlet(new ServletHolder(new GetServlet(bytes, contentType)), "/get"); jettyContext.addServlet(new ServletHolder(new GetServlet(new byte[0], contentType)), "/get/nothing"); jettyContext.addServlet( @@ -100,6 +106,14 @@ public class RestTemplateIntegrationTests { assertEquals("Invalid content", helloWorld, s); } + @Test + public void getEntity() { + HttpEntity entity = template.getForEntity(URI + "/{method}", String.class, "get"); + assertEquals("Invalid content", helloWorld, entity.getBody()); + assertFalse("No headers", entity.getHeaders().isEmpty()); + assertEquals("Invalid content-type", contentType, entity.getHeaders().getContentType()); + } + @Test public void getNoResponse() { String s = template.getForObject(URI + "/get/nothing", String.class); @@ -112,6 +126,13 @@ public class RestTemplateIntegrationTests { assertEquals("Invalid location", new URI(URI + "/post/1"), location); } + @Test + public void postForLocationEntity() throws URISyntaxException { + HttpEntity entity = new HttpEntity(helloWorld, new MediaType("text", "plain", Charset.forName("ISO-8859-15"))); + URI location = template.postForLocation(URI + "/{method}", entity, "post"); + assertEquals("Invalid location", new URI(URI + "/post/1"), location); + } + @Test public void postForObject() throws URISyntaxException { String s = template.postForObject(URI + "/{method}", helloWorld, String.class, "post"); @@ -156,6 +177,7 @@ public class RestTemplateIntegrationTests { template.postForLocation(URI + "/multipart", parts); } + /** Servlet that returns and error message for a given status code. */ private static class ErrorServlet extends GenericServlet { @@ -175,9 +197,9 @@ public class RestTemplateIntegrationTests { private final byte[] buf; - private final String contentType; + private final MediaType contentType; - private GetServlet(byte[] buf, String contentType) { + private GetServlet(byte[] buf, MediaType contentType) { this.buf = buf; this.contentType = contentType; } @@ -185,7 +207,7 @@ public class RestTemplateIntegrationTests { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - response.setContentType(contentType); + response.setContentType(contentType.toString()); response.setContentLength(buf.length); FileCopyUtils.copy(buf, response.getOutputStream()); } @@ -199,9 +221,9 @@ public class RestTemplateIntegrationTests { private final byte[] buf; - private final String contentType; + private final MediaType contentType; - private PostServlet(String s, String location, byte[] buf, String contentType) { + private PostServlet(String s, String location, byte[] buf, MediaType contentType) { this.s = s; this.location = location; this.buf = buf; @@ -218,7 +240,7 @@ public class RestTemplateIntegrationTests { response.setStatus(HttpServletResponse.SC_CREATED); response.setHeader("Location", location); response.setContentLength(buf.length); - response.setContentType(contentType); + response.setContentType(contentType.toString()); FileCopyUtils.copy(buf, response.getOutputStream()); } } diff --git a/org.springframework.web/src/test/java/org/springframework/web/client/RestTemplateTests.java b/org.springframework.web/src/test/java/org/springframework/web/client/RestTemplateTests.java index 82038361e170963f0c31a7ba258159405dc9c075..87bf2bab9016b631d482e5db08e1231cedcbaf2e 100644 --- a/org.springframework.web/src/test/java/org/springframework/web/client/RestTemplateTests.java +++ b/org.springframework.web/src/test/java/org/springframework/web/client/RestTemplateTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 the original author or authors. + * Copyright 2002-2010 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. @@ -28,6 +28,7 @@ import static org.junit.Assert.*; import org.junit.Before; import org.junit.Test; +import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; @@ -176,6 +177,36 @@ public class RestTemplateTests { verifyMocks(); } + + @Test + public void getForEntity() throws Exception { + expect(converter.canRead(String.class, null)).andReturn(true); + MediaType textPlain = new MediaType("text", "plain"); + expect(converter.getSupportedMediaTypes()).andReturn(Collections.singletonList(textPlain)); + expect(requestFactory.createRequest(new URI("http://example.com"), HttpMethod.GET)).andReturn(request); + HttpHeaders requestHeaders = new HttpHeaders(); + expect(request.getHeaders()).andReturn(requestHeaders); + expect(request.execute()).andReturn(response); + expect(errorHandler.hasError(response)).andReturn(false); + HttpHeaders responseHeaders = new HttpHeaders(); + responseHeaders.setContentType(textPlain); + expect(response.getHeaders()).andReturn(responseHeaders).times(2); + expect(converter.canRead(String.class, textPlain)).andReturn(true); + String expected = "Hello World"; + expect(converter.read(String.class, response)).andReturn(expected); + response.close(); + + replayMocks(); + + HttpEntity result = template.getForEntity("http://example.com", String.class); + assertEquals("Invalid GET result", expected, result.getBody()); + assertEquals("Invalid Accept header", textPlain.toString(), requestHeaders.getFirst("Accept")); + assertEquals("Invalid Content-Type header", textPlain, result.getHeaders().getContentType()); + + verifyMocks(); + } + + @Test public void headForHeaders() throws Exception { expect(requestFactory.createRequest(new URI("http://example.com"), HttpMethod.HEAD)).andReturn(request); @@ -215,6 +246,60 @@ public class RestTemplateTests { verifyMocks(); } + @Test + public void postForLocationEntityContentType() throws Exception { + expect(requestFactory.createRequest(new URI("http://example.com"), HttpMethod.POST)).andReturn(request); + String helloWorld = "Hello World"; + MediaType contentType = MediaType.TEXT_PLAIN; + expect(converter.canWrite(String.class, contentType)).andReturn(true); + HttpHeaders requestHeaders = new HttpHeaders(); + expect(request.getHeaders()).andReturn(requestHeaders); + converter.write(helloWorld, contentType, request); + expect(request.execute()).andReturn(response); + expect(errorHandler.hasError(response)).andReturn(false); + HttpHeaders responseHeaders = new HttpHeaders(); + URI expected = new URI("http://example.com/hotels"); + responseHeaders.setLocation(expected); + expect(response.getHeaders()).andReturn(responseHeaders); + response.close(); + + replayMocks(); + + HttpEntity entity = new HttpEntity(helloWorld, contentType); + + URI result = template.postForLocation("http://example.com", entity); + assertEquals("Invalid POST result", expected, result); + + verifyMocks(); + } + + @Test + public void postForLocationEntityCustomHeader() throws Exception { + expect(requestFactory.createRequest(new URI("http://example.com"), HttpMethod.POST)).andReturn(request); + String helloWorld = "Hello World"; + expect(converter.canWrite(String.class, null)).andReturn(true); + HttpHeaders requestHeaders = new HttpHeaders(); + expect(request.getHeaders()).andReturn(requestHeaders); + converter.write(helloWorld, null, request); + expect(request.execute()).andReturn(response); + expect(errorHandler.hasError(response)).andReturn(false); + HttpHeaders responseHeaders = new HttpHeaders(); + URI expected = new URI("http://example.com/hotels"); + responseHeaders.setLocation(expected); + expect(response.getHeaders()).andReturn(responseHeaders); + response.close(); + + replayMocks(); + + HttpEntity entity = new HttpEntity(helloWorld, Collections.singletonMap("MyHeader", "MyValue")); + + URI result = template.postForLocation("http://example.com", entity); + assertEquals("Invalid POST result", expected, result); + assertEquals("No custom header set", "MyValue", requestHeaders.getFirst("MyHeader")); + + verifyMocks(); + } + @Test public void postForLocationNoLocation() throws Exception { expect(requestFactory.createRequest(new URI("http://example.com"), HttpMethod.POST)).andReturn(request); @@ -283,6 +368,37 @@ public class RestTemplateTests { verifyMocks(); } + @Test + public void postForEntity() throws Exception { + MediaType textPlain = new MediaType("text", "plain"); + expect(converter.canRead(Integer.class, null)).andReturn(true); + expect(converter.getSupportedMediaTypes()).andReturn(Collections.singletonList(textPlain)); + expect(requestFactory.createRequest(new URI("http://example.com"), HttpMethod.POST)).andReturn(this.request); + HttpHeaders requestHeaders = new HttpHeaders(); + expect(this.request.getHeaders()).andReturn(requestHeaders); + String request = "Hello World"; + expect(converter.canWrite(String.class, null)).andReturn(true); + converter.write(request, null, this.request); + expect(this.request.execute()).andReturn(response); + expect(errorHandler.hasError(response)).andReturn(false); + HttpHeaders responseHeaders = new HttpHeaders(); + responseHeaders.setContentType(textPlain); + expect(response.getHeaders()).andReturn(responseHeaders).times(2); + Integer expected = 42; + expect(converter.canRead(Integer.class, textPlain)).andReturn(true); + expect(converter.read(Integer.class, response)).andReturn(expected); + response.close(); + + replayMocks(); + + HttpEntity result = template.postForEntity("http://example.com", request, Integer.class); + assertEquals("Invalid POST result", expected, result.getBody()); + assertEquals("Invalid Content-Type", textPlain, result.getHeaders().getContentType()); + assertEquals("Invalid Accept header", textPlain.toString(), requestHeaders.getFirst("Accept")); + + verifyMocks(); + } + @Test public void postForObjectNull() throws Exception { MediaType textPlain = new MediaType("text", "plain"); @@ -301,7 +417,34 @@ public class RestTemplateTests { response.close(); replayMocks(); - template.postForObject("http://example.com", null, Integer.class); + Integer result = template.postForObject("http://example.com", null, Integer.class); + assertNull("Invalid POST result", result); + assertEquals("Invalid content length", 0, requestHeaders.getContentLength()); + + verifyMocks(); + } + + @Test + public void postForEntityNull() throws Exception { + MediaType textPlain = new MediaType("text", "plain"); + expect(converter.canRead(Integer.class, null)).andReturn(true); + expect(converter.getSupportedMediaTypes()).andReturn(Collections.singletonList(textPlain)); + expect(requestFactory.createRequest(new URI("http://example.com"), HttpMethod.POST)).andReturn(request); + HttpHeaders requestHeaders = new HttpHeaders(); + expect(request.getHeaders()).andReturn(requestHeaders).times(2); + expect(request.execute()).andReturn(response); + expect(errorHandler.hasError(response)).andReturn(false); + HttpHeaders responseHeaders = new HttpHeaders(); + responseHeaders.setContentType(textPlain); + expect(response.getHeaders()).andReturn(responseHeaders).times(2); + expect(converter.canRead(Integer.class, textPlain)).andReturn(true); + expect(converter.read(Integer.class, response)).andReturn(null); + response.close(); + + replayMocks(); + HttpEntity result = template.postForEntity("http://example.com", null, Integer.class); + assertFalse("Invalid POST result", result.hasBody()); + assertEquals("Invalid Content-Type", textPlain, result.getHeaders().getContentType()); assertEquals("Invalid content length", 0, requestHeaders.getContentLength()); verifyMocks();