Skip to content

Commit

Permalink
DATAREST-976 - Embedded properties are now considered in sort propert…
Browse files Browse the repository at this point in the history
…y 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.
  • Loading branch information
mp911de committed Jan 13, 2017
1 parent 0f9ee7d commit c1fb7bf
Show file tree
Hide file tree
Showing 7 changed files with 63 additions and 16 deletions.
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -59,5 +66,17 @@ public Book(String isbn, String title, long soldUnits, Iterable<Author> authors)
author.books.add(this);
this.authors.add(author);
}

this.offer = offer;
}

@Getter
@Embeddable
@AllArgsConstructor
@NoArgsConstructor
static class Offer {

double price;
String currency;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -494,12 +494,12 @@ public void exectuesSearchThatTakesASort() throws Exception {
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"));
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -18,6 +18,7 @@
import java.util.Arrays;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.rest.webmvc.jpa.Book.Offer;

/**
* @author Jon Brisbin
Expand Down Expand Up @@ -54,8 +55,8 @@ private void populateAuthorsAndBooks() {

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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -804,7 +804,8 @@ protected List<HandlerMethodArgumentResolver> defaultMethodArgumentResolvers() {
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,
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -31,6 +31,7 @@
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;
Expand Down Expand Up @@ -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);
}

/**
Expand Down Expand Up @@ -117,6 +120,7 @@ public static class SortTranslator {

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
Expand Down Expand Up @@ -181,7 +185,7 @@ private List<String> mapPropertyPath(PersistentEntity<?, ?> rootEntity, List<Str

for (PersistentProperty<?> persistentProperty : persistentProperties) {

if (persistentProperty.isAssociation()) {
if (associations.isLinkableAssociation(persistentProperty)) {
return Collections.emptyList();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;

import java.util.Collections;
import java.util.List;
Expand All @@ -27,7 +28,11 @@
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;
Expand Down Expand Up @@ -57,7 +62,9 @@ public void setUp() {
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
Expand Down Expand Up @@ -119,15 +126,24 @@ public void shouldSkipWrongNestedProperties() {
assertThat(translatedSort, is(nullValue()));
}

@Test // DATAREST-910
@Test // DATAREST-910, DATAREST-976
public void shouldSkipKnownAssociationProperties() {

Sort translatedSort = sortTranslator.translateSort(new Sort("refEmbedded.name"),
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.getOrderFor("refEmbedded.name"), is(notNullValue()));
}

@Test // DATAREST-910
public void shouldJacksonFieldNameForNestedFieldMapping() {

Expand Down Expand Up @@ -161,6 +177,7 @@ static class Plain {
public String name;
public Embedded embedded;
@Reference public Embedded refEmbedded;
@Reference public AnotherRootEntity association;
}

static class UnwrapEmbedded {
Expand Down Expand Up @@ -192,4 +209,9 @@ static class EmbeddedWithJsonProperty {
}

static interface SomeInterface {}

@RestResource
static class AnotherRootEntity {
public String name;
}
}
2 changes: 1 addition & 1 deletion src/main/asciidoc/paging-and-sorting.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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.

0 comments on commit c1fb7bf

Please sign in to comment.