From 1ad622c045c810f9127c79d60ca1ffc0a3bb2d05 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Wed, 24 Oct 2018 14:05:23 +0200 Subject: [PATCH 1/3] DATAMONGO-2113 - Prepare issue branch. --- pom.xml | 2 +- spring-data-mongodb-benchmarks/pom.xml | 2 +- spring-data-mongodb-cross-store/pom.xml | 4 ++-- spring-data-mongodb-distribution/pom.xml | 2 +- spring-data-mongodb/pom.xml | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pom.xml b/pom.xml index 28dc5c96c1..b0281d7ece 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-mongodb-parent - 2.2.0.BUILD-SNAPSHOT + 2.2.0.DATAMONGO-2113-SNAPSHOT pom Spring Data MongoDB diff --git a/spring-data-mongodb-benchmarks/pom.xml b/spring-data-mongodb-benchmarks/pom.xml index c2ff37b35c..3293be68a0 100644 --- a/spring-data-mongodb-benchmarks/pom.xml +++ b/spring-data-mongodb-benchmarks/pom.xml @@ -7,7 +7,7 @@ org.springframework.data spring-data-mongodb-parent - 2.2.0.BUILD-SNAPSHOT + 2.2.0.DATAMONGO-2113-SNAPSHOT ../pom.xml diff --git a/spring-data-mongodb-cross-store/pom.xml b/spring-data-mongodb-cross-store/pom.xml index fd36f227c0..df5bb50c1f 100644 --- a/spring-data-mongodb-cross-store/pom.xml +++ b/spring-data-mongodb-cross-store/pom.xml @@ -6,7 +6,7 @@ org.springframework.data spring-data-mongodb-parent - 2.2.0.BUILD-SNAPSHOT + 2.2.0.DATAMONGO-2113-SNAPSHOT ../pom.xml @@ -50,7 +50,7 @@ org.springframework.data spring-data-mongodb - 2.2.0.BUILD-SNAPSHOT + 2.2.0.DATAMONGO-2113-SNAPSHOT diff --git a/spring-data-mongodb-distribution/pom.xml b/spring-data-mongodb-distribution/pom.xml index cb441dd8ef..40a0251cf1 100644 --- a/spring-data-mongodb-distribution/pom.xml +++ b/spring-data-mongodb-distribution/pom.xml @@ -13,7 +13,7 @@ org.springframework.data spring-data-mongodb-parent - 2.2.0.BUILD-SNAPSHOT + 2.2.0.DATAMONGO-2113-SNAPSHOT ../pom.xml diff --git a/spring-data-mongodb/pom.xml b/spring-data-mongodb/pom.xml index f3c85a046a..ec9596e561 100644 --- a/spring-data-mongodb/pom.xml +++ b/spring-data-mongodb/pom.xml @@ -11,7 +11,7 @@ org.springframework.data spring-data-mongodb-parent - 2.2.0.BUILD-SNAPSHOT + 2.2.0.DATAMONGO-2113-SNAPSHOT ../pom.xml From 1df22edfc21a7b9e8f3fab50216687ebd11e6531 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Thu, 25 Oct 2018 10:52:06 +0200 Subject: [PATCH 2/3] DATAMONGO-2113 - Fix resumeTimestamp conversion for change streams. We now use the first 32 bits of the timestamp to create the instant and ignore the ordinal value. --- .../data/mongodb/core/ChangeStreamEvent.java | 4 +++- .../mongodb/core/ReactiveMongoTemplate.java | 2 +- .../mongodb/core/convert/MongoConverters.java | 21 +++++++++++++++++++ .../core/messaging/ChangeStreamTask.java | 2 +- .../core/ReactiveMongoTemplateTests.java | 5 +++-- .../convert/MongoConvertersUnitTests.java | 17 +++++++++++++-- .../core/messaging/ChangeStreamTests.java | 5 ++++- 7 files changed, 48 insertions(+), 8 deletions(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ChangeStreamEvent.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ChangeStreamEvent.java index b25b3eb4fb..8f661c5af2 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ChangeStreamEvent.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ChangeStreamEvent.java @@ -83,7 +83,9 @@ public ChangeStreamDocument getRaw() { */ @Nullable public Instant getTimestamp() { - return raw != null && raw.getClusterTime() != null ? Instant.ofEpochMilli(raw.getClusterTime().getValue()) : null; + + return raw != null && raw.getClusterTime() != null + ? converter.getConversionService().convert(raw.getClusterTime(), Instant.class) : null; } /** diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoTemplate.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoTemplate.java index 67d41f04d0..81e7d821d2 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoTemplate.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoTemplate.java @@ -1915,7 +1915,7 @@ public Flux> changeStream(@Nullable String database, @N publisher = options.getResumeToken().map(BsonValue::asDocument).map(publisher::resumeAfter).orElse(publisher); publisher = options.getCollation().map(Collation::toMongoCollation).map(publisher::collation).orElse(publisher); - publisher = options.getResumeTimestamp().map(it -> new BsonTimestamp(it.toEpochMilli())) + publisher = options.getResumeTimestamp().map(it -> new BsonTimestamp((int) it.getEpochSecond(), 0)) .map(publisher::startAtOperationTime).orElse(publisher); publisher = publisher.fullDocument(options.getFullDocumentLookup().orElse(fullDocument)); diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoConverters.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoConverters.java index 83d27510a2..018eba49c5 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoConverters.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoConverters.java @@ -19,6 +19,7 @@ import java.math.BigInteger; import java.net.MalformedURLException; import java.net.URL; +import java.time.Instant; import java.util.ArrayList; import java.util.Collection; import java.util.Currency; @@ -26,6 +27,7 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; +import org.bson.BsonTimestamp; import org.bson.Document; import org.bson.types.Binary; import org.bson.types.Code; @@ -86,6 +88,7 @@ static Collection getConvertersToRegister() { converters.add(LongToAtomicLongConverter.INSTANCE); converters.add(IntegerToAtomicIntegerConverter.INSTANCE); converters.add(BinaryToByteArrayConverter.INSTANCE); + converters.add(BsonTimestampToInstantConverter.INSTANCE); return converters; } @@ -465,4 +468,22 @@ public byte[] convert(Binary source) { return source.getData(); } } + + /** + * {@link Converter} implementation converting {@link BsonTimestamp} into {@link Instant}. + * + * @author Christoph Strobl + * @since 2.1.2 + */ + @ReadingConverter + enum BsonTimestampToInstantConverter implements Converter { + + INSTANCE; + + @Nullable + @Override + public Instant convert(BsonTimestamp source) { + return Instant.ofEpochSecond(source.getTime(), 0); + } + } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/messaging/ChangeStreamTask.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/messaging/ChangeStreamTask.java index 6e887548ab..5d0ff694d7 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/messaging/ChangeStreamTask.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/messaging/ChangeStreamTask.java @@ -115,7 +115,7 @@ protected MongoCursor> initCursor(MongoTemplate t .orElseGet(() -> ClassUtils.isAssignable(Document.class, targetType) ? FullDocument.DEFAULT : FullDocument.UPDATE_LOOKUP); - startAt = changeStreamOptions.getResumeTimestamp().map(Instant::toEpochMilli).map(BsonTimestamp::new) + startAt = changeStreamOptions.getResumeTimestamp().map(it -> new BsonTimestamp((int) it.getEpochSecond(), 0)) .orElse(null); } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveMongoTemplateTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveMongoTemplateTests.java index 0324628db8..22fc49317d 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveMongoTemplateTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveMongoTemplateTests.java @@ -29,6 +29,7 @@ import reactor.core.publisher.Mono; import reactor.test.StepVerifier; +import java.time.Duration; import java.time.Instant; import java.util.Arrays; import java.util.Collections; @@ -1355,7 +1356,7 @@ public void watchesDatabaseCorrectly() throws InterruptedException { } } - @Test // DATAMONGO-2012 + @Test // DATAMONGO-2012, DATAMONGO-2113 public void resumesAtTimestampCorrectly() throws InterruptedException { Assumptions.assumeThat(ReplicaSet.required().runsAsReplicaSet()).isTrue(); @@ -1372,7 +1373,7 @@ public void resumesAtTimestampCorrectly() throws InterruptedException { Person person2 = new Person("Data", 37); Person person3 = new Person("MongoDB", 39); - StepVerifier.create(template.save(person1)).expectNextCount(1).verifyComplete(); + StepVerifier.create(template.save(person1).delayElement(Duration.ofSeconds(1))).expectNextCount(1).verifyComplete(); StepVerifier.create(template.save(person2)).expectNextCount(1).verifyComplete(); Thread.sleep(500); // just give it some time to link receive all events diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MongoConvertersUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MongoConvertersUnitTests.java index 753f033caa..8e5d0bb15d 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MongoConvertersUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MongoConvertersUnitTests.java @@ -19,10 +19,15 @@ import static org.junit.Assert.*; import java.math.BigDecimal; +import java.time.Instant; +import java.time.temporal.ChronoUnit; import java.util.Currency; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; +import org.assertj.core.data.TemporalUnitLessThanOffset; +import org.bson.BsonTimestamp; +import org.bson.Document; import org.junit.Test; import org.springframework.data.geo.Box; import org.springframework.data.geo.Circle; @@ -32,14 +37,14 @@ import org.springframework.data.mongodb.core.convert.MongoConverters.AtomicIntegerToIntegerConverter; import org.springframework.data.mongodb.core.convert.MongoConverters.AtomicLongToLongConverter; import org.springframework.data.mongodb.core.convert.MongoConverters.BigDecimalToStringConverter; +import org.springframework.data.mongodb.core.convert.MongoConverters.BsonTimestampToInstantConverter; import org.springframework.data.mongodb.core.convert.MongoConverters.CurrencyToStringConverter; import org.springframework.data.mongodb.core.convert.MongoConverters.IntegerToAtomicIntegerConverter; import org.springframework.data.mongodb.core.convert.MongoConverters.LongToAtomicLongConverter; import org.springframework.data.mongodb.core.convert.MongoConverters.StringToBigDecimalConverter; import org.springframework.data.mongodb.core.convert.MongoConverters.StringToCurrencyConverter; import org.springframework.data.mongodb.core.geo.Sphere; - -import org.bson.Document; +import org.springframework.data.mongodb.test.util.Assertions; /** * Unit tests for {@link MongoConverters}. @@ -145,4 +150,12 @@ public void convertsLongToAtomicLongCorrectly() { public void convertsIntegerToAtomicIntegerCorrectly() { assertThat(IntegerToAtomicIntegerConverter.INSTANCE.convert(100), is(instanceOf(AtomicInteger.class))); } + + @Test // DATAMONGO-2113 + public void convertsBsonTimestampToInstantCorrectly() { + + Assertions.assertThat(BsonTimestampToInstantConverter.INSTANCE.convert(new BsonTimestamp(6615900307735969796L))) + .isCloseTo(Instant.ofEpochSecond(1540384327), new TemporalUnitLessThanOffset(100, ChronoUnit.MILLIS)); + } + } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/messaging/ChangeStreamTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/messaging/ChangeStreamTests.java index c76db2b593..a1d3361c08 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/messaging/ChangeStreamTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/messaging/ChangeStreamTests.java @@ -405,7 +405,7 @@ public void readsFullDocumentForUpdateWhenNotMappedToDomainTypeButLookupSpecifie .append("user_name", "jellyBelly").append("age", 8).append("_class", User.class.getName())); } - @Test // DATAMONGO-2012 + @Test // DATAMONGO-2012, DATAMONGO-2113 public void resumeAtTimestampCorrectly() throws InterruptedException { CollectingMessageListener, User> messageListener1 = new CollectingMessageListener<>(); @@ -415,6 +415,9 @@ public void resumeAtTimestampCorrectly() throws InterruptedException { awaitSubscription(subscription1); template.save(jellyBelly); + + Thread.sleep(1000); // cluster timestamp is in seconds, so we need to wait at least one. + template.save(sugarSplashy); awaitMessages(messageListener1, 12); From b1759db926e3b5d6720d61ea4d7f0f6255673ab1 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Thu, 25 Oct 2018 10:57:03 +0200 Subject: [PATCH 3/3] DATAMONGO-2113 - Polishing. Use AssertJ in tests. --- .../convert/MongoConvertersUnitTests.java | 34 +++++++++---------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MongoConvertersUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MongoConvertersUnitTests.java index 8e5d0bb15d..78e990ae9a 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MongoConvertersUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MongoConvertersUnitTests.java @@ -15,8 +15,7 @@ */ package org.springframework.data.mongodb.core.convert; -import static org.hamcrest.Matchers.*; -import static org.junit.Assert.*; +import static org.assertj.core.api.Assertions.*; import java.math.BigDecimal; import java.time.Instant; @@ -44,7 +43,6 @@ import org.springframework.data.mongodb.core.convert.MongoConverters.StringToBigDecimalConverter; import org.springframework.data.mongodb.core.convert.MongoConverters.StringToCurrencyConverter; import org.springframework.data.mongodb.core.geo.Sphere; -import org.springframework.data.mongodb.test.util.Assertions; /** * Unit tests for {@link MongoConverters}. @@ -60,10 +58,10 @@ public void convertsBigDecimalToStringAndBackCorrectly() { BigDecimal bigDecimal = BigDecimal.valueOf(254, 1); String value = BigDecimalToStringConverter.INSTANCE.convert(bigDecimal); - assertThat(value, is("25.4")); + assertThat(value).isEqualTo("25.4"); BigDecimal reference = StringToBigDecimalConverter.INSTANCE.convert(value); - assertThat(reference, is(bigDecimal)); + assertThat(reference).isEqualTo(bigDecimal); } @Test // DATAMONGO-858 @@ -74,7 +72,7 @@ public void convertsBoxToDocumentAndBackCorrectly() { Document document = GeoConverters.BoxToDocumentConverter.INSTANCE.convert(box); Shape shape = GeoConverters.DocumentToBoxConverter.INSTANCE.convert(document); - assertThat(shape, is((org.springframework.data.geo.Shape) box)); + assertThat(shape).isEqualTo(box); } @Test // DATAMONGO-858 @@ -85,7 +83,7 @@ public void convertsCircleToDocumentAndBackCorrectly() { Document document = GeoConverters.CircleToDocumentConverter.INSTANCE.convert(circle); Shape shape = GeoConverters.DocumentToCircleConverter.INSTANCE.convert(document); - assertThat(shape, is((org.springframework.data.geo.Shape) circle)); + assertThat(shape).isEqualTo(circle); } @Test // DATAMONGO-858 @@ -96,7 +94,7 @@ public void convertsPolygonToDocumentAndBackCorrectly() { Document document = GeoConverters.PolygonToDocumentConverter.INSTANCE.convert(polygon); Shape shape = GeoConverters.DocumentToPolygonConverter.INSTANCE.convert(document); - assertThat(shape, is((org.springframework.data.geo.Shape) polygon)); + assertThat(shape).isEqualTo(polygon); } @Test // DATAMONGO-858 @@ -107,7 +105,7 @@ public void convertsSphereToDocumentAndBackCorrectly() { Document document = GeoConverters.SphereToDocumentConverter.INSTANCE.convert(sphere); org.springframework.data.geo.Shape shape = GeoConverters.DocumentToSphereConverter.INSTANCE.convert(document); - assertThat(shape, is((org.springframework.data.geo.Shape) sphere)); + assertThat(shape).isEqualTo(sphere); } @Test // DATAMONGO-858 @@ -118,43 +116,43 @@ public void convertsPointToListAndBackCorrectly() { Document document = GeoConverters.PointToDocumentConverter.INSTANCE.convert(point); org.springframework.data.geo.Point converted = GeoConverters.DocumentToPointConverter.INSTANCE.convert(document); - assertThat(converted, is((org.springframework.data.geo.Point) point)); + assertThat(converted).isEqualTo(point); } @Test // DATAMONGO-1372 public void convertsCurrencyToStringCorrectly() { - assertThat(CurrencyToStringConverter.INSTANCE.convert(Currency.getInstance("USD")), is("USD")); + assertThat(CurrencyToStringConverter.INSTANCE.convert(Currency.getInstance("USD"))).isEqualTo("USD"); } @Test // DATAMONGO-1372 public void convertsStringToCurrencyCorrectly() { - assertThat(StringToCurrencyConverter.INSTANCE.convert("USD"), is(Currency.getInstance("USD"))); + assertThat(StringToCurrencyConverter.INSTANCE.convert("USD")).isEqualTo(Currency.getInstance("USD")); } @Test // DATAMONGO-1416 public void convertsAtomicLongToLongCorrectly() { - assertThat(AtomicLongToLongConverter.INSTANCE.convert(new AtomicLong(100L)), is(100L)); + assertThat(AtomicLongToLongConverter.INSTANCE.convert(new AtomicLong(100L))).isEqualTo(100L); } @Test // DATAMONGO-1416 public void convertsAtomicIntegerToIntegerCorrectly() { - assertThat(AtomicIntegerToIntegerConverter.INSTANCE.convert(new AtomicInteger(100)), is(100)); + assertThat(AtomicIntegerToIntegerConverter.INSTANCE.convert(new AtomicInteger(100))).isEqualTo(100); } @Test // DATAMONGO-1416 public void convertsLongToAtomicLongCorrectly() { - assertThat(LongToAtomicLongConverter.INSTANCE.convert(100L), is(instanceOf(AtomicLong.class))); + assertThat(LongToAtomicLongConverter.INSTANCE.convert(100L)).isInstanceOf(AtomicLong.class); } @Test // DATAMONGO-1416 public void convertsIntegerToAtomicIntegerCorrectly() { - assertThat(IntegerToAtomicIntegerConverter.INSTANCE.convert(100), is(instanceOf(AtomicInteger.class))); + assertThat(IntegerToAtomicIntegerConverter.INSTANCE.convert(100)).isInstanceOf(AtomicInteger.class); } @Test // DATAMONGO-2113 public void convertsBsonTimestampToInstantCorrectly() { - - Assertions.assertThat(BsonTimestampToInstantConverter.INSTANCE.convert(new BsonTimestamp(6615900307735969796L))) + + assertThat(BsonTimestampToInstantConverter.INSTANCE.convert(new BsonTimestamp(6615900307735969796L))) .isCloseTo(Instant.ofEpochSecond(1540384327), new TemporalUnitLessThanOffset(100, ChronoUnit.MILLIS)); }