diff --git a/src/main/asciidoc/entity.adoc b/src/main/asciidoc/entity.adoc index ed9c3dceb..3273d0e89 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 0a4155c5e..11dae7309 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 f1c78a26f..ad95272ad 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 ExecutableInsertById insertById(final Class domainType) { 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 36a5eed8d..69284fed9 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 ExecutableMutateInById mutateInById(final Class domainType) { 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 fa1d0afe5..96520492f 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 ExecutableRemoveById removeById(Class domainType) { 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 5151a039d..291abfabb 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 ExecutableReplaceById replaceById(final Class domainType) { 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 bd5a71b36..edbc4759a 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 ExecutableUpsertById upsertById(final Class domainType) { 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 2c71a0910..66a834268 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 ReactiveInsertById insertById(final Class domainType) { 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 f44c871f3..06f97f9db 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 ReactiveMutateInById mutateInById(final Class domainType) { 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 7e45b2cfe..8f63e508f 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 ReactiveRemoveById removeById() { 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 3c2ae98c3..805b2ae53 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 ReactiveReplaceById replaceById(final Class domainType) { 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 01ec65eb4..93ffd1c83 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 ReactiveUpsertById upsertById(final Class domainType) { 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 729aeaebb..95b313e4f 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.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 @@ * @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 @@ private void validateExpirationConfiguration() { } } + private void validateDurabilityConfiguration() { + Document annotation = getType().getAnnotation(Document.class); + if (annotation != null && annotation.durabilityLevel() != DurabilityLevel.NONE && StringUtils.hasLength(annotation.durabilityExpression())) { + String msg = String.format("Incorrect durability configuration on class %s using %s. " + + "You cannot use 'durabilityLevel' and 'durabilityExpression' at the same time", getType().getName(), annotation); + throw new IllegalArgumentException(msg); + } + } + @Override public void setEnvironment(Environment environment) { this.environment = environment; @@ -158,6 +170,30 @@ private static int getExpiryValue(Expiry annotation, Environment environment) { return expiryValue; } + @Override + public DurabilityLevel getDurabilityLevel() { + return getDurabilityLevel(AnnotatedElementUtils.findMergedAnnotation(getType(), Durability.class), environment); + } + + private static DurabilityLevel getDurabilityLevel(Durability annotation, Environment environment) { + if (annotation == null) { + return DurabilityLevel.NONE; + } + DurabilityLevel durabilityLevel = annotation.durabilityLevel(); + String durabilityExpressionString = annotation.durabilityExpression(); + if (StringUtils.hasLength(durabilityExpressionString)) { + Assert.notNull(environment, "Environment must be set to use 'durabilityExpressionString'"); + String durabilityWithReplacedPlaceholders = environment.resolveRequiredPlaceholders(durabilityExpressionString); + try { + durabilityLevel = DurabilityLevel.valueOf(durabilityWithReplacedPlaceholders); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException( + "Invalid value for durability expression: " + durabilityWithReplacedPlaceholders); + } + } + return durabilityLevel; + } + @Override public boolean isTouchOnRead() { org.springframework.data.couchbase.core.mapping.Document annotation = getType() 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 9c7357b0b..988e8e4fc 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 @@ -18,6 +18,7 @@ import java.time.Duration; +import com.couchbase.client.core.msg.kv.DurabilityLevel; import org.springframework.data.mapping.PersistentEntity; /** @@ -25,6 +26,7 @@ * * @author Michael Nitschinger * @author Michael Reiche + * @author Tigran Babloyan */ public interface CouchbasePersistentEntity 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 a2caeec55..caeedeedc 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 @@ @Target({ ElementType.TYPE }) @Expiry @ScanConsistency +@Durability public @interface Document { /** @@ -105,5 +106,16 @@ * 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 000000000..d9eba776a --- /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 db6eebf19..e7f297bc2 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 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 static String getScopeFrom(Class domainType) { 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 e144a6dd1..e7915bba5 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.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 @@ */ @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 @@ void findProjectingPath() { @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 @@ void insertByIdWithAnnotatedDurability2() { 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 87dd2edd2..ce31e24ef 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.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.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 @@ void doesNotAllowUseExpiryAndExpressionSimultaneously() { () -> 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 @@ class ExpiryWithMissingProperty {} @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 000000000..fdd7b708e --- /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); + } +}