Skip to content

Commit 7478ee1

Browse files
committed
Changed linkTo so that it can be invoked outside of a HttpRequest.
Changed linkTo so that it can be invoked outside of a HttpRequest - issue spring-projects#408. Added linkTo method, which takes an explicit UriComponentBuilder.
1 parent 4e1e5ed commit 7478ee1

File tree

4 files changed

+67
-15
lines changed

4 files changed

+67
-15
lines changed

src/main/java/org/springframework/hateoas/MethodLinkBuilderFactory.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
import org.springframework.hateoas.core.DummyInvocationUtils;
2121
import org.springframework.hateoas.mvc.ControllerLinkBuilder;
22+
import org.springframework.web.util.UriComponentsBuilder;
2223

2324
/**
2425
* Extension of {@link LinkBuilderFactory} for implementations that also support creating {@link LinkBuilder}s by
@@ -59,4 +60,16 @@ public interface MethodLinkBuilderFactory<T extends LinkBuilder> extends LinkBui
5960
* @return
6061
*/
6162
T linkTo(Object methodInvocationResult);
63+
64+
/**
65+
* Returns a {@link LinkBuilder} pointing to the URI mapped to the method the result is handed into this method. Use
66+
* {@link DummyInvocationUtils#methodOn(Class, Object...)} to obtain a dummy instance of a controller to record a
67+
* dummy method invocation on. See {@link ControllerLinkBuilder#linkTo(Object)} for an example.
68+
*
69+
* @see ControllerLinkBuilder#linkTo(Object)
70+
* @param builder must not be {@literal null}.
71+
* @param methodInvocationResult must not be {@literal null}.
72+
* @return
73+
*/
74+
T linkTo(UriComponentsBuilder builder, Object methodInvocationResult);
6275
}

src/main/java/org/springframework/hateoas/mvc/ControllerLinkBuilder.java

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ public static ControllerLinkBuilder linkTo(Class<?> controller, Object... parame
9595
* @see org.springframework.hateoas.MethodLinkBuilderFactory#linkTo(Method, Object...)
9696
*/
9797
public static ControllerLinkBuilder linkTo(Method method, Object... parameters) {
98-
return linkTo(method.getDeclaringClass(), method, parameters);
98+
return linkTo(method.getDeclaringClass(), method, parameters);
9999
}
100100

101101
/*
@@ -138,6 +138,18 @@ public static ControllerLinkBuilder linkTo(Object invocationValue) {
138138
return FACTORY.linkTo(invocationValue);
139139
}
140140

141+
/**
142+
* Creates a {@link ControllerLinkBuilder} pointing to a controller method. Hand in a dummy method invocation result
143+
* you can create via {@link #methodOn(Class, Object...)} or {@link DummyInvocationUtils#methodOn(Class, Object...)}.
144+
*
145+
* @param builder
146+
* @param invocationValue
147+
* @return
148+
*/
149+
public static ControllerLinkBuilder linkTo(UriComponentsBuilder builder, Object invocationValue) {
150+
return FACTORY.linkTo(builder, invocationValue);
151+
}
152+
141153
/**
142154
* Wrapper for {@link DummyInvocationUtils#methodOn(Class, Object...)} to be available in case you work with static
143155
* imports of {@link ControllerLinkBuilder}.
@@ -185,8 +197,13 @@ public UriComponentsBuilder toUriComponentsBuilder() {
185197
* @return
186198
*/
187199
static UriComponentsBuilder getBuilder() {
188-
189-
HttpServletRequest request = getCurrentRequest();
200+
HttpServletRequest request;
201+
try {
202+
request = getCurrentRequest();
203+
} catch (IllegalStateException e){
204+
// not in the context of a Http Request, so return blank UriComponentsBuilder...
205+
return UriComponentsBuilder.fromPath("");
206+
}
190207
ServletUriComponentsBuilder builder = ServletUriComponentsBuilder.fromServletMapping(request);
191208

192209
ForwardedHeader forwarded = ForwardedHeader.of(request.getHeader(ForwardedHeader.NAME));

src/main/java/org/springframework/hateoas/mvc/ControllerLinkBuilderFactory.java

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,12 +103,21 @@ public ControllerLinkBuilder linkTo(Class<?> controller, Method method, Object..
103103
return ControllerLinkBuilder.linkTo(controller, method, parameters);
104104
}
105105

106-
/*
106+
/*
107107
* (non-Javadoc)
108108
* @see org.springframework.hateoas.MethodLinkBuilderFactory#linkTo(java.lang.Object)
109109
*/
110110
@Override
111111
public ControllerLinkBuilder linkTo(Object invocationValue) {
112+
return linkTo(ControllerLinkBuilder.getBuilder(), invocationValue);
113+
}
114+
115+
/*
116+
* (non-Javadoc)
117+
* @see org.springframework.hateoas.MethodLinkBuilderFactory#linkTo(UriComponentsBuilder builder, java.lang.Object)
118+
*/
119+
@Override
120+
public ControllerLinkBuilder linkTo(UriComponentsBuilder builder, Object invocationValue) {
112121

113122
Assert.isInstanceOf(LastInvocationAware.class, invocationValue);
114123
LastInvocationAware invocations = (LastInvocationAware) invocationValue;
@@ -118,7 +127,7 @@ public ControllerLinkBuilder linkTo(Object invocationValue) {
118127
Method method = invocation.getMethod();
119128

120129
String mapping = DISCOVERER.getMapping(invocation.getTargetType(), method);
121-
UriComponentsBuilder builder = ControllerLinkBuilder.getBuilder().path(mapping);
130+
builder.path(mapping);
122131

123132
UriTemplate template = new UriTemplate(mapping);
124133
Map<String, Object> values = new HashMap<String, Object>();

src/test/java/org/springframework/hateoas/mvc/ControllerLinkBuilderOutsideSpringMvcUnitTest.java

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,13 @@
33
import static org.hamcrest.MatcherAssert.*;
44
import static org.hamcrest.Matchers.*;
55
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.*;
6+
import static org.springframework.web.util.UriComponentsBuilder.*;
67

78
import org.junit.Before;
89
import org.junit.Test;
10+
import org.springframework.hateoas.Link;
911
import org.springframework.web.context.request.RequestContextHolder;
12+
import org.springframework.web.util.UriComponentsBuilder;
1013

1114
/**
1215
* Test cases for {@link ControllerLinkBuilder} that are NOT inside an existing Spring MVC request
@@ -24,18 +27,28 @@ public void setUp() {
2427
}
2528

2629
/**
27-
* @see #342
30+
* Calling linkTo outside of a HTTP request should not throw an exception.
31+
* @see #408
2832
*/
29-
@Test(expected = IllegalStateException.class)
30-
public void createsLinkToMethodOnParameterizedControllerRoot() {
33+
@Test
34+
public void callingLinkToOutsideOfHttpRequestShouldNotThrowException() {
35+
Link link = linkTo(methodOn(ControllerLinkBuilderUnitTest.PersonsAddressesController.class, 15)
36+
.getAddressesForCountry("DE")).withSelfRel();
37+
assertThat(link.getHref(), is("/people/15/addresses/DE"));
38+
}
3139

32-
try {
33-
linkTo(methodOn(ControllerLinkBuilderUnitTest.PersonsAddressesController.class, 15)
34-
.getAddressesForCountry("DE")).withSelfRel();
35-
} catch (IllegalStateException e) {
36-
assertThat(e.getMessage(), equalTo("Could not find current request via RequestContextHolder. Is this being called from a Spring MVC handler?"));
37-
throw e;
38-
}
40+
/**
41+
* Calling linkTo outside of a HTTP request, should allow passing in a custom builder.
42+
* @see #408
43+
*/
44+
@Test
45+
public void linkToShouldTakeAUriCompoentsBuilder() {
46+
Link link = linkTo(fromUriString("https://myproxy.net:1234/somepath"), methodOn
47+
(ControllerLinkBuilderUnitTest
48+
.PersonsAddressesController
49+
.class, 15)
50+
.getAddressesForCountry("DE")).withSelfRel();
51+
assertThat(link.getHref(), is("https://myproxy.net:1234/somepath/people/15/addresses/DE"));
3952
}
4053

4154
}

0 commit comments

Comments
 (0)