diff --git a/build.gradle b/build.gradle
index cd33239f50..d6118cd820 100644
--- a/build.gradle
+++ b/build.gradle
@@ -360,7 +360,7 @@ project('spring-web') {
compile("commons-fileupload:commons-fileupload:1.2", optional)
runtime("commons-io:commons-io:1.3", optional)
compile("commons-httpclient:commons-httpclient:3.1", optional)
- compile("org.apache.httpcomponents:httpclient:4.1.1", optional)
+ compile("org.apache.httpcomponents:httpclient:4.2", optional)
compile("org.codehaus.jackson:jackson-mapper-asl:1.4.2", optional)
compile("com.fasterxml.jackson.core:jackson-databind:2.0.1", optional)
compile("taglibs:standard:1.1.2", optional)
diff --git a/spring-web/src/main/java/org/springframework/http/HttpMethod.java b/spring-web/src/main/java/org/springframework/http/HttpMethod.java
index 361784b4ab..f006c703cc 100644
--- a/spring-web/src/main/java/org/springframework/http/HttpMethod.java
+++ b/spring-web/src/main/java/org/springframework/http/HttpMethod.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2009 the original author or authors.
+ * Copyright 2002-2012 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.
@@ -26,6 +26,6 @@ package org.springframework.http;
*/
public enum HttpMethod {
- GET, POST, HEAD, OPTIONS, PUT, DELETE, TRACE
+ GET, POST, HEAD, OPTIONS, PUT, PATCH, DELETE, TRACE
}
diff --git a/spring-web/src/main/java/org/springframework/http/client/CommonsClientHttpRequestFactory.java b/spring-web/src/main/java/org/springframework/http/client/CommonsClientHttpRequestFactory.java
index e70c8f36e8..afd7bdb3cc 100644
--- a/spring-web/src/main/java/org/springframework/http/client/CommonsClientHttpRequestFactory.java
+++ b/spring-web/src/main/java/org/springframework/http/client/CommonsClientHttpRequestFactory.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2011 the original author or authors.
+ * Copyright 2002-2012 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.
@@ -141,6 +141,9 @@ public class CommonsClientHttpRequestFactory implements ClientHttpRequestFactory
return new PutMethod(uri);
case TRACE:
return new TraceMethod(uri);
+ case PATCH:
+ throw new IllegalArgumentException(
+ "HTTP method PATCH not available before Apache HttpComponents HttpClient 4.2");
default:
throw new IllegalArgumentException("Invalid HTTP method: " + httpMethod);
}
diff --git a/spring-web/src/main/java/org/springframework/http/client/HttpComponentsClientHttpRequestFactory.java b/spring-web/src/main/java/org/springframework/http/client/HttpComponentsClientHttpRequestFactory.java
index f5dee91ec9..7e8f6c5d0b 100644
--- a/spring-web/src/main/java/org/springframework/http/client/HttpComponentsClientHttpRequestFactory.java
+++ b/spring-web/src/main/java/org/springframework/http/client/HttpComponentsClientHttpRequestFactory.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2011 the original author or authors.
+ * Copyright 2002-2012 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.
@@ -17,12 +17,9 @@
package org.springframework.http.client;
import java.io.IOException;
+import java.lang.reflect.Constructor;
import java.net.URI;
-import org.springframework.beans.factory.DisposableBean;
-import org.springframework.http.HttpMethod;
-import org.springframework.util.Assert;
-
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
@@ -40,6 +37,10 @@ import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.params.CoreConnectionPNames;
import org.apache.http.protocol.HttpContext;
+import org.springframework.beans.factory.DisposableBean;
+import org.springframework.http.HttpMethod;
+import org.springframework.util.Assert;
+import org.springframework.util.ClassUtils;
/**
* {@link org.springframework.http.client.ClientHttpRequestFactory} implementation that uses
@@ -155,11 +156,30 @@ public class HttpComponentsClientHttpRequestFactory implements ClientHttpRequest
return new HttpPut(uri);
case TRACE:
return new HttpTrace(uri);
+ case PATCH:
+ return createHttpPatch(uri);
default:
throw new IllegalArgumentException("Invalid HTTP method: " + httpMethod);
}
}
+ private HttpUriRequest createHttpPatch(URI uri) {
+ String className = "org.apache.http.client.methods.HttpPatch";
+ ClassLoader classloader = this.getClass().getClassLoader();
+ if (!ClassUtils.isPresent(className, classloader)) {
+ throw new IllegalArgumentException(
+ "HTTP method PATCH not available before Apache HttpComponents HttpClient 4.2");
+ }
+ try {
+ Class> clazz = classloader.loadClass(className);
+ Constructor> constructor = clazz.getConstructor(URI.class);
+ return (HttpUriRequest) constructor.newInstance(uri);
+ }
+ catch (Throwable ex) {
+ throw new IllegalStateException("Unable to instantiate " + className, ex);
+ }
+ }
+
/**
* Template method that allows for manipulating the {@link HttpUriRequest} before it is
* returned as part of a {@link HttpComponentsClientHttpRequest}.
diff --git a/spring-web/src/main/java/org/springframework/http/client/SimpleClientHttpRequestFactory.java b/spring-web/src/main/java/org/springframework/http/client/SimpleClientHttpRequestFactory.java
index a0b8474f71..22d17b50f0 100644
--- a/spring-web/src/main/java/org/springframework/http/client/SimpleClientHttpRequestFactory.java
+++ b/spring-web/src/main/java/org/springframework/http/client/SimpleClientHttpRequestFactory.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2011 the original author or authors.
+ * Copyright 2002-2012 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.
@@ -152,7 +152,7 @@ public class SimpleClientHttpRequestFactory implements ClientHttpRequestFactory
else {
connection.setInstanceFollowRedirects(false);
}
- if ("PUT".equals(httpMethod) || "POST".equals(httpMethod)) {
+ if ("PUT".equals(httpMethod) || "POST".equals(httpMethod) || "PATCH".equals(httpMethod)) {
connection.setDoOutput(true);
}
else {
diff --git a/spring-web/src/main/java/org/springframework/web/HttpMediaTypeNotSupportedException.java b/spring-web/src/main/java/org/springframework/web/HttpMediaTypeNotSupportedException.java
index 68fe27551c..731c64ac30 100644
--- a/spring-web/src/main/java/org/springframework/web/HttpMediaTypeNotSupportedException.java
+++ b/spring-web/src/main/java/org/springframework/web/HttpMediaTypeNotSupportedException.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2009 the original author or authors.
+ * Copyright 2002-2012 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.
@@ -21,7 +21,7 @@ import java.util.List;
import org.springframework.http.MediaType;
/**
- * Exception thrown when a client POSTs or PUTs content
+ * Exception thrown when a client POSTs, PUTs, or PATCHes content of a type
* not supported by request handler.
*
* @author Arjen Poutsma
diff --git a/spring-web/src/main/java/org/springframework/web/bind/annotation/RequestMapping.java b/spring-web/src/main/java/org/springframework/web/bind/annotation/RequestMapping.java
index 00c9a52f92..f7ba780479 100644
--- a/spring-web/src/main/java/org/springframework/web/bind/annotation/RequestMapping.java
+++ b/spring-web/src/main/java/org/springframework/web/bind/annotation/RequestMapping.java
@@ -271,7 +271,7 @@ public @interface RequestMapping {
/**
* The HTTP request methods to map to, narrowing the primary mapping:
- * GET, POST, HEAD, OPTIONS, PUT, DELETE, TRACE.
+ * GET, POST, HEAD, OPTIONS, PUT, PATCH, DELETE, TRACE.
*
Supported at the type level as well as at the method level!
* When used at the type level, all method-level mappings inherit
* this HTTP method restriction (i.e. the type-level restriction
diff --git a/spring-web/src/main/java/org/springframework/web/bind/annotation/RequestMethod.java b/spring-web/src/main/java/org/springframework/web/bind/annotation/RequestMethod.java
index 3695cb3425..814687ca65 100644
--- a/spring-web/src/main/java/org/springframework/web/bind/annotation/RequestMethod.java
+++ b/spring-web/src/main/java/org/springframework/web/bind/annotation/RequestMethod.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2009 the original author or authors.
+ * Copyright 2002-2012 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.
@@ -22,7 +22,7 @@ package org.springframework.web.bind.annotation;
* {@link RequestMapping} annotation.
*
*
Note that, by default, {@link org.springframework.web.servlet.DispatcherServlet}
- * supports GET, HEAD, POST, PUT and DELETE only. DispatcherServlet will
+ * supports GET, HEAD, POST, PUT, PATCH and DELETE only. DispatcherServlet will
* process TRACE and OPTIONS with the default HttpServlet behavior unless
* explicitly told to dispatch those request types as well: Check out
* the "dispatchOptionsRequest" and "dispatchTraceRequest" properties,
@@ -36,6 +36,6 @@ package org.springframework.web.bind.annotation;
*/
public enum RequestMethod {
- GET, HEAD, POST, PUT, DELETE, OPTIONS, TRACE
+ GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE
}
diff --git a/spring-web/src/main/java/org/springframework/web/filter/HttpPutFormContentFilter.java b/spring-web/src/main/java/org/springframework/web/filter/HttpPutFormContentFilter.java
index 540ed24741..74982a361c 100644
--- a/spring-web/src/main/java/org/springframework/web/filter/HttpPutFormContentFilter.java
+++ b/spring-web/src/main/java/org/springframework/web/filter/HttpPutFormContentFilter.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2011 the original author or authors.
+ * Copyright 2002-2012 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.
@@ -44,24 +44,24 @@ import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
/**
- * {@link javax.servlet.Filter} that makes form encoded data available through
- * the {@code ServletRequest.getParameter*()} family of methods during HTTP PUT
- * requests.
- *
- *
The Servlet spec requires form data to be available for HTTP POST but
- * not for HTTP PUT requests. This filter intercepts HTTP PUT requests where
- * content type is {@code 'application/x-www-form-urlencoded'}, reads form
- * encoded content from the body of the request, and wraps the ServletRequest
+ * {@link javax.servlet.Filter} that makes form encoded data available through
+ * the {@code ServletRequest.getParameter*()} family of methods during HTTP PUT
+ * or PATCH requests.
+ *
+ *
The Servlet spec requires form data to be available for HTTP POST but
+ * not for HTTP PUT or PATCH requests. This filter intercepts HTTP PUT and PATCH
+ * requests where content type is {@code 'application/x-www-form-urlencoded'},
+ * reads form encoded content from the body of the request, and wraps the ServletRequest
* in order to make the form data available as request parameters just like
* it is for HTTP POST requests.
- *
+ *
* @author Rossen Stoyanchev
* @since 3.1
*/
public class HttpPutFormContentFilter extends OncePerRequestFilter {
private final FormHttpMessageConverter formConverter = new XmlAwareFormHttpMessageConverter();
-
+
/**
* The default character set to use for reading form data.
*/
@@ -73,7 +73,7 @@ public class HttpPutFormContentFilter extends OncePerRequestFilter {
protected void doFilterInternal(final HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
- if ("PUT".equals(request.getMethod()) && isFormContentType(request)) {
+ if (("PUT".equals(request.getMethod()) || "PATCH".equals(request.getMethod())) && isFormContentType(request)) {
HttpInputMessage inputMessage = new ServletServerHttpRequest(request) {
@Override
public InputStream getBody() throws IOException {
@@ -102,7 +102,7 @@ public class HttpPutFormContentFilter extends OncePerRequestFilter {
}
private static class HttpPutFormContentRequestWrapper extends HttpServletRequestWrapper {
-
+
private MultiValueMap formParameters;
public HttpPutFormContentRequestWrapper(HttpServletRequest request, MultiValueMap parameters) {
diff --git a/spring-web/src/test/java/org/springframework/http/client/AbstractHttpRequestFactoryTestCase.java b/spring-web/src/test/java/org/springframework/http/client/AbstractHttpRequestFactoryTestCase.java
index 8fe6752e20..58ed4ebbe0 100644
--- a/spring-web/src/test/java/org/springframework/http/client/AbstractHttpRequestFactoryTestCase.java
+++ b/spring-web/src/test/java/org/springframework/http/client/AbstractHttpRequestFactoryTestCase.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2011 the original author or authors.
+ * Copyright 2002-2012 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.
@@ -68,6 +68,7 @@ public abstract class AbstractHttpRequestFactoryTestCase {
jettyContext.addServlet(new ServletHolder(new MethodServlet("OPTIONS")), "/methods/options");
jettyContext.addServlet(new ServletHolder(new PostServlet()), "/methods/post");
jettyContext.addServlet(new ServletHolder(new MethodServlet("PUT")), "/methods/put");
+ jettyContext.addServlet(new ServletHolder(new MethodServlet("PATCH")), "/methods/patch");
jettyServer.start();
}
@@ -160,7 +161,7 @@ public abstract class AbstractHttpRequestFactoryTestCase {
assertHttpMethod("delete", HttpMethod.DELETE);
}
- private void assertHttpMethod(String path, HttpMethod method) throws Exception {
+ protected void assertHttpMethod(String path, HttpMethod method) throws Exception {
ClientHttpResponse response = null;
try {
ClientHttpRequest request = factory.createRequest(new URI(baseUrl + "/methods/" + path), method);
diff --git a/spring-web/src/test/java/org/springframework/http/client/BufferedSimpleHttpRequestFactoryTests.java b/spring-web/src/test/java/org/springframework/http/client/BufferedSimpleHttpRequestFactoryTests.java
index 6ffd7fc8d9..7408552ff5 100644
--- a/spring-web/src/test/java/org/springframework/http/client/BufferedSimpleHttpRequestFactoryTests.java
+++ b/spring-web/src/test/java/org/springframework/http/client/BufferedSimpleHttpRequestFactoryTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2011 the original author or authors.
+ * Copyright 2002-2012 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,10 +16,26 @@
package org.springframework.http.client;
+import java.net.ProtocolException;
+
+import org.junit.Test;
+import org.springframework.http.HttpMethod;
+
public class BufferedSimpleHttpRequestFactoryTests extends AbstractHttpRequestFactoryTestCase {
@Override
protected ClientHttpRequestFactory createRequestFactory() {
return new SimpleClientHttpRequestFactory();
}
+
+ @Test
+ public void httpMethods() throws Exception {
+ try {
+ assertHttpMethod("patch", HttpMethod.PATCH);
+ }
+ catch (ProtocolException ex) {
+ // Currently HttpURLConnection does not support HTTP PATCH
+ }
+ }
+
}
diff --git a/spring-web/src/test/java/org/springframework/http/client/CommonsHttpRequestFactoryTests.java b/spring-web/src/test/java/org/springframework/http/client/CommonsHttpRequestFactoryTests.java
index 0f7f5de40d..4fd3017ca2 100644
--- a/spring-web/src/test/java/org/springframework/http/client/CommonsHttpRequestFactoryTests.java
+++ b/spring-web/src/test/java/org/springframework/http/client/CommonsHttpRequestFactoryTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2009 the original author or authors.
+ * Copyright 2002-2012 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,9 +16,10 @@
package org.springframework.http.client;
-import org.springframework.http.client.AbstractHttpRequestFactoryTestCase;
-import org.springframework.http.client.ClientHttpRequestFactory;
-import org.springframework.http.client.CommonsClientHttpRequestFactory;
+import java.net.URI;
+
+import org.junit.Test;
+import org.springframework.http.HttpMethod;
public class CommonsHttpRequestFactoryTests extends AbstractHttpRequestFactoryTestCase {
@@ -26,4 +27,10 @@ public class CommonsHttpRequestFactoryTests extends AbstractHttpRequestFactoryTe
protected ClientHttpRequestFactory createRequestFactory() {
return new CommonsClientHttpRequestFactory();
}
+
+ @Test(expected=IllegalArgumentException.class)
+ public void httpPatch() throws Exception {
+ factory.createRequest(new URI(baseUrl + "/methods/PATCH"), HttpMethod.PATCH);
+ }
+
}
diff --git a/spring-web/src/test/java/org/springframework/http/client/HttpComponentsClientHttpRequestFactoryTests.java b/spring-web/src/test/java/org/springframework/http/client/HttpComponentsClientHttpRequestFactoryTests.java
index 399db694a4..917e5de114 100644
--- a/spring-web/src/test/java/org/springframework/http/client/HttpComponentsClientHttpRequestFactoryTests.java
+++ b/spring-web/src/test/java/org/springframework/http/client/HttpComponentsClientHttpRequestFactoryTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2011 the original author or authors.
+ * Copyright 2002-2012 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,10 +16,19 @@
package org.springframework.http.client;
+import org.junit.Test;
+import org.springframework.http.HttpMethod;
+
public class HttpComponentsClientHttpRequestFactoryTests extends AbstractHttpRequestFactoryTestCase {
@Override
protected ClientHttpRequestFactory createRequestFactory() {
return new HttpComponentsClientHttpRequestFactory();
}
+
+ @Test
+ public void httpMethods() throws Exception {
+ assertHttpMethod("patch", HttpMethod.PATCH);
+ }
+
}
diff --git a/spring-web/src/test/java/org/springframework/web/filter/HttpPutFormContentFilterTests.java b/spring-web/src/test/java/org/springframework/web/filter/HttpPutFormContentFilterTests.java
index 17fdb03517..d73cc484e8 100644
--- a/spring-web/src/test/java/org/springframework/web/filter/HttpPutFormContentFilterTests.java
+++ b/spring-web/src/test/java/org/springframework/web/filter/HttpPutFormContentFilterTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2011 the original author or authors.
+ * Copyright 2002-2012 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.
@@ -29,25 +29,26 @@ import java.util.Map;
import org.junit.Before;
import org.junit.Test;
+import org.springframework.http.HttpMethod;
import org.springframework.mock.web.MockFilterChain;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
/**
* Test fixture for {@link HttpPutFormContentFilter}.
- *
+ *
* @author Rossen Stoyanchev
*/
public class HttpPutFormContentFilterTests {
private HttpPutFormContentFilter filter;
-
+
private MockHttpServletRequest request;
-
+
private MockHttpServletResponse response;
-
+
private MockFilterChain filterChain;
-
+
@Before
public void setup() {
filter = new HttpPutFormContentFilter();
@@ -59,14 +60,18 @@ public class HttpPutFormContentFilterTests {
}
@Test
- public void wrapPutOnly() throws Exception {
+ public void wrapPutAndPatchOnly() throws Exception {
request.setContent("".getBytes("ISO-8859-1"));
- String[] methods = new String[] {"GET", "POST", "DELETE", "HEAD", "OPTIONS", "TRACE"};
- for (String method : methods) {
- request.setMethod(method);
+ for (HttpMethod method : HttpMethod.values()) {
+ request.setMethod(method.name());
filterChain = new MockFilterChain();
filter.doFilter(request, response, filterChain);
- assertSame("Should not wrap for HTTP method " + method, request, filterChain.getRequest());
+ if (method.equals(HttpMethod.PUT) || method.equals(HttpMethod.PATCH)) {
+ assertNotSame("Should wrap HTTP method " + method, request, filterChain.getRequest());
+ }
+ else {
+ assertSame("Should not wrap for HTTP method " + method, request, filterChain.getRequest());
+ }
}
}
@@ -81,31 +86,31 @@ public class HttpPutFormContentFilterTests {
assertSame("Should not wrap for content type " + contentType, request, filterChain.getRequest());
}
}
-
+
@Test
public void getParameter() throws Exception {
request.setContent("name=value".getBytes("ISO-8859-1"));
filter.doFilter(request, response, filterChain);
-
+
assertEquals("value", filterChain.getRequest().getParameter("name"));
}
-
+
@Test
public void getParameterFromQueryString() throws Exception {
request.addParameter("name", "value1");
request.setContent("name=value2".getBytes("ISO-8859-1"));
filter.doFilter(request, response, filterChain);
-
+
assertNotSame("Request not wrapped", request, filterChain.getRequest());
- assertEquals("Query string parameters should be listed ahead of form parameters",
+ assertEquals("Query string parameters should be listed ahead of form parameters",
"value1", filterChain.getRequest().getParameter("name"));
}
-
+
@Test
public void getParameterNullValue() throws Exception {
request.setContent("name=value".getBytes("ISO-8859-1"));
filter.doFilter(request, response, filterChain);
-
+
assertNotSame("Request not wrapped", request, filterChain.getRequest());
assertNull(filterChain.getRequest().getParameter("noSuchParam"));
}
@@ -115,27 +120,27 @@ public class HttpPutFormContentFilterTests {
request.addParameter("name1", "value1");
request.addParameter("name2", "value2");
request.setContent("name1=value1&name3=value3&name4=value4".getBytes("ISO-8859-1"));
-
+
filter.doFilter(request, response, filterChain);
List names = Collections.list(filterChain.getRequest().getParameterNames());
assertNotSame("Request not wrapped", request, filterChain.getRequest());
assertEquals(Arrays.asList("name1", "name2", "name3", "name4"), names);
}
-
+
@Test
public void getParameterValues() throws Exception {
request.addParameter("name", "value1");
request.addParameter("name", "value2");
request.setContent("name=value3&name=value4".getBytes("ISO-8859-1"));
-
+
filter.doFilter(request, response, filterChain);
String[] values = filterChain.getRequest().getParameterValues("name");
assertNotSame("Request not wrapped", request, filterChain.getRequest());
assertArrayEquals(new String[]{"value1", "value2", "value3", "value4"}, values);
}
-
+
@Test
public void getParameterValuesFromQueryString() throws Exception {
request.addParameter("name", "value1");
@@ -148,33 +153,33 @@ public class HttpPutFormContentFilterTests {
assertNotSame("Request not wrapped", request, filterChain.getRequest());
assertArrayEquals(new String[]{"value1", "value2"}, values);
}
-
+
@Test
public void getParameterValuesFromFormContent() throws Exception {
request.addParameter("name", "value1");
request.addParameter("name", "value2");
request.setContent("anotherName=anotherValue".getBytes("ISO-8859-1"));
-
+
filter.doFilter(request, response, filterChain);
String[] values = filterChain.getRequest().getParameterValues("anotherName");
assertNotSame("Request not wrapped", request, filterChain.getRequest());
assertArrayEquals(new String[]{"anotherValue"}, values);
}
-
+
@Test
public void getParameterValuesInvalidName() throws Exception {
request.addParameter("name", "value1");
request.addParameter("name", "value2");
request.setContent("anotherName=anotherValue".getBytes("ISO-8859-1"));
-
+
filter.doFilter(request, response, filterChain);
String[] values = filterChain.getRequest().getParameterValues("noSuchParameter");
assertNotSame("Request not wrapped", request, filterChain.getRequest());
assertNull(values);
}
-
+
@Test
public void getParameterMap() throws Exception {
request.addParameter("name", "value1");
@@ -189,5 +194,5 @@ public class HttpPutFormContentFilterTests {
assertArrayEquals(new String[] {"value1", "value2", "value3"}, parameters.get("name"));
assertArrayEquals(new String[] {"value4"}, parameters.get("name4"));
}
-
+
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/FrameworkServlet.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/FrameworkServlet.java
index b8d7855ed2..c50b827608 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/FrameworkServlet.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/FrameworkServlet.java
@@ -41,6 +41,7 @@ import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
+import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.context.ConfigurableWebApplicationContext;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestAttributes;
@@ -767,6 +768,25 @@ public abstract class FrameworkServlet extends HttpServletBean {
}
+ /**
+ * Override the parent class implementation in order to intercept PATCH
+ * requests.
+ *
+ * @see #doPatch(HttpServletRequest, HttpServletResponse)
+ */
+ @Override
+ protected void service(HttpServletRequest request, HttpServletResponse response)
+ throws ServletException, IOException {
+
+ String method = request.getMethod();
+ if (method.equalsIgnoreCase(RequestMethod.PATCH.name())) {
+ doPatch(request, response);
+ }
+ else {
+ super.service(request, response);
+ }
+ }
+
/**
* Delegate GET requests to processRequest/doService.
* Will also be invoked by HttpServlet's default implementation of doHead,
@@ -803,6 +823,16 @@ public abstract class FrameworkServlet extends HttpServletBean {
processRequest(request, response);
}
+ /**
+ * Delegate PATCH requests to {@link #processRequest}.
+ * @see #doService
+ */
+ protected final void doPatch(HttpServletRequest request, HttpServletResponse response)
+ throws ServletException, IOException {
+
+ processRequest(request, response);
+ }
+
/**
* Delegate DELETE requests to {@link #processRequest}.
* @see #doService
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletAnnotationControllerHandlerMethodTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletAnnotationControllerHandlerMethodTests.java
index ef81648a07..c8e6a1083c 100644
--- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletAnnotationControllerHandlerMethodTests.java
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletAnnotationControllerHandlerMethodTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2011 the original author or authors.
+ * Copyright 2002-2012 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.
@@ -144,20 +144,20 @@ import org.springframework.web.servlet.view.InternalResourceViewResolver;
/**
* The origin of this test class is {@link ServletAnnotationControllerHandlerMethodTests}.
- *
+ *
* Tests in this class run against the {@link HandlerMethod} infrastructure:
*
- * - RequestMappingHandlerMapping
- *
- RequestMappingHandlerAdapter
+ *
- RequestMappingHandlerMapping
+ *
- RequestMappingHandlerAdapter
*
- ExceptionHandlerExceptionResolver
*
- *
+ *
* Rather than against the existing infrastructure:
*
* - DefaultAnnotationHandlerMapping
*
- AnnotationMethodHandlerAdapter
*
- AnnotationMethodHandlerExceptionResolver
- *
+ *
*
* @author Rossen Stoyanchev
*/
@@ -249,7 +249,7 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl
context.registerBeanDefinition("ppc", ppc);
}
}, DefaultExpressionValueParamController.class);
-
+
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/myApp/myPath.do");
request.setContextPath("/myApp");
MockHttpServletResponse response = new MockHttpServletResponse();
@@ -656,7 +656,7 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl
final MockServletContext servletContext = new MockServletContext();
final MockServletConfig servletConfig = new MockServletConfig(servletContext);
- WebApplicationContext webAppContext =
+ WebApplicationContext webAppContext =
initServlet(new ApplicationContextInitializer() {
public void initialize(GenericWebApplicationContext wac) {
wac.setServletContext(servletContext);
@@ -705,7 +705,7 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl
getServlet().service(request, response);
assertEquals("mySurpriseView", response.getContentAsString());
- MyParameterDispatchingController deserialized =
+ MyParameterDispatchingController deserialized =
(MyParameterDispatchingController) SerializationTestUtils.serializeAndDeserialize(
webAppContext.getBean(MyParameterDispatchingController.class.getSimpleName()));
assertNotNull(deserialized.request);
@@ -813,6 +813,21 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl
assertEquals(requestBody, response.getContentAsString());
}
+ @Test
+ public void httpPatch() throws ServletException, IOException {
+ initServletWithControllers(RequestResponseBodyController.class);
+
+ MockHttpServletRequest request = new MockHttpServletRequest("PATCH", "/something");
+ String requestBody = "Hello world!";
+ request.setContent(requestBody.getBytes("UTF-8"));
+ request.addHeader("Content-Type", "text/plain; charset=utf-8");
+ request.addHeader("Accept", "text/*, */*");
+ MockHttpServletResponse response = new MockHttpServletResponse();
+ getServlet().service(request, response);
+ assertEquals(200, response.getStatus());
+ assertEquals(requestBody, response.getContentAsString());
+ }
+
@Test
public void responseBodyNoAcceptableMediaType() throws ServletException, IOException {
initServlet(new ApplicationContextInitializer() {
@@ -1248,7 +1263,7 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl
assertEquals("Content-Type=[text/html],Custom-Header=[value21,value22]", response.getContentAsString());
}
-
+
@Test
public void requestMappingInterface() throws Exception {
initServletWithControllers(IMyControllerImpl.class);
@@ -1298,7 +1313,7 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl
assertEquals("handle", response.getContentAsString());
}
-
+
@Test
public void trailingSlash() throws Exception {
initServletWithControllers(TrailingSlashController.class);
@@ -1444,7 +1459,7 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/");
MockHttpServletResponse response = new MockHttpServletResponse();
getServlet().service(request, response);
-
+
assertEquals(200, response.getStatus());
assertEquals("home", response.getForwardedUrl());
@@ -1462,11 +1477,11 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl
request.addHeader("Accept", "application/json");
response = new MockHttpServletResponse();
getServlet().service(request, response);
-
+
assertEquals(200, response.getStatus());
assertEquals("application/json", response.getHeader("Content-Type"));
assertEquals("homeJson", response.getContentAsString());
- }
+ }
@Test
public void redirectAttribute() throws Exception {
@@ -1479,7 +1494,7 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl
// POST -> bind error
getServlet().service(request, response);
-
+
assertEquals(200, response.getStatus());
assertEquals("messages/new", response.getForwardedUrl());
assertTrue(RequestContextUtils.getOutputFlashMap(request).isEmpty());
@@ -1516,20 +1531,20 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl
context.registerBeanDefinition("controller", beanDef);
}
});
-
+
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/");
request.addParameter("param", "1");
MockHttpServletResponse response = new MockHttpServletResponse();
getServlet().service(request, response);
-
+
assertEquals("count:3", response.getContentAsString());
response = new MockHttpServletResponse();
getServlet().service(request, response);
-
+
assertEquals("count:3", response.getContentAsString());
}
-
+
/*
* Controllers
*/
@@ -2335,6 +2350,12 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl
public String handle(@RequestBody String body) throws IOException {
return body;
}
+
+ @RequestMapping(value = "/something", method = RequestMethod.PATCH)
+ @ResponseBody
+ public String handlePartialUpdate(@RequestBody String content) throws IOException {
+ return content;
+ }
}
@Controller
@@ -2366,7 +2387,7 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl
@XmlRootElement
public static class A {
-
+
}
@XmlRootElement
@@ -2729,7 +2750,7 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl
public void root(Writer writer) throws IOException {
writer.write("root");
}
-
+
@RequestMapping(value = "/{templatePath}/", method = RequestMethod.GET)
public void templatePath(Writer writer) throws IOException {
writer.write("templatePath");
@@ -2839,7 +2860,7 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl
@Controller
static class HeadersConditionController {
-
+
@RequestMapping(value = "/", method = RequestMethod.GET)
public String home() {
return "home";
@@ -2887,7 +2908,7 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl
public void initBinder(WebDataBinder dataBinder) {
this.count++;
}
-
+
@ModelAttribute
public void populate(Model model) {
this.count++;
@@ -2899,16 +2920,16 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl
writer.write("count:" + this.count);
}
}
-
+
// Test cases deleted from the original SevletAnnotationControllerTests:
-
-// @Ignore("Controller interface => no method-level @RequestMapping annotation")
+
+// @Ignore("Controller interface => no method-level @RequestMapping annotation")
// public void standardHandleMethod() throws Exception {
-
+
// @Ignore("ControllerClassNameHandlerMapping")
// public void emptyRequestMapping() throws Exception {
-// @Ignore("Controller interface => no method-level @RequestMapping annotation")
+// @Ignore("Controller interface => no method-level @RequestMapping annotation")
// public void proxiedStandardHandleMethod() throws Exception {
// @Ignore("ServletException no longer thrown for unmatched parameter constraints")
@@ -2919,10 +2940,10 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl
// @Ignore("Method name dispatching")
// public void methodNameDispatchingControllerWithSuffix() throws Exception {
-
+
// @Ignore("ControllerClassNameHandlerMapping")
// public void controllerClassNamePlusMethodNameDispatchingController() throws Exception {
-
+
// @Ignore("Method name dispatching")
// public void postMethodNameDispatchingController() throws Exception {
diff --git a/src/dist/changelog.txt b/src/dist/changelog.txt
index 9093f76eb7..41190821ca 100644
--- a/src/dist/changelog.txt
+++ b/src/dist/changelog.txt
@@ -11,6 +11,7 @@ Changes in version 3.2 M2
* add JacksonObjectMapperFactoryBean for configuring a Jackson ObjectMapper in XML
* infer return type of parameterized factory methods (SPR-9493)
* add ContentNegotiationManager/ContentNegotiationStrategy to resolve requested media types
+* add support for the HTTP PATCH method
Changes in version 3.2 M1 (2012-05-28)
--------------------------------------
diff --git a/src/reference/docbook/mvc.xml b/src/reference/docbook/mvc.xml
index 9b5133a8ee..3b58280cb1 100644
--- a/src/reference/docbook/mvc.xml
+++ b/src/reference/docbook/mvc.xml
@@ -1975,7 +1975,7 @@ public class EditPetForm {
ServletRequest.getParameter*() family of methods to
support form field access only for HTTP POST, not for HTTP PUT.
- To support HTTP PUT requests, the spring-web
+ To support HTTP PUT and PATCH requests, the spring-web
module provides the filter
HttpPutFormContentFilter, which can be
configured in web.xml:
@@ -1995,7 +1995,7 @@ public class EditPetForm {
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>
- The above filter intercepts HTTP PUT requests with content type
+ The above filter intercepts HTTP PUT and PATCH requests with content type
application/x-www-form-urlencoded, reads the form
data from the body of the request, and wraps the
ServletRequest in order to make the form data