Skip to content

DATACOUCH-630 Add expiry option to replace. #285

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Dec 9, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -46,6 +47,11 @@ interface ReplaceByIdWithDurability<T> extends ReplaceByIdWithCollection<T> {

}

interface ExecutableReplaceById<T> extends ReplaceByIdWithDurability<T> {}
interface ReplaceByIdWithExpiry<T> extends ReplaceByIdWithDurability<T> {

ReplaceByIdWithDurability<T> withExpiry(final Duration expiry);
}

interface ExecutableReplaceById<T> extends ReplaceByIdWithExpiry<T> {}

}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package org.springframework.data.couchbase.core;

import java.time.Duration;
import java.util.Collection;

import org.springframework.util.Assert;
Expand All @@ -35,7 +36,7 @@ public ExecutableReplaceByIdOperationSupport(final CouchbaseTemplate template) {
public <T> ExecutableReplaceById<T> replaceById(final Class<T> 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<T> implements ExecutableReplaceById<T> {
Expand All @@ -46,18 +47,21 @@ static class ExecutableReplaceByIdSupport<T> implements ExecutableReplaceById<T>
private final PersistTo persistTo;
private final ReplicateTo replicateTo;
private final DurabilityLevel durabilityLevel;
private final Duration expiry;
private final ReactiveReplaceByIdOperationSupport.ReactiveReplaceByIdSupport<T> reactiveSupport;

ExecutableReplaceByIdSupport(final CouchbaseTemplate template, final Class<T> 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
Expand All @@ -74,22 +78,29 @@ public Collection<? extends T> all(Collection<? extends T> objects) {
public TerminatingReplaceById<T> 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<T> 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
public ReplaceByIdWithCollection<T> 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 ExecutableReplaceByIdSupport<>(template, domainType, collection, persistTo, replicateTo,
durabilityLevel);
durabilityLevel, expiry);
}

@Override
public ReplaceByIdWithDurability<T> withExpiry(final Duration expiry) {
Assert.notNull(expiry, "expiry must not be null.");
return new ExecutableReplaceByIdSupport<>(template, domainType, collection, persistTo, replicateTo,
durabilityLevel, expiry);
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -49,6 +50,11 @@ interface ReplaceByIdWithDurability<T> extends ReplaceByIdWithCollection<T> {

}

interface ReactiveReplaceById<T> extends ReplaceByIdWithDurability<T> {}
interface ReplaceByIdWithExpiry<T> extends ReplaceByIdWithDurability<T> {

ReplaceByIdWithDurability<T> withExpiry(final Duration expiry);
}

interface ReactiveReplaceById<T> extends ReplaceByIdWithExpiry<T> {}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -40,7 +42,7 @@ public ReactiveReplaceByIdOperationSupport(final ReactiveCouchbaseTemplate templ
public <T> ReactiveReplaceById<T> replaceById(final Class<T> 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<T> implements ReactiveReplaceById<T> {
Expand All @@ -51,16 +53,18 @@ static class ReactiveReplaceByIdSupport<T> implements ReactiveReplaceById<T> {
private final PersistTo persistTo;
private final ReplicateTo replicateTo;
private final DurabilityLevel durabilityLevel;
private final Duration expiry;

ReactiveReplaceByIdSupport(final ReactiveCouchbaseTemplate template, final Class<T> 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
Expand Down Expand Up @@ -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());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could some of these values (like the expiry unit be null and cause a NPE if not set?)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Document annotation has a default expiryUnit of SECONDS and a default expiry of 0

options.expiry(Duration.ofSeconds(durationSeconds));
}
long cas = template.support().getCas(object);
options.cas(cas);
return options;
Expand All @@ -101,23 +112,30 @@ private ReplaceOptions buildReplaceOptions(T object) {
@Override
public TerminatingReplaceById<T> 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<T> 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<T> 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<T> withExpiry(final Duration expiry) {
Assert.notNull(expiry, "expiry must not be null.");
return new ReactiveReplaceByIdSupport<>(template, domainType, collection, persistTo, replicateTo, durabilityLevel,
expiry);
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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.
*
Expand Down Expand Up @@ -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()));
Expand Down