From e4b103cdfabff09e07db5ad5422ecfd0640b549b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20Basl=C3=A9?= Date: Wed, 14 Oct 2015 10:43:01 +0200 Subject: [PATCH] DATACOUCH-158 - Allow more explicit expiry unit The Document annotation now also support explicitly giving a TimeUnit for the expiry, ensuring the framework will correctly convert that to the expected Couchbase format. --- .../BasicCouchbasePersistentEntity.java | 29 +++- .../core/mapping/CouchbaseDocument.java | 3 + .../mapping/CouchbasePersistentEntity.java | 13 +- .../data/couchbase/core/mapping/Document.java | 8 +- .../BasicCouchbasePersistentEntityTests.java | 140 ++++++++++++++++++ 5 files changed, 184 insertions(+), 9 deletions(-) create mode 100644 src/test/java/org/springframework/data/couchbase/core/mapping/BasicCouchbasePersistentEntityTests.java diff --git a/src/main/java/org/springframework/data/couchbase/core/mapping/BasicCouchbasePersistentEntity.java b/src/main/java/org/springframework/data/couchbase/core/mapping/BasicCouchbasePersistentEntity.java index df697fac..10c8cb46 100644 --- a/src/main/java/org/springframework/data/couchbase/core/mapping/BasicCouchbasePersistentEntity.java +++ b/src/main/java/org/springframework/data/couchbase/core/mapping/BasicCouchbasePersistentEntity.java @@ -16,6 +16,10 @@ package org.springframework.data.couchbase.core.mapping; +import java.util.Calendar; +import java.util.TimeZone; +import java.util.concurrent.TimeUnit; + import com.couchbase.client.java.repository.annotation.Id; import org.springframework.beans.BeansException; @@ -95,15 +99,28 @@ public class BasicCouchbasePersistentEntity extends BasicPersistentEntity TTL_IN_SECONDS_INCLUSIVE_END) { + //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, annotation.expiry()); + } else { + //use the shift in seconds since resolution should be smaller + cal.add(Calendar.SECOND, (int) secondsShift); + } + return (int) (cal.getTimeInMillis() / 1000); //note: Unix UTC time representation in int is okay until year 2038 + } else { + return (int) secondsShift; + } } } diff --git a/src/main/java/org/springframework/data/couchbase/core/mapping/CouchbaseDocument.java b/src/main/java/org/springframework/data/couchbase/core/mapping/CouchbaseDocument.java index 07a1de7f..77cc386c 100644 --- a/src/main/java/org/springframework/data/couchbase/core/mapping/CouchbaseDocument.java +++ b/src/main/java/org/springframework/data/couchbase/core/mapping/CouchbaseDocument.java @@ -225,6 +225,9 @@ public class CouchbaseDocument implements CouchbaseStorable { *

* If the expiration time is 0, then the document will be persisted until * deleted manually ("forever"). + *

+ * Expiration should be expressed as seconds if <= 30 days (30 x 24 x 60 x 60 seconds), + * or as an expiry date (UTC, UNIX time ie. seconds form the Epoch) if > 30 days. * * @param expiration * @return the {@link CouchbaseDocument} for chaining. diff --git a/src/main/java/org/springframework/data/couchbase/core/mapping/CouchbasePersistentEntity.java b/src/main/java/org/springframework/data/couchbase/core/mapping/CouchbasePersistentEntity.java index cfaf7e5e..d5f09020 100644 --- a/src/main/java/org/springframework/data/couchbase/core/mapping/CouchbasePersistentEntity.java +++ b/src/main/java/org/springframework/data/couchbase/core/mapping/CouchbasePersistentEntity.java @@ -27,9 +27,18 @@ public interface CouchbasePersistentEntity extends PersistentEntity { /** - * Returns the expiry time for the document. + * The threshold (inclusive) after which expiry should be expressed as a Unix UTC time. + */ + long TTL_IN_SECONDS_INCLUSIVE_END = 30 * 24 * 60 * 60; + + /** + * Returns the expiration time of the entity. + *

+ * 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. + * @return the expiration time in correct Couchbase format. */ int getExpiry(); diff --git a/src/main/java/org/springframework/data/couchbase/core/mapping/Document.java b/src/main/java/org/springframework/data/couchbase/core/mapping/Document.java index 4dff8e6b..a1b6856f 100644 --- a/src/main/java/org/springframework/data/couchbase/core/mapping/Document.java +++ b/src/main/java/org/springframework/data/couchbase/core/mapping/Document.java @@ -21,6 +21,7 @@ import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import java.util.concurrent.TimeUnit; import org.springframework.data.annotation.Persistent; @@ -36,8 +37,13 @@ import org.springframework.data.annotation.Persistent; public @interface Document { /** - * An optional expiry time for the document. + * An optional expiry time for the document. Default is no expiry. */ int expiry() default 0; + /** + * An optional time unit for the document's {@link #expiry()}, if set. Default is {@link TimeUnit#SECONDS}. + */ + TimeUnit expiryUnit() default TimeUnit.SECONDS; + } diff --git a/src/test/java/org/springframework/data/couchbase/core/mapping/BasicCouchbasePersistentEntityTests.java b/src/test/java/org/springframework/data/couchbase/core/mapping/BasicCouchbasePersistentEntityTests.java new file mode 100644 index 00000000..38ce03c6 --- /dev/null +++ b/src/test/java/org/springframework/data/couchbase/core/mapping/BasicCouchbasePersistentEntityTests.java @@ -0,0 +1,140 @@ +/* + * Copyright 2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.data.couchbase.core.mapping; + +import static org.junit.Assert.assertEquals; + +import java.util.Calendar; +import java.util.Date; +import java.util.TimeZone; +import java.util.concurrent.TimeUnit; + +import org.junit.Test; + +import org.springframework.data.util.ClassTypeInformation; + +/** + * Verifies the correct behavior of annotation at the class level on persistable objects. + * + * @author Simon Baslé + */ +public class BasicCouchbasePersistentEntityTests { + + @Test + public void testNoExpiryByDefault() { + CouchbasePersistentEntity entity = new BasicCouchbasePersistentEntity( + ClassTypeInformation.from(DefaultExpiry.class)); + + assertEquals(0, entity.getExpiry()); + } + + @Test + public void testDefaultExpiryUnitIsSeconds() { + CouchbasePersistentEntity entity = new BasicCouchbasePersistentEntity( + ClassTypeInformation.from(DefaultExpiryUnit.class)); + + assertEquals(78, entity.getExpiry()); + } + + @Test + public void testLargeExpiry30DaysStillInSeconds() { + CouchbasePersistentEntity entityUnder = new BasicCouchbasePersistentEntity( + ClassTypeInformation.from(LimitDaysExpiry.class)); + assertEquals(30 * 24 * 60 * 60, entityUnder.getExpiry()); + } + + @Test + public void testLargeExpiry31DaysIsConvertedToUnixUtcTime() { + CouchbasePersistentEntity entityOver = new BasicCouchbasePersistentEntity( + ClassTypeInformation.from(OverLimitDaysExpiry.class)); + + int expiryOver = entityOver.getExpiry(); + Calendar expected = Calendar.getInstance(TimeZone.getTimeZone("UTC")); + expected.add(Calendar.DAY_OF_YEAR, 31); + + Date dateOver = new Date(expiryOver * 1000L); + System.out.println(entityOver + " => " + dateOver); + + Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC")); + calendar.clear(); + calendar.add(Calendar.SECOND, expiryOver); + assertEquals(expected.get(Calendar.YEAR), calendar.get(Calendar.YEAR)); + assertEquals(expected.get(Calendar.MONTH), calendar.get(Calendar.MONTH)); + assertEquals(expected.get(Calendar.DAY_OF_MONTH), calendar.get(Calendar.DAY_OF_MONTH)); + assertEquals(expected.get(Calendar.HOUR_OF_DAY), calendar.get(Calendar.HOUR_OF_DAY)); + assertEquals(expected.get(Calendar.MINUTE), calendar.get(Calendar.MINUTE)); + assertEquals(expected.get(Calendar.SECOND), calendar.get(Calendar.SECOND)); + } + + @Test + public void testLargeExpiry31DaysInSecondsIsConvertedToUnixUtcTime() { + CouchbasePersistentEntity entityOver = new BasicCouchbasePersistentEntity( + ClassTypeInformation.from(OverLimitSecondsExpiry.class)); + + int expiryOver = entityOver.getExpiry(); + Calendar expected = Calendar.getInstance(TimeZone.getTimeZone("UTC")); + expected.add(Calendar.DAY_OF_YEAR, 31); + + Date dateOver = new Date(expiryOver * 1000L); + System.out.println(entityOver + " => " + dateOver); + + Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC")); + calendar.clear(); + calendar.add(Calendar.SECOND, expiryOver); + assertEquals(expected.get(Calendar.YEAR), calendar.get(Calendar.YEAR)); + assertEquals(expected.get(Calendar.MONTH), calendar.get(Calendar.MONTH)); + assertEquals(expected.get(Calendar.DAY_OF_MONTH), calendar.get(Calendar.DAY_OF_MONTH)); + assertEquals(expected.get(Calendar.HOUR_OF_DAY), calendar.get(Calendar.HOUR_OF_DAY)); + assertEquals(expected.get(Calendar.MINUTE), calendar.get(Calendar.MINUTE)); + assertEquals(expected.get(Calendar.SECOND), calendar.get(Calendar.SECOND)); + } + + /** + * Simple POJO to test default expiry. + */ + @Document + private class DefaultExpiry { + } + + /** + * Simple POJO to test default expiry unit. + */ + @Document(expiry = 78) + private class DefaultExpiryUnit { + } + + /** + * Simple POJO to test limit expiry. + */ + @Document(expiry = 30, expiryUnit = TimeUnit.DAYS) + private class LimitDaysExpiry { + } + + /** + * Simple POJO to test larger than 30 days expiry. + */ + @Document(expiry = 31, expiryUnit = TimeUnit.DAYS) + public class OverLimitDaysExpiry { + } + + /** + * Simple POJO to test larger than 30 days expiry, when expressed in default time unit (SECONDS). + */ + @Document(expiry = 31 * 24 * 60 * 60) + public class OverLimitSecondsExpiry { + } +}