From 84efebc7a65afd7b5decbae054af13406ccb59c2 Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Sat, 23 Feb 2013 22:26:23 +0100 Subject: [PATCH] #26, #39 - Added support for request parameters to ControllerLinkBuilder. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We now transparently handle "?foo=bar" expressions passed into ….slash(…). Added method to return UriComponentsBuilder to allow more fine grained control over the URI to be built if needed. The dummy method invocations handled to ….linkTo(…) now transparently adds the values handed in for parameters annotated with @RequestParam. Most of the commit is highly inspired by Dietrich Schultens work at [0]. I just reworked and polished it slightly as we had too many merge conflicts due to related changes in the meantime. [0] https://github.com/dschulten/spring-hateoas/commit/1ebdc9025a458e91e92f573692e0484ca568982b --- .../hateoas/core/LinkBuilderSupport.java | 13 ++- .../hateoas/mvc/ControllerLinkBuilder.java | 11 ++- .../mvc/ControllerLinkBuilderFactory.java | 29 +++++-- .../mvc/ControllerLinkBuilderUnitTest.java | 86 ++++++++++++++++++- 4 files changed, 127 insertions(+), 12 deletions(-) diff --git a/src/main/java/org/springframework/hateoas/core/LinkBuilderSupport.java b/src/main/java/org/springframework/hateoas/core/LinkBuilderSupport.java index e7c64406..c5af037a 100644 --- a/src/main/java/org/springframework/hateoas/core/LinkBuilderSupport.java +++ b/src/main/java/org/springframework/hateoas/core/LinkBuilderSupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2012 the original author or authors. + * Copyright 2012-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,7 +21,6 @@ import org.springframework.hateoas.Identifiable; import org.springframework.hateoas.Link; import org.springframework.hateoas.LinkBuilder; import org.springframework.util.Assert; -import org.springframework.util.StringUtils; import org.springframework.web.util.UriComponents; import org.springframework.web.util.UriComponentsBuilder; @@ -60,8 +59,14 @@ public abstract class LinkBuilderSupport implements LinkB return slash((Identifiable) object); } - String[] segments = StringUtils.tokenizeToStringArray(object.toString(), "/"); - return createNewInstance(UriComponentsBuilder.fromUri(uriComponents.toUri()).pathSegment(segments)); + UriComponents components = UriComponentsBuilder.fromUriString(object.toString()).build(); + UriComponentsBuilder builder = UriComponentsBuilder.fromUri(uriComponents.toUri()); + + for (String pathSegment : components.getPathSegments()) { + builder.pathSegment(pathSegment); + } + + return createNewInstance(builder.query(components.getQuery())); } /* diff --git a/src/main/java/org/springframework/hateoas/mvc/ControllerLinkBuilder.java b/src/main/java/org/springframework/hateoas/mvc/ControllerLinkBuilder.java index 88e66d7c..0bcaf476 100755 --- a/src/main/java/org/springframework/hateoas/mvc/ControllerLinkBuilder.java +++ b/src/main/java/org/springframework/hateoas/mvc/ControllerLinkBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2012 the original author or authors. + * Copyright 2012-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -152,6 +152,15 @@ public class ControllerLinkBuilder extends LinkBuilderSupport { private static final MappingDiscoverer DISCOVERER = new AnnotationMappingDiscoverer(RequestMapping.class); - private static final AnnotatedParametersParameterAccessor ACCESSOR = new AnnotatedParametersParameterAccessor( + private static final AnnotatedParametersParameterAccessor PATH_VARIABLE_ACCESSOR = new AnnotatedParametersParameterAccessor( new AnnotationAttribute(PathVariable.class)); + private static final AnnotatedParametersParameterAccessor REQUEST_PARAM_ACCESSOR = new AnnotatedParametersParameterAccessor( + new AnnotationAttribute(RequestParam.class)); private List uriComponentsContributors = new ArrayList(); @@ -107,11 +111,24 @@ public class ControllerLinkBuilderFactory implements MethodLinkBuilderFactory param : REQUEST_PARAM_ACCESSOR.getBoundParameters(invocation).entrySet()) { + + Object value = param.getValue(); + String key = param.getKey(); + + if (value instanceof Collection) { + for (Object element : (Collection) value) { + builder.queryParam(key, element); + } + } else { + builder.queryParam(key, value); + } + } + + return new ControllerLinkBuilder(applyUriComponentsContributer(builder, invocation)); } /* diff --git a/src/test/java/org/springframework/hateoas/mvc/ControllerLinkBuilderUnitTest.java b/src/test/java/org/springframework/hateoas/mvc/ControllerLinkBuilderUnitTest.java index 47a9bcc6..372777ab 100644 --- a/src/test/java/org/springframework/hateoas/mvc/ControllerLinkBuilderUnitTest.java +++ b/src/test/java/org/springframework/hateoas/mvc/ControllerLinkBuilderUnitTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2012 the original author or authors. + * Copyright 2012-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,9 @@ import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; import static org.springframework.hateoas.mvc.ControllerLinkBuilder.*; +import java.util.Arrays; +import java.util.List; + import org.hamcrest.Matchers; import org.junit.Test; import org.mockito.Mockito; @@ -26,11 +29,17 @@ import org.springframework.hateoas.Identifiable; import org.springframework.hateoas.Link; import org.springframework.hateoas.TestUtils; import org.springframework.http.HttpEntity; +import org.springframework.util.MultiValueMap; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; 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; /** + * Unit tests for {@link ControllerLinkBuilder}. + * * @author Oliver Gierke */ public class ControllerLinkBuilderUnitTest extends TestUtils { @@ -124,6 +133,69 @@ public class ControllerLinkBuilderUnitTest extends TestUtils { assertThat(link.getHref(), startsWith("http://somethingDifferent")); } + /** + * @see #26, #39 + */ + @Test + public void addsRequestParametersHandedIntoSlashCorrectly() { + + Link link = linkTo(PersonController.class).slash("?foo=bar").withSelfRel(); + + UriComponents components = toComponents(link); + assertThat(components.getQuery(), is("foo=bar")); + } + + /** + * @see #26, #39 + */ + @Test + public void linksToMethodWithPathVariableAndRequestParams() { + + Link link = linkTo(methodOn(ControllerWithMethods.class).methodForNextPage("1", 10, 5)).withSelfRel(); + + UriComponents components = toComponents(link); + assertThat(components.getPath(), is("/something/1/foo")); + + MultiValueMap queryParams = components.getQueryParams(); + assertThat(queryParams.get("limit"), contains("5")); + assertThat(queryParams.get("offset"), contains("10")); + } + + /** + * @see #26, #39 + */ + @Test + public void linksToMethodWithPathVariableAndMultiValueRequestParams() { + + Link link = linkTo( + methodOn(ControllerWithMethods.class).methodWithMultiValueRequestParams("1", Arrays.asList(3, 7), 5)) + .withSelfRel(); + + UriComponents components = toComponents(link); + assertThat(components.getPath(), is("/something/1/foo")); + + MultiValueMap queryParams = components.getQueryParams(); + assertThat(queryParams.get("limit"), contains("5")); + assertThat(queryParams.get("items"), containsInAnyOrder("3", "7")); + } + + /** + * @see #26, #39 + */ + @Test + public void returnsUriComponentsBuilder() { + + UriComponents components = linkTo(PersonController.class).slash("something?foo=bar").toUriComponentsBuilder() + .build(); + + assertThat(components.getPath(), is("/people/something")); + assertThat(components.getQuery(), is("foo=bar")); + } + + private static UriComponents toComponents(Link link) { + return UriComponentsBuilder.fromUriString(link.getHref()).build(); + } + static class Person implements Identifiable { Long id; @@ -169,5 +241,17 @@ public class ControllerLinkBuilderUnitTest extends TestUtils { HttpEntity methodWithPathVariable(@PathVariable String id) { return null; } + + @RequestMapping(value = "/{id}/foo") + HttpEntity methodForNextPage(@PathVariable String id, @RequestParam Integer offset, + @RequestParam Integer limit) { + return null; + } + + @RequestMapping(value = "/{id}/foo") + HttpEntity methodWithMultiValueRequestParams(@PathVariable String id, @RequestParam List items, + @RequestParam Integer limit) { + return null; + } } }