From 7478ee104c2fefc34472628347d0bda21b2cf2e8 Mon Sep 17 00:00:00 2001 From: Nick Grealy Date: Mon, 26 Oct 2015 13:50:38 +1100 Subject: [PATCH 1/2] Changed linkTo so that it can be invoked outside of a HttpRequest. Changed linkTo so that it can be invoked outside of a HttpRequest - issue #408. Added linkTo method, which takes an explicit UriComponentBuilder. --- .../hateoas/MethodLinkBuilderFactory.java | 13 ++++++++ .../hateoas/mvc/ControllerLinkBuilder.java | 23 +++++++++++-- .../mvc/ControllerLinkBuilderFactory.java | 13 ++++++-- ...erLinkBuilderOutsideSpringMvcUnitTest.java | 33 +++++++++++++------ 4 files changed, 67 insertions(+), 15 deletions(-) diff --git a/src/main/java/org/springframework/hateoas/MethodLinkBuilderFactory.java b/src/main/java/org/springframework/hateoas/MethodLinkBuilderFactory.java index 1f990b982..1d99c10df 100644 --- a/src/main/java/org/springframework/hateoas/MethodLinkBuilderFactory.java +++ b/src/main/java/org/springframework/hateoas/MethodLinkBuilderFactory.java @@ -19,6 +19,7 @@ import org.springframework.hateoas.core.DummyInvocationUtils; import org.springframework.hateoas.mvc.ControllerLinkBuilder; +import org.springframework.web.util.UriComponentsBuilder; /** * Extension of {@link LinkBuilderFactory} for implementations that also support creating {@link LinkBuilder}s by @@ -59,4 +60,16 @@ public interface MethodLinkBuilderFactory extends LinkBui * @return */ T linkTo(Object methodInvocationResult); + + /** + * Returns a {@link LinkBuilder} pointing to the URI mapped to the method the result is handed into this method. Use + * {@link DummyInvocationUtils#methodOn(Class, Object...)} to obtain a dummy instance of a controller to record a + * dummy method invocation on. See {@link ControllerLinkBuilder#linkTo(Object)} for an example. + * + * @see ControllerLinkBuilder#linkTo(Object) + * @param builder must not be {@literal null}. + * @param methodInvocationResult must not be {@literal null}. + * @return + */ + T linkTo(UriComponentsBuilder builder, Object methodInvocationResult); } diff --git a/src/main/java/org/springframework/hateoas/mvc/ControllerLinkBuilder.java b/src/main/java/org/springframework/hateoas/mvc/ControllerLinkBuilder.java index 1c760e9ff..43b69e15d 100755 --- a/src/main/java/org/springframework/hateoas/mvc/ControllerLinkBuilder.java +++ b/src/main/java/org/springframework/hateoas/mvc/ControllerLinkBuilder.java @@ -95,7 +95,7 @@ public static ControllerLinkBuilder linkTo(Class controller, Object... parame * @see org.springframework.hateoas.MethodLinkBuilderFactory#linkTo(Method, Object...) */ public static ControllerLinkBuilder linkTo(Method method, Object... parameters) { - return linkTo(method.getDeclaringClass(), method, parameters); + return linkTo(method.getDeclaringClass(), method, parameters); } /* @@ -138,6 +138,18 @@ public static ControllerLinkBuilder linkTo(Object invocationValue) { return FACTORY.linkTo(invocationValue); } + /** + * Creates a {@link ControllerLinkBuilder} pointing to a controller method. Hand in a dummy method invocation result + * you can create via {@link #methodOn(Class, Object...)} or {@link DummyInvocationUtils#methodOn(Class, Object...)}. + * + * @param builder + * @param invocationValue + * @return + */ + public static ControllerLinkBuilder linkTo(UriComponentsBuilder builder, Object invocationValue) { + return FACTORY.linkTo(builder, invocationValue); + } + /** * Wrapper for {@link DummyInvocationUtils#methodOn(Class, Object...)} to be available in case you work with static * imports of {@link ControllerLinkBuilder}. @@ -185,8 +197,13 @@ public UriComponentsBuilder toUriComponentsBuilder() { * @return */ static UriComponentsBuilder getBuilder() { - - HttpServletRequest request = getCurrentRequest(); + HttpServletRequest request; + try { + request = getCurrentRequest(); + } catch (IllegalStateException e){ + // not in the context of a Http Request, so return blank UriComponentsBuilder... + return UriComponentsBuilder.fromPath(""); + } ServletUriComponentsBuilder builder = ServletUriComponentsBuilder.fromServletMapping(request); ForwardedHeader forwarded = ForwardedHeader.of(request.getHeader(ForwardedHeader.NAME)); diff --git a/src/main/java/org/springframework/hateoas/mvc/ControllerLinkBuilderFactory.java b/src/main/java/org/springframework/hateoas/mvc/ControllerLinkBuilderFactory.java index 20fb052ed..244bc25e0 100644 --- a/src/main/java/org/springframework/hateoas/mvc/ControllerLinkBuilderFactory.java +++ b/src/main/java/org/springframework/hateoas/mvc/ControllerLinkBuilderFactory.java @@ -103,12 +103,21 @@ public ControllerLinkBuilder linkTo(Class controller, Method method, Object.. return ControllerLinkBuilder.linkTo(controller, method, parameters); } - /* + /* * (non-Javadoc) * @see org.springframework.hateoas.MethodLinkBuilderFactory#linkTo(java.lang.Object) */ @Override public ControllerLinkBuilder linkTo(Object invocationValue) { + return linkTo(ControllerLinkBuilder.getBuilder(), invocationValue); + } + + /* + * (non-Javadoc) + * @see org.springframework.hateoas.MethodLinkBuilderFactory#linkTo(UriComponentsBuilder builder, java.lang.Object) + */ + @Override + public ControllerLinkBuilder linkTo(UriComponentsBuilder builder, Object invocationValue) { Assert.isInstanceOf(LastInvocationAware.class, invocationValue); LastInvocationAware invocations = (LastInvocationAware) invocationValue; @@ -118,7 +127,7 @@ public ControllerLinkBuilder linkTo(Object invocationValue) { Method method = invocation.getMethod(); String mapping = DISCOVERER.getMapping(invocation.getTargetType(), method); - UriComponentsBuilder builder = ControllerLinkBuilder.getBuilder().path(mapping); + builder.path(mapping); UriTemplate template = new UriTemplate(mapping); Map values = new HashMap(); diff --git a/src/test/java/org/springframework/hateoas/mvc/ControllerLinkBuilderOutsideSpringMvcUnitTest.java b/src/test/java/org/springframework/hateoas/mvc/ControllerLinkBuilderOutsideSpringMvcUnitTest.java index 947b42ab9..2eaba3847 100644 --- a/src/test/java/org/springframework/hateoas/mvc/ControllerLinkBuilderOutsideSpringMvcUnitTest.java +++ b/src/test/java/org/springframework/hateoas/mvc/ControllerLinkBuilderOutsideSpringMvcUnitTest.java @@ -3,10 +3,13 @@ import static org.hamcrest.MatcherAssert.*; import static org.hamcrest.Matchers.*; import static org.springframework.hateoas.mvc.ControllerLinkBuilder.*; +import static org.springframework.web.util.UriComponentsBuilder.*; import org.junit.Before; import org.junit.Test; +import org.springframework.hateoas.Link; import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.util.UriComponentsBuilder; /** * Test cases for {@link ControllerLinkBuilder} that are NOT inside an existing Spring MVC request @@ -24,18 +27,28 @@ public void setUp() { } /** - * @see #342 + * Calling linkTo outside of a HTTP request should not throw an exception. + * @see #408 */ - @Test(expected = IllegalStateException.class) - public void createsLinkToMethodOnParameterizedControllerRoot() { + @Test + public void callingLinkToOutsideOfHttpRequestShouldNotThrowException() { + Link link = linkTo(methodOn(ControllerLinkBuilderUnitTest.PersonsAddressesController.class, 15) + .getAddressesForCountry("DE")).withSelfRel(); + assertThat(link.getHref(), is("/people/15/addresses/DE")); + } - try { - linkTo(methodOn(ControllerLinkBuilderUnitTest.PersonsAddressesController.class, 15) - .getAddressesForCountry("DE")).withSelfRel(); - } catch (IllegalStateException e) { - assertThat(e.getMessage(), equalTo("Could not find current request via RequestContextHolder. Is this being called from a Spring MVC handler?")); - throw e; - } + /** + * Calling linkTo outside of a HTTP request, should allow passing in a custom builder. + * @see #408 + */ + @Test + public void linkToShouldTakeAUriCompoentsBuilder() { + Link link = linkTo(fromUriString("https://myproxy.net:1234/somepath"), methodOn + (ControllerLinkBuilderUnitTest + .PersonsAddressesController + .class, 15) + .getAddressesForCountry("DE")).withSelfRel(); + assertThat(link.getHref(), is("https://myproxy.net:1234/somepath/people/15/addresses/DE")); } } From b272993b0b1167312972a936f0de40e88c886365 Mon Sep 17 00:00:00 2001 From: Nick Grealy Date: Mon, 26 Oct 2015 14:41:26 +1100 Subject: [PATCH 2/2] Missed failing test. --- .../hateoas/mvc/ControllerLinkBuilderUnitTest.java | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/test/java/org/springframework/hateoas/mvc/ControllerLinkBuilderUnitTest.java b/src/test/java/org/springframework/hateoas/mvc/ControllerLinkBuilderUnitTest.java index 918ace863..2bf4041f4 100644 --- a/src/test/java/org/springframework/hateoas/mvc/ControllerLinkBuilderUnitTest.java +++ b/src/test/java/org/springframework/hateoas/mvc/ControllerLinkBuilderUnitTest.java @@ -447,14 +447,11 @@ public void linksToMethodWithRequestParamImplicitlySetToFalse() { @Test public void mentionsRequiredUsageWithinWebRequestInException() { - exception.expect(IllegalStateException.class); - exception.expectMessage("request"); - exception.expectMessage("Spring MVC"); - RequestContextHolder.setRequestAttributes(null); - linkTo(methodOn(ControllerLinkBuilderUnitTest.PersonsAddressesController.class, 15).getAddressesForCountry("DE")) + Link link = linkTo(methodOn(PersonsAddressesController.class, 15).getAddressesForCountry("DE")) .withSelfRel(); + assertThat(link.getHref(), endsWith("/people/15/addresses/DE")); } private static UriComponents toComponents(Link link) {