Skip to content

Commit 69c3937

Browse files
mp911deodrotbohm
authored andcommitted
DATAREST-976 - Embedded properties are now considered in sort property paths.
We now allow sorting by properties of embedded objects. An embedded object is not linkable to a root object but embedded in the resource itself. If any part of the sort property path points to a linkable association, the whole sort property path is discarded silently and not used for sorting any further. Original pull request: #251.
1 parent ebe45bc commit 69c3937

File tree

7 files changed

+63
-16
lines changed

7 files changed

+63
-16
lines changed

spring-data-rest-tests/spring-data-rest-tests-jpa/src/main/java/org/springframework/data/rest/webmvc/jpa/Book.java

+21-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2013-2016 the original author or authors.
2+
* Copyright 2013-2017 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -15,10 +15,15 @@
1515
*/
1616
package org.springframework.data.rest.webmvc.jpa;
1717

18+
import lombok.AllArgsConstructor;
19+
import lombok.Getter;
20+
import lombok.NoArgsConstructor;
21+
1822
import java.util.HashSet;
1923
import java.util.Set;
2024

2125
import javax.persistence.CascadeType;
26+
import javax.persistence.Embeddable;
2227
import javax.persistence.Entity;
2328
import javax.persistence.GeneratedValue;
2429
import javax.persistence.Id;
@@ -45,9 +50,11 @@ public class Book {
4550
@RestResource(path = "creators") //
4651
public Set<Author> authors;
4752

53+
public Offer offer;
54+
4855
protected Book() {}
4956

50-
public Book(String isbn, String title, long soldUnits, Iterable<Author> authors) {
57+
public Book(String isbn, String title, long soldUnits, Iterable<Author> authors, Offer offer) {
5158

5259
this.isbn = isbn;
5360
this.title = title;
@@ -59,5 +66,17 @@ public Book(String isbn, String title, long soldUnits, Iterable<Author> authors)
5966
author.books.add(this);
6067
this.authors.add(author);
6168
}
69+
70+
this.offer = offer;
71+
}
72+
73+
@Getter
74+
@Embeddable
75+
@AllArgsConstructor
76+
@NoArgsConstructor
77+
static class Offer {
78+
79+
double price;
80+
String currency;
6281
}
6382
}

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -494,12 +494,12 @@ public void exectuesSearchThatTakesASort() throws Exception {
494494
assertThat(findBySortedLink.getVariableNames(), hasItems("sort", "projection"));
495495

496496
// Assert results returned as specified
497-
client.follow(findBySortedLink.expand("title,desc")).//
497+
client.follow(findBySortedLink.expand("offer.price,desc")).//
498498
andExpect(jsonPath("$._embedded.books[0].title").value("Spring Data (Second Edition)")).//
499499
andExpect(jsonPath("$._embedded.books[1].title").value("Spring Data")).//
500500
andExpect(client.hasLinkWithRel("self"));
501501

502-
client.follow(findBySortedLink.expand("title,asc")).//
502+
client.follow(findBySortedLink.expand("offer.price,asc")).//
503503
andExpect(jsonPath("$._embedded.books[0].title").value("Spring Data")).//
504504
andExpect(jsonPath("$._embedded.books[1].title").value("Spring Data (Second Edition)")).//
505505
andExpect(client.hasLinkWithRel("self"));

spring-data-rest-tests/spring-data-rest-tests-jpa/src/test/java/org/springframework/data/rest/webmvc/jpa/TestDataPopulator.java

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2013-2016 the original author or authors.
2+
* Copyright 2013-2017 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@
1818
import java.util.Arrays;
1919

2020
import org.springframework.beans.factory.annotation.Autowired;
21+
import org.springframework.data.rest.webmvc.jpa.Book.Offer;
2122

2223
/**
2324
* @author Jon Brisbin
@@ -54,8 +55,8 @@ private void populateAuthorsAndBooks() {
5455

5556
Iterable<Author> authors = this.authors.save(Arrays.asList(ollie, mark, michael, david, john, thomas));
5657

57-
books.save(new Book("1449323952", "Spring Data", 1000, authors));
58-
books.save(new Book("1449323953", "Spring Data (Second Edition)", 2000, authors));
58+
books.save(new Book("1449323952", "Spring Data", 1000, authors, new Offer(21.21, "EUR")));
59+
books.save(new Book("1449323953", "Spring Data (Second Edition)", 2000, authors, new Offer(30.99, "EUR")));
5960
}
6061

6162
private void populateOrders() {

spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/config/RepositoryRestMvcConfiguration.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -804,7 +804,8 @@ protected List<HandlerMethodArgumentResolver> defaultMethodArgumentResolvers() {
804804
PageableHandlerMethodArgumentResolver pageableResolver = pageableResolver();
805805

806806
JacksonMappingAwareSortTranslator sortTranslator = new JacksonMappingAwareSortTranslator(objectMapper(),
807-
repositories(), DomainClassResolver.of(repositories(), resourceMappings(), baseUri()), persistentEntities());
807+
repositories(), DomainClassResolver.of(repositories(), resourceMappings(), baseUri()), persistentEntities(),
808+
associationLinks());
808809

809810
HandlerMethodArgumentResolver sortResolver = new MappingAwareSortArgumentResolver(sortTranslator, sortResolver());
810811
HandlerMethodArgumentResolver jacksonPageableResolver = new MappingAwarePageableArgumentResolver(sortTranslator,

spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/json/JacksonMappingAwareSortTranslator.java

+8-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2016 the original author or authors.
2+
* Copyright 2016-2017 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -31,6 +31,7 @@
3131
import org.springframework.data.mapping.PersistentProperty;
3232
import org.springframework.data.mapping.context.PersistentEntities;
3333
import org.springframework.data.repository.support.Repositories;
34+
import org.springframework.data.rest.webmvc.mapping.Associations;
3435
import org.springframework.data.rest.webmvc.support.DomainClassResolver;
3536
import org.springframework.util.Assert;
3637
import org.springframework.util.StringUtils;
@@ -62,16 +63,18 @@ public class JacksonMappingAwareSortTranslator {
6263
* @param repositories must not be {@literal null}.
6364
* @param domainClassResolver must not be {@literal null}.
6465
* @param persistentEntities must not be {@literal null}.
66+
* @param associations must not be {@literal null}.
6567
*/
6668
public JacksonMappingAwareSortTranslator(ObjectMapper objectMapper, Repositories repositories,
67-
DomainClassResolver domainClassResolver, PersistentEntities persistentEntities) {
69+
DomainClassResolver domainClassResolver, PersistentEntities persistentEntities, Associations associations) {
6870

6971
Assert.notNull(repositories, "Repositories must not be null!");
7072
Assert.notNull(domainClassResolver, "DomainClassResolver must not be null!");
73+
Assert.notNull(associations, "Associations must not be null!");
7174

7275
this.repositories = repositories;
7376
this.domainClassResolver = domainClassResolver;
74-
this.sortTranslator = new SortTranslator(persistentEntities, objectMapper);
77+
this.sortTranslator = new SortTranslator(persistentEntities, objectMapper, associations);
7578
}
7679

7780
/**
@@ -117,6 +120,7 @@ public static class SortTranslator {
117120

118121
private final @NonNull PersistentEntities persistentEntities;
119122
private final @NonNull ObjectMapper objectMapper;
123+
private final @NonNull Associations associations;
120124

121125
/**
122126
* Translates {@link Sort} orders from Jackson-mapped field names to {@link PersistentProperty} names. Properties
@@ -181,7 +185,7 @@ private List<String> mapPropertyPath(PersistentEntity<?, ?> rootEntity, List<Str
181185

182186
for (PersistentProperty<?> persistentProperty : persistentProperties) {
183187

184-
if (persistentProperty.isAssociation()) {
188+
if (associations.isLinkableAssociation(persistentProperty)) {
185189
return Collections.emptyList();
186190
}
187191

spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/json/SortTranslatorUnitTests.java

+25-3
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import static org.hamcrest.Matchers.*;
1919
import static org.junit.Assert.*;
20+
import static org.mockito.Mockito.*;
2021

2122
import java.util.Collections;
2223
import java.util.List;
@@ -27,7 +28,11 @@
2728
import org.springframework.data.domain.Sort;
2829
import org.springframework.data.keyvalue.core.mapping.context.KeyValueMappingContext;
2930
import org.springframework.data.mapping.context.PersistentEntities;
31+
import org.springframework.data.rest.core.annotation.RestResource;
32+
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
33+
import org.springframework.data.rest.core.mapping.PersistentEntitiesResourceMappings;
3034
import org.springframework.data.rest.webmvc.json.JacksonMappingAwareSortTranslator.SortTranslator;
35+
import org.springframework.data.rest.webmvc.mapping.Associations;
3136

3237
import com.fasterxml.jackson.annotation.JsonProperty;
3338
import com.fasterxml.jackson.annotation.JsonUnwrapped;
@@ -57,7 +62,9 @@ public void setUp() {
5762
mappingContext.getPersistentEntity(MultiUnwrapped.class);
5863

5964
persistentEntities = new PersistentEntities(Collections.singleton(mappingContext));
60-
sortTranslator = new SortTranslator(persistentEntities, objectMapper);
65+
66+
sortTranslator = new SortTranslator(persistentEntities, objectMapper, new Associations(
67+
new PersistentEntitiesResourceMappings(persistentEntities), mock(RepositoryRestConfiguration.class)));
6168
}
6269

6370
@Test // DATAREST-883
@@ -119,15 +126,24 @@ public void shouldSkipWrongNestedProperties() {
119126
assertThat(translatedSort, is(nullValue()));
120127
}
121128

122-
@Test // DATAREST-910
129+
@Test // DATAREST-910, DATAREST-976
123130
public void shouldSkipKnownAssociationProperties() {
124131

125-
Sort translatedSort = sortTranslator.translateSort(new Sort("refEmbedded.name"),
132+
Sort translatedSort = sortTranslator.translateSort(new Sort("association.name"),
126133
mappingContext.getPersistentEntity(Plain.class));
127134

128135
assertThat(translatedSort, is(nullValue()));
129136
}
130137

138+
@Test // DATAREST-976
139+
public void shouldMapEmbeddableAssociationProperties() {
140+
141+
Sort translatedSort = sortTranslator.translateSort(new Sort("refEmbedded.name"),
142+
mappingContext.getPersistentEntity(Plain.class));
143+
144+
assertThat(translatedSort.getOrderFor("refEmbedded.name"), is(notNullValue()));
145+
}
146+
131147
@Test // DATAREST-910
132148
public void shouldJacksonFieldNameForNestedFieldMapping() {
133149

@@ -161,6 +177,7 @@ static class Plain {
161177
public String name;
162178
public Embedded embedded;
163179
@Reference public Embedded refEmbedded;
180+
@Reference public AnotherRootEntity association;
164181
}
165182

166183
static class UnwrapEmbedded {
@@ -192,4 +209,9 @@ static class EmbeddedWithJsonProperty {
192209
}
193210

194211
static interface SomeInterface {}
212+
213+
@RestResource
214+
static class AnotherRootEntity {
215+
public String name;
216+
}
195217
}

src/main/asciidoc/paging-and-sorting.adoc

+1-1
Original file line numberDiff line numberDiff line change
@@ -127,4 +127,4 @@ To have your results sorted on a particular property, add a `sort` URL parameter
127127
curl -v "http://localhost:8080/people/search/nameStartsWith?name=K&sort=name,desc"
128128
----
129129

130-
To sort the results by more than one property, keep adding as many `sort=PROPERTY` parameters as you need. They will be added to the `Pageable` in the order they appear in the query string.
130+
To sort the results by more than one property, keep adding as many `sort=PROPERTY` parameters as you need. They will be added to the `Pageable` in the order they appear in the query string. Results can be sorted by top-level and nested properties. Use property path notation to express a nested sort property. Sorting by linkable associations (i.e. resources to top-level resources) is not supported.

0 commit comments

Comments
 (0)