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));
+ }
}