Skip to content

Commit 2884cf4

Browse files
committed
Template variables SPI
1 parent f3198b7 commit 2884cf4

File tree

6 files changed

+142
-26
lines changed

6 files changed

+142
-26
lines changed
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

+7-6
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@
3434
import java.util.Map;
3535
import java.util.Optional;
3636
import java.util.concurrent.ConcurrentHashMap;
37-
import java.util.function.BiFunction;
3837
import java.util.function.Function;
3938
import java.util.stream.Collectors;
4039

@@ -66,6 +65,7 @@
6665
*
6766
* @author Greg Turnquist
6867
* @author Oliver Drotbohm
68+
* @author Réda Housni Alaoui
6969
*/
7070
public class WebHandler {
7171

@@ -84,20 +84,19 @@ public interface PreparedWebHandler<T extends LinkBuilder> {
8484

8585
public static <T extends LinkBuilder> PreparedWebHandler<T> linkTo(Object invocationValue,
8686
LinkBuilderCreator<T> creator) {
87-
return linkTo(invocationValue, creator,
88-
(BiFunction<UriComponentsBuilder, MethodInvocation, UriComponentsBuilder>) null);
87+
return linkTo(invocationValue, creator, null);
8988
}
9089

9190
public static <T extends LinkBuilder> T linkTo(Object invocationValue, LinkBuilderCreator<T> creator,
92-
@Nullable BiFunction<UriComponentsBuilder, MethodInvocation, UriComponentsBuilder> additionalUriHandler,
91+
@Nullable AdditionalUriHandler additionalUriHandler,
9392
Function<String, UriComponentsBuilder> finisher) {
9493

9594
return linkTo(invocationValue, creator, additionalUriHandler).conclude(finisher);
9695
}
9796

9897
private static <T extends LinkBuilder> PreparedWebHandler<T> linkTo(Object invocationValue,
9998
LinkBuilderCreator<T> creator,
100-
@Nullable BiFunction<UriComponentsBuilder, MethodInvocation, UriComponentsBuilder> additionalUriHandler) {
99+
@Nullable AdditionalUriHandler additionalUriHandler) {
101100

102101
Assert.isInstanceOf(LastInvocationAware.class, invocationValue);
103102

@@ -159,7 +158,9 @@ private static <T extends LinkBuilder> PreparedWebHandler<T> linkTo(Object invoc
159158
? builder.buildAndExpand(values) //
160159
: additionalUriHandler.apply(builder, invocation).buildAndExpand(values);
161160

162-
TemplateVariables variables = NONE;
161+
TemplateVariables variables = additionalUriHandler == null
162+
? NONE
163+
: additionalUriHandler.apply(NONE, components, invocation);
163164

164165
for (String parameter : optionalEmptyParameters) {
165166

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

+34-10
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,15 @@
2525

2626
import org.springframework.core.MethodParameter;
2727
import org.springframework.hateoas.Link;
28+
import org.springframework.hateoas.TemplateVariables;
2829
import org.springframework.hateoas.server.MethodLinkBuilderFactory;
30+
import org.springframework.hateoas.server.core.AdditionalUriHandler;
2931
import org.springframework.hateoas.server.core.LinkBuilderSupport;
32+
import org.springframework.hateoas.server.core.MethodInvocation;
3033
import org.springframework.hateoas.server.core.MethodParameters;
3134
import org.springframework.hateoas.server.core.WebHandler;
35+
import org.springframework.web.util.UriComponents;
36+
import org.springframework.web.util.UriComponentsBuilder;
3237

3338
/**
3439
* Factory for {@link LinkBuilderSupport} instances based on the request mapping annotated on the given controller.
@@ -42,6 +47,7 @@
4247
* @author Kevin Conaway
4348
* @author Andrew Naydyonock
4449
* @author Greg Turnquist
50+
* @author Réda Housni Alaoui
4551
* @deprecated use {@link WebMvcLinkBuilderFactory} instead.
4652
*/
4753
@Deprecated
@@ -103,8 +109,30 @@ public ControllerLinkBuilder linkTo(Class<?> controller, Method method, Object..
103109
@Override
104110
public ControllerLinkBuilder linkTo(Object invocationValue) {
105111

106-
return WebHandler.linkTo(invocationValue, ControllerLinkBuilder::new, (builder, invocation) -> {
112+
return WebHandler.linkTo(invocationValue, ControllerLinkBuilder::new,
113+
new UriComponentsContributorsAdditionalUriHandler(uriComponentsContributors),
114+
mapping -> ControllerLinkBuilder.getBuilder().path(mapping));
115+
}
116+
117+
/*
118+
* (non-Javadoc)
119+
* @see org.springframework.hateoas.MethodLinkBuilderFactory#linkTo(java.lang.reflect.Method, java.lang.Object[])
120+
*/
121+
@Override
122+
public ControllerLinkBuilder linkTo(Method method, Object... parameters) {
123+
return ControllerLinkBuilder.linkTo(method, parameters);
124+
}
125+
126+
private static class UriComponentsContributorsAdditionalUriHandler implements AdditionalUriHandler {
107127

128+
private final List<UriComponentsContributor> uriComponentsContributors;
129+
130+
private UriComponentsContributorsAdditionalUriHandler(List<UriComponentsContributor> uriComponentsContributors) {
131+
this.uriComponentsContributors = uriComponentsContributors;
132+
}
133+
134+
@Override
135+
public UriComponentsBuilder apply(UriComponentsBuilder builder, MethodInvocation invocation) {
108136
MethodParameters parameters = MethodParameters.of(invocation.getMethod());
109137
Iterator<Object> parameterValues = Arrays.asList(invocation.getArguments()).iterator();
110138

@@ -120,15 +148,11 @@ public ControllerLinkBuilder linkTo(Object invocationValue) {
120148
}
121149

122150
return builder;
123-
}, mapping -> ControllerLinkBuilder.getBuilder().path(mapping));
124-
}
151+
}
125152

126-
/*
127-
* (non-Javadoc)
128-
* @see org.springframework.hateoas.MethodLinkBuilderFactory#linkTo(java.lang.reflect.Method, java.lang.Object[])
129-
*/
130-
@Override
131-
public ControllerLinkBuilder linkTo(Method method, Object... parameters) {
132-
return ControllerLinkBuilder.linkTo(method, parameters);
153+
@Override
154+
public TemplateVariables apply(TemplateVariables templateVariables, UriComponents uriComponents, MethodInvocation methodInvocation) {
155+
return templateVariables;
156+
}
133157
}
134158
}

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

+18
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

+42-10
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

+26
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)