DATAREST-910 - Support nested Sort properties.

We now support nested Sort properties considering Jackson mapping. Sort translation is optional and skipped if the domain class is not resolvable. Translation in the scope of a domain class maps property paths to apply sorting using embedded properties.

A sort string `nested-name` resolves to a property path `anotherWrap.embedded.name`.

class Aggregate {

	@JsonUnwrapped
	public UnwrapEmbedded anotherWrap;
}

class UnwrapEmbedded {

	@JsonUnwrapped(prefix = "nested-")
	public Embedded embedded;
}

class Embedded {
	public String name;
}

Original pull request: #232.
This commit is contained in:
Mark Paluch
2016-09-28 11:06:04 +02:00
committed by Oliver Gierke
parent 24d26943af
commit 0dc97dcc56
10 changed files with 861 additions and 41 deletions

View File

@@ -18,12 +18,19 @@ package org.springframework.data.rest.webmvc.json;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import java.util.Collections;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
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.webmvc.json.JacksonMappingAwareSortTranslator.SortTranslator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonUnwrapped;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
@@ -35,7 +42,10 @@ import com.fasterxml.jackson.databind.ObjectMapper;
*/
public class SortTranslatorUnitTests {
private ObjectMapper objectMapper = new ObjectMapper();
private KeyValueMappingContext mappingContext;
private PersistentEntities persistentEntities;
private SortTranslator sortTranslator;
@Before
public void setUp() {
@@ -43,6 +53,11 @@ public class SortTranslatorUnitTests {
mappingContext = new KeyValueMappingContext();
mappingContext.getPersistentEntity(Plain.class);
mappingContext.getPersistentEntity(WithJsonProperty.class);
mappingContext.getPersistentEntity(UnwrapEmbedded.class);
mappingContext.getPersistentEntity(MultiUnwrapped.class);
persistentEntities = new PersistentEntities(Collections.singleton(mappingContext));
sortTranslator = new SortTranslator(persistentEntities, objectMapper);
}
/**
@@ -51,10 +66,8 @@ public class SortTranslatorUnitTests {
@Test
public void shouldMapKnownProperties() {
MappedProperties mappedProperties = MappedProperties
.fromJacksonProperties(mappingContext.getPersistentEntity(Plain.class), new ObjectMapper());
Sort translatedSort = new JacksonMappingAwareSortTranslator.SortTranslator(mappedProperties)
.translateSort(new Sort("hello", "name"));
Sort translatedSort = sortTranslator.translateSort(new Sort("hello", "name"),
mappingContext.getPersistentEntity(Plain.class));
assertThat(translatedSort.getOrderFor("hello"), is(nullValue()));
assertThat(translatedSort.getOrderFor("name"), is(notNullValue()));
@@ -66,10 +79,8 @@ public class SortTranslatorUnitTests {
@Test
public void returnsNullSortIfNoPropertiesMatch() {
MappedProperties mappedProperties = MappedProperties
.fromJacksonProperties(mappingContext.getPersistentEntity(Plain.class), new ObjectMapper());
Sort translatedSort = new JacksonMappingAwareSortTranslator.SortTranslator(mappedProperties)
.translateSort(new Sort("hello", "world"));
Sort translatedSort = sortTranslator.translateSort(new Sort("hello", "world"),
mappingContext.getPersistentEntity(Plain.class));
assertThat(translatedSort, is(nullValue()));
}
@@ -80,10 +91,8 @@ public class SortTranslatorUnitTests {
@Test
public void shouldMapKnownPropertiesWithJsonProperty() {
MappedProperties mappedProperties = MappedProperties
.fromJacksonProperties(mappingContext.getPersistentEntity(WithJsonProperty.class), new ObjectMapper());
Sort translatedSort = new JacksonMappingAwareSortTranslator.SortTranslator(mappedProperties)
.translateSort(new Sort("hello", "foo"));
Sort translatedSort = sortTranslator.translateSort(new Sort("hello", "foo"),
mappingContext.getPersistentEntity(WithJsonProperty.class));
assertThat(translatedSort.getOrderFor("hello"), is(nullValue()));
assertThat(translatedSort.getOrderFor("name"), is(notNullValue()));
@@ -95,19 +104,122 @@ public class SortTranslatorUnitTests {
@Test
public void shouldJacksonFieldNameForMapping() {
MappedProperties mappedProperties = MappedProperties
.fromJacksonProperties(mappingContext.getPersistentEntity(WithJsonProperty.class), new ObjectMapper());
Sort translatedSort = new JacksonMappingAwareSortTranslator.SortTranslator(mappedProperties)
.translateSort(new Sort("name"));
Sort translatedSort = sortTranslator.translateSort(new Sort("name"),
mappingContext.getPersistentEntity(WithJsonProperty.class));
assertThat(translatedSort, is(nullValue()));
}
/**
* @see DATAREST-910
*/
@Test
public void shouldMapKnownNestedProperties() {
Sort translatedSort = sortTranslator.translateSort(
new Sort("embedded.name", "embedded.collection", "embedded.someInterface"),
mappingContext.getPersistentEntity(Plain.class));
assertThat(translatedSort.getOrderFor("embedded.name"), is(notNullValue()));
assertThat(translatedSort.getOrderFor("embedded.collection"), is(notNullValue()));
assertThat(translatedSort.getOrderFor("embedded.someInterface"), is(notNullValue()));
}
/**
* @see DATAREST-910
*/
@Test
public void shouldSkipWrongNestedProperties() {
Sort translatedSort = sortTranslator.translateSort(new Sort("embedded.unknown"),
mappingContext.getPersistentEntity(Plain.class));
assertThat(translatedSort, is(nullValue()));
}
/**
* @see DATAREST-910
*/
@Test
public void shouldSkipKnownAssociationProperties() {
Sort translatedSort = sortTranslator.translateSort(new Sort("refEmbedded.name"),
mappingContext.getPersistentEntity(Plain.class));
assertThat(translatedSort, is(nullValue()));
}
/**
* @see DATAREST-910
*/
@Test
public void shouldJacksonFieldNameForNestedFieldMapping() {
Sort translatedSort = sortTranslator.translateSort(new Sort("em.foo"),
mappingContext.getPersistentEntity(WithJsonProperty.class));
assertThat(translatedSort.getOrderFor("embeddedWithJsonProperty.bar"), is(notNullValue()));
}
/**
* @see DATAREST-910
*/
@Test
public void shouldTranslatePathForSingleLevelJsonUnwrappedObject() {
Sort translatedSort = sortTranslator.translateSort(new Sort("un-name"),
mappingContext.getPersistentEntity(UnwrapEmbedded.class));
assertThat(translatedSort.getOrderFor("embedded.name"), is(notNullValue()));
}
/**
* @see DATAREST-910
*/
@Test
public void shouldTranslatePathForMultiLevelLevelJsonUnwrappedObject() {
Sort translatedSort = sortTranslator.translateSort(new Sort("un-name", "burrito.un-name"),
mappingContext.getPersistentEntity(MultiUnwrapped.class));
assertThat(translatedSort.getOrderFor("anotherWrap.embedded.name"), is(notNullValue()));
assertThat(translatedSort.getOrderFor("burrito.embedded.name"), is(notNullValue()));
}
static class Plain {
public String name;
public Embedded embedded;
@Reference public Embedded refEmbedded;
}
static class UnwrapEmbedded {
@JsonUnwrapped(prefix = "un-") public Embedded embedded;
}
static class MultiUnwrapped {
public String name;
@JsonUnwrapped public UnwrapEmbedded anotherWrap;
public UnwrapEmbedded burrito;
}
static class Embedded {
public String name;
public List<String> collection;
public SomeInterface someInterface;
}
static class WithJsonProperty {
public @JsonProperty("foo") String name;
@JsonProperty("foo") public String name;
@JsonProperty("em") public EmbeddedWithJsonProperty embeddedWithJsonProperty;
}
static class EmbeddedWithJsonProperty {
@JsonProperty("foo") public String bar;
}
static interface SomeInterface {}
}

View File

@@ -0,0 +1,207 @@
/*
* Copyright 2016 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.rest.webmvc.json;
import static org.hamcrest.MatcherAssert.*;
import static org.hamcrest.Matchers.*;
import java.util.Collections;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.springframework.data.keyvalue.core.mapping.context.KeyValueMappingContext;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.context.PersistentEntities;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonUnwrapped;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* Unit tests for {@link WrappedProperties}.
*
* @author Mark Paluch
*/
public class WrappedPropertiesUnitTests {
private static final ObjectMapper MAPPER = new ObjectMapper();
private KeyValueMappingContext mappingContext;
private PersistentEntities persistentEntities;
@Before
public void setUp() {
mappingContext = new KeyValueMappingContext();
mappingContext.getPersistentEntity(MultiLevelNesting.class);
mappingContext.getPersistentEntity(SyntheticProperties.class);
persistentEntities = new PersistentEntities(Collections.singleton(mappingContext));
}
/**
* @see DATAREST-910
*/
@Test
public void wrappedPropertiesShouldConsiderSingleLevelUnwrapping() {
PersistentEntity<?, ?> persistentEntity = persistentEntities.getPersistentEntity(OneLevelNesting.class);
WrappedProperties wrappedProperties = WrappedProperties.fromJacksonProperties(persistentEntities, persistentEntity,
MAPPER);
assertThat(wrappedProperties.hasPersistentPropertiesForField("street"), is(true));
assertThat(wrappedProperties.hasPersistentPropertiesForField("one"), is(false));
List<PersistentProperty<?>> street = wrappedProperties.getPersistentProperties("street");
PersistentProperty<?> addressProperty = persistentEntity.getPersistentProperty("address");
PersistentProperty<?> streetProperty = persistentEntities.getPersistentEntity(Address.class)
.getPersistentProperty("street");
assertThat(street, contains(addressProperty, streetProperty));
}
/**
* @see DATAREST-910
*/
@Test
public void wrappedPropertiesShouldConsiderMultiLevelUnwrapping() {
PersistentEntity<?, ?> persistentEntity = persistentEntities.getPersistentEntity(MultiLevelNesting.class);
WrappedProperties wrappedProperties = WrappedProperties.fromJacksonProperties(persistentEntities, persistentEntity,
new ObjectMapper());
assertThat(wrappedProperties.hasPersistentPropertiesForField("pre-one-post"), is(true));
assertThat(wrappedProperties.hasPersistentPropertiesForField("pre-street-post"), is(true));
assertThat(wrappedProperties.hasPersistentPropertiesForField("nested"), is(false));
List<PersistentProperty<?>> street = wrappedProperties.getPersistentProperties("pre-street-post");
PersistentProperty<?> oneLevelNestingProperty = persistentEntity.getPersistentProperty("unwrapped");
PersistentProperty<?> addressProperty = persistentEntities.getPersistentEntity(OneLevelNesting.class)
.getPersistentProperty("address");
PersistentProperty<?> streetProperty = persistentEntities.getPersistentEntity(Address.class)
.getPersistentProperty("street");
assertThat(street, contains(oneLevelNestingProperty, addressProperty, streetProperty));
}
/**
* @see DATAREST-910
*/
@Test
public void wrappedPropertiesShouldConsiderJacksonFieldNames() {
PersistentEntity<?, ?> persistentEntity = persistentEntities.getPersistentEntity(MultiLevelNesting.class);
WrappedProperties wrappedProperties = WrappedProperties.fromJacksonProperties(persistentEntities, persistentEntity,
new ObjectMapper());
assertThat(wrappedProperties.hasPersistentPropertiesForField("pre-zip-post"), is(true));
}
/**
* @see DATAREST-910
*/
@Test
public void wrappedPropertiesShouldIgnoreIgnoredJacksonFields() {
PersistentEntity<?, ?> persistentEntity = persistentEntities.getPersistentEntity(MultiLevelNesting.class);
WrappedProperties wrappedProperties = WrappedProperties.fromJacksonProperties(persistentEntities, persistentEntity,
new ObjectMapper());
assertThat(wrappedProperties.hasPersistentPropertiesForField("pre-street-ignored"), is(false));
}
/**
* @see DATAREST-910
*/
@Test
public void wrappedPropertiesShouldIgnoreSyntheticProperties() {
PersistentEntity<?, ?> persistentEntity = persistentEntities.getPersistentEntity(SyntheticProperties.class);
WrappedProperties wrappedProperties = WrappedProperties.fromJacksonProperties(persistentEntities, persistentEntity,
new ObjectMapper());
assertThat(wrappedProperties.hasPersistentPropertiesForField("street"), is(false));
}
@Data
@AllArgsConstructor
@NoArgsConstructor
static class OneLevelNesting {
String one;
@JsonUnwrapped Address address;
}
static class SyntheticProperties {
@JsonUnwrapped
Address getUnwrapped() {
return null;
}
MultiLevelNesting getSynthetic() {
return null;
}
@JsonUnwrapped
void setWrapped(OneLevelNesting address) {}
}
/**
* <pre>
* <code>
{
"multi": "multi",
"pre-one-post": "one",
"pre-street-post": "street",
"pre-zip-post": "zip",
"nested": {
"one": "one",
"street": "street",
"zip": "zip"
}
}
</code>
* </pre>
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
static class MultiLevelNesting {
String multi;
@JsonUnwrapped(prefix = "pre-", suffix = "-post") OneLevelNesting unwrapped;
@JsonIgnore @JsonUnwrapped(prefix = "pre-", suffix = "-ignored") OneLevelNesting ignored;
@JsonUnwrapped(enabled = false) OneLevelNesting nested;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
static class Address {
String street;
@JsonProperty("zip") String zipCode;
}
}

View File

@@ -0,0 +1,72 @@
/*
* Copyright 2016 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.rest.webmvc.util;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import java.lang.reflect.Method;
import java.util.List;
import org.junit.Test;
import org.springframework.util.ClassUtils;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* Unit tests for {@link UriUtils}.
*
* @author Mark Paluch
*/
public class UriUtilsUnitTests {
/**
* @see DATAREST-910
*/
@Test
public void pathSegmentsShouldDiscoverPathUsingMethodMapping() throws Exception {
Method method = ClassUtils.getMethod(MappedMethod.class, "method");
List<String> pathSegments = UriUtils.getPathSegments(method);
assertThat(pathSegments, hasItems("hello", "world"));
}
/**
* @see DATAREST-910
*/
@Test
public void pathSegmentsShouldDiscoverPathUsingTypeAndMethodMapping() throws Exception {
Method method = ClassUtils.getMethod(MappedClassAndMethod.class, "method");
List<String> pathSegments = UriUtils.getPathSegments(method);
assertThat(pathSegments, hasItems("hello", "world"));
}
static class MappedMethod {
@RequestMapping("hello/world")
public void method() {}
}
@RequestMapping("hello")
static class MappedClassAndMethod {
@RequestMapping("world")
public void method() {}
}
}