diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/annotation/RequestMapping.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/annotation/RequestMapping.java
index 6adc9606d41..c5d0e0c4c35 100644
--- a/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/annotation/RequestMapping.java
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/annotation/RequestMapping.java
@@ -230,4 +230,25 @@ public @interface RequestMapping {
*/
String[] params() default {};
+ /**
+ * The headers of the mapped request, narrowing the primary mapping.
+ *
Same format for any environment: a sequence of "My-Header=myValue" style
+ * expressions, with a request only mapped if each such header is found
+ * to have the given value. "My-Header" style expressions are also supported,
+ * with such headers having to be present in the request (allowed to have
+ * any value). Finally, "!My-Header" style expressions indicate that the
+ * specified header is not supposed to be present in the request.
+ *
Also supports media type wildcards (*), for headers such as Accept
+ * and Content-Type. For instance,
+ *
+ * @RequestMapping(value = "/something", headers = "content-type=text/*")
+ *
+ * will match requests with a Content-Type of "text/html", "text/plain", etc.
+ * Supported at the type level as well as at the method level!
+ * When used at the type level, all method-level mappings inherit
+ * this header restriction (i.e. the type-level restriction
+ * gets checked before the handler method is even resolved).
+ * @see org.springframework.http.MediaType
+ */
+ String[] headers() default {};
}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/annotation/AnnotationMethodHandlerAdapter.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/annotation/AnnotationMethodHandlerAdapter.java
index 2b936b5de37..e712a60ff4f 100644
--- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/annotation/AnnotationMethodHandlerAdapter.java
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/annotation/AnnotationMethodHandlerAdapter.java
@@ -396,12 +396,15 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
if (!hasTypeLevelMapping() || !Arrays.equals(mapping.params(), getTypeLevelMapping().params())) {
mappingInfo.params = mapping.params();
}
+ if (!hasTypeLevelMapping() || !Arrays.equals(mapping.headers(), getTypeLevelMapping().headers())) {
+ mappingInfo.headers = mapping.headers();
+ }
boolean match = false;
if (mappingInfo.paths.length > 0) {
List matchedPaths = new ArrayList(mappingInfo.paths.length);
for (String methodLevelPattern : mappingInfo.paths) {
if (isPathMatch(methodLevelPattern, lookupPath)) {
- if (checkParameters(mappingInfo, request)) {
+ if (mappingInfo.matches(request)) {
match = true;
matchedPaths.add(methodLevelPattern);
}
@@ -418,7 +421,7 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
}
else {
// No paths specified: parameter match sufficient.
- match = checkParameters(mappingInfo, request);
+ match = mappingInfo.matches(request);
if (match && mappingInfo.methods.length == 0 && mappingInfo.params.length == 0 &&
resolvedMethodName != null && !resolvedMethodName.equals(handlerMethod.getName())) {
match = false;
@@ -514,11 +517,6 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
return false;
}
- private boolean checkParameters(RequestMappingInfo mapping, HttpServletRequest request) {
- return ServletAnnotationMappingUtils.checkRequestMethod(mapping.methods, request) &&
- ServletAnnotationMappingUtils.checkParameters(mapping.params, request);
- }
-
@SuppressWarnings("unchecked")
private void extractHandlerMethodUriTemplates(String mappedPath,
String lookupPath,
@@ -726,21 +724,29 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
String[] params = new String[0];
+ String[] headers = new String[0];
+
String bestMatchedPath() {
return matchedPaths.length > 0 ? matchedPaths[0] : null;
}
+ public boolean matches(HttpServletRequest request) {
+ return ServletAnnotationMappingUtils.checkRequestMethod(this.methods, request) &&
+ ServletAnnotationMappingUtils.checkParameters(this.params, request) &&
+ ServletAnnotationMappingUtils.checkHeaders(this.headers, request);
+ }
+
@Override
public boolean equals(Object obj) {
RequestMappingInfo other = (RequestMappingInfo) obj;
return (Arrays.equals(this.paths, other.paths) && Arrays.equals(this.methods, other.methods) &&
- Arrays.equals(this.params, other.params));
+ Arrays.equals(this.params, other.params) && Arrays.equals(this.headers, other.headers));
}
@Override
public int hashCode() {
- return (Arrays.hashCode(this.paths) * 29 + Arrays.hashCode(this.methods) * 31 +
- Arrays.hashCode(this.params));
+ return (Arrays.hashCode(this.paths) * 23 + Arrays.hashCode(this.methods) * 29 +
+ Arrays.hashCode(this.params) * 31 + Arrays.hashCode(this.headers));
}
}
@@ -777,14 +783,21 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
}
else if (info1MethodCount == 1 & info2MethodCount > 1) {
return -1;
-
}
else if (info2MethodCount == 1 & info1MethodCount > 1) {
return 1;
}
int info1ParamCount = info1.params.length;
int info2ParamCount = info2.params.length;
- return (info1ParamCount < info2ParamCount ? 1 : (info1ParamCount == info2ParamCount ? 0 : -1));
+ if (info1ParamCount != info2ParamCount) {
+ return info2ParamCount - info1ParamCount;
+ }
+ int info1HeaderCount = info1.headers.length;
+ int info2HeaderCount = info2.headers.length;
+ if (info1HeaderCount != info2HeaderCount) {
+ return info2HeaderCount - info1HeaderCount;
+ }
+ return 0;
}
}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/annotation/ServletAnnotationMappingUtils.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/annotation/ServletAnnotationMappingUtils.java
index 70cf9526a68..9fa4ab88f17 100644
--- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/annotation/ServletAnnotationMappingUtils.java
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/annotation/ServletAnnotationMappingUtils.java
@@ -16,8 +16,11 @@
package org.springframework.web.servlet.mvc.annotation;
+import java.util.Iterator;
+import java.util.List;
import javax.servlet.http.HttpServletRequest;
+import org.springframework.http.MediaType;
import org.springframework.util.ObjectUtils;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.util.WebUtils;
@@ -32,6 +35,7 @@ abstract class ServletAnnotationMappingUtils {
/**
* Check whether the given request matches the specified request methods.
+ *
* @param methods the HTTP request methods to check against
* @param request the current HTTP request to check
*/
@@ -49,8 +53,8 @@ abstract class ServletAnnotationMappingUtils {
/**
* Check whether the given request matches the specified parameter conditions.
- * @param params the parameter conditions, following
- * {@link org.springframework.web.bind.annotation.RequestMapping#params()}
+ *
+ * @param params the parameter conditions, following {@link org.springframework.web.bind.annotation.RequestMapping#params()}
* @param request the current HTTP request to check
*/
public static boolean checkParameters(String[] params, HttpServletRequest request) {
@@ -79,4 +83,62 @@ abstract class ServletAnnotationMappingUtils {
return true;
}
+ /**
+ * Check whether the given request matches the specified header conditions.
+ *
+ * @param headers the header conditions, following {@link org.springframework.web.bind.annotation.RequestMapping#headers()}
+ * @param request the current HTTP request to check
+ */
+ public static boolean checkHeaders(String[] headers, HttpServletRequest request) {
+ if (!ObjectUtils.isEmpty(headers)) {
+ for (String header : headers) {
+ int separator = header.indexOf('=');
+ if (separator == -1) {
+ if (header.startsWith("!")) {
+ if (hasHeader(request, header.substring(1))) {
+ return false;
+ }
+ }
+ else if (!hasHeader(request, header)) {
+ return false;
+ }
+ }
+ else {
+ String key = header.substring(0, separator);
+ String value = header.substring(separator + 1);
+ if (isMediaTypeHeader(key)) {
+ List requestMediaTypes = MediaType.parseMediaTypes(request.getHeader(key));
+ List valueMediaTypes = MediaType.parseMediaTypes(value);
+ boolean found = false;
+ for (Iterator valIter = valueMediaTypes.iterator(); valIter.hasNext() && !found;) {
+ MediaType valueMediaType = valIter.next();
+ for (Iterator reqIter = requestMediaTypes.iterator(); reqIter.hasNext() && !found;) {
+ MediaType requestMediaType = reqIter.next();
+ if (valueMediaType.includes(requestMediaType)) {
+ found = true;
+ }
+ }
+
+ }
+ if (!found) {
+ return false;
+ }
+ }
+ else if (!value.equals(request.getHeader(key))) {
+ return false;
+ }
+ }
+ }
+ }
+ return true;
+ }
+
+ private static boolean hasHeader(HttpServletRequest request, String headerName) {
+ return request.getHeader(headerName) != null;
+ }
+
+ private static boolean isMediaTypeHeader(String headerName) {
+ return "Accept".equalsIgnoreCase(headerName) || "Content-Type".equalsIgnoreCase(headerName);
+ }
+
}
diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/annotation/ServletAnnotationControllerTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/annotation/ServletAnnotationControllerTests.java
index 62dd293a9ca..5ee4c427c6f 100644
--- a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/annotation/ServletAnnotationControllerTests.java
+++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/annotation/ServletAnnotationControllerTests.java
@@ -894,6 +894,23 @@ public class ServletAnnotationControllerTests {
assertEquals("Invalid response status code", HttpServletResponse.SC_BAD_REQUEST, response.getStatus());
}
+ @Test
+ public void headers() throws ServletException, IOException {
+ initServlet(HeadersController.class);
+
+ MockHttpServletRequest request = new MockHttpServletRequest("GET", "/something");
+ request.addHeader("Content-Type", "application/pdf");
+ MockHttpServletResponse response = new MockHttpServletResponse();
+ servlet.service(request, response);
+ assertEquals("pdf", response.getContentAsString());
+
+ request = new MockHttpServletRequest("GET", "/something");
+ request.addHeader("Content-Type", "text/html");
+ response = new MockHttpServletResponse();
+ servlet.service(request, response);
+ assertEquals("text", response.getContentAsString());
+ }
+
/*
* Controllers
*/
@@ -1472,6 +1489,20 @@ public class ServletAnnotationControllerTests {
}
}
+ @Controller
+ public static class HeadersController {
+
+ @RequestMapping(value = "/something", headers = "content-type=application/pdf")
+ public void handlePdf(Writer writer) throws IOException {
+ writer.write("pdf");
+ }
+
+ @RequestMapping(value = "/something", headers = "content-type=text/*")
+ public void handleHtml(Writer writer) throws IOException {
+ writer.write("text");
+ }
+ }
+
public static class MyMessageConverter implements HttpMessageConverter {
diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/annotation/ServletAnnotationMappingUtilsTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/annotation/ServletAnnotationMappingUtilsTests.java
index 294006af299..f11dc01bfb2 100644
--- a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/annotation/ServletAnnotationMappingUtilsTests.java
+++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/annotation/ServletAnnotationMappingUtilsTests.java
@@ -1,7 +1,22 @@
+/*
+ * Copyright 2002-2009 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.web.servlet.mvc.annotation;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.*;
import org.junit.Test;
import org.springframework.mock.web.MockHttpServletRequest;
@@ -26,4 +41,96 @@ public class ServletAnnotationMappingUtilsTests {
assertFalse("Invalid request method result", result);
}
+ @Test
+ public void checkParametersSimpleMatch() {
+ MockHttpServletRequest request = new MockHttpServletRequest("GET", "/");
+ request.addParameter("param1", "value1");
+ String[] params = new String[]{"param1", "!param2"};
+ boolean result = ServletAnnotationMappingUtils.checkParameters(params, request);
+ assertTrue("Invalid request method result", result);
+ }
+
+ @Test
+ public void checkParametersSimpleNoMatch() {
+ MockHttpServletRequest request = new MockHttpServletRequest("GET", "/");
+ request.addParameter("param1", "value1");
+ request.addParameter("param2", "value2");
+ String[] params = new String[]{"param1", "!param2"};
+ boolean result = ServletAnnotationMappingUtils.checkParameters(params, request);
+ assertFalse("Invalid request method result", result);
+ }
+
+ @Test
+ public void checkParametersKeyValueMatch() {
+ MockHttpServletRequest request = new MockHttpServletRequest("GET", "/");
+ request.addParameter("param1", "value1");
+ String[] params = new String[]{"param1=value1"};
+ boolean result = ServletAnnotationMappingUtils.checkParameters(params, request);
+ assertTrue("Invalid request method result", result);
+ }
+
+ @Test
+ public void checkParametersKeyValueNoMatch() {
+ MockHttpServletRequest request = new MockHttpServletRequest("GET", "/");
+ request.addParameter("param1", "value1");
+ String[] params = new String[]{"param1=foo"};
+ boolean result = ServletAnnotationMappingUtils.checkParameters(params, request);
+ assertFalse("Invalid request method result", result);
+ }
+
+ @Test
+ public void checkHeadersSimpleMatch() {
+ MockHttpServletRequest request = new MockHttpServletRequest("GET", "/");
+ request.addHeader("header1", "value1");
+ String[] headers = new String[]{"header1", "!header2"};
+ boolean result = ServletAnnotationMappingUtils.checkHeaders(headers, request);
+ assertTrue("Invalid request method result", result);
+ }
+
+ @Test
+ public void checkHeadersSimpleNoMatch() {
+ MockHttpServletRequest request = new MockHttpServletRequest("GET", "/");
+ request.addHeader("header1", "value1");
+ request.addHeader("header2", "value2");
+ String[] headers = new String[]{"header1", "!header2"};
+ boolean result = ServletAnnotationMappingUtils.checkHeaders(headers, request);
+ assertFalse("Invalid request method result", result);
+ }
+
+ @Test
+ public void checkHeadersKeyValueMatch() {
+ MockHttpServletRequest request = new MockHttpServletRequest("GET", "/");
+ request.addHeader("header1", "value1");
+ String[] headers = new String[]{"header1=value1"};
+ boolean result = ServletAnnotationMappingUtils.checkHeaders(headers, request);
+ assertTrue("Invalid request method result", result);
+ }
+
+ @Test
+ public void checkHeadersKeyValueNoMatch() {
+ MockHttpServletRequest request = new MockHttpServletRequest("GET", "/");
+ request.addHeader("header1", "value1");
+ String[] headers = new String[]{"header1=foo"};
+ boolean result = ServletAnnotationMappingUtils.checkHeaders(headers, request);
+ assertFalse("Invalid request method result", result);
+ }
+
+ @Test
+ public void checkHeadersAcceptMatch() {
+ MockHttpServletRequest request = new MockHttpServletRequest("GET", "/");
+ request.addHeader("Accept", "application/pdf, text/html");
+ String[] headers = new String[]{"accept=text/html, application/*"};
+ boolean result = ServletAnnotationMappingUtils.checkHeaders(headers, request);
+ assertTrue("Invalid request method result", result);
+ }
+
+ @Test
+ public void checkHeadersAcceptNoMatch() {
+ MockHttpServletRequest request = new MockHttpServletRequest("GET", "/");
+ request.addHeader("Accept", "application/pdf, text/html");
+ String[] headers = new String[]{"accept=audio/basic, application/xml"};
+ boolean result = ServletAnnotationMappingUtils.checkHeaders(headers, request);
+ assertFalse("Invalid request method result", result);
+ }
+
}