diff --git a/src/main/asciidoc/entity.adoc b/src/main/asciidoc/entity.adoc index ed9c3dce..3273d0e8 100644 --- a/src/main/asciidoc/entity.adoc +++ b/src/main/asciidoc/entity.adoc @@ -78,8 +78,8 @@ This key needs to be any string with a length of maximum 250 characters. Feel free to use whatever fits your use case, be it a UUID, an email address or anything else. Writes to Couchbase-Server buckets can optionally be assigned durability requirements; which instruct Couchbase Server to update the specified document on multiple nodes in memory and/or disk locations across the cluster; before considering the write to be committed. -Default durability requirements can also be configured through the `@Document` annotation. -For example: `@Document(durabilityLevel = DurabilityLevel.MAJORITY)` will force mutations to be replicated to a majority of the Data Service nodes. +Default durability requirements can also be configured through the `@Document` or `@Durability` annotations. +For example: `@Document(durabilityLevel = DurabilityLevel.MAJORITY)` will force mutations to be replicated to a majority of the Data Service nodes. Both of the annotations support expression based durability level assignment via `durabilityExpression` attribute (Note SPEL is not supported). [[datatypes]] == Datatypes and Converters diff --git a/src/main/asciidoc/index.adoc b/src/main/asciidoc/index.adoc index 0a4155c5..11dae730 100644 --- a/src/main/asciidoc/index.adoc +++ b/src/main/asciidoc/index.adoc @@ -1,10 +1,10 @@ = Spring Data Couchbase - Reference Documentation -Michael Nitschinger, Oliver Gierke, Simon Basle, Michael Reiche +Michael Nitschinger, Oliver Gierke, Simon Basle, Michael Reiche, Tigran Babloyan :revnumber: {version} :revdate: {localdate} :spring-data-commons-docs: ../../../../spring-data-commons/src/main/asciidoc -(C) 2014-2022 The original author(s). +(C) 2014-2023 The original author(s). NOTE: Copies of this document may be made for your own use and for distribution to others, provided that you do not charge any fee for such copies and further provided that each copy contains this Copyright Notice, whether distributed in print or electronically. diff --git a/src/main/java/org/springframework/data/couchbase/core/ExecutableInsertByIdOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ExecutableInsertByIdOperationSupport.java index f1c78a26..ad95272a 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ExecutableInsertByIdOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ExecutableInsertByIdOperationSupport.java @@ -40,7 +40,7 @@ public class ExecutableInsertByIdOperationSupport implements ExecutableInsertByI Assert.notNull(domainType, "DomainType must not be null!"); return new ExecutableInsertByIdSupport<>(template, domainType, OptionsBuilder.getScopeFrom(domainType), OptionsBuilder.getCollectionFrom(domainType), null, OptionsBuilder.getPersistTo(domainType), - OptionsBuilder.getReplicateTo(domainType), OptionsBuilder.getDurabilityLevel(domainType), + OptionsBuilder.getReplicateTo(domainType), OptionsBuilder.getDurabilityLevel(domainType, template.getConverter()), null); } diff --git a/src/main/java/org/springframework/data/couchbase/core/ExecutableMutateInByIdOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ExecutableMutateInByIdOperationSupport.java index 36a5eed8..69284fed 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ExecutableMutateInByIdOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ExecutableMutateInByIdOperationSupport.java @@ -51,7 +51,7 @@ public class ExecutableMutateInByIdOperationSupport implements ExecutableMutateI Assert.notNull(domainType, "DomainType must not be null!"); return new ExecutableMutateInByIdSupport(template, domainType, OptionsBuilder.getScopeFrom(domainType), OptionsBuilder.getCollectionFrom(domainType), null, OptionsBuilder.getPersistTo(domainType), - OptionsBuilder.getReplicateTo(domainType), OptionsBuilder.getDurabilityLevel(domainType), + OptionsBuilder.getReplicateTo(domainType), OptionsBuilder.getDurabilityLevel(domainType, template.getConverter()), null, Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), false); } diff --git a/src/main/java/org/springframework/data/couchbase/core/ExecutableRemoveByIdOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ExecutableRemoveByIdOperationSupport.java index fa1d0afe..96520492 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ExecutableRemoveByIdOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ExecutableRemoveByIdOperationSupport.java @@ -52,7 +52,7 @@ public class ExecutableRemoveByIdOperationSupport implements ExecutableRemoveByI return new ExecutableRemoveByIdSupport(template, domainType, OptionsBuilder.getScopeFrom(domainType), OptionsBuilder.getCollectionFrom(domainType), null, OptionsBuilder.getPersistTo(domainType), - OptionsBuilder.getReplicateTo(domainType), OptionsBuilder.getDurabilityLevel(domainType), + OptionsBuilder.getReplicateTo(domainType), OptionsBuilder.getDurabilityLevel(domainType, template.getConverter()), null); } 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 5151a039..291abfab 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ExecutableReplaceByIdOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ExecutableReplaceByIdOperationSupport.java @@ -40,7 +40,7 @@ public class ExecutableReplaceByIdOperationSupport implements ExecutableReplaceB Assert.notNull(domainType, "DomainType must not be null!"); return new ExecutableReplaceByIdSupport<>(template, domainType, OptionsBuilder.getScopeFrom(domainType), OptionsBuilder.getCollectionFrom(domainType), null, OptionsBuilder.getPersistTo(domainType), - OptionsBuilder.getReplicateTo(domainType), OptionsBuilder.getDurabilityLevel(domainType), + OptionsBuilder.getReplicateTo(domainType), OptionsBuilder.getDurabilityLevel(domainType, template.getConverter()), null); } diff --git a/src/main/java/org/springframework/data/couchbase/core/ExecutableUpsertByIdOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ExecutableUpsertByIdOperationSupport.java index bd5a71b3..edbc4759 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ExecutableUpsertByIdOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ExecutableUpsertByIdOperationSupport.java @@ -40,7 +40,7 @@ public class ExecutableUpsertByIdOperationSupport implements ExecutableUpsertByI Assert.notNull(domainType, "DomainType must not be null!"); return new ExecutableUpsertByIdSupport<>(template, domainType, OptionsBuilder.getScopeFrom(domainType), OptionsBuilder.getCollectionFrom(domainType), null, OptionsBuilder.getPersistTo(domainType), - OptionsBuilder.getReplicateTo(domainType), OptionsBuilder.getDurabilityLevel(domainType), + OptionsBuilder.getReplicateTo(domainType), OptionsBuilder.getDurabilityLevel(domainType, template.getConverter()), null); } diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveInsertByIdOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveInsertByIdOperationSupport.java index 2c71a091..66a83426 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveInsertByIdOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveInsertByIdOperationSupport.java @@ -61,7 +61,7 @@ public class ReactiveInsertByIdOperationSupport implements ReactiveInsertByIdOpe Assert.notNull(domainType, "DomainType must not be null!"); return new ReactiveInsertByIdSupport<>(template, domainType, OptionsBuilder.getScopeFrom(domainType), OptionsBuilder.getCollectionFrom(domainType), null, OptionsBuilder.getPersistTo(domainType), - OptionsBuilder.getReplicateTo(domainType), OptionsBuilder.getDurabilityLevel(domainType), + OptionsBuilder.getReplicateTo(domainType), OptionsBuilder.getDurabilityLevel(domainType, template.getConverter()), null, template.support()); } diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveMutateInByIdOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveMutateInByIdOperationSupport.java index f44c871f..06f97f9d 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveMutateInByIdOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveMutateInByIdOperationSupport.java @@ -52,7 +52,7 @@ public class ReactiveMutateInByIdOperationSupport implements ReactiveMutateInByI Assert.notNull(domainType, "DomainType must not be null!"); return new ReactiveMutateInByIdSupport<>(template, domainType, OptionsBuilder.getScopeFrom(domainType), OptionsBuilder.getCollectionFrom(domainType), null, OptionsBuilder.getPersistTo(domainType), - OptionsBuilder.getReplicateTo(domainType), OptionsBuilder.getDurabilityLevel(domainType), + OptionsBuilder.getReplicateTo(domainType), OptionsBuilder.getDurabilityLevel(domainType, template.getConverter()), null, template.support(), Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), false); } diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveRemoveByIdOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveRemoveByIdOperationSupport.java index 7e45b2cf..8f63e508 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveRemoveByIdOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveRemoveByIdOperationSupport.java @@ -67,7 +67,7 @@ public class ReactiveRemoveByIdOperationSupport implements ReactiveRemoveByIdOpe public ReactiveRemoveById removeById(Class domainType) { return new ReactiveRemoveByIdSupport(template, domainType, OptionsBuilder.getScopeFrom(domainType), OptionsBuilder.getCollectionFrom(domainType), null, OptionsBuilder.getPersistTo(domainType), - OptionsBuilder.getReplicateTo(domainType), OptionsBuilder.getDurabilityLevel(domainType), + OptionsBuilder.getReplicateTo(domainType), OptionsBuilder.getDurabilityLevel(domainType, template.getConverter()), null); } 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 3c2ae98c..805b2ae5 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveReplaceByIdOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveReplaceByIdOperationSupport.java @@ -64,7 +64,7 @@ public class ReactiveReplaceByIdOperationSupport implements ReactiveReplaceByIdO Assert.notNull(domainType, "DomainType must not be null!"); return new ReactiveReplaceByIdSupport<>(template, domainType, OptionsBuilder.getScopeFrom(domainType), OptionsBuilder.getCollectionFrom(domainType), null, OptionsBuilder.getPersistTo(domainType), - OptionsBuilder.getReplicateTo(domainType), OptionsBuilder.getDurabilityLevel(domainType), + OptionsBuilder.getReplicateTo(domainType), OptionsBuilder.getDurabilityLevel(domainType, template.getConverter()), null, template.support()); } diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveUpsertByIdOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveUpsertByIdOperationSupport.java index 01ec65eb..93ffd1c8 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveUpsertByIdOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveUpsertByIdOperationSupport.java @@ -53,7 +53,7 @@ public class ReactiveUpsertByIdOperationSupport implements ReactiveUpsertByIdOpe Assert.notNull(domainType, "DomainType must not be null!"); return new ReactiveUpsertByIdSupport<>(template, domainType, OptionsBuilder.getScopeFrom(domainType), OptionsBuilder.getCollectionFrom(domainType), null, OptionsBuilder.getPersistTo(domainType), - OptionsBuilder.getReplicateTo(domainType), OptionsBuilder.getDurabilityLevel(domainType), + OptionsBuilder.getReplicateTo(domainType), OptionsBuilder.getDurabilityLevel(domainType, template.getConverter()), null, template.support()); } 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 729aeaeb..95b313e4 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 @@ -21,6 +21,7 @@ import java.util.Calendar; import java.util.TimeZone; import java.util.concurrent.TimeUnit; +import com.couchbase.client.core.msg.kv.DurabilityLevel; import org.springframework.context.EnvironmentAware; import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.core.env.Environment; @@ -36,6 +37,7 @@ import org.springframework.util.StringUtils; * @author Michael Nitschinger * @author Mark Paluch * @author Michael Reiche + * @author Tigran Babloyan */ public class BasicCouchbasePersistentEntity extends BasicPersistentEntity implements CouchbasePersistentEntity, EnvironmentAware { @@ -50,6 +52,7 @@ public class BasicCouchbasePersistentEntity extends BasicPersistentEntity typeInformation) { super(typeInformation); validateExpirationConfiguration(); + validateDurabilityConfiguration(); } private void validateExpirationConfiguration() { @@ -61,6 +64,15 @@ public class BasicCouchbasePersistentEntity extends BasicPersistentEntity extends BasicPersistentEntity extends PersistentEntity { @@ -54,6 +56,15 @@ public interface CouchbasePersistentEntity extends PersistentEntity + * Allows the application to wait until this replication (or persistence) is successful before proceeding + * + * @return the durability level. + */ + DurabilityLevel getDurabilityLevel(); + /** * Flag for using getAndTouch operations for reads, resetting the expiration (if one was set) when the entity is * directly read (eg. findOne, findById). 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 a2caeec5..caeedeed 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 @@ -47,6 +47,7 @@ import com.couchbase.client.java.query.QueryScanConsistency; @Target({ ElementType.TYPE }) @Expiry @ScanConsistency +@Durability public @interface Document { /** @@ -105,5 +106,16 @@ public @interface Document { * The optional durabilityLevel for all mutating operations, allows the application to wait until this replication * (or persistence) is successful before proceeding */ + @AliasFor(annotation = Durability.class, attribute = "durabilityLevel") DurabilityLevel durabilityLevel() default DurabilityLevel.NONE; + + /** + * Same as {@link #durabilityLevel()} but allows the actual value to be set using standard Spring property sources mechanism. + * Only one might be set at the same time: either {@link #durabilityLevel()} or {@link #durabilityExpression()}.
+ * Syntax is the same as for {@link org.springframework.core.env.Environment#resolveRequiredPlaceholders(String)}. + *
+ * SpEL is NOT supported. + */ + @AliasFor(annotation = Durability.class, attribute = "durabilityExpression") + String durabilityExpression() default ""; } diff --git a/src/main/java/org/springframework/data/couchbase/core/mapping/Durability.java b/src/main/java/org/springframework/data/couchbase/core/mapping/Durability.java new file mode 100644 index 00000000..d9eba776 --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/mapping/Durability.java @@ -0,0 +1,32 @@ +package org.springframework.data.couchbase.core.mapping; + +import com.couchbase.client.core.msg.kv.DurabilityLevel; +import org.springframework.data.annotation.Persistent; + +import java.lang.annotation.*; + +/** + * Durability annotation + * + * @author Tigran Babloyan + */ +@Persistent +@Inherited +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.TYPE, ElementType.ANNOTATION_TYPE }) +public @interface Durability { + /** + * The optional durabilityLevel for all mutating operations, allows the application to wait until this replication + * (or persistence) is successful before proceeding + */ + DurabilityLevel durabilityLevel() default DurabilityLevel.NONE; + + /** + * Same as {@link #durabilityLevel()} but allows the actual value to be set using standard Spring property sources mechanism. + * Only one might be set at the same time: either {@link #durabilityLevel()} or {@link #durabilityExpression()}.
+ * Syntax is the same as for {@link org.springframework.core.env.Environment#resolveRequiredPlaceholders(String)}. + *
+ * SpEL is NOT supported. + */ + String durabilityExpression() default ""; +} diff --git a/src/main/java/org/springframework/data/couchbase/core/query/OptionsBuilder.java b/src/main/java/org/springframework/data/couchbase/core/query/OptionsBuilder.java index db6eebf1..e7f297bc 100644 --- a/src/main/java/org/springframework/data/couchbase/core/query/OptionsBuilder.java +++ b/src/main/java/org/springframework/data/couchbase/core/query/OptionsBuilder.java @@ -35,7 +35,9 @@ import com.couchbase.client.core.error.InvalidArgumentException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.annotation.AnnotatedElementUtils; +import org.springframework.data.couchbase.core.convert.CouchbaseConverter; import org.springframework.data.couchbase.core.mapping.CouchbaseDocument; +import org.springframework.data.couchbase.core.mapping.CouchbasePersistentEntity; import org.springframework.data.couchbase.core.mapping.Document; import org.springframework.data.couchbase.repository.Collection; import org.springframework.data.couchbase.repository.ScanConsistency; @@ -284,12 +286,14 @@ public class OptionsBuilder { return null; } - public static DurabilityLevel getDurabilityLevel(Class domainType) { + public static DurabilityLevel getDurabilityLevel(Class domainType, CouchbaseConverter converter) { if (domainType == null) { return DurabilityLevel.NONE; } - Document document = AnnotatedElementUtils.findMergedAnnotation(domainType, Document.class); - return document != null ? document.durabilityLevel() : DurabilityLevel.NONE; + final CouchbasePersistentEntity entity = converter.getMappingContext() + .getRequiredPersistentEntity(domainType); + + return entity.getDurabilityLevel(); } public static PersistTo getPersistTo(Class domainType) { 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 e144a6dd..e7915bba 100644 --- a/src/test/java/org/springframework/data/couchbase/core/CouchbaseTemplateKeyValueIntegrationTests.java +++ b/src/test/java/org/springframework/data/couchbase/core/CouchbaseTemplateKeyValueIntegrationTests.java @@ -46,6 +46,7 @@ import org.springframework.data.couchbase.domain.*; import org.springframework.data.couchbase.util.ClusterType; import org.springframework.data.couchbase.util.IgnoreWhen; import org.springframework.data.couchbase.util.JavaIntegrationTests; +import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; import com.couchbase.client.core.error.CouchbaseException; @@ -63,6 +64,7 @@ import com.couchbase.client.java.query.QueryScanConsistency; */ @IgnoreWhen(clusterTypes = ClusterType.MOCKED) @SpringJUnitConfig(Config.class) +@TestPropertySource(properties = { "valid.document.durability = MAJORITY" }) class CouchbaseTemplateKeyValueIntegrationTests extends JavaIntegrationTests { @Autowired public CouchbaseTemplate couchbaseTemplate; @@ -290,7 +292,8 @@ class CouchbaseTemplateKeyValueIntegrationTests extends JavaIntegrationTests { @Test void withDurability() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { - for (Class clazz : new Class[] { User.class, UserAnnotatedDurability.class, UserAnnotatedPersistTo.class, UserAnnotatedReplicateTo.class }) { + for (Class clazz : new Class[] { User.class, UserAnnotatedDurability.class, UserAnnotatedDurabilityExpression.class, + UserAnnotatedPersistTo.class, UserAnnotatedReplicateTo.class }) { // insert, replace, upsert for (OneAndAllEntity operator : new OneAndAllEntity[]{couchbaseTemplate.insertById(clazz), couchbaseTemplate.replaceById(clazz), couchbaseTemplate.upsertById(clazz)}) { @@ -1132,6 +1135,31 @@ class CouchbaseTemplateKeyValueIntegrationTests extends JavaIntegrationTests { couchbaseTemplate.removeById(UserAnnotatedDurability.class).one(user.getId()); } + @Test + void insertByIdWithAnnotatedDurabilityExpression() { + UserAnnotatedDurabilityExpression user = new UserAnnotatedDurabilityExpression(UUID.randomUUID().toString(), "firstname", "lastname"); + UserAnnotatedDurabilityExpression inserted = null; + + // occasionally gives "reactor.core.Exceptions$OverflowException: Could not emit value due to lack of requests" + for (int i = 1; i != 5; i++) { + try { + inserted = couchbaseTemplate.insertById(UserAnnotatedDurabilityExpression.class) + .one(user); + break; + } catch (Exception ofe) { + System.out.println("" + i + " caught: " + ofe); + couchbaseTemplate.removeByQuery(UserAnnotatedDurabilityExpression.class).withConsistency(QueryScanConsistency.REQUEST_PLUS).all(); + if (i == 4) { + throw ofe; + } + sleepSecs(1); + } + } + assertEquals(user, inserted); + assertThrows(DuplicateKeyException.class, () -> couchbaseTemplate.insertById(UserAnnotatedDurabilityExpression.class).one(user)); + couchbaseTemplate.removeById(UserAnnotatedDurabilityExpression.class).one(user.getId()); + } + @Test void existsById() { String id = UUID.randomUUID().toString(); 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 index 87dd2edd..ce31e24e 100644 --- a/src/test/java/org/springframework/data/couchbase/core/mapping/BasicCouchbasePersistentEntityTests.java +++ b/src/test/java/org/springframework/data/couchbase/core/mapping/BasicCouchbasePersistentEntityTests.java @@ -23,6 +23,7 @@ import java.util.Calendar; import java.util.TimeZone; import java.util.concurrent.TimeUnit; +import com.couchbase.client.core.msg.kv.DurabilityLevel; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; @@ -33,7 +34,8 @@ import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; @SpringJUnitConfig -@TestPropertySource(properties = { "valid.document.expiry = 10", "invalid.document.expiry = abc" }) +@TestPropertySource(properties = { "valid.document.expiry = 10", "invalid.document.expiry = abc", + "invalid.document.durability = some", "valid.document.durability = MAJORITY" }) public class BasicCouchbasePersistentEntityTests { @Autowired ConfigurableEnvironment environment; @@ -180,6 +182,34 @@ public class BasicCouchbasePersistentEntityTests { () -> getBasicCouchbasePersistentEntity(ExpiryAndExpression.class).getExpiry()); } + @Test + void doesNotAllowUseDurabilityAndExpressionSimultaneously() { + assertThrows(IllegalArgumentException.class, + () -> getBasicCouchbasePersistentEntity(DurabilityAndExpression.class).getDurabilityLevel()); + } + + @Test + void failsIfDurabilityExpressionMissesRequiredProperty() { + assertThrows(IllegalArgumentException.class, + () -> getBasicCouchbasePersistentEntity(DurabilityWithMissingProperty.class).getDurabilityLevel()); + } + + @Test + void doesNotAllowUseDurabilityFromInvalidExpression() { + assertThrows(IllegalArgumentException.class, + () -> getBasicCouchbasePersistentEntity(DurabilityWithInvalidExpression.class).getDurabilityLevel()); + } + + @Test + void usesGetDurabilityExpression() { + assertThat(getBasicCouchbasePersistentEntity(ConstantDurabilityExpression.class).getDurabilityLevel()).isEqualTo(DurabilityLevel.MAJORITY); + } + + @Test + void usesGetDurabilityFromValidExpression() { + assertThat(getBasicCouchbasePersistentEntity(DurabilityWithValidExpression.class).getDurabilityLevel()).isEqualTo(DurabilityLevel.MAJORITY); + } + private BasicCouchbasePersistentEntity getBasicCouchbasePersistentEntity(Class clazz) { BasicCouchbasePersistentEntity basicCouchbasePersistentEntity = new BasicCouchbasePersistentEntity( ClassTypeInformation.from(clazz)); @@ -264,4 +294,34 @@ public class BasicCouchbasePersistentEntityTests { @Document(expiry = 10, expiryExpression = "10") class ExpiryAndExpression {} + /** + * Simple POJO to test that durability and durability expression cannot be used simultaneously + */ + @Document(durabilityLevel = DurabilityLevel.MAJORITY, durabilityExpression = "MAJORITY") + class DurabilityAndExpression {} + + /** + * Simple POJO to test durability expression logic failure to resolve property placeholder + */ + @Document(durabilityExpression = "${missing.durability}") + class DurabilityWithMissingProperty {} + + /** + * Simple POJO to test invalid durability expression + */ + @Document(durabilityExpression = "${invalid.document.durability}") + class DurabilityWithInvalidExpression {} + + /** + * Simple POJO to test constant expiry expression + */ + @Document(durabilityExpression = "MAJORITY") + class ConstantDurabilityExpression {} + + /** + * Simple POJO to test valid expiry expression by resolving simple property from environment + */ + @Document(durabilityExpression = "${valid.document.durability}") + class DurabilityWithValidExpression {} + } diff --git a/src/test/java/org/springframework/data/couchbase/domain/UserAnnotatedDurabilityExpression.java b/src/test/java/org/springframework/data/couchbase/domain/UserAnnotatedDurabilityExpression.java new file mode 100644 index 00000000..fdd7b708 --- /dev/null +++ b/src/test/java/org/springframework/data/couchbase/domain/UserAnnotatedDurabilityExpression.java @@ -0,0 +1,36 @@ +/* + * Copyright 2020-2023 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 + * + * https://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.domain; + +import com.couchbase.client.core.msg.kv.DurabilityLevel; +import org.springframework.data.couchbase.core.mapping.Document; + +import java.io.Serializable; + +/** + * Annotated User entity for tests + * + * @author Tigran Babloyan + */ + +@Document(durabilityExpression = "${valid.document.durability}") +public class UserAnnotatedDurabilityExpression extends User implements Serializable { + + public UserAnnotatedDurabilityExpression(String id, String firstname, String lastname) { + super(id, firstname, lastname); + } +}