From 2234fd76df1f96197ca1bd947921085fe3d06fdd Mon Sep 17 00:00:00 2001 From: mikereiche Date: Thu, 7 Aug 2025 18:17:01 -0700 Subject: [PATCH] Populates expiry property from result.getExpiry Also passes options into save() Closes #1639. Signed-off-by: mikereiche --- .../core/AbstractTemplateSupport.java | 10 ++- .../couchbase/core/CouchbaseOperations.java | 17 ++++ .../couchbase/core/CouchbaseTemplate.java | 13 +++ .../core/CouchbaseTemplateSupport.java | 8 +- .../ExecutableExistsByIdOperationSupport.java | 5 +- ...utableFindByAnalyticsOperationSupport.java | 3 +- .../ExecutableFindByIdOperationSupport.java | 4 +- ...ExecutableFindByQueryOperationSupport.java | 3 +- ...eFindFromReplicasByIdOperationSupport.java | 5 +- .../ExecutableInsertByIdOperationSupport.java | 4 +- ...xecutableMutateInByIdOperationSupport.java | 28 +++---- .../ExecutableRangeScanOperationSupport.java | 5 +- .../ExecutableRemoveByIdOperationSupport.java | 4 +- ...ecutableRemoveByQueryOperationSupport.java | 4 +- ...ExecutableReplaceByIdOperationSupport.java | 4 +- .../ExecutableUpsertByIdOperationSupport.java | 4 +- .../core/NonReactiveSupportWrapper.java | 9 ++- .../ReactiveCouchbaseTemplateSupport.java | 9 ++- .../ReactiveExistsByIdOperationSupport.java | 5 +- ...activeFindByAnalyticsOperationSupport.java | 6 +- .../ReactiveFindByIdOperationSupport.java | 69 +++++++++------- .../ReactiveFindByQueryOperationSupport.java | 11 ++- ...eFindFromReplicasByIdOperationSupport.java | 10 +-- .../ReactiveInsertByIdOperationSupport.java | 4 +- .../ReactiveMutateInByIdOperationSupport.java | 30 ++++--- .../ReactiveRangeScanOperationSupport.java | 10 +-- .../ReactiveRemoveByIdOperationSupport.java | 5 +- ...ReactiveRemoveByQueryOperationSupport.java | 7 +- .../ReactiveReplaceByIdOperationSupport.java | 4 +- .../core/ReactiveTemplateSupport.java | 6 +- .../ReactiveUpsertByIdOperationSupport.java | 4 +- .../data/couchbase/core/TemplateSupport.java | 6 +- .../BasicCouchbasePersistentEntity.java | 11 +++ .../mapping/CouchbasePersistentEntity.java | 2 + .../couchbase/core/query/OptionsBuilder.java | 81 ++++++++++++++++++- .../couchbase/core/support/PseudoArgs.java | 19 ++++- .../support/CouchbaseRepositoryBase.java | 9 +++ .../support/SimpleCouchbaseRepository.java | 14 ++-- ...chbaseRepositoryQueryIntegrationTests.java | 11 ++- 39 files changed, 319 insertions(+), 144 deletions(-) diff --git a/src/main/java/org/springframework/data/couchbase/core/AbstractTemplateSupport.java b/src/main/java/org/springframework/data/couchbase/core/AbstractTemplateSupport.java index 967b9db75..def07ed03 100644 --- a/src/main/java/org/springframework/data/couchbase/core/AbstractTemplateSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/AbstractTemplateSupport.java @@ -16,6 +16,7 @@ package org.springframework.data.couchbase.core; import java.lang.reflect.InaccessibleObjectException; +import java.time.Instant; import java.util.Map; import java.util.Set; @@ -67,8 +68,8 @@ public AbstractTemplateSupport(ReactiveCouchbaseTemplate template, CouchbaseConv abstract ReactiveCouchbaseTemplate getReactiveTemplate(); - public T decodeEntityBase(Object id, String source, Long cas, Class entityClass, String scope, - String collection, Object txResultHolder, CouchbaseResourceHolder holder) { + public T decodeEntityBase(Object id, String source, Long cas, Instant expiryTime, Class entityClass, + String scope, String collection, Object txResultHolder, CouchbaseResourceHolder holder) { // this is the entity class defined for the repository. It may not be the class of the document that was read // we will reset it after reading the document @@ -127,6 +128,11 @@ public T decodeEntityBase(Object id, String source, Long cas, Class entit if (cas != null && cas != 0 && persistentEntity.getVersionProperty() != null) { accessor.setProperty(persistentEntity.getVersionProperty(), cas); } + + if (expiryTime != null && persistentEntity.getExpiryProperty() != null) { + accessor.setProperty(persistentEntity.getExpiryProperty(), expiryTime); + } + N1qlJoinResolver.handleProperties(persistentEntity, accessor, getReactiveTemplate(), id.toString(), scope, collection); if (holder != null) { diff --git a/src/main/java/org/springframework/data/couchbase/core/CouchbaseOperations.java b/src/main/java/org/springframework/data/couchbase/core/CouchbaseOperations.java index 541c07439..f918e5e09 100644 --- a/src/main/java/org/springframework/data/couchbase/core/CouchbaseOperations.java +++ b/src/main/java/org/springframework/data/couchbase/core/CouchbaseOperations.java @@ -16,6 +16,7 @@ package org.springframework.data.couchbase.core; +import com.couchbase.client.java.CommonOptions; import org.springframework.data.couchbase.CouchbaseClientFactory; import org.springframework.data.couchbase.core.convert.CouchbaseConverter; import org.springframework.data.couchbase.core.query.Query; @@ -54,6 +55,22 @@ public interface CouchbaseOperations extends FluentCouchbaseOperations { */ QueryScanConsistency getConsistency(); + /** + * Save the entity to couchbase.
+ * If there is no version property on the entity class, and this is in a transaction, use insert.
+ * If there is no version property on the entity class, and this is not in a transaction, use upsert.
+ * If there is a version property on the entity class, and it is non-zero, then this is an existing document, use + * replace.
+ * Otherwise, there is a version property for the entity, but it is zero or null, use insert.
+ * + * @param entity the entity to save in couchbase + * @param options options + * @param scopeAndCollection for use by repositories only. these are varargs for the scope and collection. + * @param the entity class + * @return + */ + T save(T entity, CommonOptions options, String... scopeAndCollection); + /** * Save the entity to couchbase.
* If there is no version property on the entity class, and this is in a transaction, use insert.
diff --git a/src/main/java/org/springframework/data/couchbase/core/CouchbaseTemplate.java b/src/main/java/org/springframework/data/couchbase/core/CouchbaseTemplate.java index 0d6aabee4..709d6f368 100644 --- a/src/main/java/org/springframework/data/couchbase/core/CouchbaseTemplate.java +++ b/src/main/java/org/springframework/data/couchbase/core/CouchbaseTemplate.java @@ -16,6 +16,10 @@ package org.springframework.data.couchbase.core; +import com.couchbase.client.java.CommonOptions; +import com.couchbase.client.java.kv.InsertOptions; +import com.couchbase.client.java.kv.ReplaceOptions; +import com.couchbase.client.java.kv.UpsertOptions; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; @@ -86,6 +90,11 @@ public CouchbaseTemplate(final CouchbaseClientFactory clientFactory, final Couch @Override public T save(T entity, String... scopeAndCollection) { + return save(entity, null, scopeAndCollection); + } + + @Override + public T save(T entity, CommonOptions options, String... scopeAndCollection) { Assert.notNull(entity, "Entity must not be null!"); String scope = scopeAndCollection.length > 0 ? scopeAndCollection[0] : null; @@ -109,10 +118,12 @@ public T save(T entity, String... scopeAndCollection) { if (ctx.isPresent()) { return (T) insertById(clazz).inScope(scope) .inCollection(collection) + .withOptions((InsertOptions) options) .one(entity); } else { // if not in a tx, then upsert will work return (T) upsertById(clazz).inScope(scope) .inCollection(collection) + .withOptions((UpsertOptions) options) .one(entity); } }).block(); @@ -121,11 +132,13 @@ public T save(T entity, String... scopeAndCollection) { // Updating existing document with cas return (T)replaceById(clazz).inScope(scope) .inCollection(collection) + .withOptions((ReplaceOptions) options) .one(entity); } else { // there is a version property, but it's zero or not set. // Creating new document return (T)insertById(clazz).inScope(scope) .inCollection(collection) + .withOptions((InsertOptions) options) .one(entity); } } diff --git a/src/main/java/org/springframework/data/couchbase/core/CouchbaseTemplateSupport.java b/src/main/java/org/springframework/data/couchbase/core/CouchbaseTemplateSupport.java index 05277c691..5250d7e72 100644 --- a/src/main/java/org/springframework/data/couchbase/core/CouchbaseTemplateSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/CouchbaseTemplateSupport.java @@ -30,6 +30,8 @@ import org.springframework.data.mapping.callback.EntityCallbacks; import org.springframework.util.Assert; +import java.time.Instant; + /** * Internal encode/decode support for CouchbaseTemplate. * @@ -62,9 +64,9 @@ public CouchbaseDocument encodeEntity(final Object entityToEncode) { } @Override - public T decodeEntity(Object id, String source, Long cas, Class entityClass, String scope, String collection, - Object txHolder, CouchbaseResourceHolder holder) { - return decodeEntityBase(id, source, cas, entityClass, scope, collection, txHolder, holder); + public T decodeEntity(Object id, String source, Long cas, Instant expiryTime, Class entityClass, + String scope, String collection, Object txHolder, CouchbaseResourceHolder holder) { + return decodeEntityBase(id, source, cas, expiryTime, entityClass, scope, collection, txHolder, holder); } @Override diff --git a/src/main/java/org/springframework/data/couchbase/core/ExecutableExistsByIdOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ExecutableExistsByIdOperationSupport.java index 5dfc2429e..74f936f68 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ExecutableExistsByIdOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ExecutableExistsByIdOperationSupport.java @@ -20,7 +20,6 @@ import org.springframework.data.couchbase.core.ReactiveExistsByIdOperationSupport.ReactiveExistsByIdSupport; import org.springframework.data.couchbase.core.query.OptionsBuilder; -import org.springframework.util.Assert; import com.couchbase.client.java.kv.ExistsOptions; @@ -82,8 +81,8 @@ public ExistsByIdWithOptions inCollection(final String collection) { @Override public TerminatingExistsById withOptions(final ExistsOptions options) { - Assert.notNull(options, "Options must not be null."); - return new ExecutableExistsByIdSupport(template, domainType, scope, collection, options); + return new ExecutableExistsByIdSupport(template, domainType, scope, collection, + options != null ? options : this.options); } @Override diff --git a/src/main/java/org/springframework/data/couchbase/core/ExecutableFindByAnalyticsOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ExecutableFindByAnalyticsOperationSupport.java index bfe71e111..40f0097da 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ExecutableFindByAnalyticsOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ExecutableFindByAnalyticsOperationSupport.java @@ -92,9 +92,8 @@ public TerminatingFindByAnalytics matching(final AnalyticsQuery query) { @Override public FindByAnalyticsWithQuery withOptions(final AnalyticsOptions options) { - Assert.notNull(options, "Options must not be null."); return new ExecutableFindByAnalyticsSupport<>(template, domainType, returnType, query, scanConsistency, scope, - collection, options); + collection, options != null ? options : this.options); } @Override diff --git a/src/main/java/org/springframework/data/couchbase/core/ExecutableFindByIdOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ExecutableFindByIdOperationSupport.java index e1bf1305b..20c585d4f 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ExecutableFindByIdOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ExecutableFindByIdOperationSupport.java @@ -78,8 +78,8 @@ public Collection all(final Collection ids) { @Override public TerminatingFindById withOptions(final GetOptions options) { - Assert.notNull(options, "Options must not be null."); - return new ExecutableFindByIdSupport<>(template, domainType, scope, collection, options, fields, expiry, lockDuration); + return new ExecutableFindByIdSupport<>(template, domainType, scope, collection, + options != null ? options : this.options, fields, expiry, lockDuration); } @Override diff --git a/src/main/java/org/springframework/data/couchbase/core/ExecutableFindByQueryOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ExecutableFindByQueryOperationSupport.java index e201f2e15..81a55a32c 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ExecutableFindByQueryOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ExecutableFindByQueryOperationSupport.java @@ -168,9 +168,8 @@ public boolean exists() { @Override public TerminatingFindByQuery withOptions(final QueryOptions options) { - Assert.notNull(options, "Options must not be null."); return new ExecutableFindByQuerySupport<>(template, domainType, returnType, query, scanConsistency, scope, - collection, options, distinctFields, fields); + collection, options != null ? options : this.options, distinctFields, fields); } @Override diff --git a/src/main/java/org/springframework/data/couchbase/core/ExecutableFindFromReplicasByIdOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ExecutableFindFromReplicasByIdOperationSupport.java index cdb1308e3..3746abfde 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ExecutableFindFromReplicasByIdOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ExecutableFindFromReplicasByIdOperationSupport.java @@ -19,7 +19,6 @@ import org.springframework.data.couchbase.core.ReactiveFindFromReplicasByIdOperationSupport.ReactiveFindFromReplicasByIdSupport; import org.springframework.data.couchbase.core.query.OptionsBuilder; -import org.springframework.util.Assert; import com.couchbase.client.java.kv.GetAnyReplicaOptions; @@ -71,8 +70,8 @@ public Collection any(Collection ids) { @Override public TerminatingFindFromReplicasById withOptions(final GetAnyReplicaOptions options) { - Assert.notNull(options, "Options must not be null."); - return new ExecutableFindFromReplicasByIdSupport<>(template, domainType, returnType, scope, collection, options); + return new ExecutableFindFromReplicasByIdSupport<>(template, domainType, returnType, scope, collection, + options != null ? options : this.options); } @Override 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 daf40b847..714948121 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ExecutableInsertByIdOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ExecutableInsertByIdOperationSupport.java @@ -85,8 +85,8 @@ public Collection all(Collection objects) { @Override public TerminatingInsertById withOptions(final InsertOptions options) { - Assert.notNull(options, "Options must not be null."); - return new ExecutableInsertByIdSupport<>(template, domainType, scope, collection, options, persistTo, replicateTo, + return new ExecutableInsertByIdSupport<>(template, domainType, scope, collection, + options != null ? options : this.options, persistTo, replicateTo, durabilityLevel, expiry); } 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 fee04bd2d..b6ac8d84e 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ExecutableMutateInByIdOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ExecutableMutateInByIdOperationSupport.java @@ -15,22 +15,22 @@ */ package org.springframework.data.couchbase.core; -import com.couchbase.client.core.msg.kv.DurabilityLevel; -import com.couchbase.client.java.kv.MutateInOptions; -import com.couchbase.client.java.kv.MutateInSpec; -import com.couchbase.client.java.kv.PersistTo; -import com.couchbase.client.java.kv.ReplicateTo; +import java.time.Duration; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.data.couchbase.core.mapping.CouchbaseDocument; import org.springframework.data.couchbase.core.query.OptionsBuilder; -import org.springframework.data.couchbase.core.support.PseudoArgs; import org.springframework.util.Assert; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; -import java.time.Duration; -import java.util.*; +import com.couchbase.client.core.msg.kv.DurabilityLevel; +import com.couchbase.client.java.kv.MutateInOptions; +import com.couchbase.client.java.kv.PersistTo; +import com.couchbase.client.java.kv.ReplicateTo; /** * {@link ExecutableMutateInByIdOperation} implementations for Couchbase. @@ -49,7 +49,7 @@ public ExecutableMutateInByIdOperationSupport(final CouchbaseTemplate template) @Override public ExecutableMutateInById mutateInById(final Class domainType) { Assert.notNull(domainType, "DomainType must not be null!"); - return new ExecutableMutateInByIdSupport(template, domainType, OptionsBuilder.getScopeFrom(domainType), + return new ExecutableMutateInByIdSupport<>(template, domainType, OptionsBuilder.getScopeFrom(domainType), OptionsBuilder.getCollectionFrom(domainType), null, OptionsBuilder.getPersistTo(domainType), OptionsBuilder.getReplicateTo(domainType), OptionsBuilder.getDurabilityLevel(domainType, template.getConverter()), null, Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), @@ -109,8 +109,8 @@ public Collection all(Collection objects) { @Override public TerminatingMutateInById withOptions(final MutateInOptions options) { - Assert.notNull(options, "Options must not be null."); - return new ExecutableMutateInByIdSupport(template, domainType, scope, collection, options, persistTo, replicateTo, + return new ExecutableMutateInByIdSupport<>(template, domainType, scope, collection, + options != null ? options : this.options, persistTo, replicateTo, durabilityLevel, expiry, removePaths, upsertPaths, insertPaths, replacePaths, provideCas); } diff --git a/src/main/java/org/springframework/data/couchbase/core/ExecutableRangeScanOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ExecutableRangeScanOperationSupport.java index 656aaa819..27e641008 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ExecutableRangeScanOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ExecutableRangeScanOperationSupport.java @@ -19,7 +19,6 @@ import org.springframework.data.couchbase.core.ReactiveRangeScanOperationSupport.ReactiveRangeScanSupport; import org.springframework.data.couchbase.core.query.OptionsBuilder; -import org.springframework.util.Assert; import com.couchbase.client.java.kv.MutationState; import com.couchbase.client.java.kv.ScanOptions; @@ -70,8 +69,8 @@ static class ExecutableRangeScanSupport implements ExecutableRangeScan { @Override public TerminatingRangeScan withOptions(final ScanOptions options) { - Assert.notNull(options, "Options must not be null."); - return new ExecutableRangeScanSupport<>(template, domainType, scope, collection, options, sort, + return new ExecutableRangeScanSupport<>(template, domainType, scope, collection, + options != null ? options : this.options, sort, mutationState, batchItemLimit, batchByteLimit); } 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 95a6d775a..c97372fbd 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ExecutableRemoveByIdOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ExecutableRemoveByIdOperationSupport.java @@ -128,8 +128,8 @@ public RemoveByIdInScope withDurability(final PersistTo persistTo, final Replica @Override public TerminatingRemoveById withOptions(final RemoveOptions options) { - Assert.notNull(options, "Options must not be null."); - return new ExecutableRemoveByIdSupport(template, domainType, scope, collection, options, persistTo, replicateTo, + return new ExecutableRemoveByIdSupport(template, domainType, scope, collection, + options != null ? options : this.options, persistTo, replicateTo, durabilityLevel, cas); } diff --git a/src/main/java/org/springframework/data/couchbase/core/ExecutableRemoveByQueryOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ExecutableRemoveByQueryOperationSupport.java index da04c6ab7..52581af92 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ExecutableRemoveByQueryOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ExecutableRemoveByQueryOperationSupport.java @@ -20,7 +20,6 @@ import org.springframework.data.couchbase.core.ReactiveRemoveByQueryOperationSupport.ReactiveRemoveByQuerySupport; import org.springframework.data.couchbase.core.query.OptionsBuilder; import org.springframework.data.couchbase.core.query.Query; -import org.springframework.util.Assert; import com.couchbase.client.java.query.QueryOptions; import com.couchbase.client.java.query.QueryScanConsistency; @@ -97,9 +96,8 @@ public RemoveByQueryWithConsistency inCollection(final String collection) { @Override public RemoveByQueryWithQuery withOptions(final QueryOptions options) { - Assert.notNull(options, "Options must not be null."); return new ExecutableRemoveByQuerySupport<>(template, domainType, query, scanConsistency, scope, collection, - options); + options != null ? options : this.options); } @Override 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 65d9b3a5a..5150d3264 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ExecutableReplaceByIdOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ExecutableReplaceByIdOperationSupport.java @@ -113,8 +113,8 @@ public ReplaceByIdWithDurability withExpiry(final Duration expiry) { @Override public TerminatingReplaceById withOptions(final ReplaceOptions options) { - Assert.notNull(options, "Options must not be null."); - return new ExecutableReplaceByIdSupport<>(template, domainType, scope, collection, options, persistTo, + return new ExecutableReplaceByIdSupport<>(template, domainType, scope, collection, + options != null ? options : this.options, persistTo, replicateTo, durabilityLevel, expiry); } 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 fbeb540a5..de896f045 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ExecutableUpsertByIdOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ExecutableUpsertByIdOperationSupport.java @@ -85,8 +85,8 @@ public Collection all(Collection objects) { @Override public TerminatingUpsertById withOptions(final UpsertOptions options) { - Assert.notNull(options, "Options must not be null."); - return new ExecutableUpsertByIdSupport<>(template, domainType, scope, collection, options, persistTo, replicateTo, + return new ExecutableUpsertByIdSupport<>(template, domainType, scope, collection, + options != null ? options : this.options, persistTo, replicateTo, durabilityLevel, expiry); } diff --git a/src/main/java/org/springframework/data/couchbase/core/NonReactiveSupportWrapper.java b/src/main/java/org/springframework/data/couchbase/core/NonReactiveSupportWrapper.java index 4c19404c9..c337a4e81 100644 --- a/src/main/java/org/springframework/data/couchbase/core/NonReactiveSupportWrapper.java +++ b/src/main/java/org/springframework/data/couchbase/core/NonReactiveSupportWrapper.java @@ -21,6 +21,8 @@ import org.springframework.data.couchbase.core.mapping.CouchbaseDocument; import org.springframework.data.couchbase.transaction.CouchbaseResourceHolder; +import java.time.Instant; + /** * Wrapper of {@link TemplateSupport} methods to adapt them to {@link ReactiveTemplateSupport}. * @@ -42,9 +44,10 @@ public Mono encodeEntity(Object entityToEncode) { } @Override - public Mono decodeEntity(Object id, String source, Long cas, Class entityClass, String scope, String collection, - Object txResultHolder, CouchbaseResourceHolder holder) { - return Mono.fromSupplier(() -> support.decodeEntity(id, source, cas, entityClass, scope, collection, txResultHolder, holder)); + public Mono decodeEntity(Object id, String source, Long cas, Instant expiryTime, Class entityClass, + String scope, String collection, Object txResultHolder, CouchbaseResourceHolder holder) { + return Mono.fromSupplier(() -> support.decodeEntity(id, source, cas, expiryTime, entityClass, scope, collection, + txResultHolder, holder)); } @Override diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveCouchbaseTemplateSupport.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveCouchbaseTemplateSupport.java index 558eed09e..9f09ae1c8 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveCouchbaseTemplateSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveCouchbaseTemplateSupport.java @@ -18,6 +18,8 @@ import reactor.core.publisher.Mono; +import java.time.Instant; + import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; @@ -69,10 +71,11 @@ ReactiveCouchbaseTemplate getReactiveTemplate() { } @Override - public Mono decodeEntity(Object id, String source, Long cas, Class entityClass, String scope, - String collection, Object txResultHolder, CouchbaseResourceHolder holder) { + public Mono decodeEntity(Object id, String source, Long cas, Instant expiryTime, Class entityClass, + String scope, String collection, Object txResultHolder, CouchbaseResourceHolder holder) { return Mono - .fromSupplier(() -> decodeEntityBase(id, source, cas, entityClass, scope, collection, txResultHolder, holder)); + .fromSupplier(() -> decodeEntityBase(id, source, cas, expiryTime, entityClass, scope, collection, + txResultHolder, holder)); } @Override diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveExistsByIdOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveExistsByIdOperationSupport.java index f8e03ea4c..c7164dd2e 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveExistsByIdOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveExistsByIdOperationSupport.java @@ -27,7 +27,6 @@ import org.slf4j.LoggerFactory; import org.springframework.data.couchbase.core.query.OptionsBuilder; import org.springframework.data.couchbase.core.support.PseudoArgs; -import org.springframework.util.Assert; import com.couchbase.client.java.kv.ExistsOptions; import com.couchbase.client.java.kv.ExistsResult; @@ -112,8 +111,8 @@ public ExistsByIdWithOptions inCollection(final String collection) { @Override public TerminatingExistsById withOptions(final ExistsOptions options) { - Assert.notNull(options, "Options must not be null."); - return new ReactiveExistsByIdSupport(template, domainType, scope, collection, options); + return new ReactiveExistsByIdSupport(template, domainType, scope, collection, + options != null ? options : this.options); } @Override diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByAnalyticsOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByAnalyticsOperationSupport.java index aab918318..09da4a064 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByAnalyticsOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByAnalyticsOperationSupport.java @@ -139,7 +139,8 @@ public Flux all() { } row.removeKey(TemplateUtils.SELECT_ID); row.removeKey(TemplateUtils.SELECT_CAS); - return support.decodeEntity(id, row.toString(), cas, returnType, null, null, null, null); + return support.decodeEntity(id, row.toString(), cas, null /* EXPIRY from query*/ , + returnType, null, null, null, null); }); }); } @@ -170,9 +171,8 @@ public Mono exists() { @Override public TerminatingFindByAnalytics withOptions(final AnalyticsOptions options) { - Assert.notNull(options, "Options must not be null."); return new ReactiveFindByAnalyticsSupport<>(template, domainType, returnType, query, scanConsistency, scope, - collection, options, support); + collection, options != null ? options : this.options, support); } @Override diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByIdOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByIdOperationSupport.java index 6a848aa69..dd5501abd 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByIdOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByIdOperationSupport.java @@ -15,11 +15,10 @@ */ package org.springframework.data.couchbase.core; -import static com.couchbase.client.java.kv.GetAndTouchOptions.getAndTouchOptions; import static com.couchbase.client.java.kv.GetAndLockOptions.getAndLockOptions; +import static com.couchbase.client.java.kv.GetAndTouchOptions.getAndTouchOptions; import static com.couchbase.client.java.transactions.internal.ConverterUtil.makeCollectionIdentifier; -import com.couchbase.client.java.kv.GetAndLockOptions; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -40,7 +39,7 @@ import com.couchbase.client.core.error.DocumentNotFoundException; import com.couchbase.client.java.CommonOptions; import com.couchbase.client.java.ReactiveCollection; -import com.couchbase.client.java.codec.RawJsonTranscoder; +import com.couchbase.client.java.kv.GetAndLockOptions; import com.couchbase.client.java.kv.GetAndTouchOptions; import com.couchbase.client.java.kv.GetOptions; @@ -95,8 +94,11 @@ static class ReactiveFindByIdSupport implements ReactiveFindById { @Override public Mono one(final Object id) { - CommonOptions gOptions = initGetOptions(); - PseudoArgs pArgs = new PseudoArgs(template, scope, collection, gOptions, domainType); + PseudoArgs testPargs = new PseudoArgs(template, scope, collection, null, domainType); + CommonOptions gOptions = testPargs.getOptions() != null ? (CommonOptions) testPargs.getOptions() + : initGetOptions(); + PseudoArgs pArgs = new PseudoArgs(template, testPargs.getScope(), testPargs.getCollection(), gOptions, + domainType); if (LOG.isDebugEnabled()) { LOG.debug("findById key={} {}", id, pArgs); } @@ -106,23 +108,31 @@ public Mono one(final Object id) { Mono reactiveEntity = TransactionalSupport.checkForTransactionInThreadLocalStorage().flatMap(ctxOpt -> { if (!ctxOpt.isPresent()) { if (pArgs.getOptions() instanceof GetAndTouchOptions options) { - return rc.getAndTouch(id.toString(), expiryToUse, options) - .flatMap(result -> support.decodeEntity(id, result.contentAs(String.class), result.cas(), domainType, - pArgs.getScope(), pArgs.getCollection(), null, null)); + return rc + .getAndTouch(id.toString(), expiryToUse, + buildOptions((GetAndTouchOptions) pArgs.getOptions())) + .flatMap(result -> support.decodeEntity(id, result.contentAs(String.class), + result.cas(), result.expiryTime().orElse(null), domainType, pArgs.getScope(), + pArgs.getCollection(), null, null)); } else if (pArgs.getOptions() instanceof GetAndLockOptions options) { - return rc.getAndLock(id.toString(), Optional.of(lockDuration).orElse(Duration.ZERO), options) - .flatMap(result -> support.decodeEntity(id, result.contentAs(String.class), result.cas(), domainType, - pArgs.getScope(), pArgs.getCollection(), null, null)); + return rc + .getAndLock(id.toString(), Optional.of(lockDuration).orElse(Duration.ZERO), + buildOptions((GetAndLockOptions) pArgs.getOptions())) + .flatMap(result -> support.decodeEntity(id, result.contentAs(String.class), + result.cas(), result.expiryTime().orElse(null), domainType, pArgs.getScope(), + pArgs.getCollection(), null, null)); } else { - return rc.get(id.toString(), (GetOptions) pArgs.getOptions()) - .flatMap(result -> support.decodeEntity(id, result.contentAs(String.class), result.cas(), domainType, + return rc.get(id.toString(), buildOptions((GetOptions) pArgs.getOptions())) + .flatMap(result -> support.decodeEntity(id, result.contentAs(String.class), + result.cas(), result.expiryTime().orElse(null), domainType, pArgs.getScope(), pArgs.getCollection(), null, null)); } } else { rejectInvalidTransactionalOptions(); return ctxOpt.get().getCore().get(makeCollectionIdentifier(rc.async()), id.toString()) .flatMap(result -> support.decodeEntity(id, new String(result.contentAsBytes(), StandardCharsets.UTF_8), - result.cas(), domainType, pArgs.getScope(), pArgs.getCollection(), null, null)); + result.cas(), null, domainType, pArgs.getScope(), pArgs.getCollection(), null, + null)); } }); @@ -161,10 +171,22 @@ public Flux all(final Collection ids) { return Flux.fromIterable(ids).flatMap(this::one); } + public GetOptions buildOptions(GetOptions options) { + return OptionsBuilder.buildGetOptions(options); + } + + public GetAndTouchOptions buildOptions(GetAndTouchOptions options) { + return OptionsBuilder.buildGetAndTouchOptions(options); + } + + public GetAndLockOptions buildOptions(GetAndLockOptions options) { + return OptionsBuilder.buildGetAndLockOptions(options); + } + @Override public FindByIdInScope withOptions(final GetOptions options) { - Assert.notNull(options, "Options must not be null."); - return new ReactiveFindByIdSupport<>(template, domainType, scope, collection, options, fields, expiry, + return new ReactiveFindByIdSupport<>(template, domainType, scope, collection, + options != null ? options : this.options, fields, expiry, lockDuration, support); } @@ -205,11 +227,7 @@ private CommonOptions initGetOptions() { .getRequiredPersistentEntity(domainType); Boolean isTouchOnRead = entity.isTouchOnRead(); if(lockDuration != null || options instanceof GetAndLockOptions) { - GetAndLockOptions gOptions = options != null ? (GetAndLockOptions) options : getAndLockOptions(); - if (gOptions.build().transcoder() == null) { - gOptions.transcoder(RawJsonTranscoder.INSTANCE); - } - getOptions = gOptions; + getOptions = options != null ? (GetAndLockOptions) options : getAndLockOptions(); } else if (expiry != null || isTouchOnRead || options instanceof GetAndTouchOptions) { if (expiry != null) { expiryToUse = expiry; @@ -218,16 +236,9 @@ private CommonOptions initGetOptions() { } else { expiryToUse = Duration.ZERO; } - GetAndTouchOptions gOptions = options != null ? (GetAndTouchOptions) options : getAndTouchOptions(); - if (gOptions.build().transcoder() == null) { - gOptions.transcoder(RawJsonTranscoder.INSTANCE); - } - getOptions = gOptions; + getOptions = options != null ? (GetAndTouchOptions) options : getAndTouchOptions(); } else { GetOptions gOptions = options != null ? (GetOptions) options : GetOptions.getOptions(); - if (gOptions.build().transcoder() == null) { - gOptions.transcoder(RawJsonTranscoder.INSTANCE); - } if (fields != null && !fields.isEmpty()) { gOptions.project(fields); } diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByQueryOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByQueryOperationSupport.java index 523d882b3..6eeb83dd5 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByQueryOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByQueryOperationSupport.java @@ -15,8 +15,6 @@ */ package org.springframework.data.couchbase.core; -import com.couchbase.client.core.api.query.CoreQueryContext; -import com.couchbase.client.core.api.query.CoreQueryOptions; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -29,6 +27,8 @@ import org.springframework.data.couchbase.core.support.TemplateUtils; import org.springframework.util.Assert; +import com.couchbase.client.core.api.query.CoreQueryContext; +import com.couchbase.client.core.api.query.CoreQueryOptions; import com.couchbase.client.java.ReactiveScope; import com.couchbase.client.java.codec.JsonSerializer; import com.couchbase.client.java.query.QueryOptions; @@ -109,9 +109,8 @@ public FindByQueryWithQuery matching(Query query) { @Override public TerminatingFindByQuery withOptions(final QueryOptions options) { - Assert.notNull(options, "Options must not be null."); return new ReactiveFindByQuerySupport<>(template, domainType, returnType, query, scanConsistency, scope, - collection, options, distinctFields, fields, support); + collection, options != null ? options : this.options, distinctFields, fields, support); } @Override @@ -228,8 +227,8 @@ public Flux all() { row.removeKey(TemplateUtils.SELECT_ID); row.removeKey(TemplateUtils.SELECT_CAS); } - return support.decodeEntity(id, row.toString(), cas, returnType, pArgs.getScope(), pArgs.getCollection(), - null, null); + return support.decodeEntity(id, row.toString(), cas, null /* expiry from query */, returnType, + pArgs.getScope(), pArgs.getCollection(), null, null); }); } diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveFindFromReplicasByIdOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveFindFromReplicasByIdOperationSupport.java index a93302752..ca0a21108 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveFindFromReplicasByIdOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveFindFromReplicasByIdOperationSupport.java @@ -26,7 +26,6 @@ import org.slf4j.LoggerFactory; import org.springframework.data.couchbase.core.query.OptionsBuilder; import org.springframework.data.couchbase.core.support.PseudoArgs; -import org.springframework.util.Assert; import com.couchbase.client.java.codec.RawJsonTranscoder; import com.couchbase.client.java.kv.GetAnyReplicaOptions; @@ -86,8 +85,9 @@ public Mono any(final String id) { return TransactionalSupport.verifyNotInTransaction("findFromReplicasById").then(Mono.just(id)) .flatMap(docId -> template.getCouchbaseClientFactory().withScope(pArgs.getScope()) .getCollection(pArgs.getCollection()).reactive().getAnyReplica(docId, pArgs.getOptions())) - .flatMap(result -> support.decodeEntity(id, result.contentAs(String.class), result.cas(), returnType, - pArgs.getScope(), pArgs.getCollection(), null, null)) + .flatMap(result -> support.decodeEntity(id, result.contentAs(String.class), result.cas(), + result.expiryTime().orElse(null), returnType, pArgs.getScope(), pArgs.getCollection(), null, + null)) .onErrorMap(throwable -> { if (throwable instanceof RuntimeException) { return template.potentiallyConvertRuntimeException((RuntimeException) throwable); @@ -104,8 +104,8 @@ public Flux any(Collection ids) { @Override public TerminatingFindFromReplicasById withOptions(final GetAnyReplicaOptions options) { - Assert.notNull(options, "Options must not be null."); - return new ReactiveFindFromReplicasByIdSupport<>(template, domainType, returnType, scope, collection, options, + return new ReactiveFindFromReplicasByIdSupport<>(template, domainType, returnType, scope, collection, + options != null ? options : this.options, support); } 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 28add4a4b..05b64c4f6 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveInsertByIdOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveInsertByIdOperationSupport.java @@ -160,8 +160,8 @@ public InsertOptions buildOptions(InsertOptions options, CouchbaseDocument doc) @Override public TerminatingInsertById withOptions(final InsertOptions options) { - Assert.notNull(options, "Options must not be null."); - return new ReactiveInsertByIdSupport<>(template, domainType, scope, collection, options, persistTo, replicateTo, + return new ReactiveInsertByIdSupport<>(template, domainType, scope, collection, + options != null ? options : this.options, persistTo, replicateTo, durabilityLevel, expiry, 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 3dc1fd4f2..6307c4589 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveMutateInByIdOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveMutateInByIdOperationSupport.java @@ -15,23 +15,29 @@ */ package org.springframework.data.couchbase.core; -import com.couchbase.client.core.msg.kv.DurabilityLevel; -import com.couchbase.client.java.kv.MutateInOptions; -import com.couchbase.client.java.kv.MutateInSpec; -import com.couchbase.client.java.kv.PersistTo; -import com.couchbase.client.java.kv.ReplicateTo; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.data.couchbase.core.mapping.CouchbaseDocument; -import org.springframework.data.couchbase.core.mapping.CouchbaseList; import org.springframework.data.couchbase.core.query.OptionsBuilder; import org.springframework.data.couchbase.core.support.PseudoArgs; import org.springframework.util.Assert; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; -import java.time.Duration; -import java.util.*; +import com.couchbase.client.core.msg.kv.DurabilityLevel; +import com.couchbase.client.java.kv.MutateInOptions; +import com.couchbase.client.java.kv.MutateInSpec; +import com.couchbase.client.java.kv.PersistTo; +import com.couchbase.client.java.kv.ReplicateTo; /** * {@link ReactiveMutateInByIdOperation} implementations for Couchbase. @@ -131,8 +137,8 @@ public Flux all(Collection objects) { @Override public TerminatingMutateInById withOptions(final MutateInOptions options) { - Assert.notNull(options, "Options must not be null."); - return new ReactiveMutateInByIdSupport(template, domainType, scope, collection, options, persistTo, replicateTo, + return new ReactiveMutateInByIdSupport(template, domainType, scope, collection, + options != null ? options : this.options, persistTo, replicateTo, durabilityLevel, expiry, support, removePaths, upsertPaths, insertPaths, replacePaths, provideCas); } diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveRangeScanOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveRangeScanOperationSupport.java index 8e0c825e4..cbc8df98a 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveRangeScanOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveRangeScanOperationSupport.java @@ -23,7 +23,6 @@ import org.slf4j.LoggerFactory; import org.springframework.data.couchbase.core.query.OptionsBuilder; import org.springframework.data.couchbase.core.support.PseudoArgs; -import org.springframework.util.Assert; import com.couchbase.client.java.ReactiveCollection; import com.couchbase.client.java.kv.MutationState; @@ -77,8 +76,8 @@ static class ReactiveRangeScanSupport implements ReactiveRangeScan { @Override public TerminatingRangeScan withOptions(final ScanOptions options) { - Assert.notNull(options, "Options must not be null."); - return new ReactiveRangeScanSupport<>(template, domainType, scope, collection, options, sort, + return new ReactiveRangeScanSupport<>(template, domainType, scope, collection, + options != null ? options : this.options, sort, mutationState, batchItemLimit, batchByteLimit, support); } @@ -163,8 +162,9 @@ Flux rangeScan(String lower, String upper, boolean isSamplingScan, Long limit Flux reactiveEntities = TransactionalSupport.verifyNotInTransaction("rangeScan") .thenMany(rc.scan(scanType, buildScanOptions(pArgs.getOptions(), false)) .flatMap(result -> support.decodeEntity(result.id(), - new String(result.contentAsBytes(), StandardCharsets.UTF_8), result.cas(), domainType, - pArgs.getScope(), pArgs.getCollection(), null, null))); + new String(result.contentAsBytes(), StandardCharsets.UTF_8), result.cas(), + result.expiryTime().orElse(null), domainType, pArgs.getScope(), + pArgs.getCollection(), null, null))); return reactiveEntities.onErrorMap(throwable -> { if (throwable instanceof RuntimeException) { 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 ff8f8613c..082912406 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveRemoveByIdOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveRemoveByIdOperationSupport.java @@ -203,8 +203,9 @@ public RemoveByIdInCollection inScope(final String scope) { @Override public TerminatingRemoveById withOptions(final RemoveOptions options) { - Assert.notNull(options, "Options must not be null."); - return new ReactiveRemoveByIdSupport(template, domainType, scope, collection, options, persistTo, replicateTo, + return new ReactiveRemoveByIdSupport( + template, domainType, scope, collection, + options != null ? options : this.options, persistTo, replicateTo, durabilityLevel, cas); } diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveRemoveByQueryOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveRemoveByQueryOperationSupport.java index cc9b3c50e..d50061aed 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveRemoveByQueryOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveRemoveByQueryOperationSupport.java @@ -15,8 +15,6 @@ */ package org.springframework.data.couchbase.core; -import com.couchbase.client.core.api.query.CoreQueryContext; -import com.couchbase.client.core.io.CollectionIdentifier; import reactor.core.publisher.Flux; import java.util.Optional; @@ -28,8 +26,8 @@ import org.springframework.data.couchbase.core.query.Query; import org.springframework.data.couchbase.core.support.PseudoArgs; import org.springframework.data.couchbase.core.support.TemplateUtils; -import org.springframework.util.Assert; +import com.couchbase.client.core.api.query.CoreQueryContext; import com.couchbase.client.java.ReactiveScope; import com.couchbase.client.java.json.JsonObject; import com.couchbase.client.java.query.QueryOptions; @@ -150,9 +148,8 @@ private String assembleDeleteQuery(String scope, String collection) { @Override public RemoveByQueryWithQuery withOptions(final QueryOptions options) { - Assert.notNull(options, "Options must not be null."); return new ReactiveRemoveByQuerySupport<>(template, domainType, query, scanConsistency, scope, collection, - options); + options != null ? options : this.options); } @Override 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 05392598a..4dbea9056 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveReplaceByIdOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveReplaceByIdOperationSupport.java @@ -178,8 +178,8 @@ private ReplaceOptions buildReplaceOptions(ReplaceOptions options, T object, Cou @Override public TerminatingReplaceById withOptions(final ReplaceOptions options) { - Assert.notNull(options, "Options must not be null."); - return new ReactiveReplaceByIdSupport<>(template, domainType, scope, collection, options, persistTo, replicateTo, + return new ReactiveReplaceByIdSupport<>(template, domainType, scope, collection, + options != null ? options : this.options, persistTo, replicateTo, durabilityLevel, expiry, support); } diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveTemplateSupport.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveTemplateSupport.java index 24be1c749..9e9d1935d 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveTemplateSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveTemplateSupport.java @@ -17,6 +17,8 @@ import reactor.core.publisher.Mono; +import java.time.Instant; + import org.springframework.data.couchbase.core.convert.translation.TranslationService; import org.springframework.data.couchbase.core.mapping.CouchbaseDocument; import org.springframework.data.couchbase.transaction.CouchbaseResourceHolder; @@ -30,8 +32,8 @@ public interface ReactiveTemplateSupport { Mono encodeEntity(Object entityToEncode); - Mono decodeEntity(Object id, String source, Long cas, Class entityClass, String scope, String collection, - Object txResultHolder, CouchbaseResourceHolder holder); + Mono decodeEntity(Object id, String source, Long cas, Instant expiryTime, Class entityClass, String scope, + String collection, Object txResultHolder, CouchbaseResourceHolder holder); Mono applyResult(T entity, CouchbaseDocument converted, Object id, Long cas, Object txResultHolder, CouchbaseResourceHolder holder); 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 aa5bf920d..348eda73e 100644 --- a/src/main/java/org/springframework/data/couchbase/core/ReactiveUpsertByIdOperationSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveUpsertByIdOperationSupport.java @@ -122,8 +122,8 @@ private UpsertOptions buildUpsertOptions(UpsertOptions options, CouchbaseDocumen @Override public TerminatingUpsertById withOptions(final UpsertOptions options) { - Assert.notNull(options, "Options must not be null."); - return new ReactiveUpsertByIdSupport(template, domainType, scope, collection, options, persistTo, replicateTo, + return new ReactiveUpsertByIdSupport(template, domainType, scope, collection, + options != null ? options : this.options, persistTo, replicateTo, durabilityLevel, expiry, support); } diff --git a/src/main/java/org/springframework/data/couchbase/core/TemplateSupport.java b/src/main/java/org/springframework/data/couchbase/core/TemplateSupport.java index 935392ab4..b7e0a532f 100644 --- a/src/main/java/org/springframework/data/couchbase/core/TemplateSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/TemplateSupport.java @@ -15,6 +15,8 @@ */ package org.springframework.data.couchbase.core; +import java.time.Instant; + import org.springframework.data.couchbase.core.convert.translation.TranslationService; import org.springframework.data.couchbase.core.mapping.CouchbaseDocument; import org.springframework.data.couchbase.transaction.CouchbaseResourceHolder; @@ -26,8 +28,8 @@ public interface TemplateSupport { CouchbaseDocument encodeEntity(Object entityToEncode); - T decodeEntity(Object id, String source, Long cas, Class entityClass, String scope, String collection, - Object txResultHolder, CouchbaseResourceHolder holder); + T decodeEntity(Object id, String source, Long cas, Instant expiryTIme, Class entityClass, String scope, + String collection, Object txResultHolder, CouchbaseResourceHolder holder); T applyResult(T entity, CouchbaseDocument converted, Object id, long cas, Object txResultHolder, CouchbaseResourceHolder holder); 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 9a210a92d..4d4e20ac2 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 @@ -43,6 +43,7 @@ public class BasicCouchbasePersistentEntity extends BasicPersistentEntity, EnvironmentAware { private Environment environment; + private CouchbasePersistentProperty expiryProperty; /** * Create a new entity. @@ -82,6 +83,11 @@ public void setEnvironment(Environment environment) { @Override protected CouchbasePersistentProperty returnPropertyIfBetterIdPropertyCandidateOrNull( CouchbasePersistentProperty property) { + + if (expiryProperty == null && property.isExpirationProperty()) { + expiryProperty = property; + } + if (!property.isIdProperty()) { return null; } @@ -211,4 +217,9 @@ public CouchbasePersistentProperty getTextScoreProperty() { return null; } + @Override + public CouchbasePersistentProperty getExpiryProperty() { + return expiryProperty; + } + } 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 3a9b5aa0d..4ae07f3b7 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 @@ -76,4 +76,6 @@ public interface CouchbasePersistentEntity extends PersistentEntity domainType) { return null; } + static String toString(GetOptions o) { + StringBuilder s = new StringBuilder(); + GetOptions.Built b = o.build(); + s.append("{"); + s.append("withExpiry: " + b.withExpiry()); + s.append(", transcoder: " + b.transcoder()); + s.append(", timeout: " + b.timeout()); + s.append(", retryStrategy: " + b.retryStrategy()); + s.append(", clientContext: " + b.clientContext()); + s.append(", parentSpan: " + b.parentSpan()); + s.append("}"); + return s.toString(); + } + + static String toString(GetAndLockOptions o) { + StringBuilder s = new StringBuilder(); + GetAndLockOptions.Built b = o.build(); + s.append("{"); + s.append("transcoder: " + b.transcoder()); + s.append(", timeout: " + b.timeout()); + s.append(", retryStrategy: " + b.retryStrategy()); + s.append(", clientContext: " + b.clientContext()); + s.append(", parentSpan: " + b.parentSpan()); + s.append("}"); + return s.toString(); + } + + static String toString(GetAndTouchOptions o) { + StringBuilder s = new StringBuilder(); + GetAndTouchOptions.Built b = o.build(); + s.append("{"); + s.append("transcoder: " + b.transcoder()); + s.append(", timeout: " + b.timeout()); + s.append(", retryStrategy: " + b.retryStrategy()); + s.append(", clientContext: " + b.clientContext()); + s.append(", parentSpan: " + b.parentSpan()); + s.append("}"); + return s.toString(); + } + static String toString(InsertOptions o) { StringBuilder s = new StringBuilder(); InsertOptions.Built b = o.build(); s.append("{"); - s.append("durabilityLevel: " + b.durabilityLevel()); + s.append("expiry: " + b.expiry()); + s.append(", transcoder: " + b.transcoder()); + s.append(", durabilityLevel: " + b.durabilityLevel()); s.append(", persistTo: " + b.persistTo()); s.append(", replicateTo: " + b.replicateTo()); s.append(", timeout: " + b.timeout()); @@ -349,7 +418,9 @@ static String toString(UpsertOptions o) { StringBuilder s = new StringBuilder(); UpsertOptions.Built b = o.build(); s.append("{"); - s.append("durabilityLevel: " + b.durabilityLevel()); + s.append("expiry: " + b.expiry()); + s.append(", transcoder: " + b.transcoder()); + s.append(", durabilityLevel: " + b.durabilityLevel()); s.append(", persistTo: " + b.persistTo()); s.append(", replicateTo: " + b.replicateTo()); s.append(", timeout: " + b.timeout()); @@ -365,6 +436,8 @@ static String toString(ReplaceOptions o) { ReplaceOptions.Built b = o.build(); s.append("{"); s.append("cas: " + b.cas()); + s.append(", expiry: " + b.expiry()); + s.append(", transcoder: " + b.transcoder()); s.append(", durabilityLevel: " + b.durabilityLevel()); s.append(", persistTo: " + b.persistTo()); s.append(", replicateTo: " + b.replicateTo()); @@ -397,6 +470,7 @@ static String toString(MutateInOptions o) { MutateInOptions.Built b = o.build(); s.append("{"); s.append("cas: " + b.cas()); + s.append(", expiry: " + b.expiry()); s.append(", durabilityLevel: " + b.durabilityLevel()); s.append(", persistTo: " + b.persistTo()); s.append(", replicateTo: " + b.replicateTo()); @@ -572,4 +646,5 @@ public static CoreQueryContext queryContext(String scope, String collection, Str && (collection == null || CollectionIdentifier.DEFAULT_COLLECTION.equals(collection)) ? null : CoreQueryContext.of(bucketName, scope); } + } diff --git a/src/main/java/org/springframework/data/couchbase/core/support/PseudoArgs.java b/src/main/java/org/springframework/data/couchbase/core/support/PseudoArgs.java index 2e3db313f..e7a49a6c0 100644 --- a/src/main/java/org/springframework/data/couchbase/core/support/PseudoArgs.java +++ b/src/main/java/org/springframework/data/couchbase/core/support/PseudoArgs.java @@ -21,6 +21,10 @@ import com.couchbase.client.core.error.CouchbaseException; import com.couchbase.client.core.io.CollectionIdentifier; +import com.couchbase.client.java.codec.RawJsonTranscoder; +import com.couchbase.client.java.kv.GetAndLockOptions; +import com.couchbase.client.java.kv.GetAndTouchOptions; +import com.couchbase.client.java.kv.GetOptions; /** * Determine the arguments to be used in the operation from various sources @@ -29,7 +33,7 @@ * @param */ public class PseudoArgs { - private final OPTS options; + private OPTS options; private final String scopeName; private final String collectionName; @@ -114,6 +118,19 @@ public PseudoArgs(ReactiveCouchbaseTemplate template, String scope, String colle throw new CouchbaseException( new IllegalArgumentException("if scope is not default or null, then collection must be specified")); } + if (optionsForQuery instanceof GetAndLockOptions gOptions) { + if (gOptions.build().transcoder() == null) { + gOptions.transcoder(RawJsonTranscoder.INSTANCE); + } + } else if (optionsForQuery instanceof GetAndTouchOptions gOptions) { + if (gOptions.build().transcoder() == null) { + gOptions.transcoder(RawJsonTranscoder.INSTANCE); + } + } else if (optionsForQuery instanceof GetOptions gOptions) { + if (gOptions.build().transcoder() == null) { + gOptions.transcoder(RawJsonTranscoder.INSTANCE); + } + } this.options = optionsForQuery; } diff --git a/src/main/java/org/springframework/data/couchbase/repository/support/CouchbaseRepositoryBase.java b/src/main/java/org/springframework/data/couchbase/repository/support/CouchbaseRepositoryBase.java index e8d8edcfb..31c10c992 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/support/CouchbaseRepositoryBase.java +++ b/src/main/java/org/springframework/data/couchbase/repository/support/CouchbaseRepositoryBase.java @@ -18,6 +18,7 @@ import java.lang.reflect.AnnotatedElement; +import com.couchbase.client.java.CommonOptions; import org.springframework.data.couchbase.core.ReactiveCouchbaseTemplate; import org.springframework.data.couchbase.core.query.OptionsBuilder; import org.springframework.data.couchbase.core.support.PseudoArgs; @@ -110,6 +111,14 @@ protected String getCollection() { fromAnnotation); } + /** + * Get the options from repository.withOptions(...) + */ + protected CommonOptions getOptions() { + PseudoArgs> pa = (PseudoArgs>) getReactiveTemplate().getPseudoArgs(); + return pa != null ? pa.getOptions() : null; + } + protected abstract ReactiveCouchbaseTemplate getReactiveTemplate(); /** diff --git a/src/main/java/org/springframework/data/couchbase/repository/support/SimpleCouchbaseRepository.java b/src/main/java/org/springframework/data/couchbase/repository/support/SimpleCouchbaseRepository.java index f71ea46cd..12cbd92cd 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/support/SimpleCouchbaseRepository.java +++ b/src/main/java/org/springframework/data/couchbase/repository/support/SimpleCouchbaseRepository.java @@ -36,6 +36,7 @@ import org.springframework.data.util.Streamable; import org.springframework.util.Assert; +import com.couchbase.client.java.CommonOptions; import com.couchbase.client.java.query.QueryScanConsistency; /** @@ -72,11 +73,10 @@ public SimpleCouchbaseRepository(CouchbaseEntityInformation entityInf public S save(S entity) { String scopeName = getScope(); String collectionName = getCollection(); + CommonOptions options = getOptions(); // clear out the PseudoArgs here as whatever is called by operations.save() could be in a different thread. - // note that this will also clear out Options, but that's ok as any options would not work - // with all of insert/upsert/replace. If Options are needed, use template.insertById/upsertById/replaceById getReactiveTemplate().setPseudoArgs(null); - return operations.save(entity, scopeName, collectionName); + return operations.save(entity, options, scopeName, collectionName); } @Override @@ -84,11 +84,11 @@ public Iterable saveAll(Iterable entities) { Assert.notNull(entities, "The given Iterable of entities must not be null!"); String scopeName = getScope(); String collectionName = getCollection(); + CommonOptions options = getOptions(); // clear out the PseudoArgs here as whatever is called by operations.save() could be in a different thread. - // note that this will also clear out Options, but that's ok as any options would not work - // with all of insert/upsert/replace. If Options are needed, use template.insertById/upsertById/replaceById - getReactiveTemplate().setPseudoArgs(null); - return Streamable.of(entities).stream().map((e) -> operations.save(e,scopeName, collectionName)).collect(StreamUtils.toUnmodifiableList()); + getReactiveTemplate().setPseudoArgs(null); + return Streamable.of(entities).stream().map((e) -> operations.save(e, options, scopeName, collectionName)) + .collect(StreamUtils.toUnmodifiableList()); } @Override diff --git a/src/test/java/org/springframework/data/couchbase/repository/CouchbaseRepositoryQueryIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/repository/CouchbaseRepositoryQueryIntegrationTests.java index 7e0f4d75d..35f3a920f 100644 --- a/src/test/java/org/springframework/data/couchbase/repository/CouchbaseRepositoryQueryIntegrationTests.java +++ b/src/test/java/org/springframework/data/couchbase/repository/CouchbaseRepositoryQueryIntegrationTests.java @@ -30,6 +30,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static org.springframework.data.couchbase.config.BeanNames.COUCHBASE_TEMPLATE; +import com.couchbase.client.java.kv.GetOptions; import jakarta.validation.ConstraintViolationException; import junit.framework.AssertionFailedError; @@ -811,9 +812,15 @@ public void testCas() { @Test public void testExpiration() { Airport airport = new Airport("1", "iata21", "icao21"); - airportRepository.withOptions(InsertOptions.insertOptions().expiry(Duration.ofSeconds(10))).save(airport); - Airport foundAirport = airportRepository.findByIata(airport.getIata()); + + airportRepository.withOptions(InsertOptions.insertOptions().expiry(Duration.ofSeconds(100))).save(airport); + Airport foundAirportByQuery = airportRepository.findByIata(airport.getIata()); + assertNotEquals(0, foundAirportByQuery.getExpiration()); + + Airport foundAirport = airportRepository.withOptions(GetOptions.getOptions().withExpiry(true)) + .findById(airport.getId()).get(); assertNotEquals(0, foundAirport.getExpiration()); + airportRepository.delete(airport); }