Skip to content

Commit 60ee0bb

Browse files
committed
SPR-8020 Support UriComponentsBuilder as a controller method argument.
The UriComponentsBuilder instance passed into the method is initialized with current request information including host, scheme, port, context path, and the servlet mapping's literal part. Also added shortcut methods to buildAndExpand in UriComponentsBuilder.
1 parent e4fada5 commit 60ee0bb

File tree

10 files changed

+210
-12
lines changed

10 files changed

+210
-12
lines changed

build-spring-framework/resources/changelog.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,9 @@ Changes in version 3.1 RC2 (2011-11-15)
2323
* added ignoreDefaultModelOnRedirect attribute to <mvc:annotation-driven/>
2424
* added methods to UriComponentsBuilder for replacing the path or the query
2525
* added ServletUriComponentsBuilder to build a UriComponents instance starting with a ServletRequest
26+
* support UriComponentsBuilder as @Controller method argument
2627
* MockHttpServletRequest and MockHttpServletResponse now keep contentType field and Content-Type header in sync
27-
* Fix issue with cache ignoring prototype-scoped controllers in RequestMappingHandlerAdapter
28+
* fixed issue with cache ignoring prototype-scoped controllers in RequestMappingHandlerAdapter
2829

2930

3031
Changes in version 3.1 RC1 (2011-10-11)

org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@
8989
import org.springframework.web.servlet.mvc.method.annotation.support.ServletModelAttributeMethodProcessor;
9090
import org.springframework.web.servlet.mvc.method.annotation.support.ServletRequestMethodArgumentResolver;
9191
import org.springframework.web.servlet.mvc.method.annotation.support.ServletResponseMethodArgumentResolver;
92+
import org.springframework.web.servlet.mvc.method.annotation.support.UriComponentsBuilderMethodArgumentResolver;
9293
import org.springframework.web.servlet.mvc.method.annotation.support.ViewMethodReturnValueHandler;
9394
import org.springframework.web.servlet.mvc.method.annotation.support.ViewNameMethodReturnValueHandler;
9495
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
@@ -425,7 +426,7 @@ public void afterPropertiesSet() {
425426
* Return the list of argument resolvers to use including built-in resolvers
426427
* and custom resolvers provided via {@link #setCustomArgumentResolvers}.
427428
*/
428-
protected List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
429+
private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
429430
List<HandlerMethodArgumentResolver> resolvers = new ArrayList<HandlerMethodArgumentResolver>();
430431

431432
// Annotation-based argument resolution
@@ -449,6 +450,7 @@ protected List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
449450
resolvers.add(new MapMethodProcessor());
450451
resolvers.add(new ErrorsMethodArgumentResolver());
451452
resolvers.add(new SessionStatusMethodArgumentResolver());
453+
resolvers.add(new UriComponentsBuilderMethodArgumentResolver());
452454

453455
// Custom arguments
454456
if (getCustomArgumentResolvers() != null) {
@@ -466,7 +468,7 @@ protected List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
466468
* Return the list of argument resolvers to use for {@code @InitBinder}
467469
* methods including built-in and custom resolvers.
468470
*/
469-
protected List<HandlerMethodArgumentResolver> getDefaultInitBinderArgumentResolvers() {
471+
private List<HandlerMethodArgumentResolver> getDefaultInitBinderArgumentResolvers() {
470472
List<HandlerMethodArgumentResolver> resolvers = new ArrayList<HandlerMethodArgumentResolver>();
471473

472474
// Annotation-based argument resolution
@@ -494,7 +496,7 @@ protected List<HandlerMethodArgumentResolver> getDefaultInitBinderArgumentResolv
494496
* Return the list of return value handlers to use including built-in and
495497
* custom handlers provided via {@link #setReturnValueHandlers}.
496498
*/
497-
protected List<HandlerMethodReturnValueHandler> getDefaultReturnValueHandlers() {
499+
private List<HandlerMethodReturnValueHandler> getDefaultReturnValueHandlers() {
498500
List<HandlerMethodReturnValueHandler> handlers = new ArrayList<HandlerMethodReturnValueHandler>();
499501

500502
// Single-purpose return value types
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*
2+
* Copyright 2002-2011 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.web.servlet.mvc.method.annotation.support;
18+
19+
import javax.servlet.http.HttpServletRequest;
20+
21+
import org.springframework.core.MethodParameter;
22+
import org.springframework.web.bind.support.WebDataBinderFactory;
23+
import org.springframework.web.context.request.NativeWebRequest;
24+
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
25+
import org.springframework.web.method.support.ModelAndViewContainer;
26+
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
27+
import org.springframework.web.util.UriComponentsBuilder;
28+
29+
/**
30+
* Resolvers argument values of type {@link UriComponentsBuilder}.
31+
*
32+
* <p>The returned instance is initialized via
33+
* {@link ServletUriComponentsBuilder#fromServletMapping(HttpServletRequest)}.
34+
*
35+
* @author Rossen Stoyanchev
36+
* @since 3.1
37+
*/
38+
public class UriComponentsBuilderMethodArgumentResolver implements HandlerMethodArgumentResolver {
39+
40+
public boolean supportsParameter(MethodParameter parameter) {
41+
return UriComponentsBuilder.class.isAssignableFrom(parameter.getParameterType());
42+
}
43+
44+
public Object resolveArgument(MethodParameter parameter,
45+
ModelAndViewContainer mavContainer,
46+
NativeWebRequest webRequest,
47+
WebDataBinderFactory binderFactory) throws Exception {
48+
49+
HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
50+
return ServletUriComponentsBuilder.fromServletMapping(request);
51+
}
52+
53+
}

org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapterIntegrationTests.java

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
import java.awt.Color;
2727
import java.lang.reflect.Method;
28+
import java.net.URI;
2829
import java.security.Principal;
2930
import java.text.SimpleDateFormat;
3031
import java.util.ArrayList;
@@ -85,6 +86,7 @@
8586
import org.springframework.web.servlet.HandlerMapping;
8687
import org.springframework.web.servlet.ModelAndView;
8788
import org.springframework.web.servlet.mvc.method.annotation.support.ServletWebArgumentResolverAdapter;
89+
import org.springframework.web.util.UriComponentsBuilder;
8890

8991
/**
9092
* A test fixture with a controller with all supported method signature styles
@@ -142,7 +144,7 @@ public void handle() throws Exception {
142144
Class<?>[] parameterTypes = new Class<?>[] { int.class, String.class, String.class, String.class, Map.class,
143145
Date.class, Map.class, String.class, String.class, TestBean.class, Errors.class, TestBean.class,
144146
Color.class, HttpServletRequest.class, HttpServletResponse.class, User.class, OtherUser.class,
145-
Model.class };
147+
Model.class, UriComponentsBuilder.class };
146148

147149
String datePattern = "yyyy.MM.dd";
148150
String formattedDate = "2011.03.16";
@@ -159,6 +161,7 @@ public void handle() throws Exception {
159161
request.setContent("Hello World".getBytes("UTF-8"));
160162
request.setUserPrincipal(new User());
161163
request.setContextPath("/contextPath");
164+
request.setServletPath("/main");
162165
System.setProperty("systemHeader", "systemHeaderValue");
163166
Map<String, String> uriTemplateVars = new HashMap<String, String>();
164167
uriTemplateVars.put("pathvar", "pathvarValue");
@@ -206,6 +209,8 @@ public void handle() throws Exception {
206209
assertTrue(model.get("customArg") instanceof Color);
207210
assertEquals(User.class, model.get("user").getClass());
208211
assertEquals(OtherUser.class, model.get("otherUser").getClass());
212+
213+
assertEquals(new URI("http://localhost/contextPath/main/path"), model.get("url"));
209214
}
210215

211216
@Test
@@ -309,13 +314,15 @@ public String handle(
309314
HttpServletResponse response,
310315
User user,
311316
@ModelAttribute OtherUser otherUser,
312-
Model model) throws Exception {
317+
Model model,
318+
UriComponentsBuilder builder) throws Exception {
313319

314320
model.addAttribute("cookie", cookie).addAttribute("pathvar", pathvar).addAttribute("header", header)
315321
.addAttribute("systemHeader", systemHeader).addAttribute("headerMap", headerMap)
316322
.addAttribute("dateParam", dateParam).addAttribute("paramMap", paramMap)
317323
.addAttribute("paramByConvention", paramByConvention).addAttribute("value", value)
318-
.addAttribute("customArg", customArg).addAttribute(user);
324+
.addAttribute("customArg", customArg).addAttribute(user)
325+
.addAttribute("url", builder.path("/path").build().toUri());
319326

320327
assertNotNull(request);
321328
assertNotNull(response);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/*
2+
* Copyright 2002-2011 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.web.servlet.mvc.method.annotation.support;
18+
19+
import static org.junit.Assert.assertEquals;
20+
import static org.junit.Assert.assertFalse;
21+
import static org.junit.Assert.assertNotNull;
22+
import static org.junit.Assert.assertTrue;
23+
24+
import java.lang.reflect.Method;
25+
26+
import org.junit.Before;
27+
import org.junit.Test;
28+
import org.springframework.core.MethodParameter;
29+
import org.springframework.mock.web.MockHttpServletRequest;
30+
import org.springframework.web.context.request.ServletWebRequest;
31+
import org.springframework.web.method.support.ModelAndViewContainer;
32+
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
33+
import org.springframework.web.util.UriComponentsBuilder;
34+
35+
/**
36+
* Test fixture with {@link UriComponentsBuilderMethodArgumentResolver}.
37+
*
38+
* @author Rossen Stoyanchev
39+
*/
40+
public class UriComponentsBuilderMethodArgumentResolverTests {
41+
42+
private UriComponentsBuilderMethodArgumentResolver resolver;
43+
44+
private MethodParameter builderParam;
45+
46+
private MethodParameter servletBuilderParam;
47+
48+
private MethodParameter intParam;
49+
50+
private ServletWebRequest webRequest;
51+
52+
private MockHttpServletRequest servletRequest;
53+
54+
@Before
55+
public void setUp() throws Exception {
56+
this.resolver = new UriComponentsBuilderMethodArgumentResolver();
57+
Method method = this.getClass().getDeclaredMethod("handle", UriComponentsBuilder.class, ServletUriComponentsBuilder.class, int.class);
58+
this.builderParam = new MethodParameter(method, 0);
59+
this.servletBuilderParam = new MethodParameter(method, 1);
60+
this.intParam = new MethodParameter(method, 2);
61+
this.servletRequest = new MockHttpServletRequest();
62+
this.webRequest = new ServletWebRequest(this.servletRequest);
63+
}
64+
65+
@Test
66+
public void supportsParameter() throws Exception {
67+
assertTrue(this.resolver.supportsParameter(this.builderParam));
68+
assertTrue(this.resolver.supportsParameter(this.servletBuilderParam));
69+
assertFalse(this.resolver.supportsParameter(this.intParam));
70+
}
71+
72+
@Test
73+
public void resolveArgument() throws Exception {
74+
this.servletRequest.setContextPath("/myapp");
75+
this.servletRequest.setServletPath("/main");
76+
this.servletRequest.setPathInfo("/accounts");
77+
78+
Object actual = this.resolver.resolveArgument(this.builderParam, new ModelAndViewContainer(), this.webRequest, null);
79+
80+
assertNotNull(actual);
81+
assertEquals(ServletUriComponentsBuilder.class, actual.getClass());
82+
assertEquals("http://localhost/myapp/main", ((ServletUriComponentsBuilder) actual).build().toUriString());
83+
}
84+
85+
86+
void handle(UriComponentsBuilder builder, ServletUriComponentsBuilder servletBuilder, int value) {
87+
}
88+
89+
}

org.springframework.web/src/main/java/org/springframework/web/bind/annotation/RequestMapping.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,9 @@
130130
* for marking form processing as complete (triggering the cleanup of session
131131
* attributes that have been indicated by the {@link SessionAttributes} annotation
132132
* at the handler type level).
133+
* <li>{@link org.springframework.web.util.UriComponentsBuilder} a builder for
134+
* preparing a URL relative to the current request's host, port, scheme, context
135+
* path, and the literal part of the servlet mapping.
133136
* </ul>
134137
*
135138
* <p>The following return types are supported for handler methods:

org.springframework.web/src/main/java/org/springframework/web/util/UriComponentsBuilder.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import java.util.ArrayList;
2121
import java.util.Collections;
2222
import java.util.List;
23+
import java.util.Map;
2324
import java.util.regex.Matcher;
2425
import java.util.regex.Pattern;
2526

@@ -221,6 +222,30 @@ public UriComponents build(boolean encoded) {
221222
return new UriComponents(scheme, userInfo, host, port, pathBuilder.build(), queryParams, fragment, encoded, true);
222223
}
223224

225+
/**
226+
* Builds a {@code UriComponents} instance and replaces URI template variables
227+
* with the values from a map. This is a shortcut method, which combines
228+
* calls to {@link #build()} and then {@link UriComponents#expand(Map)}.
229+
*
230+
* @param uriVariables the map of URI variables
231+
* @return the URI components with expanded values
232+
*/
233+
public UriComponents buildAndExpand(Map<String, ?> uriVariables) {
234+
return build(false).expand(uriVariables);
235+
}
236+
237+
/**
238+
* Builds a {@code UriComponents} instance and replaces URI template variables
239+
* with the values from an array. This is a shortcut method, which combines
240+
* calls to {@link #build()} and then {@link UriComponents#expand(Object...)}.
241+
*
242+
* @param uriVariableValues URI variable values
243+
* @return the URI components with expanded values
244+
*/
245+
public UriComponents buildAndExpand(Object... uriVariableValues) {
246+
return build(false).expand(uriVariableValues);
247+
}
248+
224249
// URI components methods
225250

226251
/**

org.springframework.web/src/test/java/org/springframework/web/util/UriComponentsBuilderTests.java

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,19 @@
1616

1717
package org.springframework.web.util;
1818

19+
import static org.junit.Assert.assertEquals;
20+
import static org.junit.Assert.assertNull;
21+
1922
import java.net.URI;
2023
import java.net.URISyntaxException;
2124
import java.util.Arrays;
25+
import java.util.HashMap;
26+
import java.util.Map;
2227

2328
import org.junit.Test;
24-
2529
import org.springframework.util.LinkedMultiValueMap;
2630
import org.springframework.util.MultiValueMap;
2731

28-
import static org.junit.Assert.*;
29-
3032
/** @author Arjen Poutsma */
3133
public class UriComponentsBuilderTests {
3234

@@ -227,7 +229,6 @@ public void emptyQueryParam() throws URISyntaxException {
227229
assertEquals(expectedQueryParams, result.getQueryParams());
228230
}
229231

230-
231232
@Test
232233
public void replaceQueryParam() {
233234
UriComponentsBuilder builder = UriComponentsBuilder.newInstance().queryParam("baz", "qux", 42);
@@ -243,4 +244,15 @@ public void replaceQueryParam() {
243244
assertNull("Query param should have been deleted", result.getQuery());
244245
}
245246

247+
@Test
248+
public void buildAndExpand() {
249+
UriComponents result = UriComponentsBuilder.fromPath("/{foo}").buildAndExpand("fooValue");
250+
assertEquals("/fooValue", result.toUriString());
251+
252+
Map<String, String> values = new HashMap<String, String>();
253+
values.put("foo", "fooValue");
254+
values.put("bar", "barValue");
255+
result = UriComponentsBuilder.fromPath("/{foo}/{bar}").buildAndExpand(values);
256+
assertEquals("/fooValue/barValue", result.toUriString());
257+
}
246258
}

org.springframework.web/src/test/java/org/springframework/web/util/UriComponentsTests.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,6 @@ public void expandEncoded() {
6262
@Test(expected = IllegalArgumentException.class)
6363
public void invalidCharacters() {
6464
UriComponentsBuilder.fromPath("/{foo}").build(true);
65-
6665
}
6766

6867
@Test(expected = IllegalArgumentException.class)

spring-framework-reference/src/mvc.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1199,6 +1199,13 @@ public class RelativePathUriTemplateController {
11991199
indicated by the <classname>@SessionAttributes</classname>
12001200
annotation at the handler type level.</para>
12011201
</listitem>
1202+
1203+
<listitem>
1204+
<para><classname>org.springframework.web.util.UriComponentsBuilder</classname>
1205+
a builder for preparing a URL relative to the current request's
1206+
host, port, scheme, context path, and the literal part of the
1207+
servlet mapping.</para>
1208+
</listitem>
12021209
</itemizedlist></para>
12031210

12041211
<para>The <interfacename>Errors</interfacename> or

0 commit comments

Comments
 (0)