Skip to content

Commit 6d0034f

Browse files
committed
Support for query methods returning a Slice.
Built on the the just introduced RepresentationModelAssembler implementations based on Slice in Spring HATEOAS and Spring Data Commons we now support returning a SlicedModel from the controller backing search resources ultimately triggering repository query methods. The introduction triggered the refactoring to introduce RepresentationModelAssemblers (RMA) to remove the need for controllers inheriting from AbstractRepositoryController to access RepresentationModel assembly functionality. RMA acts as a facade for both Paged-/SlicedResourceAssembler as well as PersistentEntityResourceAssembler. Fixes #2235.
1 parent 7c9a527 commit 6d0034f

25 files changed

+482
-388
lines changed

Diff for: spring-data-rest-tests/spring-data-rest-tests-jpa/src/test/java/org/springframework/data/rest/webmvc/RepositoryPropertyReferenceControllerIntegrationTests.java

+6-4
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,13 @@
1616
package org.springframework.data.rest.webmvc;
1717

1818
import static org.assertj.core.api.Assertions.*;
19-
import static org.mockito.Mockito.*;
2019

2120
import org.junit.jupiter.api.BeforeEach;
2221
import org.junit.jupiter.api.Test;
22+
import org.junit.jupiter.api.extension.ExtendWith;
23+
import org.mockito.Answers;
24+
import org.mockito.Mock;
25+
import org.mockito.junit.jupiter.MockitoExtension;
2326
import org.springframework.beans.factory.annotation.Autowired;
2427
import org.springframework.data.rest.tests.AbstractControllerIntegrationTests;
2528
import org.springframework.data.rest.webmvc.jpa.Book;
@@ -33,6 +36,7 @@
3336
/**
3437
* @author Oliver Gierke
3538
*/
39+
@ExtendWith(MockitoExtension.class)
3640
@ContextConfiguration(classes = JpaRepositoryConfig.class)
3741
@Transactional
3842
class RepositoryPropertyReferenceControllerIntegrationTests extends AbstractControllerIntegrationTests {
@@ -41,13 +45,11 @@ class RepositoryPropertyReferenceControllerIntegrationTests extends AbstractCont
4145
@Autowired TestDataPopulator populator;
4246
@Autowired BookRepository books;
4347

44-
PersistentEntityResourceAssembler assembler;
48+
@Mock(answer = Answers.RETURNS_MOCKS) RepresentationModelAssemblers assembler;
4549
RootResourceInformation information;
4650

4751
@BeforeEach
4852
void setUp() {
49-
50-
this.assembler = mock(PersistentEntityResourceAssembler.class);
5153
this.information = getResourceInformation(Book.class);
5254
this.populator.populateRepositories();
5355
}

Diff for: spring-data-rest-tests/spring-data-rest-tests-jpa/src/test/java/org/springframework/data/rest/webmvc/RepositorySearchControllerIntegrationTests.java

+41-8
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,17 @@
1616
package org.springframework.data.rest.webmvc;
1717

1818
import static org.assertj.core.api.Assertions.*;
19+
import static org.mockito.ArgumentMatchers.*;
20+
import static org.mockito.Mockito.*;
1921
import static org.springframework.data.rest.tests.TestMvcClient.*;
2022

2123
import org.junit.jupiter.api.BeforeEach;
2224
import org.junit.jupiter.api.Test;
25+
import org.mockito.Answers;
26+
import org.mockito.invocation.InvocationOnMock;
27+
import org.mockito.stubbing.Answer;
2328
import org.springframework.beans.factory.annotation.Autowired;
29+
import org.springframework.data.domain.Page;
2430
import org.springframework.data.domain.PageRequest;
2531
import org.springframework.data.domain.Sort;
2632
import org.springframework.data.rest.core.mapping.ResourceMetadata;
@@ -35,6 +41,7 @@
3541
import org.springframework.data.rest.webmvc.jpa.Person;
3642
import org.springframework.data.rest.webmvc.jpa.TestDataPopulator;
3743
import org.springframework.data.rest.webmvc.support.DefaultedPageable;
44+
import org.springframework.data.web.PagedResourcesAssembler;
3845
import org.springframework.hateoas.CollectionModel;
3946
import org.springframework.hateoas.PagedModel;
4047
import org.springframework.hateoas.RepresentationModel;
@@ -61,11 +68,16 @@ class RepositorySearchControllerIntegrationTests extends AbstractControllerInteg
6168

6269
@Autowired TestDataPopulator loader;
6370
@Autowired RepositorySearchController controller;
64-
@Autowired PersistentEntityResourceAssembler assembler;
71+
@Autowired PagedResourcesAssembler<Object> pagedResourcesAssembler;
72+
@Autowired PersistentEntityResourceAssembler entityResourceAssembler;
73+
74+
RepresentationModelAssemblers assembler;
6575

6676
@BeforeEach
6777
void setUp() {
6878
loader.populateRepositories();
79+
80+
this.assembler = mock(RepresentationModelAssemblers.class, Answers.RETURNS_SMART_NULLS);
6981
}
7082

7183
@Test
@@ -76,7 +88,8 @@ void rendersCorrectSearchLinksForPersons() throws Exception {
7688

7789
ResourceTester tester = ResourceTester.of(resource);
7890
tester.assertNumberOfLinks(7); // Self link included
79-
tester.assertHasLinkEndingWith("findFirstPersonByFirstName", "findFirstPersonByFirstName{?firstname,projection}");
91+
tester.assertHasLinkEndingWith("findFirstPersonByFirstName",
92+
"findFirstPersonByFirstName{?firstname,projection}");
8093
tester.assertHasLinkEndingWith("firstname", "firstname{?firstname,page,size,sort,projection}");
8194
tester.assertHasLinkEndingWith("lastname", "lastname{?lastname,sort,projection}");
8295
tester.assertHasLinkEndingWith("findByCreatedUsingISO8601Date",
@@ -107,8 +120,19 @@ void executesSearchAgainstRepository() {
107120
MultiValueMap<String, Object> parameters = new LinkedMultiValueMap<String, Object>(1);
108121
parameters.add("firstname", "John");
109122

123+
doAnswer(new Answer<CollectionModel<?>>() {
124+
125+
@Override
126+
public CollectionModel<?> answer(InvocationOnMock invocation) throws Throwable {
127+
128+
var page = (Page<Object>) invocation.getArgument(0);
129+
130+
return pagedResourcesAssembler.toModel(page, entityResourceAssembler);
131+
}
132+
}).when(assembler).toCollectionModel(any(), any());
133+
110134
ResponseEntity<?> response = controller.executeSearch(resourceInformation, parameters, "firstname", PAGEABLE,
111-
Sort.unsorted(), assembler, new HttpHeaders());
135+
Sort.unsorted(), new HttpHeaders(), assembler);
112136

113137
ResourceTester tester = ResourceTester.of(response.getBody());
114138
PagedModel<Object> pagedResources = tester.assertIsPage();
@@ -173,13 +197,22 @@ void queryMethodResourceSupportsGetOnly() {
173197
@Test // DATAREST-502
174198
void interpretsUriAsReferenceToRelatedEntity() {
175199

176-
MultiValueMap<String, Object> parameters = new LinkedMultiValueMap<String, Object>(1);
200+
var parameters = new LinkedMultiValueMap<String, Object>(1);
177201
parameters.add("author", "/author/1");
178202

179-
RootResourceInformation resourceInformation = getResourceInformation(Book.class);
203+
var resourceInformation = getResourceInformation(Book.class);
204+
205+
when(assembler.toCollectionModel(any(), any()))
206+
.thenAnswer(new Answer<CollectionModel<?>>() {
207+
208+
@Override
209+
public CollectionModel<?> answer(InvocationOnMock invocation) throws Throwable {
210+
return CollectionModel.of(invocation.getArgument(0));
211+
}
212+
});
180213

181-
ResponseEntity<?> result = controller.executeSearch(resourceInformation, parameters, "findByAuthorsContains",
182-
PAGEABLE, Sort.unsorted(), assembler, new HttpHeaders());
214+
var result = controller.executeSearch(resourceInformation, parameters, "findByAuthorsContains", PAGEABLE,
215+
Sort.unsorted(), new HttpHeaders(), assembler);
183216

184217
assertThat(result.getBody()).isInstanceOf(CollectionModel.class);
185218
}
@@ -199,7 +232,7 @@ void returnsSimpleResponseEntityForQueryMethod() {
199232
parameters.add("lastname", "Thornton");
200233

201234
ResponseEntity<?> entity = controller.executeSearch(getResourceInformation(Person.class), parameters,
202-
"findCreatedDateByLastName", PAGEABLE, Sort.unsorted(), assembler, new HttpHeaders());
235+
"findCreatedDateByLastName", PAGEABLE, Sort.unsorted(), new HttpHeaders(), assembler);
203236

204237
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
205238
assertThat(entity.getHeaders()).isEmpty();

Diff for: spring-data-rest-tests/spring-data-rest-tests-jpa/src/test/java/org/springframework/data/rest/webmvc/jpa/JpaWebTests.java

-2
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,6 @@
5353
import org.springframework.http.MediaType;
5454
import org.springframework.mock.web.MockHttpServletResponse;
5555
import org.springframework.test.context.ContextConfiguration;
56-
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
5756
import org.springframework.transaction.annotation.Transactional;
5857
import org.springframework.util.LinkedMultiValueMap;
5958
import org.springframework.util.MultiValueMap;
@@ -556,7 +555,6 @@ void exectuesSearchThatTakesASort() throws Exception {
556555

557556
// Assert results returned as specified
558557
client.follow(findBySortedLink.expand(Arrays.asList("title", "desc"))).//
559-
andDo(MockMvcResultHandlers.print()).//
560558
andExpect(jsonPath("$._embedded.books[0].title").value("Spring Data (Second Edition)")).//
561559
andExpect(jsonPath("$._embedded.books[1].title").value("Spring Data")).//
562560
andExpect(client.hasLinkWithRel(IanaLinkRelations.SELF));

Diff for: spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/AbstractRepositoryRestController.java

-119
This file was deleted.

Diff for: spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/BasePathAwareHandlerMapping.java

+4-8
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package org.springframework.data.rest.webmvc;
1717

1818
import static org.springframework.core.annotation.AnnotatedElementUtils.*;
19+
1920
import jakarta.servlet.http.HttpServletRequest;
2021
import jakarta.servlet.http.HttpServletRequestWrapper;
2122

@@ -27,9 +28,6 @@
2728
import java.util.List;
2829
import java.util.Set;
2930

30-
import jakarta.servlet.http.HttpServletRequest;
31-
import jakarta.servlet.http.HttpServletRequestWrapper;
32-
3331
import org.springframework.core.annotation.AnnotatedElementUtils;
3432
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
3533
import org.springframework.data.util.ProxyUtils;
@@ -115,7 +113,7 @@ protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handler
115113
String[] customPrefixes = getBasePathedPrefixes(handlerType);
116114
Builder builder = info.mutate();
117115

118-
if ((customPrefixes.length != 0) || StringUtils.hasText(baseUri)) {
116+
if (customPrefixes.length != 0 || StringUtils.hasText(baseUri)) {
119117
builder = builder.paths(resolveEmbeddedValuesInPatterns(customPrefixes));
120118
}
121119

@@ -219,17 +217,15 @@ public CustomAcceptHeaderHttpServletRequest(HttpServletRequest request, List<Med
219217
@Override
220218
public String getHeader(String name) {
221219

222-
return HttpHeaders.ACCEPT.equalsIgnoreCase(name) && (acceptMediaTypes != null //
223-
)
220+
return HttpHeaders.ACCEPT.equalsIgnoreCase(name) && acceptMediaTypes != null
224221
? StringUtils.collectionToCommaDelimitedString(acceptMediaTypes) //
225222
: super.getHeader(name);
226223
}
227224

228225
@Override
229226
public Enumeration<String> getHeaders(String name) {
230227

231-
return HttpHeaders.ACCEPT.equalsIgnoreCase(name) && (acceptMediaTypes != null //
232-
)
228+
return HttpHeaders.ACCEPT.equalsIgnoreCase(name) && acceptMediaTypes != null
233229
? Collections.enumeration(acceptMediaTypeStrings) //
234230
: super.getHeaders(name);
235231
}

Diff for: spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/ControllerUtils.java

+6
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,13 @@
1717

1818
import java.util.Optional;
1919

20+
import org.springframework.hateoas.Link;
2021
import org.springframework.hateoas.RepresentationModel;
2122
import org.springframework.http.HttpHeaders;
2223
import org.springframework.http.HttpStatus;
2324
import org.springframework.http.ResponseEntity;
2425
import org.springframework.util.Assert;
26+
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
2527

2628
/**
2729
* @author Oliver Gierke
@@ -80,4 +82,8 @@ public static ResponseEntity<RepresentationModel<?>> toEmptyResponse(HttpStatus
8082
public static ResponseEntity<RepresentationModel<?>> toEmptyResponse(HttpStatus status, HttpHeaders headers) {
8183
return toResponseEntity(status, headers, Optional.empty());
8284
}
85+
86+
static Link getDefaultSelfLink() {
87+
return Link.of(ServletUriComponentsBuilder.fromCurrentRequest().build().toUriString());
88+
}
8389
}

Diff for: spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/PersistentEntityResource.java

-1
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,6 @@ public class PersistentEntityResource extends EntityModel<Object> {
5858
* @param links must not be {@literal null}.
5959
* @param embeddeds can be {@literal null}.
6060
*/
61-
@SuppressWarnings("deprecation")
6261
private PersistentEntityResource(PersistentEntity<?, ?> entity, Object content, Iterable<Link> links,
6362
Iterable<EmbeddedWrapper> embeddeds, boolean isNew, boolean nested) {
6463

0 commit comments

Comments
 (0)