diff --git a/spring-web/src/main/java/org/springframework/web/client/RestOperations.java b/spring-web/src/main/java/org/springframework/web/client/RestOperations.java
index e684dd6aa19..2f453a8e743 100644
--- a/spring-web/src/main/java/org/springframework/web/client/RestOperations.java
+++ b/spring-web/src/main/java/org/springframework/web/client/RestOperations.java
@@ -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.
+ *
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 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 patchForObject(String url, Object request, Class 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.
+ * 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 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 patchForObject(String url, Object request, Class responseType, Map uriVariables)
+ throws RestClientException;
+
+ /**
+ * Update a resource by PATCHing 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 {@code null}
+ * @param responseType the type of the return value
+ * @return the converted object
+ * @see HttpEntity
+ */
+ T patchForObject(URI url, Object request, Class responseType) throws RestClientException;
+
+
+
// DELETE
/**
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 45f52de57ae..df4e868b8ff 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
@@ -458,6 +458,37 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat
execute(url, HttpMethod.PUT, requestCallback, null);
}
+ // PATCH
+
+ @Override
+ public T patchForObject(String url, Object request, Class responseType,
+ Object... uriVariables) throws RestClientException {
+
+ RequestCallback requestCallback = httpEntityCallback(request, responseType);
+ HttpMessageConverterExtractor responseExtractor =
+ new HttpMessageConverterExtractor<>(responseType, getMessageConverters(), logger);
+ return execute(url, HttpMethod.PATCH, requestCallback, responseExtractor, uriVariables);
+ }
+
+ @Override
+ public T patchForObject(String url, Object request, Class responseType,
+ Map uriVariables) throws RestClientException {
+
+ RequestCallback requestCallback = httpEntityCallback(request, responseType);
+ HttpMessageConverterExtractor responseExtractor =
+ new HttpMessageConverterExtractor<>(responseType, getMessageConverters(), logger);
+ return execute(url, HttpMethod.PATCH, requestCallback, responseExtractor, uriVariables);
+ }
+
+ @Override
+ public T patchForObject(URI url, Object request, Class responseType)
+ throws RestClientException {
+
+ RequestCallback requestCallback = httpEntityCallback(request, responseType);
+ HttpMessageConverterExtractor responseExtractor =
+ new HttpMessageConverterExtractor<>(responseType, getMessageConverters());
+ return execute(url, HttpMethod.PATCH, requestCallback, responseExtractor);
+ }
// DELETE
diff --git a/spring-web/src/test/java/org/springframework/web/client/AbstractJettyServerTestCase.java b/spring-web/src/test/java/org/springframework/web/client/AbstractJettyServerTestCase.java
index 0b58c740965..9da3a8f7ccb 100644
--- a/spring-web/src/test/java/org/springframework/web/client/AbstractJettyServerTestCase.java
+++ b/spring-web/src/test/java/org/springframework/web/client/AbstractJettyServerTestCase.java
@@ -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());
+ }
+ }
+
}
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 8b7176f437a..140469398d2 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
@@ -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 {
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 5674ad55fe9..1b3f4fe24c1 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
@@ -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);