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:
committed by
Oliver Gierke
parent
24d26943af
commit
0dc97dcc56
@@ -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 {}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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() {}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user