diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/condition/AbstractRequestCondition.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/condition/AbstractRequestCondition.java index 72c3dae280b..d6c74efa5f7 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/condition/AbstractRequestCondition.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/condition/AbstractRequestCondition.java @@ -20,7 +20,8 @@ import java.util.Collection; import java.util.Iterator; /** - * Base class for {@link RequestCondition}s. + * A base class for {@link RequestCondition} types providing implementations of + * {@link #equals(Object)}, {@link #hashCode()}, and {@link #toString()}. * * @author Rossen Stoyanchev * @since 3.1 diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/condition/ConsumesRequestCondition.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/condition/ConsumesRequestCondition.java index ef150a590de..a61e015e9cf 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/condition/ConsumesRequestCondition.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/condition/ConsumesRequestCondition.java @@ -32,13 +32,11 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.servlet.mvc.condition.HeadersRequestCondition.HeaderExpression; /** - * A logical disjunction (' || ') request condition to match requests against consumable media type expressions. - * - *
For details on the syntax of the expressions see {@link RequestMapping#consumes()}. If the condition is - * created with 0 consumable media type expressions, it matches to every request. - * - *
This request condition is also capable of parsing header expressions specifically selecting 'Content-Type'
- * header expressions and converting them to consumable media type expressions.
+ * A logical disjunction (' || ') request condition to match a request's 'Content-Type'
+ * header to a list of media type expressions. Two kinds of media type expressions are
+ * supported, which are described in {@link RequestMapping#consumes()} and
+ * {@link RequestMapping#headers()} where the header name is 'Content-Type'.
+ * Regardless of which syntax is used, the semantics are the same.
*
* @author Arjen Poutsma
* @author Rossen Stoyanchev
@@ -49,26 +47,27 @@ public final class ConsumesRequestCondition extends AbstractRequestCondition It is assumed that both instances have been obtained via {@link #getMatchingCondition(HttpServletRequest)}
- * and each instance contains the matching consumable media type expression only or is otherwise empty.
+ * It is assumed that both instances have been obtained via
+ * {@link #getMatchingCondition(HttpServletRequest)} and each instance contains
+ * the matching consumable media type expression only or is otherwise empty.
*/
public int compareTo(ConsumesRequestCondition other, HttpServletRequest request) {
if (expressions.isEmpty() && other.expressions.isEmpty()) {
@@ -182,8 +185,7 @@ public final class ConsumesRequestCondition extends AbstractRequestCondition Note: when parsing header expressions, {@code "Accept"} and {@code "Content-Type"} header expressions
- * are filtered out. Those should be converted and used as "produces" and "consumes" conditions instead.
- * See the constructors for {@link ProducesRequestCondition} and {@link ConsumesRequestCondition}.
+ * Expressions passed to the constructor with header names 'Accept' or
+ * 'Content-Type' are ignored. See {@link ConsumesRequestCondition} and
+ * {@link ProducesRequestCondition} for those.
*
* @author Arjen Poutsma
* @author Rossen Stoyanchev
@@ -44,13 +42,11 @@ public final class HeadersRequestCondition extends AbstractRequestCondition Note: {@code "Accept"} and {@code "Content-Type"} header expressions are filtered out.
- * Those should be converted and used as "produces" and "consumes" conditions instead.
- * See the constructors for {@link ProducesRequestCondition} and {@link ConsumesRequestCondition}.
- *
- * @param headers 0 or more header expressions; if 0, the condition will match to every request.
+ * Create a new instance from the given header expressions. Expressions with
+ * header names 'Accept' or 'Content-Type' are ignored. See {@link ConsumesRequestCondition}
+ * and {@link ProducesRequestCondition} for those.
+ * @param headers media type expressions with syntax defined in {@link RequestMapping#headers()};
+ * if 0, the condition will match to every request.
*/
public HeadersRequestCondition(String... headers) {
this(parseExpressions(headers));
@@ -85,7 +81,8 @@ public final class HeadersRequestCondition extends AbstractRequestCondition It is assumed that both instances have been obtained via {@link #getMatchingCondition(HttpServletRequest)}
- * and each instance contains the matching header expression only or is otherwise empty.
+ * It is assumed that both instances have been obtained via
+ * {@link #getMatchingCondition(HttpServletRequest)} and each instance
+ * contains the matching header expression only or is otherwise empty.
*/
public int compareTo(HeadersRequestCondition other, HttpServletRequest request) {
return other.expressions.size() - this.expressions.size();
}
/**
- * Parsing and request matching logic for header expressions.
- * @see RequestMapping#headers()
+ * Parses and matches a single header expression to a request.
*/
static class HeaderExpression extends AbstractNameValueExpression For details on the syntax of the expressions see {@link RequestMapping#params()}. If the condition is
- * created with 0 parameter expressions, it will match to every request.
+ * A logical conjunction (' && ') request condition that matches a request against
+ * a set parameter expressions with syntax defined in {@link RequestMapping#params()}.
*
* @author Arjen Poutsma
* @author Rossen Stoyanchev
@@ -41,9 +39,9 @@ public final class ParamsRequestCondition extends AbstractRequestCondition expressions;
/**
- * Create a {@link ParamsRequestCondition} with the given param expressions.
- *
- * @param params 0 or more param expressions; if 0, the condition will match to every request.
+ * Create a new instance from the given param expressions.
+ * @param params expressions with syntax defined in {@link RequestMapping#params()};
+ * if 0, the condition will match to every request.
*/
public ParamsRequestCondition(String... params) {
this(parseExpressions(params));
@@ -74,7 +72,8 @@ public final class ParamsRequestCondition extends AbstractRequestCondition set = new LinkedHashSet It is assumed that both instances have been obtained via {@link #getMatchingCondition(HttpServletRequest)}
- * and each instance contains the matching parameter expressions only or is otherwise empty.
+ * It is assumed that both instances have been obtained via
+ * {@link #getMatchingCondition(HttpServletRequest)} and each instance
+ * contains the matching parameter expressions only or is otherwise empty.
*/
public int compareTo(ParamsRequestCondition other, HttpServletRequest request) {
return other.expressions.size() - this.expressions.size();
}
/**
- * Parsing and request matching logic for parameter expressions.
- * @see RequestMapping#params()
+ * Parses and matches a single param expression to a request.
*/
static class ParamExpression extends AbstractNameValueExpression See Javadoc on individual methods for details on how URL patterns are matched, combined, and compared.
+ * A logical disjunction (' || ') request condition that matches a request
+ * against a set of URL path patterns.
*
* @author Rossen Stoyanchev
* @since 3.1
@@ -52,9 +51,17 @@ public final class PatternsRequestCondition extends AbstractRequestCondition It is assumed that both instances have been obtained via
+ * {@link #getMatchingCondition(HttpServletRequest)} to ensure they
+ * contain only patterns that match the request and are sorted with
+ * the best matches on top.
*/
public int compareTo(PatternsRequestCondition other, HttpServletRequest request) {
String lookupPath = urlPathHelper.getLookupPathForRequest(request);
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/condition/ProducesRequestCondition.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/condition/ProducesRequestCondition.java
index 70f8b5b8294..d9aef6fc8f6 100644
--- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/condition/ProducesRequestCondition.java
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/condition/ProducesRequestCondition.java
@@ -17,7 +17,6 @@
package org.springframework.web.servlet.mvc.condition;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
@@ -33,14 +32,11 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.mvc.condition.HeadersRequestCondition.HeaderExpression;
/**
- * A logical disjunction (' || ') request condition to match requests against producible
- * media type expressions.
- *
- * For details on the syntax of the expressions see {@link RequestMapping#consumes()}.
- * If the condition is created without media type expressions, it matches to every request.
- *
- * This request condition is also capable of parsing header expressions by selecting
- * 'Accept' header expressions and converting them to prodicuble media type expressions.
+ * A logical disjunction (' || ') request condition to match a request's 'Accept' header
+ * to a list of media type expressions. Two kinds of media type expressions are
+ * supported, which are described in {@link RequestMapping#produces()} and
+ * {@link RequestMapping#headers()} where the header name is 'Accept'.
+ * Regardless of which syntax is used, the semantics are the same.
*
* @author Arjen Poutsma
* @author Rossen Stoyanchev
@@ -51,26 +47,27 @@ public final class ProducesRequestCondition extends AbstractRequestCondition It is assumed that both instances have been obtained via {@link #getMatchingCondition(HttpServletRequest)}
- * and each instance contains the matching producible media type expression only or is otherwise empty.
+ * If a request media type is {@link MediaType#ALL} or if there is no 'Accept'
+ * header, and therefore both conditions match, preference is given to one
+ * Produces condition if it is empty and the other one is not.
+ *
+ * It is assumed that both instances have been obtained via
+ * {@link #getMatchingCondition(HttpServletRequest)} and each instance
+ * contains the matching producible media type expression only or
+ * is otherwise empty.
*/
public int compareTo(ProducesRequestCondition other, HttpServletRequest request) {
String acceptHeader = request.getHeader("Accept");
List Request conditions can be combined via {@link #combine(Object)}, matched to a request via
- * {@link #getMatchingCondition(HttpServletRequest)}, and compared to each other via
- * {@link #compareTo(Object, HttpServletRequest)} to determine which matches a request more closely.
+ * Request conditions can be combined via {@link #combine(Object)}, matched to
+ * a request via {@link #getMatchingCondition(HttpServletRequest)}, and compared
+ * to each other via {@link #compareTo(Object, HttpServletRequest)} to determine
+ * which matches a request more closely.
*
- * @param If the condition is created with 0 HTTP request methods, it matches to every request.
+ * A logical disjunction (' || ') request condition that matches a request
+ * against a set of {@link RequestMethod}s.
*
* @author Arjen Poutsma
* @author Rossen Stoyanchev
@@ -41,8 +40,9 @@ public final class RequestMethodsRequestCondition extends AbstractRequestConditi
private final Set It is assumed that both instances have been obtained via {@link #getMatchingCondition(HttpServletRequest)}
- * and therefore each instance contains the matching HTTP request method only or is otherwise empty.
+ * It is assumed that both instances have been obtained via
+ * {@link #getMatchingCondition(HttpServletRequest)} and therefore each instance
+ * contains the matching HTTP request method only or is otherwise empty.
*/
public int compareTo(RequestMethodsRequestCondition other, HttpServletRequest request) {
return other.methods.size() - this.methods.size();
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 b0386b5f945..f1450ed4ab7 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
@@ -16,6 +16,14 @@
package org.springframework.web.servlet.mvc.annotation;
+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.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
import java.beans.PropertyEditorSupport;
import java.io.IOException;
import java.io.Serializable;
@@ -39,6 +47,7 @@ import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
+
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
@@ -51,7 +60,6 @@ import javax.validation.constraints.NotNull;
import javax.xml.bind.annotation.XmlRootElement;
import org.junit.Test;
-
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.aop.interceptor.SimpleTraceInterceptor;
import org.springframework.aop.support.DefaultPointcutAdvisor;
@@ -133,8 +141,6 @@ import org.springframework.web.servlet.mvc.support.ControllerClassNameHandlerMap
import org.springframework.web.servlet.view.InternalResourceViewResolver;
import org.springframework.web.util.NestedServletException;
-import static org.junit.Assert.*;
-
/**
* @author Juergen Hoeller
* @author Sam Brannen
@@ -1869,6 +1875,40 @@ public class ServletAnnotationControllerTests {
servlet.service(request, response);
assertEquals(405, response.getStatus());
}
+
+ // SPR-8536
+
+ @Test
+ public void testHeadersCondition() throws Exception {
+ initServlet(HeadersConditionController.class);
+
+ // No "Accept" header
+ MockHttpServletRequest request = new MockHttpServletRequest("GET", "/");
+ MockHttpServletResponse response = new MockHttpServletResponse();
+ servlet.service(request, response);
+
+ assertEquals(200, response.getStatus());
+ assertEquals("home", response.getForwardedUrl());
+
+ // Accept "*/*"
+ request = new MockHttpServletRequest("GET", "/");
+ request.addHeader("Accept", "*/*");
+ response = new MockHttpServletResponse();
+ servlet.service(request, response);
+
+ assertEquals(200, response.getStatus());
+ assertEquals("home", response.getForwardedUrl());
+
+ // Accept "application/json"
+ request = new MockHttpServletRequest("GET", "/");
+ request.addHeader("Accept", "application/json");
+ response = new MockHttpServletResponse();
+ servlet.service(request, response);
+
+ assertEquals(200, response.getStatus());
+ assertEquals("application/json", response.getHeader("Content-Type"));
+ assertEquals("homeJson", response.getContentAsString());
+ }
/*
@@ -3151,5 +3191,20 @@ public class ServletAnnotationControllerTests {
writer.write("handle2");
}
}
+
+ @Controller
+ static class HeadersConditionController {
+
+ @RequestMapping(value = "/", method = RequestMethod.GET)
+ public String home() {
+ return "home";
+ }
+
+ @RequestMapping(value = "/", method = RequestMethod.GET, headers="Accept=application/json")
+ @ResponseBody
+ public String homeJson() {
+ return "homeJson";
+ }
+ }
}
diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/condition/ProducesRequestConditionTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/condition/ProducesRequestConditionTests.java
index 4e57104a4c9..91a8dc71410 100644
--- a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/condition/ProducesRequestConditionTests.java
+++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/condition/ProducesRequestConditionTests.java
@@ -120,7 +120,7 @@ public class ProducesRequestConditionTests {
}
@Test
- public void compareToSingle() {
+ public void compareToWithSingleExpression() {
MockHttpServletRequest request = new MockHttpServletRequest();
request.addHeader("Accept", "text/plain");
@@ -135,7 +135,7 @@ public class ProducesRequestConditionTests {
}
@Test
- public void compareToMultiple() {
+ public void compareToMultipleExpressions() {
ProducesRequestCondition condition1 = new ProducesRequestCondition("*/*", "text/plain");
ProducesRequestCondition condition2 = new ProducesRequestCondition("text/*", "text/plain;q=0.7");
@@ -147,22 +147,10 @@ public class ProducesRequestConditionTests {
result = condition2.compareTo(condition1, request);
assertTrue("Invalid comparison result: " + result, result > 0);
-
- condition1 = new ProducesRequestCondition("*/*");
- condition2 = new ProducesRequestCondition("text/*");
-
- request = new MockHttpServletRequest();
- request.addHeader("Accept", "text/*");
-
- result = condition1.compareTo(condition2, request);
- assertTrue("Invalid comparison result: " + result, result > 0);
-
- result = condition2.compareTo(condition1, request);
- assertTrue("Invalid comparison result: " + result, result < 0);
}
@Test
- public void compareToMultipleAccept() {
+ public void compareToMultipleExpressionsAndMultipeAcceptHeaderValues() {
ProducesRequestCondition condition1 = new ProducesRequestCondition("text/*", "text/plain");
ProducesRequestCondition condition2 = new ProducesRequestCondition("application/*", "application/xml");
@@ -186,7 +174,36 @@ public class ProducesRequestConditionTests {
result = condition2.compareTo(condition1, request);
assertTrue("Invalid comparison result: " + result, result < 0);
}
-
+
+ @Test
+ public void compareToEmptyCondition() {
+ MockHttpServletRequest request = new MockHttpServletRequest();
+ request.addHeader("Accept", "*/*");
+
+ ProducesRequestCondition condition1 = new ProducesRequestCondition();
+ ProducesRequestCondition condition2 = new ProducesRequestCondition("application/json");
+
+ int result = condition1.compareTo(condition2, request);
+ assertTrue("Invalid comparison result: " + result, result < 0);
+
+ result = condition2.compareTo(condition1, request);
+ assertTrue("Invalid comparison result: " + result, result > 0);
+ }
+
+ @Test
+ public void compareToWithoutAcceptHeader() {
+ MockHttpServletRequest request = new MockHttpServletRequest();
+
+ ProducesRequestCondition condition1 = new ProducesRequestCondition();
+ ProducesRequestCondition condition2 = new ProducesRequestCondition("application/json");
+
+ int result = condition1.compareTo(condition2, request);
+ assertTrue("Invalid comparison result: " + result, result < 0);
+
+ result = condition2.compareTo(condition1, request);
+ assertTrue("Invalid comparison result: " + result, result > 0);
+ }
+
@Test
public void combine() {
ProducesRequestCondition condition1 = new ProducesRequestCondition("text/plain");
diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletAnnotationControllerHandlerMethodTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletAnnotationControllerHandlerMethodTests.java
index c3e198278f2..12ff2acf5bd 100644
--- a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletAnnotationControllerHandlerMethodTests.java
+++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletAnnotationControllerHandlerMethodTests.java
@@ -1419,6 +1419,40 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl
assertEquals(405, response.getStatus());
}
+ // SPR-8536
+
+ @Test
+ public void testHeadersCondition() throws Exception {
+ initServletWithControllers(HeadersConditionController.class);
+
+ // No "Accept" header
+ MockHttpServletRequest request = new MockHttpServletRequest("GET", "/");
+ MockHttpServletResponse response = new MockHttpServletResponse();
+ getServlet().service(request, response);
+
+ assertEquals(200, response.getStatus());
+ assertEquals("home", response.getForwardedUrl());
+
+ // Accept "*/*"
+ request = new MockHttpServletRequest("GET", "/");
+ request.addHeader("Accept", "*/*");
+ response = new MockHttpServletResponse();
+ getServlet().service(request, response);
+
+ assertEquals(200, response.getStatus());
+ assertEquals("home", response.getForwardedUrl());
+
+ // Accept "application/json"
+ request = new MockHttpServletRequest("GET", "/");
+ 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());
+ }
+
/*
* Controllers
@@ -2713,6 +2747,21 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl
}
}
+ @Controller
+ static class HeadersConditionController {
+
+ @RequestMapping(value = "/", method = RequestMethod.GET)
+ public String home() {
+ return "home";
+ }
+
+ @RequestMapping(value = "/", method = RequestMethod.GET, headers="Accept=application/json")
+ @ResponseBody
+ public String homeJson() {
+ return "homeJson";
+ }
+ }
+
// Test cases deleted from the original SevletAnnotationControllerTests:
// @Ignore("Controller interface => no method-level @RequestMapping annotation")
*
*
* @param request the current request
*
* @return the same instance if the condition contains no patterns;
- * or a new condition with sorted matching patterns; or {@code null} if no patterns match.
+ * or a new condition with sorted matching patterns;
+ * or {@code null} if no patterns match.
*/
public PatternsRequestCondition getMatchingCondition(HttpServletRequest request) {
if (patterns.isEmpty()) {
@@ -207,13 +207,16 @@ public final class PatternsRequestCondition extends AbstractRequestCondition
+ *
+ *
+ *