Skip to content

Commit af4ea49

Browse files
committed
Template variables SPI
1 parent 59aed85 commit af4ea49

File tree

5 files changed

+108
-16
lines changed

5 files changed

+108
-16
lines changed
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package org.springframework.hateoas.server.core;
2+
3+
import org.springframework.hateoas.TemplateVariables;
4+
import org.springframework.web.util.UriComponents;
5+
import org.springframework.web.util.UriComponentsBuilder;
6+
7+
/**
8+
* @author Réda Housni Alaoui
9+
*/
10+
public interface AdditionalUriHandler {
11+
12+
UriComponentsBuilder apply(UriComponentsBuilder uriComponentsBuilder, MethodInvocation methodInvocation);
13+
14+
TemplateVariables apply(TemplateVariables templateVariables, UriComponents uriComponents, MethodInvocation methodInvocation);
15+
}

src/main/java/org/springframework/hateoas/server/core/WebHandler.java

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
import java.lang.reflect.Method;
2525
import java.util.*;
2626
import java.util.concurrent.ConcurrentHashMap;
27-
import java.util.function.BiFunction;
2827
import java.util.function.Function;
2928
import java.util.stream.Collectors;
3029

@@ -56,6 +55,7 @@
5655
*
5756
* @author Greg Turnquist
5857
* @author Oliver Drotbohm
58+
* @author Réda Housni Alaoui
5959
*/
6060
public class WebHandler {
6161

@@ -75,20 +75,19 @@ public interface PreparedWebHandler<T extends LinkBuilder> {
7575

7676
public static <T extends LinkBuilder> PreparedWebHandler<T> linkTo(Object invocationValue,
7777
LinkBuilderCreator<T> creator) {
78-
return linkTo(invocationValue, creator,
79-
(BiFunction<UriComponentsBuilder, MethodInvocation, UriComponentsBuilder>) null);
78+
return linkTo(invocationValue, creator, null);
8079
}
8180

8281
public static <T extends LinkBuilder> T linkTo(Object invocationValue, LinkBuilderCreator<T> creator,
83-
@Nullable BiFunction<UriComponentsBuilder, MethodInvocation, UriComponentsBuilder> additionalUriHandler,
82+
@Nullable AdditionalUriHandler additionalUriHandler,
8483
Function<String, UriComponentsBuilder> finisher) {
8584

8685
return linkTo(invocationValue, creator, additionalUriHandler).conclude(finisher);
8786
}
8887

8988
private static <T extends LinkBuilder> PreparedWebHandler<T> linkTo(Object invocationValue,
9089
LinkBuilderCreator<T> creator,
91-
@Nullable BiFunction<UriComponentsBuilder, MethodInvocation, UriComponentsBuilder> additionalUriHandler) {
90+
@Nullable AdditionalUriHandler additionalUriHandler) {
9291

9392
Assert.isInstanceOf(LastInvocationAware.class, invocationValue);
9493

@@ -150,7 +149,9 @@ private static <T extends LinkBuilder> PreparedWebHandler<T> linkTo(Object invoc
150149
? builder.buildAndExpand(values) //
151150
: additionalUriHandler.apply(builder, invocation).buildAndExpand(values);
152151

153-
TemplateVariables variables = NONE;
152+
TemplateVariables variables = additionalUriHandler == null
153+
? NONE
154+
: additionalUriHandler.apply(NONE, components, invocation);
154155

155156
for (String parameter : optionalEmptyParameters) {
156157

src/main/java/org/springframework/hateoas/server/mvc/UriComponentsContributor.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,16 @@
1515
*/
1616
package org.springframework.hateoas.server.mvc;
1717

18+
import java.util.Collection;
19+
import java.util.Collections;
20+
1821
import org.springframework.core.MethodParameter;
22+
import org.springframework.hateoas.TemplateVariable;
23+
import org.springframework.hateoas.TemplateVariables;
1924
import org.springframework.hateoas.server.MethodLinkBuilderFactory;
2025
import org.springframework.lang.Nullable;
2126
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
27+
import org.springframework.web.util.UriComponents;
2228
import org.springframework.web.util.UriComponentsBuilder;
2329

2430
/**
@@ -28,6 +34,7 @@
2834
*
2935
* @see MethodLinkBuilderFactory#linkTo(Object)
3036
* @author Oliver Gierke
37+
* @author Réda Housni Alaoui
3138
*/
3239
public interface UriComponentsContributor {
3340

@@ -47,4 +54,15 @@ public interface UriComponentsContributor {
4754
* @param value can be {@literal null}.
4855
*/
4956
void enhance(UriComponentsBuilder builder, @Nullable MethodParameter parameter, @Nullable Object value);
57+
58+
/**
59+
* Enhance the given {@link TemplateVariables}
60+
*
61+
* @param templateVariables will never be {@literal null}.
62+
* @param uriComponents will never be {@literal null}.
63+
* @param parameter can be {@literal null}.
64+
*/
65+
default TemplateVariables enhance(TemplateVariables templateVariables, UriComponents uriComponents, @Nullable MethodParameter parameter){
66+
return templateVariables;
67+
}
5068
}

src/main/java/org/springframework/hateoas/server/mvc/WebMvcLinkBuilderFactory.java

Lines changed: 42 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,14 @@
2626

2727
import org.springframework.core.MethodParameter;
2828
import org.springframework.hateoas.Link;
29+
import org.springframework.hateoas.TemplateVariables;
2930
import org.springframework.hateoas.server.MethodLinkBuilderFactory;
31+
import org.springframework.hateoas.server.core.AdditionalUriHandler;
3032
import org.springframework.hateoas.server.core.LinkBuilderSupport;
33+
import org.springframework.hateoas.server.core.MethodInvocation;
3134
import org.springframework.hateoas.server.core.MethodParameters;
3235
import org.springframework.hateoas.server.core.WebHandler;
36+
import org.springframework.web.util.UriComponents;
3337
import org.springframework.web.util.UriComponentsBuilder;
3438

3539
/**
@@ -44,6 +48,7 @@
4448
* @author Kevin Conaway
4549
* @author Andrew Naydyonock
4650
* @author Greg Turnquist
51+
* @author Réda Housni Alaoui
4752
*/
4853
public class WebMvcLinkBuilderFactory implements MethodLinkBuilderFactory<WebMvcLinkBuilder> {
4954

@@ -106,8 +111,29 @@ public WebMvcLinkBuilder linkTo(Object invocationValue) {
106111
Function<String, UriComponentsBuilder> builderFactory = mapping -> UriComponentsBuilderFactory.getBuilder()
107112
.path(mapping);
108113

109-
return WebHandler.linkTo(invocationValue, WebMvcLinkBuilder::new, (builder, invocation) -> {
114+
return WebHandler.linkTo(invocationValue, WebMvcLinkBuilder::new,
115+
new UriComponentsContributorsAdditionalUriHandler(uriComponentsContributors), builderFactory);
116+
}
110117

118+
/*
119+
* (non-Javadoc)
120+
* @see org.springframework.hateoas.MethodLinkBuilderFactory#linkTo(java.lang.reflect.Method, java.lang.Object[])
121+
*/
122+
@Override
123+
public WebMvcLinkBuilder linkTo(Method method, Object... parameters) {
124+
return WebMvcLinkBuilder.linkTo(method, parameters);
125+
}
126+
127+
private static class UriComponentsContributorsAdditionalUriHandler implements AdditionalUriHandler {
128+
129+
private final List<UriComponentsContributor> uriComponentsContributors;
130+
131+
private UriComponentsContributorsAdditionalUriHandler(List<UriComponentsContributor> uriComponentsContributors) {
132+
this.uriComponentsContributors = uriComponentsContributors;
133+
}
134+
135+
@Override
136+
public UriComponentsBuilder apply(UriComponentsBuilder builder, MethodInvocation invocation) {
111137
MethodParameters parameters = MethodParameters.of(invocation.getMethod());
112138
Iterator<Object> parameterValues = Arrays.asList(invocation.getArguments()).iterator();
113139

@@ -123,16 +149,22 @@ public WebMvcLinkBuilder linkTo(Object invocationValue) {
123149
}
124150

125151
return builder;
152+
}
126153

127-
}, builderFactory);
128-
}
154+
@Override
155+
public TemplateVariables apply(TemplateVariables templateVariables, UriComponents uriComponents, MethodInvocation invocation) {
156+
MethodParameters parameters = MethodParameters.of(invocation.getMethod());
129157

130-
/*
131-
* (non-Javadoc)
132-
* @see org.springframework.hateoas.MethodLinkBuilderFactory#linkTo(java.lang.reflect.Method, java.lang.Object[])
133-
*/
134-
@Override
135-
public WebMvcLinkBuilder linkTo(Method method, Object... parameters) {
136-
return WebMvcLinkBuilder.linkTo(method, parameters);
158+
for (MethodParameter parameter : parameters.getParameters()) {
159+
160+
for (UriComponentsContributor contributor : uriComponentsContributors) {
161+
if (contributor.supportsParameter(parameter)) {
162+
templateVariables = contributor.enhance(templateVariables, uriComponents, parameter);
163+
}
164+
}
165+
}
166+
167+
return templateVariables;
168+
}
137169
}
138170
}

src/test/java/org/springframework/hateoas/server/mvc/WebMvcLinkBuilderFactoryUnitTest.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@
3131
import org.springframework.format.annotation.DateTimeFormat.ISO;
3232
import org.springframework.hateoas.IanaLinkRelations;
3333
import org.springframework.hateoas.Link;
34+
import org.springframework.hateoas.TemplateVariable;
35+
import org.springframework.hateoas.TemplateVariables;
3436
import org.springframework.hateoas.TestUtils;
3537
import org.springframework.hateoas.server.mvc.WebMvcLinkBuilderUnitTest.ControllerWithMethods;
3638
import org.springframework.hateoas.server.mvc.WebMvcLinkBuilderUnitTest.PersonControllerImpl;
@@ -41,6 +43,7 @@
4143
import org.springframework.web.bind.annotation.PathVariable;
4244
import org.springframework.web.bind.annotation.RequestMapping;
4345
import org.springframework.web.bind.annotation.RequestParam;
46+
import org.springframework.web.util.UriComponents;
4447
import org.springframework.web.util.UriComponentsBuilder;
4548

4649
/**
@@ -50,6 +53,7 @@
5053
* @author Oliver Gierke
5154
* @author Kamill Sokol
5255
* @author Ross Turner
56+
* @author Réda Housni Alaoui
5357
*/
5458
class WebMvcLinkBuilderFactoryUnitTest extends TestUtils {
5559

@@ -174,6 +178,17 @@ void createsLinkToParameterizedControllerRootWithParameterMap() {
174178
assertThat(link.getHref()).endsWith("/people/17/addresses");
175179
}
176180

181+
@Test
182+
void appliesTemplateVariableIfContributorConfigured() {
183+
184+
WebMvcLinkBuilderFactory factory = new WebMvcLinkBuilderFactory();
185+
factory.setUriComponentsContributors(Collections.singletonList(new SampleUriComponentsContributor()));
186+
187+
Link link = factory.linkTo(methodOn(SampleController.class).sampleMethod(1L, null)).withSelfRel();
188+
assertPointsToMockServer(link);
189+
assertThat(link.getHref()).endsWith("/sample/1{?foo}");
190+
}
191+
177192
interface SampleController {
178193

179194
@RequestMapping("/sample/{id}")
@@ -198,8 +213,19 @@ public boolean supportsParameter(MethodParameter parameter) {
198213

199214
@Override
200215
public void enhance(UriComponentsBuilder builder, MethodParameter parameter, Object value) {
216+
if (value == null) {
217+
return;
218+
}
201219
builder.queryParam("foo", ((SpecialType) value).parameterValue);
202220
}
221+
222+
@Override
223+
public TemplateVariables enhance(TemplateVariables templateVariables, UriComponents uriComponents, MethodParameter parameter) {
224+
if (uriComponents.getQueryParams().containsKey("foo")) {
225+
return templateVariables;
226+
}
227+
return templateVariables.concat(TemplateVariable.requestParameter("foo"));
228+
}
203229
}
204230

205231
static class SpecialType {

0 commit comments

Comments
 (0)