Add PATCH method operation to RestTemplate
This commit adds a HTTP PATCH operation to the RestTemplate: patchForObject. As with most operations, there are three variants: varargs, Map, and URI based. Issue: SPR-14857
This commit is contained in:
parent
f0ceefba0e
commit
dbe81bef52
|
|
@ -1,11 +1,11 @@
|
|||
/*
|
||||
* Copyright 2002-2014 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* 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,
|
||||
|
|
@ -306,6 +306,56 @@ public interface RestOperations {
|
|||
void put(URI url, Object request) throws RestClientException;
|
||||
|
||||
|
||||
// PATCH
|
||||
|
||||
/**
|
||||
* Update a resource by PATCHing the given object to the URI template,
|
||||
* and returns the representation found in the response.
|
||||
* <p>URI Template variables are expanded using the given URI variables, if any.
|
||||
* <p>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 PATCHed, may be {@code null}
|
||||
* @param responseType the type of the return value
|
||||
* @param uriVariables the variables to expand the template
|
||||
* @return the converted object
|
||||
* @see HttpEntity
|
||||
* @since 5.0
|
||||
*/
|
||||
<T> T patchForObject(String url, Object request, Class<T> responseType, Object... uriVariables)
|
||||
throws RestClientException;
|
||||
|
||||
/**
|
||||
* Update a resource by PATCHing the given object to the URI template,
|
||||
* and returns the representation found in the response.
|
||||
* <p>URI Template variables are expanded using the given map.
|
||||
* <p>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 PATCHed, may be {@code null}
|
||||
* @param responseType the type of the return value
|
||||
* @param uriVariables the variables to expand the template
|
||||
* @return the converted object
|
||||
* @see HttpEntity
|
||||
*/
|
||||
<T> T patchForObject(String url, Object request, Class<T> responseType, Map<String, ?> uriVariables)
|
||||
throws RestClientException;
|
||||
|
||||
/**
|
||||
* Update a resource by PATCHing the given object to the URL,
|
||||
* and returns the representation found in the response.
|
||||
* <p>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 {@code null}
|
||||
* @param responseType the type of the return value
|
||||
* @return the converted object
|
||||
* @see HttpEntity
|
||||
*/
|
||||
<T> T patchForObject(URI url, Object request, Class<T> responseType) throws RestClientException;
|
||||
|
||||
|
||||
|
||||
// DELETE
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -458,6 +458,37 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat
|
|||
execute(url, HttpMethod.PUT, requestCallback, null);
|
||||
}
|
||||
|
||||
// PATCH
|
||||
|
||||
@Override
|
||||
public <T> T patchForObject(String url, Object request, Class<T> responseType,
|
||||
Object... uriVariables) throws RestClientException {
|
||||
|
||||
RequestCallback requestCallback = httpEntityCallback(request, responseType);
|
||||
HttpMessageConverterExtractor<T> responseExtractor =
|
||||
new HttpMessageConverterExtractor<>(responseType, getMessageConverters(), logger);
|
||||
return execute(url, HttpMethod.PATCH, requestCallback, responseExtractor, uriVariables);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T patchForObject(String url, Object request, Class<T> responseType,
|
||||
Map<String, ?> uriVariables) throws RestClientException {
|
||||
|
||||
RequestCallback requestCallback = httpEntityCallback(request, responseType);
|
||||
HttpMessageConverterExtractor<T> responseExtractor =
|
||||
new HttpMessageConverterExtractor<>(responseType, getMessageConverters(), logger);
|
||||
return execute(url, HttpMethod.PATCH, requestCallback, responseExtractor, uriVariables);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T patchForObject(URI url, Object request, Class<T> responseType)
|
||||
throws RestClientException {
|
||||
|
||||
RequestCallback requestCallback = httpEntityCallback(request, responseType);
|
||||
HttpMessageConverterExtractor<T> responseExtractor =
|
||||
new HttpMessageConverterExtractor<>(responseType, getMessageConverters());
|
||||
return execute(url, HttpMethod.PATCH, requestCallback, responseExtractor);
|
||||
}
|
||||
|
||||
// DELETE
|
||||
|
||||
|
|
|
|||
|
|
@ -44,7 +44,10 @@ import org.junit.BeforeClass;
|
|||
import org.springframework.http.MediaType;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
/**
|
||||
* @author Arjen Poutsma
|
||||
|
|
@ -92,6 +95,8 @@ public class AbstractJettyServerTestCase {
|
|||
handler.addServlet(new ServletHolder(new MultipartServlet()), "/multipart");
|
||||
handler.addServlet(new ServletHolder(new FormServlet()), "/form");
|
||||
handler.addServlet(new ServletHolder(new DeleteServlet()), "/delete");
|
||||
handler.addServlet(new ServletHolder(new PatchServlet(helloWorld, bytes, textContentType)),
|
||||
"/patch");
|
||||
handler.addServlet(
|
||||
new ServletHolder(new PutServlet(helloWorld, bytes, textContentType)),
|
||||
"/put");
|
||||
|
|
@ -333,4 +338,36 @@ public class AbstractJettyServerTestCase {
|
|||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
private static class PatchServlet extends GenericServlet {
|
||||
|
||||
private final String content;
|
||||
|
||||
private final byte[] buf;
|
||||
|
||||
private final MediaType contentType;
|
||||
|
||||
public PatchServlet(String content, byte[] buf, MediaType contentType) {
|
||||
this.content = content;
|
||||
this.buf = buf;
|
||||
this.contentType = contentType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void service(ServletRequest req, ServletResponse res)
|
||||
throws ServletException, IOException {
|
||||
HttpServletRequest request = (HttpServletRequest) req;
|
||||
HttpServletResponse response = (HttpServletResponse) res;
|
||||
assertEquals("PATCH", request.getMethod());
|
||||
assertTrue("Invalid request content-length", request.getContentLength() > 0);
|
||||
assertNotNull("No content-type", request.getContentType());
|
||||
String body = FileCopyUtils.copyToString(request.getReader());
|
||||
assertEquals("Invalid request body", content, body);
|
||||
response.setStatus(HttpServletResponse.SC_CREATED);
|
||||
response.setContentLength(buf.length);
|
||||
response.setContentType(contentType.toString());
|
||||
FileCopyUtils.copy(buf, response.getOutputStream());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,7 +45,13 @@ import org.springframework.http.converter.json.MappingJacksonValue;
|
|||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
/**
|
||||
* @author Arjen Poutsma
|
||||
|
|
@ -123,6 +129,12 @@ public class RestTemplateIntegrationTests extends AbstractJettyServerTestCase {
|
|||
assertEquals("Invalid content", helloWorld, s);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void patchForObject() throws URISyntaxException {
|
||||
String s = template.patchForObject(baseUrl + "/{method}", helloWorld, String.class, "patch");
|
||||
assertEquals("Invalid content", helloWorld, s);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void notFound() {
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -44,8 +44,17 @@ import org.springframework.http.converter.GenericHttpMessageConverter;
|
|||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.web.util.DefaultUriTemplateHandler;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.BDDMockito.*;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.mockito.BDDMockito.any;
|
||||
import static org.mockito.BDDMockito.eq;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.BDDMockito.mock;
|
||||
import static org.mockito.BDDMockito.verify;
|
||||
import static org.mockito.BDDMockito.willThrow;
|
||||
|
||||
/**
|
||||
* @author Arjen Poutsma
|
||||
|
|
@ -590,6 +599,69 @@ public class RestTemplateTests {
|
|||
verify(response).close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void patchForObject() throws Exception {
|
||||
MediaType textPlain = new MediaType("text", "plain");
|
||||
given(converter.canRead(Integer.class, null)).willReturn(true);
|
||||
given(converter.getSupportedMediaTypes()).willReturn(Collections.singletonList(textPlain));
|
||||
given(requestFactory.createRequest(new URI("http://example.com"), HttpMethod.PATCH)).willReturn(this.request);
|
||||
HttpHeaders requestHeaders = new HttpHeaders();
|
||||
given(this.request.getHeaders()).willReturn(requestHeaders);
|
||||
String request = "Hello World";
|
||||
given(converter.canWrite(String.class, null)).willReturn(true);
|
||||
converter.write(request, null, this.request);
|
||||
given(this.request.execute()).willReturn(response);
|
||||
given(errorHandler.hasError(response)).willReturn(false);
|
||||
Integer expected = 42;
|
||||
HttpHeaders responseHeaders = new HttpHeaders();
|
||||
responseHeaders.setContentType(textPlain);
|
||||
responseHeaders.setContentLength(10);
|
||||
given(response.getStatusCode()).willReturn(HttpStatus.OK);
|
||||
given(response.getHeaders()).willReturn(responseHeaders);
|
||||
given(response.getBody()).willReturn(new ByteArrayInputStream(expected.toString().getBytes()));
|
||||
given(converter.canRead(Integer.class, textPlain)).willReturn(true);
|
||||
given(converter.read(eq(Integer.class), any(HttpInputMessage.class))).willReturn(expected);
|
||||
HttpStatus status = HttpStatus.OK;
|
||||
given(response.getStatusCode()).willReturn(status);
|
||||
given(response.getStatusText()).willReturn(status.getReasonPhrase());
|
||||
|
||||
Integer result = template.patchForObject("http://example.com", request, Integer.class);
|
||||
assertEquals("Invalid POST result", expected, result);
|
||||
assertEquals("Invalid Accept header", textPlain.toString(), requestHeaders.getFirst("Accept"));
|
||||
|
||||
verify(response).close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void patchForObjectNull() throws Exception {
|
||||
MediaType textPlain = new MediaType("text", "plain");
|
||||
given(converter.canRead(Integer.class, null)).willReturn(true);
|
||||
given(converter.getSupportedMediaTypes()).willReturn(Collections.singletonList(textPlain));
|
||||
given(requestFactory.createRequest(new URI("http://example.com"), HttpMethod.PATCH)).willReturn(request);
|
||||
HttpHeaders requestHeaders = new HttpHeaders();
|
||||
given(request.getHeaders()).willReturn(requestHeaders);
|
||||
given(request.execute()).willReturn(response);
|
||||
given(errorHandler.hasError(response)).willReturn(false);
|
||||
HttpHeaders responseHeaders = new HttpHeaders();
|
||||
responseHeaders.setContentType(textPlain);
|
||||
responseHeaders.setContentLength(10);
|
||||
given(response.getStatusCode()).willReturn(HttpStatus.OK);
|
||||
given(response.getHeaders()).willReturn(responseHeaders);
|
||||
given(converter.canRead(Integer.class, textPlain)).willReturn(true);
|
||||
given(converter.read(Integer.class, response)).willReturn(null);
|
||||
HttpStatus status = HttpStatus.OK;
|
||||
given(response.getStatusCode()).willReturn(status);
|
||||
given(response.getStatusText()).willReturn(status.getReasonPhrase());
|
||||
|
||||
Integer result = template.patchForObject("http://example.com", null, Integer.class);
|
||||
assertNull("Invalid POST result", result);
|
||||
assertEquals("Invalid content length", 0, requestHeaders.getContentLength());
|
||||
|
||||
verify(response).close();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Test
|
||||
public void delete() throws Exception {
|
||||
given(requestFactory.createRequest(new URI("http://example.com"), HttpMethod.DELETE)).willReturn(request);
|
||||
|
|
|
|||
Loading…
Reference in New Issue