From bce764c7123a3f44d1aa0dbb00ce1bbd06fbbbac Mon Sep 17 00:00:00 2001 From: Michael Reiche <48999328+mikereiche@users.noreply.github.com> Date: Wed, 9 Dec 2020 07:56:08 -0800 Subject: [PATCH] DATACOUCH-630 - Add expiry to replace 4.2.x (#287) Co-authored-by: mikereiche --- .../core/ExecutableReplaceByIdOperation.java | 8 ++- ...ExecutableReplaceByIdOperationSupport.java | 23 ++++++-- .../core/ReactiveReplaceByIdOperation.java | 8 ++- .../ReactiveReplaceByIdOperationSupport.java | 34 +++++++++--- ...hbaseTemplateKeyValueIntegrationTests.java | 55 +++++++++++++++++-- 5 files changed, 106 insertions(+), 22 deletions(-) diff --git a/src/main/java/org/springframework/data/couchbase/core/ExecutableReplaceByIdOperation.java b/src/main/java/org/springframework/data/couchbase/core/ExecutableReplaceByIdOperation.java index f613ffd5..98fc758d 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ExecutableReplaceByIdOperation.java +++ b/src/main/java/org/springframework/data/couchbase/core/ExecutableReplaceByIdOperation.java @@ -15,6 +15,7 @@ */ package org.springframework.data.couchbase.core; +import java.time.Duration; import java.util.Collection; import com.couchbase.client.core.msg.kv.DurabilityLevel; @@ -46,6 +47,11 @@ public interface ExecutableReplaceByIdOperation { } - interface ExecutableReplaceById extends ReplaceByIdWithDurability {} + interface ReplaceByIdWithExpiry extends ReplaceByIdWithDurability { + + ReplaceByIdWithDurability withExpiry(final Duration expiry); + } + + interface ExecutableReplaceById extends ReplaceByIdWithExpiry {} } diff --git a/src/main/java/org/springframework/data/couchbase/core/ExecutableReplaceByIdOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ExecutableReplaceByIdOperationSupport.java index 12ff3797..1e4572cd 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ExecutableReplaceByIdOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ExecutableReplaceByIdOperationSupport.java @@ -15,6 +15,7 @@ */ package org.springframework.data.couchbase.core; +import java.time.Duration; import java.util.Collection; import org.springframework.util.Assert; @@ -35,7 +36,7 @@ public class ExecutableReplaceByIdOperationSupport implements ExecutableReplaceB public ExecutableReplaceById replaceById(final Class domainType) { Assert.notNull(domainType, "DomainType must not be null!"); return new ExecutableReplaceByIdSupport<>(template, domainType, null, PersistTo.NONE, ReplicateTo.NONE, - DurabilityLevel.NONE); + DurabilityLevel.NONE, Duration.ZERO); } static class ExecutableReplaceByIdSupport implements ExecutableReplaceById { @@ -46,18 +47,21 @@ public class ExecutableReplaceByIdOperationSupport implements ExecutableReplaceB private final PersistTo persistTo; private final ReplicateTo replicateTo; private final DurabilityLevel durabilityLevel; + private final Duration expiry; private final ReactiveReplaceByIdOperationSupport.ReactiveReplaceByIdSupport reactiveSupport; ExecutableReplaceByIdSupport(final CouchbaseTemplate template, final Class domainType, final String collection, - final PersistTo persistTo, final ReplicateTo replicateTo, final DurabilityLevel durabilityLevel) { + final PersistTo persistTo, final ReplicateTo replicateTo, final DurabilityLevel durabilityLevel, + final Duration expiry) { this.template = template; this.domainType = domainType; this.collection = collection; this.persistTo = persistTo; this.replicateTo = replicateTo; this.durabilityLevel = durabilityLevel; + this.expiry = expiry; this.reactiveSupport = new ReactiveReplaceByIdOperationSupport.ReactiveReplaceByIdSupport<>(template.reactive(), - domainType, collection, persistTo, replicateTo, durabilityLevel); + domainType, collection, persistTo, replicateTo, durabilityLevel, expiry); } @Override @@ -74,14 +78,14 @@ public class ExecutableReplaceByIdOperationSupport implements ExecutableReplaceB public TerminatingReplaceById inCollection(final String collection) { Assert.hasText(collection, "Collection must not be null nor empty."); return new ExecutableReplaceByIdSupport<>(template, domainType, collection, persistTo, replicateTo, - durabilityLevel); + durabilityLevel, expiry); } @Override public ReplaceByIdWithCollection withDurability(final DurabilityLevel durabilityLevel) { Assert.notNull(durabilityLevel, "Durability Level must not be null."); return new ExecutableReplaceByIdSupport<>(template, domainType, collection, persistTo, replicateTo, - durabilityLevel); + durabilityLevel, expiry); } @Override @@ -89,7 +93,14 @@ public class ExecutableReplaceByIdOperationSupport implements ExecutableReplaceB Assert.notNull(persistTo, "PersistTo must not be null."); Assert.notNull(replicateTo, "ReplicateTo must not be null."); return new ExecutableReplaceByIdSupport<>(template, domainType, collection, persistTo, replicateTo, - durabilityLevel); + durabilityLevel, expiry); + } + + @Override + public ReplaceByIdWithDurability withExpiry(final Duration expiry) { + Assert.notNull(expiry, "expiry must not be null."); + return new ExecutableReplaceByIdSupport<>(template, domainType, collection, persistTo, replicateTo, + durabilityLevel, expiry); } } diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveReplaceByIdOperation.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveReplaceByIdOperation.java index b6ea112a..acdf825f 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveReplaceByIdOperation.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveReplaceByIdOperation.java @@ -18,6 +18,7 @@ package org.springframework.data.couchbase.core; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import java.time.Duration; import java.util.Collection; import com.couchbase.client.core.msg.kv.DurabilityLevel; @@ -49,6 +50,11 @@ public interface ReactiveReplaceByIdOperation { } - interface ReactiveReplaceById extends ReplaceByIdWithDurability {} + interface ReplaceByIdWithExpiry extends ReplaceByIdWithDurability { + + ReplaceByIdWithDurability withExpiry(final Duration expiry); + } + + interface ReactiveReplaceById extends ReplaceByIdWithExpiry {} } diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveReplaceByIdOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveReplaceByIdOperationSupport.java index 0ff9038a..dd9300bc 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveReplaceByIdOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveReplaceByIdOperationSupport.java @@ -18,9 +18,11 @@ package org.springframework.data.couchbase.core; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import java.time.Duration; import java.util.Collection; import org.springframework.data.couchbase.core.mapping.CouchbaseDocument; +import org.springframework.data.couchbase.core.mapping.Document; import org.springframework.util.Assert; import com.couchbase.client.core.msg.kv.DurabilityLevel; @@ -40,7 +42,7 @@ public class ReactiveReplaceByIdOperationSupport implements ReactiveReplaceByIdO public ReactiveReplaceById replaceById(final Class domainType) { Assert.notNull(domainType, "DomainType must not be null!"); return new ReactiveReplaceByIdSupport<>(template, domainType, null, PersistTo.NONE, ReplicateTo.NONE, - DurabilityLevel.NONE); + DurabilityLevel.NONE, Duration.ZERO); } static class ReactiveReplaceByIdSupport implements ReactiveReplaceById { @@ -51,16 +53,18 @@ public class ReactiveReplaceByIdOperationSupport implements ReactiveReplaceByIdO private final PersistTo persistTo; private final ReplicateTo replicateTo; private final DurabilityLevel durabilityLevel; + private final Duration expiry; ReactiveReplaceByIdSupport(final ReactiveCouchbaseTemplate template, final Class domainType, final String collection, final PersistTo persistTo, final ReplicateTo replicateTo, - final DurabilityLevel durabilityLevel) { + final DurabilityLevel durabilityLevel, final Duration expiry) { this.template = template; this.domainType = domainType; this.collection = collection; this.persistTo = persistTo; this.replicateTo = replicateTo; this.durabilityLevel = durabilityLevel; + this.expiry = expiry; } @Override @@ -93,6 +97,13 @@ public class ReactiveReplaceByIdOperationSupport implements ReactiveReplaceByIdO } else if (durabilityLevel != DurabilityLevel.NONE) { options.durability(durabilityLevel); } + if (expiry != null && !expiry.isZero()) { + options.expiry(expiry); + } else if (domainType.isAnnotationPresent(Document.class)) { + Document documentAnn = domainType.getAnnotation(Document.class); + long durationSeconds = documentAnn.expiryUnit().toSeconds(documentAnn.expiry()); + options.expiry(Duration.ofSeconds(durationSeconds)); + } long cas = template.support().getCas(object); options.cas(cas); return options; @@ -101,23 +112,30 @@ public class ReactiveReplaceByIdOperationSupport implements ReactiveReplaceByIdO @Override public TerminatingReplaceById inCollection(final String collection) { Assert.hasText(collection, "Collection must not be null nor empty."); - return new ReactiveReplaceByIdSupport<>(template, domainType, collection, persistTo, replicateTo, - durabilityLevel); + return new ReactiveReplaceByIdSupport<>(template, domainType, collection, persistTo, replicateTo, durabilityLevel, + expiry); } @Override public ReplaceByIdWithCollection withDurability(final DurabilityLevel durabilityLevel) { Assert.notNull(durabilityLevel, "Durability Level must not be null."); - return new ReactiveReplaceByIdSupport<>(template, domainType, collection, persistTo, replicateTo, - durabilityLevel); + return new ReactiveReplaceByIdSupport<>(template, domainType, collection, persistTo, replicateTo, durabilityLevel, + expiry); } @Override public ReplaceByIdWithCollection withDurability(final PersistTo persistTo, final ReplicateTo replicateTo) { Assert.notNull(persistTo, "PersistTo must not be null."); Assert.notNull(replicateTo, "ReplicateTo must not be null."); - return new ReactiveReplaceByIdSupport<>(template, domainType, collection, persistTo, replicateTo, - durabilityLevel); + return new ReactiveReplaceByIdSupport<>(template, domainType, collection, persistTo, replicateTo, durabilityLevel, + expiry); + } + + @Override + public ReplaceByIdWithDurability withExpiry(final Duration expiry) { + Assert.notNull(expiry, "expiry must not be null."); + return new ReactiveReplaceByIdSupport<>(template, domainType, collection, persistTo, replicateTo, durabilityLevel, + expiry); } } diff --git a/src/test/java/org/springframework/data/couchbase/core/CouchbaseTemplateKeyValueIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/core/CouchbaseTemplateKeyValueIntegrationTests.java index 9f0aa782..e6ad975c 100644 --- a/src/test/java/org/springframework/data/couchbase/core/CouchbaseTemplateKeyValueIntegrationTests.java +++ b/src/test/java/org/springframework/data/couchbase/core/CouchbaseTemplateKeyValueIntegrationTests.java @@ -16,23 +16,25 @@ package org.springframework.data.couchbase.core; -import static org.junit.jupiter.api.Assertions.*; -import static org.springframework.data.couchbase.config.BeanNames.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.springframework.data.couchbase.config.BeanNames.COUCHBASE_TEMPLATE; +import static org.springframework.data.couchbase.config.BeanNames.REACTIVE_COUCHBASE_TEMPLATE; import java.io.IOException; import java.time.Duration; import java.util.UUID; -import com.couchbase.client.core.error.DocumentNotFoundException; -import com.couchbase.client.java.kv.PersistTo; -import com.couchbase.client.java.kv.ReplicateTo; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; -import org.springframework.dao.DataIntegrityViolationException;; +import org.springframework.dao.DataIntegrityViolationException; import org.springframework.dao.DataRetrievalFailureException; import org.springframework.dao.DuplicateKeyException; import org.springframework.data.couchbase.CouchbaseClientFactory; @@ -44,6 +46,9 @@ import org.springframework.data.couchbase.util.ClusterAwareIntegrationTests; import org.springframework.data.couchbase.util.ClusterType; import org.springframework.data.couchbase.util.IgnoreWhen; +import com.couchbase.client.java.kv.PersistTo; +import com.couchbase.client.java.kv.ReplicateTo; + /** * KV tests Theses tests rely on a cb server running. * @@ -141,6 +146,44 @@ class CouchbaseTemplateKeyValueIntegrationTests extends ClusterAwareIntegrationT } } + @Test + void replaceWithExpiry() { + User user = new User(UUID.randomUUID().toString(), "firstname", "lastname"); + try { + User modified = couchbaseTemplate.upsertById(User.class).withExpiry(Duration.ofSeconds(1)).one(user); + couchbaseTemplate.replaceById(User.class).withExpiry(Duration.ofSeconds(1)).one(user); + assertEquals(user, modified); + sleepSecs(2); + User found = couchbaseTemplate.findById(User.class).one(user.getId()); + assertNull(found, "found should have been null as document should be expired"); + } finally { + try { + couchbaseTemplate.removeById().one(user.getId()); + } catch (DataRetrievalFailureException e) { + // + } + } + } + + @Test + void replaceWithExpiryAnnotation() { + UserAnnotated user = new UserAnnotated(UUID.randomUUID().toString(), "firstname", "lastname"); + try { + UserAnnotated modified = couchbaseTemplate.upsertById(UserAnnotated.class).one(user); + modified = couchbaseTemplate.replaceById(UserAnnotated.class).one(user); + assertEquals(user, modified); + sleepSecs(6); + User found = couchbaseTemplate.findById(UserAnnotated.class).one(user.getId()); + assertNull(found, "found should have been null as document should be expired"); + } finally { + try { + couchbaseTemplate.removeById().one(user.getId()); + } catch (DataRetrievalFailureException e) { + // + } + } + } + @Test void findDocWhichDoesNotExist() { assertNull(couchbaseTemplate.findById(User.class).one(UUID.randomUUID().toString()));