diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/domain/Sort.java b/spring-data-commons-core/src/main/java/org/springframework/data/domain/Sort.java index 8d3c73387..8d733568f 100644 --- a/spring-data-commons-core/src/main/java/org/springframework/data/domain/Sort.java +++ b/spring-data-commons-core/src/main/java/org/springframework/data/domain/Sort.java @@ -192,7 +192,7 @@ public class Sort implements } catch (Exception e) { throw new IllegalArgumentException( String.format( - "Invalid value '%s' for orders given! Has to be either 'desc' or 'asc'.", + "Invalid value '%s' for orders given! Has to be either 'desc' or 'asc' (case insensitive).", value), e); } } diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/query/parser/OrderBySource.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/query/parser/OrderBySource.java index b1bbcbd7e..e7000befc 100644 --- a/spring-data-commons-core/src/main/java/org/springframework/data/repository/query/parser/OrderBySource.java +++ b/spring-data-commons-core/src/main/java/org/springframework/data/repository/query/parser/OrderBySource.java @@ -17,11 +17,12 @@ package org.springframework.data.repository.query.parser; import java.util.ArrayList; import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort.Direction; import org.springframework.data.domain.Sort.Order; -import org.springframework.util.Assert; import org.springframework.util.StringUtils; @@ -36,45 +37,59 @@ import org.springframework.util.StringUtils; */ public class OrderBySource { + private final String BLOCK_SPLIT = "(?<=Asc|Desc)(?=[A-Z])"; + private final Pattern DIRECTION_SPLIT = Pattern.compile("(.+)(Asc|Desc)$"); + private final List orders; public OrderBySource(String clause) { + this(clause, null); + } + + + public OrderBySource(String clause, Class domainClass) { + this.orders = new ArrayList(); - List properties = new ArrayList(); - for (String part : clause.split("(?<=[a-z])(?=[A-Z])")) { + for (String part : clause.split(BLOCK_SPLIT)) { - Direction direction = defaultedFrom(part); + Matcher matcher = DIRECTION_SPLIT.matcher(part); - if (direction == null) { - properties.add(StringUtils.uncapitalize(part)); - } else { - Assert.notEmpty( - properties, - "Invalid order syntax! You have to provide at least one property before the sort direction."); - orders.addAll(Order.create(direction, properties)); - properties.clear(); + if (!matcher.find()) { + throw new IllegalArgumentException(String.format( + "Invalid order syntax for part %s!", part)); } + + Direction direction = Direction.fromString(matcher.group(2)); + this.orders.add(createOrder(matcher.group(1), direction, + domainClass)); } } /** - * Tries to resolve a {@link Direction} for the given {@link String}. - * Returns {@literal null} if resolving fails. + * Creates an {@link Order} instance from the given property source, + * direction and domain class. If the domain class is given, we will use it + * for nested property traversal checks. * - * @param candidate + * @see Property#from(String, Class) + * @param propertySource + * @param direction + * @param domainClass can be {@literal null}. * @return */ - private Direction defaultedFrom(String candidate) { + private Order createOrder(String propertySource, Direction direction, + Class domainClass) { - try { - return Direction.fromString(candidate); - } catch (IllegalArgumentException e) { - return null; + if (null == domainClass) { + return new Order(direction, + StringUtils.uncapitalize(propertySource)); } + + Property property = Property.from(propertySource, domainClass); + return new Order(direction, property.toDotPath()); } diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/query/parser/PartTree.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/query/parser/PartTree.java index 924a0cb55..e0e0116ca 100644 --- a/spring-data-commons-core/src/main/java/org/springframework/data/repository/query/parser/PartTree.java +++ b/spring-data-commons-core/src/main/java/org/springframework/data/repository/query/parser/PartTree.java @@ -71,7 +71,8 @@ public class PartTree implements Iterable { buildTree(parts[0], domainClass); this.orderBySource = - parts.length == 2 ? new OrderBySource(parts[1]) : null; + parts.length == 2 ? new OrderBySource(parts[1], domainClass) + : null; } diff --git a/spring-data-commons-core/src/test/java/org/springframework/data/repository/query/parser/OrderBySourceUnitTests.java b/spring-data-commons-core/src/test/java/org/springframework/data/repository/query/parser/OrderBySourceUnitTests.java index 122e0f1f2..5f67ed439 100644 --- a/spring-data-commons-core/src/test/java/org/springframework/data/repository/query/parser/OrderBySourceUnitTests.java +++ b/spring-data-commons-core/src/test/java/org/springframework/data/repository/query/parser/OrderBySourceUnitTests.java @@ -40,11 +40,10 @@ public class OrderBySourceUnitTests { @Test - public void handlesSingleDirectionAndMultiplePropertiesCorrectly() - throws Exception { + public void handlesCamelCasePropertyCorrecty() throws Exception { assertThat(new OrderBySource("LastnameUsernameDesc").toSort(), - is(new Sort(DESC, "lastname", "username"))); + is(new Sort(DESC, "lastnameUsername"))); } @@ -63,4 +62,25 @@ public class OrderBySourceUnitTests { new OrderBySource("Desc"); } + + + @Test + public void usesNestedPropertyCorrectly() throws Exception { + + OrderBySource source = new OrderBySource("BarNameDesc", Foo.class); + assertThat(source.toSort(), is(new Sort(new Order(DESC, "bar.name")))); + + } + + @SuppressWarnings("unused") + private class Foo { + + private Bar bar; + } + + @SuppressWarnings("unused") + private class Bar { + + private String name; + } }