diff --git a/src/main/java/org/springframework/data/web/PagedResourcesAssemblerArgumentResolver.java b/src/main/java/org/springframework/data/web/PagedResourcesAssemblerArgumentResolver.java index 32ba63f1dd..2921347bf6 100644 --- a/src/main/java/org/springframework/data/web/PagedResourcesAssemblerArgumentResolver.java +++ b/src/main/java/org/springframework/data/web/PagedResourcesAssemblerArgumentResolver.java @@ -15,10 +15,11 @@ */ package org.springframework.data.web; +import javax.annotation.Nonnull; +import javax.servlet.http.HttpServletRequest; import java.lang.reflect.Method; import java.util.List; - -import javax.annotation.Nonnull; +import java.util.Optional; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -34,6 +35,7 @@ import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.method.support.ModelAndViewContainer; +import org.springframework.web.servlet.support.ServletUriComponentsBuilder; import org.springframework.web.util.UriComponents; import org.springframework.web.util.UriComponentsBuilder; @@ -45,6 +47,7 @@ * @author Oliver Gierke * @author Nick Williams * @author Christoph Strobl + * @author Réda Housni Alaoui */ public class PagedResourcesAssemblerArgumentResolver implements HandlerMethodArgumentResolver { @@ -88,7 +91,7 @@ public boolean supportsParameter(MethodParameter parameter) { public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) { - UriComponents fromUriString = resolveBaseUri(parameter); + UriComponents fromUriString = resolveBaseUri(webRequest).orElseGet(() -> resolveBaseUri(parameter)); MethodParameter pageableParameter = findMatchingPageableParameter(parameter); if (pageableParameter != null) { @@ -98,6 +101,15 @@ public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewC } } + private Optional resolveBaseUri(NativeWebRequest webRequest) { + return Optional.of(webRequest) + .map(NativeWebRequest::getNativeRequest) + .filter(HttpServletRequest.class::isInstance) + .map(HttpServletRequest.class::cast) + .map(ServletUriComponentsBuilder::fromRequest) + .map(UriComponentsBuilder::build); + } + /** * Eagerly resolve a base URI for the given {@link MethodParameter} to be handed to the assembler. * diff --git a/src/test/java/org/springframework/data/web/HateoasPageableHandlerMethodArgumentResolverIntegrationTests.java b/src/test/java/org/springframework/data/web/HateoasPageableHandlerMethodArgumentResolverIntegrationTests.java new file mode 100644 index 0000000000..f5db2dd627 --- /dev/null +++ b/src/test/java/org/springframework/data/web/HateoasPageableHandlerMethodArgumentResolverIntegrationTests.java @@ -0,0 +1,88 @@ +package org.springframework.data.web; + + +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +import java.util.Collections; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.data.web.config.HateoasAwareSpringDataWebConfiguration; +import org.springframework.hateoas.EntityModel; +import org.springframework.hateoas.MediaTypes; +import org.springframework.hateoas.PagedModel; +import org.springframework.hateoas.config.EnableHypermediaSupport; +import org.springframework.stereotype.Controller; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; + +/** + * @author Réda Housni Alaoui + */ +public class HateoasPageableHandlerMethodArgumentResolverIntegrationTests { + + @Configuration + @EnableWebMvc + @EnableHypermediaSupport(type = {EnableHypermediaSupport.HypermediaType.HAL}) + @Import(HateoasAwareSpringDataWebConfiguration.class) + static class Config { + + @Bean + SampleController controller() { + return new SampleController(); + } + } + + @BeforeEach + void setUp() { + WebTestUtils.initWebTest(); + } + + @Test // DATACMNS-1757 + void navigationLinksPreserveQueryParams() throws Exception { + WebApplicationContext context = WebTestUtils.createApplicationContext(Config.class); + MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(context).build(); + + mockMvc.perform(MockMvcRequestBuilders.get("/persons").queryParam("foo", "bar") + .accept(MediaTypes.HAL_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._links.first.href").value("http://localhost/persons?foo=bar&page=0&size=20")) + .andExpect(jsonPath("$._links.self.href").value("http://localhost/persons?foo=bar&page=0&size=20")) + .andExpect(jsonPath("$._links.next.href").value("http://localhost/persons?foo=bar&page=1&size=20")) + .andExpect(jsonPath("$._links.last.href").value("http://localhost/persons?foo=bar&page=1&size=20")); + } + + @Controller + static class SampleController { + + @GetMapping("/persons") + @ResponseBody + PagedModel> sample(Pageable pageable, PagedResourcesAssembler assembler) { + + Page page = new PageImpl<>(Collections.singletonList(new Person("John Doe")), pageable, + pageable.getOffset() + pageable.getPageSize() + 1); + + return assembler.toModel(page); + } + } + + static class Person { + + public final String name; + + Person(String name) { + this.name = name; + } + } +} diff --git a/src/test/java/org/springframework/data/web/PagedResourcesAssemblerArgumentResolverUnitTests.java b/src/test/java/org/springframework/data/web/PagedResourcesAssemblerArgumentResolverUnitTests.java index e6cd73d3e6..e82b4bf78a 100755 --- a/src/test/java/org/springframework/data/web/PagedResourcesAssemblerArgumentResolverUnitTests.java +++ b/src/test/java/org/springframework/data/web/PagedResourcesAssemblerArgumentResolverUnitTests.java @@ -22,18 +22,21 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; - import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.core.MethodParameter; import org.springframework.data.domain.Pageable; +import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.test.util.ReflectionTestUtils; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.context.request.ServletWebRequest; import org.springframework.web.util.UriComponents; /** * Unit tests for {@link PagedResourcesAssemblerArgumentResolver}. * * @author Oliver Gierke + * @author Réda Housni Alaoui * @since 1.7 */ class PagedResourcesAssemblerArgumentResolverUnitTests { @@ -53,7 +56,7 @@ void setUp() { void createsPlainAssemblerWithoutContext() throws Exception { Method method = Controller.class.getMethod("noContext", PagedResourcesAssembler.class); - Object result = resolver.resolveArgument(new MethodParameter(method, 0), null, null, null); + Object result = resolver.resolveArgument(new MethodParameter(method, 0), null, TestUtils.getWebRequest(), null); assertThat(result).isInstanceOf(PagedResourcesAssembler.class); assertThat(result).isNotInstanceOf(MethodParameterAwarePagedResourcesAssembler.class); @@ -107,7 +110,7 @@ void rejectsAmbiguityWithoutMatchingQualifiers() throws Exception { void doesNotFailForTemplatedMethodMapping() throws Exception { Method method = Controller.class.getMethod("methodWithPathVariable", PagedResourcesAssembler.class); - Object result = resolver.resolveArgument(new MethodParameter(method, 0), null, null, null); + Object result = resolver.resolveArgument(new MethodParameter(method, 0), null, TestUtils.getWebRequest(), null); assertThat(result).isNotNull(); } @@ -125,7 +128,8 @@ public java.lang.Class getDeclaringClass() { } }; - Object result = resolver.resolveArgument(methodParameter, null, null, null); + NativeWebRequest webRequest = new ServletWebRequest(new MockHttpServletRequest("GET", "/foo/mapping")); + Object result = resolver.resolveArgument(methodParameter, null, webRequest, null); assertThat(result).isInstanceOf(PagedResourcesAssembler.class); @@ -136,11 +140,26 @@ public java.lang.Class getDeclaringClass() { }); } + @Test // DATACMNS-1757 + void preservesQueryString() throws Exception { + Method method = Controller.class.getMethod("methodWithMapping", PagedResourcesAssembler.class); + NativeWebRequest webRequest = new ServletWebRequest(new MockHttpServletRequest("GET", "/foo/mapping?bar=baz")); + Object result = resolver.resolveArgument(new MethodParameter(method, 0), null, webRequest, null); + + assertThat(result).isNotNull(); + + Optional uriComponents = (Optional) ReflectionTestUtils.getField(result, "baseUri"); + + assertThat(uriComponents).hasValueSatisfying(it -> { + assertThat(it.getPath()).isEqualTo("/foo/mapping?bar=baz"); + }); + } + private void assertSelectsParameter(Method method, int expectedIndex) { MethodParameter parameter = new MethodParameter(method, 0); - Object result = resolver.resolveArgument(parameter, null, null, null); + Object result = resolver.resolveArgument(parameter, null, TestUtils.getWebRequest(), null); assertMethodParameterAwarePagedResourcesAssemblerFor(result, new MethodParameter(method, expectedIndex)); } @@ -158,7 +177,7 @@ private void assertRejectsAmbiguity(String methodName) throws Exception { Pageable.class); assertThatIllegalStateException() - .isThrownBy(() -> resolver.resolveArgument(new MethodParameter(method, 0), null, null, null)); + .isThrownBy(() -> resolver.resolveArgument(new MethodParameter(method, 0), null, TestUtils.getWebRequest(), null)); } @RequestMapping("/")