From 31d443456277ae1864405dd617877a8f1fec123c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Vodra=CC=81z=CC=8Cka?= Date: Wed, 2 Nov 2016 18:13:24 +0100 Subject: [PATCH] DATAMONGO-1141 - Add support for $push $sort in Update. Sorting update modifier added. Supports sorting arrays by document fields and element values. Original pull request: #405. --- .../data/mongodb/core/query/Update.java | 86 +++++++++++++++++++ .../core/convert/UpdateMapperUnitTests.java | 82 ++++++++++++++++-- 2 files changed, 160 insertions(+), 8 deletions(-) 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 324dc9cfc..9e0486880 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 @@ -28,6 +28,9 @@ import java.util.Set; import org.bson.Document; 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; @@ -42,6 +45,7 @@ import org.springframework.util.StringUtils; * @author Thomas Darimont * @author Alexey Plotnik * @author Mark Paluch + * @author Pavel Vodrazka */ public class Update { @@ -659,6 +663,58 @@ public class Update { } } + /** + * 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 Document createDBObject(Sort sort) { + + Document obj = new Document(); + + 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 * @@ -705,6 +761,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 58c7ea4e3..2de45a656 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 @@ -41,6 +41,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.DocumentTestUtils; @@ -60,6 +63,7 @@ import com.mongodb.DBRef; * @author Christoph Strobl * @author Thomas Darimont * @author Mark Paluch + * @author Pavel Vodrazka */ @RunWith(MockitoJUnitRunner.class) public class UpdateMapperUnitTests { @@ -398,13 +402,76 @@ public class UpdateMapperUnitTests { Document key = getAsDocument(push, "key"); assertThat(key.containsKey("$slice"), is(true)); - assertThat(key.get("$slice"), is(5)); + assertThat((Integer) key.get("$slice"), is(5)); assertThat(key.containsKey("$each"), is(true)); Document key2 = getAsDocument(push, "key-2"); assertThat(key2.containsKey("$slice"), is(true)); - assertThat(key2.get("$slice"), is(-2)); + assertThat((Integer) key2.get("$slice"), is(-2)); + assertThat(key2.containsKey("$each"), is(true)); + } + + /** + * @see DATAMONGO-1141 + */ + @Test + public void updatePushEachWithValueSortShouldRenderCorrectly() { + + Update update = new Update().push("scores").sort(Direction.DESC).each(42, 23, 68); + + Document mappedObject = mapper.getMappedObject(update.getUpdateObject(), context.getPersistentEntity(Object.class)); + + Document push = getAsDocument(mappedObject, "$push"); + Document key = getAsDocument(push, "scores"); + + assertThat(key.containsKey("$sort"), is(true)); + assertThat((Integer) key.get("$sort"), is(-1)); + assertThat(key.containsKey("$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()); + + Document mappedObject = mapper.getMappedObject(update.getUpdateObject(), context.getPersistentEntity(Object.class)); + + Document push = getAsDocument(mappedObject, "$push"); + Document key = getAsDocument(push, "names"); + + assertThat(key.containsKey("$sort"), is(true)); + assertThat((Document) key.get("$sort"), equalTo(new Document("last", 1).append("first", 1))); + assertThat(key.containsKey("$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()); + + Document mappedObject = mapper.getMappedObject(update.getUpdateObject(), context.getPersistentEntity(Object.class)); + + Document push = getAsDocument(mappedObject, "$push"); + Document key1 = getAsDocument(push, "authors"); + + assertThat(key1.containsKey("$sort"), is(true)); + assertThat((Integer) key1.get("$sort"), is(1)); + assertThat(key1.containsKey("$each"), is(true)); + + Document key2 = getAsDocument(push, "chapters"); + + assertThat(key2.containsKey("$sort"), is(true)); + assertThat((Document) key2.get("$sort"), equalTo(new Document("order", 1))); assertThat(key2.containsKey("$each"), is(true)); } @@ -790,8 +857,7 @@ public class UpdateMapperUnitTests { @Test public void mappingShouldNotContainTypeInformationWhenValueTypeOfMapMatchesDeclaration() { - Map map = Collections.singletonMap("jasnah", - new NestedDocument("kholin")); + Map map = Collections.singletonMap("jasnah", new NestedDocument("kholin")); Update update = new Update().set("concreteMap", map); Document mappedUpdate = mapper.getMappedObject(update.getUpdateObject(), @@ -808,8 +874,8 @@ public class UpdateMapperUnitTests { @SuppressWarnings("unchecked") public void mapsUpdateWithBothReadingAndWritingConverterRegistered() { - CustomConversions conversions = new CustomConversions( - Arrays.asList(ClassWithEnum.AllocationToStringConverter.INSTANCE, ClassWithEnum.StringToAllocationConverter.INSTANCE)); + CustomConversions conversions = new CustomConversions(Arrays.asList( + ClassWithEnum.AllocationToStringConverter.INSTANCE, ClassWithEnum.StringToAllocationConverter.INSTANCE)); MongoMappingContext mappingContext = new MongoMappingContext(); mappingContext.setSimpleTypeHolder(conversions.getSimpleTypeHolder()); @@ -953,8 +1019,8 @@ public class UpdateMapperUnitTests { @SuppressWarnings("unchecked") public void mappingShouldConsiderCustomConvertersForEnumMapKeys() { - CustomConversions conversions = new CustomConversions( - Arrays.asList(ClassWithEnum.AllocationToStringConverter.INSTANCE, ClassWithEnum.StringToAllocationConverter.INSTANCE)); + CustomConversions conversions = new CustomConversions(Arrays.asList( + ClassWithEnum.AllocationToStringConverter.INSTANCE, ClassWithEnum.StringToAllocationConverter.INSTANCE)); MongoMappingContext mappingContext = new MongoMappingContext(); mappingContext.setSimpleTypeHolder(conversions.getSimpleTypeHolder());