Introduce PropertyPath based SortAccessor.

Allow easy configuration of PropertyPath based sorting.

See: #565
This commit is contained in:
Christoph Strobl
2024-03-07 10:34:33 +01:00
parent 99c1e2ea5b
commit 869303f690
10 changed files with 503 additions and 2 deletions

View File

@@ -0,0 +1,186 @@
/*
* Copyright 2024 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
*
* https://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.keyvalue.core;
import static org.assertj.core.api.Assertions.*;
import java.util.Comparator;
import org.junit.jupiter.api.Test;
/**
* Unit tests for {@link PropertyPathComparator}.
*
* @author Christoph Strobl
*/
class PropertyPathComparatorUnitTests {
private static final SomeType ONE = new SomeType("one", 1, 1);
private static final SomeType TWO = new SomeType("two", 2, 2);
private static final WrapperType WRAPPER_ONE = new WrapperType("w-one", ONE);
private static final WrapperType WRAPPER_TWO = new WrapperType("w-two", TWO);
@Test // DATACMNS-525
void shouldCompareStringAscCorrectly() {
Comparator<SomeType> comparator = new PropertyPathComparator<>("stringProperty");
assertThat(comparator.compare(ONE, TWO)).isEqualTo(ONE.getStringProperty().compareTo(TWO.getStringProperty()));
}
@Test // DATACMNS-525
void shouldCompareStringDescCorrectly() {
Comparator<SomeType> comparator = new PropertyPathComparator<SomeType>("stringProperty").desc();
assertThat(comparator.compare(ONE, TWO)).isEqualTo(TWO.getStringProperty().compareTo(ONE.getStringProperty()));
}
@Test // DATACMNS-525
void shouldCompareIntegerAscCorrectly() {
Comparator<SomeType> comparator = new PropertyPathComparator<>("integerProperty");
assertThat(comparator.compare(ONE, TWO)).isEqualTo(ONE.getIntegerProperty().compareTo(TWO.getIntegerProperty()));
}
@Test // DATACMNS-525
void shouldCompareIntegerDescCorrectly() {
Comparator<SomeType> comparator = new PropertyPathComparator<SomeType>("integerProperty").desc();
assertThat(comparator.compare(ONE, TWO)).isEqualTo(TWO.getIntegerProperty().compareTo(ONE.getIntegerProperty()));
}
@Test // DATACMNS-525
void shouldComparePrimitiveIntegerAscCorrectly() {
Comparator<SomeType> comparator = new PropertyPathComparator<>("primitiveProperty");
assertThat(comparator.compare(ONE, TWO))
.isEqualTo(Integer.compare(ONE.getPrimitiveProperty(), TWO.getPrimitiveProperty()));
}
@Test // DATACMNS-525
void shouldNotFailOnNullValues() {
Comparator<SomeType> comparator = new PropertyPathComparator<>("stringProperty");
assertThat(comparator.compare(ONE, new SomeType(null, null, 2))).isEqualTo(1);
}
@Test // DATACMNS-525
void shouldComparePrimitiveIntegerDescCorrectly() {
Comparator<SomeType> comparator = new PropertyPathComparator<SomeType>("primitiveProperty").desc();
assertThat(comparator.compare(ONE, TWO))
.isEqualTo(Integer.compare(TWO.getPrimitiveProperty(), ONE.getPrimitiveProperty()));
}
@Test // DATACMNS-525
void shouldSortNullsFirstCorrectly() {
Comparator<SomeType> comparator = new PropertyPathComparator<SomeType>("stringProperty").nullsFirst();
assertThat(comparator.compare(ONE, new SomeType(null, null, 2))).isEqualTo(1);
}
@Test // DATACMNS-525
void shouldSortNullsLastCorrectly() {
Comparator<SomeType> comparator = new PropertyPathComparator<SomeType>("stringProperty").nullsLast();
assertThat(comparator.compare(ONE, new SomeType(null, null, 2))).isEqualTo(-1);
}
@Test // DATACMNS-525
void shouldCompareNestedTypesCorrectly() {
Comparator<WrapperType> comparator = new PropertyPathComparator<>("nestedType.stringProperty");
assertThat(comparator.compare(WRAPPER_ONE, WRAPPER_TWO)).isEqualTo(
WRAPPER_ONE.getNestedType().getStringProperty().compareTo(WRAPPER_TWO.getNestedType().getStringProperty()));
}
@Test // DATACMNS-525
void shouldCompareNestedTypesCorrectlyWhenOneOfThemHasNullValue() {
PropertyPathComparator<WrapperType> comparator = new PropertyPathComparator<>("nestedType.stringProperty");
assertThat(comparator.compare(WRAPPER_ONE, new WrapperType("two", null))).isGreaterThanOrEqualTo(1);
}
public static class WrapperType {
private String stringPropertyWrapper;
private SomeType nestedType;
WrapperType(String stringPropertyWrapper, SomeType nestedType) {
this.stringPropertyWrapper = stringPropertyWrapper;
this.nestedType = nestedType;
}
public String getStringPropertyWrapper() {
return stringPropertyWrapper;
}
public void setStringPropertyWrapper(String stringPropertyWrapper) {
this.stringPropertyWrapper = stringPropertyWrapper;
}
public SomeType getNestedType() {
return nestedType;
}
public void setNestedType(SomeType nestedType) {
this.nestedType = nestedType;
}
}
@SuppressWarnings("WeakerAccess")
public static class SomeType {
public SomeType() {
}
SomeType(String stringProperty, Integer integerProperty, int primitiveProperty) {
this.stringProperty = stringProperty;
this.integerProperty = integerProperty;
this.primitiveProperty = primitiveProperty;
}
String stringProperty;
Integer integerProperty;
int primitiveProperty;
public String getStringProperty() {
return stringProperty;
}
public void setStringProperty(String stringProperty) {
this.stringProperty = stringProperty;
}
public Integer getIntegerProperty() {
return integerProperty;
}
public void setIntegerProperty(Integer integerProperty) {
this.integerProperty = integerProperty;
}
public int getPrimitiveProperty() {
return primitiveProperty;
}
public void setPrimitiveProperty(int primitiveProperty) {
this.primitiveProperty = primitiveProperty;
}
}
}

View File

@@ -24,6 +24,7 @@ import java.util.Arrays;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentSkipListMap;
import org.assertj.core.api.InstanceOfAssertFactories;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
@@ -37,6 +38,7 @@ import org.springframework.data.annotation.Id;
import org.springframework.data.keyvalue.core.KeyValueAdapter;
import org.springframework.data.keyvalue.core.KeyValueOperations;
import org.springframework.data.keyvalue.core.KeyValueTemplate;
import org.springframework.data.keyvalue.core.PathSortAccessor;
import org.springframework.data.keyvalue.repository.KeyValueRepository;
import org.springframework.data.map.MapKeyValueAdapter;
import org.springframework.test.util.ReflectionTestUtils;
@@ -96,6 +98,12 @@ class MapRepositoriesConfigurationExtensionIntegrationTests {
ConfigWithCustomizedMapTypeAndExplicitDefinitionOfKeyValueTemplate.class));
}
@Test // GH-565
void considersSortAccessorConfiguredOnAnnotation() {
assertKeyValueTemplateWithSortAccessorFor(PathSortAccessor.class,
new AnnotationConfigApplicationContext(ConfigWithCustomizedSortAccessor.class));
}
private static void assertKeyValueTemplateWithAdapterFor(Class<?> mapType, ApplicationContext context) {
KeyValueTemplate template = context.getBean(KeyValueTemplate.class);
@@ -105,6 +113,19 @@ class MapRepositoriesConfigurationExtensionIntegrationTests {
assertThat(ReflectionTestUtils.getField(adapter, "store")).isInstanceOf(mapType);
}
private static void assertKeyValueTemplateWithSortAccessorFor(Class<?> sortAccessorType, ApplicationContext context) {
KeyValueTemplate template = context.getBean(KeyValueTemplate.class);
Object adapter = ReflectionTestUtils.getField(template, "adapter");
assertThat(adapter).isInstanceOf(MapKeyValueAdapter.class);
Object engine = ReflectionTestUtils.getField(adapter, "engine");
Object sortAccessor = ReflectionTestUtils.getField(engine, "sortAccessor");
assertThat(sortAccessor).asInstanceOf(InstanceOfAssertFactories.OPTIONAL).containsInstanceOf(sortAccessorType);
}
@Configuration
@EnableMapRepositories
static class Config {}
@@ -149,6 +170,9 @@ class MapRepositoriesConfigurationExtensionIntegrationTests {
}
}
@EnableMapRepositories(sortAccessor = PathSortAccessor.class)
static class ConfigWithCustomizedSortAccessor {}
interface PersonRepository extends KeyValueRepository<Person, String> {
}