Skip to content

Commit febcd0c

Browse files
committed
Add baseUrl overloaded MvcUriComponentsBuilder methods
Issue: SPR-12800
1 parent 7f9975e commit febcd0c

File tree

4 files changed

+133
-26
lines changed

4 files changed

+133
-26
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -697,7 +697,7 @@ public UriComponentsBuilder fragment(String fragment) {
697697
}
698698

699699
@Override
700-
protected Object clone() {
700+
public Object clone() {
701701
return new UriComponentsBuilder(this);
702702
}
703703

spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/MvcUriComponentsBuilder.java

Lines changed: 97 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import java.util.HashMap;
2222
import java.util.List;
2323
import java.util.Map;
24+
2425
import javax.servlet.http.HttpServletRequest;
2526

2627
import org.aopalliance.intercept.MethodInterceptor;
@@ -122,8 +123,28 @@ protected MvcUriComponentsBuilder(MvcUriComponentsBuilder other) {
122123
* @return a UriComponentsBuilder instance (never {@code null})
123124
*/
124125
public static UriComponentsBuilder fromController(Class<?> controllerType) {
126+
return fromController(null, controllerType);
127+
}
128+
129+
/**
130+
* An alternative to {@link #fromController(Class)} that accepts a
131+
* {@code UriComponentsBuilder} representing the base URL. This is useful
132+
* when using MvcUriComponentsBuilder outside the context of processing a
133+
* request or to apply a custom baseUrl not matching the current request.
134+
* @param builder the builder for the base URL; the builder will be cloned
135+
* and therefore not modified and may be re-used for further calls.
136+
* @param controllerType the controller to build a URI for
137+
* @return a UriComponentsBuilder instance (never {@code null})
138+
*/
139+
public static UriComponentsBuilder fromController(UriComponentsBuilder builder, Class<?> controllerType) {
140+
if (builder != null) {
141+
builder = (UriComponentsBuilder) builder.clone();
142+
}
143+
else {
144+
builder = ServletUriComponentsBuilder.fromCurrentServletMapping();
145+
}
125146
String mapping = getTypeRequestMapping(controllerType);
126-
return ServletUriComponentsBuilder.fromCurrentServletMapping().path(mapping);
147+
return builder.path(mapping);
127148
}
128149

129150
private static String getTypeRequestMapping(Class<?> controllerType) {
@@ -144,29 +165,49 @@ private static String getTypeRequestMapping(Class<?> controllerType) {
144165
* to {@link #fromMethod(java.lang.reflect.Method, Object...)}.
145166
* @param controllerType the controller
146167
* @param methodName the method name
147-
* @param argumentValues the argument values
168+
* @param args the argument values
148169
* @return a UriComponentsBuilder instance, never {@code null}
149170
* @throws IllegalArgumentException if there is no matching or
150171
* if there is more than one matching method
151172
*/
152-
public static UriComponentsBuilder fromMethodName(Class<?> controllerType, String methodName, Object... argumentValues) {
153-
Method method = getMethod(controllerType, methodName, argumentValues);
154-
return fromMethod(method, argumentValues);
173+
public static UriComponentsBuilder fromMethodName(Class<?> controllerType, String methodName, Object... args) {
174+
return fromMethodName(null, controllerType, methodName, args);
155175
}
156176

157-
private static Method getMethod(Class<?> controllerType, String methodName, Object... argumentValues) {
177+
/**
178+
* An alternative to {@link #fromMethodName(Class, String, Object...)} that
179+
* accepts a {@code UriComponentsBuilder} representing the base URL. This is
180+
* useful when using MvcUriComponentsBuilder outside the context of processing
181+
* a request or to apply a custom baseUrl not matching the current request.
182+
* @param builder the builder for the base URL; the builder will be cloned
183+
* and therefore not modified and may be re-used for further calls.
184+
* @param controllerType the controller
185+
* @param methodName the method name
186+
* @param args the argument values
187+
* @return a UriComponentsBuilder instance, never {@code null}
188+
* @throws IllegalArgumentException if there is no matching or
189+
* if there is more than one matching method
190+
*/
191+
public static UriComponentsBuilder fromMethodName(UriComponentsBuilder builder,
192+
Class<?> controllerType, String methodName, Object... args) {
193+
194+
Method method = getMethod(controllerType, methodName, args);
195+
return fromMethod(builder, method, args);
196+
}
197+
198+
private static Method getMethod(Class<?> controllerType, String methodName, Object... args) {
158199
Method match = null;
159200
for (Method method : controllerType.getDeclaredMethods()) {
160-
if (method.getName().equals(methodName) && method.getParameterTypes().length == argumentValues.length) {
201+
if (method.getName().equals(methodName) && method.getParameterTypes().length == args.length) {
161202
if (match != null) {
162203
throw new IllegalArgumentException("Found two methods named '" + methodName + "' having " +
163-
Arrays.asList(argumentValues) + " arguments, controller " + controllerType.getName());
204+
Arrays.asList(args) + " arguments, controller " + controllerType.getName());
164205
}
165206
match = method;
166207
}
167208
}
168209
if (match == null) {
169-
throw new IllegalArgumentException("No method '" + methodName + "' with " + argumentValues.length +
210+
throw new IllegalArgumentException("No method '" + methodName + "' with " + args.length +
170211
" parameters found in " + controllerType.getName());
171212
}
172213
return match;
@@ -208,9 +249,24 @@ private static Method getMethod(Class<?> controllerType, String methodName, Obje
208249
* @return a UriComponents instance
209250
*/
210251
public static UriComponentsBuilder fromMethodCall(Object invocationInfo) {
252+
return fromMethodCall(null, invocationInfo);
253+
}
254+
255+
/**
256+
* An alternative to {@link #fromMethodCall(Object)} that accepts a
257+
* {@code UriComponentsBuilder} representing the base URL. This is useful
258+
* when using MvcUriComponentsBuilder outside the context of processing a
259+
* request or to apply a custom baseUrl not matching the current request.
260+
* @param builder the builder for the base URL; the builder will be cloned
261+
* and therefore not modified and may be re-used for further calls.
262+
* @param invocationInfo either the value returned from a "mock" controller
263+
* invocation or the "mock" controller itself after an invocation
264+
* @return a UriComponents instance
265+
*/
266+
public static UriComponentsBuilder fromMethodCall(UriComponentsBuilder builder, Object invocationInfo) {
211267
Assert.isInstanceOf(MethodInvocationInfo.class, invocationInfo);
212268
MethodInvocationInfo info = (MethodInvocationInfo) invocationInfo;
213-
return fromMethod(info.getControllerMethod(), info.getArgumentValues());
269+
return fromMethod(builder, info.getControllerMethod(), info.getArgumentValues());
214270
}
215271

216272
/**
@@ -277,20 +333,42 @@ public static MethodArgumentBuilder fromMappingName(String mappingName) {
277333
* and an array of method argument values. The array of values must match the
278334
* signature of the controller method. Values for {@code @RequestParam} and
279335
* {@code @PathVariable} are used for building the URI (via implementations of
280-
* {@link org.springframework.web.method.support.UriComponentsContributor})
281-
* while remaining argument values are ignored and can be {@code null}.
336+
* {@link org.springframework.web.method.support.UriComponentsContributor
337+
* UriComponentsContributor}) while remaining argument values are ignored and
338+
* can be {@code null}.
339+
* @param method the controller method
340+
* @param args argument values for the controller method
341+
* @return a UriComponentsBuilder instance, never {@code null}
342+
*/
343+
public static UriComponentsBuilder fromMethod(Method method, Object... args) {
344+
return fromMethod(null, method, args);
345+
}
346+
347+
/**
348+
* An alternative to {@link #fromMethod(java.lang.reflect.Method, Object...)}
349+
* that accepts a {@code UriComponentsBuilder} representing the base URL.
350+
* This is useful when using MvcUriComponentsBuilder outside the context of
351+
* processing a request or to apply a custom baseUrl not matching the
352+
* current request.
353+
* @param builder the builder for the base URL; the builder will be cloned
354+
* and therefore not modified and may be re-used for further calls.
282355
* @param method the controller method
283-
* @param argumentValues argument values for the controller method
356+
* @param args argument values for the controller method
284357
* @return a UriComponentsBuilder instance, never {@code null}
285358
*/
286-
public static UriComponentsBuilder fromMethod(Method method, Object... argumentValues) {
359+
public static UriComponentsBuilder fromMethod(UriComponentsBuilder builder, Method method, Object... args) {
360+
if (builder != null) {
361+
builder = (UriComponentsBuilder) builder.clone();
362+
}
363+
else {
364+
builder = ServletUriComponentsBuilder.fromCurrentServletMapping();
365+
}
287366
String typePath = getTypeRequestMapping(method.getDeclaringClass());
288367
String methodPath = getMethodRequestMapping(method);
289368
String path = pathMatcher.combine(typePath, methodPath);
290-
291-
UriComponentsBuilder builder = ServletUriComponentsBuilder.fromCurrentServletMapping().path(path);
292-
UriComponents uriComponents = applyContributors(builder, method, argumentValues);
293-
return ServletUriComponentsBuilder.newInstance().uriComponents(uriComponents);
369+
builder.path(path);
370+
UriComponents uriComponents = applyContributors(builder, method, args);
371+
return UriComponentsBuilder.newInstance().uriComponents(uriComponents);
294372
}
295373

296374
private static String getMethodRequestMapping(Method method) {
@@ -453,7 +531,7 @@ private static <T> T initProxy(Class<?> type, ControllerMethodInvocationIntercep
453531
}
454532

455533
@Override
456-
protected Object clone() {
534+
public Object clone() {
457535
return new MvcUriComponentsBuilder(this);
458536
}
459537

spring-webmvc/src/main/java/org/springframework/web/servlet/support/ServletUriComponentsBuilder.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,7 @@ public String removePathExtension() {
223223
}
224224

225225
@Override
226-
protected Object clone() {
226+
public Object clone() {
227227
return new ServletUriComponentsBuilder(this);
228228
}
229229

spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/MvcUriComponentsBuilderTests.java

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2014 the original author or authors.
2+
* Copyright 2012-2015 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,6 +16,10 @@
1616

1717
package org.springframework.web.servlet.mvc.method.annotation;
1818

19+
import static org.hamcrest.Matchers.*;
20+
import static org.junit.Assert.*;
21+
import static org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder.*;
22+
1923
import java.util.Arrays;
2024
import java.util.List;
2125

@@ -38,10 +42,7 @@
3842
import org.springframework.web.context.request.RequestContextHolder;
3943
import org.springframework.web.context.request.ServletRequestAttributes;
4044
import org.springframework.web.util.UriComponents;
41-
42-
import static org.hamcrest.Matchers.*;
43-
import static org.junit.Assert.*;
44-
import static org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder.*;
45+
import org.springframework.web.util.UriComponentsBuilder;
4546

4647
/**
4748
* Unit tests for {@link org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder}.
@@ -98,6 +99,15 @@ public void testFromControllerNotMapped() {
9899
assertThat(uriComponents.toUriString(), is("http://localhost/"));
99100
}
100101

102+
@Test
103+
public void testFromControllerWithCustomBaseUrl() {
104+
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString("http://example.org:9090/base");
105+
UriComponents uriComponents = fromController(builder, PersonControllerImpl.class).build();
106+
107+
assertEquals("http://example.org:9090/base/people", uriComponents.toString());
108+
assertEquals("http://example.org:9090/base", builder.toUriString());
109+
}
110+
101111
@Test
102112
public void testFromMethodNamePathVariable() throws Exception {
103113
UriComponents uriComponents = fromMethodName(
@@ -151,6 +161,16 @@ public void testFromMethodNameNotMapped() throws Exception {
151161
assertThat(uriComponents.toUriString(), is("http://localhost/"));
152162
}
153163

164+
@Test
165+
public void testFromMethodNameWithCustomBaseUrl() throws Exception {
166+
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString("http://example.org:9090/base");
167+
UriComponents uriComponents = fromMethodName(builder, ControllerWithMethods.class,
168+
"methodWithPathVariable", new Object[] {"1"}).build();
169+
170+
assertEquals("http://example.org:9090/base/something/1/foo", uriComponents.toString());
171+
assertEquals("http://example.org:9090/base", builder.toUriString());
172+
}
173+
154174
@Test
155175
public void testFromMethodCall() {
156176
UriComponents uriComponents = fromMethodCall(on(ControllerWithMethods.class).myMethod(null)).build();
@@ -201,6 +221,15 @@ public void testFromMethodCallWithPathVarAndMultiValueRequestParams() {
201221
assertThat(queryParams.get("items"), containsInAnyOrder("3", "7"));
202222
}
203223

224+
@Test
225+
public void testFromMethodCallWithCustomBaseUrl() {
226+
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString("http://example.org:9090/base");
227+
UriComponents uriComponents = fromMethodCall(builder, on(ControllerWithMethods.class).myMethod(null)).build();
228+
229+
assertEquals("http://example.org:9090/base/something/else", uriComponents.toString());
230+
assertEquals("http://example.org:9090/base", builder.toUriString());
231+
}
232+
204233
@Test
205234
public void usesForwardedHostAsHostIfHeaderIsSet() {
206235
this.request.addHeader("X-Forwarded-Host", "somethingDifferent");

0 commit comments

Comments
 (0)