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.
This commit is contained in:
committed by
Oliver Gierke
parent
629ca4e33d
commit
ffe96e4079
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2016 the original author or authors.
|
||||
* Copyright 2013-2017 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.
|
||||
@@ -15,10 +15,15 @@
|
||||
*/
|
||||
package org.springframework.data.rest.webmvc.jpa;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.persistence.CascadeType;
|
||||
import javax.persistence.Embeddable;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.Id;
|
||||
@@ -45,9 +50,11 @@ public class Book {
|
||||
@RestResource(path = "creators") //
|
||||
public Set<Author> authors;
|
||||
|
||||
public Offer offer;
|
||||
|
||||
protected Book() {}
|
||||
|
||||
public Book(String isbn, String title, long soldUnits, Iterable<Author> authors) {
|
||||
public Book(String isbn, String title, long soldUnits, Iterable<Author> authors, Offer offer) {
|
||||
|
||||
this.isbn = isbn;
|
||||
this.title = title;
|
||||
@@ -59,5 +66,17 @@ public class Book {
|
||||
author.books.add(this);
|
||||
this.authors.add(author);
|
||||
}
|
||||
|
||||
this.offer = offer;
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Embeddable
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
static class Offer {
|
||||
|
||||
double price;
|
||||
String currency;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -494,12 +494,12 @@ public class JpaWebTests extends CommonWebTests {
|
||||
assertThat(findBySortedLink.getVariableNames(), hasItems("sort", "projection"));
|
||||
|
||||
// Assert results returned as specified
|
||||
client.follow(findBySortedLink.expand("title,desc")).//
|
||||
client.follow(findBySortedLink.expand("offer.price,desc")).//
|
||||
andExpect(jsonPath("$._embedded.books[0].title").value("Spring Data (Second Edition)")).//
|
||||
andExpect(jsonPath("$._embedded.books[1].title").value("Spring Data")).//
|
||||
andExpect(client.hasLinkWithRel("self"));
|
||||
|
||||
client.follow(findBySortedLink.expand("title,asc")).//
|
||||
client.follow(findBySortedLink.expand("offer.price,asc")).//
|
||||
andExpect(jsonPath("$._embedded.books[0].title").value("Spring Data")).//
|
||||
andExpect(jsonPath("$._embedded.books[1].title").value("Spring Data (Second Edition)")).//
|
||||
andExpect(client.hasLinkWithRel("self"));
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2016 the original author or authors.
|
||||
* Copyright 2013-2017 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.
|
||||
@@ -18,6 +18,7 @@ package org.springframework.data.rest.webmvc.jpa;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.rest.webmvc.jpa.Book.Offer;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin
|
||||
@@ -54,8 +55,8 @@ public class TestDataPopulator {
|
||||
|
||||
Iterable<Author> authors = this.authors.save(Arrays.asList(ollie, mark, michael, david, john, thomas));
|
||||
|
||||
books.save(new Book("1449323952", "Spring Data", 1000, authors));
|
||||
books.save(new Book("1449323953", "Spring Data (Second Edition)", 2000, authors));
|
||||
books.save(new Book("1449323952", "Spring Data", 1000, authors, new Offer(21.21, "EUR")));
|
||||
books.save(new Book("1449323953", "Spring Data (Second Edition)", 2000, authors, new Offer(30.99, "EUR")));
|
||||
}
|
||||
|
||||
private void populateOrders() {
|
||||
|
||||
@@ -804,7 +804,8 @@ public class RepositoryRestMvcConfiguration extends HateoasAwareSpringDataWebCon
|
||||
PageableHandlerMethodArgumentResolver pageableResolver = pageableResolver();
|
||||
|
||||
JacksonMappingAwareSortTranslator sortTranslator = new JacksonMappingAwareSortTranslator(objectMapper(),
|
||||
repositories(), DomainClassResolver.of(repositories(), resourceMappings(), baseUri()), persistentEntities());
|
||||
repositories(), DomainClassResolver.of(repositories(), resourceMappings(), baseUri()), persistentEntities(),
|
||||
associationLinks());
|
||||
|
||||
HandlerMethodArgumentResolver sortResolver = new MappingAwareSortArgumentResolver(sortTranslator, sortResolver());
|
||||
HandlerMethodArgumentResolver jacksonPageableResolver = new MappingAwarePageableArgumentResolver(sortTranslator,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
* Copyright 2016-2017 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.
|
||||
@@ -31,6 +31,7 @@ import org.springframework.data.mapping.PersistentEntity;
|
||||
import org.springframework.data.mapping.PersistentProperty;
|
||||
import org.springframework.data.mapping.context.PersistentEntities;
|
||||
import org.springframework.data.repository.support.Repositories;
|
||||
import org.springframework.data.rest.webmvc.mapping.Associations;
|
||||
import org.springframework.data.rest.webmvc.support.DomainClassResolver;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
@@ -62,16 +63,18 @@ public class JacksonMappingAwareSortTranslator {
|
||||
* @param repositories must not be {@literal null}.
|
||||
* @param domainClassResolver must not be {@literal null}.
|
||||
* @param persistentEntities must not be {@literal null}.
|
||||
* @param associations must not be {@literal null}.
|
||||
*/
|
||||
public JacksonMappingAwareSortTranslator(ObjectMapper objectMapper, Repositories repositories,
|
||||
DomainClassResolver domainClassResolver, PersistentEntities persistentEntities) {
|
||||
DomainClassResolver domainClassResolver, PersistentEntities persistentEntities, Associations associations) {
|
||||
|
||||
Assert.notNull(repositories, "Repositories must not be null!");
|
||||
Assert.notNull(domainClassResolver, "DomainClassResolver must not be null!");
|
||||
Assert.notNull(associations, "Associations must not be null!");
|
||||
|
||||
this.repositories = repositories;
|
||||
this.domainClassResolver = domainClassResolver;
|
||||
this.sortTranslator = new SortTranslator(persistentEntities, objectMapper);
|
||||
this.sortTranslator = new SortTranslator(persistentEntities, objectMapper, associations);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -117,6 +120,7 @@ public class JacksonMappingAwareSortTranslator {
|
||||
|
||||
private final @NonNull PersistentEntities persistentEntities;
|
||||
private final @NonNull ObjectMapper objectMapper;
|
||||
private final @NonNull Associations associations;
|
||||
|
||||
/**
|
||||
* Translates {@link Sort} orders from Jackson-mapped field names to {@link PersistentProperty} names. Properties
|
||||
@@ -181,7 +185,7 @@ public class JacksonMappingAwareSortTranslator {
|
||||
|
||||
for (PersistentProperty<?> persistentProperty : persistentProperties) {
|
||||
|
||||
if (persistentProperty.isAssociation()) {
|
||||
if (associations.isLinkableAssociation(persistentProperty)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ package org.springframework.data.rest.webmvc.json;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@@ -27,7 +28,11 @@ import org.springframework.data.annotation.Reference;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.keyvalue.core.mapping.context.KeyValueMappingContext;
|
||||
import org.springframework.data.mapping.context.PersistentEntities;
|
||||
import org.springframework.data.rest.core.annotation.RestResource;
|
||||
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
|
||||
import org.springframework.data.rest.core.mapping.PersistentEntitiesResourceMappings;
|
||||
import org.springframework.data.rest.webmvc.json.JacksonMappingAwareSortTranslator.SortTranslator;
|
||||
import org.springframework.data.rest.webmvc.mapping.Associations;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonUnwrapped;
|
||||
@@ -57,7 +62,9 @@ public class SortTranslatorUnitTests {
|
||||
mappingContext.getPersistentEntity(MultiUnwrapped.class);
|
||||
|
||||
persistentEntities = new PersistentEntities(Collections.singleton(mappingContext));
|
||||
sortTranslator = new SortTranslator(persistentEntities, objectMapper);
|
||||
|
||||
sortTranslator = new SortTranslator(persistentEntities, objectMapper, new Associations(
|
||||
new PersistentEntitiesResourceMappings(persistentEntities), mock(RepositoryRestConfiguration.class)));
|
||||
}
|
||||
|
||||
@Test // DATAREST-883
|
||||
@@ -119,13 +126,22 @@ public class SortTranslatorUnitTests {
|
||||
assertThat(translatedSort, is(nullValue()));
|
||||
}
|
||||
|
||||
@Test // DATAREST-910
|
||||
@Test // DATAREST-910, DATAREST-976
|
||||
public void shouldSkipKnownAssociationProperties() {
|
||||
|
||||
Sort translatedSort = sortTranslator.translateSort(new Sort("association.name"),
|
||||
mappingContext.getPersistentEntity(Plain.class));
|
||||
|
||||
assertThat(translatedSort, is(nullValue()));
|
||||
}
|
||||
|
||||
@Test // DATAREST-976
|
||||
public void shouldMapEmbeddableAssociationProperties() {
|
||||
|
||||
Sort translatedSort = sortTranslator.translateSort(new Sort("refEmbedded.name"),
|
||||
mappingContext.getPersistentEntity(Plain.class));
|
||||
|
||||
assertThat(translatedSort, is(nullValue()));
|
||||
assertThat(translatedSort.getOrderFor("refEmbedded.name"), is(notNullValue()));
|
||||
}
|
||||
|
||||
@Test // DATAREST-910
|
||||
@@ -161,6 +177,7 @@ public class SortTranslatorUnitTests {
|
||||
public String name;
|
||||
public Embedded embedded;
|
||||
@Reference public Embedded refEmbedded;
|
||||
@Reference public AnotherRootEntity association;
|
||||
}
|
||||
|
||||
static class UnwrapEmbedded {
|
||||
@@ -192,4 +209,9 @@ public class SortTranslatorUnitTests {
|
||||
}
|
||||
|
||||
static interface SomeInterface {}
|
||||
|
||||
@RestResource
|
||||
static class AnotherRootEntity {
|
||||
public String name;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,4 +127,4 @@ To have your results sorted on a particular property, add a `sort` URL parameter
|
||||
curl -v "http://localhost:8080/people/search/nameStartsWith?name=K&sort=name,desc"
|
||||
----
|
||||
|
||||
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.
|
||||
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.
|
||||
Reference in New Issue
Block a user