Skip to content

Commit 7268a81

Browse files
committed
Add a template variables contribution SPI
spring-projects#1312
1 parent 764644e commit 7268a81

File tree

5 files changed

+93
-8
lines changed

5 files changed

+93
-8
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: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
import java.util.*;
2525
import java.util.Map.Entry;
2626
import java.util.concurrent.ConcurrentHashMap;
27-
import java.util.function.BiFunction;
2827
import java.util.function.Function;
2928
import java.util.function.Supplier;
3029
import java.util.stream.Collectors;
@@ -73,12 +72,11 @@ public interface PreparedWebHandler<T extends LinkBuilder> {
7372

7473
public static <T extends LinkBuilder> PreparedWebHandler<T> linkTo(Object invocationValue,
7574
LinkBuilderCreator<T> creator) {
76-
return linkTo(invocationValue, creator,
77-
(BiFunction<UriComponentsBuilder, MethodInvocation, UriComponentsBuilder>) null);
75+
return linkTo(invocationValue, creator, null);
7876
}
7977

8078
public static <T extends LinkBuilder> T linkTo(Object invocationValue, LinkBuilderCreator<T> creator,
81-
@Nullable BiFunction<UriComponentsBuilder, MethodInvocation, UriComponentsBuilder> additionalUriHandler,
79+
@Nullable AdditionalUriHandler additionalUriHandler,
8280
Function<UriMapping, UriComponentsBuilder> finisher, Supplier<ConversionService> conversionService) {
8381

8482
return linkTo(invocationValue, creator, additionalUriHandler).conclude(finisher,
@@ -87,7 +85,7 @@ public static <T extends LinkBuilder> T linkTo(Object invocationValue, LinkBuild
8785

8886
private static <T extends LinkBuilder> PreparedWebHandler<T> linkTo(Object invocationValue,
8987
LinkBuilderCreator<T> creator,
90-
@Nullable BiFunction<UriComponentsBuilder, MethodInvocation, UriComponentsBuilder> additionalUriHandler) {
88+
@Nullable AdditionalUriHandler additionalUriHandler) {
9189

9290
Assert.isInstanceOf(LastInvocationAware.class, invocationValue);
9391

@@ -177,7 +175,9 @@ private static <T extends LinkBuilder> PreparedWebHandler<T> linkTo(Object invoc
177175
? builder.buildAndExpand(values) //
178176
: additionalUriHandler.apply(builder, invocation).buildAndExpand(values);
179177

180-
TemplateVariables variables = NONE;
178+
TemplateVariables variables = additionalUriHandler == null
179+
? NONE
180+
: additionalUriHandler.apply(NONE, components, invocation);
181181

182182
for (String parameter : optionalEmptyParameters) {
183183

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,11 @@
1616
package org.springframework.hateoas.server.mvc;
1717

1818
import org.springframework.core.MethodParameter;
19+
import org.springframework.hateoas.TemplateVariables;
1920
import org.springframework.hateoas.server.MethodLinkBuilderFactory;
2021
import org.springframework.lang.Nullable;
2122
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
23+
import org.springframework.web.util.UriComponents;
2224
import org.springframework.web.util.UriComponentsBuilder;
2325

2426
/**
@@ -28,6 +30,7 @@
2830
*
2931
* @see MethodLinkBuilderFactory#linkTo(Object)
3032
* @author Oliver Gierke
33+
* @author Réda Housni Alaoui
3134
*/
3235
public interface UriComponentsContributor {
3336

@@ -47,4 +50,15 @@ public interface UriComponentsContributor {
4750
* @param value can be {@literal null}.
4851
*/
4952
void enhance(UriComponentsBuilder builder, @Nullable MethodParameter parameter, @Nullable Object value);
53+
54+
/**
55+
* Enhance the given {@link TemplateVariables}
56+
*
57+
* @param templateVariables will never be {@literal null}.
58+
* @param uriComponents will never be {@literal null}.
59+
* @param parameter will never be {@literal null}.
60+
*/
61+
default TemplateVariables enhance(TemplateVariables templateVariables, UriComponents uriComponents, MethodParameter parameter){
62+
return templateVariables;
63+
}
5064
}

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

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,11 @@
3131
import org.springframework.core.convert.ConversionService;
3232
import org.springframework.format.support.DefaultFormattingConversionService;
3333
import org.springframework.hateoas.Link;
34+
import org.springframework.hateoas.TemplateVariables;
3435
import org.springframework.hateoas.server.MethodLinkBuilderFactory;
36+
import org.springframework.hateoas.server.core.AdditionalUriHandler;
3537
import org.springframework.hateoas.server.core.LinkBuilderSupport;
38+
import org.springframework.hateoas.server.core.MethodInvocation;
3639
import org.springframework.hateoas.server.core.MethodParameters;
3740
import org.springframework.hateoas.server.core.SpringAffordanceBuilder;
3841
import org.springframework.hateoas.server.core.UriMapping;
@@ -44,6 +47,7 @@
4447
import org.springframework.web.context.support.WebApplicationContextUtils;
4548
import org.springframework.web.servlet.mvc.condition.NameValueExpression;
4649
import org.springframework.web.servlet.mvc.condition.ParamsRequestCondition;
50+
import org.springframework.web.util.UriComponents;
4751
import org.springframework.web.util.UriComponentsBuilder;
4852

4953
/**
@@ -150,8 +154,20 @@ public WebMvcLinkBuilder linkTo(Object invocationValue) {
150154
Function<UriMapping, UriComponentsBuilder> builderFactory = mapping -> UriComponentsBuilderFactory
151155
.forMapping(mapping);
152156

153-
return WebHandler.linkTo(invocationValue, WebMvcLinkBuilder::new, (builder, invocation) -> {
157+
return WebHandler.linkTo(invocationValue, WebMvcLinkBuilder::new,
158+
new UriComponentsContributorsAdditionalUriHandler(uriComponentsContributors), builderFactory, getConversionService());
159+
}
160+
161+
private static class UriComponentsContributorsAdditionalUriHandler implements AdditionalUriHandler {
162+
163+
private final List<UriComponentsContributor> uriComponentsContributors;
164+
165+
private UriComponentsContributorsAdditionalUriHandler(List<UriComponentsContributor> uriComponentsContributors) {
166+
this.uriComponentsContributors = uriComponentsContributors;
167+
}
154168

169+
@Override
170+
public UriComponentsBuilder apply(UriComponentsBuilder builder, MethodInvocation invocation) {
155171
String[] primaryParams = SpringAffordanceBuilder.DISCOVERER.getParams(invocation.getMethod());
156172

157173
if (primaryParams.length > 0) {
@@ -189,8 +205,23 @@ public WebMvcLinkBuilder linkTo(Object invocationValue) {
189205
}
190206

191207
return builder;
208+
}
209+
210+
@Override
211+
public TemplateVariables apply(TemplateVariables templateVariables, UriComponents uriComponents, MethodInvocation invocation) {
212+
MethodParameters parameters = MethodParameters.of(invocation.getMethod());
213+
214+
for (MethodParameter parameter : parameters.getParameters()) {
215+
216+
for (UriComponentsContributor contributor : uriComponentsContributors) {
217+
if (contributor.supportsParameter(parameter)) {
218+
templateVariables = contributor.enhance(templateVariables, uriComponents, parameter);
219+
}
220+
}
221+
}
192222

193-
}, builderFactory, getConversionService());
223+
return templateVariables;
224+
}
194225
}
195226

196227
private static Supplier<ConversionService> getConversionService() {

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

Lines changed: 25 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
/**
@@ -184,6 +187,17 @@ void linksToMethodWithPrimaryParam() {
184187
assertThat(link.getHref()).endsWith("/something/foo?a=1&b=2");
185188
}
186189

190+
@Test
191+
void appliesTemplateVariableIfContributorConfigured() {
192+
193+
WebMvcLinkBuilderFactory factory = new WebMvcLinkBuilderFactory();
194+
factory.setUriComponentsContributors(Collections.singletonList(new SampleUriComponentsContributor()));
195+
196+
Link link = factory.linkTo(methodOn(SampleController.class).sampleMethod(1L, null)).withSelfRel();
197+
assertPointsToMockServer(link);
198+
assertThat(link.getHref()).endsWith("/sample/1{?foo}");
199+
}
200+
187201
interface SampleController {
188202

189203
@RequestMapping("/sample/{id}")
@@ -208,8 +222,19 @@ public boolean supportsParameter(MethodParameter parameter) {
208222

209223
@Override
210224
public void enhance(UriComponentsBuilder builder, MethodParameter parameter, Object value) {
225+
if (value == null) {
226+
return;
227+
}
211228
builder.queryParam("foo", ((SpecialType) value).parameterValue);
212229
}
230+
231+
@Override
232+
public TemplateVariables enhance(TemplateVariables templateVariables, UriComponents uriComponents, MethodParameter parameter) {
233+
if (uriComponents.getQueryParams().containsKey("foo")) {
234+
return templateVariables;
235+
}
236+
return templateVariables.concat(TemplateVariable.requestParameter("foo"));
237+
}
213238
}
214239

215240
static class SpecialType {

0 commit comments

Comments
 (0)