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 f613ffd50..98fc758da 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 @@ interface ReplaceByIdWithDurability extends ReplaceByIdWithCollection { } - 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 12ff3797d..1e4572cd1 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 ExecutableReplaceByIdOperationSupport(final CouchbaseTemplate template) { 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 @@ static class ExecutableReplaceByIdSupport implements ExecutableReplaceById 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 Collection all(Collection objects) { 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 ReplaceByIdWithCollection withDurability(final PersistTo persistTo, fi 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 b6ea112a9..acdf825f1 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 @@ 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 @@ interface ReplaceByIdWithDurability extends ReplaceByIdWithCollection { } - 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 0ff9038a4..dd9300bc4 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 @@ 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 ReactiveReplaceByIdOperationSupport(final ReactiveCouchbaseTemplate templ 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 @@ static class ReactiveReplaceByIdSupport implements ReactiveReplaceById { 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 @@ private ReplaceOptions buildReplaceOptions(T object) { } 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 @@ private ReplaceOptions buildReplaceOptions(T object) { @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 5ceaaa63b..e6ad975c0 100644 --- a/src/test/java/org/springframework/data/couchbase/core/CouchbaseTemplateKeyValueIntegrationTests.java +++ b/src/test/java/org/springframework/data/couchbase/core/CouchbaseTemplateKeyValueIntegrationTests.java @@ -16,24 +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.DataRetrievalFailureException; -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; @@ -45,6 +46,9 @@ 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. * @@ -142,6 +146,44 @@ void upsertWithExpiryAnnotation() { } } + @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()));