diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Update.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Update.java index 44f2718c4..362d40e0a 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Update.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Update.java @@ -27,6 +27,9 @@ import java.util.Map; import java.util.Set; import org.springframework.dao.InvalidDataAccessApiUsageException; +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; @@ -44,6 +47,7 @@ import com.mongodb.DBObject; * @author Thomas Darimont * @author Alexey Plotnik * @author Mark Paluch + * @author Pavel Vodrazka */ public class Update { @@ -660,6 +664,58 @@ public class Update { return this.count; } } + + /** + * Implementation of {@link Modifier} representing {@code $sort}. + * + * @author Pavel Vodrazka + * @since 1.10 + */ + private static class SortModifier implements Modifier { + + private final Object sort; + + public SortModifier(Direction direction) { + this.sort = direction.isAscending() ? 1 : -1; + } + + public SortModifier(Sort sort) { + this.sort = createDBObject(sort); + } + + private DBObject createDBObject(Sort sort) { + + DBObject obj = new BasicDBObject(); + + for (Order order : sort) { + if (order.isIgnoreCase()) { + throw new IllegalArgumentException(String.format("Given sort contained an Order for %s with ignore case! " + + "MongoDB does not support sorting ignoring case currently!", order.getProperty())); + } + obj.put(order.getProperty(), order.isAscending() ? 1 : -1); + } + + return obj; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.query.Update.Modifier#getKey() + */ + @Override + public String getKey() { + return "$sort"; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.query.Update.Modifier#getValue() + */ + @Override + public Object getValue() { + return this.sort; + } + } /** * Builder for creating {@code $push} modifiers @@ -707,6 +763,36 @@ public class Update { return this; } + /** + * Propagates {@code $sort} to {@code $push}. {@code $sort} requires the {@code $each} operator. + * Forces elements to be sorted by values in given {@literal direction}. + * + * @param direction must not be {@literal null}. + * @return never {@literal null}. + * @since 1.10 + */ + public PushOperatorBuilder sort(Direction direction) { + + Assert.notNull(direction, "Direction must not be 'null'."); + this.modifiers.addModifier(new SortModifier(direction)); + return this; + } + + /** + * Propagates {@code $sort} to {@code $push}. {@code $sort} requires the {@code $each} operator. + * Forces document elements to be sorted in given {@literal order}. + * + * @param order must not be {@literal null}. + * @return never {@literal null}. + * @since 1.10 + */ + public PushOperatorBuilder sort(Sort order) { + + Assert.notNull(order, "Order must not be 'null'."); + this.modifiers.addModifier(new SortModifier(order)); + return this; + } + /** * Forces values to be added at the given {@literal position}. * diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/UpdateMapperUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/UpdateMapperUnitTests.java index d6942a15f..f3726052b 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/UpdateMapperUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/UpdateMapperUnitTests.java @@ -16,6 +16,7 @@ package org.springframework.data.mongodb.core.convert; import static org.hamcrest.CoreMatchers.*; +import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.collection.IsMapContaining.*; import static org.junit.Assert.*; import static org.mockito.Mockito.*; @@ -42,6 +43,9 @@ import org.mockito.runners.MockitoJUnitRunner; import org.springframework.core.convert.converter.Converter; import org.springframework.data.annotation.Id; import org.springframework.data.convert.WritingConverter; +import org.springframework.data.domain.Sort; +import org.springframework.data.domain.Sort.Direction; +import org.springframework.data.domain.Sort.Order; import org.springframework.data.mapping.model.MappingException; import org.springframework.data.mongodb.MongoDbFactory; import org.springframework.data.mongodb.core.DBObjectTestUtils; @@ -68,6 +72,7 @@ import com.mongodb.DBRef; * @author Christoph Strobl * @author Thomas Darimont * @author Mark Paluch + * @author Pavel Vodrazka */ @RunWith(MockitoJUnitRunner.class) public class UpdateMapperUnitTests { @@ -416,6 +421,68 @@ public class UpdateMapperUnitTests { assertThat(key2.containsField("$each"), is(true)); } + /** + * @see DATAMONGO-1141 + */ + @Test + public void updatePushEachWithValueSortShouldRenderCorrectly() { + + Update update = new Update().push("scores").sort(Direction.DESC).each(42, 23, 68); + + DBObject mappedObject = mapper.getMappedObject(update.getUpdateObject(), context.getPersistentEntity(Object.class)); + + DBObject push = getAsDBObject(mappedObject, "$push"); + DBObject key = getAsDBObject(push, "scores"); + + assertThat(key.containsField("$sort"), is(true)); + assertThat((Integer) key.get("$sort"), is(-1)); + assertThat(key.containsField("$each"), is(true)); + } + + /** + * @see DATAMONGO-1141 + */ + @Test + public void updatePushEachWithDocumentSortShouldRenderCorrectly() { + + Update update = new Update().push("names").sort(new Sort(new Order(Direction.ASC, "last"), new Order(Direction.ASC, "first"))) + .each(Collections.emptyList()); + + DBObject mappedObject = mapper.getMappedObject(update.getUpdateObject(), context.getPersistentEntity(Object.class)); + + DBObject push = getAsDBObject(mappedObject, "$push"); + DBObject key = getAsDBObject(push, "names"); + + assertThat(key.containsField("$sort"), is(true)); + assertThat((DBObject) key.get("$sort"), equalTo(new BasicDBObjectBuilder().add("last", 1).add("first", 1).get())); + assertThat(key.containsField("$each"), is(true)); + } + + /** + * @see DATAMONGO-1141 + */ + @Test + public void updatePushEachWithSortShouldRenderCorrectlyWhenUsingMultiplePush() { + + Update update = new Update().push("authors").sort(Direction.ASC).each("Harry") + .push("chapters").sort(new Sort(Direction.ASC, "order")).each(Collections.emptyList()); + + DBObject mappedObject = mapper.getMappedObject(update.getUpdateObject(), context.getPersistentEntity(Object.class)); + + DBObject push = getAsDBObject(mappedObject, "$push"); + DBObject key1 = getAsDBObject(push, "authors"); + + assertThat(key1.containsField("$sort"), is(true)); + assertThat((Integer) key1.get("$sort"), is(1)); + assertThat(key1.containsField("$each"), is(true)); + + DBObject key2 = getAsDBObject(push, "chapters"); + + assertThat(key2.containsField("$sort"), is(true)); + assertThat((DBObject) key2.get("$sort"), equalTo(new BasicDBObjectBuilder().add("order", 1).get())); + assertThat(key2.containsField("$each"), is(true)); + } + /** * @see DATAMONGO-410 */