diff --git a/build-spring-framework/resources/changelog.txt b/build-spring-framework/resources/changelog.txt index 83d3b8b0b8c..d7c1072c920 100644 --- a/build-spring-framework/resources/changelog.txt +++ b/build-spring-framework/resources/changelog.txt @@ -23,8 +23,9 @@ Changes in version 3.1 RC2 (2011-11-15) * added ignoreDefaultModelOnRedirect attribute to * added methods to UriComponentsBuilder for replacing the path or the query * added ServletUriComponentsBuilder to build a UriComponents instance starting with a ServletRequest +* support UriComponentsBuilder as @Controller method argument * MockHttpServletRequest and MockHttpServletResponse now keep contentType field and Content-Type header in sync -* Fix issue with cache ignoring prototype-scoped controllers in RequestMappingHandlerAdapter +* fixed issue with cache ignoring prototype-scoped controllers in RequestMappingHandlerAdapter Changes in version 3.1 RC1 (2011-10-11) diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java index 021982464fd..14db8301aa4 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java @@ -89,6 +89,7 @@ import org.springframework.web.servlet.mvc.method.annotation.support.ServletCook import org.springframework.web.servlet.mvc.method.annotation.support.ServletModelAttributeMethodProcessor; import org.springframework.web.servlet.mvc.method.annotation.support.ServletRequestMethodArgumentResolver; import org.springframework.web.servlet.mvc.method.annotation.support.ServletResponseMethodArgumentResolver; +import org.springframework.web.servlet.mvc.method.annotation.support.UriComponentsBuilderMethodArgumentResolver; import org.springframework.web.servlet.mvc.method.annotation.support.ViewMethodReturnValueHandler; import org.springframework.web.servlet.mvc.method.annotation.support.ViewNameMethodReturnValueHandler; import org.springframework.web.servlet.mvc.support.RedirectAttributes; @@ -425,7 +426,7 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i * Return the list of argument resolvers to use including built-in resolvers * and custom resolvers provided via {@link #setCustomArgumentResolvers}. */ - protected List getDefaultArgumentResolvers() { + private List getDefaultArgumentResolvers() { List resolvers = new ArrayList(); // Annotation-based argument resolution @@ -449,6 +450,7 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i resolvers.add(new MapMethodProcessor()); resolvers.add(new ErrorsMethodArgumentResolver()); resolvers.add(new SessionStatusMethodArgumentResolver()); + resolvers.add(new UriComponentsBuilderMethodArgumentResolver()); // Custom arguments if (getCustomArgumentResolvers() != null) { @@ -466,7 +468,7 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i * Return the list of argument resolvers to use for {@code @InitBinder} * methods including built-in and custom resolvers. */ - protected List getDefaultInitBinderArgumentResolvers() { + private List getDefaultInitBinderArgumentResolvers() { List resolvers = new ArrayList(); // Annotation-based argument resolution @@ -494,7 +496,7 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i * Return the list of return value handlers to use including built-in and * custom handlers provided via {@link #setReturnValueHandlers}. */ - protected List getDefaultReturnValueHandlers() { + private List getDefaultReturnValueHandlers() { List handlers = new ArrayList(); // Single-purpose return value types diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/UriComponentsBuilderMethodArgumentResolver.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/UriComponentsBuilderMethodArgumentResolver.java new file mode 100644 index 00000000000..7045c0f1ef2 --- /dev/null +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/UriComponentsBuilderMethodArgumentResolver.java @@ -0,0 +1,53 @@ +/* + * 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.support; + +import javax.servlet.http.HttpServletRequest; + +import org.springframework.core.MethodParameter; +import org.springframework.web.bind.support.WebDataBinderFactory; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.method.support.ModelAndViewContainer; +import org.springframework.web.servlet.support.ServletUriComponentsBuilder; +import org.springframework.web.util.UriComponentsBuilder; + +/** + * Resolvers argument values of type {@link UriComponentsBuilder}. + * + *

The returned instance is initialized via + * {@link ServletUriComponentsBuilder#fromServletMapping(HttpServletRequest)}. + * + * @author Rossen Stoyanchev + * @since 3.1 + */ +public class UriComponentsBuilderMethodArgumentResolver implements HandlerMethodArgumentResolver { + + public boolean supportsParameter(MethodParameter parameter) { + return UriComponentsBuilder.class.isAssignableFrom(parameter.getParameterType()); + } + + public Object resolveArgument(MethodParameter parameter, + ModelAndViewContainer mavContainer, + NativeWebRequest webRequest, + WebDataBinderFactory binderFactory) throws Exception { + + HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class); + return ServletUriComponentsBuilder.fromServletMapping(request); + } + +} diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapterIntegrationTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapterIntegrationTests.java index ee2e052b3da..33dd364f29c 100644 --- a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapterIntegrationTests.java +++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapterIntegrationTests.java @@ -25,6 +25,7 @@ import static org.junit.Assert.assertTrue; import java.awt.Color; import java.lang.reflect.Method; +import java.net.URI; import java.security.Principal; import java.text.SimpleDateFormat; import java.util.ArrayList; @@ -85,6 +86,7 @@ import org.springframework.web.method.support.InvocableHandlerMethod; import org.springframework.web.servlet.HandlerMapping; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.mvc.method.annotation.support.ServletWebArgumentResolverAdapter; +import org.springframework.web.util.UriComponentsBuilder; /** * A test fixture with a controller with all supported method signature styles @@ -142,7 +144,7 @@ public class RequestMappingHandlerAdapterIntegrationTests { Class[] parameterTypes = new Class[] { int.class, String.class, String.class, String.class, Map.class, Date.class, Map.class, String.class, String.class, TestBean.class, Errors.class, TestBean.class, Color.class, HttpServletRequest.class, HttpServletResponse.class, User.class, OtherUser.class, - Model.class }; + Model.class, UriComponentsBuilder.class }; String datePattern = "yyyy.MM.dd"; String formattedDate = "2011.03.16"; @@ -159,6 +161,7 @@ public class RequestMappingHandlerAdapterIntegrationTests { request.setContent("Hello World".getBytes("UTF-8")); request.setUserPrincipal(new User()); request.setContextPath("/contextPath"); + request.setServletPath("/main"); System.setProperty("systemHeader", "systemHeaderValue"); Map uriTemplateVars = new HashMap(); uriTemplateVars.put("pathvar", "pathvarValue"); @@ -206,6 +209,8 @@ public class RequestMappingHandlerAdapterIntegrationTests { assertTrue(model.get("customArg") instanceof Color); assertEquals(User.class, model.get("user").getClass()); assertEquals(OtherUser.class, model.get("otherUser").getClass()); + + assertEquals(new URI("http://localhost/contextPath/main/path"), model.get("url")); } @Test @@ -309,13 +314,15 @@ public class RequestMappingHandlerAdapterIntegrationTests { HttpServletResponse response, User user, @ModelAttribute OtherUser otherUser, - Model model) throws Exception { + Model model, + UriComponentsBuilder builder) throws Exception { model.addAttribute("cookie", cookie).addAttribute("pathvar", pathvar).addAttribute("header", header) .addAttribute("systemHeader", systemHeader).addAttribute("headerMap", headerMap) .addAttribute("dateParam", dateParam).addAttribute("paramMap", paramMap) .addAttribute("paramByConvention", paramByConvention).addAttribute("value", value) - .addAttribute("customArg", customArg).addAttribute(user); + .addAttribute("customArg", customArg).addAttribute(user) + .addAttribute("url", builder.path("/path").build().toUri()); assertNotNull(request); assertNotNull(response); diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/support/UriComponentsBuilderMethodArgumentResolverTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/support/UriComponentsBuilderMethodArgumentResolverTests.java new file mode 100644 index 00000000000..3ebe78307ad --- /dev/null +++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/support/UriComponentsBuilderMethodArgumentResolverTests.java @@ -0,0 +1,89 @@ +/* + * 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.support; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.lang.reflect.Method; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.core.MethodParameter; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.web.context.request.ServletWebRequest; +import org.springframework.web.method.support.ModelAndViewContainer; +import org.springframework.web.servlet.support.ServletUriComponentsBuilder; +import org.springframework.web.util.UriComponentsBuilder; + +/** + * Test fixture with {@link UriComponentsBuilderMethodArgumentResolver}. + * + * @author Rossen Stoyanchev + */ +public class UriComponentsBuilderMethodArgumentResolverTests { + + private UriComponentsBuilderMethodArgumentResolver resolver; + + private MethodParameter builderParam; + + private MethodParameter servletBuilderParam; + + private MethodParameter intParam; + + private ServletWebRequest webRequest; + + private MockHttpServletRequest servletRequest; + + @Before + public void setUp() throws Exception { + this.resolver = new UriComponentsBuilderMethodArgumentResolver(); + Method method = this.getClass().getDeclaredMethod("handle", UriComponentsBuilder.class, ServletUriComponentsBuilder.class, int.class); + this.builderParam = new MethodParameter(method, 0); + this.servletBuilderParam = new MethodParameter(method, 1); + this.intParam = new MethodParameter(method, 2); + this.servletRequest = new MockHttpServletRequest(); + this.webRequest = new ServletWebRequest(this.servletRequest); + } + + @Test + public void supportsParameter() throws Exception { + assertTrue(this.resolver.supportsParameter(this.builderParam)); + assertTrue(this.resolver.supportsParameter(this.servletBuilderParam)); + assertFalse(this.resolver.supportsParameter(this.intParam)); + } + + @Test + public void resolveArgument() throws Exception { + this.servletRequest.setContextPath("/myapp"); + this.servletRequest.setServletPath("/main"); + this.servletRequest.setPathInfo("/accounts"); + + Object actual = this.resolver.resolveArgument(this.builderParam, new ModelAndViewContainer(), this.webRequest, null); + + assertNotNull(actual); + assertEquals(ServletUriComponentsBuilder.class, actual.getClass()); + assertEquals("http://localhost/myapp/main", ((ServletUriComponentsBuilder) actual).build().toUriString()); + } + + + void handle(UriComponentsBuilder builder, ServletUriComponentsBuilder servletBuilder, int value) { + } + +} \ No newline at end of file diff --git a/org.springframework.web/src/main/java/org/springframework/web/bind/annotation/RequestMapping.java b/org.springframework.web/src/main/java/org/springframework/web/bind/annotation/RequestMapping.java index 7fe0deaff54..7a4ff06b2e5 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/bind/annotation/RequestMapping.java +++ b/org.springframework.web/src/main/java/org/springframework/web/bind/annotation/RequestMapping.java @@ -130,6 +130,9 @@ import java.lang.annotation.Target; * for marking form processing as complete (triggering the cleanup of session * attributes that have been indicated by the {@link SessionAttributes} annotation * at the handler type level). + *

  • {@link org.springframework.web.util.UriComponentsBuilder} a builder for + * preparing a URL relative to the current request's host, port, scheme, context + * path, and the literal part of the servlet mapping. * * *

    The following return types are supported for handler methods: diff --git a/org.springframework.web/src/main/java/org/springframework/web/util/UriComponentsBuilder.java b/org.springframework.web/src/main/java/org/springframework/web/util/UriComponentsBuilder.java index 7a307290cc7..f4ed1f252e1 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/util/UriComponentsBuilder.java +++ b/org.springframework.web/src/main/java/org/springframework/web/util/UriComponentsBuilder.java @@ -20,6 +20,7 @@ import java.net.URI; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -221,6 +222,30 @@ public class UriComponentsBuilder { return new UriComponents(scheme, userInfo, host, port, pathBuilder.build(), queryParams, fragment, encoded, true); } + /** + * Builds a {@code UriComponents} instance and replaces URI template variables + * with the values from a map. This is a shortcut method, which combines + * calls to {@link #build()} and then {@link UriComponents#expand(Map)}. + * + * @param uriVariables the map of URI variables + * @return the URI components with expanded values + */ + public UriComponents buildAndExpand(Map uriVariables) { + return build(false).expand(uriVariables); + } + + /** + * Builds a {@code UriComponents} instance and replaces URI template variables + * with the values from an array. This is a shortcut method, which combines + * calls to {@link #build()} and then {@link UriComponents#expand(Object...)}. + * + * @param uriVariableValues URI variable values + * @return the URI components with expanded values + */ + public UriComponents buildAndExpand(Object... uriVariableValues) { + return build(false).expand(uriVariableValues); + } + // URI components methods /** diff --git a/org.springframework.web/src/test/java/org/springframework/web/util/UriComponentsBuilderTests.java b/org.springframework.web/src/test/java/org/springframework/web/util/UriComponentsBuilderTests.java index 0890729c23a..303bcd2d157 100644 --- a/org.springframework.web/src/test/java/org/springframework/web/util/UriComponentsBuilderTests.java +++ b/org.springframework.web/src/test/java/org/springframework/web/util/UriComponentsBuilderTests.java @@ -16,17 +16,19 @@ package org.springframework.web.util; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + import java.net.URI; import java.net.URISyntaxException; import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; import org.junit.Test; - import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; -import static org.junit.Assert.*; - /** @author Arjen Poutsma */ public class UriComponentsBuilderTests { @@ -227,7 +229,6 @@ public class UriComponentsBuilderTests { assertEquals(expectedQueryParams, result.getQueryParams()); } - @Test public void replaceQueryParam() { UriComponentsBuilder builder = UriComponentsBuilder.newInstance().queryParam("baz", "qux", 42); @@ -243,4 +244,15 @@ public class UriComponentsBuilderTests { assertNull("Query param should have been deleted", result.getQuery()); } + @Test + public void buildAndExpand() { + UriComponents result = UriComponentsBuilder.fromPath("/{foo}").buildAndExpand("fooValue"); + assertEquals("/fooValue", result.toUriString()); + + Map values = new HashMap(); + values.put("foo", "fooValue"); + values.put("bar", "barValue"); + result = UriComponentsBuilder.fromPath("/{foo}/{bar}").buildAndExpand(values); + assertEquals("/fooValue/barValue", result.toUriString()); + } } diff --git a/org.springframework.web/src/test/java/org/springframework/web/util/UriComponentsTests.java b/org.springframework.web/src/test/java/org/springframework/web/util/UriComponentsTests.java index 596a89959f6..00049b00c8b 100644 --- a/org.springframework.web/src/test/java/org/springframework/web/util/UriComponentsTests.java +++ b/org.springframework.web/src/test/java/org/springframework/web/util/UriComponentsTests.java @@ -62,7 +62,6 @@ public class UriComponentsTests { @Test(expected = IllegalArgumentException.class) public void invalidCharacters() { UriComponentsBuilder.fromPath("/{foo}").build(true); - } @Test(expected = IllegalArgumentException.class) diff --git a/spring-framework-reference/src/mvc.xml b/spring-framework-reference/src/mvc.xml index 1ddf737e170..0e0aead14d7 100644 --- a/spring-framework-reference/src/mvc.xml +++ b/spring-framework-reference/src/mvc.xml @@ -1199,6 +1199,13 @@ public class RelativePathUriTemplateController { indicated by the @SessionAttributes annotation at the handler type level. + + + org.springframework.web.util.UriComponentsBuilder + a builder for preparing a URL relative to the current request's + host, port, scheme, context path, and the literal part of the + servlet mapping. + The Errors or