Add support for matrix variables

A new @MatrixVariable annotation allows injecting matrix variables
into @RequestMapping methods. The matrix variables may appear in any
path segment and should be wrapped in a URI template for request
mapping purposes to ensure request matching is not affected by the
order or the presence/absence of such variables. The @MatrixVariable
annotation has an optional "pathVar" attribute that can be used to
refer to the URI template where a matrix variable is located.

Previously, ";" (semicolon) delimited content was removed from the
path used for request mapping purposes. To preserve backwards
compatibility that continues to be the case (except for the MVC
namespace and Java config) and may be changed by setting the
"removeSemicolonContent" property of RequestMappingHandlerMapping to
"false". Applications using the  MVC namespace and Java config do not
need to do anything further to extract and use matrix variables.

Issue: SPR-5499, SPR-7818
This commit is contained in:
Rossen Stoyanchev
2012-08-26 16:22:37 -04:00
parent da05b094f5
commit 2201dd8c45
29 changed files with 1392 additions and 116 deletions

View File

@@ -45,7 +45,7 @@ public class MappedInterceptorTests {
}
@Test
public void includePatternOnly() {
public void includePattern() {
MappedInterceptor mappedInterceptor = new MappedInterceptor(new String[] { "/foo/*" }, this.interceptor);
assertTrue(mappedInterceptor.matches("/foo/bar", pathMatcher));
@@ -53,7 +53,13 @@ public class MappedInterceptorTests {
}
@Test
public void excludePatternOnly() {
public void includePatternWithMatrixVariables() {
MappedInterceptor mappedInterceptor = new MappedInterceptor(new String[] { "/foo*/*" }, this.interceptor);
assertTrue(mappedInterceptor.matches("/foo;q=1/bar;s=2", pathMatcher));
}
@Test
public void excludePattern() {
MappedInterceptor mappedInterceptor = new MappedInterceptor(null, new String[] { "/admin/**" }, this.interceptor);
assertTrue(mappedInterceptor.matches("/foo", pathMatcher));

View File

@@ -55,6 +55,15 @@ public class UrlFilenameViewControllerTests extends TestCase {
assertTrue(mv.getModel().isEmpty());
}
public void testWithFilenameAndMatrixVariables() throws Exception {
UrlFilenameViewController ctrl = new UrlFilenameViewController();
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/index;a=A;b=B");
MockHttpServletResponse response = new MockHttpServletResponse();
ModelAndView mv = ctrl.handleRequest(request, response);
assertEquals("index", mv.getViewName());
assertTrue(mv.getModel().isEmpty());
}
public void testWithPrefixAndSuffix() throws Exception {
UrlFilenameViewController ctrl = new UrlFilenameViewController();
ctrl.setPrefix("mypre_");

View File

@@ -30,12 +30,15 @@ import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import org.junit.Before;
import org.junit.Test;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.stereotype.Controller;
import org.springframework.util.MultiValueMap;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
@@ -65,7 +68,7 @@ import org.springframework.web.util.UrlPathHelper;
*/
public class RequestMappingInfoHandlerMappingTests {
private TestRequestMappingInfoHandlerMapping mapping;
private TestRequestMappingInfoHandlerMapping handlerMapping;
private Handler handler;
@@ -85,15 +88,16 @@ public class RequestMappingInfoHandlerMappingTests {
this.barMethod = new HandlerMethod(handler, "bar");
this.emptyMethod = new HandlerMethod(handler, "empty");
this.mapping = new TestRequestMappingInfoHandlerMapping();
this.mapping.registerHandler(this.handler);
this.handlerMapping = new TestRequestMappingInfoHandlerMapping();
this.handlerMapping.registerHandler(this.handler);
this.handlerMapping.setRemoveSemicolonContent(false);
}
@Test
public void getMappingPathPatterns() throws Exception {
RequestMappingInfo info = new RequestMappingInfo(
new PatternsRequestCondition("/foo/*", "/foo", "/bar/*", "/bar"), null, null, null, null, null, null);
Set<String> paths = this.mapping.getMappingPathPatterns(info);
Set<String> paths = this.handlerMapping.getMappingPathPatterns(info);
HashSet<String> expected = new HashSet<String>(Arrays.asList("/foo/*", "/foo", "/bar/*", "/bar"));
assertEquals(expected, paths);
@@ -102,25 +106,25 @@ public class RequestMappingInfoHandlerMappingTests {
@Test
public void directMatch() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo");
HandlerMethod hm = (HandlerMethod) this.mapping.getHandler(request).getHandler();
HandlerMethod hm = (HandlerMethod) this.handlerMapping.getHandler(request).getHandler();
assertEquals(this.fooMethod.getMethod(), hm.getMethod());
}
@Test
public void globMatch() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/bar");
HandlerMethod hm = (HandlerMethod) this.mapping.getHandler(request).getHandler();
HandlerMethod hm = (HandlerMethod) this.handlerMapping.getHandler(request).getHandler();
assertEquals(this.barMethod.getMethod(), hm.getMethod());
}
@Test
public void emptyPathMatch() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest("GET", "");
HandlerMethod hm = (HandlerMethod) this.mapping.getHandler(request).getHandler();
HandlerMethod hm = (HandlerMethod) this.handlerMapping.getHandler(request).getHandler();
assertEquals(this.emptyMethod.getMethod(), hm.getMethod());
request = new MockHttpServletRequest("GET", "/");
hm = (HandlerMethod) this.mapping.getHandler(request).getHandler();
hm = (HandlerMethod) this.handlerMapping.getHandler(request).getHandler();
assertEquals(this.emptyMethod.getMethod(), hm.getMethod());
}
@@ -128,7 +132,7 @@ public class RequestMappingInfoHandlerMappingTests {
public void bestMatch() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo");
request.setParameter("p", "anything");
HandlerMethod hm = (HandlerMethod) this.mapping.getHandler(request).getHandler();
HandlerMethod hm = (HandlerMethod) this.handlerMapping.getHandler(request).getHandler();
assertEquals(this.fooParamMethod.getMethod(), hm.getMethod());
}
@@ -136,7 +140,7 @@ public class RequestMappingInfoHandlerMappingTests {
public void requestMethodNotAllowed() throws Exception {
try {
MockHttpServletRequest request = new MockHttpServletRequest("POST", "/bar");
this.mapping.getHandler(request);
this.handlerMapping.getHandler(request);
fail("HttpRequestMethodNotSupportedException expected");
}
catch (HttpRequestMethodNotSupportedException ex) {
@@ -155,7 +159,7 @@ public class RequestMappingInfoHandlerMappingTests {
try {
MockHttpServletRequest request = new MockHttpServletRequest("PUT", url);
request.setContentType("application/json");
this.mapping.getHandler(request);
this.handlerMapping.getHandler(request);
fail("HttpMediaTypeNotSupportedException expected");
}
catch (HttpMediaTypeNotSupportedException ex) {
@@ -175,7 +179,7 @@ public class RequestMappingInfoHandlerMappingTests {
try {
MockHttpServletRequest request = new MockHttpServletRequest("GET", url);
request.addHeader("Accept", "application/json");
this.mapping.getHandler(request);
this.handlerMapping.getHandler(request);
fail("HttpMediaTypeNotAcceptableException expected");
}
catch (HttpMediaTypeNotAcceptableException ex) {
@@ -190,7 +194,7 @@ public class RequestMappingInfoHandlerMappingTests {
RequestMappingInfo key = new RequestMappingInfo(patterns, null, null, null, null, null, null);
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/1/2");
String lookupPath = new UrlPathHelper().getLookupPathForRequest(request);
this.mapping.handleMatch(key, lookupPath, request);
this.handlerMapping.handleMatch(key, lookupPath, request);
@SuppressWarnings("unchecked")
Map<String, String> uriVariables =
@@ -214,8 +218,8 @@ public class RequestMappingInfoHandlerMappingTests {
pathHelper.setUrlDecode(false);
String lookupPath = pathHelper.getLookupPathForRequest(request);
this.mapping.setUrlPathHelper(pathHelper);
this.mapping.handleMatch(key, lookupPath, request);
this.handlerMapping.setUrlPathHelper(pathHelper);
this.handlerMapping.handleMatch(key, lookupPath, request);
@SuppressWarnings("unchecked")
Map<String, String> uriVariables =
@@ -232,7 +236,7 @@ public class RequestMappingInfoHandlerMappingTests {
RequestMappingInfo key = new RequestMappingInfo(patterns, null, null, null, null, null, null);
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/1/2");
this.mapping.handleMatch(key, "/1/2", request);
this.handlerMapping.handleMatch(key, "/1/2", request);
assertEquals("/{path1}/2", request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE));
}
@@ -243,7 +247,7 @@ public class RequestMappingInfoHandlerMappingTests {
RequestMappingInfo key = new RequestMappingInfo(patterns, null, null, null, null, null, null);
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/1/2");
this.mapping.handleMatch(key, "/1/2", request);
this.handlerMapping.handleMatch(key, "/1/2", request);
assertEquals("/1/2", request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE));
}
@@ -252,14 +256,14 @@ public class RequestMappingInfoHandlerMappingTests {
public void producibleMediaTypesAttribute() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/content");
request.addHeader("Accept", "application/xml");
this.mapping.getHandler(request);
this.handlerMapping.getHandler(request);
assertEquals(Collections.singleton(MediaType.APPLICATION_XML),
request.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE));
request = new MockHttpServletRequest("GET", "/content");
request.addHeader("Accept", "application/json");
this.mapping.getHandler(request);
this.handlerMapping.getHandler(request);
assertNull("Negated expression should not be listed as a producible type",
request.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE));
@@ -285,7 +289,74 @@ public class RequestMappingInfoHandlerMappingTests {
assertNull(chain);
}
@SuppressWarnings("unused")
@Test
public void matrixVariables() {
MockHttpServletRequest request;
MultiValueMap<String, String> matrixVariables;
Map<String, String> uriVariables;
String lookupPath = "/cars;colors=red,blue,green;year=2012";
// Pattern "/{cars}" : matrix variables stripped from "cars" variable
request = new MockHttpServletRequest();
testHandleMatch(request, "/{cars}", lookupPath);
matrixVariables = getMatrixVariables(request, "cars");
assertNotNull(matrixVariables);
assertEquals(Arrays.asList("red", "blue", "green"), matrixVariables.get("colors"));
assertEquals("2012", matrixVariables.getFirst("year"));
uriVariables = getUriTemplateVariables(request);
assertEquals("cars", uriVariables.get("cars"));
// Pattern "/{cars:[^;]+}{params}" : "cars" and "params" variables unchanged
request = new MockHttpServletRequest();
testHandleMatch(request, "/{cars:[^;]+}{params}", lookupPath);
matrixVariables = getMatrixVariables(request, "params");
assertNotNull(matrixVariables);
assertEquals(Arrays.asList("red", "blue", "green"), matrixVariables.get("colors"));
assertEquals("2012", matrixVariables.getFirst("year"));
uriVariables = getUriTemplateVariables(request);
assertEquals("cars", uriVariables.get("cars"));
assertEquals(";colors=red,blue,green;year=2012", uriVariables.get("params"));
// matrix variables not present : "params" variable is empty
request = new MockHttpServletRequest();
testHandleMatch(request, "/{cars:[^;]+}{params}", "/cars");
matrixVariables = getMatrixVariables(request, "params");
assertNull(matrixVariables);
uriVariables = getUriTemplateVariables(request);
assertEquals("cars", uriVariables.get("cars"));
assertEquals("", uriVariables.get("params"));
}
private void testHandleMatch(MockHttpServletRequest request, String pattern, String lookupPath) {
PatternsRequestCondition patterns = new PatternsRequestCondition(pattern);
RequestMappingInfo info = new RequestMappingInfo(patterns, null, null, null, null, null, null);
this.handlerMapping.handleMatch(info, lookupPath, request);
}
@SuppressWarnings("unchecked")
private MultiValueMap<String, String> getMatrixVariables(HttpServletRequest request, String uriVarName) {
String attrName = HandlerMapping.MATRIX_VARIABLES_ATTRIBUTE;
return ((Map<String, MultiValueMap<String, String>>) request.getAttribute(attrName)).get(uriVarName);
}
@SuppressWarnings("unchecked")
private Map<String, String> getUriTemplateVariables(HttpServletRequest request) {
String attrName = HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE;
return (Map<String, String>) request.getAttribute(attrName);
}
@Controller
private static class Handler {

View File

@@ -31,24 +31,24 @@ import org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionRes
import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver;
/**
* Base class for tests using on the DispatcherServlet and HandlerMethod infrastructure classes:
* Base class for tests using on the DispatcherServlet and HandlerMethod infrastructure classes:
* <ul>
* <li>RequestMappingHandlerMapping
* <li>RequestMappingHandlerAdapter
* <li>RequestMappingHandlerMapping
* <li>RequestMappingHandlerAdapter
* <li>ExceptionHandlerExceptionResolver
* </ul>
*
*
* @author Rossen Stoyanchev
*/
public class AbstractServletHandlerMethodTests {
private DispatcherServlet servlet;
@After
public void tearDown() {
this.servlet = null;
}
protected DispatcherServlet getServlet() {
assertNotNull("DispatcherServlet not initialized", servlet);
return servlet;
@@ -68,30 +68,32 @@ public class AbstractServletHandlerMethodTests {
*/
@SuppressWarnings("serial")
protected WebApplicationContext initServlet(
final ApplicationContextInitializer<GenericWebApplicationContext> initializer,
final ApplicationContextInitializer<GenericWebApplicationContext> initializer,
final Class<?>... controllerClasses) throws ServletException {
final GenericWebApplicationContext wac = new GenericWebApplicationContext();
servlet = new DispatcherServlet() {
@Override
protected WebApplicationContext createWebApplicationContext(WebApplicationContext parent) {
for (Class<?> clazz : controllerClasses) {
wac.registerBeanDefinition(clazz.getSimpleName(), new RootBeanDefinition(clazz));
}
Class<?> mappingType = RequestMappingHandlerMapping.class;
wac.registerBeanDefinition("handlerMapping", new RootBeanDefinition(mappingType));
RootBeanDefinition beanDef = new RootBeanDefinition(mappingType);
beanDef.getPropertyValues().add("removeSemicolonContent", "false");
wac.registerBeanDefinition("handlerMapping", beanDef);
Class<?> adapterType = RequestMappingHandlerAdapter.class;
wac.registerBeanDefinition("handlerAdapter", new RootBeanDefinition(adapterType));
Class<?> resolverType = ExceptionHandlerExceptionResolver.class;
wac.registerBeanDefinition("requestMappingResolver", new RootBeanDefinition(resolverType));
resolverType = ResponseStatusExceptionResolver.class;
wac.registerBeanDefinition("responseStatusResolver", new RootBeanDefinition(resolverType));
resolverType = DefaultHandlerExceptionResolver.class;
wac.registerBeanDefinition("defaultResolver", new RootBeanDefinition(resolverType));
@@ -103,9 +105,9 @@ public class AbstractServletHandlerMethodTests {
return wac;
}
};
servlet.init(new MockServletConfig());
return wac;
}

View File

@@ -0,0 +1,186 @@
/*
* Copyright 2002-2011 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.method.annotation;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import org.junit.Before;
import org.junit.Test;
import org.springframework.core.MethodParameter;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.MatrixVariable;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.HandlerMapping;
/**
* Test fixture with {@link MatrixVariableMethodArgumentResolver}.
*
* @author Rossen Stoyanchev
*/
public class MatrixVariablesMapMethodArgumentResolverTests {
private MatrixVariableMapMethodArgumentResolver resolver;
private MethodParameter paramString;
private MethodParameter paramMap;
private MethodParameter paramMultivalueMap;
private MethodParameter paramMapForPathVar;
private MethodParameter paramMapWithName;
private ModelAndViewContainer mavContainer;
private ServletWebRequest webRequest;
private MockHttpServletRequest request;
@Before
public void setUp() throws Exception {
this.resolver = new MatrixVariableMapMethodArgumentResolver();
Method method = getClass().getMethod("handle", String.class,
Map.class, MultiValueMap.class, MultiValueMap.class, Map.class);
this.paramString = new MethodParameter(method, 0);
this.paramMap = new MethodParameter(method, 1);
this.paramMultivalueMap = new MethodParameter(method, 2);
this.paramMapForPathVar = new MethodParameter(method, 3);
this.paramMapWithName = new MethodParameter(method, 4);
this.mavContainer = new ModelAndViewContainer();
this.request = new MockHttpServletRequest();
this.webRequest = new ServletWebRequest(request, new MockHttpServletResponse());
Map<String, MultiValueMap<String, String>> params = new LinkedHashMap<String, MultiValueMap<String, String>>();
this.request.setAttribute(HandlerMapping.MATRIX_VARIABLES_ATTRIBUTE, params);
}
@Test
public void supportsParameter() {
assertFalse(resolver.supportsParameter(paramString));
assertTrue(resolver.supportsParameter(paramMap));
assertTrue(resolver.supportsParameter(paramMultivalueMap));
assertTrue(resolver.supportsParameter(paramMapForPathVar));
assertFalse(resolver.supportsParameter(paramMapWithName));
}
@Test
public void resolveArgument() throws Exception {
MultiValueMap<String, String> params = getMatrixVariables("cars");
params.add("colors", "red");
params.add("colors", "green");
params.add("colors", "blue");
params.add("year", "2012");
@SuppressWarnings("unchecked")
Map<String, String> map = (Map<String, String>) this.resolver.resolveArgument(
this.paramMap, this.mavContainer, this.webRequest, null);
assertEquals(Arrays.asList("red", "green", "blue"), map.get("colors"));
@SuppressWarnings("unchecked")
MultiValueMap<String, String> multivalueMap = (MultiValueMap<String, String>) this.resolver.resolveArgument(
this.paramMultivalueMap, this.mavContainer, this.webRequest, null);
assertEquals(Arrays.asList("red", "green", "blue"), multivalueMap.get("colors"));
}
@Test
public void resolveArgumentPathVariable() throws Exception {
MultiValueMap<String, String> params1 = getMatrixVariables("cars");
params1.add("colors", "red");
params1.add("colors", "purple");
MultiValueMap<String, String> params2 = getMatrixVariables("planes");
params2.add("colors", "yellow");
params2.add("colors", "orange");
@SuppressWarnings("unchecked")
Map<String, String> mapForPathVar = (Map<String, String>) this.resolver.resolveArgument(
this.paramMapForPathVar, this.mavContainer, this.webRequest, null);
assertEquals(Arrays.asList("red", "purple"), mapForPathVar.get("colors"));
@SuppressWarnings("unchecked")
Map<String, String> mapAll = (Map<String, String>) this.resolver.resolveArgument(
this.paramMap, this.mavContainer, this.webRequest, null);
assertEquals(Arrays.asList("red", "purple", "yellow", "orange"), mapAll.get("colors"));
}
@Test
public void resolveArgumentNoParams() throws Exception {
@SuppressWarnings("unchecked")
Map<String, String> map = (Map<String, String>) this.resolver.resolveArgument(
this.paramMap, this.mavContainer, this.webRequest, null);
assertEquals(Collections.emptyMap(), map);
}
@Test
public void resolveArgumentNoMatch() throws Exception {
MultiValueMap<String, String> params2 = getMatrixVariables("planes");
params2.add("colors", "yellow");
params2.add("colors", "orange");
@SuppressWarnings("unchecked")
Map<String, String> map = (Map<String, String>) this.resolver.resolveArgument(
this.paramMapForPathVar, this.mavContainer, this.webRequest, null);
assertEquals(Collections.emptyMap(), map);
}
@SuppressWarnings("unchecked")
private MultiValueMap<String, String> getMatrixVariables(String pathVarName) {
Map<String, MultiValueMap<String, String>> matrixVariables =
(Map<String, MultiValueMap<String, String>>) this.request.getAttribute(
HandlerMapping.MATRIX_VARIABLES_ATTRIBUTE);
MultiValueMap<String, String> params = new LinkedMultiValueMap<String, String>();
matrixVariables.put(pathVarName, params);
return params;
}
public void handle(
String stringArg,
@MatrixVariable Map<String, String> map,
@MatrixVariable MultiValueMap<String, String> multivalueMap,
@MatrixVariable(pathVar="cars") MultiValueMap<String, String> mapForPathVar,
@MatrixVariable("name") Map<String, String> mapWithName) {
}
}

View File

@@ -0,0 +1,159 @@
/*
* Copyright 2002-2011 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.method.annotation;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.junit.Before;
import org.junit.Test;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.MethodParameter;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.annotation.MatrixVariable;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.HandlerMapping;
/**
* Test fixture with {@link MatrixVariableMethodArgumentResolver}.
*
* @author Rossen Stoyanchev
*/
public class MatrixVariablesMethodArgumentResolverTests {
private MatrixVariableMethodArgumentResolver resolver;
private MethodParameter paramString;
private MethodParameter paramColors;
private MethodParameter paramYear;
private ModelAndViewContainer mavContainer;
private ServletWebRequest webRequest;
private MockHttpServletRequest request;
@Before
public void setUp() throws Exception {
this.resolver = new MatrixVariableMethodArgumentResolver();
Method method = getClass().getMethod("handle", String.class, List.class, int.class);
this.paramString = new MethodParameter(method, 0);
this.paramColors = new MethodParameter(method, 1);
this.paramYear = new MethodParameter(method, 2);
this.paramColors.initParameterNameDiscovery(new LocalVariableTableParameterNameDiscoverer());
this.mavContainer = new ModelAndViewContainer();
this.request = new MockHttpServletRequest();
this.webRequest = new ServletWebRequest(request, new MockHttpServletResponse());
Map<String, MultiValueMap<String, String>> params = new LinkedHashMap<String, MultiValueMap<String, String>>();
this.request.setAttribute(HandlerMapping.MATRIX_VARIABLES_ATTRIBUTE, params);
}
@Test
public void supportsParameter() {
assertFalse(resolver.supportsParameter(paramString));
assertTrue(resolver.supportsParameter(paramColors));
assertTrue(resolver.supportsParameter(paramYear));
}
@Test
public void resolveArgument() throws Exception {
MultiValueMap<String, String> params = getMatrixVariables("cars");
params.add("colors", "red");
params.add("colors", "green");
params.add("colors", "blue");
assertEquals(Arrays.asList("red", "green", "blue"),
this.resolver.resolveArgument(this.paramColors, this.mavContainer, this.webRequest, null));
}
@Test
public void resolveArgumentPathVariable() throws Exception {
getMatrixVariables("cars").add("year", "2006");
getMatrixVariables("bikes").add("year", "2005");
assertEquals("2006", this.resolver.resolveArgument(this.paramYear, this.mavContainer, this.webRequest, null));
}
@Test
public void resolveArgumentDefaultValue() throws Exception {
assertEquals("2013", resolver.resolveArgument(this.paramYear, this.mavContainer, this.webRequest, null));
}
@Test(expected=ServletRequestBindingException.class)
public void resolveArgumentMultipleMatches() throws Exception {
getMatrixVariables("var1").add("colors", "red");
getMatrixVariables("var2").add("colors", "green");
this.resolver.resolveArgument(this.paramColors, this.mavContainer, this.webRequest, null);
}
@Test(expected=ServletRequestBindingException.class)
public void resolveArgumentRequired() throws Exception {
resolver.resolveArgument(this.paramColors, this.mavContainer, this.webRequest, null);
}
@Test
public void resolveArgumentNoMatch() throws Exception {
MultiValueMap<String, String> params = getMatrixVariables("cars");
params.add("anotherYear", "2012");
assertEquals("2013", this.resolver.resolveArgument(this.paramYear, this.mavContainer, this.webRequest, null));
}
@SuppressWarnings("unchecked")
private MultiValueMap<String, String> getMatrixVariables(String pathVarName) {
Map<String, MultiValueMap<String, String>> matrixVariables =
(Map<String, MultiValueMap<String, String>>) this.request.getAttribute(
HandlerMapping.MATRIX_VARIABLES_ATTRIBUTE);
MultiValueMap<String, String> params = new LinkedMultiValueMap<String, String>();
matrixVariables.put(pathVarName, params);
return params;
}
public void handle(
String stringArg,
@MatrixVariable List<String> colors,
@MatrixVariable(value="year", pathVar="cars", required=false, defaultValue="2013") int preferredYear) {
}
}

View File

@@ -22,9 +22,11 @@ import static org.junit.Assert.assertTrue;
import java.io.IOException;
import java.io.Writer;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@@ -38,8 +40,10 @@ import org.springframework.context.ApplicationContextInitializer;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.stereotype.Controller;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.MatrixVariable;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@@ -52,22 +56,22 @@ import org.springframework.web.servlet.mvc.annotation.UriTemplateServletAnnotati
import org.springframework.web.servlet.view.AbstractView;
/**
* The origin of this test class is {@link UriTemplateServletAnnotationControllerTests}.
*
* The origin of this test class is {@link UriTemplateServletAnnotationControllerTests}.
*
* Tests in this class run against the {@link HandlerMethod} infrastructure:
* <ul>
* <li>RequestMappingHandlerMapping
* <li>RequestMappingHandlerAdapter
* <li>RequestMappingHandlerMapping
* <li>RequestMappingHandlerAdapter
* <li>ExceptionHandlerExceptionResolver
* </ul>
*
*
* <p>Rather than against the existing infrastructure:
* <ul>
* <li>DefaultAnnotationHandlerMapping
* <li>AnnotationMethodHandlerAdapter
* <li>AnnotationMethodHandlerExceptionResolver
* </ul>
*
* </ul>
*
* @author Rossen Stoyanchev
* @since 3.1
*/
@@ -80,17 +84,18 @@ public class UriTemplateServletAnnotationControllerHandlerMethodTests extends Ab
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/42");
MockHttpServletResponse response = new MockHttpServletResponse();
getServlet().service(request, response);
assertEquals("test-42", response.getContentAsString());
assertEquals("test-42-7", response.getContentAsString());
}
@Test
public void multiple() throws Exception {
initServletWithControllers(MultipleUriTemplateController.class);
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/hotels/42/bookings/21-other");
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/hotels/42;q=24/bookings/21-other;q=12");
MockHttpServletResponse response = new MockHttpServletResponse();
getServlet().service(request, response);
assertEquals("test-42-21-other", response.getContentAsString());
assertEquals(200, response.getStatus());
assertEquals("test-42-q24-21-other-q12", response.getContentAsString());
}
@Test
@@ -99,8 +104,8 @@ public class UriTemplateServletAnnotationControllerHandlerMethodTests extends Ab
pathVars.put("hotel", "42");
pathVars.put("booking", 21);
pathVars.put("other", "other");
WebApplicationContext wac =
WebApplicationContext wac =
initServlet(new ApplicationContextInitializer<GenericWebApplicationContext>() {
public void initialize(GenericWebApplicationContext context) {
RootBeanDefinition beanDef = new RootBeanDefinition(ModelValidatingViewResolver.class);
@@ -109,9 +114,9 @@ public class UriTemplateServletAnnotationControllerHandlerMethodTests extends Ab
}
}, ViewRenderingController.class);
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/hotels/42/bookings/21-other");
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/hotels/42;q=1,2/bookings/21-other;q=3;r=R");
getServlet().service(request, new MockHttpServletResponse());
ModelValidatingViewResolver resolver = wac.getBean(ModelValidatingViewResolver.class);
assertEquals(3, resolver.validatedAttrCount);
}
@@ -166,10 +171,10 @@ public class UriTemplateServletAnnotationControllerHandlerMethodTests extends Ab
public void extension() throws Exception {
initServletWithControllers(SimpleUriTemplateController.class);
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/42.xml");
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/42;jsessionid=c0o7fszeb1;q=24.xml");
MockHttpServletResponse response = new MockHttpServletResponse();
getServlet().service(request, response);
assertEquals("test-42", response.getContentAsString());
assertEquals("test-42-24", response.getContentAsString());
}
@@ -287,10 +292,11 @@ public class UriTemplateServletAnnotationControllerHandlerMethodTests extends Ab
public void customRegex() throws Exception {
initServletWithControllers(CustomRegexController.class);
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/42");
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/42;q=1;q=2");
MockHttpServletResponse response = new MockHttpServletResponse();
getServlet().service(request, response);
assertEquals("test-42", response.getContentAsString());
assertEquals(200, response.getStatus());
assertEquals("test-42-;q=1;q=2-[1, 2]", response.getContentAsString());
}
/*
@@ -343,7 +349,7 @@ public class UriTemplateServletAnnotationControllerHandlerMethodTests extends Ab
@Test
public void doIt() throws Exception {
initServletWithControllers(Spr6978Controller.class);
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo/100");
MockHttpServletResponse response = new MockHttpServletResponse();
getServlet().service(request, response);
@@ -375,9 +381,11 @@ public class UriTemplateServletAnnotationControllerHandlerMethodTests extends Ab
public static class SimpleUriTemplateController {
@RequestMapping("/{root}")
public void handle(@PathVariable("root") int root, Writer writer) throws IOException {
public void handle(@PathVariable("root") int root, @MatrixVariable(required=false, defaultValue="7") int q,
Writer writer) throws IOException {
assertEquals("Invalid path variable value", 42, root);
writer.write("test-" + root);
writer.write("test-" + root + "-" + q);
}
}
@@ -389,10 +397,12 @@ public class UriTemplateServletAnnotationControllerHandlerMethodTests extends Ab
public void handle(@PathVariable("hotel") String hotel,
@PathVariable int booking,
@PathVariable String other,
@MatrixVariable(value="q", pathVar="hotel") int qHotel,
@MatrixVariable(value="q", pathVar="other") int qOther,
Writer writer) throws IOException {
assertEquals("Invalid path variable value", "42", hotel);
assertEquals("Invalid path variable value", 21, booking);
writer.write("test-" + hotel + "-" + booking + "-" + other);
writer.write("test-" + hotel + "-q" + qHotel + "-" + booking + "-" + other + "-q" + qOther);
}
}
@@ -401,9 +411,13 @@ public class UriTemplateServletAnnotationControllerHandlerMethodTests extends Ab
public static class ViewRenderingController {
@RequestMapping("/hotels/{hotel}/bookings/{booking}-{other}")
public void handle(@PathVariable("hotel") String hotel, @PathVariable int booking, @PathVariable String other) {
public void handle(@PathVariable("hotel") String hotel, @PathVariable int booking,
@PathVariable String other, @MatrixVariable MultiValueMap<String, String> params) {
assertEquals("Invalid path variable value", "42", hotel);
assertEquals("Invalid path variable value", 21, booking);
assertEquals(Arrays.asList("1", "2", "3"), params.get("q"));
assertEquals("R", params.getFirst("r"));
}
}
@@ -499,12 +513,13 @@ public class UriTemplateServletAnnotationControllerHandlerMethodTests extends Ab
@Controller
public static class CustomRegexController {
@RequestMapping("/{root:\\d+}")
public void handle(@PathVariable("root") int root, Writer writer) throws IOException {
assertEquals("Invalid path variable value", 42, root);
writer.write("test-" + root);
}
@RequestMapping("/{root:\\d+}{params}")
public void handle(@PathVariable("root") int root, @PathVariable("params") String paramString,
@MatrixVariable List<Integer> q, Writer writer) throws IOException {
assertEquals("Invalid path variable value", 42, root);
writer.write("test-" + root + "-" + paramString + "-" + q);
}
}
@Controller
@@ -515,7 +530,6 @@ public class UriTemplateServletAnnotationControllerHandlerMethodTests extends Ab
throws IOException {
writer.write("latitude-" + latitude + "-longitude-" + longitude);
}
}
@@ -650,9 +664,9 @@ public class UriTemplateServletAnnotationControllerHandlerMethodTests extends Ab
public static class ModelValidatingViewResolver implements ViewResolver {
private final Map<String, Object> attrsToValidate;
int validatedAttrCount;
public ModelValidatingViewResolver(Map<String, Object> attrsToValidate) {
this.attrsToValidate = attrsToValidate;
}
@@ -674,8 +688,8 @@ public class UriTemplateServletAnnotationControllerHandlerMethodTests extends Ab
};
}
}
// @Ignore("ControllerClassNameHandlerMapping")
// @Ignore("ControllerClassNameHandlerMapping")
// public void controllerClassName() throws Exception {
// @Ignore("useDefaultSuffixPattern property not supported")
@@ -683,5 +697,5 @@ public class UriTemplateServletAnnotationControllerHandlerMethodTests extends Ab
// @Ignore("useDefaultSuffixPattern property not supported")
// public void noDefaultSuffixPattern() throws Exception {
}

View File

@@ -84,6 +84,12 @@ public final class DefaultRequestToViewNameTranslatorTests {
assertViewName(VIEW_NAME);
}
@Test
public void testGetViewNameWithSemicolonContent() {
request.setRequestURI(CONTEXT_PATH + VIEW_NAME + ";a=A;b=B");
assertViewName(VIEW_NAME);
}
@Test
public void testGetViewNameWithPrefix() {
final String prefix = "fiona_";