Skip to content

Commit

Permalink
Template variables SPI
Browse files Browse the repository at this point in the history
  • Loading branch information
reda-alaoui committed Jun 24, 2020
1 parent f3198b7 commit 996df75
Show file tree
Hide file tree
Showing 6 changed files with 141 additions and 26 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package org.springframework.hateoas.server.core;

import org.springframework.hateoas.TemplateVariables;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;

/**
* @author Réda Housni Alaoui
*/
public interface AdditionalUriHandler {

UriComponentsBuilder apply(UriComponentsBuilder uriComponentsBuilder, MethodInvocation methodInvocation);

TemplateVariables apply(TemplateVariables templateVariables, UriComponents uriComponents, MethodInvocation methodInvocation);
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.stream.Collectors;

Expand Down Expand Up @@ -66,6 +65,7 @@
*
* @author Greg Turnquist
* @author Oliver Drotbohm
* @author Réda Housni Alaoui
*/
public class WebHandler {

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

public static <T extends LinkBuilder> PreparedWebHandler<T> linkTo(Object invocationValue,
LinkBuilderCreator<T> creator) {
return linkTo(invocationValue, creator,
(BiFunction<UriComponentsBuilder, MethodInvocation, UriComponentsBuilder>) null);
return linkTo(invocationValue, creator, null);
}

public static <T extends LinkBuilder> T linkTo(Object invocationValue, LinkBuilderCreator<T> creator,
@Nullable BiFunction<UriComponentsBuilder, MethodInvocation, UriComponentsBuilder> additionalUriHandler,
@Nullable AdditionalUriHandler additionalUriHandler,
Function<String, UriComponentsBuilder> finisher) {

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

private static <T extends LinkBuilder> PreparedWebHandler<T> linkTo(Object invocationValue,
LinkBuilderCreator<T> creator,
@Nullable BiFunction<UriComponentsBuilder, MethodInvocation, UriComponentsBuilder> additionalUriHandler) {
@Nullable AdditionalUriHandler additionalUriHandler) {

Assert.isInstanceOf(LastInvocationAware.class, invocationValue);

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

TemplateVariables variables = NONE;
TemplateVariables variables = additionalUriHandler == null
? NONE
: additionalUriHandler.apply(NONE, components, invocation);

for (String parameter : optionalEmptyParameters) {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,15 @@

import org.springframework.core.MethodParameter;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.TemplateVariables;
import org.springframework.hateoas.server.MethodLinkBuilderFactory;
import org.springframework.hateoas.server.core.AdditionalUriHandler;
import org.springframework.hateoas.server.core.LinkBuilderSupport;
import org.springframework.hateoas.server.core.MethodInvocation;
import org.springframework.hateoas.server.core.MethodParameters;
import org.springframework.hateoas.server.core.WebHandler;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;

/**
* Factory for {@link LinkBuilderSupport} instances based on the request mapping annotated on the given controller.
Expand All @@ -42,6 +47,7 @@
* @author Kevin Conaway
* @author Andrew Naydyonock
* @author Greg Turnquist
* @author Réda Housni Alaoui
* @deprecated use {@link WebMvcLinkBuilderFactory} instead.
*/
@Deprecated
Expand Down Expand Up @@ -103,8 +109,30 @@ public ControllerLinkBuilder linkTo(Class<?> controller, Method method, Object..
@Override
public ControllerLinkBuilder linkTo(Object invocationValue) {

return WebHandler.linkTo(invocationValue, ControllerLinkBuilder::new, (builder, invocation) -> {
return WebHandler.linkTo(invocationValue, ControllerLinkBuilder::new,
new UriComponentsContributorsAdditionalUriHandler(uriComponentsContributors),
mapping -> ControllerLinkBuilder.getBuilder().path(mapping));
}

/*
* (non-Javadoc)
* @see org.springframework.hateoas.MethodLinkBuilderFactory#linkTo(java.lang.reflect.Method, java.lang.Object[])
*/
@Override
public ControllerLinkBuilder linkTo(Method method, Object... parameters) {
return ControllerLinkBuilder.linkTo(method, parameters);
}

private static class UriComponentsContributorsAdditionalUriHandler implements AdditionalUriHandler {

private final List<UriComponentsContributor> uriComponentsContributors;

private UriComponentsContributorsAdditionalUriHandler(List<UriComponentsContributor> uriComponentsContributors) {
this.uriComponentsContributors = uriComponentsContributors;
}

@Override
public UriComponentsBuilder apply(UriComponentsBuilder builder, MethodInvocation invocation) {
MethodParameters parameters = MethodParameters.of(invocation.getMethod());
Iterator<Object> parameterValues = Arrays.asList(invocation.getArguments()).iterator();

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

return builder;
}, mapping -> ControllerLinkBuilder.getBuilder().path(mapping));
}
}

/*
* (non-Javadoc)
* @see org.springframework.hateoas.MethodLinkBuilderFactory#linkTo(java.lang.reflect.Method, java.lang.Object[])
*/
@Override
public ControllerLinkBuilder linkTo(Method method, Object... parameters) {
return ControllerLinkBuilder.linkTo(method, parameters);
@Override
public TemplateVariables apply(TemplateVariables templateVariables, UriComponents uriComponents, MethodInvocation methodInvocation) {
return templateVariables;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,16 @@
*/
package org.springframework.hateoas.server.mvc;

import java.util.Collection;
import java.util.Collections;

import org.springframework.core.MethodParameter;
import org.springframework.hateoas.TemplateVariable;
import org.springframework.hateoas.TemplateVariables;
import org.springframework.hateoas.server.MethodLinkBuilderFactory;
import org.springframework.lang.Nullable;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;

/**
Expand All @@ -28,6 +34,7 @@
*
* @see MethodLinkBuilderFactory#linkTo(Object)
* @author Oliver Gierke
* @author Réda Housni Alaoui
*/
public interface UriComponentsContributor {

Expand All @@ -47,4 +54,15 @@ public interface UriComponentsContributor {
* @param value can be {@literal null}.
*/
void enhance(UriComponentsBuilder builder, @Nullable MethodParameter parameter, @Nullable Object value);

/**
* Enhance the given {@link TemplateVariables}
*
* @param templateVariables will never be {@literal null}.
* @param uriComponents will never be {@literal null}.
* @param parameter can be {@literal null}.
*/
default TemplateVariables enhance(TemplateVariables templateVariables, UriComponents uriComponents, @Nullable MethodParameter parameter){
return templateVariables;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,14 @@

import org.springframework.core.MethodParameter;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.TemplateVariables;
import org.springframework.hateoas.server.MethodLinkBuilderFactory;
import org.springframework.hateoas.server.core.AdditionalUriHandler;
import org.springframework.hateoas.server.core.LinkBuilderSupport;
import org.springframework.hateoas.server.core.MethodInvocation;
import org.springframework.hateoas.server.core.MethodParameters;
import org.springframework.hateoas.server.core.WebHandler;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;

/**
Expand All @@ -44,6 +48,7 @@
* @author Kevin Conaway
* @author Andrew Naydyonock
* @author Greg Turnquist
* @author Réda Housni Alaoui
*/
public class WebMvcLinkBuilderFactory implements MethodLinkBuilderFactory<WebMvcLinkBuilder> {

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

return WebHandler.linkTo(invocationValue, WebMvcLinkBuilder::new, (builder, invocation) -> {
return WebHandler.linkTo(invocationValue, WebMvcLinkBuilder::new,
new UriComponentsContributorsAdditionalUriHandler(uriComponentsContributors), builderFactory);
}

/*
* (non-Javadoc)
* @see org.springframework.hateoas.MethodLinkBuilderFactory#linkTo(java.lang.reflect.Method, java.lang.Object[])
*/
@Override
public WebMvcLinkBuilder linkTo(Method method, Object... parameters) {
return WebMvcLinkBuilder.linkTo(method, parameters);
}

private static class UriComponentsContributorsAdditionalUriHandler implements AdditionalUriHandler {

private final List<UriComponentsContributor> uriComponentsContributors;

private UriComponentsContributorsAdditionalUriHandler(List<UriComponentsContributor> uriComponentsContributors) {
this.uriComponentsContributors = uriComponentsContributors;
}

@Override
public UriComponentsBuilder apply(UriComponentsBuilder builder, MethodInvocation invocation) {
MethodParameters parameters = MethodParameters.of(invocation.getMethod());
Iterator<Object> parameterValues = Arrays.asList(invocation.getArguments()).iterator();

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

return builder;
}

}, builderFactory);
}
@Override
public TemplateVariables apply(TemplateVariables templateVariables, UriComponents uriComponents, MethodInvocation invocation) {
MethodParameters parameters = MethodParameters.of(invocation.getMethod());

/*
* (non-Javadoc)
* @see org.springframework.hateoas.MethodLinkBuilderFactory#linkTo(java.lang.reflect.Method, java.lang.Object[])
*/
@Override
public WebMvcLinkBuilder linkTo(Method method, Object... parameters) {
return WebMvcLinkBuilder.linkTo(method, parameters);
for (MethodParameter parameter : parameters.getParameters()) {

for (UriComponentsContributor contributor : uriComponentsContributors) {
if (contributor.supportsParameter(parameter)) {
templateVariables = contributor.enhance(templateVariables, uriComponents, parameter);
}
}
}

return templateVariables;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
import org.springframework.format.annotation.DateTimeFormat.ISO;
import org.springframework.hateoas.IanaLinkRelations;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.TemplateVariable;
import org.springframework.hateoas.TemplateVariables;
import org.springframework.hateoas.TestUtils;
import org.springframework.hateoas.server.mvc.WebMvcLinkBuilderUnitTest.ControllerWithMethods;
import org.springframework.hateoas.server.mvc.WebMvcLinkBuilderUnitTest.PersonControllerImpl;
Expand All @@ -41,6 +43,7 @@
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;

/**
Expand Down Expand Up @@ -174,6 +177,17 @@ void createsLinkToParameterizedControllerRootWithParameterMap() {
assertThat(link.getHref()).endsWith("/people/17/addresses");
}

@Test
void appliesTemplateVariableIfContributorConfigured() {

WebMvcLinkBuilderFactory factory = new WebMvcLinkBuilderFactory();
factory.setUriComponentsContributors(Collections.singletonList(new SampleUriComponentsContributor()));

Link link = factory.linkTo(methodOn(SampleController.class).sampleMethod(1L, null)).withSelfRel();
assertPointsToMockServer(link);
assertThat(link.getHref()).endsWith("/sample/1{?foo}");
}

interface SampleController {

@RequestMapping("/sample/{id}")
Expand All @@ -198,8 +212,19 @@ public boolean supportsParameter(MethodParameter parameter) {

@Override
public void enhance(UriComponentsBuilder builder, MethodParameter parameter, Object value) {
if (value == null) {
return;
}
builder.queryParam("foo", ((SpecialType) value).parameterValue);
}

@Override
public TemplateVariables enhance(TemplateVariables templateVariables, UriComponents uriComponents, MethodParameter parameter) {
if (uriComponents.getQueryParams().containsKey("foo")) {
return templateVariables;
}
return templateVariables.concat(TemplateVariable.requestParameter("foo"));
}
}

static class SpecialType {
Expand Down

0 comments on commit 996df75

Please sign in to comment.