Use expiry(duration) with duration. (#1223)

Also adds test for exceptions thrown during events - with validator.

Closes #1204.
This commit is contained in:
Michael Reiche
2021-09-14 09:49:12 -07:00
committed by GitHub
parent 7c1167f302
commit a879d0b4e3
7 changed files with 107 additions and 23 deletions

View File

@@ -503,7 +503,7 @@ public class MappingCouchbaseConverter extends AbstractCouchbaseConverter implem
final TreeMap<Integer, String> suffixes = new TreeMap<>();
final TreeMap<Integer, String> idAttributes = new TreeMap<>();
target.setExpiration(entity.getExpiry());
target.setExpiration((int)(entity.getExpiryDuration().getSeconds()));
entity.doWithProperties(new PropertyHandler<CouchbasePersistentProperty>() {
@Override

View File

@@ -16,6 +16,8 @@
package org.springframework.data.couchbase.core.mapping;
import java.time.Duration;
import java.time.Instant;
import java.util.Calendar;
import java.util.TimeZone;
import java.util.concurrent.TimeUnit;
@@ -101,8 +103,9 @@ public class BasicCouchbasePersistentEntity<T> extends BasicPersistentEntity<T,
}
public static int getExpiry(Expiry annotation, Environment environment) {
if (annotation == null)
if (annotation == null) {
return 0;
}
int expiryValue = getExpiryValue(annotation, environment);
@@ -123,6 +126,47 @@ public class BasicCouchbasePersistentEntity<T> extends BasicPersistentEntity<T,
}
}
@Override
public Duration getExpiryDuration() {
return getExpiryDuration(AnnotatedElementUtils.findMergedAnnotation(getType(), Expiry.class), environment);
}
private static Duration getExpiryDuration(Expiry annotation, Environment environment) {
if (annotation == null) {
return Duration.ofSeconds(0);
}
int expiryValue = getExpiryValue(annotation, environment);
long secondsShift = annotation.expiryUnit().toSeconds(expiryValue);
return Duration.ofSeconds(secondsShift);
}
@Override
public Instant getExpiryInstant() {
return getExpiryInstant(AnnotatedElementUtils.findMergedAnnotation(getType(), Expiry.class), environment);
}
private static Instant getExpiryInstant(Expiry annotation, Environment environment) {
if (annotation == null) {
return Instant.ofEpochSecond(0);
}
int expiryValue = getExpiryValue(annotation, environment);
long secondsShift = annotation.expiryUnit().toSeconds(expiryValue);
if(secondsShift == 0 ){
return Instant.ofEpochSecond(0);
}
// we want it to be represented as a UNIX timestamp style, seconds since Epoch in UTC
Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
if (annotation.expiryUnit() == TimeUnit.DAYS) {
// makes sure we won't lose resolution
cal.add(Calendar.DAY_OF_MONTH, expiryValue);
} else {
// use the shift in seconds since resolution should be smaller
cal.add(Calendar.SECOND, (int) secondsShift);
}
return Instant.ofEpochSecond(cal.getTimeInMillis() / 1000); // note: Unix UTC time representation in int is okay
// until year 2038
}
private static int getExpiryValue(Expiry annotation, Environment environment) {
int expiryValue = annotation.expiry();
String expiryExpressionString = annotation.expiryExpression();

View File

@@ -18,6 +18,9 @@ package org.springframework.data.couchbase.core.mapping;
import org.springframework.data.mapping.PersistentEntity;
import java.time.Duration;
import java.time.Instant;
/**
* Represents an entity that can be persisted which contains 0 or more properties.
*
@@ -40,6 +43,26 @@ public interface CouchbasePersistentEntity<T> extends PersistentEntity<T, Couchb
*/
int getExpiry();
/**
* Returns the expiration time of the entity.
* <p/>
* The Couchbase format for expiration time is: - for TTL < 31 days (<= 30 * 24 * 60 * 60): expressed as a TTL in
* seconds - for TTL > 30 days: expressed as Unix UTC time of expiry (number of SECONDS since the Epoch)
*
* @return the expiration time Duration
*/
Duration getExpiryDuration();
/**
* Returns the expiration time of the entity.
* <p/>
* The Couchbase format for expiration time is: - for TTL < 31 days (<= 30 * 24 * 60 * 60): expressed as a TTL in
* seconds - for TTL > 30 days: expressed as Unix UTC time of expiry (number of SECONDS since the Epoch)
*
* @return the expiration time Instant
*/
Instant getExpiryInstant();
/**
* Flag for using getAndTouch operations for reads, resetting the expiration (if one was set) when the entity is
* directly read (eg. findOne, findById).

View File

@@ -27,14 +27,15 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.time.Duration;
import java.time.Instant;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import com.couchbase.client.core.error.CouchbaseException;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.dao.DataIntegrityViolationException;
@@ -47,7 +48,6 @@ import org.springframework.data.couchbase.core.support.OneAndAllId;
import org.springframework.data.couchbase.core.support.WithDurability;
import org.springframework.data.couchbase.core.support.WithExpiry;
import org.springframework.data.couchbase.domain.Address;
import org.springframework.data.couchbase.domain.Course;
import org.springframework.data.couchbase.domain.NaiveAuditorAware;
import org.springframework.data.couchbase.domain.PersonValue;
import org.springframework.data.couchbase.domain.Submission;
@@ -60,6 +60,7 @@ import org.springframework.data.couchbase.util.ClusterType;
import org.springframework.data.couchbase.util.IgnoreWhen;
import org.springframework.data.couchbase.util.JavaIntegrationTests;
import com.couchbase.client.core.error.CouchbaseException;
import com.couchbase.client.java.kv.PersistTo;
import com.couchbase.client.java.kv.ReplicateTo;
import com.couchbase.client.java.query.QueryOptions;
@@ -84,6 +85,7 @@ class CouchbaseTemplateKeyValueIntegrationTests extends JavaIntegrationTests {
couchbaseTemplate.removeByQuery(UserAnnotated.class).all();
couchbaseTemplate.removeByQuery(UserAnnotated2.class).all();
couchbaseTemplate.removeByQuery(UserAnnotated3.class).all();
couchbaseTemplate.removeByQuery(User.class).withConsistency(QueryScanConsistency.REQUEST_PLUS).all();
}
@Test
@@ -179,8 +181,8 @@ class CouchbaseTemplateKeyValueIntegrationTests extends JavaIntegrationTests {
user.setSubmissions(
Arrays.asList(new Submission(UUID.randomUUID().toString(), user.getId(), "tid", "status", 123)));
couchbaseTemplate.upsertById(UserSubmission.class).one(user);
assertThrows(CouchbaseException.class, () -> couchbaseTemplate.findByQuery(UserSubmission.class).project(new String[] { "address.street" })
.withConsistency(QueryScanConsistency.REQUEST_PLUS).all());
assertThrows(CouchbaseException.class, () -> couchbaseTemplate.findByQuery(UserSubmission.class)
.project(new String[] { "address.street" }).withConsistency(QueryScanConsistency.REQUEST_PLUS).all());
List<UserSubmission> found = couchbaseTemplate.findByQuery(UserSubmission.class).project(new String[] { "address" })
.withConsistency(QueryScanConsistency.REQUEST_PLUS).all();
@@ -282,15 +284,23 @@ class CouchbaseTemplateKeyValueIntegrationTests extends JavaIntegrationTests {
}
// check that they are gone after a few seconds.
sleepSecs(4);
List<String> errorList = new LinkedList();
for (User user : users) {
User found = couchbaseTemplate.findById(user.getClass()).one(user.getId());
if (found instanceof UserAnnotated3) {
assertNotNull(found, "found should be non null as it was set to have no expiry");
if (user.getId().endsWith(UserAnnotated3.class.getSimpleName())) {
if (found == null) {
errorList.add("\nfound should be non null as it was set to have no expiry " + user.getId() );
}
} else {
assertNull(found, "found should have been null as document should be expired");
if (found != null) {
errorList.add("\nfound should have been null as document should be expired " + user.getId());
}
}
}
if (!errorList.isEmpty()) {
throw new RuntimeException(errorList.toString());
}
}
@Test

View File

@@ -30,6 +30,7 @@ import java.time.Duration;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.UUID;
@@ -72,7 +73,7 @@ class ReactiveCouchbaseTemplateKeyValueIntegrationTests extends JavaIntegrationT
@Override
public void beforeEach() {
super.beforeEach();
List<RemoveResult> r1 = reactiveCouchbaseTemplate.removeByQuery(User.class).all().collectList().block();
List<RemoveResult> r1 = reactiveCouchbaseTemplate.removeByQuery(User.class).all().collectList().block();
List<RemoveResult> r2 = reactiveCouchbaseTemplate.removeByQuery(UserAnnotated.class).all().collectList().block();
List<RemoveResult> r3 = reactiveCouchbaseTemplate.removeByQuery(UserAnnotated2.class).all().collectList().block();
}
@@ -219,15 +220,23 @@ class ReactiveCouchbaseTemplateKeyValueIntegrationTests extends JavaIntegrationT
}
// check that they are gone after a few seconds.
sleepSecs(4);
List<String> errorList = new LinkedList();
for (User user : users) {
User found = reactiveCouchbaseTemplate.findById(user.getClass()).one(user.getId()).block();
if (found instanceof UserAnnotated3) {
assertNotNull(found, "found should be non null as it was set to have no expiry");
if (user.getId().endsWith(UserAnnotated3.class.getSimpleName())) {
if (found == null) {
errorList.add("\nfound should be non null as it was set to have no expiry " + user.getId());
}
} else {
assertNull(found, "found should have been null as document should be expired");
if (found != null) {
errorList.add("\nfound should have been null as document should be expired " + user.getId());
}
}
}
if (!errorList.isEmpty()) {
throw new RuntimeException(errorList.toString());
}
}
@Test

View File

@@ -43,7 +43,7 @@ public class BasicCouchbasePersistentEntityTests {
CouchbasePersistentEntity<DefaultExpiry> entity = new BasicCouchbasePersistentEntity<>(
ClassTypeInformation.from(DefaultExpiry.class));
assertThat(entity.getExpiry()).isEqualTo(0);
assertThat(entity.getExpiryDuration().getSeconds()).isEqualTo(0);
}
@Test
@@ -51,14 +51,14 @@ public class BasicCouchbasePersistentEntityTests {
CouchbasePersistentEntity<DefaultExpiryUnit> entity = new BasicCouchbasePersistentEntity<>(
ClassTypeInformation.from(DefaultExpiryUnit.class));
assertThat(entity.getExpiry()).isEqualTo(78);
assertThat(entity.getExpiryDuration().getSeconds()).isEqualTo(78);
}
@Test
void testLargeExpiry30DaysStillInSeconds() {
CouchbasePersistentEntity<LimitDaysExpiry> entityUnder = new BasicCouchbasePersistentEntity<>(
ClassTypeInformation.from(LimitDaysExpiry.class));
assertThat(entityUnder.getExpiry()).isEqualTo(30 * 24 * 60 * 60);
assertThat(entityUnder.getExpiryDuration().getSeconds()).isEqualTo(30 * 24 * 60 * 60);
}
@Test
@@ -66,7 +66,7 @@ public class BasicCouchbasePersistentEntityTests {
CouchbasePersistentEntity<OverLimitDaysExpiry> entityOver = new BasicCouchbasePersistentEntity<>(
ClassTypeInformation.from(OverLimitDaysExpiry.class));
int expiryOver = entityOver.getExpiry();
int expiryOver = (int)entityOver.getExpiryInstant().getEpochSecond();
Calendar expected = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
expected.add(Calendar.DAY_OF_YEAR, 31);
@@ -87,7 +87,7 @@ public class BasicCouchbasePersistentEntityTests {
ClassTypeInformation.from(OverLimitDaysExpiryExpression.class));
entityOver.setEnvironment(environment);
int expiryOver = entityOver.getExpiry();
int expiryOver = (int)entityOver.getExpiryInstant().getEpochSecond();
Calendar expected = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
expected.add(Calendar.DAY_OF_YEAR, 31);
@@ -107,7 +107,7 @@ public class BasicCouchbasePersistentEntityTests {
CouchbasePersistentEntity<OverLimitSecondsExpiry> entityOver = new BasicCouchbasePersistentEntity<>(
ClassTypeInformation.from(OverLimitSecondsExpiry.class));
int expiryOver = entityOver.getExpiry();
int expiryOver = (int)entityOver.getExpiryInstant().getEpochSecond();
Calendar expected = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
expected.add(Calendar.DAY_OF_YEAR, 31);

View File

@@ -23,6 +23,7 @@ import java.util.Optional;
import java.util.Properties;
import java.util.UUID;
import com.couchbase.client.java.query.QueryScanConsistency;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
@@ -98,11 +99,8 @@ class StringN1qlQueryCreatorTests extends ClusterAwareIntegrationTests {
Query query = creator.createQuery();
try {
Thread.sleep(3000);
} catch (Exception e) {}
ExecutableFindByQuery q = (ExecutableFindByQuery) couchbaseTemplate
.findByQuery(Airline.class).matching(query);
.findByQuery(Airline.class).withConsistency(QueryScanConsistency.REQUEST_PLUS).matching(query);
Optional<Airline> al = q.one();
assertEquals(airline.toString(), al.get().toString());