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; + } } }