Scopes and collections for repositories (#1149)

* Add support for scopes and collections for repositories.

Adds DynamicProxyable and DynamicInvocationHandler to
set scope/collection/options on PseudoArgs when calling
operations via repository interfaces.

Closes #963.

Co-authored-by: mikereiche <michael.reiche@couchbase.com>
This commit is contained in:
Michael Reiche
2021-08-09 09:57:29 -07:00
committed by mikereiche
parent 03dcde4cb8
commit 1581712765
97 changed files with 2617 additions and 962 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors
* Copyright 2012-2021 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.
@@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase;
import java.util.function.Supplier;
@@ -33,6 +32,9 @@ import com.couchbase.client.java.env.ClusterEnvironment;
/**
* The default implementation of a {@link CouchbaseClientFactory}.
*
* @author Michael Nitschinger
* @author Michael Reiche
*/
public class SimpleCouchbaseClientFactory implements CouchbaseClientFactory {
@@ -74,7 +76,7 @@ public class SimpleCouchbaseClientFactory implements CouchbaseClientFactory {
@Override
public CouchbaseClientFactory withScope(final String scopeName) {
return new SimpleCouchbaseClientFactory(cluster, bucket.name(), scopeName);
return new SimpleCouchbaseClientFactory(cluster, bucket.name(), scopeName != null ? scopeName : getScope().name());
}
@Override

View File

@@ -22,7 +22,6 @@ import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;

View File

@@ -28,7 +28,6 @@ import org.springframework.data.couchbase.core.index.CouchbasePersistentEntityIn
import org.springframework.data.couchbase.core.mapping.CouchbaseMappingContext;
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentEntity;
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty;
import org.springframework.data.couchbase.core.support.PseudoArgs;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.lang.Nullable;
@@ -107,13 +106,25 @@ public class CouchbaseTemplate implements CouchbaseOperations, ApplicationContex
}
@Override
@Deprecated
public ExecutableRemoveById removeById() {
return new ExecutableRemoveByIdOperationSupport(this).removeById();
return removeById(null);
}
@Override
public ExecutableRemoveById removeById(Class<?> domainType) {
return new ExecutableRemoveByIdOperationSupport(this).removeById(domainType);
}
@Override
@Deprecated
public ExecutableExistsById existsById() {
return new ExecutableExistsByIdOperationSupport(this).existsById();
return existsById(null);
}
@Override
public ExecutableExistsById existsById(Class<?> domainType) {
return new ExecutableExistsByIdOperationSupport(this).existsById(domainType);
}
@Override

View File

@@ -17,6 +17,8 @@
package org.springframework.data.couchbase.core;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
@@ -38,9 +40,6 @@ import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
import org.springframework.util.Assert;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Internal encode/decode support for CouchbaseTemplate.
*

View File

@@ -36,8 +36,14 @@ public interface ExecutableExistsByIdOperation {
/**
* Checks if the document exists in the bucket.
*/
@Deprecated
ExecutableExistsById existsById();
/**
* Checks if the document exists in the bucket.
*/
ExecutableExistsById existsById(Class<?> domainType);
/**
* Terminating operations invoking the actual execution.
*/
@@ -78,7 +84,6 @@ public interface ExecutableExistsByIdOperation {
}
/**
*
* Fluent method to specify the collection.
*
* @param <T> the entity type to use for the results.

View File

@@ -32,26 +32,34 @@ public class ExecutableExistsByIdOperationSupport implements ExecutableExistsByI
}
@Override
@Deprecated
public ExecutableExistsById existsById() {
return new ExecutableExistsByIdSupport(template, null, null, null);
return existsById(null);
}
@Override
public ExecutableExistsById existsById(Class<?> domainType) {
return new ExecutableExistsByIdSupport(template, domainType, null, null, null);
}
static class ExecutableExistsByIdSupport implements ExecutableExistsById {
private final CouchbaseTemplate template;
private final Class<?> domainType;
private final String scope;
private final String collection;
private final ExistsOptions options;
private final ReactiveExistsByIdSupport reactiveSupport;
ExecutableExistsByIdSupport(final CouchbaseTemplate template, final String scope, final String collection,
final ExistsOptions options) {
ExecutableExistsByIdSupport(final CouchbaseTemplate template, final Class<?> domainType, final String scope,
final String collection, final ExistsOptions options) {
this.template = template;
this.domainType = domainType;
this.scope = scope;
this.collection = collection;
this.options = options;
this.reactiveSupport = new ReactiveExistsByIdSupport(template.reactive(), scope, collection, options);
this.reactiveSupport = new ReactiveExistsByIdSupport(template.reactive(), domainType, scope, collection, options);
}
@Override
@@ -66,20 +74,18 @@ public class ExecutableExistsByIdOperationSupport implements ExecutableExistsByI
@Override
public ExistsByIdWithOptions inCollection(final String collection) {
Assert.hasText(collection, "Collection must not be null nor empty.");
return new ExecutableExistsByIdSupport(template, scope, collection, options);
return new ExecutableExistsByIdSupport(template, domainType, scope, collection, options);
}
@Override
public TerminatingExistsById withOptions(final ExistsOptions options) {
Assert.notNull(options, "Options must not be null.");
return new ExecutableExistsByIdSupport(template, scope, collection, options);
return new ExecutableExistsByIdSupport(template, domainType, scope, collection, options);
}
@Override
public ExistsByIdInCollection inScope(final String scope) {
Assert.hasText(scope, "Scope must not be null nor empty.");
return new ExecutableExistsByIdSupport(template, scope, collection, options);
return new ExecutableExistsByIdSupport(template, domainType, scope, collection, options);
}
}

View File

@@ -97,14 +97,12 @@ public class ExecutableFindByAnalyticsOperationSupport implements ExecutableFind
@Override
public FindByAnalyticsInCollection<T> inScope(final String scope) {
Assert.hasText(scope, "Scope must not be null nor empty.");
return new ExecutableFindByAnalyticsSupport<>(template, domainType, returnType, query, scanConsistency, scope,
collection, options);
}
@Override
public FindByAnalyticsWithConsistency<T> inCollection(final String collection) {
Assert.hasText(collection, "Collection must not be null nor empty.");
return new ExecutableFindByAnalyticsSupport<>(template, domainType, returnType, query, scanConsistency, scope,
collection, options);
}

View File

@@ -77,19 +77,17 @@ public class ExecutableFindByIdOperationSupport implements ExecutableFindByIdOpe
@Override
public FindByIdWithOptions<T> inCollection(final String collection) {
Assert.hasText(collection, "Collection must not be null nor empty.");
return new ExecutableFindByIdSupport<>(template, domainType, scope, collection, options, fields);
}
@Override
public FindByIdInCollection<T> inScope(final String scope) {
Assert.hasText(scope, "Scope must not be null nor empty.");
return new ExecutableFindByIdSupport<>(template, domainType, scope, collection, options, fields);
}
@Override
public FindByIdInScope<T> project(String... fields) {
Assert.notEmpty(fields, "Fields must not be null nor empty.");
Assert.notEmpty(fields, "Fields must not be null.");
return new ExecutableFindByIdSupport<>(template, domainType, scope, collection, options, Arrays.asList(fields));
}

View File

@@ -68,7 +68,8 @@ public class ExecutableFindByQueryOperationSupport implements ExecutableFindByQu
this.returnType = returnType;
this.query = query;
this.reactiveSupport = new ReactiveFindByQuerySupport<T>(template.reactive(), domainType, returnType, query,
scanConsistency, scope, collection, options, distinctFields, new NonReactiveSupportWrapper(template.support()));
scanConsistency, scope, collection, options, distinctFields,
new NonReactiveSupportWrapper(template.support()));
this.scanConsistency = scanConsistency;
this.scope = scope;
this.collection = collection;
@@ -126,8 +127,12 @@ public class ExecutableFindByQueryOperationSupport implements ExecutableFindByQu
@Override
public FindByQueryWithProjection<T> distinct(final String[] distinctFields) {
Assert.notNull(distinctFields, "distinctFields must not be null!");
// Coming from an annotation, this cannot be null.
// But a non-null but empty distinctFields means distinct on all fields
// So to indicate do not use distinct, we use {"-"} from the annotation, and here we change it to null.
String[] dFields = distinctFields.length == 1 && "-".equals(distinctFields[0]) ? null : distinctFields;
return new ExecutableFindByQuerySupport<>(template, domainType, returnType, query, scanConsistency, scope,
collection, options, distinctFields);
collection, options, dFields);
}
@Override
@@ -154,14 +159,12 @@ public class ExecutableFindByQueryOperationSupport implements ExecutableFindByQu
@Override
public FindByQueryInCollection<T> inScope(final String scope) {
Assert.hasText(scope, "Scope must not be null nor empty.");
return new ExecutableFindByQuerySupport<>(template, domainType, returnType, query, scanConsistency, scope,
collection, options, distinctFields);
}
@Override
public FindByQueryWithConsistency<T> inCollection(final String collection) {
Assert.hasText(collection, "Collection must not be null nor empty.");
return new ExecutableFindByQuerySupport<>(template, domainType, returnType, query, scanConsistency, scope,
collection, options, distinctFields);
}

View File

@@ -18,9 +18,9 @@ package org.springframework.data.couchbase.core;
import java.util.Collection;
import org.springframework.data.couchbase.core.ReactiveFindFromReplicasByIdOperationSupport.ReactiveFindFromReplicasByIdSupport;
import org.springframework.util.Assert;
import com.couchbase.client.java.kv.GetAnyReplicaOptions;
import org.springframework.util.Assert;
public class ExecutableFindFromReplicasByIdOperationSupport implements ExecutableFindFromReplicasByIdOperation {
@@ -54,7 +54,7 @@ public class ExecutableFindFromReplicasByIdOperationSupport implements Executabl
this.options = options;
this.returnType = returnType;
this.reactiveSupport = new ReactiveFindFromReplicasByIdSupport<>(template.reactive(), domainType, returnType,
scope, collection, options, new NonReactiveSupportWrapper(template.support()));
scope, collection, options, new NonReactiveSupportWrapper(template.support()));
}
@Override
@@ -75,13 +75,11 @@ public class ExecutableFindFromReplicasByIdOperationSupport implements Executabl
@Override
public FindFromReplicasByIdWithOptions<T> inCollection(final String collection) {
Assert.hasText(collection, "Collection must not be null nor empty.");
return new ExecutableFindFromReplicasByIdSupport<>(template, domainType, returnType, scope, collection, options);
}
@Override
public FindFromReplicasByIdInCollection<T> inScope(final String scope) {
Assert.hasText(scope, "Scope must not be null nor empty.");
return new ExecutableFindFromReplicasByIdSupport<>(template, domainType, returnType, scope, collection, options);
}

View File

@@ -89,14 +89,12 @@ public class ExecutableInsertByIdOperationSupport implements ExecutableInsertByI
@Override
public InsertByIdInCollection<T> inScope(final String scope) {
Assert.hasText(scope, "Scope must not be null nor empty.");
return new ExecutableInsertByIdSupport<>(template, domainType, scope, collection, options, persistTo, replicateTo,
durabilityLevel, expiry);
}
@Override
public InsertByIdWithOptions<T> inCollection(final String collection) {
Assert.hasText(collection, "Collection must not be null nor empty.");
return new ExecutableInsertByIdSupport<>(template, domainType, scope, collection, options, persistTo, replicateTo,
durabilityLevel, expiry);
}

View File

@@ -18,7 +18,6 @@ package org.springframework.data.couchbase.core;
import java.util.Collection;
import java.util.List;
import org.springframework.data.couchbase.core.query.WithConsistency;
import org.springframework.data.couchbase.core.support.InCollection;
import org.springframework.data.couchbase.core.support.InScope;
import org.springframework.data.couchbase.core.support.OneAndAllId;
@@ -39,6 +38,12 @@ public interface ExecutableRemoveByIdOperation {
/**
* Removes a document.
*/
ExecutableRemoveById removeById(Class<?> domainType);
/**
* Removes a document.
*/
@Deprecated
ExecutableRemoveById removeById();
/**

View File

@@ -35,14 +35,21 @@ public class ExecutableRemoveByIdOperationSupport implements ExecutableRemoveByI
}
@Override
@Deprecated
public ExecutableRemoveById removeById() {
return new ExecutableRemoveByIdSupport(template, null, null, null, PersistTo.NONE, ReplicateTo.NONE,
return removeById(null);
}
@Override
public ExecutableRemoveById removeById(Class<?> domainType) {
return new ExecutableRemoveByIdSupport(template, domainType, null, null, null, PersistTo.NONE, ReplicateTo.NONE,
DurabilityLevel.NONE, null);
}
static class ExecutableRemoveByIdSupport implements ExecutableRemoveById {
private final CouchbaseTemplate template;
private final Class<?> domainType;
private final String scope;
private final String collection;
private final RemoveOptions options;
@@ -52,18 +59,19 @@ public class ExecutableRemoveByIdOperationSupport implements ExecutableRemoveByI
private final Long cas;
private final ReactiveRemoveByIdSupport reactiveRemoveByIdSupport;
ExecutableRemoveByIdSupport(final CouchbaseTemplate template, final String scope, final String collection,
final RemoveOptions options, final PersistTo persistTo, final ReplicateTo replicateTo,
ExecutableRemoveByIdSupport(final CouchbaseTemplate template, final Class<?> domainType, final String scope,
final String collection, final RemoveOptions options, final PersistTo persistTo, final ReplicateTo replicateTo,
final DurabilityLevel durabilityLevel, Long cas) {
this.template = template;
this.domainType = domainType;
this.scope = scope;
this.collection = collection;
this.options = options;
this.persistTo = persistTo;
this.replicateTo = replicateTo;
this.durabilityLevel = durabilityLevel;
this.reactiveRemoveByIdSupport = new ReactiveRemoveByIdSupport(template.reactive(), scope, collection, options,
persistTo, replicateTo, durabilityLevel, cas);
this.reactiveRemoveByIdSupport = new ReactiveRemoveByIdSupport(template.reactive(), domainType, scope, collection,
options, persistTo, replicateTo, durabilityLevel, cas);
this.cas = cas;
}
@@ -79,15 +87,14 @@ public class ExecutableRemoveByIdOperationSupport implements ExecutableRemoveByI
@Override
public RemoveByIdWithOptions inCollection(final String collection) {
Assert.hasText(collection, "Collection must not be null nor empty.");
return new ExecutableRemoveByIdSupport(template, scope, collection, options, persistTo, replicateTo,
return new ExecutableRemoveByIdSupport(template, domainType, scope, collection, options, persistTo, replicateTo,
durabilityLevel, cas);
}
@Override
public RemoveByIdInCollection withDurability(final DurabilityLevel durabilityLevel) {
Assert.notNull(durabilityLevel, "Durability Level must not be null.");
return new ExecutableRemoveByIdSupport(template, scope, collection, options, persistTo, replicateTo,
return new ExecutableRemoveByIdSupport(template, domainType, scope, collection, options, persistTo, replicateTo,
durabilityLevel, cas);
}
@@ -95,27 +102,26 @@ public class ExecutableRemoveByIdOperationSupport implements ExecutableRemoveByI
public RemoveByIdInCollection withDurability(final PersistTo persistTo, final ReplicateTo replicateTo) {
Assert.notNull(persistTo, "PersistTo must not be null.");
Assert.notNull(replicateTo, "ReplicateTo must not be null.");
return new ExecutableRemoveByIdSupport(template, scope, collection, options, persistTo, replicateTo,
return new ExecutableRemoveByIdSupport(template, domainType, scope, collection, options, persistTo, replicateTo,
durabilityLevel, cas);
}
@Override
public TerminatingRemoveById withOptions(final RemoveOptions options) {
Assert.notNull(options, "Options must not be null.");
return new ExecutableRemoveByIdSupport(template, scope, collection, options, persistTo, replicateTo,
return new ExecutableRemoveByIdSupport(template, domainType, scope, collection, options, persistTo, replicateTo,
durabilityLevel, cas);
}
@Override
public RemoveByIdInCollection inScope(final String scope) {
Assert.hasText(scope, "Scope must not be null nor empty.");
return new ExecutableRemoveByIdSupport(template, scope, collection, options, persistTo, replicateTo,
return new ExecutableRemoveByIdSupport(template, domainType, scope, collection, options, persistTo, replicateTo,
durabilityLevel, cas);
}
@Override
public RemoveByIdWithDurability withCas(Long cas) {
return new ExecutableRemoveByIdSupport(template, scope, collection, options, persistTo, replicateTo,
return new ExecutableRemoveByIdSupport(template, domainType, scope, collection, options, persistTo, replicateTo,
durabilityLevel, cas);
}
}

View File

@@ -19,10 +19,10 @@ import java.util.List;
import org.springframework.data.couchbase.core.ReactiveRemoveByQueryOperationSupport.ReactiveRemoveByQuerySupport;
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;
import org.springframework.util.Assert;
public class ExecutableRemoveByQueryOperationSupport implements ExecutableRemoveByQueryOperation {
@@ -36,8 +36,7 @@ public class ExecutableRemoveByQueryOperationSupport implements ExecutableRemove
@Override
public <T> ExecutableRemoveByQuery<T> removeByQuery(Class<T> domainType) {
return new ExecutableRemoveByQuerySupport<>(template, domainType, ALL_QUERY, null, null,
null, null);
return new ExecutableRemoveByQuerySupport<>(template, domainType, ALL_QUERY, null, null, null, null);
}
static class ExecutableRemoveByQuerySupport<T> implements ExecutableRemoveByQuery<T> {
@@ -90,7 +89,6 @@ public class ExecutableRemoveByQueryOperationSupport implements ExecutableRemove
@Override
public RemoveByQueryWithConsistency<T> inCollection(final String collection) {
Assert.hasText(collection, "Collection must not be null nor empty.");
return new ExecutableRemoveByQuerySupport<>(template, domainType, query, scanConsistency, scope, collection,
options);
}
@@ -104,7 +102,6 @@ public class ExecutableRemoveByQueryOperationSupport implements ExecutableRemove
@Override
public RemoveByQueryInCollection<T> inScope(final String scope) {
Assert.hasText(scope, "Scope must not be null nor empty.");
return new ExecutableRemoveByQuerySupport<>(template, domainType, query, scanConsistency, scope, collection,
options);
}

View File

@@ -82,7 +82,6 @@ public class ExecutableReplaceByIdOperationSupport implements ExecutableReplaceB
@Override
public ReplaceByIdWithOptions<T> inCollection(final String collection) {
Assert.hasText(collection, "Collection must not be null nor empty.");
return new ExecutableReplaceByIdSupport<>(template, domainType, scope, collection, options, persistTo,
replicateTo, durabilityLevel, expiry);
}
@@ -118,7 +117,6 @@ public class ExecutableReplaceByIdOperationSupport implements ExecutableReplaceB
@Override
public ReplaceByIdInCollection<T> inScope(final String scope) {
Assert.hasText(scope, "Scope must not be null nor empty.");
return new ExecutableReplaceByIdSupport<>(template, domainType, scope, collection, options, persistTo,
replicateTo, durabilityLevel, expiry);
}

View File

@@ -89,14 +89,12 @@ public class ExecutableUpsertByIdOperationSupport implements ExecutableUpsertByI
@Override
public UpsertByIdInCollection<T> inScope(final String scope) {
Assert.hasText(scope, "Scope must not be null nor empty.");
return new ExecutableUpsertByIdSupport<>(template, domainType, scope, collection, options, persistTo, replicateTo,
durabilityLevel, expiry);
}
@Override
public UpsertByIdWithOptions<T> inCollection(final String collection) {
Assert.hasText(collection, "Collection must not be null nor empty.");
return new ExecutableUpsertByIdSupport<>(template, domainType, scope, collection, options, persistTo, replicateTo,
durabilityLevel, expiry);
}

View File

@@ -15,11 +15,11 @@
*/
package org.springframework.data.couchbase.core;
import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
import org.springframework.data.couchbase.core.mapping.event.CouchbaseMappingEvent;
import reactor.core.publisher.Mono;
import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
import org.springframework.data.couchbase.core.mapping.event.CouchbaseMappingEvent;
/**
* Wrapper of {@link TemplateSupport} methods to adapt them to {@link ReactiveTemplateSupport}.
*

View File

@@ -13,16 +13,17 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.core;
import org.springframework.data.couchbase.CouchbaseClientFactory;
import org.springframework.data.couchbase.core.convert.CouchbaseConverter;
import org.springframework.data.couchbase.core.support.PseudoArgs;
/**
* Defines common operations on the Couchbase data source, most commonly implemented by
* {@link ReactiveCouchbaseTemplate}.
*
* @author Michael Nitschinger
* @author Michael Reiche
*/
public interface ReactiveCouchbaseOperations extends ReactiveFluentCouchbaseOperations {
@@ -46,9 +47,4 @@ public interface ReactiveCouchbaseOperations extends ReactiveFluentCouchbaseOper
*/
CouchbaseClientFactory getCouchbaseClientFactory();
/**
* @@return the pseudoArgs from the ThreadLocal field of the CouchbaseOperations
*/
PseudoArgs<?> getPseudoArgs();
}

View File

@@ -43,7 +43,7 @@ public class ReactiveCouchbaseTemplate implements ReactiveCouchbaseOperations, A
private final CouchbaseConverter converter;
private final PersistenceExceptionTranslator exceptionTranslator;
private final ReactiveCouchbaseTemplateSupport templateSupport;
private ThreadLocal<PseudoArgs<?>> threadLocalArgs = new ThreadLocal<>();
private ThreadLocal<PseudoArgs<?>> threadLocalArgs = null;
public ReactiveCouchbaseTemplate(final CouchbaseClientFactory clientFactory, final CouchbaseConverter converter) {
this(clientFactory, converter, new JacksonTranslationService());
@@ -64,7 +64,12 @@ public class ReactiveCouchbaseTemplate implements ReactiveCouchbaseOperations, A
@Override
public ReactiveExistsById existsById() {
return new ReactiveExistsByIdOperationSupport(this).existsById();
return existsById(null);
}
@Override
public ReactiveExistsById existsById(Class<?> domainType) {
return new ReactiveExistsByIdOperationSupport(this).existsById(domainType);
}
@Override
@@ -89,7 +94,12 @@ public class ReactiveCouchbaseTemplate implements ReactiveCouchbaseOperations, A
@Override
public ReactiveRemoveById removeById() {
return new ReactiveRemoveByIdOperationSupport(this).removeById();
return removeById(null);
}
@Override
public ReactiveRemoveById removeById(Class<?> domainType) {
return new ReactiveRemoveByIdOperationSupport(this).removeById(domainType);
}
@Override
@@ -158,11 +168,18 @@ public class ReactiveCouchbaseTemplate implements ReactiveCouchbaseOperations, A
}
/**
* {@inheritDoc}
* @return the pseudoArgs from the ThreadLocal field
*/
@Override
public PseudoArgs<?> getPseudoArgs() {
return threadLocalArgs == null ? null : threadLocalArgs.get();
}
/**
* set the ThreadLocal field
*/
public void setPseudoArgs(PseudoArgs<?> threadLocalArgs) {
this.threadLocalArgs = new ThreadLocal<>();
this.threadLocalArgs.set(threadLocalArgs);
}
}

View File

@@ -16,8 +16,6 @@
package org.springframework.data.couchbase.core;
import org.springframework.data.couchbase.core.mapping.event.AfterSaveEvent;
import org.springframework.data.couchbase.core.mapping.event.ReactiveAfterSaveEvent;
import reactor.core.publisher.Mono;
import org.slf4j.Logger;
@@ -30,11 +28,12 @@ import org.springframework.data.couchbase.core.convert.translation.TranslationSe
import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentEntity;
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty;
import org.springframework.data.couchbase.core.mapping.event.AfterSaveEvent;
import org.springframework.data.couchbase.core.mapping.event.BeforeConvertEvent;
import org.springframework.data.couchbase.core.mapping.event.BeforeSaveEvent;
import org.springframework.data.couchbase.core.mapping.event.CouchbaseMappingEvent;
import org.springframework.data.couchbase.core.mapping.event.ReactiveAfterConvertCallback;
import org.springframework.data.couchbase.core.mapping.event.ReactiveBeforeConvertCallback;
import org.springframework.data.couchbase.core.mapping.event.ReactiveBeforeConvertEvent;
import org.springframework.data.couchbase.core.mapping.event.ReactiveBeforeSaveEvent;
import org.springframework.data.couchbase.repository.support.MappingCouchbaseEntityInformation;
import org.springframework.data.mapping.PersistentPropertyAccessor;
import org.springframework.data.mapping.callback.EntityCallbacks;
@@ -68,13 +67,13 @@ class ReactiveCouchbaseTemplateSupport implements ApplicationContextAware, React
@Override
public Mono<CouchbaseDocument> encodeEntity(final Object entityToEncode) {
return Mono.just(entityToEncode).doOnNext(entity -> maybeEmitEvent(new ReactiveBeforeConvertEvent<>(entity)))
return Mono.just(entityToEncode).doOnNext(entity -> maybeEmitEvent(new BeforeConvertEvent<>(entity)))
.flatMap(entity -> maybeCallBeforeConvert(entity, "")).map(maybeNewEntity -> {
final CouchbaseDocument converted = new CouchbaseDocument();
converter.write(maybeNewEntity, converted);
return converted;
}).flatMap(converted -> maybeCallAfterConvert(entityToEncode, converted, "").thenReturn(converted))
.doOnNext(converted -> maybeEmitEvent(new ReactiveBeforeSaveEvent<>(entityToEncode, converted)));
.doOnNext(converted -> maybeEmitEvent(new BeforeSaveEvent<>(entityToEncode, converted)));
}
@Override
@@ -112,7 +111,7 @@ class ReactiveCouchbaseTemplateSupport implements ApplicationContextAware, React
} else {
returnValue = entity;
}
maybeEmitEvent(new ReactiveAfterSaveEvent(returnValue, converted));
maybeEmitEvent(new AfterSaveEvent(returnValue, converted));
return returnValue;
});
}

View File

@@ -26,6 +26,7 @@ import org.springframework.data.couchbase.core.support.OneAndAllExistsReactive;
import org.springframework.data.couchbase.core.support.WithExistsOptions;
import com.couchbase.client.java.kv.ExistsOptions;
/**
* Exists Operations
*
@@ -37,8 +38,14 @@ public interface ReactiveExistsByIdOperation {
/**
* Checks if the document exists in the bucket.
*/
@Deprecated
ReactiveExistsById existsById();
/**
* Checks if the document exists in the bucket.
*/
ReactiveExistsById existsById(Class<?> domainType);
/**
* Terminating operations invoking the actual execution.
*/

View File

@@ -25,6 +25,7 @@ import java.util.Map;
import org.slf4j.Logger;
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;
@@ -41,20 +42,28 @@ public class ReactiveExistsByIdOperationSupport implements ReactiveExistsByIdOpe
}
@Override
@Deprecated
public ReactiveExistsById existsById() {
return new ReactiveExistsByIdSupport(template, null, null, null);
return existsById(null);
}
@Override
public ReactiveExistsById existsById(Class<?> domainType) {
return new ReactiveExistsByIdSupport(template, domainType, null, null, null);
}
static class ReactiveExistsByIdSupport implements ReactiveExistsById {
private final ReactiveCouchbaseTemplate template;
private final Class<?> domainType;
private final String scope;
private final String collection;
private final ExistsOptions options;
ReactiveExistsByIdSupport(final ReactiveCouchbaseTemplate template, final String scope, final String collection,
final ExistsOptions options) {
ReactiveExistsByIdSupport(final ReactiveCouchbaseTemplate template, final Class<?> domainType, final String scope,
final String collection, final ExistsOptions options) {
this.template = template;
this.domainType = domainType;
this.scope = scope;
this.collection = collection;
this.options = options;
@@ -62,12 +71,12 @@ public class ReactiveExistsByIdOperationSupport implements ReactiveExistsByIdOpe
@Override
public Mono<Boolean> one(final String id) {
PseudoArgs<ExistsOptions> pArgs = new PseudoArgs<>(template, scope, collection,
options != null ? options : ExistsOptions.existsOptions());
LOG.trace("statement: {} scope: {} collection: {}", "exitsById", pArgs.getScope(), pArgs.getCollection());
PseudoArgs<ExistsOptions> pArgs = new PseudoArgs<>(template, scope, collection, options, domainType);
LOG.trace("existsById {}", pArgs);
return Mono.just(id)
.flatMap(docId -> template.getCouchbaseClientFactory().withScope(pArgs.getScope())
.getCollection(pArgs.getCollection()).reactive().exists(id, pArgs.getOptions()).map(ExistsResult::exists))
.getCollection(pArgs.getCollection()).reactive().exists(id, buildOptions(pArgs.getOptions()))
.map(ExistsResult::exists))
.onErrorMap(throwable -> {
if (throwable instanceof RuntimeException) {
return template.potentiallyConvertRuntimeException((RuntimeException) throwable);
@@ -77,6 +86,10 @@ public class ReactiveExistsByIdOperationSupport implements ReactiveExistsByIdOpe
});
}
private ExistsOptions buildOptions(ExistsOptions options) {
return OptionsBuilder.buildExistsOptions(options);
}
@Override
public Mono<Map<String, Boolean>> all(final Collection<String> ids) {
return Flux.fromIterable(ids).flatMap(id -> one(id).map(result -> Tuples.of(id, result)))
@@ -85,20 +98,18 @@ public class ReactiveExistsByIdOperationSupport implements ReactiveExistsByIdOpe
@Override
public ExistsByIdWithOptions inCollection(final String collection) {
Assert.hasText(collection, "Collection must not be null nor empty.");
return new ReactiveExistsByIdSupport(template, scope, collection, options);
return new ReactiveExistsByIdSupport(template, domainType, scope, collection, options);
}
@Override
public TerminatingExistsById withOptions(final ExistsOptions options) {
Assert.notNull(options, "Options must not be null.");
return new ReactiveExistsByIdSupport(template, scope, collection, options);
return new ReactiveExistsByIdSupport(template, domainType, scope, collection, options);
}
@Override
public ExistsByIdInCollection inScope(final String scope) {
Assert.hasText(scope, "Scope must not be null nor empty.");
return new ReactiveExistsByIdSupport(template, scope, collection, options);
return new ReactiveExistsByIdSupport(template, domainType, scope, collection, options);
}
}

View File

@@ -167,14 +167,12 @@ public class ReactiveFindByAnalyticsOperationSupport implements ReactiveFindByAn
@Override
public FindByAnalyticsInCollection<T> inScope(final String scope) {
Assert.hasText(scope, "Scope must not be null nor empty.");
return new ReactiveFindByAnalyticsSupport<>(template, domainType, returnType, query, scanConsistency, scope,
collection, options, support);
}
@Override
public FindByAnalyticsWithConsistency<T> inCollection(final String collection) {
Assert.hasText(collection, "Collection must not be null nor empty.");
return new ReactiveFindByAnalyticsSupport<>(template, domainType, returnType, query, scanConsistency, scope,
collection, options, support);
}

View File

@@ -36,7 +36,6 @@ import com.couchbase.client.java.kv.GetOptions;
public class ReactiveFindByIdOperationSupport implements ReactiveFindByIdOperation {
private final ReactiveCouchbaseTemplate template;
private static final Logger LOG = LoggerFactory.getLogger(ReactiveFindByIdOperationSupport.class);
ReactiveFindByIdOperationSupport(ReactiveCouchbaseTemplate template) {
@@ -71,19 +70,19 @@ public class ReactiveFindByIdOperationSupport implements ReactiveFindByIdOperati
@Override
public Mono<T> one(final String id) {
return Mono.just(id).flatMap(docId -> {
GetOptions gOptions = options != null ? options : getOptions();
if (gOptions.build().transcoder() == null) {
gOptions.transcoder(RawJsonTranscoder.INSTANCE);
}
if (fields != null && !fields.isEmpty()) {
gOptions.project(fields);
}
PseudoArgs<GetOptions> pArgs = new PseudoArgs(template, scope, collection, gOptions);
LOG.trace("statement: {} scope: {} collection: {}", "findById", pArgs.getScope(), pArgs.getCollection());
return template.getCouchbaseClientFactory().withScope(pArgs.getScope()).getCollection(pArgs.getCollection())
.reactive().get(docId, pArgs.getOptions());
}).flatMap(result -> support.decodeEntity(id, result.contentAs(String.class), result.cas(), domainType))
GetOptions gOptions = options != null ? options : getOptions();
if (gOptions.build().transcoder() == null) {
gOptions.transcoder(RawJsonTranscoder.INSTANCE);
}
if (fields != null && !fields.isEmpty()) {
gOptions.project(fields);
}
PseudoArgs<GetOptions> pArgs = new PseudoArgs(template, scope, collection, gOptions, domainType);
LOG.trace("findById {}", pArgs);
return Mono.just(id)
.flatMap(docId -> template.getCouchbaseClientFactory().withScope(pArgs.getScope())
.getCollection(pArgs.getCollection()).reactive().get(docId, pArgs.getOptions()))
.flatMap(result -> support.decodeEntity(id, result.contentAs(String.class), result.cas(), domainType))
.onErrorResume(throwable -> {
if (throwable instanceof RuntimeException) {
if (throwable instanceof DocumentNotFoundException) {
@@ -113,19 +112,17 @@ public class ReactiveFindByIdOperationSupport implements ReactiveFindByIdOperati
@Override
public FindByIdWithOptions<T> inCollection(final String collection) {
Assert.hasText(collection, "Collection must not be null nor empty.");
return new ReactiveFindByIdSupport<>(template, domainType, scope, collection, options, fields, support);
}
@Override
public FindByIdInCollection<T> inScope(final String scope) {
Assert.hasText(scope, "Scope must not be null nor empty.");
return new ReactiveFindByIdSupport<>(template, domainType, scope, collection, options, fields, support);
}
@Override
public FindByIdInScope<T> project(String... fields) {
Assert.notEmpty(fields, "Fields must not be null nor empty.");
Assert.notNull(fields, "Fields must not be null");
return new ReactiveFindByIdSupport<>(template, domainType, scope, collection, options, Arrays.asList(fields),
support);
}

View File

@@ -89,8 +89,6 @@ public interface ReactiveFindByQueryOperation {
*/
Mono<Boolean> exists();
QueryOptions buildOptions(QueryOptions options);
}
/**
@@ -170,7 +168,7 @@ public interface ReactiveFindByQueryOperation {
}
/**
* Fluent method to specify scan consistency. Scan consistency may also come from an annotation.
* Fluent method to specify scan consistency. Scan consistency may also come from an annotation.
*
* @param <T> the entity type to use for the results.
*/

View File

@@ -41,7 +41,6 @@ public class ReactiveFindByQueryOperationSupport implements ReactiveFindByQueryO
private static final Query ALL_QUERY = new Query();
private final ReactiveCouchbaseTemplate template;
private static final Logger LOG = LoggerFactory.getLogger(ReactiveFindByQueryOperationSupport.class);
public ReactiveFindByQueryOperationSupport(final ReactiveCouchbaseTemplate template) {
@@ -62,11 +61,8 @@ public class ReactiveFindByQueryOperationSupport implements ReactiveFindByQueryO
private final Query query;
private final QueryScanConsistency scanConsistency;
private final String collection;
private String scope;
private final String scope;
private final String[] distinctFields;
// this would hold scanConsistency etc. from the fluent api if they were converted from standalone fields
// withScope(scopeName) could put raw("query_context",default:<bucket>.<scope>)
// this is not the options argument in save( entity, options ). That becomes query.getCouchbaseOptions()
private final QueryOptions options;
private final ReactiveTemplateSupport support;
@@ -91,7 +87,8 @@ public class ReactiveFindByQueryOperationSupport implements ReactiveFindByQueryO
@Override
public FindByQueryWithQuery<T> matching(Query query) {
QueryScanConsistency scanCons;
if (query.getScanConsistency() != null) {
if (query.getScanConsistency() != null) { // redundant, since buildQueryOptions() will use
// query.getScanConsistency()
scanCons = query.getScanConsistency();
} else {
scanCons = scanConsistency;
@@ -109,14 +106,12 @@ public class ReactiveFindByQueryOperationSupport implements ReactiveFindByQueryO
@Override
public FindByQueryInCollection<T> inScope(final String scope) {
Assert.hasText(scope, "Scope must not be null nor empty.");
return new ReactiveFindByQuerySupport<>(template, domainType, returnType, query, scanConsistency, scope,
collection, options, distinctFields, support);
}
@Override
public FindByQueryWithConsistency<T> inCollection(final String collection) {
Assert.hasText(collection, "Collection must not be null nor empty.");
return new ReactiveFindByQuerySupport<>(template, domainType, returnType, query, scanConsistency, scope,
collection, options, distinctFields, support);
}
@@ -142,10 +137,14 @@ public class ReactiveFindByQueryOperationSupport implements ReactiveFindByQueryO
}
@Override
public FindByQueryWithDistinct<T> distinct(String[] distinctFields) {
public FindByQueryWithDistinct<T> distinct(final String[] distinctFields) {
Assert.notNull(distinctFields, "distinctFields must not be null!");
// Coming from an annotation, this cannot be null.
// But a non-null but empty distinctFields means distinct on all fields
// So to indicate do not use distinct, we use {"-"} from the annotation, and here we change it to null.
String[] dFields = distinctFields.length == 1 && "-".equals(distinctFields[0]) ? null : distinctFields;
return new ReactiveFindByQuerySupport<>(template, domainType, returnType, query, scanConsistency, scope,
collection, options, distinctFields, support);
collection, options, dFields, support);
}
@Override
@@ -160,72 +159,65 @@ public class ReactiveFindByQueryOperationSupport implements ReactiveFindByQueryO
@Override
public Flux<T> all() {
return Flux.defer(() -> {
PseudoArgs<QueryOptions> pArgs = new PseudoArgs(template, scope, collection, options);
String statement = assembleEntityQuery(false, distinctFields, pArgs.getCollection());
LOG.trace("statement: {} {}", "findByQuery", statement);
Mono<ReactiveQueryResult> allResult = pArgs.getScope() == null
? template.getCouchbaseClientFactory().getCluster().reactive().query(statement,
buildOptions(pArgs.getOptions()))
: template.getCouchbaseClientFactory().withScope(pArgs.getScope()).getScope().reactive().query(statement,
buildOptions(pArgs.getOptions()));
return allResult.onErrorMap(throwable -> {
if (throwable instanceof RuntimeException) {
return template.potentiallyConvertRuntimeException((RuntimeException) throwable);
} else {
return throwable;
PseudoArgs<QueryOptions> pArgs = new PseudoArgs(template, scope, collection, options, domainType);
String statement = assembleEntityQuery(false, distinctFields, pArgs.getCollection());
LOG.trace("findByQuery {} statement: {}", pArgs, statement);
Mono<ReactiveQueryResult> allResult = pArgs.getScope() == null
? template.getCouchbaseClientFactory().getCluster().reactive().query(statement,
buildOptions(pArgs.getOptions()))
: template.getCouchbaseClientFactory().withScope(pArgs.getScope()).getScope().reactive().query(statement,
buildOptions(pArgs.getOptions()));
return Flux.defer(() -> allResult.onErrorMap(throwable -> {
if (throwable instanceof RuntimeException) {
return template.potentiallyConvertRuntimeException((RuntimeException) throwable);
} else {
return throwable;
}
}).flatMapMany(ReactiveQueryResult::rowsAsObject).flatMap(row -> {
String id = "";
long cas = 0;
if (distinctFields == null) {
if (row.getString(TemplateUtils.SELECT_ID) == null) {
return Flux.error(new CouchbaseException(
"query did not project " + TemplateUtils.SELECT_ID + ". Either use #{#n1ql.selectEntity} or project "
+ TemplateUtils.SELECT_ID + " and " + TemplateUtils.SELECT_CAS + " : " + statement));
}
}).flatMapMany(ReactiveQueryResult::rowsAsObject).flatMap(row -> {
String id = "";
long cas = 0;
if (distinctFields == null) {
if (row.getString(TemplateUtils.SELECT_ID) == null) {
return Flux.error(new CouchbaseException(
"query did not project " + TemplateUtils.SELECT_ID + ". Either use #{#n1ql.selectEntity} or project "
+ TemplateUtils.SELECT_ID + " and " + TemplateUtils.SELECT_CAS + " : " + statement));
}
id = row.getString(TemplateUtils.SELECT_ID);
if (row.getLong(TemplateUtils.SELECT_CAS) == null) {
return Flux.error(new CouchbaseException(
"query did not project " + TemplateUtils.SELECT_CAS + ". Either use #{#n1ql.selectEntity} or project "
+ TemplateUtils.SELECT_ID + " and " + TemplateUtils.SELECT_CAS + " : " + statement));
}
cas = row.getLong(TemplateUtils.SELECT_CAS);
row.removeKey(TemplateUtils.SELECT_ID);
row.removeKey(TemplateUtils.SELECT_CAS);
id = row.getString(TemplateUtils.SELECT_ID);
if (row.getLong(TemplateUtils.SELECT_CAS) == null) {
return Flux.error(new CouchbaseException(
"query did not project " + TemplateUtils.SELECT_CAS + ". Either use #{#n1ql.selectEntity} or project "
+ TemplateUtils.SELECT_ID + " and " + TemplateUtils.SELECT_CAS + " : " + statement));
}
return support.decodeEntity(id, row.toString(), cas, returnType);
});
});
cas = row.getLong(TemplateUtils.SELECT_CAS);
row.removeKey(TemplateUtils.SELECT_ID);
row.removeKey(TemplateUtils.SELECT_CAS);
}
return support.decodeEntity(id, row.toString(), cas, returnType);
}));
}
@Override
public QueryOptions buildOptions(QueryOptions options) {
private QueryOptions buildOptions(QueryOptions options) {
QueryOptions opts = query.buildQueryOptions(options, scanConsistency);
return opts;
}
@Override
public Mono<Long> count() {
return Mono.defer(() -> {
PseudoArgs<QueryOptions> pArgs = new PseudoArgs(template, scope, collection, options);
String statement = assembleEntityQuery(true, distinctFields, pArgs.getCollection());
LOG.trace("statement: {} {}", "findByQuery", statement);
Mono<ReactiveQueryResult> countResult = this.collection == null
? template.getCouchbaseClientFactory().getCluster().reactive().query(statement,
buildOptions(pArgs.getOptions()))
: template.getCouchbaseClientFactory().withScope(pArgs.getScope()).getScope().reactive().query(statement,
buildOptions(pArgs.getOptions()));
return countResult.onErrorMap(throwable -> {
if (throwable instanceof RuntimeException) {
return template.potentiallyConvertRuntimeException((RuntimeException) throwable);
} else {
return throwable;
}
}).flatMapMany(ReactiveQueryResult::rowsAsObject).map(row -> {
return row.getLong(TemplateUtils.SELECT_COUNT);
}).next();
});
PseudoArgs<QueryOptions> pArgs = new PseudoArgs(template, scope, collection, options, domainType);
String statement = assembleEntityQuery(true, distinctFields, pArgs.getCollection());
LOG.trace("findByQuery {} statement: {}", pArgs, statement);
Mono<ReactiveQueryResult> countResult = pArgs.getScope() == null
? template.getCouchbaseClientFactory().getCluster().reactive().query(statement,
buildOptions(pArgs.getOptions()))
: template.getCouchbaseClientFactory().withScope(pArgs.getScope()).getScope().reactive().query(statement,
buildOptions(pArgs.getOptions()));
return Mono.defer(() -> countResult.onErrorMap(throwable -> {
if (throwable instanceof RuntimeException) {
return template.potentiallyConvertRuntimeException((RuntimeException) throwable);
} else {
return throwable;
}
}).flatMapMany(ReactiveQueryResult::rowsAsObject).map(row -> row.getLong(TemplateUtils.SELECT_COUNT)).next());
}
@Override

View File

@@ -68,16 +68,16 @@ public class ReactiveFindFromReplicasByIdOperationSupport implements ReactiveFin
@Override
public Mono<T> any(final String id) {
return Mono.just(id).flatMap(docId -> {
GetAnyReplicaOptions garOptions = options != null ? options : getAnyReplicaOptions();
if (garOptions.build().transcoder() == null) {
garOptions.transcoder(RawJsonTranscoder.INSTANCE);
}
PseudoArgs<GetAnyReplicaOptions> pArgs = new PseudoArgs<>(template, scope, collection, garOptions);
LOG.trace("statement: {} scope: {} collection: {}", "getAnyReplica", pArgs.getScope(), pArgs.getCollection());
return 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))
GetAnyReplicaOptions garOptions = options != null ? options : getAnyReplicaOptions();
if (garOptions.build().transcoder() == null) {
garOptions.transcoder(RawJsonTranscoder.INSTANCE);
}
PseudoArgs<GetAnyReplicaOptions> pArgs = new PseudoArgs<>(template, scope, collection, garOptions, domainType);
LOG.trace("getAnyReplica {}", pArgs);
return 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))
.onErrorMap(throwable -> {
if (throwable instanceof RuntimeException) {
return template.potentiallyConvertRuntimeException((RuntimeException) throwable);
@@ -101,14 +101,12 @@ public class ReactiveFindFromReplicasByIdOperationSupport implements ReactiveFin
@Override
public FindFromReplicasByIdWithOptions<T> inCollection(final String collection) {
Assert.hasText(collection, "Collection must not be null nor empty.");
return new ReactiveFindFromReplicasByIdSupport<>(template, domainType, returnType, scope, collection, options,
support);
}
@Override
public FindFromReplicasByIdInCollection<T> inScope(final String scope) {
Assert.hasText(scope, "Scope must not be null nor empty.");
return new ReactiveFindFromReplicasByIdSupport<>(template, domainType, returnType, scope, collection, options,
support);
}

View File

@@ -21,7 +21,6 @@ import reactor.core.publisher.Mono;
import java.time.Duration;
import java.util.Collection;
import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
import org.springframework.data.couchbase.core.support.InCollection;
import org.springframework.data.couchbase.core.support.InScope;
import org.springframework.data.couchbase.core.support.OneAndAllEntityReactive;
@@ -68,8 +67,6 @@ public interface ReactiveInsertByIdOperation {
@Override
Flux<? extends T> all(Collection<? extends T> objects);
InsertOptions buildOptions(InsertOptions options, CouchbaseDocument doc);
}
/**

View File

@@ -24,6 +24,7 @@ import java.util.Collection;
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;
@@ -34,8 +35,8 @@ import com.couchbase.client.java.kv.ReplicateTo;
public class ReactiveInsertByIdOperationSupport implements ReactiveInsertByIdOperation {
private static final Logger LOG = LoggerFactory.getLogger(ReactiveInsertByIdOperationSupport.class);
private final ReactiveCouchbaseTemplate template;
private static final Logger LOG = LoggerFactory.getLogger(ReactiveInsertByIdOperationSupport.class);
public ReactiveInsertByIdOperationSupport(final ReactiveCouchbaseTemplate template) {
this.template = template;
@@ -78,14 +79,12 @@ public class ReactiveInsertByIdOperationSupport implements ReactiveInsertByIdOpe
@Override
public Mono<T> one(T object) {
PseudoArgs<InsertOptions> pArgs = new PseudoArgs(template, scope, collection,
options != null ? options : InsertOptions.insertOptions());
LOG.trace("statement: {} scope: {} collection: {} options: {}", "insertById", pArgs.getScope(),
pArgs.getCollection(), pArgs.getOptions());
PseudoArgs<InsertOptions> pArgs = new PseudoArgs(template, scope, collection, options, domainType);
LOG.trace("insertById {}", pArgs);
return Mono.just(object).flatMap(support::encodeEntity)
.flatMap(converted -> template.getCouchbaseClientFactory().withScope(pArgs.getScope())
.getCollection(pArgs.getCollection()).reactive()
.insert(converted.getId(), converted.export(), buildOptions(pArgs.getOptions(), converted))
.flatMap(converted -> template.getCouchbaseClientFactory().withScope(pArgs.getScope())
.getCollection(pArgs.getCollection()).reactive()
.insert(converted.getId(), converted.export(), buildOptions(pArgs.getOptions(), converted))
.flatMap(result -> support.applyUpdatedId(object, converted.getId())
.flatMap(updatedObject -> support.applyUpdatedCas(updatedObject, converted, result.cas()))))
.onErrorMap(throwable -> {
@@ -102,20 +101,8 @@ public class ReactiveInsertByIdOperationSupport implements ReactiveInsertByIdOpe
return Flux.fromIterable(objects).flatMap(this::one);
}
@Override
public InsertOptions buildOptions(InsertOptions options, CouchbaseDocument doc) { // CouchbaseDocument converted
options = options != null ? options : InsertOptions.insertOptions();
if (persistTo != PersistTo.NONE || replicateTo != ReplicateTo.NONE) {
options.durability(persistTo, replicateTo);
} else if (durabilityLevel != DurabilityLevel.NONE) {
options.durability(durabilityLevel);
}
if (expiry != null) {
options.expiry(expiry);
} else if (doc.getExpiration() != 0) {
options.expiry(Duration.ofSeconds(doc.getExpiration()));
}
return options;
return OptionsBuilder.buildInsertOptions(options, persistTo, replicateTo, durabilityLevel, expiry, doc);
}
@Override
@@ -127,14 +114,12 @@ public class ReactiveInsertByIdOperationSupport implements ReactiveInsertByIdOpe
@Override
public InsertByIdInCollection<T> inScope(final String scope) {
Assert.hasText(scope, "Scope must not be null nor empty.");
return new ReactiveInsertByIdSupport<>(template, domainType, scope, collection, options, persistTo, replicateTo,
durabilityLevel, expiry, support);
}
@Override
public InsertByIdWithOptions<T> inCollection(final String collection) {
Assert.hasText(collection, "Collection must not be null nor empty.");
return new ReactiveInsertByIdSupport<>(template, domainType, scope, collection, options, persistTo, replicateTo,
durabilityLevel, expiry, support);
}

View File

@@ -40,8 +40,14 @@ public interface ReactiveRemoveByIdOperation {
/**
* Removes a document.
*/
@Deprecated
ReactiveRemoveById removeById();
/**
* Removes a document.
*/
ReactiveRemoveById removeById(Class<?> domainType);
/**
* Terminating operations invoking the actual execution.
*/
@@ -106,6 +112,7 @@ public interface ReactiveRemoveByIdOperation {
interface RemoveByIdWithDurability extends RemoveByIdInScope, WithDurability<RemoveResult> {
@Override
RemoveByIdInCollection withDurability(DurabilityLevel durabilityLevel);
@Override
RemoveByIdInCollection withDurability(PersistTo persistTo, ReplicateTo replicateTo);

View File

@@ -15,13 +15,14 @@
*/
package org.springframework.data.couchbase.core;
import org.springframework.data.couchbase.core.mapping.event.ReactiveAfterDeleteEvent;
import org.springframework.data.couchbase.core.mapping.event.ReactiveBeforeDeleteEvent;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.Collection;
import org.slf4j.Logger;
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;
@@ -33,20 +34,28 @@ import com.couchbase.client.java.kv.ReplicateTo;
public class ReactiveRemoveByIdOperationSupport implements ReactiveRemoveByIdOperation {
private final ReactiveCouchbaseTemplate template;
private static final Logger LOG = LoggerFactory.getLogger(ReactiveRemoveByIdOperationSupport.class);
public ReactiveRemoveByIdOperationSupport(final ReactiveCouchbaseTemplate template) {
this.template = template;
}
@Override
@Deprecated
public ReactiveRemoveById removeById() {
return new ReactiveRemoveByIdSupport(template, null, null, null, PersistTo.NONE, ReplicateTo.NONE,
return removeById(null);
}
@Override
public ReactiveRemoveById removeById(Class<?> domainType) {
return new ReactiveRemoveByIdSupport(template, domainType, null, null, null, PersistTo.NONE, ReplicateTo.NONE,
DurabilityLevel.NONE, null);
}
static class ReactiveRemoveByIdSupport implements ReactiveRemoveById {
private final ReactiveCouchbaseTemplate template;
private final Class<?> domainType;
private final String scope;
private final String collection;
private final RemoveOptions options;
@@ -55,10 +64,11 @@ public class ReactiveRemoveByIdOperationSupport implements ReactiveRemoveByIdOpe
private final DurabilityLevel durabilityLevel;
private final Long cas;
ReactiveRemoveByIdSupport(final ReactiveCouchbaseTemplate template, final String scope, final String collection,
final RemoveOptions options, final PersistTo persistTo, final ReplicateTo replicateTo,
ReactiveRemoveByIdSupport(final ReactiveCouchbaseTemplate template, final Class<?> domainType, final String scope,
final String collection, final RemoveOptions options, final PersistTo persistTo, final ReplicateTo replicateTo,
final DurabilityLevel durabilityLevel, Long cas) {
this.template = template;
this.domainType = domainType;
this.scope = scope;
this.collection = collection;
this.options = options;
@@ -70,22 +80,19 @@ public class ReactiveRemoveByIdOperationSupport implements ReactiveRemoveByIdOpe
@Override
public Mono<RemoveResult> one(final String id) {
PseudoArgs<RemoveOptions> pArgs = new PseudoArgs(template, scope, collection,
options != null ? options : RemoveOptions.removeOptions());
return Mono.just(id).map(r -> {
template.support().maybeEmitEvent(new ReactiveBeforeDeleteEvent<>(r));
return r;
}).flatMap(docId -> template.getCouchbaseClientFactory().withScope(pArgs.getScope())
.getCollection(pArgs.getCollection()).reactive().remove(id, buildRemoveOptions(pArgs.getOptions())).map(r -> {
template.support().maybeEmitEvent(new ReactiveAfterDeleteEvent<>(r));
return RemoveResult.from(docId, r);
})).onErrorMap(throwable -> {
if (throwable instanceof RuntimeException) {
return template.potentiallyConvertRuntimeException((RuntimeException) throwable);
} else {
return throwable;
}
});
PseudoArgs<RemoveOptions> pArgs = new PseudoArgs<>(template, scope, collection, options, domainType);
LOG.trace("removeById {}", pArgs);
return Mono.just(id)
.flatMap(docId -> template.getCouchbaseClientFactory().withScope(pArgs.getScope())
.getCollection(pArgs.getCollection()).reactive().remove(id, buildRemoveOptions(pArgs.getOptions()))
.map(r -> RemoveResult.from(docId, r)))
.onErrorMap(throwable -> {
if (throwable instanceof RuntimeException) {
return template.potentiallyConvertRuntimeException((RuntimeException) throwable);
} else {
return throwable;
}
});
}
@Override
@@ -94,22 +101,13 @@ public class ReactiveRemoveByIdOperationSupport implements ReactiveRemoveByIdOpe
}
private RemoveOptions buildRemoveOptions(RemoveOptions options) {
options = options != null ? options : RemoveOptions.removeOptions();
if (persistTo != PersistTo.NONE || replicateTo != ReplicateTo.NONE) {
options.durability(persistTo, replicateTo);
} else if (durabilityLevel != DurabilityLevel.NONE) {
options.durability(durabilityLevel);
}
if (cas != null) {
options.cas(cas);
}
return options;
return OptionsBuilder.buildRemoveOptions(options, persistTo, replicateTo, durabilityLevel, cas);
}
@Override
public RemoveByIdInCollection withDurability(final DurabilityLevel durabilityLevel) {
Assert.notNull(durabilityLevel, "Durability Level must not be null.");
return new ReactiveRemoveByIdSupport(template, scope, collection, options, persistTo, replicateTo,
return new ReactiveRemoveByIdSupport(template, domainType, scope, collection, options, persistTo, replicateTo,
durabilityLevel, cas);
}
@@ -117,34 +115,32 @@ public class ReactiveRemoveByIdOperationSupport implements ReactiveRemoveByIdOpe
public RemoveByIdInCollection withDurability(final PersistTo persistTo, final ReplicateTo replicateTo) {
Assert.notNull(persistTo, "PersistTo must not be null.");
Assert.notNull(replicateTo, "ReplicateTo must not be null.");
return new ReactiveRemoveByIdSupport(template, scope, collection, options, persistTo, replicateTo,
return new ReactiveRemoveByIdSupport(template, domainType, scope, collection, options, persistTo, replicateTo,
durabilityLevel, cas);
}
@Override
public RemoveByIdWithDurability inCollection(final String collection) {
Assert.hasText(collection, "Collection must not be null nor empty.");
return new ReactiveRemoveByIdSupport(template, scope, collection, options, persistTo, replicateTo,
return new ReactiveRemoveByIdSupport(template, domainType, scope, collection, options, persistTo, replicateTo,
durabilityLevel, cas);
}
@Override
public RemoveByIdInCollection inScope(final String scope) {
Assert.hasText(scope, "Scope must not be null nor empty.");
return new ReactiveRemoveByIdSupport(template, scope, collection, options, persistTo, replicateTo,
return new ReactiveRemoveByIdSupport(template, domainType, scope, collection, options, persistTo, replicateTo,
durabilityLevel, cas);
}
@Override
public TerminatingRemoveById withOptions(final RemoveOptions options) {
Assert.notNull(options, "Options must not be null.");
return new ReactiveRemoveByIdSupport(template, scope, collection, options, persistTo, replicateTo,
return new ReactiveRemoveByIdSupport(template, domainType, scope, collection, options, persistTo, replicateTo,
durabilityLevel, cas);
}
@Override
public RemoveByIdWithDurability withCas(Long cas) {
return new ReactiveRemoveByIdSupport(template, scope, collection, options, persistTo, replicateTo,
return new ReactiveRemoveByIdSupport(template, domainType, scope, collection, options, persistTo, replicateTo,
durabilityLevel, cas);
}
}

View File

@@ -15,7 +15,6 @@
*/
package org.springframework.data.couchbase.core;
import org.springframework.util.Assert;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@@ -26,6 +25,7 @@ import org.slf4j.LoggerFactory;
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.java.query.QueryOptions;
import com.couchbase.client.java.query.QueryScanConsistency;
@@ -36,7 +36,6 @@ public class ReactiveRemoveByQueryOperationSupport implements ReactiveRemoveByQu
private static final Query ALL_QUERY = new Query();
private final ReactiveCouchbaseTemplate template;
private static final Logger LOG = LoggerFactory.getLogger(ReactiveRemoveByQueryOperationSupport.class);
public ReactiveRemoveByQueryOperationSupport(final ReactiveCouchbaseTemplate template) {
@@ -45,8 +44,7 @@ public class ReactiveRemoveByQueryOperationSupport implements ReactiveRemoveByQu
@Override
public <T> ReactiveRemoveByQuery<T> removeByQuery(Class<T> domainType) {
return new ReactiveRemoveByQuerySupport<>(template, domainType, ALL_QUERY,null, null,
null, null);
return new ReactiveRemoveByQuerySupport<>(template, domainType, ALL_QUERY, null, null, null, null);
}
static class ReactiveRemoveByQuerySupport<T> implements ReactiveRemoveByQuery<T> {
@@ -72,25 +70,23 @@ public class ReactiveRemoveByQueryOperationSupport implements ReactiveRemoveByQu
@Override
public Flux<RemoveResult> all() {
return Flux.defer(() -> {
PseudoArgs<QueryOptions> pArgs = new PseudoArgs<>(template, scope, collection, options);
String statement = assembleDeleteQuery(pArgs.getCollection());
LOG.trace("statement: {}", statement);
Mono<ReactiveQueryResult> allResult = pArgs.getCollection() == null
? template.getCouchbaseClientFactory().getCluster().reactive().query(statement,
buildQueryOptions(pArgs.getOptions()))
: template.getCouchbaseClientFactory().withScope(pArgs.getScope()).getScope().reactive().query(statement,
buildQueryOptions(pArgs.getOptions()));
return allResult.onErrorMap(throwable -> {
if (throwable instanceof RuntimeException) {
return template.potentiallyConvertRuntimeException((RuntimeException) throwable);
} else {
return throwable;
}
}).flatMapMany(ReactiveQueryResult::rowsAsObject)
.map(row -> new RemoveResult(row.getString(TemplateUtils.SELECT_ID), row.getLong(TemplateUtils.SELECT_CAS),
Optional.empty()));
});
PseudoArgs<QueryOptions> pArgs = new PseudoArgs<>(template, scope, collection, options, domainType);
String statement = assembleDeleteQuery(pArgs.getCollection());
LOG.trace("removeByQuery {} statement: {}", pArgs, statement);
Mono<ReactiveQueryResult> allResult = pArgs.getScope() == null
? template.getCouchbaseClientFactory().getCluster().reactive().query(statement,
buildQueryOptions(pArgs.getOptions()))
: template.getCouchbaseClientFactory().withScope(pArgs.getScope()).getScope().reactive().query(statement,
buildQueryOptions(pArgs.getOptions()));
return Flux.defer(() -> allResult.onErrorMap(throwable -> {
if (throwable instanceof RuntimeException) {
return template.potentiallyConvertRuntimeException((RuntimeException) throwable);
} else {
return throwable;
}
}).flatMapMany(ReactiveQueryResult::rowsAsObject)
.map(row -> new RemoveResult(row.getString(TemplateUtils.SELECT_ID), row.getLong(TemplateUtils.SELECT_CAS),
Optional.empty())));
}
private QueryOptions buildQueryOptions(QueryOptions options) {
@@ -105,7 +101,6 @@ public class ReactiveRemoveByQueryOperationSupport implements ReactiveRemoveByQu
@Override
public RemoveByQueryWithConsistency<T> inCollection(final String collection) {
Assert.hasText(collection, "Collection must not be null nor empty.");
return new ReactiveRemoveByQuerySupport<>(template, domainType, query, scanConsistency, scope, collection,
options);
}
@@ -136,7 +131,6 @@ public class ReactiveRemoveByQueryOperationSupport implements ReactiveRemoveByQu
@Override
public RemoveByQueryInCollection<T> inScope(final String scope) {
Assert.hasText(scope, "Scope must not be null nor empty.");
return new ReactiveRemoveByQuerySupport<>(template, domainType, query, scanConsistency, scope, collection,
options);
}

View File

@@ -24,6 +24,7 @@ import java.util.Collection;
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;
@@ -34,8 +35,8 @@ import com.couchbase.client.java.kv.ReplicateTo;
public class ReactiveReplaceByIdOperationSupport implements ReactiveReplaceByIdOperation {
private static final Logger LOG = LoggerFactory.getLogger(ReactiveReplaceByIdOperationSupport.class);
private final ReactiveCouchbaseTemplate template;
private static final Logger LOG = LoggerFactory.getLogger(ReactiveReplaceByIdOperationSupport.class);
public ReactiveReplaceByIdOperationSupport(final ReactiveCouchbaseTemplate template) {
this.template = template;
@@ -78,13 +79,13 @@ public class ReactiveReplaceByIdOperationSupport implements ReactiveReplaceByIdO
@Override
public Mono<T> one(T object) {
PseudoArgs<ReplaceOptions> pArgs = new PseudoArgs<>(template, scope, collection,
options != null ? options : ReplaceOptions.replaceOptions());
LOG.trace("statement: {} pArgs: {}", "replaceById", pArgs);
PseudoArgs<ReplaceOptions> pArgs = new PseudoArgs<>(template, scope, collection, options, domainType);
LOG.trace("replaceById {}", pArgs);
return Mono.just(object).flatMap(support::encodeEntity)
.flatMap(converted -> template.getCouchbaseClientFactory().withScope(pArgs.getScope())
.getCollection(pArgs.getCollection()).reactive()
.replace(converted.getId(), converted.export(), buildReplaceOptions(pArgs.getOptions(), object, converted))
.flatMap(converted -> template.getCouchbaseClientFactory().withScope(pArgs.getScope())
.getCollection(pArgs.getCollection()).reactive()
.replace(converted.getId(), converted.export(),
buildReplaceOptions(pArgs.getOptions(), object, converted))
.flatMap(result -> support.applyUpdatedCas(object, converted, result.cas())))
.onErrorMap(throwable -> {
if (throwable instanceof RuntimeException) {
@@ -101,20 +102,8 @@ public class ReactiveReplaceByIdOperationSupport implements ReactiveReplaceByIdO
}
private ReplaceOptions buildReplaceOptions(ReplaceOptions options, T object, CouchbaseDocument doc) {
options = options != null ? options : ReplaceOptions.replaceOptions();
if (persistTo != PersistTo.NONE || replicateTo != ReplicateTo.NONE) {
options.durability(persistTo, replicateTo);
} else if (durabilityLevel != DurabilityLevel.NONE) {
options.durability(durabilityLevel);
}
if (expiry != null) {
options.expiry(expiry);
} else if (doc.getExpiration() != 0) {
options.expiry(Duration.ofSeconds(doc.getExpiration()));
}
long cas = support.getCas(object);
options.cas(cas);
return options;
return OptionsBuilder.buildReplaceOptions(options, persistTo, replicateTo, durabilityLevel, expiry,
support.getCas(object), doc);
}
@Override
@@ -126,14 +115,12 @@ public class ReactiveReplaceByIdOperationSupport implements ReactiveReplaceByIdO
@Override
public ReplaceByIdWithDurability<T> inCollection(final String collection) {
Assert.hasText(collection, "Collection must not be null nor empty.");
return new ReactiveReplaceByIdSupport<>(template, domainType, scope, collection, options, persistTo, replicateTo,
durabilityLevel, expiry, support);
}
@Override
public ReplaceByIdInCollection<T> inScope(final String scope) {
Assert.hasText(scope, "Scope must not be null nor empty.");
return new ReactiveReplaceByIdSupport<>(template, domainType, scope, collection, options, persistTo, replicateTo,
durabilityLevel, expiry, support);
}

View File

@@ -15,24 +15,24 @@
*/
package org.springframework.data.couchbase.core;
import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
import org.springframework.data.couchbase.core.mapping.event.CouchbaseMappingEvent;
import reactor.core.publisher.Mono;
import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
import org.springframework.data.couchbase.core.mapping.event.CouchbaseMappingEvent;
public interface ReactiveTemplateSupport {
Mono<CouchbaseDocument> encodeEntity(Object entityToEncode);
Mono<CouchbaseDocument> encodeEntity(Object entityToEncode);
<T> Mono<T> decodeEntity(String id, String source, long cas, Class<T> entityClass);
<T> Mono<T> decodeEntity(String id, String source, long cas, Class<T> entityClass);
<T> Mono<T> applyUpdatedCas(T entity, CouchbaseDocument converted, long cas);
<T> Mono<T> applyUpdatedCas(T entity, CouchbaseDocument converted, long cas);
<T> Mono<T> applyUpdatedId(T entity, Object id);
<T> Mono<T> applyUpdatedId(T entity, Object id);
Long getCas(Object entity);
Long getCas(Object entity);
String getJavaNameForEntity(Class<?> clazz);
String getJavaNameForEntity(Class<?> clazz);
void maybeEmitEvent(CouchbaseMappingEvent<?> event);
void maybeEmitEvent(CouchbaseMappingEvent<?> event);
}

View File

@@ -21,7 +21,10 @@ import reactor.core.publisher.Mono;
import java.time.Duration;
import java.util.Collection;
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;
@@ -33,6 +36,7 @@ import com.couchbase.client.java.kv.UpsertOptions;
public class ReactiveUpsertByIdOperationSupport implements ReactiveUpsertByIdOperation {
private final ReactiveCouchbaseTemplate template;
private static final Logger LOG = LoggerFactory.getLogger(ReactiveUpsertByIdOperationSupport.class);
public ReactiveUpsertByIdOperationSupport(final ReactiveCouchbaseTemplate template) {
this.template = template;
@@ -75,12 +79,12 @@ public class ReactiveUpsertByIdOperationSupport implements ReactiveUpsertByIdOpe
@Override
public Mono<T> one(T object) {
PseudoArgs<UpsertOptions> pArgs = new PseudoArgs<>(template, scope, collection,
options != null ? options : UpsertOptions.upsertOptions());
PseudoArgs<UpsertOptions> pArgs = new PseudoArgs(template, scope, collection, options, domainType);
LOG.trace("upsertById {}", pArgs);
return Mono.just(object).flatMap(support::encodeEntity)
.flatMap(converted -> template.getCouchbaseClientFactory().withScope(pArgs.getScope())
.getCollection(pArgs.getCollection()).reactive()
.upsert(converted.getId(), converted.export(), buildUpsertOptions(pArgs.getOptions(), converted))
.flatMap(converted -> template.getCouchbaseClientFactory().withScope(pArgs.getScope())
.getCollection(pArgs.getCollection()).reactive()
.upsert(converted.getId(), converted.export(), buildUpsertOptions(pArgs.getOptions(), converted))
.flatMap(result -> support.applyUpdatedId(object, converted.getId())
.flatMap(updatedObject -> support.applyUpdatedCas(updatedObject, converted, result.cas()))))
.onErrorMap(throwable -> {
@@ -98,18 +102,7 @@ public class ReactiveUpsertByIdOperationSupport implements ReactiveUpsertByIdOpe
}
private UpsertOptions buildUpsertOptions(UpsertOptions options, CouchbaseDocument doc) {
options = options != null ? options : UpsertOptions.upsertOptions();
if (persistTo != PersistTo.NONE || replicateTo != ReplicateTo.NONE) {
options.durability(persistTo, replicateTo);
} else if (durabilityLevel != DurabilityLevel.NONE) {
options.durability(durabilityLevel);
}
if (expiry != null) {
options.expiry(expiry);
} else if (doc.getExpiration() != 0) {
options.expiry(Duration.ofSeconds(doc.getExpiration()));
}
return options;
return OptionsBuilder.buildUpsertOptions(options, persistTo, replicateTo, durabilityLevel, expiry, doc);
}
@Override
@@ -121,14 +114,12 @@ public class ReactiveUpsertByIdOperationSupport implements ReactiveUpsertByIdOpe
@Override
public UpsertByIdWithDurability<T> inCollection(final String collection) {
Assert.hasText(collection, "Collection must not be null nor empty.");
return new ReactiveUpsertByIdSupport<>(template, domainType, scope, collection, options, persistTo, replicateTo,
durabilityLevel, expiry, support);
}
@Override
public UpsertByIdInCollection<T> inScope(final String scope) {
Assert.hasText(scope, "Scope must not be null nor empty.");
return new ReactiveUpsertByIdSupport<>(template, domainType, scope, collection, options, persistTo, replicateTo,
durabilityLevel, expiry, support);
}

View File

@@ -20,17 +20,17 @@ import org.springframework.data.couchbase.core.mapping.event.CouchbaseMappingEve
public interface TemplateSupport {
CouchbaseDocument encodeEntity(Object entityToEncode);
CouchbaseDocument encodeEntity(Object entityToEncode);
<T> T decodeEntity(String id, String source, long cas, Class<T> entityClass);
<T> T decodeEntity(String id, String source, long cas, Class<T> entityClass);
<T> T applyUpdatedCas(T entity, CouchbaseDocument converted, long cas);
<T> T applyUpdatedCas(T entity, CouchbaseDocument converted, long cas);
<T> T applyUpdatedId(T entity, Object id);
<T> T applyUpdatedId(T entity, Object id);
long getCas(Object entity);
long getCas(Object entity);
String getJavaNameForEntity(Class<?> clazz);
String getJavaNameForEntity(Class<?> clazz);
void maybeEmitEvent(CouchbaseMappingEvent<?> event);
void maybeEmitEvent(CouchbaseMappingEvent<?> event);
}

View File

@@ -34,6 +34,7 @@ import org.springframework.context.ApplicationContextAware;
import org.springframework.core.CollectionFactory;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.data.annotation.Transient;
import org.springframework.data.convert.EntityInstantiator;
import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
import org.springframework.data.couchbase.core.mapping.CouchbaseList;
@@ -532,6 +533,10 @@ public class MappingCouchbaseConverter extends AbstractCouchbaseConverter implem
idAttributes.put(order, convertToString(propertyObj));
}
if (prop.isAnnotationPresent(Transient.class)) {
return;
}
if (!conversions.isSimpleType(propertyObj.getClass())) {
writePropertyInternal(propertyObj, target, prop, false);
} else {

View File

@@ -21,10 +21,9 @@ import java.util.TimeZone;
import java.util.concurrent.TimeUnit;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.env.Environment;
import org.springframework.data.annotation.Id;
import org.springframework.data.mapping.MappingException;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.model.BasicPersistentEntity;
import org.springframework.data.util.TypeInformation;
import org.springframework.util.Assert;
@@ -98,11 +97,14 @@ public class BasicCouchbasePersistentEntity<T> extends BasicPersistentEntity<T,
@Override
public int getExpiry() {
Document annotation = getType().getAnnotation(Document.class);
return getExpiry(AnnotatedElementUtils.findMergedAnnotation(getType(), Expiry.class), environment);
}
public static int getExpiry(Expiry annotation, Environment environment) {
if (annotation == null)
return 0;
int expiryValue = getExpiryValue(annotation);
int expiryValue = getExpiryValue(annotation, environment);
long secondsShift = annotation.expiryUnit().toSeconds(expiryValue);
if (secondsShift > TTL_IN_SECONDS_INCLUSIVE_END) {
@@ -121,7 +123,7 @@ public class BasicCouchbasePersistentEntity<T> extends BasicPersistentEntity<T,
}
}
private int getExpiryValue(Document annotation) {
private static int getExpiryValue(Expiry annotation, Environment environment) {
int expiryValue = annotation.expiry();
String expiryExpressionString = annotation.expiryExpression();
if (StringUtils.hasLength(expiryExpressionString)) {

View File

@@ -23,7 +23,13 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.concurrent.TimeUnit;
import org.springframework.core.annotation.AliasFor;
import org.springframework.data.annotation.Persistent;
import org.springframework.data.couchbase.repository.Collection;
import org.springframework.data.couchbase.repository.ScanConsistency;
import org.springframework.data.couchbase.repository.Scope;
import com.couchbase.client.java.query.QueryScanConsistency;
/**
* Identifies a domain object to be persisted to Couchbase.
@@ -35,12 +41,15 @@ import org.springframework.data.annotation.Persistent;
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE })
@Expiry
@ScanConsistency
public @interface Document {
/**
* An optional expiry time for the document. Default is no expiry. Only one of two might might be set at the same
* time: either {@link #expiry()} or {@link #expiryExpression()}
*/
@AliasFor(annotation = Expiry.class, attribute = "expiry")
int expiry() default 0;
/**
@@ -55,11 +64,13 @@ public @interface Document {
* <br />
* SpEL is NOT supported.
*/
@AliasFor(annotation = Expiry.class, attribute = "expiryExpression")
String expiryExpression() default "";
/**
* An optional time unit for the document's {@link #expiry()}, if set. Default is {@link TimeUnit#SECONDS}.
*/
@AliasFor(annotation = Expiry.class, attribute = "expiryUnit")
TimeUnit expiryUnit() default TimeUnit.SECONDS;
/**
@@ -68,4 +79,9 @@ public @interface Document {
*/
boolean touchOnRead() default false;
/**
* An optional string indicating the query scan consistency
*/
@AliasFor(annotation = ScanConsistency.class, attribute = "query")
QueryScanConsistency queryScanConsistency() default QueryScanConsistency.NOT_BOUNDED;
}

View File

@@ -0,0 +1,64 @@
/*
* Copyright 2012-2021 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.core.mapping;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.concurrent.TimeUnit;
import org.springframework.data.annotation.Persistent;
/**
* Expiry annotation
*
* @author Michael Nitschinger
*/
@Persistent
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE })
public @interface Expiry {
/**
* An optional expiry time for the document. Default is no expiry. Only one of two might might be set at the same
* time: either {@link #expiry()} or {@link #expiryExpression()}
*/
int expiry() default 0;
/**
* Same as {@link #expiry} 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 #expiry()} or {@link #expiryExpression()}. <br />
* Syntax is the same as for {@link org.springframework.core.env.Environment#resolveRequiredPlaceholders(String)}.
* <br />
* <br />
* The value will be recalculated for every {@link org.springframework.data.couchbase.core.CouchbaseTemplate}
* save/insert/update call, thus allowing actual expiration to reflect changes on-the-fly as soon as property sources
* change. <br />
* <br />
* SpEL is NOT supported.
*/
String expiryExpression() default "";
/**
* An optional time unit for the document's {@link #expiry()}, if set. Default is {@link TimeUnit#SECONDS}.
*/
TimeUnit expiryUnit() default TimeUnit.SECONDS;
}

View File

@@ -50,13 +50,9 @@ public class AbstractCouchbaseEventListener<E> implements ApplicationListener<Co
if (event instanceof BeforeDeleteEvent) {
onBeforeDelete(event.getSource(), event.getDocument());
return;
} else if (event instanceof AfterDeleteEvent) {
onAfterDelete(event.getSource(), event.getDocument());
return;
}
if (event instanceof BeforeConvertEvent) {
} else if (event instanceof BeforeConvertEvent) {
onBeforeConvert(source);
} else if (event instanceof BeforeSaveEvent) {
onBeforeSave(source, event.getDocument());
@@ -66,32 +62,32 @@ public class AbstractCouchbaseEventListener<E> implements ApplicationListener<Co
}
public void onBeforeConvert(E source) {
if (LOG.isDebugEnabled()) {
LOG.debug("onBeforeConvert({})", source);
if (LOG.isTraceEnabled()) {
LOG.trace("onBeforeConvert({})", source);
}
}
public void onBeforeSave(E source, CouchbaseDocument doc) {
if (LOG.isDebugEnabled()) {
LOG.debug("onBeforeSave({}, {})", source, doc);
if (LOG.isTraceEnabled()) {
LOG.trace("onBeforeSave({}, {})", source, doc);
}
}
public void onAfterSave(E source, CouchbaseDocument doc) {
if (LOG.isDebugEnabled()) {
LOG.debug("onAfterSave({}, {})", source, doc);
if (LOG.isTraceEnabled()) {
LOG.trace("onAfterSave({}, {})", source, doc);
}
}
public void onAfterDelete(Object source, CouchbaseDocument doc) {
if (LOG.isDebugEnabled()) {
LOG.debug("onAfterConvert({})", doc);
if (LOG.isTraceEnabled()) {
LOG.trace("onAfterConvert({})", doc);
}
}
public void onBeforeDelete(Object source, CouchbaseDocument doc) {
if (LOG.isDebugEnabled()) {
LOG.debug("onAfterConvert({})", doc);
if (LOG.isTraceEnabled()) {
LOG.trace("onAfterConvert({})", doc);
}
}

View File

@@ -54,7 +54,7 @@ public class AuditingEntityCallback implements BeforeConvertCallback<Object>, Af
*/
@Override
public Object onBeforeConvert(Object entity, String collection) {
//LOG.debug("onBeforeConvert " + entity);
// LOG.trace("onBeforeConvert " + entity);
return entity; // markAudited called in AuditingEventListener.onApplicationEvent()
// auditingHandlerFactory.getObject().markAudited(entity);
}
@@ -65,7 +65,7 @@ public class AuditingEntityCallback implements BeforeConvertCallback<Object>, Af
*/
@Override
public Object onAfterConvert(Object entity, CouchbaseDocument document, String collection) {
//LOG.debug("onAfterConvert " + document);
// LOG.trace("onAfterConvert " + document);
return entity;
}

View File

@@ -65,24 +65,14 @@ public class AuditingEventListener implements ApplicationListener<CouchbaseMappi
if (event instanceof BeforeConvertEvent) {
Optional.ofNullable(event.getSource())//
.ifPresent(it -> auditingHandlerFactory.getObject().markAudited(it));
// LOG.info(event.getClass().getSimpleName() + " " + event);
}
if (event instanceof BeforeSaveEvent) {
// LOG.info(event.getClass().getSimpleName() + " " + event);
}
if (event instanceof AfterSaveEvent) {
// LOG.info(event.getClass().getSimpleName() + " " + event);
}
if (event instanceof BeforeDeleteEvent) {
// LOG.info(event.getClass().getSimpleName() + " " + event);
}
if (event instanceof AfterDeleteEvent) {
// LOG.info(event.getClass().getSimpleName() + " " + event);
}
if (!event.getClass().getSimpleName().startsWith("Reactive")) {
if (LOG.isDebugEnabled()) {
LOG.debug(event.getClass().getSimpleName() + " " + event.getSource());
}
if (event instanceof BeforeSaveEvent) {}
if (event instanceof AfterSaveEvent) {}
if (event instanceof BeforeDeleteEvent) {}
if (event instanceof AfterDeleteEvent) {}
if (LOG.isTraceEnabled()) {
LOG.trace("{} {}", event.getClass().getSimpleName(), event.getSource());
}
}

View File

@@ -1,28 +0,0 @@
/*
* Copyright 2012-2020 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.core.mapping.event;
/**
* @author Michael Reiche
*/
public class ReactiveAfterDeleteEvent<E> extends CouchbaseMappingEvent<E> {
public ReactiveAfterDeleteEvent(E source) {
super(source, null);
}
}

View File

@@ -63,7 +63,9 @@ public class ReactiveAuditingEntityCallback
*/
@Override
public Publisher<Object> onBeforeConvert(final Object entity, final String collection) {
LOG.debug("onBeforeConvert " + entity.toString());
if (LOG.isTraceEnabled()) {
LOG.trace("onBeforeConvert {}", entity.toString());
}
return this.auditingHandlerFactory.getObject().markAudited(entity);
}
@@ -76,9 +78,12 @@ public class ReactiveAuditingEntityCallback
*/
@Override
public Publisher<Object> onAfterConvert(Object entity, CouchbaseDocument document, String collection) {
LOG.debug("onAfterConvert " + document.toString());
if (LOG.isTraceEnabled()) {
LOG.trace("onAfterConvert {}", document.toString());
}
return Mono.just(entity);
}
/*
* (non-Javadoc)
*

View File

@@ -1,87 +0,0 @@
/*
* Copyright 2012-2021 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.core.mapping.event;
import java.util.Optional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.context.ApplicationListener;
import org.springframework.data.auditing.AuditingHandler;
import org.springframework.data.auditing.IsNewAwareAuditingHandler;
import org.springframework.data.auditing.ReactiveIsNewAwareAuditingHandler;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.util.Assert;
/**
* Reactive Event listener to populate auditing related fields on an entity about to be saved.
*
* @author Michael Reiche
*/
public class ReactiveAuditingEventListener implements ApplicationListener<CouchbaseMappingEvent<Object>> {
private final ObjectFactory<ReactiveIsNewAwareAuditingHandler> auditingHandlerFactory;
public ReactiveAuditingEventListener() {
this.auditingHandlerFactory = null;
}
private static final Logger LOG = LoggerFactory.getLogger(ReactiveAuditingEventListener.class);
/**
* Creates a new {@link ReactiveAuditingEventListener} using the given {@link MappingContext} and
* {@link AuditingHandler} provided by the given {@link ObjectFactory}. Registered in CouchbaseAuditingRegistrar
*
* @param auditingHandlerFactory must not be {@literal null}.
*/
public ReactiveAuditingEventListener(ObjectFactory<ReactiveIsNewAwareAuditingHandler> auditingHandlerFactory) {
Assert.notNull(auditingHandlerFactory, "IsNewAwareAuditingHandler must not be null!");
this.auditingHandlerFactory = auditingHandlerFactory;
}
/*
* (non-Javadoc)
* @see org.springframework.context.ApplicationListener#onApplicationEvent(org.springframework.context.ApplicationEvent)
*/
@Override
public void onApplicationEvent(CouchbaseMappingEvent<Object> event) {
if (event instanceof ReactiveBeforeConvertEvent) {
Optional.ofNullable(event.getSource())//
.ifPresent(it -> auditingHandlerFactory.getObject().markAudited(it));
// LOG.info(event.getClass().getSimpleName() + " " + event);
}
if (event instanceof ReactiveBeforeSaveEvent) {
// LOG.info(event.getClass().getSimpleName() + " " + event);
}
if (event instanceof ReactiveAfterSaveEvent) {
// LOG.info(event.getClass().getSimpleName() + " " + event);
}
if (event instanceof ReactiveBeforeDeleteEvent) {
// LOG.info(event.getClass().getSimpleName() + " " + event);
}
if (event instanceof ReactiveAfterDeleteEvent) {
// LOG.info(event.getClass().getSimpleName() + " " + event);
}
if (event.getClass().getSimpleName().startsWith("Reactive")) {
if (LOG.isDebugEnabled()) {
LOG.debug(event.getClass().getSimpleName() + " " + event.getSource());
}
}
}
}

View File

@@ -1,28 +0,0 @@
/*
* Copyright 2012-2021 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.core.mapping.event;
/**
* @author Michael Reiche
*/
public class ReactiveBeforeConvertEvent<E> extends CouchbaseMappingEvent<E> {
public ReactiveBeforeConvertEvent(E source) {
super(source, null);
}
}

View File

@@ -1,28 +0,0 @@
/*
* Copyright 2012-2020 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.core.mapping.event;
/**
* @author Michael Reiche
*/
public class ReactiveBeforeDeleteEvent<E> extends CouchbaseMappingEvent<E> {
public ReactiveBeforeDeleteEvent(E source) {
super(source, null);
}
}

View File

@@ -1,30 +0,0 @@
/*
* Copyright 2012-2021 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.core.mapping.event;
import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
/**
* @author Michael Reiche
*/
public class ReactiveBeforeSaveEvent<E> extends CouchbaseMappingEvent<E> {
public ReactiveBeforeSaveEvent(E source, CouchbaseDocument document) {
super(source, document);
}
}

View File

@@ -60,7 +60,6 @@ public class ValidatingCouchbaseEventListener extends AbstractCouchbaseEventList
Set violations = validator.validate(source);
if (!violations.isEmpty()) {
LOG.info("During object: {} validation violations found: {}", source, violations);
throw new ConstraintViolationException(violations);
}

View File

@@ -32,8 +32,9 @@ import org.springframework.util.StringUtils;
*/
public class Meta {
private enum MetaKey {
EXAMPLE("$example");
public enum MetaKey {
SCAN_CONSISTENCY("scan_consistency"), SCOPE("scope"), COLLECTION("collection"), EXPIRY("expiry"), EXPIRY_UNIT(
"expiry_unit"), EXPIRY_EXPRESSION("expiry_expression"), TIMEOUT("timeout"), RETRY_STRATEGY("retry_strategy");
private String key;
@@ -42,7 +43,7 @@ public class Meta {
}
}
private final Map<String, Object> values = new LinkedHashMap<>(2);
private final Map<MetaKey, Object> values = new LinkedHashMap<>(2);
public Meta() {}
@@ -68,7 +69,7 @@ public class Meta {
*
* @return
*/
public Iterable<Entry<String, Object>> values() {
public Iterable<Entry<MetaKey, Object>> values() {
return Collections.unmodifiableSet(this.values.entrySet());
}
@@ -78,10 +79,26 @@ public class Meta {
* @param key must not be {@literal null} or empty.
* @param value
*/
void setValue(String key, @Nullable Object value) {
public void setValue(String key, @Nullable Object value) {
Assert.hasText(key, "Meta key must not be 'null' or blank.");
if (value == null || (value instanceof String && !StringUtils.hasText((String) value))) {
this.values.remove(MetaKey.valueOf(key));
}
this.values.put(MetaKey.valueOf(key), value);
}
public void setValue(MetaKey key, @Nullable Object value) {
if (value == null || (value instanceof String && !StringUtils.hasText((String) value))) {
this.values.remove(key);
}
this.values.put(key, value);
}
public void set(MetaKey key, @Nullable Object value) {
if (value == null || (value instanceof String && !StringUtils.hasText((String) value))) {
this.values.remove(key);
}
@@ -90,11 +107,15 @@ public class Meta {
@Nullable
@SuppressWarnings("unchecked")
private <T> T getValue(String key) {
public <T> T getValue(String key) {
return (T) this.values.get(MetaKey.valueOf(key));
}
public <T> T get(MetaKey key) {
return (T) this.values.get(key);
}
private <T> T getValue(String key, T defaultValue) {
public <T> T getValue(String key, T defaultValue) {
T value = getValue(key);
return value != null ? value : defaultValue;

View File

@@ -0,0 +1,425 @@
/*
* Copyright 2021 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.core.query;
import static org.springframework.data.couchbase.core.query.Meta.MetaKey.RETRY_STRATEGY;
import static org.springframework.data.couchbase.core.query.Meta.MetaKey.SCAN_CONSISTENCY;
import static org.springframework.data.couchbase.core.query.Meta.MetaKey.TIMEOUT;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.time.Duration;
import java.util.Optional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
import org.springframework.data.couchbase.repository.Collection;
import org.springframework.data.couchbase.repository.ScanConsistency;
import org.springframework.data.couchbase.repository.Scope;
import org.springframework.data.couchbase.repository.query.CouchbaseQueryMethod;
import com.couchbase.client.core.io.CollectionIdentifier;
import com.couchbase.client.core.msg.kv.DurabilityLevel;
import com.couchbase.client.core.retry.RetryStrategy;
import com.couchbase.client.java.json.JsonArray;
import com.couchbase.client.java.json.JsonObject;
import com.couchbase.client.java.kv.ExistsOptions;
import com.couchbase.client.java.kv.InsertOptions;
import com.couchbase.client.java.kv.PersistTo;
import com.couchbase.client.java.kv.RemoveOptions;
import com.couchbase.client.java.kv.ReplaceOptions;
import com.couchbase.client.java.kv.ReplicateTo;
import com.couchbase.client.java.kv.UpsertOptions;
import com.couchbase.client.java.query.QueryOptions;
import com.couchbase.client.java.query.QueryScanConsistency;
public class OptionsBuilder {
private static final Logger LOG = LoggerFactory.getLogger(OptionsBuilder.class);
static QueryOptions buildQueryOptions(Query query, QueryOptions options, QueryScanConsistency scanConsistency) {
options = options != null ? options : QueryOptions.queryOptions();
if (query.getParameters() != null) {
if (query.getParameters() instanceof JsonArray) {
options.parameters((JsonArray) query.getParameters());
} else {
options.parameters((JsonObject) query.getParameters());
}
}
Meta meta = query.getMeta() != null ? query.getMeta() : new Meta();
QueryOptions.Built optsBuilt = options.build();
JsonObject optsJson = getQueryOpts(optsBuilt);
QueryScanConsistency metaQueryScanConsistency = meta.get(SCAN_CONSISTENCY) != null
? ((ScanConsistency) meta.get(SCAN_CONSISTENCY)).query()
: null;
QueryScanConsistency qsc = fromFirst(QueryScanConsistency.NOT_BOUNDED, getScanConsistency(optsJson),
scanConsistency, metaQueryScanConsistency);
Duration timeout = fromFirst(Duration.ofSeconds(0), getTimeout(optsBuilt), meta.get(TIMEOUT));
RetryStrategy retryStrategy = fromFirst(null, getRetryStrategy(optsBuilt), meta.get(RETRY_STRATEGY));
if (qsc != null) {
options.scanConsistency(qsc);
}
if (timeout != null) {
options.timeout(timeout);
}
if (retryStrategy != null) {
options.retryStrategy(retryStrategy);
}
if (LOG.isTraceEnabled()) {
LOG.trace("query options: {}", getQueryOpts(options.build()));
}
return options;
}
public static ExistsOptions buildExistsOptions(ExistsOptions options) {
options = options != null ? options : ExistsOptions.existsOptions();
return options;
}
public static InsertOptions buildInsertOptions(InsertOptions options, PersistTo persistTo, ReplicateTo replicateTo,
DurabilityLevel durabilityLevel, Duration expiry, CouchbaseDocument doc) {
options = options != null ? options : InsertOptions.insertOptions();
if (persistTo != PersistTo.NONE || replicateTo != ReplicateTo.NONE) {
options.durability(persistTo, replicateTo);
} else if (durabilityLevel != DurabilityLevel.NONE) {
options.durability(durabilityLevel);
}
if (expiry != null) {
options.expiry(expiry);
} else if (doc.getExpiration() != 0) {
options.expiry(Duration.ofSeconds(doc.getExpiration()));
}
if (LOG.isTraceEnabled()) {
LOG.trace("insert options: {}" + toString(options));
}
return options;
}
public static UpsertOptions buildUpsertOptions(UpsertOptions options, PersistTo persistTo, ReplicateTo replicateTo,
DurabilityLevel durabilityLevel, Duration expiry, CouchbaseDocument doc) {
options = options != null ? options : UpsertOptions.upsertOptions();
if (persistTo != PersistTo.NONE || replicateTo != ReplicateTo.NONE) {
options.durability(persistTo, replicateTo);
} else if (durabilityLevel != DurabilityLevel.NONE) {
options.durability(durabilityLevel);
}
if (expiry != null) {
options.expiry(expiry);
} else if (doc.getExpiration() != 0) {
options.expiry(Duration.ofSeconds(doc.getExpiration()));
}
if (LOG.isTraceEnabled()) {
LOG.trace("upsert options: {}" + toString(options));
}
return options;
}
public static ReplaceOptions buildReplaceOptions(ReplaceOptions options, PersistTo persistTo, ReplicateTo replicateTo,
DurabilityLevel durabilityLevel, Duration expiry, Long cas, CouchbaseDocument doc) {
options = options != null ? options : ReplaceOptions.replaceOptions();
if (persistTo != PersistTo.NONE || replicateTo != ReplicateTo.NONE) {
options.durability(persistTo, replicateTo);
} else if (durabilityLevel != DurabilityLevel.NONE) {
options.durability(durabilityLevel);
}
if (expiry != null) {
options.expiry(expiry);
} else if (doc.getExpiration() != 0) {
options.expiry(Duration.ofSeconds(doc.getExpiration()));
}
if (cas != null) {
options.cas(cas);
}
if (LOG.isTraceEnabled()) {
LOG.trace("replace options: {}" + toString(options));
}
return options;
}
public static RemoveOptions buildRemoveOptions(RemoveOptions options, PersistTo persistTo, ReplicateTo replicateTo,
DurabilityLevel durabilityLevel, Long cas) {
options = options != null ? options : RemoveOptions.removeOptions();
if (persistTo != PersistTo.NONE || replicateTo != ReplicateTo.NONE) {
options.durability(persistTo, replicateTo);
} else if (durabilityLevel != DurabilityLevel.NONE) {
options.durability(durabilityLevel);
}
RemoveOptions.Built optsBuilt = options.build();
Duration timeout = fromFirst(Duration.ofSeconds(0), optsBuilt.timeout());
RetryStrategy retryStrategy = fromFirst(null, optsBuilt.retryStrategy());
if (timeout != null) {
options.timeout(timeout);
}
if (retryStrategy != null) {
options.retryStrategy(retryStrategy);
}
if (cas != null) {
options.cas(cas);
}
if (LOG.isTraceEnabled()) {
LOG.trace("remove options: {}" + toString(options));
}
return options;
}
/**
* scope annotation could be a
*
* @param domainType
* @return
*/
public static String getScopeFrom(Class<?> domainType) {
if (domainType == null) {
return null;
}
Scope ann = AnnotatedElementUtils.findMergedAnnotation(domainType, Scope.class);
if (ann != null && !CollectionIdentifier.DEFAULT_COLLECTION.equals(ann.value())) {
return ann.value();
}
return null;
}
public static String getCollectionFrom(Class<?> domainType) {
if (domainType == null) {
return null;
}
Collection ann = AnnotatedElementUtils.findMergedAnnotation(domainType, Collection.class);
if (ann != null && !CollectionIdentifier.DEFAULT_COLLECTION.equals(ann.value())) {
return ann.value();
}
return null;
}
static String toString(InsertOptions o) {
StringBuilder s = new StringBuilder();
InsertOptions.Built b = o.build();
s.append("{");
s.append("durabilityLevel: " + b.durabilityLevel());
s.append(", persistTo: " + b.persistTo());
s.append(", replicateTo: " + b.replicateTo());
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(UpsertOptions o) {
StringBuilder s = new StringBuilder();
UpsertOptions.Built b = o.build();
s.append("{");
s.append("durabilityLevel: " + b.durabilityLevel());
s.append(", persistTo: " + b.persistTo());
s.append(", replicateTo: " + b.replicateTo());
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(ReplaceOptions o) {
StringBuilder s = new StringBuilder();
ReplaceOptions.Built b = o.build();
s.append("{");
s.append("cas: " + b.cas());
s.append(", durabilityLevel: " + b.durabilityLevel());
s.append(", persistTo: " + b.persistTo());
s.append(", replicateTo: " + b.replicateTo());
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(RemoveOptions o) {
StringBuilder s = new StringBuilder();
RemoveOptions.Built b = o.build();
s.append("{");
s.append("cas: " + b.cas());
s.append(", durabilityLevel: " + b.durabilityLevel());
s.append(", persistTo: " + b.persistTo());
s.append(", replicateTo: " + b.replicateTo());
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();
}
private static JsonObject getQueryOpts(QueryOptions.Built optsBuilt) {
JsonObject jo = JsonObject.create();
optsBuilt.injectParams(jo);
return jo;
}
/**
* Get the most-specific
*
* @param deflt the default value, which we treat as not set
* @param choice array of values or Optional&lt;values&gt;, ordered from most to least specific
* @param <T>
* @return the most specific choice
*/
public static <T> T fromFirst(T deflt, Object... choice) {
T chosen = choice[0] instanceof Optional ? ((Optional<T>) choice[0]).orElse(null) : (T) choice[0];
for (int i = 1; i < choice.length; i++) {
if (chosen == null || chosen.equals(deflt)) { // overwrite null or default...
if (choice[i] != null) { // ... with non-null
chosen = choice[i] instanceof Optional ? ((Optional<T>) choice[i]).orElse(null) : (T) choice[i];
}
}
}
return chosen;
}
private static QueryScanConsistency getScanConsistency(JsonObject opts) {
String str = opts.getString("scan_consistency");
if ("at_plus".equals(str)) {
return null;
}
return str == null ? null : QueryScanConsistency.valueOf(str.toUpperCase());
}
private static JsonObject getScanVectors(JsonObject opts) {
return opts.getObject("scan_vectors");
}
private static Duration getTimeout(QueryOptions.Built optsBuilt) {
Optional<Duration> timeout = optsBuilt.timeout();
return timeout.isPresent() ? timeout.get() : null;
}
private static RetryStrategy getRetryStrategy(QueryOptions.Built optsBuilt) {
Optional<RetryStrategy> retryStrategy = optsBuilt.retryStrategy();
return retryStrategy.isPresent() ? retryStrategy.get() : null;
}
public static Meta buildMeta(CouchbaseQueryMethod method, Class<?> typeToRead) {
Meta meta = new Meta();
// Scope and Collection annotations are handled in PseudArgs
// this would include a ScanConsistency in a composed annotation as well.
meta.set(SCAN_CONSISTENCY, method.getScanConsistencyAnnotation());
return meta;
}
/**
* return the first merged annotation which does not have attribute with null/defaultValue from the listed elements.
*
* @param <A>
* @param annotation
* @param attributeName
* @param defaultValue
* @param elements
* @return
*/
public static <A extends Annotation, V> A annotation(Class<A> annotation, String attributeName, V defaultValue,
AnnotatedElement... elements) {
int i = 1;
for (AnnotatedElement el : elements) {
A an = AnnotatedElementUtils.findMergedAnnotation(el, annotation);
if (an != null) {
if (defaultValue != null) {
try {
Method m = an.getClass().getMethod(attributeName);
V value = (V) m.invoke(an);
if (!defaultValue.equals(value)) {
return an;
}
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
} else {
return an;
}
}
}
return null;
}
public static <A extends Annotation, V> A annotation(Class<A> annotation, V defaultValue,
AnnotatedElement[] elements) {
return annotation(annotation, "value", defaultValue, elements);
}
/**
* return the first merged annotation which is not null/defaultValue from the listed elements.
*
* @param <A>
* @param annotation
* @param defaultValue
* @param elements
* @return
*/
public static <A extends Annotation, V> V annotationAttribute(Class<A> annotation, String attributeName,
V defaultValue, AnnotatedElement[] elements) {
for (AnnotatedElement el : elements) {
A an = AnnotatedElementUtils.findMergedAnnotation(el, annotation);
if (an != null) {
if (defaultValue != null && !defaultValue.equals(an)) {
try {
Method m = an.getClass().getMethod(attributeName);
return (V) m.invoke(an);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
}
}
}
return null;
}
/**
* return the toString() of the first merged annotation which is not null/defaultValue from the listed elements.
*
* @param annotation
* @param defaultValue
* @param elements
* @param <A>
* @return
*/
public static <A extends Annotation> String annotationString(Class<A> annotation, String attributeName,
Object defaultValue, AnnotatedElement[] elements) {
A result = annotation(annotation, defaultValue, elements);
if (result == null) {
return null;
}
try {
Method m = result.getClass().getMethod(attributeName);
Object value = m.invoke(result);
return value.toString();
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
}
public static <A extends Annotation> String annotationString(Class<A> annotation, Object defaultValue,
AnnotatedElement[] elements) {
return annotationString(annotation, "value", defaultValue, elements);
}
}

View File

@@ -25,6 +25,7 @@ import org.slf4j.LoggerFactory;
import org.springframework.data.couchbase.core.ReactiveCouchbaseTemplate;
import org.springframework.data.couchbase.core.convert.CouchbaseConverter;
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentEntity;
import org.springframework.data.couchbase.repository.query.CouchbaseQueryMethod;
import org.springframework.data.couchbase.repository.query.StringBasedN1qlQueryParser;
import org.springframework.data.couchbase.repository.support.MappingCouchbaseEntityInformation;
import org.springframework.data.domain.Pageable;
@@ -52,6 +53,7 @@ public class Query {
private int limit;
private Sort sort = Sort.unsorted();
private QueryScanConsistency queryScanConsistency;
private Meta meta;
static private final Pattern WHERE_PATTERN = Pattern.compile("\\sWHERE\\s");
private static final Logger LOG = LoggerFactory.getLogger(Query.class);
@@ -326,28 +328,22 @@ public class Query {
* @return QueryOptions
*/
public QueryOptions buildQueryOptions(QueryOptions options, QueryScanConsistency scanConsistency) {
if (options == null) { // add/override what we got from PseudoArgs
options = QueryOptions.queryOptions();
}
if (getParameters() != null) {
if (getParameters() instanceof JsonArray) {
options.parameters((JsonArray) getParameters());
} else {
options.parameters((JsonObject) getParameters());
}
}
if (scanConsistency == null
|| scanConsistency == QueryScanConsistency.NOT_BOUNDED && getScanConsistency() != null) {
scanConsistency = getScanConsistency();
}
if (scanConsistency != null) {
options.scanConsistency(scanConsistency);
}
return options;
return OptionsBuilder.buildQueryOptions(this, options, scanConsistency);
}
public void setMeta(Meta metaAnnotation) {
Meta meta = metaAnnotation;
/**
* this collections annotations from the method, repository class and possibly the entity class to be used as options.
* This will find annotations included in composed annotations as well. Ideally
*
* @param method representing the query.
* @return the query with the annotations applied
*/
public void setMeta(CouchbaseQueryMethod method, Class<?> typeToRead) {
meta = OptionsBuilder.buildMeta(method, typeToRead);
}
public Meta getMeta() {
return meta;
}
}

View File

@@ -15,9 +15,14 @@
*/
package org.springframework.data.couchbase.core.support;
import com.couchbase.client.core.io.CollectionIdentifier;
import static org.springframework.data.couchbase.core.query.OptionsBuilder.fromFirst;
import static org.springframework.data.couchbase.core.query.OptionsBuilder.getCollectionFrom;
import static org.springframework.data.couchbase.core.query.OptionsBuilder.getScopeFrom;
import org.springframework.data.couchbase.core.ReactiveCouchbaseTemplate;
import com.couchbase.client.core.io.CollectionIdentifier;
public class PseudoArgs<OPTS> {
private final OPTS options;
private final String scopeName;
@@ -35,24 +40,32 @@ public class PseudoArgs<OPTS> {
* 2) values from dynamic proxy (via template threadLocal)<br>
* 3) the values from the couchbaseClientFactory<br>
*
* @param template to hold
* @param scope
* @param collection
* @param options
* @param template which holds ThreadLocal pseudo args
* @param scope - from calling operation
* @param collection - from calling operation
* @param options - from calling operation
* @param domainType - entity that may have annotations
*/
public PseudoArgs(ReactiveCouchbaseTemplate template, String scope, String collection, OPTS options) {
public PseudoArgs(ReactiveCouchbaseTemplate template, String scope, String collection, OPTS options,
Class<?> domainType) {
// 1) values from the args (fluent api)
String scopeForQuery = null;
String collectionForQuery = null;
OPTS optionsForQuery = null;
String scopeForQuery = scope;
String collectionForQuery = collection;
OPTS optionsForQuery = options;
// 1) repository from DynamicProxy via template threadLocal - has precedence over annotation
// 2) from DynamicProxy via template threadLocal
PseudoArgs<OPTS> threadLocal = (PseudoArgs<OPTS>) template.getPseudoArgs();
template.setPseudoArgs(null);
if (threadLocal != null) {
scopeForQuery = threadLocal.getScope();
collectionForQuery = threadLocal.getCollection();
optionsForQuery = threadLocal.getOptions();
}
scopeForQuery = scopeForQuery != null ? scopeForQuery : getThreadLocalScopeName(template);
collectionForQuery = collectionForQuery != null ? collectionForQuery : getThreadLocalCollectionName(template);
optionsForQuery = optionsForQuery != null ? optionsForQuery : getThreadLocalOptions(template);
scopeForQuery = fromFirst(null, scopeForQuery, scope, getScopeFrom(domainType));
collectionForQuery = fromFirst(null, collectionForQuery, collection, getCollectionFrom(domainType));
optionsForQuery = fromFirst(null, options, optionsForQuery);
// if a collection was specified but no scope, use the scope from the clientFactory
@@ -62,56 +75,42 @@ public class PseudoArgs<OPTS> {
// specifying scope and collection = _default is not necessary and will fail if server doesn't have collections
if ((scopeForQuery == null || CollectionIdentifier.DEFAULT_SCOPE.equals(scopeForQuery))
&& (collectionForQuery == null || CollectionIdentifier.DEFAULT_COLLECTION.equals(collectionForQuery))) {
scopeForQuery = null;
collectionForQuery = null;
if (scopeForQuery == null || CollectionIdentifier.DEFAULT_SCOPE.equals(scopeForQuery)) {
if (collectionForQuery == null || CollectionIdentifier.DEFAULT_COLLECTION.equals(collectionForQuery)) {
collectionForQuery = null;
scopeForQuery = null;
}
}
this.scopeName = scopeForQuery;
this.collectionName = collectionForQuery;
this.options = optionsForQuery;
}
/**
* @@return the options
* @return the options
*/
public OPTS getOptions() {
return this.options;
}
/**
* @@return the scope name
* @return the scope name
*/
public String getScope() {
return this.scopeName;
}
/**
* @@return the collection name
* @return the collection name
*/
public String getCollection() {
return this.collectionName;
}
/**
* @@return the options from the ThreadLocal field of the template
*/
private OPTS getThreadLocalOptions(ReactiveCouchbaseTemplate template) {
return template.getPseudoArgs() == null ? null : (OPTS) (template.getPseudoArgs().getOptions());
}
/**
* @@return the scope name from the ThreadLocal field of the template
*/
private String getThreadLocalScopeName(ReactiveCouchbaseTemplate template) {
return template.getPseudoArgs() == null ? null : template.getPseudoArgs().getScope();
}
/**
* @@return the collection name from the ThreadLocal field of the template
*/
private String getThreadLocalCollectionName(ReactiveCouchbaseTemplate template) {
return template.getPseudoArgs() == null ? null : template.getPseudoArgs().getCollection();
@Override
public String toString() {
return "scope: " + getScope() + " collection: " + getCollection() + " options: " + getOptions();
}
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright 2012-2021 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.repository;
import static com.couchbase.client.core.io.CollectionIdentifier.DEFAULT_COLLECTION;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.data.annotation.QueryAnnotation;
/**
* Collection Annotation
*
* @author Michael Reiche
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE })
@Documented
@QueryAnnotation
public @interface Collection {
/**
* Specifies the collection name
*
* @return the collection name configured, defaults to not DEFAULT_COLLECTION.
*/
String value() default DEFAULT_COLLECTION;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2020 the original author or authors.
* Copyright 2013-2021 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.
@@ -18,16 +18,20 @@ package org.springframework.data.couchbase.repository;
import java.util.List;
import com.couchbase.client.java.query.QueryScanConsistency;
import org.springframework.data.couchbase.core.CouchbaseOperations;
import org.springframework.data.couchbase.repository.query.CouchbaseEntityInformation;
import org.springframework.data.domain.Sort;
import org.springframework.data.repository.NoRepositoryBean;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.repository.Repository;
import com.couchbase.client.java.query.QueryScanConsistency;
/**
* Couchbase specific {@link Repository} interface.
*
* @author Michael Nitschinger
* @author Michael Reiche
*/
@NoRepositoryBean
public interface CouchbaseRepository<T, ID> extends PagingAndSortingRepository<T, ID> {
@@ -43,4 +47,8 @@ public interface CouchbaseRepository<T, ID> extends PagingAndSortingRepository<T
@Override
List<T> findAllById(Iterable<ID> iterable);
CouchbaseEntityInformation<T, String> getEntityInformation();
CouchbaseOperations getOperations();
}

View File

@@ -0,0 +1,78 @@
/*
* Copyright 2017-2021 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.repository;
import java.lang.reflect.Proxy;
import org.springframework.data.couchbase.repository.query.CouchbaseEntityInformation;
import org.springframework.data.couchbase.repository.support.DynamicInvocationHandler;
import com.couchbase.client.java.CommonOptions;
/**
* The generic parameter needs to be REPO which is either a CouchbaseRepository parameterized on T,ID or a
* ReactiveCouchbaseRepository parameterized on T,ID. i.e.: interface AirportRepository extends
* CouchbaseRepository<Airport, String>, DynamicProxyable<AirportRepository>
*
* @param <REPO>
* @author Michael Reiche
*/
public interface DynamicProxyable<REPO> {
CouchbaseEntityInformation getEntityInformation();
Object getOperations();
/**
* Support for Couchbase-specific options, scope and collections The three "with" methods will return a new proxy
* instance with the specified options, scope, or collections set. The setters are called with the corresponding
* options, scope and collection to set the ThreadLocal fields on the CouchbaseOperations of the repository just
* before the call is made to the repository, and called again with 'null' just after the call is made. The repository
* method will fetch those values to use in the call.
*/
/**
* @param options - the options to set on the returned repository object
*/
@SuppressWarnings("unchecked")
default REPO withOptions(CommonOptions<?> options) {
REPO proxyInstance = (REPO) Proxy.newProxyInstance(this.getClass().getClassLoader(),
this.getClass().getInterfaces(), new DynamicInvocationHandler(this, options, null, (String) null));
return proxyInstance;
}
/**
* @param scope - the scope to set on the returned repository object
*/
@SuppressWarnings("unchecked")
default REPO withScope(String scope) {
REPO proxyInstance = (REPO) Proxy.newProxyInstance(this.getClass().getClassLoader(),
this.getClass().getInterfaces(), new DynamicInvocationHandler<>(this, null, null, scope));
return proxyInstance;
}
/**
* @param collection - the collection to set on the returned repository object
*/
@SuppressWarnings("unchecked")
default REPO withCollection(String collection) {
REPO proxyInstance = (REPO) Proxy.newProxyInstance(this.getClass().getClassLoader(),
this.getClass().getInterfaces(), new DynamicInvocationHandler<>(this, null, collection, null));
return proxyInstance;
}
}

View File

@@ -0,0 +1,117 @@
/*
* Copyright 2012-2021 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.repository;
import static com.couchbase.client.core.io.CollectionIdentifier.DEFAULT_COLLECTION;
import static com.couchbase.client.core.io.CollectionIdentifier.DEFAULT_SCOPE;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.concurrent.TimeUnit;
import org.springframework.data.annotation.QueryAnnotation;
import com.couchbase.client.java.analytics.AnalyticsScanConsistency;
import com.couchbase.client.java.query.QueryScanConsistency;
/**
* Scope Annotation
*
* @author Michael Reiche
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE })
@Documented
@QueryAnnotation
public @interface Options {
/**
* Specifies the scope name
*
* @return the scope name configured, defaults to not DEFAULT_SCOPE.
*/
String scope() default DEFAULT_SCOPE;
/**
* Specifies the scope name
*
* @return the scope name configured, defaults to not DEFAULT_SCOPE.
*/
String collection() default DEFAULT_COLLECTION;
/**
* Specifies a custom scan consistency for N1QL queries.
*
* @return the scan consistency configured, defaults to not bounded.
*/
QueryScanConsistency query() default QueryScanConsistency.NOT_BOUNDED;
/**
* Specifies a custom scan consistency for analytics queries.
*
* @return the scan consistency configured, defaults to not bounded.
*/
AnalyticsScanConsistency analytics() default AnalyticsScanConsistency.NOT_BOUNDED;
/**
* Specifies a custom projection.
*
* @return the projection configured, defaults to an empty array (project everything).
*/
String[] project() default {};
/**
* Specifies a custom array of distinct fields.
*
* @return the projection configured, we need to do something tricky with the default. We need to default to<br>
* no distinct, which is specified by a null array (an empty array means distinct on everything). We'll make
* an array of a single element "-" mean no distinct.
*/
String[] distinct() default { "-" };
/**
* An optional expiry time for the document. Default is no expiry. Only one of two might might be set at the same
* time: either {@link #expiry()} or {@link #expiryExpression()}
*/
int expiry() default 0;
/**
* Same as {@link #expiry} 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 #expiry()} or {@link #expiryExpression()}. <br />
* Syntax is the same as for {@link org.springframework.core.env.Environment#resolveRequiredPlaceholders(String)}.
* <br />
* <br />
* The value will be recalculated for every {@link org.springframework.data.couchbase.core.CouchbaseTemplate}
* save/insert/update call, thus allowing actual expiration to reflect changes on-the-fly as soon as property sources
* change. <br />
* <br />
* SpEL is NOT supported.
*/
String expiryExpression() default "";
/**
* An optional time unit for the document's {@link #expiry()}, if set. Default is {@link TimeUnit#SECONDS}.
*/
TimeUnit expiryUnit() default TimeUnit.SECONDS;
/**
* An timeout for the operation. Default is no timeout.
*/
long timeoutMs() default 0;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2017-2020 the original author or authors.
* Copyright 2017-2021 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.
@@ -15,6 +15,8 @@
*/
package org.springframework.data.couchbase.repository;
import org.springframework.data.couchbase.core.ReactiveCouchbaseOperations;
import org.springframework.data.couchbase.repository.query.CouchbaseEntityInformation;
import org.springframework.data.repository.NoRepositoryBean;
import org.springframework.data.repository.reactive.ReactiveSortingRepository;
@@ -22,9 +24,12 @@ import org.springframework.data.repository.reactive.ReactiveSortingRepository;
* Couchbase-specific {@link ReactiveSortingRepository} implementation.
*
* @author Subhashni Balakrishnan
* @author Michael Reiche
* @since 3.0
*/
@NoRepositoryBean
public interface ReactiveCouchbaseRepository<T, ID> extends ReactiveSortingRepository<T, ID> {
ReactiveCouchbaseOperations getOperations();
CouchbaseEntityInformation<T, String> getEntityInformation();
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors
* Copyright 2012-2021 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.
@@ -24,8 +24,13 @@ import java.lang.annotation.Target;
import com.couchbase.client.java.analytics.AnalyticsScanConsistency;
import com.couchbase.client.java.query.QueryScanConsistency;
/**
* Scan Consistency Annotation
*
* @author Michael Reiche
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE })
@Documented
public @interface ScanConsistency {

View File

@@ -1,11 +1,11 @@
/*
* Copyright 2020 the original author or authors.
* Copyright 2012-2021 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
* 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,
@@ -15,6 +15,8 @@
*/
package org.springframework.data.couchbase.repository;
import static com.couchbase.client.core.io.CollectionIdentifier.DEFAULT_SCOPE;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
@@ -24,13 +26,21 @@ import java.lang.annotation.Target;
import org.springframework.data.annotation.QueryAnnotation;
/**
* Scope Annotation
*
* @author Michael Reiche
* @since 4.1
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE })
@Documented
@QueryAnnotation
public @interface Meta {
public @interface Scope {
/**
* Specifies the scope name
*
* @return the scope name configured, defaults to DEFAULT_SCOPE.
*/
String value() default DEFAULT_SCOPE;
}

View File

@@ -1,3 +1,18 @@
/*
* Copyright 2021 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.repository.auditing;
import static org.springframework.data.couchbase.config.BeanNames.REACTIVE_COUCHBASE_AUDITING_HANDLER;
@@ -13,7 +28,6 @@ import org.springframework.data.auditing.config.AuditingBeanDefinitionRegistrarS
import org.springframework.data.auditing.config.AuditingConfiguration;
import org.springframework.data.config.ParsingUtils;
import org.springframework.data.couchbase.core.mapping.event.ReactiveAuditingEntityCallback;
import org.springframework.data.couchbase.core.mapping.event.ReactiveAuditingEventListener;
import org.springframework.util.Assert;
/**
@@ -82,17 +96,6 @@ class ReactiveCouchbaseAuditingRegistrar extends AuditingBeanDefinitionRegistrar
registerInfrastructureBeanWithId(builder.getBeanDefinition(), ReactiveAuditingEntityCallback.class.getName(),
registry);
// Register the AuditingEventListener
BeanDefinitionBuilder builder2 = BeanDefinitionBuilder.rootBeanDefinition(ReactiveAuditingEventListener.class);
builder2
.addConstructorArgValue(ParsingUtils.getObjectFactoryBeanDefinition(getAuditingHandlerBeanName(), registry));
builder.getRawBeanDefinition().setSource(auditingHandlerDefinition.getSource());
registerInfrastructureBeanWithId(builder2.getBeanDefinition(), ReactiveAuditingEventListener.class.getName(),
registry);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2020 the original author or authors
* Copyright 2020-2021 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.
@@ -15,7 +15,6 @@
*/
package org.springframework.data.couchbase.repository.query;
import com.couchbase.client.core.io.CollectionIdentifier;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.couchbase.core.CouchbaseOperations;
import org.springframework.data.couchbase.core.ExecutableFindByQueryOperation.ExecutableFindByQuery;
@@ -60,10 +59,11 @@ public abstract class AbstractCouchbaseQuery extends AbstractCouchbaseQueryBase<
Assert.notNull(operations, "ReactiveCouchbaseOperations must not be null!");
Assert.notNull(expressionParser, "SpelExpressionParser must not be null!");
Assert.notNull(evaluationContextProvider, "QueryMethodEvaluationContextProvider must not be null!");
// this.operations = operations;
EntityMetadata<?> metadata = method.getEntityInformation();
Class<?> type = metadata.getJavaType();
this.findOperationWithProjection = operations.findByQuery(type);
ExecutableFindByQuery<?> findOp = operations.findByQuery(type);
findOp = (ExecutableFindByQuery<?>) (findOp.inScope(method.getScope()).inCollection(method.getCollection()));
this.findOperationWithProjection = findOp;
}
/**
@@ -80,18 +80,14 @@ public abstract class AbstractCouchbaseQuery extends AbstractCouchbaseQueryBase<
ParametersParameterAccessor accessor, @Nullable Class<?> typeToRead) {
Query query = createQuery(accessor);
query = applyAnnotatedConsistencyIfPresent(query);
// query = applyAnnotatedCollationIfPresent(query, accessor); // not yet implemented
query = applyQueryMetaAttributesIfPresent(query, typeToRead);
ExecutableFindByQuery<?> find = typeToRead == null ? findOperationWithProjection //
: findOperationWithProjection; // not yet implemented in core .as(typeToRead);
String collection = null;
ExecutableFindByQuery<?> find = findOperationWithProjection;
CouchbaseQueryExecution execution = getExecution(accessor,
new ResultProcessingConverter<>(processor, getOperations(), getInstantiators()), find);
return execution.execute(query, processor.getReturnedType().getDomainType(), collection);
return execution.execute(query, processor.getReturnedType().getDomainType(), null);
}
/**
@@ -114,8 +110,7 @@ public abstract class AbstractCouchbaseQuery extends AbstractCouchbaseQueryBase<
* @param operation must not be {@literal null}.
* @return
*/
private CouchbaseQueryExecution getExecutionToWrap(ParameterAccessor accessor,
ExecutableFindByQuery<?> operation) {
private CouchbaseQueryExecution getExecutionToWrap(ParameterAccessor accessor, ExecutableFindByQuery<?> operation) {
if (isDeleteQuery()) {
return new DeleteExecution(getOperations(), getQueryMethod());
@@ -123,6 +118,8 @@ public abstract class AbstractCouchbaseQuery extends AbstractCouchbaseQueryBase<
return (q, t, c) -> operation.matching(q.with(accessor.getPageable())).all(); // s/b tail() instead of all()
} else if (getQueryMethod().isCollectionQuery()) {
return (q, t, c) -> operation.matching(q.with(accessor.getPageable())).all();
} else if (getQueryMethod().isStreamQuery()) {
return (q, t, c) -> operation.matching(q.with(accessor.getPageable())).stream();
} else if (isCountQuery()) {
return (q, t, c) -> operation.matching(q).count();
} else if (isExistsQuery()) {
@@ -140,18 +137,4 @@ public abstract class AbstractCouchbaseQuery extends AbstractCouchbaseQueryBase<
}
}
/**
* Apply Meta annotation to query
*
* @param query must not be {@literal null}.
* @return Query
*/
Query applyQueryMetaAttributesWhenPresent(Query query) {
if (getQueryMethod().hasQueryMetaAttributes()) {
query.setMeta(getQueryMethod().getQueryMetaAttributes());
}
return query;
}
}

View File

@@ -146,18 +146,14 @@ public abstract class AbstractCouchbaseQueryBase<CouchbaseOperationsType> implem
ParametersParameterAccessor accessor, @Nullable Class<?> typeToRead);
/**
* Add a scan consistency from {@link org.springframework.data.couchbase.repository.ScanConsistency} to the given
* {@link Query} if present.
* Apply Meta annotation to query
*
* @param query the {@link Query} to potentially apply the sort to.
* @return the query with potential scan consistency applied.
* @since 4.1
* @param query must not be {@literal null}.
* @return Query
*/
Query applyAnnotatedConsistencyIfPresent(Query query) {
if (!method.hasScanConsistencyAnnotation()) {
return query;
}
return query.scanConsistency(method.getScanConsistencyAnnotation().query());
Query applyQueryMetaAttributesIfPresent(Query query, Class<?> typeToRead) {
query.setMeta(getQueryMethod(), typeToRead);
return query;
}
/**

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2020 the original author or authors.
* Copyright 2020-2021 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.
@@ -15,7 +15,6 @@
*/
package org.springframework.data.couchbase.repository.query;
import com.couchbase.client.core.io.CollectionIdentifier;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.couchbase.core.ReactiveCouchbaseOperations;
import org.springframework.data.couchbase.core.ReactiveFindByQueryOperation;
@@ -63,7 +62,9 @@ public abstract class AbstractReactiveCouchbaseQuery extends AbstractCouchbaseQu
EntityMetadata<?> metadata = method.getEntityInformation();
Class<?> type = metadata.getJavaType();
this.findOperationWithProjection = operations.findByQuery(type);
ReactiveFindByQuery<?> findOp = operations.findByQuery(type);
findOp = (ReactiveFindByQuery<?>) (findOp.inScope(method.getScope()).inCollection(method.getCollection()));
this.findOperationWithProjection = findOp;
}
/**
@@ -79,18 +80,14 @@ public abstract class AbstractReactiveCouchbaseQuery extends AbstractCouchbaseQu
ParametersParameterAccessor accessor, @Nullable Class<?> typeToRead) {
Query query = createQuery(accessor);
query = applyAnnotatedConsistencyIfPresent(query);
// query = applyAnnotatedCollationIfPresent(query, accessor); // not yet implemented
query = applyQueryMetaAttributesIfPresent(query, typeToRead);
ReactiveFindByQuery<?> find = typeToRead == null //
? findOperationWithProjection //
: findOperationWithProjection; // note yet implemented in core .as(typeToRead);
String collection = null;
ReactiveFindByQuery<?> find = findOperationWithProjection;
ReactiveCouchbaseQueryExecution execution = getExecution(accessor,
new ResultProcessingConverter<>(processor, getOperations(), getInstantiators()), find);
return execution.execute(query, processor.getReturnedType().getDomainType(), collection);
return execution.execute(query, processor.getReturnedType().getDomainType(), null);
}
/**
@@ -121,6 +118,8 @@ public abstract class AbstractReactiveCouchbaseQuery extends AbstractCouchbaseQu
return (q, t, c) -> operation.matching(q.with(accessor.getPageable())).all(); // s/b tail() instead of all()
} else if (getQueryMethod().isCollectionQuery()) {
return (q, t, c) -> operation.matching(q.with(accessor.getPageable())).all();
// } else if (getQueryMethod().isStreamQuery()) {
// return (q, t, c) -> operation.matching(q.with(accessor.getPageable())).all().toStream();
} else if (isCountQuery()) {
return (q, t, c) -> operation.matching(q).count();
} else if (isExistsQuery()) {
@@ -133,18 +132,4 @@ public abstract class AbstractReactiveCouchbaseQuery extends AbstractCouchbaseQu
}
}
/**
* Apply Meta annotation to query
*
* @param query must not be {@literal null}.
* @return Query
*/
Query applyQueryMetaAttributesWhenPresent(Query query) {
if (getQueryMethod().hasQueryMetaAttributes()) {
query.setMeta(getQueryMethod().getQueryMetaAttributes());
}
return query;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2020 the original author or authors.
* Copyright 2013-2021 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.
@@ -16,28 +16,33 @@
package org.springframework.data.couchbase.repository.query;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.util.Locale;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentEntity;
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty;
import org.springframework.data.couchbase.core.query.Dimensional;
import org.springframework.data.couchbase.core.query.OptionsBuilder;
import org.springframework.data.couchbase.core.query.View;
import org.springframework.data.couchbase.core.query.WithConsistency;
import org.springframework.data.couchbase.repository.Meta;
import org.springframework.data.couchbase.repository.Collection;
import org.springframework.data.couchbase.repository.Query;
import org.springframework.data.couchbase.repository.ScanConsistency;
import org.springframework.data.couchbase.repository.Scope;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.query.Parameter;
import org.springframework.data.repository.query.QueryMethod;
import org.springframework.data.repository.util.ReactiveWrapperConverters;
import org.springframework.lang.Nullable;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import com.couchbase.client.core.io.CollectionIdentifier;
/**
* Represents a query method with couchbase extensions, allowing to discover if View-based query or N1QL-based query
* must be used.
@@ -50,13 +55,13 @@ import org.springframework.util.StringUtils;
public class CouchbaseQueryMethod extends QueryMethod {
private final Method method;
private final RepositoryMetadata repositoryMetadata;
public CouchbaseQueryMethod(Method method, RepositoryMetadata metadata, ProjectionFactory factory,
MappingContext<? extends CouchbasePersistentEntity<?>, CouchbasePersistentProperty> mappingContext) {
super(method, metadata, factory);
this.method = method;
this.repositoryMetadata = metadata;
}
/**
@@ -176,39 +181,46 @@ public class CouchbaseQueryMethod extends QueryMethod {
* @return the @ScanConsistency annotation
*/
public ScanConsistency getScanConsistencyAnnotation() {
return method.getAnnotation(ScanConsistency.class);
AnnotatedElement[] annotated = new AnnotatedElement[] { method, method.getDeclaringClass(),
repositoryMetadata.getRepositoryInterface(), repositoryMetadata.getDomainType() };
return OptionsBuilder.annotation(ScanConsistency.class, "query", CollectionIdentifier.DEFAULT_COLLECTION,
annotated);
}
/**
* @return return true if {@link Meta} annotation is available.
* Caution: findMergedAnnotation() will return the default if there are any annotations but not this annotation
*
* @return annotation
*/
public boolean hasQueryMetaAttributes() {
return getMetaAnnotation() != null;
public <A extends Annotation> A getAnnotation(Class<A> annotationClass) {
return AnnotatedElementUtils.findMergedAnnotation(method, annotationClass);
}
/**
* @return return {@link Meta} annotation
* Caution: findMergedAnnotation() will return the default if there are any annotations but not this annotation
*
* @return annotation
*/
private Meta getMetaAnnotation() {
return method.getAnnotation(Meta.class);
public <A extends Annotation> A getClassAnnotation(Class<A> annotationClass) {
return AnnotatedElementUtils.findMergedAnnotation(method.getDeclaringClass(), annotationClass);
}
/**
* Returns the {@link org.springframework.data.couchbase.core.query.Meta} attributes to be applied.
*
* @return never {@literal null}.
* Caution: findMergedAnnotation() will return the default if there are any annotations but not this annotation
*
* @return annotation
*/
@Nullable
public org.springframework.data.couchbase.core.query.Meta getQueryMetaAttributes() {
public <A extends Annotation> A getEntityAnnotation(Class<A> annotationClass) {
return AnnotatedElementUtils.findMergedAnnotation(getEntityInformation().getJavaType(), annotationClass);
}
Meta meta = getMetaAnnotation();
if (meta == null) {
return new org.springframework.data.couchbase.core.query.Meta();
}
org.springframework.data.couchbase.core.query.Meta metaAttributes = new org.springframework.data.couchbase.core.query.Meta();
return metaAttributes;
/**
* Caution: findMergedAnnotation() will return the default if there are any annotations but not this annotation
*
* @return annotation
*/
public <A extends Annotation> A getRepositoryAnnotation(Class<A> annotationClass) {
return AnnotatedElementUtils.findMergedAnnotation(repositoryMetadata.getRepositoryInterface(), annotationClass);
}
/**
@@ -264,4 +276,18 @@ public class CouchbaseQueryMethod extends QueryMethod {
return false;
}
public String getCollection() {
// Try the repository method, then the repository class, then the entity class
AnnotatedElement[] annotated = new AnnotatedElement[] { method, method.getDeclaringClass(),
repositoryMetadata.getRepositoryInterface(), repositoryMetadata.getDomainType() };
return OptionsBuilder.annotationString(Collection.class, CollectionIdentifier.DEFAULT_COLLECTION, annotated);
}
public String getScope() {
// Try the repository method, then the repository class, then the entity class
AnnotatedElement[] annotated = new AnnotatedElement[] { method, method.getDeclaringClass(),
repositoryMetadata.getRepositoryInterface(), repositoryMetadata.getDomainType() };
return OptionsBuilder.annotationString(Scope.class, CollectionIdentifier.DEFAULT_SCOPE, annotated);
}
}

View File

@@ -91,7 +91,7 @@ public class ReactiveStringBasedCouchbaseQuery extends AbstractReactiveCouchbase
@Override
protected Query createCountQuery(ParametersParameterAccessor accessor) {
return applyQueryMetaAttributesWhenPresent(createQuery(accessor));
return applyQueryMetaAttributesIfPresent(createQuery(accessor), null);
}
/*

View File

@@ -79,8 +79,8 @@ public class StringBasedCouchbaseQuery extends AbstractCouchbaseQuery {
namedQueries);
Query query = creator.createQuery();
if (LOG.isDebugEnabled()) {
LOG.debug("Created query " + query.export());
if (LOG.isTraceEnabled()) {
LOG.trace("Created query " + query.export());
}
return query;
@@ -88,7 +88,7 @@ public class StringBasedCouchbaseQuery extends AbstractCouchbaseQuery {
@Override
protected Query createCountQuery(ParametersParameterAccessor accessor) {
return applyQueryMetaAttributesWhenPresent(createQuery(accessor));
return applyQueryMetaAttributesIfPresent(createQuery(accessor), null);
}
/*

View File

@@ -22,7 +22,6 @@ import static org.springframework.data.couchbase.core.support.TemplateUtils.SELE
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.regex.Matcher;
@@ -30,11 +29,7 @@ import java.util.regex.Pattern;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.data.convert.CustomConversions;
import org.springframework.data.couchbase.core.convert.CouchbaseConverter;
import org.springframework.data.couchbase.core.convert.CouchbaseCustomConversions;
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty;
import org.springframework.data.couchbase.core.query.N1QLExpression;
import org.springframework.data.couchbase.repository.Query;
@@ -131,8 +126,9 @@ public class StringBasedN1qlQueryParser {
this.statement = statement;
this.queryMethod = queryMethod;
this.couchbaseConverter = couchbaseConverter;
this.statementContext = createN1qlSpelValues(bucketName, null, null, null, typeField, typeValue, false, null);
this.countContext = createN1qlSpelValues(bucketName, null, null, null, typeField, typeValue, true, null);
String collection = queryMethod.getCollection();
this.statementContext = createN1qlSpelValues(bucketName, collection, null, null, typeField, typeValue, false, null);
this.countContext = createN1qlSpelValues(bucketName, collection, null, null, typeField, typeValue, true, null);
this.parsedExpression = getExpression(accessor, getParameters(accessor), null, parser, evaluationContextProvider);
checkPlaceholders(this.parsedExpression.toString());
}

View File

@@ -0,0 +1,123 @@
/*
* Copyright 2021 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.repository.support;
import java.lang.reflect.AnnotatedElement;
import org.springframework.data.couchbase.core.query.OptionsBuilder;
import org.springframework.data.couchbase.repository.Collection;
import org.springframework.data.couchbase.repository.ScanConsistency;
import org.springframework.data.couchbase.repository.Scope;
import org.springframework.data.couchbase.repository.query.CouchbaseEntityInformation;
import com.couchbase.client.core.io.CollectionIdentifier;
import com.couchbase.client.java.query.QueryScanConsistency;
public class CouchbaseRepositoryBase<T, ID> {
/**
* Contains information about the entity being used in this repository.
*/
private final CouchbaseEntityInformation<T, String> entityInformation;
private final Class<?> repositoryInterface;
private CrudMethodMetadata crudMethodMetadata;
public CouchbaseRepositoryBase(CouchbaseEntityInformation<T, String> entityInformation,
Class<?> repositoryInterface) {
this.entityInformation = entityInformation;
this.repositoryInterface = repositoryInterface;
}
/**
* Returns the information for the underlying template.
*
* @return the underlying entity information.
*/
public CouchbaseEntityInformation<T, String> getEntityInformation() {
return entityInformation;
}
Class<T> getJavaType() {
return getEntityInformation().getJavaType();
}
<S extends T> String getId(S entity) {
return getEntityInformation().getId(entity);
}
/**
* Get the Scope from <br>
* 1. The repository<br>
* 2. The entity<br>
* 3. otherwise null<br>
* This can be overriden in the operation method by<br>
* 1. repository.withCollection() 2. Annotation on the method
*/
String getScope() {
String fromAnnotation = OptionsBuilder.annotationString(Scope.class, CollectionIdentifier.DEFAULT_SCOPE,
new AnnotatedElement[] { getJavaType(), repositoryInterface });
String fromMetadata = crudMethodMetadata.getScope();
return OptionsBuilder.fromFirst(CollectionIdentifier.DEFAULT_SCOPE, fromMetadata, fromAnnotation);
}
/**
* Get the Collection from <br>
* 1. The repository<br>
* 2. The entity<br>
* 3. otherwise null<br>
* This can be overriden in the operation method by<br>
* 1. repository.withCollection()
*/
String getCollection() {
String fromAnnotation = OptionsBuilder.annotationString(Collection.class, CollectionIdentifier.DEFAULT_COLLECTION,
new AnnotatedElement[] { getJavaType(), repositoryInterface });
String fromMetadata = crudMethodMetadata.getCollection();
return OptionsBuilder.fromFirst(CollectionIdentifier.DEFAULT_COLLECTION, fromMetadata, fromAnnotation);
}
/**
* Get the QueryScanConsistency from <br>
* 1. The method annotation (method *could* be available from crudMethodMetadata)<br>
* 2. The repository<br>
* 3. The entity<br>
* 4. otherwise null<br>
* This can be overriden in the operation method by<br>
* 1. Options.scanConsistency (?)<br>
* AbstractCouchbaseQueryBase.applyAnnotatedConsistencyIfPresent() <br>
* CouchbaseRepository get picked up? If I have the following, will the annotation be picked up?<br>
* Only via crudMethodMetadata<br>
* \@ScanConsistency(query=QueryScanConsistency.REQUEST_PLUS)<br>
* List<T> findAll();<br>
*/
QueryScanConsistency buildQueryScanConsistency() {
ScanConsistency sc = crudMethodMetadata.getScanConsistency();
QueryScanConsistency fromMeta = sc != null ? sc.query() : null;
QueryScanConsistency fromAnnotation = OptionsBuilder.annotationAttribute(ScanConsistency.class, "query",
QueryScanConsistency.NOT_BOUNDED, new AnnotatedElement[] { getJavaType(), repositoryInterface });
return OptionsBuilder.fromFirst(QueryScanConsistency.NOT_BOUNDED, fromMeta, fromAnnotation);
}
/**
* Setter for the repository metadata, contains annotations on the overidden methods.
*
* @param crudMethodMetadata the injected repository metadata.
*/
void setRepositoryMethodMetadata(CrudMethodMetadata crudMethodMetadata) {
this.crudMethodMetadata = crudMethodMetadata;
}
}

View File

@@ -90,14 +90,14 @@ public class CouchbaseRepositoryFactory extends RepositoryFactorySupport {
* Returns entity information based on the domain class.
*
* @param domainClass the class for the entity.
* @param <T> the value type
* @param <ID> the id type.
* @param <T> the value type
* @param <ID> the id type.
* @return entity information for that domain class.
*/
@Override
public <T, ID> CouchbaseEntityInformation<T, ID> getEntityInformation(Class<T> domainClass) {
CouchbasePersistentEntity<T> entity = (CouchbasePersistentEntity<T>) mappingContext.getRequiredPersistentEntity(
domainClass);
CouchbasePersistentEntity<T> entity = (CouchbasePersistentEntity<T>) mappingContext
.getRequiredPersistentEntity(domainClass);
return new MappingCouchbaseEntityInformation<>(entity);
}
@@ -116,7 +116,7 @@ public class CouchbaseRepositoryFactory extends RepositoryFactorySupport {
metadata.getDomainType());
CouchbaseEntityInformation<?, Serializable> entityInformation = getEntityInformation(metadata.getDomainType());
SimpleCouchbaseRepository repository = getTargetRepositoryViaReflection(metadata, entityInformation,
couchbaseOperations);
couchbaseOperations, metadata.getRepositoryInterface());
repository.setRepositoryMethodMetadata(crudMethodMetadataPostProcessor.getCrudMethodMetadata());
return repository;
}
@@ -153,8 +153,8 @@ public class CouchbaseRepositoryFactory extends RepositoryFactorySupport {
@Override
public RepositoryQuery resolveQuery(final Method method, final RepositoryMetadata metadata,
final ProjectionFactory factory, final NamedQueries namedQueries) {
final CouchbaseOperations couchbaseOperations = couchbaseOperationsMapping.resolve(
metadata.getRepositoryInterface(), metadata.getDomainType());
final CouchbaseOperations couchbaseOperations = couchbaseOperationsMapping
.resolve(metadata.getRepositoryInterface(), metadata.getDomainType());
CouchbaseQueryMethod queryMethod = new CouchbaseQueryMethod(method, metadata, factory, mappingContext);

View File

@@ -31,4 +31,8 @@ public interface CrudMethodMetadata {
*/
ScanConsistency getScanConsistency();
String getScope();
String getCollection();
}

View File

@@ -15,7 +15,7 @@
*/
package org.springframework.data.couchbase.repository.support;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Set;
@@ -28,7 +28,10 @@ import org.springframework.aop.TargetSource;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.core.NamedThreadLocal;
import org.springframework.data.couchbase.core.query.OptionsBuilder;
import org.springframework.data.couchbase.repository.Collection;
import org.springframework.data.couchbase.repository.ScanConsistency;
import org.springframework.data.couchbase.repository.Scope;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.core.support.RepositoryProxyPostProcessor;
import org.springframework.lang.Nullable;
@@ -37,6 +40,9 @@ import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
import com.couchbase.client.core.io.CollectionIdentifier;
import com.couchbase.client.java.query.QueryScanConsistency;
/**
* {@link RepositoryProxyPostProcessor} that sets up interceptors to read metadata information from the invoked method.
* This is necessary to allow redeclaration of CRUD methods in repository interfaces and configure locking information
@@ -96,8 +102,10 @@ class CrudMethodMetadataPostProcessor implements RepositoryProxyPostProcessor, B
private final ConcurrentMap<Method, CrudMethodMetadata> metadataCache = new ConcurrentHashMap<>();
private final Set<Method> implementations = new HashSet<>();
private final RepositoryInformation repositoryInformation;
CrudMethodMetadataPopulatingMethodInterceptor(RepositoryInformation repositoryInformation) {
this.repositoryInformation = repositoryInformation;
ReflectionUtils.doWithMethods(repositoryInformation.getRepositoryInterface(), implementations::add,
method -> !repositoryInformation.isQueryMethod(method));
}
@@ -148,7 +156,7 @@ class CrudMethodMetadataPostProcessor implements RepositoryProxyPostProcessor, B
if (methodMetadata == null) {
methodMetadata = new DefaultCrudMethodMetadata(method);
methodMetadata = new DefaultCrudMethodMetadata(method, repositoryInformation);
CrudMethodMetadata tmp = metadataCache.putIfAbsent(method, methodMetadata);
if (tmp != null) {
@@ -161,10 +169,10 @@ class CrudMethodMetadataPostProcessor implements RepositoryProxyPostProcessor, B
try {
return invocation.proceed();
} finally {
TransactionSynchronizationManager.unbindResource(method);
// TransactionSynchronizationManager.unbindResource(method);
}
} finally {
currentInvocation.set(oldInvocation);
// currentInvocation.set(oldInvocation);
}
}
}
@@ -179,23 +187,38 @@ class CrudMethodMetadataPostProcessor implements RepositoryProxyPostProcessor, B
private final Method method;
private final ScanConsistency scanConsistency;
private final RepositoryInformation repositoryInformation;
private final String scope;
private final String collection;
/**
* Creates a new {@link DefaultCrudMethodMetadata} for the given {@link Method}.
*
* Creates a new {@link DefaultCrudMethodMetadata} for the given {@link Method}. This collects data from implemented
* methods (save(), findById() etc) that would be collected in query.setMeta() for unimplemented methods. There may
* be annotations if the methods were overriden in the repository.
*
* @param method must not be {@literal null}.
*/
DefaultCrudMethodMetadata(Method method) {
DefaultCrudMethodMetadata(Method method, RepositoryInformation repositoryInformation) {
Assert.notNull(method, "Method must not be null!");
this.method = method;
ScanConsistency scanConsistency = null;
for (Annotation ann : method.getAnnotations()) {
if (ann instanceof ScanConsistency) {
scanConsistency = ((ScanConsistency) ann);
}
this.repositoryInformation = repositoryInformation;
String n = method.getName();
// internal methods
if (n.equals("getEntityInformation") || n.equals("getOperations") || n.equals("withOptions")
|| n.equals("withOptions") || n.equals("withScope")) {
this.scanConsistency = null;
this.scope = null;
this.collection = null;
return;
}
this.scanConsistency = scanConsistency;
AnnotatedElement[] annotated = new AnnotatedElement[] { method, method.getDeclaringClass(),
repositoryInformation.getRepositoryInterface(), repositoryInformation.getDomainType() };
this.scanConsistency = OptionsBuilder.annotation(ScanConsistency.class, "query", QueryScanConsistency.NOT_BOUNDED,
annotated);
this.scope = OptionsBuilder.annotationString(Scope.class, CollectionIdentifier.DEFAULT_SCOPE, annotated);
this.collection = OptionsBuilder.annotationString(Collection.class, CollectionIdentifier.DEFAULT_COLLECTION,
annotated);
}
/*
@@ -211,6 +234,17 @@ class CrudMethodMetadataPostProcessor implements RepositoryProxyPostProcessor, B
public ScanConsistency getScanConsistency() {
return scanConsistency;
}
@Override
public String getScope() {
return scope;
}
@Override
public String getCollection() {
return collection;
}
}
private static class ThreadBoundTargetSource implements TargetSource {

View File

@@ -0,0 +1,123 @@
/*
* Copyright 2021 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.repository.support;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import org.springframework.data.couchbase.core.CouchbaseTemplate;
import org.springframework.data.couchbase.core.ReactiveCouchbaseTemplate;
import org.springframework.data.couchbase.core.support.PseudoArgs;
import org.springframework.data.couchbase.repository.CouchbaseRepository;
import org.springframework.data.couchbase.repository.ReactiveCouchbaseRepository;
import org.springframework.data.couchbase.repository.query.CouchbaseEntityInformation;
import com.couchbase.client.java.CommonOptions;
/**
* Invocation Handler for scope/collection/options proxy for repositories
*
* @param <T>
* @author Michael Reiche
*/
public class DynamicInvocationHandler<T> implements InvocationHandler {
final T target;
final Class<?> repositoryClass;
// needed only to detect parameters of this type to look for methods with parameter of java.lang.Object
final CouchbaseEntityInformation<?, String> entityInformation;
final ReactiveCouchbaseTemplate reactiveTemplate;
CommonOptions<?> options;
String collection;
String scope;;
public DynamicInvocationHandler(T target, CommonOptions<?> options, String collection, String scope) {
this.target = target;
if (target instanceof CouchbaseRepository) {
reactiveTemplate = ((CouchbaseTemplate) ((CouchbaseRepository) target).getOperations()).reactive();
this.entityInformation = ((CouchbaseRepository<?, String>) target).getEntityInformation();
} else if (target instanceof ReactiveCouchbaseRepository) {
reactiveTemplate = (ReactiveCouchbaseTemplate) ((ReactiveCouchbaseRepository) target).getOperations();
this.entityInformation = ((ReactiveCouchbaseRepository<?, String>) target).getEntityInformation();
} else {
throw new RuntimeException("Unknown target type: " + target.getClass());
}
this.options = options;
this.collection = collection;
this.scope = scope;
this.repositoryClass = target.getClass();
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("toString".equals(method.getName())) {
return "proxy -> target:" + target;
}
/* Cannot fall-through to use these methods on target, as they will not retain
* the scope, collection and options that may already be set on the proxy
*/
if (method.getName().equals("withOptions")) {
return Proxy.newProxyInstance(repositoryClass.getClassLoader(), target.getClass().getInterfaces(),
new DynamicInvocationHandler<>(target, (CommonOptions) args[0], collection, scope));
}
if (method.getName().equals("withScope")) {
return Proxy.newProxyInstance(repositoryClass.getClassLoader(), target.getClass().getInterfaces(),
new DynamicInvocationHandler<>(target, options, collection, (String) args[0]));
}
if (method.getName().equals("withCollection")) {
return Proxy.newProxyInstance(repositoryClass.getClassLoader(), target.getClass().getInterfaces(),
new DynamicInvocationHandler<>(target, options, (String) args[0], scope));
}
Class<?>[] paramTypes = null;
if (args != null) {
// the CouchbaseRepository methods - save(entity) etc - will have a parameter type of Object instead of entityType
// so change the paramType to match
paramTypes = Arrays.stream(args)
.map(o -> o == null ? null : (o.getClass() == entityInformation.getJavaType() ? Object.class : o.getClass()))
.toArray(Class<?>[]::new);
// the CouchbaseRepository methods - findById(id) etc - will have a parameter type of Object instead of ID
if (method.getName().endsWith("ById") && args.length == 1) {
paramTypes[0] = Object.class;
}
}
Method theMethod = repositoryClass.getMethod(method.getName(), paramTypes);
Object result;
try {
setThreadLocal();
result = theMethod.invoke(target, args);
} catch (InvocationTargetException ite) {
throw ite.getCause();
}
return result;
}
private void setThreadLocal() {
if (reactiveTemplate.getPseudoArgs() != null) {
throw new RuntimeException("pseudoArgs not yet consumed by previous caller");
}
reactiveTemplate.setPseudoArgs(new PseudoArgs(this.scope, this.collection, this.options));
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2017-2020 the original author or authors.
* Copyright 2017-2021 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.
@@ -66,7 +66,7 @@ public class ReactiveCouchbaseRepositoryFactory extends ReactiveRepositoryFactor
* @param couchbaseOperationsMapping the template for the underlying actions.
*/
public ReactiveCouchbaseRepositoryFactory(final ReactiveRepositoryOperationsMapping couchbaseOperationsMapping) {
Assert.notNull(couchbaseOperationsMapping);
Assert.notNull(couchbaseOperationsMapping, "couchbaseOperationsMapping");
this.couchbaseOperationsMapping = couchbaseOperationsMapping;
this.crudMethodMetadataPostProcessor = new CrudMethodMetadataPostProcessor();
@@ -85,14 +85,14 @@ public class ReactiveCouchbaseRepositoryFactory extends ReactiveRepositoryFactor
* Returns entity information based on the domain class.
*
* @param domainClass the class for the entity.
* @param <T> the value type
* @param <ID> the id type.
* @param <T> the value type
* @param <ID> the id type.
* @return entity information for that domain class.
*/
@Override
public <T, ID> CouchbaseEntityInformation<T, ID> getEntityInformation(Class<T> domainClass) {
CouchbasePersistentEntity<T> entity = (CouchbasePersistentEntity<T>) mappingContext.getRequiredPersistentEntity(
domainClass);
CouchbasePersistentEntity<T> entity = (CouchbasePersistentEntity<T>) mappingContext
.getRequiredPersistentEntity(domainClass);
return new MappingCouchbaseEntityInformation<>(entity);
}
@@ -107,11 +107,11 @@ public class ReactiveCouchbaseRepositoryFactory extends ReactiveRepositoryFactor
*/
@Override
protected final Object getTargetRepository(final RepositoryInformation metadata) {
ReactiveCouchbaseOperations couchbaseOperations = couchbaseOperationsMapping.resolve(
metadata.getRepositoryInterface(), metadata.getDomainType());
ReactiveCouchbaseOperations couchbaseOperations = couchbaseOperationsMapping
.resolve(metadata.getRepositoryInterface(), metadata.getDomainType());
CouchbaseEntityInformation<?, Serializable> entityInformation = getEntityInformation(metadata.getDomainType());
SimpleReactiveCouchbaseRepository repository = getTargetRepositoryViaReflection(metadata, entityInformation,
couchbaseOperations);
couchbaseOperations, metadata.getRepositoryInterface());
repository.setRepositoryMethodMetadata(crudMethodMetadataPostProcessor.getCrudMethodMetadata());
return repository;
}
@@ -150,8 +150,8 @@ public class ReactiveCouchbaseRepositoryFactory extends ReactiveRepositoryFactor
@Override
public RepositoryQuery resolveQuery(Method method, RepositoryMetadata metadata, ProjectionFactory factory,
NamedQueries namedQueries) {
final ReactiveCouchbaseOperations couchbaseOperations = couchbaseOperationsMapping.resolve(
metadata.getRepositoryInterface(), metadata.getDomainType());
final ReactiveCouchbaseOperations couchbaseOperations = couchbaseOperationsMapping
.resolve(metadata.getRepositoryInterface(), metadata.getDomainType());
ReactiveCouchbaseQueryMethod queryMethod = new ReactiveCouchbaseQueryMethod(method, metadata, factory,
mappingContext);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2020 the original author or authors.
* Copyright 2013-2021 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.
@@ -44,34 +44,27 @@ import com.couchbase.client.java.query.QueryScanConsistency;
* @author Michael Nitschinger
* @author Mark Paluch
* @author Jens Schauder
* @author Michael Reiche
*/
public class SimpleCouchbaseRepository<T, ID> implements CouchbaseRepository<T, ID> {
public class SimpleCouchbaseRepository<T, ID> extends CouchbaseRepositoryBase<T, ID>
implements CouchbaseRepository<T, ID> {
/**
* Holds the reference to the {@link org.springframework.data.couchbase.core.CouchbaseTemplate}.
*/
private final CouchbaseOperations couchbaseOperations;
/**
* Contains information about the entity being used in this repository.
*/
private final CouchbaseEntityInformation<T, String> entityInformation;
private CrudMethodMetadata crudMethodMetadata;
private final CouchbaseOperations operations;
/**
* Create a new Repository.
*
* @param entityInformation the Metadata for the entity.
* @param couchbaseOperations the reference to the template used.
* @param repositoryInterface the repository interface being fronted
*/
public SimpleCouchbaseRepository(CouchbaseEntityInformation<T, String> entityInformation,
CouchbaseOperations couchbaseOperations) {
Assert.notNull(entityInformation, "CouchbaseEntityInformation must not be null!");
Assert.notNull(couchbaseOperations, "CouchbaseOperations must not be null!");
this.entityInformation = entityInformation;
this.couchbaseOperations = couchbaseOperations;
CouchbaseOperations couchbaseOperations, Class<?> repositoryInterface) {
super(entityInformation, repositoryInterface);
this.operations = couchbaseOperations;
}
@Override
@@ -79,11 +72,13 @@ public class SimpleCouchbaseRepository<T, ID> implements CouchbaseRepository<T,
public <S extends T> S save(S entity) {
Assert.notNull(entity, "Entity must not be null!");
// if entity has non-null, non-zero version property, then replace()
if (hasNonZeroVersionProperty(entity, couchbaseOperations.getConverter())) {
return (S) couchbaseOperations.replaceById(entityInformation.getJavaType()).one(entity);
S result;
if (hasNonZeroVersionProperty(entity, operations.getConverter())) {
result = (S) operations.replaceById(getJavaType()).inScope(getScope()).inCollection(getCollection()).one(entity);
} else {
return (S) couchbaseOperations.upsertById(entityInformation.getJavaType()).one(entity);
result = (S) operations.upsertById(getJavaType()).inScope(getScope()).inCollection(getCollection()).one(entity);
}
return result;
}
@Override
@@ -95,57 +90,61 @@ public class SimpleCouchbaseRepository<T, ID> implements CouchbaseRepository<T,
@Override
public Optional<T> findById(ID id) {
Assert.notNull(id, "The given id must not be null!");
return Optional.ofNullable(couchbaseOperations.findById(entityInformation.getJavaType()).one(id.toString()));
return Optional.ofNullable(
operations.findById(getJavaType()).inScope(getScope()).inCollection(getCollection()).one(id.toString()));
}
@Override
public List<T> findAllById(Iterable<ID> ids) {
Assert.notNull(ids, "The given Iterable of ids must not be null!");
List<String> convertedIds = Streamable.of(ids).stream().map(Objects::toString).collect(Collectors.toList());
Collection<? extends T> all = couchbaseOperations.findById(entityInformation.getJavaType()).all(convertedIds);
Collection<? extends T> all = operations.findById(getJavaType()).inScope(getScope()).inCollection(getCollection())
.all(convertedIds);
return Streamable.of(all).stream().collect(StreamUtils.toUnmodifiableList());
}
@Override
public boolean existsById(ID id) {
Assert.notNull(id, "The given id must not be null!");
return couchbaseOperations.existsById().one(id.toString());
return operations.existsById(getJavaType()).inScope(getScope()).inCollection(getCollection()).one(id.toString());
}
@Override
public void deleteById(ID id) {
Assert.notNull(id, "The given id must not be null!");
couchbaseOperations.removeById().one(id.toString());
operations.removeById(getJavaType()).inScope(getScope()).inCollection(getCollection()).one(id.toString());
}
@Override
public void delete(T entity) {
Assert.notNull(entity, "Entity must not be null!");
couchbaseOperations.removeById().one(entityInformation.getId(entity));
operations.removeById(getJavaType()).inScope(getScope()).inCollection(getCollection()).one(getId(entity));
}
@Override
public void deleteAllById(Iterable<? extends ID> ids) {
Assert.notNull(ids, "The given Iterable of ids must not be null!");
couchbaseOperations.removeById().all(Streamable.of(ids).map(Objects::toString).toList());
operations.removeById(getJavaType()).inScope(getScope()).inCollection(getCollection())
.all(Streamable.of(ids).map(Objects::toString).toList());
}
@Override
public void deleteAll(Iterable<? extends T> entities) {
Assert.notNull(entities, "The given Iterable of entities must not be null!");
couchbaseOperations.removeById().all(Streamable.of(entities).map(entityInformation::getId).toList());
operations.removeById(getJavaType()).inScope(getScope()).inCollection(getCollection())
.all(Streamable.of(entities).map(this::getId).toList());
}
@Override
public long count() {
return couchbaseOperations.findByQuery(entityInformation.getJavaType()).withConsistency(buildQueryScanConsistency())
.count();
return operations.findByQuery(getJavaType()).withConsistency(buildQueryScanConsistency()).inScope(getScope())
.inCollection(getCollection()).count();
}
@Override
public void deleteAll() {
couchbaseOperations.removeByQuery(entityInformation.getJavaType()).withConsistency(buildQueryScanConsistency())
.all();
operations.removeByQuery(getJavaType()).withConsistency(buildQueryScanConsistency()).inScope(getScope())
.inCollection(getCollection()).all();
}
@Override
@@ -169,15 +168,6 @@ public class SimpleCouchbaseRepository<T, ID> implements CouchbaseRepository<T,
return new PageImpl<>(results, pageable, count());
}
/**
* Returns the information for the underlying template.
*
* @return the underlying entity information.
*/
protected CouchbaseEntityInformation<T, String> getEntityInformation() {
return entityInformation;
}
/**
* Helper method to assemble a n1ql find all query, taking annotations into acocunt.
*
@@ -185,25 +175,13 @@ public class SimpleCouchbaseRepository<T, ID> implements CouchbaseRepository<T,
* @return the list of found entities, already executed.
*/
private List<T> findAll(Query query) {
return couchbaseOperations.findByQuery(entityInformation.getJavaType()).withConsistency(buildQueryScanConsistency())
.matching(query).all();
return operations.findByQuery(getJavaType()).withConsistency(buildQueryScanConsistency()).inScope(getScope())
.inCollection(getCollection()).matching(query).all();
}
private QueryScanConsistency buildQueryScanConsistency() {
QueryScanConsistency scanConsistency = QueryScanConsistency.NOT_BOUNDED;
if (crudMethodMetadata.getScanConsistency() != null) {
scanConsistency = crudMethodMetadata.getScanConsistency().query();
}
return scanConsistency;
}
/**
* Setter for the repository metadata, contains annotations on the overidden methods.
*
* @param crudMethodMetadata the injected repository metadata.
*/
void setRepositoryMethodMetadata(CrudMethodMetadata crudMethodMetadata) {
this.crudMethodMetadata = crudMethodMetadata;
@Override
public CouchbaseOperations getOperations() {
return operations;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2017-2020 the original author or authors.
* Copyright 2017-2021 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.
@@ -35,8 +35,6 @@ import org.springframework.data.domain.Sort;
import org.springframework.data.util.Streamable;
import org.springframework.util.Assert;
import com.couchbase.client.java.query.QueryScanConsistency;
/**
* Reactive repository base implementation for Couchbase.
*
@@ -46,22 +44,17 @@ import com.couchbase.client.java.query.QueryScanConsistency;
* @author David Kelly
* @author Douglas Six
* @author Jens Schauder
* @author Michael Reiche
* @since 3.0
*/
public class SimpleReactiveCouchbaseRepository<T, ID> implements ReactiveCouchbaseRepository<T, ID> {
public class SimpleReactiveCouchbaseRepository<T, ID> extends CouchbaseRepositoryBase<T, ID>
implements ReactiveCouchbaseRepository<T, ID> {
/**
* Holds the reference to the {@link CouchbaseOperations}.
*/
private final ReactiveCouchbaseOperations operations;
/**
* Contains information about the entity being used in this repository.
*/
private final CouchbaseEntityInformation<T, String> entityInformation;
private CrudMethodMetadata crudMethodMetadata;
/**
* Create a new Repository.
*
@@ -69,11 +62,8 @@ public class SimpleReactiveCouchbaseRepository<T, ID> implements ReactiveCouchba
* @param operations the reference to the reactive template used.
*/
public SimpleReactiveCouchbaseRepository(CouchbaseEntityInformation<T, String> entityInformation,
ReactiveCouchbaseOperations operations) {
Assert.notNull(operations, "ReactiveCouchbaseOperations must not be null!");
Assert.notNull(entityInformation, "CouchbaseEntityInformation must not be null!");
this.entityInformation = entityInformation;
ReactiveCouchbaseOperations operations, Class<?> repositoryInterface) {
super(entityInformation, repositoryInterface);
this.operations = operations;
}
@@ -81,12 +71,16 @@ public class SimpleReactiveCouchbaseRepository<T, ID> implements ReactiveCouchba
@Override
public <S extends T> Mono<S> save(S entity) {
Assert.notNull(entity, "Entity must not be null!");
// if entity has non-null version property, then replace()
// if entity has non-null, non-zero version property, then replace()
Mono<S> result;
if (hasNonZeroVersionProperty(entity, operations.getConverter())) {
return (Mono<S>) operations.replaceById(entityInformation.getJavaType()).one(entity);
result = (Mono<S>) operations.replaceById(getJavaType()).inScope(getScope()).inCollection(getCollection())
.one(entity);
} else {
return (Mono<S>) operations.upsertById(entityInformation.getJavaType()).one(entity);
result = (Mono<S>) operations.upsertById(getJavaType()).inScope(getScope()).inCollection(getCollection())
.one(entity);
}
return result;
}
@Override
@@ -97,7 +91,7 @@ public class SimpleReactiveCouchbaseRepository<T, ID> implements ReactiveCouchba
@Override
public <S extends T> Flux<S> saveAll(Iterable<S> entities) {
Assert.notNull(entities, "The given Iterable of entities must not be null!");
return Flux.fromIterable(entities).flatMap(this::save);
return Flux.fromIterable(entities).flatMap(e -> save(e));
}
@Override
@@ -108,7 +102,7 @@ public class SimpleReactiveCouchbaseRepository<T, ID> implements ReactiveCouchba
@Override
public Mono<T> findById(ID id) {
return operations.findById(entityInformation.getJavaType()).one(id.toString());
return operations.findById(getJavaType()).inScope(getScope()).inCollection(getCollection()).one(id.toString());
}
@Override
@@ -120,7 +114,7 @@ public class SimpleReactiveCouchbaseRepository<T, ID> implements ReactiveCouchba
@Override
public Mono<Boolean> existsById(ID id) {
Assert.notNull(id, "The given id must not be null!");
return operations.existsById().one(id.toString());
return operations.existsById(getJavaType()).inScope(getScope()).inCollection(getCollection()).one(id.toString());
}
@Override
@@ -139,7 +133,8 @@ public class SimpleReactiveCouchbaseRepository<T, ID> implements ReactiveCouchba
public Flux<T> findAllById(Iterable<ID> ids) {
Assert.notNull(ids, "The given Iterable of ids must not be null!");
List<String> convertedIds = Streamable.of(ids).stream().map(Objects::toString).collect(Collectors.toList());
return (Flux<T>) operations.findById(entityInformation.getJavaType()).all(convertedIds);
return (Flux<T>) operations.findById(getJavaType()).inScope(getScope()).inCollection(getCollection())
.all(convertedIds);
}
@Override
@@ -150,7 +145,8 @@ public class SimpleReactiveCouchbaseRepository<T, ID> implements ReactiveCouchba
@Override
public Mono<Void> deleteById(ID id) {
return operations.removeById().one(id.toString()).then();
return operations.removeById(getJavaType()).inScope(getScope()).inCollection(getCollection()).one(id.toString())
.then();
}
@Override
@@ -162,17 +158,20 @@ public class SimpleReactiveCouchbaseRepository<T, ID> implements ReactiveCouchba
@Override
public Mono<Void> delete(T entity) {
Assert.notNull(entity, "Entity must not be null!");
return operations.removeById().one(entityInformation.getId(entity)).then();
return operations.removeById(getJavaType()).inScope(getScope()).inCollection(getCollection()).one(getId(entity))
.then();
}
@Override
public Mono<Void> deleteAllById(Iterable<? extends ID> ids) {
return operations.removeById().all(Streamable.of(ids).map(Object::toString).toList()).then();
return operations.removeById(getJavaType()).inScope(getScope()).inCollection(getCollection())
.all(Streamable.of(ids).map(Object::toString).toList()).then();
}
@Override
public Mono<Void> deleteAll(Iterable<? extends T> entities) {
return operations.removeById().all(Streamable.of(entities).map(entityInformation::getId).toList()).then();
return operations.removeById(getJavaType()).inScope(getScope()).inCollection(getCollection())
.all(Streamable.of(entities).map(this::getId).toList()).then();
}
@Override
@@ -183,43 +182,24 @@ public class SimpleReactiveCouchbaseRepository<T, ID> implements ReactiveCouchba
@Override
public Mono<Long> count() {
return operations.findByQuery(entityInformation.getJavaType()).withConsistency(buildQueryScanConsistency()).count();
return operations.findByQuery(getJavaType()).withConsistency(buildQueryScanConsistency()).inScope(getScope())
.inCollection(getCollection()).count();
}
@Override
public Mono<Void> deleteAll() {
return operations.removeByQuery(entityInformation.getJavaType()).withConsistency(buildQueryScanConsistency()).all().then();
}
/**
* Returns the information for the underlying template.
*
* @return the underlying entity information.
*/
protected CouchbaseEntityInformation<T, String> getEntityInformation() {
return entityInformation;
return operations.removeByQuery(getJavaType()).withConsistency(buildQueryScanConsistency()).inScope(getScope())
.inCollection(getCollection()).all().then();
}
private Flux<T> findAll(Query query) {
return operations.findByQuery(entityInformation.getJavaType()).withConsistency(buildQueryScanConsistency())
.matching(query).all();
return operations.findByQuery(getJavaType()).withConsistency(buildQueryScanConsistency()).inScope(getScope())
.inCollection(getCollection()).matching(query).all();
}
private QueryScanConsistency buildQueryScanConsistency() {
QueryScanConsistency scanConsistency = QueryScanConsistency.NOT_BOUNDED;
if (crudMethodMetadata.getScanConsistency() != null) {
scanConsistency = crudMethodMetadata.getScanConsistency().query();
}
return scanConsistency;
}
/**
* Setter for the repository metadata, contains annotations on the overidden methods.
*
* @param crudMethodMetadata the injected repository metadata.
*/
void setRepositoryMethodMetadata(CrudMethodMetadata crudMethodMetadata) {
this.crudMethodMetadata = crudMethodMetadata;
@Override
public ReactiveCouchbaseOperations getOperations() {
return operations;
}
}

View File

@@ -31,7 +31,6 @@ import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import com.couchbase.client.java.query.QueryScanConsistency;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.dao.DataIntegrityViolationException;
@@ -50,6 +49,7 @@ import org.springframework.data.couchbase.util.JavaIntegrationTests;
import com.couchbase.client.java.kv.PersistTo;
import com.couchbase.client.java.kv.ReplicateTo;
import com.couchbase.client.java.query.QueryScanConsistency;
;
@@ -121,7 +121,7 @@ class CouchbaseTemplateKeyValueIntegrationTests extends JavaIntegrationTests {
returned = (User) operator.one(user);
break;
} catch (Exception ofe) {
System.out.println(""+i+" caught: "+ofe);
System.out.println("" + i + " caught: " + ofe);
couchbaseTemplate.removeByQuery(User.class).withConsistency(QueryScanConsistency.REQUEST_PLUS).all();
if (i == 4) {
throw ofe;
@@ -259,7 +259,7 @@ class CouchbaseTemplateKeyValueIntegrationTests extends JavaIntegrationTests {
.one(user);
break;
} catch (Exception ofe) {
System.out.println(""+i+" caught: "+ofe);
System.out.println("" + i + " caught: " + ofe);
couchbaseTemplate.removeByQuery(User.class).withConsistency(QueryScanConsistency.REQUEST_PLUS).all();
if (i == 4) {
throw ofe;

View File

@@ -36,6 +36,7 @@ import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.dao.DataRetrievalFailureException;
import org.springframework.data.couchbase.core.query.Query;
import org.springframework.data.couchbase.core.query.QueryCriteria;
import org.springframework.data.couchbase.domain.Address;
@@ -44,18 +45,19 @@ import org.springframework.data.couchbase.domain.Course;
import org.springframework.data.couchbase.domain.NaiveAuditorAware;
import org.springframework.data.couchbase.domain.Submission;
import org.springframework.data.couchbase.domain.User;
import org.springframework.data.couchbase.domain.UserCol;
import org.springframework.data.couchbase.domain.UserJustLastName;
import org.springframework.data.couchbase.domain.UserSubmission;
import org.springframework.data.couchbase.domain.UserSubmissionProjected;
import org.springframework.data.couchbase.domain.time.AuditingDateTimeProvider;
import org.springframework.data.couchbase.util.Capabilities;
import org.springframework.data.couchbase.util.ClusterAwareIntegrationTests;
import org.springframework.data.couchbase.util.ClusterType;
import org.springframework.data.couchbase.util.CollectionAwareIntegrationTests;
import org.springframework.data.couchbase.util.IgnoreWhen;
import com.couchbase.client.core.error.AmbiguousTimeoutException;
import com.couchbase.client.core.error.UnambiguousTimeoutException;
import com.couchbase.client.core.io.CollectionIdentifier;
import com.couchbase.client.java.analytics.AnalyticsOptions;
import com.couchbase.client.java.kv.ExistsOptions;
import com.couchbase.client.java.kv.GetAnyReplicaOptions;
@@ -752,4 +754,51 @@ class CouchbaseTemplateQueryCollectionIntegrationTests extends CollectionAwareIn
.inCollection(otherCollection).withOptions(options).one(vie));
}
@Test
public void testScopeCollectionAnnotation() {
UserCol user = new UserCol("1", "Dave", "Wilson");
Query query = Query.query(QueryCriteria.where("firstname").is(user.getFirstname()));
try {
UserCol saved = couchbaseTemplate.insertById(UserCol.class).inScope(scopeName).inCollection(collectionName)
.one(user);
List<UserCol> found = couchbaseTemplate.findByQuery(UserCol.class)
.withConsistency(QueryScanConsistency.REQUEST_PLUS).inScope(scopeName).inCollection(collectionName)
.matching(query).all();
assertEquals(saved, found.get(0), "should have found what was saved");
List<UserCol> notfound = couchbaseTemplate.findByQuery(UserCol.class).inScope(CollectionIdentifier.DEFAULT_SCOPE)
.inCollection(CollectionIdentifier.DEFAULT_COLLECTION).matching(query).all();
assertEquals(0, notfound.size(), "should not have found what was saved");
couchbaseTemplate.removeByQuery(UserCol.class).inScope(scopeName).inCollection(collectionName).matching(query)
.all();
} finally {
try {
couchbaseTemplate.removeByQuery(UserCol.class).inScope(scopeName).inCollection(collectionName).matching(query)
.all();
} catch (DataRetrievalFailureException drfe) {}
}
}
@Test
public void testScopeCollectionRepoWith() {
UserCol user = new UserCol("1", "Dave", "Wilson");
Query query = Query.query(QueryCriteria.where("firstname").is(user.getFirstname()));
try {
UserCol saved = couchbaseTemplate.insertById(UserCol.class).inScope(scopeName).inCollection(collectionName)
.one(user);
List<UserCol> found = couchbaseTemplate.findByQuery(UserCol.class)
.withConsistency(QueryScanConsistency.REQUEST_PLUS).inScope(scopeName).inCollection(collectionName)
.matching(query).all();
assertEquals(saved, found.get(0), "should have found what was saved");
List<UserCol> notfound = couchbaseTemplate.findByQuery(UserCol.class).inScope(CollectionIdentifier.DEFAULT_SCOPE)
.inCollection(CollectionIdentifier.DEFAULT_COLLECTION).matching(query).all();
assertEquals(0, notfound.size(), "should not have found what was saved");
couchbaseTemplate.removeByQuery(UserCol.class).inScope(scopeName).inCollection(collectionName).matching(query)
.all();
} finally {
try {
couchbaseTemplate.removeByQuery(UserCol.class).inScope(scopeName).inCollection(collectionName).matching(query)
.all();
} catch (DataRetrievalFailureException drfe) {}
}
}
}

View File

@@ -16,7 +16,8 @@
package org.springframework.data.couchbase.core;
import static org.junit.jupiter.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import java.time.Duration;
import java.util.UUID;

View File

@@ -150,7 +150,8 @@ class ReactiveCouchbaseTemplateKeyValueIntegrationTests extends JavaIntegrationT
}
// if replace or remove, we need to insert a document to replace
if (operator instanceof ReactiveReplaceByIdOperation.ReactiveReplaceById || operator instanceof ExecutableRemoveById) {
if (operator instanceof ReactiveReplaceByIdOperation.ReactiveReplaceById
|| operator instanceof ExecutableRemoveById) {
reactiveCouchbaseTemplate.insertById(User.class).one(user).block();
}
// call to insert/replace/update

View File

@@ -19,8 +19,8 @@ package org.springframework.data.couchbase.domain;
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.PersistenceConstructor;
import org.springframework.data.annotation.Version;
import org.springframework.data.annotation.TypeAlias;
import org.springframework.data.annotation.Version;
import org.springframework.data.couchbase.core.mapping.Document;
/**
@@ -42,7 +42,6 @@ public class Airport extends ComparableEntity {
@CreatedBy private String createdBy;
@PersistenceConstructor
public Airport(String id, String iata, String icao) {
this.id = id;
@@ -78,6 +77,7 @@ public class Airport extends ComparableEntity {
version = Long.valueOf(0);
return this;
}
public String getCreatedBy() {
return createdBy;
}

View File

@@ -16,18 +16,33 @@
package org.springframework.data.couchbase.domain;
import static com.couchbase.client.core.io.CollectionIdentifier.DEFAULT_COLLECTION;
import static com.couchbase.client.core.io.CollectionIdentifier.DEFAULT_SCOPE;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import org.springframework.core.annotation.AliasFor;
import org.springframework.data.couchbase.core.RemoveResult;
import org.springframework.data.couchbase.core.mapping.Expiry;
import org.springframework.data.couchbase.repository.Collection;
import org.springframework.data.couchbase.repository.CouchbaseRepository;
import org.springframework.data.couchbase.repository.DynamicProxyable;
import org.springframework.data.couchbase.repository.Options;
import org.springframework.data.couchbase.repository.Query;
import org.springframework.data.couchbase.repository.ScanConsistency;
import org.springframework.data.couchbase.repository.Scope;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import com.couchbase.client.java.analytics.AnalyticsScanConsistency;
import com.couchbase.client.java.query.QueryScanConsistency;
/**
@@ -41,8 +56,11 @@ import com.couchbase.client.java.query.QueryScanConsistency;
* @author Michael Reiche
*/
@Repository
public interface AirportRepository extends CouchbaseRepository<Airport, String> {
// @Scope("repositoryScope")
// @ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
public interface AirportRepository extends CouchbaseRepository<Airport, String>, DynamicProxyable<AirportRepository> {
// override an annotate with REQUEST_PLUS
@Override
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
List<Airport> findAll();
@@ -51,11 +69,16 @@ public interface AirportRepository extends CouchbaseRepository<Airport, String>
List<Airport> findAllByIata(String iata);
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
@ComposedMetaAnnotation(collection = "_default", timeoutMs = 1000)
Airport findByIata(String iata);
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
Airport findByIata(Iata iata);
// NOT_BOUNDED to test ScanConsistency
// @ScanConsistency(query = QueryScanConsistency.NOT_BOUNDED)
Airport iata(String iata);
@Query("#{#n1ql.selectEntity} where iata = $1")
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
List<Airport> getAllByIata(String iata);
@@ -97,4 +120,42 @@ public interface AirportRepository extends CouchbaseRepository<Airport, String>
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
Optional<Airport> findByIdAndIata(String id, String iata);
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.TYPE })
// @Meta
@Scope
@Collection
@ScanConsistency
@Expiry
@Options
public @interface ComposedMetaAnnotation {
// @AliasFor(annotation = Meta.class, attribute = "maxExecutionTimeMs")
// long execTime() default -1;
@AliasFor(annotation = ScanConsistency.class, attribute = "query")
QueryScanConsistency query() default QueryScanConsistency.NOT_BOUNDED;
@AliasFor(annotation = ScanConsistency.class, attribute = "analytics")
AnalyticsScanConsistency analytics() default AnalyticsScanConsistency.NOT_BOUNDED;
@AliasFor(annotation = Scope.class, attribute = "value")
String scope() default DEFAULT_SCOPE;
@AliasFor(annotation = Collection.class, attribute = "value")
String collection() default DEFAULT_COLLECTION;
@AliasFor(annotation = Expiry.class, attribute = "expiry")
int expiry() default 0;
@AliasFor(annotation = Expiry.class, attribute = "expiryUnit")
TimeUnit expiryUnit() default TimeUnit.SECONDS;
@AliasFor(annotation = Expiry.class, attribute = "expiryExpression")
String expiryExpression() default "";
@AliasFor(annotation = Options.class, attribute = "timeoutMs")
long timeoutMs() default 0;
}
}

View File

@@ -38,6 +38,7 @@ import org.springframework.data.couchbase.domain.time.AuditingDateTimeProvider;
import org.springframework.data.couchbase.repository.auditing.EnableCouchbaseAuditing;
import org.springframework.data.couchbase.repository.auditing.EnableReactiveCouchbaseAuditing;
import org.springframework.data.couchbase.repository.config.EnableCouchbaseRepositories;
import org.springframework.data.couchbase.repository.config.EnableReactiveCouchbaseRepositories;
import org.springframework.data.couchbase.repository.config.ReactiveRepositoryOperationsMapping;
import org.springframework.data.couchbase.repository.config.RepositoryOperationsMapping;
@@ -52,8 +53,10 @@ import com.couchbase.client.java.json.JacksonTransformers;
*/
@Configuration
@EnableCouchbaseRepositories
@EnableCouchbaseAuditing(auditorAwareRef="auditorAwareRef", dateTimeProviderRef="dateTimeProviderRef") // this activates auditing
@EnableReactiveCouchbaseAuditing(auditorAwareRef="reactiveAuditorAwareRef", dateTimeProviderRef="dateTimeProviderRef") // this activates auditing
@EnableReactiveCouchbaseRepositories
@EnableCouchbaseAuditing(auditorAwareRef = "auditorAwareRef", dateTimeProviderRef = "dateTimeProviderRef")
@EnableReactiveCouchbaseAuditing(auditorAwareRef = "reactiveAuditorAwareRef",
dateTimeProviderRef = "dateTimeProviderRef")
public class Config extends AbstractCouchbaseConfiguration {
String bucketname = "travel-sample";

View File

@@ -16,7 +16,6 @@
package org.springframework.data.couchbase.domain;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import org.springframework.data.couchbase.repository.Query;
@@ -92,7 +91,7 @@ public interface PersonRepository extends CrudRepository<Person, String> {
<S extends Person> Iterable<S> saveAll(Iterable<S> var1);
Optional<Person> findById(UUID var1);
Person findById(UUID var1);
boolean existsById(UUID var1);

View File

@@ -16,19 +16,19 @@
package org.springframework.data.couchbase.domain;
import org.springframework.data.couchbase.core.RemoveResult;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.ArrayList;
import org.springframework.data.couchbase.repository.DynamicProxyable;
import org.springframework.data.couchbase.repository.Query;
import org.springframework.data.couchbase.repository.ReactiveCouchbaseRepository;
import org.springframework.data.couchbase.repository.ScanConsistency;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.repository.reactive.ReactiveSortingRepository;
import org.springframework.stereotype.Repository;
import com.couchbase.client.java.json.JsonArray;
@@ -41,7 +41,8 @@ import com.couchbase.client.java.query.QueryScanConsistency;
* @author Michael Reiche
*/
@Repository
public interface ReactiveAirportRepository extends ReactiveSortingRepository<Airport, String> {
public interface ReactiveAirportRepository
extends ReactiveCouchbaseRepository<Airport, String>, DynamicProxyable<ReactiveAirportRepository> {
@Override
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
@@ -57,6 +58,9 @@ public interface ReactiveAirportRepository extends ReactiveSortingRepository<Air
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
Flux<Airport> findAllByIata(String iata);
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
Mono<Airport> iata(String iata);
@Query("#{#n1ql.selectEntity} WHERE #{#n1ql.filter}")
Flux<Airport> findAllPoliciesByApplicableTypes(String state, JsonArray applicableTypes);

View File

@@ -15,9 +15,9 @@
*/
package org.springframework.data.couchbase.domain;
import org.springframework.data.domain.ReactiveAuditorAware;
import reactor.core.publisher.Mono;
import org.springframework.data.domain.ReactiveAuditorAware;
/**
* This class returns a string that represents the current user
@@ -28,6 +28,7 @@ import reactor.core.publisher.Mono;
public class ReactiveNaiveAuditorAware implements ReactiveAuditorAware<String> {
public static final String AUDITOR = "reactive_auditor";
@Override
public Mono<String> getCurrentAuditor() {
return Mono.just(AUDITOR);

View File

@@ -0,0 +1,63 @@
/*
* Copyright 2012-2020 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 reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.data.couchbase.repository.DynamicProxyable;
import org.springframework.data.couchbase.repository.Query;
import org.springframework.data.couchbase.repository.ReactiveCouchbaseRepository;
import org.springframework.data.couchbase.repository.ScanConsistency;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import com.couchbase.client.java.json.JsonArray;
import com.couchbase.client.java.query.QueryScanConsistency;
/**
* User Repository for tests
*
* @author Michael Nitschinger
* @author Michael Reiche
*/
@Repository
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
public interface ReactiveUserColRepository
extends ReactiveCouchbaseRepository<UserCol, String>, DynamicProxyable<ReactiveUserColRepository> {
<S extends UserCol> Mono<S> save(S var1);
Flux<UserCol> findByFirstname(String firstname);
Flux<UserCol> findByFirstnameIn(String... firstnames);
Flux<UserCol> findByFirstnameIn(JsonArray firstnames);
Flux<UserCol> findByFirstnameAndLastname(String firstname, String lastname);
@Query("#{#n1ql.selectEntity} where #{#n1ql.filter} and firstname = $1 and lastname = $2")
Flux<UserCol> getByFirstnameAndLastname(String firstname, String lastname);
@Query("#{#n1ql.selectEntity} where #{#n1ql.filter} and (firstname = $first or lastname = $last)")
Flux<UserCol> getByFirstnameOrLastname(@Param("first") String firstname, @Param("last") String lastname);
Flux<UserCol> findByIdIsNotNullAndFirstnameEquals(String firstname);
Flux<UserCol> findByVersionEqualsAndFirstnameEquals(Long version, String firstname);
}

View File

@@ -14,17 +14,28 @@
* limitations under the License.
*/
package org.springframework.data.couchbase.core.mapping.event;
package org.springframework.data.couchbase.domain;
import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
import org.springframework.data.annotation.PersistenceConstructor;
import org.springframework.data.couchbase.core.mapping.Document;
import org.springframework.data.couchbase.repository.Collection;
import org.springframework.data.couchbase.repository.Scope;
/**
* User entity for tests
*
* @author Michael Nitschinger
* @author Michael Reiche
*/
public class ReactiveAfterSaveEvent<E> extends CouchbaseMappingEvent<E> {
public ReactiveAfterSaveEvent(E source, CouchbaseDocument document) {
super(source, document);
@Document
@Scope("other_scope")
@Collection("other_collection")
public class UserCol extends User {
@PersistenceConstructor
public UserCol(final String id, final String firstname, final String lastname) {
super(id, firstname, lastname);
}
}

View File

@@ -0,0 +1,61 @@
/*
* Copyright 2012-2020 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 java.util.List;
import org.springframework.data.couchbase.repository.CouchbaseRepository;
import org.springframework.data.couchbase.repository.DynamicProxyable;
import org.springframework.data.couchbase.repository.Query;
import org.springframework.data.couchbase.repository.ScanConsistency;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import com.couchbase.client.java.json.JsonArray;
import com.couchbase.client.java.query.QueryScanConsistency;
/**
* User Repository for tests
*
* @author Michael Nitschinger
* @author Michael Reiche
*/
@Repository
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
public interface UserColRepository extends CouchbaseRepository<UserCol, String>, DynamicProxyable<UserColRepository> {
<S extends UserCol> S save(S var1);
List<UserCol> findByFirstname(String firstname);
List<UserCol> findByFirstnameIn(String... firstnames);
List<UserCol> findByFirstnameIn(JsonArray firstnames);
List<UserCol> findByFirstnameAndLastname(String firstname, String lastname);
@Query("#{#n1ql.selectEntity} where #{#n1ql.filter} and firstname = $1 and lastname = $2")
List<UserCol> getByFirstnameAndLastname(String firstname, String lastname);
@Query("#{#n1ql.selectEntity} where #{#n1ql.filter} and (firstname = $first or lastname = $last)")
List<UserCol> getByFirstnameOrLastname(@Param("first") String firstname, @Param("last") String lastname);
List<UserCol> findByIdIsNotNullAndFirstnameEquals(String firstname);
List<UserCol> findByVersionEqualsAndFirstnameEquals(Long version, String firstname);
}

View File

@@ -20,10 +20,12 @@ import java.util.List;
import org.springframework.data.couchbase.repository.CouchbaseRepository;
import org.springframework.data.couchbase.repository.Query;
import org.springframework.data.couchbase.repository.ScanConsistency;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import com.couchbase.client.java.json.JsonArray;
import com.couchbase.client.java.query.QueryScanConsistency;
/**
* User Repository for tests
@@ -32,6 +34,7 @@ import com.couchbase.client.java.json.JsonArray;
* @author Michael Reiche
*/
@Repository
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
public interface UserRepository extends CouchbaseRepository<User, String> {
List<User> findByFirstname(String firstname);
@@ -51,4 +54,5 @@ public interface UserRepository extends CouchbaseRepository<User, String> {
List<User> findByIdIsNotNullAndFirstnameEquals(String firstname);
List<User> findByVersionEqualsAndFirstnameEquals(Long version, String firstname);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2017-2019 the original author or authors.
* Copyright 2017-2021 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.
@@ -21,10 +21,12 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.lang.reflect.Method;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -41,21 +43,22 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.dao.DataRetrievalFailureException;
import org.springframework.data.auditing.DateTimeProvider;
import org.springframework.data.couchbase.CouchbaseClientFactory;
import org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration;
import org.springframework.data.couchbase.core.CouchbaseTemplate;
import org.springframework.data.couchbase.core.RemoveResult;
import org.springframework.data.couchbase.core.query.N1QLExpression;
import org.springframework.data.couchbase.core.query.Query;
import org.springframework.data.couchbase.core.query.QueryCriteria;
import org.springframework.data.couchbase.domain.Address;
import org.springframework.data.couchbase.domain.Airport;
import org.springframework.data.couchbase.domain.AirportRepository;
import org.springframework.data.couchbase.domain.Iata;
import org.springframework.data.couchbase.domain.NaiveAuditorAware;
import org.springframework.data.couchbase.domain.Person;
import org.springframework.data.couchbase.domain.PersonRepository;
import org.springframework.data.couchbase.domain.User;
import org.springframework.data.couchbase.domain.UserAnnotated;
import org.springframework.data.couchbase.domain.UserRepository;
import org.springframework.data.couchbase.domain.time.AuditingDateTimeProvider;
import org.springframework.data.couchbase.repository.auditing.EnableCouchbaseAuditing;
@@ -73,8 +76,14 @@ import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
import org.springframework.data.repository.core.support.DefaultRepositoryMetadata;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import com.couchbase.client.core.error.AmbiguousTimeoutException;
import com.couchbase.client.core.error.CouchbaseException;
import com.couchbase.client.core.error.IndexExistsException;
import com.couchbase.client.core.error.IndexFailureException;
import com.couchbase.client.java.env.ClusterEnvironment;
import com.couchbase.client.java.json.JsonArray;
import com.couchbase.client.java.kv.MutationState;
import com.couchbase.client.java.query.QueryOptions;
import com.couchbase.client.java.query.QueryScanConsistency;
/**
@@ -96,6 +105,9 @@ public class CouchbaseRepositoryQueryIntegrationTests extends ClusterAwareIntegr
@Autowired CouchbaseTemplate couchbaseTemplate;
String scopeName = "_default";
String collectionName = "_default";
@BeforeEach
public void beforeEach() {
try {
@@ -182,12 +194,17 @@ public class CouchbaseRepositoryQueryIntegrationTests extends ClusterAwareIntegr
try {
vie = new Airport("airports::vie", "vie", "low6");
vie = airportRepository.save(vie);
Airport airport2 = airportRepository
.withOptions(QueryOptions.queryOptions().scanConsistency(QueryScanConsistency.REQUEST_PLUS))
.findByIata(vie.getIata());
assertEquals(airport2, vie);
List<Airport> airports = airportRepository.findAllByIata("vie");
assertEquals(1, airports.size());
Airport airport1 = airportRepository.findById(airports.get(0).getId()).get();
assertEquals(airport1.getIata(), vie.getIata());
Airport airport2 = airportRepository.findByIata(airports.get(0).getIata());
assertEquals(airport1.getId(), vie.getId());
airport2 = airportRepository.findByIata(airports.get(0).getIata());
assertEquals(airport2.getId(), vie.getId());
} finally {
airportRepository.delete(vie);
}
@@ -201,7 +218,9 @@ public class CouchbaseRepositoryQueryIntegrationTests extends ClusterAwareIntegr
vie = airportRepository.save(vie);
List<Airport> airports = couchbaseTemplate.findByQuery(Airport.class)
.withConsistency(QueryScanConsistency.REQUEST_PLUS)
.matching(new Query(QueryCriteria.where(N1QLExpression.x("_class")).is("airport"))).all();
.matching(org.springframework.data.couchbase.core.query.Query
.query(QueryCriteria.where(N1QLExpression.x("_class")).is("airport")))
.all();
assertFalse(airports.isEmpty(), "should have found aiport");
} finally {
airportRepository.delete(vie);
@@ -214,18 +233,132 @@ public class CouchbaseRepositoryQueryIntegrationTests extends ClusterAwareIntegr
try {
vie = new Airport("airports::vie", "vie", "loww");
vie = airportRepository.save(vie);
Airport airport2 = airportRepository.findByIata(Iata.vie);
Airport airport2 = airportRepository.findByIata(vie.getIata());
assertNotNull(airport2, "should have found " + vie);
assertEquals(airport2.getId(), vie.getId());
} finally {
airportRepository.delete(vie);
}
}
/**
* can test against _default._default without setting up additional scope/collection and also test for collections and
* scopes that do not exist These same tests should be repeated on non-default scope and collection in a test that
* supports collections
*/
@Test
@IgnoreWhen(missesCapabilities = { Capabilities.QUERY, Capabilities.COLLECTIONS }, clusterTypes = ClusterType.MOCKED)
void findBySimplePropertyWithCollection() {
Airport vie = new Airport("airports::vie", "vie", "low7");
try {
Airport saved = airportRepository.withScope(scopeName).withCollection(collectionName).save(vie);
// given collection (on scope used by template)
Airport airport2 = airportRepository.withCollection(collectionName)
.withOptions(QueryOptions.queryOptions().scanConsistency(QueryScanConsistency.REQUEST_PLUS))
.iata(vie.getIata());
assertEquals(saved, airport2);
// given scope and collection
Airport airport3 = airportRepository.withScope(scopeName).withCollection(collectionName)
.withOptions(QueryOptions.queryOptions().scanConsistency(QueryScanConsistency.REQUEST_PLUS))
.iata(vie.getIata());
assertEquals(saved, airport3);
// given bad collection
assertThrows(IndexFailureException.class,
() -> airportRepository.withCollection("bogusCollection").iata(vie.getIata()));
// given bad scope
assertThrows(IndexFailureException.class, () -> airportRepository.withScope("bogusScope").iata(vie.getIata()));
} finally {
airportRepository.delete(vie);
}
}
@Test
@IgnoreWhen(hasCapabilities = { Capabilities.COLLECTIONS }, clusterTypes = ClusterType.MOCKED)
void findBySimplePropertyWithCollectionFail() {
// can test against _default._default without setting up additional scope/collection
// the server will throw an exception if it doesn't support COLLECTIONS
Airport vie = new Airport("airports::vie", "vie", "low8");
try {
Airport saved = airportRepository.save(vie);
assertThrows(CouchbaseException.class, () -> airportRepository.withScope("non_default_scope_name")
.withCollection(collectionName).iata(vie.getIata()));
} finally {
airportRepository.delete(vie);
}
}
@Test
void findBySimplePropertyWithOptions() {
Airport vie = new Airport("airports::vie", "vie", "low9");
JsonArray positionalParams = JsonArray.create().add("this parameter will be overridden");
// JsonObject namedParams = JsonObject.create().put("$1", vie.getIata());
try {
Airport saved = airportRepository.save(vie);
// Duration of 1 nano-second will cause timeout
assertThrows(AmbiguousTimeoutException.class, () -> airportRepository
.withOptions(QueryOptions.queryOptions().timeout(Duration.ofNanos(1))).iata(vie.getIata()));
Airport airport3 = airportRepository.withOptions(
QueryOptions.queryOptions().scanConsistency(QueryScanConsistency.REQUEST_PLUS).parameters(positionalParams))
.iata(vie.getIata());
assertEquals(saved, airport3);
} finally {
airportRepository.delete(vie);
}
}
@Test
public void saveNotBounded() {
// save() followed by query with NOT_BOUNDED will result in not finding the document
Airport vie = new Airport("airports::vie", "vie", "low9");
Airport airport2 = null;
for (int i = 1; i <= 100; i++) {
// set version == 0 so save() will be an upsert, not a replace
Airport saved = airportRepository.save(vie.clearVersion());
try {
airport2 = airportRepository
.withOptions(QueryOptions.queryOptions().scanConsistency(QueryScanConsistency.NOT_BOUNDED))
.iata(saved.getIata());
if (airport2 == null) {
break;
}
} catch (DataRetrievalFailureException drfe) {
airport2 = null; //
} finally {
// airportRepository.delete(vie);
// instead of delete, use removeResult to test QueryOptions.consistentWith()
RemoveResult removeResult = couchbaseTemplate.removeById().one(vie.getId());
assertEquals(vie.getId(), removeResult.getId());
assertTrue(removeResult.getCas() != 0);
assertTrue(removeResult.getMutationToken().isPresent());
Airport airport3 = airportRepository
.withOptions(QueryOptions.queryOptions().scanConsistency(QueryScanConsistency.REQUEST_PLUS)
.consistentWith(MutationState.from(removeResult.getMutationToken().get())))
.iata(vie.getIata());
assertNull(airport3, "should have been removed");
}
}
assertNull(airport2, "airport2 should have likely been null at least once");
}
@Test
public void testCas() {
User user = new User("1", "Dave", "Wilson");
userRepository.save(user);
userRepository.findByFirstname("Dave");
user.setVersion(user.getVersion() - 1);
assertThrows(DataIntegrityViolationException.class, () -> userRepository.save(user));
user.setVersion(0);
@@ -233,12 +366,20 @@ public class CouchbaseRepositoryQueryIntegrationTests extends ClusterAwareIntegr
userRepository.delete(user);
}
@Test
public void testExpiryAnnotation() {
UserAnnotated user = new UserAnnotated("1", "Dave", "Wilson");
userRepository.save(user);
userRepository.findByFirstname("Dave");
sleep(2000);
assertThrows(DataRetrievalFailureException.class, () -> userRepository.delete(user));
}
@Test
void count() {
String[] iatas = { "JFK", "IAD", "SFO", "SJC", "SEA", "LAX", "PHX" };
try {
airportRepository.saveAll(
Arrays.stream(iatas).map((iata) -> new Airport("airports::" + iata, iata, iata.toLowerCase(Locale.ROOT)))
.collect(Collectors.toSet()));
@@ -385,12 +526,9 @@ public class CouchbaseRepositoryQueryIntegrationTests extends ClusterAwareIntegr
Airport vienna = new Airport("airports::vie", "vie", "LOWW");
Airport frankfurt = new Airport("airports::fra", "fra", "EDDF");
Airport losAngeles = new Airport("airports::lax", "lax", "KLAX");
try {
airportRepository.saveAll(asList(vienna, frankfurt, losAngeles));
airportRepository.deleteAllById(asList(vienna.getId(), losAngeles.getId()));
assertThat(airportRepository.findAll()).containsExactly(frankfurt);
} finally {
airportRepository.deleteAll();
@@ -430,7 +568,9 @@ public class CouchbaseRepositoryQueryIntegrationTests extends ClusterAwareIntegr
private void sleep(int millis) {
try {
Thread.sleep(millis); // so they are executed out-of-order
} catch (InterruptedException ie) {}
} catch (InterruptedException ie) {
;
}
}
@Configuration
@@ -463,10 +603,16 @@ public class CouchbaseRepositoryQueryIntegrationTests extends ClusterAwareIntegr
return new NaiveAuditorAware();
}
@Override
public void configureEnvironment(final ClusterEnvironment.Builder builder) {
builder.ioConfig().maxHttpConnections(11).idleHttpConnectionTimeout(Duration.ofSeconds(4));
return;
}
@Bean(name = "dateTimeProviderRef")
public DateTimeProvider testDateTimeProvider() {
return new AuditingDateTimeProvider();
}
}
}
}

View File

@@ -73,7 +73,8 @@ public class ReactiveCouchbaseRepositoryKeyValueIntegrationTests extends Cluster
Airport saved = airportRepository.save(vie).block();
Airport airport1 = airportRepository.findById(saved.getId()).block();
assertEquals(airport1, saved);
assertEquals(saved.getCreatedBy(), ReactiveNaiveAuditorAware.AUDITOR); // ReactiveNaiveAuditorAware will provide this
assertEquals(saved.getCreatedBy(), ReactiveNaiveAuditorAware.AUDITOR); // ReactiveNaiveAuditorAware will provide
// this
} finally {
airportRepository.delete(vie).block();
}

View File

@@ -130,17 +130,17 @@ public class ReactiveCouchbaseRepositoryQueryIntegrationTests extends JavaIntegr
Airport vie = new Airport("airports::vie", "vie", "low3");
Airport saved1 = airportRepository.save(vie).block();
Airport saved2 = airportRepository.save(vie.withId(UUID.randomUUID().toString())).block();
try {
airportRepository.findAll().collectList().block(); // findAll has QueryScanConsistency;
Mono<Airport> airport = airportRepository.findPolicySnapshotByPolicyIdAndEffectiveDateTime("any", 0);
System.out.println("------------------------------");
System.out.println(airport.block());
System.out.println("------------------------------");
Flux<Airport> airports = airportRepository.findPolicySnapshotAll();
System.out.println(airports.collectList().block());
System.out.println("------------------------------");
Mono<Airport> ap = getPolicyByIdAndEffectiveDateTime("x", Instant.now());
System.out.println(ap.block());
try {
airportRepository.findAll().collectList().block(); // findAll has QueryScanConsistency;
Mono<Airport> airport = airportRepository.findPolicySnapshotByPolicyIdAndEffectiveDateTime("any", 0);
System.out.println("------------------------------");
System.out.println(airport.block());
System.out.println("------------------------------");
Flux<Airport> airports = airportRepository.findPolicySnapshotAll();
System.out.println(airports.collectList().block());
System.out.println("------------------------------");
Mono<Airport> ap = getPolicyByIdAndEffectiveDateTime("x", Instant.now());
System.out.println(ap.block());
} finally {
airportRepository.delete(saved1).block();
airportRepository.delete(saved2).block();
@@ -249,6 +249,22 @@ public class ReactiveCouchbaseRepositoryQueryIntegrationTests extends JavaIntegr
}
}
@Test
void deleteOne() {
Airport vienna = new Airport("airports::vie", "vie", "LOWW");
try {
Airport ap = airportRepository.save(vienna).block();
assertEquals(vienna.getId(), ap.getId(), "should have saved what was provided");
airportRepository.delete(vienna).as(StepVerifier::create).verifyComplete();
airportRepository.findAll().as(StepVerifier::create).verifyComplete();
} finally {
airportRepository.deleteAll().block();
}
}
@Configuration
@EnableReactiveCouchbaseRepositories("org.springframework.data.couchbase")
static class Config extends AbstractCouchbaseConfiguration {

View File

@@ -0,0 +1,211 @@
/*
* Copyright 2017-2021 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.repository.query;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import java.util.List;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.dao.DataRetrievalFailureException;
import org.springframework.data.couchbase.domain.Airport;
import org.springframework.data.couchbase.domain.AirportRepository;
import org.springframework.data.couchbase.domain.Config;
import org.springframework.data.couchbase.domain.User;
import org.springframework.data.couchbase.domain.UserCol;
import org.springframework.data.couchbase.domain.UserColRepository;
import org.springframework.data.couchbase.util.Capabilities;
import org.springframework.data.couchbase.util.ClusterType;
import org.springframework.data.couchbase.util.CollectionAwareIntegrationTests;
import org.springframework.data.couchbase.util.IgnoreWhen;
import com.couchbase.client.core.error.IndexFailureException;
import com.couchbase.client.core.io.CollectionIdentifier;
import com.couchbase.client.java.json.JsonArray;
import com.couchbase.client.java.query.QueryOptions;
import com.couchbase.client.java.query.QueryScanConsistency;
@IgnoreWhen(missesCapabilities = { Capabilities.QUERY, Capabilities.COLLECTIONS }, clusterTypes = ClusterType.MOCKED)
public class CouchbaseRepositoryQueryCollectionIntegrationTests extends CollectionAwareIntegrationTests {
@Autowired AirportRepository airportRepository;
@Autowired UserColRepository userColRepository;
@BeforeAll
public static void beforeAll() {
// first call the super method
callSuperBeforeAll(new Object() {});
// then do processing for this class
}
@AfterAll
public static void afterAll() {
// first do the processing for this class
// no-op
// then call the super method
callSuperAfterAll(new Object() {});
}
@BeforeEach
@Override
public void beforeEach() {
// first call the super method
super.beforeEach();
// then do processing for this class
couchbaseTemplate.removeByQuery(User.class).inCollection(collectionName).all();
couchbaseTemplate.removeByQuery(UserCol.class).inScope(otherScope).inCollection(otherCollection).all();
ApplicationContext ac = new AnnotationConfigApplicationContext(Config.class);
// seems that @Autowired is not adequate, so ...
airportRepository = (AirportRepository) ac.getBean("airportRepository");
userColRepository = (UserColRepository) ac.getBean("userColRepository");
}
@AfterEach
@Override
public void afterEach() {
// first do processing for this class
// no-op
// then call the super method
super.afterEach();
}
@Test
public void myTest() {
AirportRepository ar = airportRepository.withScope(scopeName).withCollection(collectionName);
Airport vie = new Airport("airports::vie", "vie", "loww");
try {
Airport saved = ar.save(vie);
Airport airport2 = ar.save(saved);
} catch (Exception e) {
e.printStackTrace();
throw e;
} finally {
ar.delete(vie);
}
}
/**
* can test against _default._default without setting up additional scope/collection and also test for collections and
* scopes that do not exist These same tests should be repeated on non-default scope and collection in a test that
* supports collections
*/
@Test
@IgnoreWhen(missesCapabilities = { Capabilities.QUERY, Capabilities.COLLECTIONS }, clusterTypes = ClusterType.MOCKED)
void findBySimplePropertyWithCollection() {
Airport vie = new Airport("airports::vie", "vie", "loww");
// create proxy with scope, collection
AirportRepository ar = airportRepository.withScope(scopeName).withCollection(collectionName);
try {
Airport saved = ar.save(vie);
// valid scope, collection in options
Airport airport2 = ar.withCollection(collectionName)
.withOptions(QueryOptions.queryOptions().scanConsistency(QueryScanConsistency.REQUEST_PLUS))
.iata(vie.getIata());
assertEquals(saved, airport2);
// given bad collectionName in fluent
assertThrows(IndexFailureException.class, () -> ar.withCollection("bogusCollection").iata(vie.getIata()));
// given bad scopeName in fluent
assertThrows(IndexFailureException.class, () -> ar.withScope("bogusScope").iata(vie.getIata()));
Airport airport6 = ar.withOptions(QueryOptions.queryOptions().scanConsistency(QueryScanConsistency.REQUEST_PLUS))
.iata(vie.getIata());
assertEquals(saved, airport6);
} catch (Exception e) {
e.printStackTrace();
throw e;
} finally {
ar.deleteAll();
}
}
@Test
void findBySimplePropertyWithOptions() {
AirportRepository ar = airportRepository.withScope(scopeName).withCollection(collectionName);
Airport vie = new Airport("airports::vie", "vie", "loww");
JsonArray positionalParams = JsonArray.create().add("\"this parameter will be overridden\"");
try {
Airport saved = ar.save(vie);
Airport airport3 = ar.withOptions(
QueryOptions.queryOptions().scanConsistency(QueryScanConsistency.REQUEST_PLUS).parameters(positionalParams))
.iata(vie.getIata());
assertEquals(saved, airport3);
} catch (Exception e) {
e.printStackTrace();
throw e;
} finally {
ar.delete(vie);
}
}
@Test
public void testScopeCollectionAnnotation() {
// template default scope is my_scope
// UserCol annotation scope is other_scope
UserCol user = new UserCol("1", "Dave", "Wilson");
try {
UserCol saved = userColRepository.withCollection(otherCollection).save(user); // should use UserCol annotation
// scope
List<UserCol> found = userColRepository.withCollection(otherCollection).findByFirstname(user.getFirstname());
assertEquals(saved, found.get(0), "should have found what was saved");
List<UserCol> notfound = userColRepository.withScope(CollectionIdentifier.DEFAULT_SCOPE)
.withCollection(CollectionIdentifier.DEFAULT_COLLECTION).findByFirstname(user.getFirstname());
assertEquals(0, notfound.size(), "should not have found what was saved");
} finally {
try {
userColRepository.withScope(otherScope).withCollection(otherCollection).delete(user);
} catch (DataRetrievalFailureException drfe) {}
}
}
// template default scope is my_scope
// UserCol annotation scope is other_scope
@Test
public void testScopeCollectionRepoWith() {
UserCol user = new UserCol("1", "Dave", "Wilson");
try {
UserCol saved = userColRepository.withScope(scopeName).withCollection(collectionName).save(user);
List<UserCol> found = userColRepository.withScope(scopeName).withCollection(collectionName)
.findByFirstname(user.getFirstname());
assertEquals(saved, found.get(0), "should have found what was saved");
List<UserCol> notfound = userColRepository.withScope(CollectionIdentifier.DEFAULT_SCOPE)
.withCollection(CollectionIdentifier.DEFAULT_COLLECTION).findByFirstname(user.getFirstname());
assertEquals(0, notfound.size(), "should not have found what was saved");
userColRepository.withScope(scopeName).withCollection(collectionName).delete(user);
} finally {
try {
userColRepository.withScope(scopeName).withCollection(collectionName).delete(user);
} catch (DataRetrievalFailureException drfe) {}
}
}
}

View File

@@ -0,0 +1,215 @@
/*
* Copyright 2017-2021 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.repository.query;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import java.util.List;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.dao.DataRetrievalFailureException;
import org.springframework.data.couchbase.domain.Airport;
import org.springframework.data.couchbase.domain.Config;
import org.springframework.data.couchbase.domain.ReactiveAirportRepository;
import org.springframework.data.couchbase.domain.ReactiveUserColRepository;
import org.springframework.data.couchbase.domain.User;
import org.springframework.data.couchbase.domain.UserCol;
import org.springframework.data.couchbase.util.Capabilities;
import org.springframework.data.couchbase.util.ClusterType;
import org.springframework.data.couchbase.util.CollectionAwareIntegrationTests;
import org.springframework.data.couchbase.util.IgnoreWhen;
import com.couchbase.client.core.error.IndexFailureException;
import com.couchbase.client.core.io.CollectionIdentifier;
import com.couchbase.client.java.json.JsonArray;
import com.couchbase.client.java.query.QueryOptions;
import com.couchbase.client.java.query.QueryScanConsistency;
@IgnoreWhen(missesCapabilities = { Capabilities.QUERY, Capabilities.COLLECTIONS }, clusterTypes = ClusterType.MOCKED)
public class ReactiveCouchbaseRepositoryQueryCollectionIntegrationTests extends CollectionAwareIntegrationTests {
@Autowired ReactiveAirportRepository airportRepository;
@Autowired ReactiveUserColRepository userColRepository;
@BeforeAll
public static void beforeAll() {
// first call the super method
callSuperBeforeAll(new Object() {});
// then do processing for this class
}
@AfterAll
public static void afterAll() {
// first do the processing for this class
// no-op
// then call the super method
callSuperAfterAll(new Object() {});
}
@BeforeEach
@Override
public void beforeEach() {
// first call the super method
super.beforeEach();
// then do processing for this class
couchbaseTemplate.removeByQuery(User.class).inCollection(collectionName).all();
couchbaseTemplate.removeByQuery(UserCol.class).inScope(otherScope).inCollection(otherCollection).all();
ApplicationContext ac = new AnnotationConfigApplicationContext(Config.class);
// seems that @Autowired is not adequate, so ...
airportRepository = (ReactiveAirportRepository) ac.getBean("reactiveAirportRepository");
userColRepository = (ReactiveUserColRepository) ac.getBean("reactiveUserColRepository");
}
@AfterEach
@Override
public void afterEach() {
// first do processing for this class
// no-op
// then call the super method
super.afterEach();
}
@Test
public void myTest() {
ReactiveAirportRepository ar = airportRepository.withScope(scopeName).withCollection(collectionName);
Airport vie = new Airport("airports::vie", "vie", "loww");
try {
Airport saved = ar.save(vie).block();
Airport airport2 = ar.save(saved).block();
} catch (Exception e) {
e.printStackTrace();
throw e;
} finally {
ar.delete(vie).block();
}
}
/**
* can test against _default._default without setting up additional scope/collection and also test for collections and
* scopes that do not exist These same tests should be repeated on non-default scope and collection in a test that
* supports collections
*/
@Test
@IgnoreWhen(missesCapabilities = { Capabilities.QUERY, Capabilities.COLLECTIONS }, clusterTypes = ClusterType.MOCKED)
void findBySimplePropertyWithCollection() {
Airport vie = new Airport("airports::vie", "vie", "loww");
// create proxy with scope, collection
ReactiveAirportRepository ar = airportRepository.withScope(scopeName).withCollection(collectionName);
try {
Airport saved = ar.save(vie).block();
// valid scope, collection in options
Airport airport2 = ar.withOptions(QueryOptions.queryOptions().scanConsistency(QueryScanConsistency.REQUEST_PLUS))
.iata(vie.getIata()).block();
assertEquals(saved, airport2);
// given bad collectionName in fluent
assertThrows(IndexFailureException.class, () -> ar.withCollection("bogusCollection").iata(vie.getIata()).block());
// given bad scopeName in fluent
assertThrows(IndexFailureException.class, () -> ar.withScope("bogusScope").iata(vie.getIata()).block());
Airport airport6 = ar.withOptions(QueryOptions.queryOptions().scanConsistency(QueryScanConsistency.REQUEST_PLUS))
.iata(vie.getIata()).block();
assertEquals(saved, airport6);
} catch (Exception e) {
e.printStackTrace();
throw e;
} finally {
ar.deleteAll().block();
}
}
@Test
void findBySimplePropertyWithOptions() {
Airport vie = new Airport("airports::vie", "vie", "loww");
ReactiveAirportRepository ar = airportRepository.withScope(scopeName).withCollection(collectionName);
JsonArray positionalParams = JsonArray.create().add("\"this parameter will be overridden\"");
try {
Airport saved = ar.save(vie).block();
Airport airport3 = ar.withOptions(
QueryOptions.queryOptions().scanConsistency(QueryScanConsistency.REQUEST_PLUS).parameters(positionalParams))
.iata(vie.getIata()).block();
assertEquals(saved, airport3);
} catch (Exception e) {
e.printStackTrace();
throw e;
} finally {
ar.delete(vie).block();
}
}
@Test
public void testScopeCollectionAnnotation() {
// template default scope is my_scope
// UserCol annotation scope is other_scope
UserCol user = new UserCol("1", "Dave", "Wilson");
try {
UserCol saved = userColRepository.withCollection(otherCollection).save(user).block(); // should use UserCol
// annotation
// scope
List<UserCol> found = userColRepository.withCollection(otherCollection).findByFirstname(user.getFirstname())
.collectList().block();
assertEquals(saved, found.get(0), "should have found what was saved");
List<UserCol> notfound = userColRepository.withScope(CollectionIdentifier.DEFAULT_SCOPE)
.withCollection(CollectionIdentifier.DEFAULT_COLLECTION).findByFirstname(user.getFirstname()).collectList()
.block();
assertEquals(0, notfound.size(), "should not have found what was saved");
} finally {
try {
userColRepository.withScope(otherScope).withCollection(otherCollection).delete(user);
} catch (DataRetrievalFailureException drfe) {}
}
}
// template default scope is my_scope
// UserCol annotation scope is other_scope
@Test
public void testScopeCollectionRepoWith() {
UserCol user = new UserCol("1", "Dave", "Wilson");
try {
UserCol saved = userColRepository.withScope(scopeName).withCollection(collectionName).save(user).block();
List<UserCol> found = userColRepository.withScope(scopeName).withCollection(collectionName)
.findByFirstname(user.getFirstname()).collectList().block();
assertEquals(saved, found.get(0), "should have found what was saved");
List<UserCol> notfound = userColRepository.withScope(CollectionIdentifier.DEFAULT_SCOPE)
.withCollection(CollectionIdentifier.DEFAULT_COLLECTION).findByFirstname(user.getFirstname()).collectList()
.block();
assertEquals(0, notfound.size(), "should not have found what was saved");
userColRepository.withScope(scopeName).withCollection(collectionName).delete(user).block();
} finally {
try {
userColRepository.withScope(scopeName).withCollection(collectionName).delete(user).block();
} catch (DataRetrievalFailureException drfe) {}
}
}
}

View File

@@ -22,7 +22,7 @@
- log additional debug info during automatic index creation
-->
<looger name="org.springframework.data.couchbase.core" level="debug"/>"
<logger name="org.springframework.data.couchbase.core" level="debug"/>"
<logger name="org.springframework.data.couchbase.repository.query" level="debug"/>
<logger name="org.springframework.data.couchbase.repository.query.SpatialViewQueryCreator" level="trace"/>
<logger name="org.springframework.data.couchbase.repository.query.StringN1qlBasedQuery" level="trace"/>