diff --git a/src/main/java/org/springframework/hateoas/server/core/AdditionalUriHandler.java b/src/main/java/org/springframework/hateoas/server/core/AdditionalUriHandler.java new file mode 100644 index 000000000..533456ad2 --- /dev/null +++ b/src/main/java/org/springframework/hateoas/server/core/AdditionalUriHandler.java @@ -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); +} diff --git a/src/main/java/org/springframework/hateoas/server/core/WebHandler.java b/src/main/java/org/springframework/hateoas/server/core/WebHandler.java index b5d5f5b79..d3455dbcd 100644 --- a/src/main/java/org/springframework/hateoas/server/core/WebHandler.java +++ b/src/main/java/org/springframework/hateoas/server/core/WebHandler.java @@ -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; @@ -66,6 +65,7 @@ * * @author Greg Turnquist * @author Oliver Drotbohm + * @author Réda Housni Alaoui */ public class WebHandler { @@ -84,12 +84,11 @@ public interface PreparedWebHandler { public static PreparedWebHandler linkTo(Object invocationValue, LinkBuilderCreator creator) { - return linkTo(invocationValue, creator, - (BiFunction) null); + return linkTo(invocationValue, creator, null); } public static T linkTo(Object invocationValue, LinkBuilderCreator creator, - @Nullable BiFunction additionalUriHandler, + @Nullable AdditionalUriHandler additionalUriHandler, Function finisher) { return linkTo(invocationValue, creator, additionalUriHandler).conclude(finisher); @@ -97,7 +96,7 @@ public static T linkTo(Object invocationValue, LinkBuild private static PreparedWebHandler linkTo(Object invocationValue, LinkBuilderCreator creator, - @Nullable BiFunction additionalUriHandler) { + @Nullable AdditionalUriHandler additionalUriHandler) { Assert.isInstanceOf(LastInvocationAware.class, invocationValue); @@ -159,7 +158,9 @@ private static PreparedWebHandler 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) { diff --git a/src/main/java/org/springframework/hateoas/server/mvc/ControllerLinkBuilderFactory.java b/src/main/java/org/springframework/hateoas/server/mvc/ControllerLinkBuilderFactory.java index b54e5e634..bf75f7f97 100644 --- a/src/main/java/org/springframework/hateoas/server/mvc/ControllerLinkBuilderFactory.java +++ b/src/main/java/org/springframework/hateoas/server/mvc/ControllerLinkBuilderFactory.java @@ -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. @@ -42,6 +47,7 @@ * @author Kevin Conaway * @author Andrew Naydyonock * @author Greg Turnquist + * @author Réda Housni Alaoui * @deprecated use {@link WebMvcLinkBuilderFactory} instead. */ @Deprecated @@ -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 uriComponentsContributors; + + private UriComponentsContributorsAdditionalUriHandler(List uriComponentsContributors) { + this.uriComponentsContributors = uriComponentsContributors; + } + + @Override + public UriComponentsBuilder apply(UriComponentsBuilder builder, MethodInvocation invocation) { MethodParameters parameters = MethodParameters.of(invocation.getMethod()); Iterator parameterValues = Arrays.asList(invocation.getArguments()).iterator(); @@ -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; + } } } diff --git a/src/main/java/org/springframework/hateoas/server/mvc/UriComponentsContributor.java b/src/main/java/org/springframework/hateoas/server/mvc/UriComponentsContributor.java index 47150cd2e..8a29d3073 100644 --- a/src/main/java/org/springframework/hateoas/server/mvc/UriComponentsContributor.java +++ b/src/main/java/org/springframework/hateoas/server/mvc/UriComponentsContributor.java @@ -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; /** @@ -28,6 +34,7 @@ * * @see MethodLinkBuilderFactory#linkTo(Object) * @author Oliver Gierke + * @author Réda Housni Alaoui */ public interface UriComponentsContributor { @@ -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; + } } diff --git a/src/main/java/org/springframework/hateoas/server/mvc/WebMvcLinkBuilderFactory.java b/src/main/java/org/springframework/hateoas/server/mvc/WebMvcLinkBuilderFactory.java index 69babfc3c..de971a162 100644 --- a/src/main/java/org/springframework/hateoas/server/mvc/WebMvcLinkBuilderFactory.java +++ b/src/main/java/org/springframework/hateoas/server/mvc/WebMvcLinkBuilderFactory.java @@ -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; /** @@ -44,6 +48,7 @@ * @author Kevin Conaway * @author Andrew Naydyonock * @author Greg Turnquist + * @author Réda Housni Alaoui */ public class WebMvcLinkBuilderFactory implements MethodLinkBuilderFactory { @@ -106,8 +111,29 @@ public WebMvcLinkBuilder linkTo(Object invocationValue) { Function 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 uriComponentsContributors; + + private UriComponentsContributorsAdditionalUriHandler(List uriComponentsContributors) { + this.uriComponentsContributors = uriComponentsContributors; + } + + @Override + public UriComponentsBuilder apply(UriComponentsBuilder builder, MethodInvocation invocation) { MethodParameters parameters = MethodParameters.of(invocation.getMethod()); Iterator parameterValues = Arrays.asList(invocation.getArguments()).iterator(); @@ -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; + } } } diff --git a/src/test/java/org/springframework/hateoas/server/mvc/WebMvcLinkBuilderFactoryUnitTest.java b/src/test/java/org/springframework/hateoas/server/mvc/WebMvcLinkBuilderFactoryUnitTest.java index a61cfeb9e..a1a346ca6 100644 --- a/src/test/java/org/springframework/hateoas/server/mvc/WebMvcLinkBuilderFactoryUnitTest.java +++ b/src/test/java/org/springframework/hateoas/server/mvc/WebMvcLinkBuilderFactoryUnitTest.java @@ -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; @@ -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; /** @@ -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}") @@ -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 {