diff --git a/pom.xml b/pom.xml index bf02aaaf94..59496a4143 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-commons - 1.8.0.BUILD-SNAPSHOT + 1.8.0.DATACMNS-491-SNAPSHOT Spring Data Core diff --git a/src/main/java/org/springframework/data/domain/Sort.java b/src/main/java/org/springframework/data/domain/Sort.java index c4bfa26875..aa55b9ecda 100644 --- a/src/main/java/org/springframework/data/domain/Sort.java +++ b/src/main/java/org/springframework/data/domain/Sort.java @@ -229,6 +229,30 @@ public static Direction fromStringOrNull(String value) { } } + /** + * Enumeration for null handling hints that can be used in {@link Order} expressions. + * + * @author Thomas Darimont + * @since 1.7 + */ + public static enum NullHandling { + + /** + * Lets the data store decide what to do with nulls. + */ + NATIVE, // + + /** + * A hint to the used data store to order entries with null values before non null entries. + */ + NULLS_FIRST, // + + /** + * A hint to the used data store to order entries with null values after non null entries. + */ + NULLS_LAST; // + } + /** * PropertyPath implements the pairing of an {@link Direction} and a property. It is used to provide input for * {@link Sort} @@ -244,6 +268,7 @@ public static class Order implements Serializable { private final Direction direction; private final String property; private final boolean ignoreCase; + private final NullHandling nullHandlingHint; /** * Creates a new {@link Order} instance. if order is {@literal null} then order defaults to @@ -254,7 +279,20 @@ public static class Order implements Serializable { */ public Order(Direction direction, String property) { - this(direction, property, DEFAULT_IGNORE_CASE); + this(direction, property, DEFAULT_IGNORE_CASE, null); + } + + /** + * Creates a new {@link Order} instance. if order is {@literal null} then order defaults to + * {@link Sort#DEFAULT_DIRECTION} + * + * @param direction can be {@literal null}, will default to {@link Sort#DEFAULT_DIRECTION} + * @param property must not be {@literal null} or empty. + * @param nullHandlingHint can be {@literal null}, will default to {@link NullHandling#NATIVE}. + */ + public Order(Direction direction, String property, NullHandling nullHandlingHint) { + + this(direction, property, DEFAULT_IGNORE_CASE, nullHandlingHint); } /** @@ -274,8 +312,10 @@ public Order(String property) { * @param direction can be {@literal null}, will default to {@link Sort#DEFAULT_DIRECTION} * @param property must not be {@literal null} or empty. * @param ignoreCase true if sorting should be case insensitive. false if sorting should be case sensitive. + * @param nullHandlingHint can be {@literal null}, will default to {@link NullHandling#NATIVE}. + * @since 1.7 */ - private Order(Direction direction, String property, boolean ignoreCase) { + private Order(Direction direction, String property, boolean ignoreCase, NullHandling nullHandlingHint) { if (!StringUtils.hasText(property)) { throw new IllegalArgumentException("Property must not null or empty!"); @@ -284,6 +324,7 @@ private Order(Direction direction, String property, boolean ignoreCase) { this.direction = direction == null ? DEFAULT_DIRECTION : direction; this.property = property; this.ignoreCase = ignoreCase; + this.nullHandlingHint = nullHandlingHint == null ? NullHandling.NATIVE : nullHandlingHint; } /** @@ -342,7 +383,7 @@ public boolean isIgnoreCase() { * @return */ public Order with(Direction order) { - return new Order(order, this.property); + return new Order(order, this.property, nullHandlingHint); } /** @@ -361,7 +402,58 @@ public Sort withProperties(String... properties) { * @return */ public Order ignoreCase() { - return new Order(direction, property, true); + return new Order(direction, property, true, nullHandlingHint); + } + + /** + * Returns a {@link Order} with the given {@link NullHandling}. + * + * @param nullHandling + * @return + * @since 1.7 + */ + public Order withNullHandling(NullHandling nullHandling) { + return new Order(direction, this.property, ignoreCase, nullHandling); + } + + /** + * Returns a {@link Order} with {@link NullHandling#NULLS_FIRST} as null handling hint. + * + * @return + * @since 1.7 + */ + public Order nullsFirst() { + return withNullHandling(NullHandling.NULLS_FIRST); + } + + /** + * Returns a {@link Order} with {@link NullHandling#NULLS_LAST} as null handling hint. + * + * @return + * @since 1.7 + */ + public Order nullsLast() { + return withNullHandling(NullHandling.NULLS_LAST); + } + + /** + * Returns a {@link Order} with {@link NullHandling#NATIVE} as null handling hint. + * + * @return + * @since 1.7 + */ + public Order nullsNative() { + return withNullHandling(NullHandling.NATIVE); + } + + /** + * Returns the used {@link NullHandling} hint, which can but may not be respected by the used datastore. + * + * @return + * @since 1.7 + */ + public NullHandling getNullHandlingHint() { + return nullHandlingHint; } /* @@ -376,6 +468,7 @@ public int hashCode() { result = 31 * result + direction.hashCode(); result = 31 * result + property.hashCode(); result = 31 * result + (ignoreCase ? 1 : 0); + result = 31 * result + (nullHandlingHint.hashCode()); return result; } @@ -398,7 +491,7 @@ public boolean equals(Object obj) { Order that = (Order) obj; return this.direction.equals(that.direction) && this.property.equals(that.property) - && this.ignoreCase == that.ignoreCase; + && this.ignoreCase == that.ignoreCase && this.nullHandlingHint.equals(that.nullHandlingHint); } /* @@ -409,7 +502,16 @@ public boolean equals(Object obj) { public String toString() { String result = String.format("%s: %s", property, direction); - return ignoreCase ? result.concat(", ignoring case") : result; + + if (!NullHandling.NATIVE.equals(nullHandlingHint)) { + result += ", " + nullHandlingHint; + } + + if (ignoreCase) { + result += ", ignoring case"; + } + + return result; } } } diff --git a/src/test/java/org/springframework/data/domain/SortUnitTests.java b/src/test/java/org/springframework/data/domain/SortUnitTests.java index 20e173c9b0..4efe19912e 100644 --- a/src/test/java/org/springframework/data/domain/SortUnitTests.java +++ b/src/test/java/org/springframework/data/domain/SortUnitTests.java @@ -20,6 +20,7 @@ import org.junit.Test; import org.springframework.data.domain.Sort.Direction; +import org.springframework.data.domain.Sort.NullHandling; import org.springframework.data.domain.Sort.Order; /** @@ -130,4 +131,36 @@ public void ordersWithDifferentIgnoreCaseDoNotEqual() { assertThat(foo, is(not(fooIgnoreCase))); assertThat(foo.hashCode(), is(not(fooIgnoreCase.hashCode()))); } + + /** + * @see DATACMNS-491 + */ + @Test + public void orderWithNullHandlingHintNullsFirst() { + assertThat(new Order("foo").nullsFirst().getNullHandlingHint(), is(NullHandling.NULLS_FIRST)); + } + + /** + * @see DATACMNS-491 + */ + @Test + public void orderWithNullHandlingHintNullsLast() { + assertThat(new Order("foo").nullsFirst().getNullHandlingHint(), is(NullHandling.NULLS_FIRST)); + } + + /** + * @see DATACMNS-491 + */ + @Test + public void orderWithNullHandlingHintNullsNative() { + assertThat(new Order("foo").nullsNative().getNullHandlingHint(), is(NullHandling.NATIVE)); + } + + /** + * @see DATACMNS-491 + */ + @Test + public void orderWithDefaultNullHandlingHint() { + assertThat(new Order("foo").getNullHandlingHint(), is(NullHandling.NATIVE)); + } }