Revert "Scopes and collections for repositories (#1149)"

This reverts commit 1581712765.
This commit is contained in:
mikereiche
2021-08-10 21:30:45 -07:00
parent 0983e6609b
commit 0bdecefb09
97 changed files with 961 additions and 2616 deletions

View File

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

View File

@@ -22,6 +22,7 @@ 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,6 +28,7 @@ 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;
@@ -106,25 +107,13 @@ public class CouchbaseTemplate implements CouchbaseOperations, ApplicationContex
}
@Override
@Deprecated
public ExecutableRemoveById removeById() {
return removeById(null);
return new ExecutableRemoveByIdOperationSupport(this).removeById();
}
@Override
public ExecutableRemoveById removeById(Class<?> domainType) {
return new ExecutableRemoveByIdOperationSupport(this).removeById(domainType);
}
@Override
@Deprecated
public ExecutableExistsById existsById() {
return existsById(null);
}
@Override
public ExecutableExistsById existsById(Class<?> domainType) {
return new ExecutableExistsByIdOperationSupport(this).existsById(domainType);
return new ExecutableExistsByIdOperationSupport(this).existsById();
}
@Override

View File

@@ -17,8 +17,6 @@
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;
@@ -40,6 +38,9 @@ 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,14 +36,8 @@ 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.
*/
@@ -84,6 +78,7 @@ public interface ExecutableExistsByIdOperation {
}
/**
*
* Fluent method to specify the collection.
*
* @param <T> the entity type to use for the results.

View File

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

View File

@@ -97,12 +97,14 @@ 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,17 +77,19 @@ 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.");
Assert.notEmpty(fields, "Fields must not be null nor empty.");
return new ExecutableFindByIdSupport<>(template, domainType, scope, collection, options, Arrays.asList(fields));
}

View File

@@ -68,8 +68,7 @@ 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;
@@ -127,12 +126,8 @@ 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, dFields);
collection, options, distinctFields);
}
@Override
@@ -159,12 +154,14 @@ 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,11 +75,13 @@ 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,12 +89,14 @@ 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,6 +18,7 @@ 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;
@@ -38,12 +39,6 @@ public interface ExecutableRemoveByIdOperation {
/**
* Removes a document.
*/
ExecutableRemoveById removeById(Class<?> domainType);
/**
* Removes a document.
*/
@Deprecated
ExecutableRemoveById removeById();
/**

View File

@@ -35,21 +35,14 @@ public class ExecutableRemoveByIdOperationSupport implements ExecutableRemoveByI
}
@Override
@Deprecated
public ExecutableRemoveById removeById() {
return removeById(null);
}
@Override
public ExecutableRemoveById removeById(Class<?> domainType) {
return new ExecutableRemoveByIdSupport(template, domainType, null, null, null, PersistTo.NONE, ReplicateTo.NONE,
return new ExecutableRemoveByIdSupport(template, 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;
@@ -59,19 +52,18 @@ public class ExecutableRemoveByIdOperationSupport implements ExecutableRemoveByI
private final Long cas;
private final ReactiveRemoveByIdSupport reactiveRemoveByIdSupport;
ExecutableRemoveByIdSupport(final CouchbaseTemplate template, final Class<?> domainType, final String scope,
final String collection, final RemoveOptions options, final PersistTo persistTo, final ReplicateTo replicateTo,
ExecutableRemoveByIdSupport(final CouchbaseTemplate template, 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(), domainType, scope, collection,
options, persistTo, replicateTo, durabilityLevel, cas);
this.reactiveRemoveByIdSupport = new ReactiveRemoveByIdSupport(template.reactive(), scope, collection, options,
persistTo, replicateTo, durabilityLevel, cas);
this.cas = cas;
}
@@ -87,14 +79,15 @@ public class ExecutableRemoveByIdOperationSupport implements ExecutableRemoveByI
@Override
public RemoveByIdWithOptions inCollection(final String collection) {
return new ExecutableRemoveByIdSupport(template, domainType, scope, collection, options, persistTo, replicateTo,
Assert.hasText(collection, "Collection must not be null nor empty.");
return new ExecutableRemoveByIdSupport(template, 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, domainType, scope, collection, options, persistTo, replicateTo,
return new ExecutableRemoveByIdSupport(template, scope, collection, options, persistTo, replicateTo,
durabilityLevel, cas);
}
@@ -102,26 +95,27 @@ 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, domainType, scope, collection, options, persistTo, replicateTo,
return new ExecutableRemoveByIdSupport(template, 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, domainType, scope, collection, options, persistTo, replicateTo,
return new ExecutableRemoveByIdSupport(template, scope, collection, options, persistTo, replicateTo,
durabilityLevel, cas);
}
@Override
public RemoveByIdInCollection inScope(final String scope) {
return new ExecutableRemoveByIdSupport(template, domainType, scope, collection, options, persistTo, replicateTo,
Assert.hasText(scope, "Scope must not be null nor empty.");
return new ExecutableRemoveByIdSupport(template, scope, collection, options, persistTo, replicateTo,
durabilityLevel, cas);
}
@Override
public RemoveByIdWithDurability withCas(Long cas) {
return new ExecutableRemoveByIdSupport(template, domainType, scope, collection, options, persistTo, replicateTo,
return new ExecutableRemoveByIdSupport(template, 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,7 +36,8 @@ 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> {
@@ -89,6 +90,7 @@ 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);
}
@@ -102,6 +104,7 @@ 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,6 +82,7 @@ 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);
}
@@ -117,6 +118,7 @@ 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,12 +89,14 @@ 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,10 +15,10 @@
*/
package org.springframework.data.couchbase.core;
import reactor.core.publisher.Mono;
import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
import org.springframework.data.couchbase.core.mapping.event.CouchbaseMappingEvent;
import reactor.core.publisher.Mono;
/**
* Wrapper of {@link TemplateSupport} methods to adapt them to {@link ReactiveTemplateSupport}.

View File

@@ -13,17 +13,16 @@
* 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 {
@@ -47,4 +46,9 @@ 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 = null;
private ThreadLocal<PseudoArgs<?>> threadLocalArgs = new ThreadLocal<>();
public ReactiveCouchbaseTemplate(final CouchbaseClientFactory clientFactory, final CouchbaseConverter converter) {
this(clientFactory, converter, new JacksonTranslationService());
@@ -64,12 +64,7 @@ public class ReactiveCouchbaseTemplate implements ReactiveCouchbaseOperations, A
@Override
public ReactiveExistsById existsById() {
return existsById(null);
}
@Override
public ReactiveExistsById existsById(Class<?> domainType) {
return new ReactiveExistsByIdOperationSupport(this).existsById(domainType);
return new ReactiveExistsByIdOperationSupport(this).existsById();
}
@Override
@@ -94,12 +89,7 @@ public class ReactiveCouchbaseTemplate implements ReactiveCouchbaseOperations, A
@Override
public ReactiveRemoveById removeById() {
return removeById(null);
}
@Override
public ReactiveRemoveById removeById(Class<?> domainType) {
return new ReactiveRemoveByIdOperationSupport(this).removeById(domainType);
return new ReactiveRemoveByIdOperationSupport(this).removeById();
}
@Override
@@ -168,18 +158,11 @@ public class ReactiveCouchbaseTemplate implements ReactiveCouchbaseOperations, A
}
/**
* @return the pseudoArgs from the ThreadLocal field
* {@inheritDoc}
*/
@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,6 +16,8 @@
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;
@@ -28,12 +30,11 @@ 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;
@@ -67,13 +68,13 @@ class ReactiveCouchbaseTemplateSupport implements ApplicationContextAware, React
@Override
public Mono<CouchbaseDocument> encodeEntity(final Object entityToEncode) {
return Mono.just(entityToEncode).doOnNext(entity -> maybeEmitEvent(new BeforeConvertEvent<>(entity)))
return Mono.just(entityToEncode).doOnNext(entity -> maybeEmitEvent(new ReactiveBeforeConvertEvent<>(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 BeforeSaveEvent<>(entityToEncode, converted)));
.doOnNext(converted -> maybeEmitEvent(new ReactiveBeforeSaveEvent<>(entityToEncode, converted)));
}
@Override
@@ -111,7 +112,7 @@ class ReactiveCouchbaseTemplateSupport implements ApplicationContextAware, React
} else {
returnValue = entity;
}
maybeEmitEvent(new AfterSaveEvent(returnValue, converted));
maybeEmitEvent(new ReactiveAfterSaveEvent(returnValue, converted));
return returnValue;
});
}

View File

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

View File

@@ -167,12 +167,14 @@ 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,6 +36,7 @@ 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) {
@@ -70,19 +71,19 @@ public class ReactiveFindByIdOperationSupport implements ReactiveFindByIdOperati
@Override
public Mono<T> one(final String id) {
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))
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))
.onErrorResume(throwable -> {
if (throwable instanceof RuntimeException) {
if (throwable instanceof DocumentNotFoundException) {
@@ -112,17 +113,19 @@ 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.notNull(fields, "Fields must not be null");
Assert.notEmpty(fields, "Fields must not be null nor empty.");
return new ReactiveFindByIdSupport<>(template, domainType, scope, collection, options, Arrays.asList(fields),
support);
}

View File

@@ -89,6 +89,8 @@ public interface ReactiveFindByQueryOperation {
*/
Mono<Boolean> exists();
QueryOptions buildOptions(QueryOptions options);
}
/**
@@ -168,7 +170,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,6 +41,7 @@ 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) {
@@ -61,8 +62,11 @@ public class ReactiveFindByQueryOperationSupport implements ReactiveFindByQueryO
private final Query query;
private final QueryScanConsistency scanConsistency;
private final String collection;
private final String scope;
private 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;
@@ -87,8 +91,7 @@ public class ReactiveFindByQueryOperationSupport implements ReactiveFindByQueryO
@Override
public FindByQueryWithQuery<T> matching(Query query) {
QueryScanConsistency scanCons;
if (query.getScanConsistency() != null) { // redundant, since buildQueryOptions() will use
// query.getScanConsistency()
if (query.getScanConsistency() != null) {
scanCons = query.getScanConsistency();
} else {
scanCons = scanConsistency;
@@ -106,12 +109,14 @@ 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);
}
@@ -137,14 +142,10 @@ public class ReactiveFindByQueryOperationSupport implements ReactiveFindByQueryO
}
@Override
public FindByQueryWithDistinct<T> distinct(final String[] distinctFields) {
public FindByQueryWithDistinct<T> distinct(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, dFields, support);
collection, options, distinctFields, support);
}
@Override
@@ -159,65 +160,72 @@ public class ReactiveFindByQueryOperationSupport implements ReactiveFindByQueryO
@Override
public Flux<T> all() {
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));
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;
}
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));
}).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);
}
cas = row.getLong(TemplateUtils.SELECT_CAS);
row.removeKey(TemplateUtils.SELECT_ID);
row.removeKey(TemplateUtils.SELECT_CAS);
}
return support.decodeEntity(id, row.toString(), cas, returnType);
}));
return support.decodeEntity(id, row.toString(), cas, returnType);
});
});
}
private QueryOptions buildOptions(QueryOptions options) {
@Override
public QueryOptions buildOptions(QueryOptions options) {
QueryOptions opts = query.buildQueryOptions(options, scanConsistency);
return opts;
}
@Override
public Mono<Long> count() {
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());
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();
});
}
@Override

View File

@@ -68,16 +68,16 @@ public class ReactiveFindFromReplicasByIdOperationSupport implements ReactiveFin
@Override
public Mono<T> any(final String id) {
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))
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))
.onErrorMap(throwable -> {
if (throwable instanceof RuntimeException) {
return template.potentiallyConvertRuntimeException((RuntimeException) throwable);
@@ -101,12 +101,14 @@ 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,6 +21,7 @@ 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;
@@ -67,6 +68,8 @@ public interface ReactiveInsertByIdOperation {
@Override
Flux<? extends T> all(Collection<? extends T> objects);
InsertOptions buildOptions(InsertOptions options, CouchbaseDocument doc);
}
/**

View File

@@ -24,7 +24,6 @@ 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;
@@ -35,8 +34,8 @@ import com.couchbase.client.java.kv.ReplicateTo;
public class ReactiveInsertByIdOperationSupport implements ReactiveInsertByIdOperation {
private final ReactiveCouchbaseTemplate template;
private static final Logger LOG = LoggerFactory.getLogger(ReactiveInsertByIdOperationSupport.class);
private final ReactiveCouchbaseTemplate template;
public ReactiveInsertByIdOperationSupport(final ReactiveCouchbaseTemplate template) {
this.template = template;
@@ -79,12 +78,14 @@ public class ReactiveInsertByIdOperationSupport implements ReactiveInsertByIdOpe
@Override
public Mono<T> one(T object) {
PseudoArgs<InsertOptions> pArgs = new PseudoArgs(template, scope, collection, options, domainType);
LOG.trace("insertById {}", pArgs);
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());
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 -> {
@@ -101,8 +102,20 @@ public class ReactiveInsertByIdOperationSupport implements ReactiveInsertByIdOpe
return Flux.fromIterable(objects).flatMap(this::one);
}
@Override
public InsertOptions buildOptions(InsertOptions options, CouchbaseDocument doc) { // CouchbaseDocument converted
return OptionsBuilder.buildInsertOptions(options, persistTo, replicateTo, durabilityLevel, expiry, 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()));
}
return options;
}
@Override
@@ -114,12 +127,14 @@ 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,14 +40,8 @@ public interface ReactiveRemoveByIdOperation {
/**
* Removes a document.
*/
@Deprecated
ReactiveRemoveById removeById();
/**
* Removes a document.
*/
ReactiveRemoveById removeById(Class<?> domainType);
/**
* Terminating operations invoking the actual execution.
*/
@@ -112,7 +106,6 @@ public interface ReactiveRemoveByIdOperation {
interface RemoveByIdWithDurability extends RemoveByIdInScope, WithDurability<RemoveResult> {
@Override
RemoveByIdInCollection withDurability(DurabilityLevel durabilityLevel);
@Override
RemoveByIdInCollection withDurability(PersistTo persistTo, ReplicateTo replicateTo);

View File

@@ -15,14 +15,13 @@
*/
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;
@@ -34,28 +33,20 @@ 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 removeById(null);
}
@Override
public ReactiveRemoveById removeById(Class<?> domainType) {
return new ReactiveRemoveByIdSupport(template, domainType, null, null, null, PersistTo.NONE, ReplicateTo.NONE,
return new ReactiveRemoveByIdSupport(template, 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;
@@ -64,11 +55,10 @@ public class ReactiveRemoveByIdOperationSupport implements ReactiveRemoveByIdOpe
private final DurabilityLevel durabilityLevel;
private final Long cas;
ReactiveRemoveByIdSupport(final ReactiveCouchbaseTemplate template, final Class<?> domainType, final String scope,
final String collection, final RemoveOptions options, final PersistTo persistTo, final ReplicateTo replicateTo,
ReactiveRemoveByIdSupport(final ReactiveCouchbaseTemplate template, 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;
@@ -80,19 +70,22 @@ public class ReactiveRemoveByIdOperationSupport implements ReactiveRemoveByIdOpe
@Override
public Mono<RemoveResult> one(final String id) {
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;
}
});
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;
}
});
}
@Override
@@ -101,13 +94,22 @@ public class ReactiveRemoveByIdOperationSupport implements ReactiveRemoveByIdOpe
}
private RemoveOptions buildRemoveOptions(RemoveOptions options) {
return OptionsBuilder.buildRemoveOptions(options, persistTo, replicateTo, durabilityLevel, 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);
}
if (cas != null) {
options.cas(cas);
}
return options;
}
@Override
public RemoveByIdInCollection withDurability(final DurabilityLevel durabilityLevel) {
Assert.notNull(durabilityLevel, "Durability Level must not be null.");
return new ReactiveRemoveByIdSupport(template, domainType, scope, collection, options, persistTo, replicateTo,
return new ReactiveRemoveByIdSupport(template, scope, collection, options, persistTo, replicateTo,
durabilityLevel, cas);
}
@@ -115,32 +117,34 @@ 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, domainType, scope, collection, options, persistTo, replicateTo,
return new ReactiveRemoveByIdSupport(template, scope, collection, options, persistTo, replicateTo,
durabilityLevel, cas);
}
@Override
public RemoveByIdWithDurability inCollection(final String collection) {
return new ReactiveRemoveByIdSupport(template, domainType, scope, collection, options, persistTo, replicateTo,
Assert.hasText(collection, "Collection must not be null nor empty.");
return new ReactiveRemoveByIdSupport(template, scope, collection, options, persistTo, replicateTo,
durabilityLevel, cas);
}
@Override
public RemoveByIdInCollection inScope(final String scope) {
return new ReactiveRemoveByIdSupport(template, domainType, scope, collection, options, persistTo, replicateTo,
Assert.hasText(scope, "Scope must not be null nor empty.");
return new ReactiveRemoveByIdSupport(template, 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, domainType, scope, collection, options, persistTo, replicateTo,
return new ReactiveRemoveByIdSupport(template, scope, collection, options, persistTo, replicateTo,
durabilityLevel, cas);
}
@Override
public RemoveByIdWithDurability withCas(Long cas) {
return new ReactiveRemoveByIdSupport(template, domainType, scope, collection, options, persistTo, replicateTo,
return new ReactiveRemoveByIdSupport(template, scope, collection, options, persistTo, replicateTo,
durabilityLevel, cas);
}
}

View File

@@ -15,6 +15,7 @@
*/
package org.springframework.data.couchbase.core;
import org.springframework.util.Assert;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@@ -25,7 +26,6 @@ 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,6 +36,7 @@ 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) {
@@ -44,7 +45,8 @@ 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> {
@@ -70,23 +72,25 @@ public class ReactiveRemoveByQueryOperationSupport implements ReactiveRemoveByQu
@Override
public Flux<RemoveResult> all() {
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())));
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()));
});
}
private QueryOptions buildQueryOptions(QueryOptions options) {
@@ -101,6 +105,7 @@ 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);
}
@@ -131,6 +136,7 @@ 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,7 +24,6 @@ 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;
@@ -35,8 +34,8 @@ import com.couchbase.client.java.kv.ReplicateTo;
public class ReactiveReplaceByIdOperationSupport implements ReactiveReplaceByIdOperation {
private final ReactiveCouchbaseTemplate template;
private static final Logger LOG = LoggerFactory.getLogger(ReactiveReplaceByIdOperationSupport.class);
private final ReactiveCouchbaseTemplate template;
public ReactiveReplaceByIdOperationSupport(final ReactiveCouchbaseTemplate template) {
this.template = template;
@@ -79,13 +78,13 @@ public class ReactiveReplaceByIdOperationSupport implements ReactiveReplaceByIdO
@Override
public Mono<T> one(T object) {
PseudoArgs<ReplaceOptions> pArgs = new PseudoArgs<>(template, scope, collection, options, domainType);
LOG.trace("replaceById {}", pArgs);
PseudoArgs<ReplaceOptions> pArgs = new PseudoArgs<>(template, scope, collection,
options != null ? options : ReplaceOptions.replaceOptions());
LOG.trace("statement: {} pArgs: {}", "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) {
@@ -102,8 +101,20 @@ public class ReactiveReplaceByIdOperationSupport implements ReactiveReplaceByIdO
}
private ReplaceOptions buildReplaceOptions(ReplaceOptions options, T object, CouchbaseDocument doc) {
return OptionsBuilder.buildReplaceOptions(options, persistTo, replicateTo, durabilityLevel, expiry,
support.getCas(object), 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;
}
@Override
@@ -115,12 +126,14 @@ 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 reactor.core.publisher.Mono;
import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
import org.springframework.data.couchbase.core.mapping.event.CouchbaseMappingEvent;
import reactor.core.publisher.Mono;
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,10 +21,7 @@ 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;
@@ -36,7 +33,6 @@ 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;
@@ -79,12 +75,12 @@ public class ReactiveUpsertByIdOperationSupport implements ReactiveUpsertByIdOpe
@Override
public Mono<T> one(T object) {
PseudoArgs<UpsertOptions> pArgs = new PseudoArgs(template, scope, collection, options, domainType);
LOG.trace("upsertById {}", pArgs);
PseudoArgs<UpsertOptions> pArgs = new PseudoArgs<>(template, scope, collection,
options != null ? options : UpsertOptions.upsertOptions());
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 -> {
@@ -102,7 +98,18 @@ public class ReactiveUpsertByIdOperationSupport implements ReactiveUpsertByIdOpe
}
private UpsertOptions buildUpsertOptions(UpsertOptions options, CouchbaseDocument doc) {
return OptionsBuilder.buildUpsertOptions(options, persistTo, replicateTo, durabilityLevel, expiry, 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;
}
@Override
@@ -114,12 +121,14 @@ 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,7 +34,6 @@ 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;
@@ -533,10 +532,6 @@ 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,9 +21,10 @@ 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;
@@ -97,14 +98,11 @@ public class BasicCouchbasePersistentEntity<T> extends BasicPersistentEntity<T,
@Override
public int getExpiry() {
return getExpiry(AnnotatedElementUtils.findMergedAnnotation(getType(), Expiry.class), environment);
}
public static int getExpiry(Expiry annotation, Environment environment) {
Document annotation = getType().getAnnotation(Document.class);
if (annotation == null)
return 0;
int expiryValue = getExpiryValue(annotation, environment);
int expiryValue = getExpiryValue(annotation);
long secondsShift = annotation.expiryUnit().toSeconds(expiryValue);
if (secondsShift > TTL_IN_SECONDS_INCLUSIVE_END) {
@@ -123,7 +121,7 @@ public class BasicCouchbasePersistentEntity<T> extends BasicPersistentEntity<T,
}
}
private static int getExpiryValue(Expiry annotation, Environment environment) {
private int getExpiryValue(Document annotation) {
int expiryValue = annotation.expiry();
String expiryExpressionString = annotation.expiryExpression();
if (StringUtils.hasLength(expiryExpressionString)) {

View File

@@ -23,13 +23,7 @@ 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.
@@ -41,15 +35,12 @@ import com.couchbase.client.java.query.QueryScanConsistency;
@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;
/**
@@ -64,13 +55,11 @@ 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;
/**
@@ -79,9 +68,4 @@ 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

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

View File

@@ -54,7 +54,7 @@ public class AuditingEntityCallback implements BeforeConvertCallback<Object>, Af
*/
@Override
public Object onBeforeConvert(Object entity, String collection) {
// LOG.trace("onBeforeConvert " + entity);
//LOG.debug("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.trace("onAfterConvert " + document);
//LOG.debug("onAfterConvert " + document);
return entity;
}

View File

@@ -65,14 +65,24 @@ 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) {}
if (event instanceof AfterSaveEvent) {}
if (event instanceof BeforeDeleteEvent) {}
if (event instanceof AfterDeleteEvent) {}
if (LOG.isTraceEnabled()) {
LOG.trace("{} {}", event.getClass().getSimpleName(), event.getSource());
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());
}
}
}

View File

@@ -0,0 +1,28 @@
/*
* 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

@@ -14,28 +14,17 @@
* limitations under the License.
*/
package org.springframework.data.couchbase.domain;
package org.springframework.data.couchbase.core.mapping.event;
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;
import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
/**
* User entity for tests
*
* @author Michael Nitschinger
* @author Michael Reiche
*/
public class ReactiveAfterSaveEvent<E> extends CouchbaseMappingEvent<E> {
@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);
public ReactiveAfterSaveEvent(E source, CouchbaseDocument document) {
super(source, document);
}
}

View File

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

View File

@@ -0,0 +1,87 @@
/*
* 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

@@ -0,0 +1,28 @@
/*
* 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

@@ -0,0 +1,28 @@
/*
* 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

@@ -0,0 +1,30 @@
/*
* 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,6 +60,7 @@ 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,9 +32,8 @@ import org.springframework.util.StringUtils;
*/
public class Meta {
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 enum MetaKey {
EXAMPLE("$example");
private String key;
@@ -43,7 +42,7 @@ public class Meta {
}
}
private final Map<MetaKey, Object> values = new LinkedHashMap<>(2);
private final Map<String, Object> values = new LinkedHashMap<>(2);
public Meta() {}
@@ -69,7 +68,7 @@ public class Meta {
*
* @return
*/
public Iterable<Entry<MetaKey, Object>> values() {
public Iterable<Entry<String, Object>> values() {
return Collections.unmodifiableSet(this.values.entrySet());
}
@@ -79,26 +78,10 @@ public class Meta {
* @param key must not be {@literal null} or empty.
* @param value
*/
public void setValue(String key, @Nullable Object value) {
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);
}
@@ -107,15 +90,11 @@ public class Meta {
@Nullable
@SuppressWarnings("unchecked")
public <T> T getValue(String key) {
return (T) this.values.get(MetaKey.valueOf(key));
}
public <T> T get(MetaKey key) {
private <T> T getValue(String key) {
return (T) this.values.get(key);
}
public <T> T getValue(String key, T defaultValue) {
private <T> T getValue(String key, T defaultValue) {
T value = getValue(key);
return value != null ? value : defaultValue;

View File

@@ -1,425 +0,0 @@
/*
* 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,7 +25,6 @@ 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;
@@ -53,7 +52,6 @@ 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);
@@ -328,22 +326,28 @@ public class Query {
* @return QueryOptions
*/
public QueryOptions buildQueryOptions(QueryOptions options, QueryScanConsistency scanConsistency) {
return OptionsBuilder.buildQueryOptions(this, options, 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;
}
/**
* 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;
public void setMeta(Meta metaAnnotation) {
Meta meta = metaAnnotation;
}
}

View File

@@ -15,13 +15,8 @@
*/
package org.springframework.data.couchbase.core.support;
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;
import org.springframework.data.couchbase.core.ReactiveCouchbaseTemplate;
public class PseudoArgs<OPTS> {
private final OPTS options;
@@ -40,32 +35,24 @@ public class PseudoArgs<OPTS> {
* 2) values from dynamic proxy (via template threadLocal)<br>
* 3) the values from the couchbaseClientFactory<br>
*
* @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
* @param template to hold
* @param scope
* @param collection
* @param options
*/
public PseudoArgs(ReactiveCouchbaseTemplate template, String scope, String collection, OPTS options,
Class<?> domainType) {
public PseudoArgs(ReactiveCouchbaseTemplate template, String scope, String collection, OPTS options) {
String scopeForQuery = null;
String collectionForQuery = null;
OPTS optionsForQuery = null;
// 1) values from the args (fluent api)
// 1) repository from DynamicProxy via template threadLocal - has precedence over annotation
String scopeForQuery = scope;
String collectionForQuery = collection;
OPTS optionsForQuery = options;
PseudoArgs<OPTS> threadLocal = (PseudoArgs<OPTS>) template.getPseudoArgs();
template.setPseudoArgs(null);
if (threadLocal != null) {
scopeForQuery = threadLocal.getScope();
collectionForQuery = threadLocal.getCollection();
optionsForQuery = threadLocal.getOptions();
}
// 2) from DynamicProxy via template threadLocal
scopeForQuery = fromFirst(null, scopeForQuery, scope, getScopeFrom(domainType));
collectionForQuery = fromFirst(null, collectionForQuery, collection, getCollectionFrom(domainType));
optionsForQuery = fromFirst(null, options, optionsForQuery);
scopeForQuery = scopeForQuery != null ? scopeForQuery : getThreadLocalScopeName(template);
collectionForQuery = collectionForQuery != null ? collectionForQuery : getThreadLocalCollectionName(template);
optionsForQuery = optionsForQuery != null ? optionsForQuery : getThreadLocalOptions(template);
// if a collection was specified but no scope, use the scope from the clientFactory
@@ -75,42 +62,56 @@ 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)) {
if (collectionForQuery == null || CollectionIdentifier.DEFAULT_COLLECTION.equals(collectionForQuery)) {
collectionForQuery = null;
scopeForQuery = null;
}
if ((scopeForQuery == null || CollectionIdentifier.DEFAULT_SCOPE.equals(scopeForQuery))
&& (collectionForQuery == null || CollectionIdentifier.DEFAULT_COLLECTION.equals(collectionForQuery))) {
scopeForQuery = null;
collectionForQuery = 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;
}
@Override
public String toString() {
return "scope: " + getScope() + " collection: " + getCollection() + " options: " + getOptions();
/**
* @@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();
}
}

View File

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

View File

@@ -1,78 +0,0 @@
/*
* 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

@@ -1,11 +1,11 @@
/*
* Copyright 2012-2021 the original author or authors
* Copyright 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
* 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,8 +15,6 @@
*/
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;
@@ -26,21 +24,13 @@ 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, ElementType.TYPE })
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
@Documented
@QueryAnnotation
public @interface Scope {
/**
* Specifies the scope name
*
* @return the scope name configured, defaults to DEFAULT_SCOPE.
*/
String value() default DEFAULT_SCOPE;
public @interface Meta {
}

View File

@@ -1,117 +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.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-2021 the original author or authors.
* Copyright 2017-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.
@@ -15,8 +15,6 @@
*/
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;
@@ -24,12 +22,9 @@ 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-2021 the original author or authors
* 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.
@@ -24,13 +24,8 @@ 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, ElementType.TYPE })
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
@Documented
public @interface ScanConsistency {

View File

@@ -1,18 +1,3 @@
/*
* 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;
@@ -28,6 +13,7 @@ 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;
/**
@@ -96,6 +82,17 @@ 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-2021 the original author or authors
* Copyright 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.
@@ -15,6 +15,7 @@
*/
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;
@@ -59,11 +60,10 @@ 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();
ExecutableFindByQuery<?> findOp = operations.findByQuery(type);
findOp = (ExecutableFindByQuery<?>) (findOp.inScope(method.getScope()).inCollection(method.getCollection()));
this.findOperationWithProjection = findOp;
this.findOperationWithProjection = operations.findByQuery(type);
}
/**
@@ -80,14 +80,18 @@ public abstract class AbstractCouchbaseQuery extends AbstractCouchbaseQueryBase<
ParametersParameterAccessor accessor, @Nullable Class<?> typeToRead) {
Query query = createQuery(accessor);
// query = applyAnnotatedCollationIfPresent(query, accessor); // not yet implemented
query = applyQueryMetaAttributesIfPresent(query, typeToRead);
ExecutableFindByQuery<?> find = findOperationWithProjection;
query = applyAnnotatedConsistencyIfPresent(query);
// query = applyAnnotatedCollationIfPresent(query, accessor); // not yet implemented
ExecutableFindByQuery<?> find = typeToRead == null ? findOperationWithProjection //
: findOperationWithProjection; // not yet implemented in core .as(typeToRead);
String collection = null;
CouchbaseQueryExecution execution = getExecution(accessor,
new ResultProcessingConverter<>(processor, getOperations(), getInstantiators()), find);
return execution.execute(query, processor.getReturnedType().getDomainType(), null);
return execution.execute(query, processor.getReturnedType().getDomainType(), collection);
}
/**
@@ -110,7 +114,8 @@ 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());
@@ -118,8 +123,6 @@ 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()) {
@@ -137,4 +140,18 @@ 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,14 +146,18 @@ public abstract class AbstractCouchbaseQueryBase<CouchbaseOperationsType> implem
ParametersParameterAccessor accessor, @Nullable Class<?> typeToRead);
/**
* Apply Meta annotation to query
* Add a scan consistency from {@link org.springframework.data.couchbase.repository.ScanConsistency} to the given
* {@link Query} if present.
*
* @param query must not be {@literal null}.
* @return Query
* @param query the {@link Query} to potentially apply the sort to.
* @return the query with potential scan consistency applied.
* @since 4.1
*/
Query applyQueryMetaAttributesIfPresent(Query query, Class<?> typeToRead) {
query.setMeta(getQueryMethod(), typeToRead);
return query;
Query applyAnnotatedConsistencyIfPresent(Query query) {
if (!method.hasScanConsistencyAnnotation()) {
return query;
}
return query.scanConsistency(method.getScanConsistencyAnnotation().query());
}
/**

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2020-2021 the original author or authors.
* Copyright 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.
@@ -15,6 +15,7 @@
*/
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;
@@ -62,9 +63,7 @@ public abstract class AbstractReactiveCouchbaseQuery extends AbstractCouchbaseQu
EntityMetadata<?> metadata = method.getEntityInformation();
Class<?> type = metadata.getJavaType();
ReactiveFindByQuery<?> findOp = operations.findByQuery(type);
findOp = (ReactiveFindByQuery<?>) (findOp.inScope(method.getScope()).inCollection(method.getCollection()));
this.findOperationWithProjection = findOp;
this.findOperationWithProjection = operations.findByQuery(type);
}
/**
@@ -80,14 +79,18 @@ 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 = findOperationWithProjection;
ReactiveFindByQuery<?> find = typeToRead == null //
? findOperationWithProjection //
: findOperationWithProjection; // note yet implemented in core .as(typeToRead);
String collection = null;
ReactiveCouchbaseQueryExecution execution = getExecution(accessor,
new ResultProcessingConverter<>(processor, getOperations(), getInstantiators()), find);
return execution.execute(query, processor.getReturnedType().getDomainType(), null);
return execution.execute(query, processor.getReturnedType().getDomainType(), collection);
}
/**
@@ -118,8 +121,6 @@ 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()) {
@@ -132,4 +133,18 @@ 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-2021 the original author or authors.
* Copyright 2013-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.
@@ -16,33 +16,28 @@
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.Collection;
import org.springframework.data.couchbase.repository.Meta;
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.
@@ -55,13 +50,13 @@ import com.couchbase.client.core.io.CollectionIdentifier;
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;
}
/**
@@ -181,46 +176,39 @@ public class CouchbaseQueryMethod extends QueryMethod {
* @return the @ScanConsistency annotation
*/
public ScanConsistency getScanConsistencyAnnotation() {
AnnotatedElement[] annotated = new AnnotatedElement[] { method, method.getDeclaringClass(),
repositoryMetadata.getRepositoryInterface(), repositoryMetadata.getDomainType() };
return OptionsBuilder.annotation(ScanConsistency.class, "query", CollectionIdentifier.DEFAULT_COLLECTION,
annotated);
return method.getAnnotation(ScanConsistency.class);
}
/**
* Caution: findMergedAnnotation() will return the default if there are any annotations but not this annotation
*
* @return annotation
* @return return true if {@link Meta} annotation is available.
*/
public <A extends Annotation> A getAnnotation(Class<A> annotationClass) {
return AnnotatedElementUtils.findMergedAnnotation(method, annotationClass);
public boolean hasQueryMetaAttributes() {
return getMetaAnnotation() != null;
}
/**
* Caution: findMergedAnnotation() will return the default if there are any annotations but not this annotation
*
* @return annotation
* @return return {@link Meta} annotation
*/
public <A extends Annotation> A getClassAnnotation(Class<A> annotationClass) {
return AnnotatedElementUtils.findMergedAnnotation(method.getDeclaringClass(), annotationClass);
private Meta getMetaAnnotation() {
return method.getAnnotation(Meta.class);
}
/**
* Caution: findMergedAnnotation() will return the default if there are any annotations but not this annotation
*
* @return annotation
* Returns the {@link org.springframework.data.couchbase.core.query.Meta} attributes to be applied.
*
* @return never {@literal null}.
*/
public <A extends Annotation> A getEntityAnnotation(Class<A> annotationClass) {
return AnnotatedElementUtils.findMergedAnnotation(getEntityInformation().getJavaType(), annotationClass);
}
@Nullable
public org.springframework.data.couchbase.core.query.Meta getQueryMetaAttributes() {
/**
* 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);
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;
}
/**
@@ -276,18 +264,4 @@ 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 applyQueryMetaAttributesIfPresent(createQuery(accessor), null);
return applyQueryMetaAttributesWhenPresent(createQuery(accessor));
}
/*

View File

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

View File

@@ -22,6 +22,7 @@ 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;
@@ -29,7 +30,11 @@ 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;
@@ -126,9 +131,8 @@ public class StringBasedN1qlQueryParser {
this.statement = statement;
this.queryMethod = queryMethod;
this.couchbaseConverter = couchbaseConverter;
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.statementContext = createN1qlSpelValues(bucketName, null, null, null, typeField, typeValue, false, null);
this.countContext = createN1qlSpelValues(bucketName, null, null, null, typeField, typeValue, true, null);
this.parsedExpression = getExpression(accessor, getParameters(accessor), null, parser, evaluationContextProvider);
checkPlaceholders(this.parsedExpression.toString());
}

View File

@@ -1,123 +0,0 @@
/*
* 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, metadata.getRepositoryInterface());
couchbaseOperations);
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,8 +31,4 @@ 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.reflect.AnnotatedElement;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Set;
@@ -28,10 +28,7 @@ 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;
@@ -40,9 +37,6 @@ 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
@@ -102,10 +96,8 @@ 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));
}
@@ -156,7 +148,7 @@ class CrudMethodMetadataPostProcessor implements RepositoryProxyPostProcessor, B
if (methodMetadata == null) {
methodMetadata = new DefaultCrudMethodMetadata(method, repositoryInformation);
methodMetadata = new DefaultCrudMethodMetadata(method);
CrudMethodMetadata tmp = metadataCache.putIfAbsent(method, methodMetadata);
if (tmp != null) {
@@ -169,10 +161,10 @@ class CrudMethodMetadataPostProcessor implements RepositoryProxyPostProcessor, B
try {
return invocation.proceed();
} finally {
// TransactionSynchronizationManager.unbindResource(method);
TransactionSynchronizationManager.unbindResource(method);
}
} finally {
// currentInvocation.set(oldInvocation);
currentInvocation.set(oldInvocation);
}
}
}
@@ -187,38 +179,23 @@ 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}. 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.
*
* Creates a new {@link DefaultCrudMethodMetadata} for the given {@link Method}.
*
* @param method must not be {@literal null}.
*/
DefaultCrudMethodMetadata(Method method, RepositoryInformation repositoryInformation) {
DefaultCrudMethodMetadata(Method method) {
Assert.notNull(method, "Method must not be null!");
this.method = method;
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;
}
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);
ScanConsistency scanConsistency = null;
for (Annotation ann : method.getAnnotations()) {
if (ann instanceof ScanConsistency) {
scanConsistency = ((ScanConsistency) ann);
}
}
this.scanConsistency = scanConsistency;
}
/*
@@ -234,17 +211,6 @@ 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

@@ -1,123 +0,0 @@
/*
* 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-2021 the original author or authors.
* Copyright 2017-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.
@@ -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, "couchbaseOperationsMapping");
Assert.notNull(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, metadata.getRepositoryInterface());
couchbaseOperations);
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-2021 the original author or authors.
* Copyright 2013-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.
@@ -44,27 +44,34 @@ import com.couchbase.client.java.query.QueryScanConsistency;
* @author Michael Nitschinger
* @author Mark Paluch
* @author Jens Schauder
* @author Michael Reiche
*/
public class SimpleCouchbaseRepository<T, ID> extends CouchbaseRepositoryBase<T, ID>
implements CouchbaseRepository<T, ID> {
public class SimpleCouchbaseRepository<T, ID> implements CouchbaseRepository<T, ID> {
/**
* Holds the reference to the {@link org.springframework.data.couchbase.core.CouchbaseTemplate}.
*/
private final CouchbaseOperations operations;
private final CouchbaseOperations couchbaseOperations;
/**
* Contains information about the entity being used in this repository.
*/
private final CouchbaseEntityInformation<T, String> entityInformation;
private CrudMethodMetadata crudMethodMetadata;
/**
* 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, Class<?> repositoryInterface) {
super(entityInformation, repositoryInterface);
this.operations = couchbaseOperations;
CouchbaseOperations couchbaseOperations) {
Assert.notNull(entityInformation, "CouchbaseEntityInformation must not be null!");
Assert.notNull(couchbaseOperations, "CouchbaseOperations must not be null!");
this.entityInformation = entityInformation;
this.couchbaseOperations = couchbaseOperations;
}
@Override
@@ -72,13 +79,11 @@ public class SimpleCouchbaseRepository<T, ID> extends CouchbaseRepositoryBase<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()
S result;
if (hasNonZeroVersionProperty(entity, operations.getConverter())) {
result = (S) operations.replaceById(getJavaType()).inScope(getScope()).inCollection(getCollection()).one(entity);
if (hasNonZeroVersionProperty(entity, couchbaseOperations.getConverter())) {
return (S) couchbaseOperations.replaceById(entityInformation.getJavaType()).one(entity);
} else {
result = (S) operations.upsertById(getJavaType()).inScope(getScope()).inCollection(getCollection()).one(entity);
return (S) couchbaseOperations.upsertById(entityInformation.getJavaType()).one(entity);
}
return result;
}
@Override
@@ -90,61 +95,57 @@ public class SimpleCouchbaseRepository<T, ID> extends CouchbaseRepositoryBase<T,
@Override
public Optional<T> findById(ID id) {
Assert.notNull(id, "The given id must not be null!");
return Optional.ofNullable(
operations.findById(getJavaType()).inScope(getScope()).inCollection(getCollection()).one(id.toString()));
return Optional.ofNullable(couchbaseOperations.findById(entityInformation.getJavaType()).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 = operations.findById(getJavaType()).inScope(getScope()).inCollection(getCollection())
.all(convertedIds);
Collection<? extends T> all = couchbaseOperations.findById(entityInformation.getJavaType()).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 operations.existsById(getJavaType()).inScope(getScope()).inCollection(getCollection()).one(id.toString());
return couchbaseOperations.existsById().one(id.toString());
}
@Override
public void deleteById(ID id) {
Assert.notNull(id, "The given id must not be null!");
operations.removeById(getJavaType()).inScope(getScope()).inCollection(getCollection()).one(id.toString());
couchbaseOperations.removeById().one(id.toString());
}
@Override
public void delete(T entity) {
Assert.notNull(entity, "Entity must not be null!");
operations.removeById(getJavaType()).inScope(getScope()).inCollection(getCollection()).one(getId(entity));
couchbaseOperations.removeById().one(entityInformation.getId(entity));
}
@Override
public void deleteAllById(Iterable<? extends ID> ids) {
Assert.notNull(ids, "The given Iterable of ids must not be null!");
operations.removeById(getJavaType()).inScope(getScope()).inCollection(getCollection())
.all(Streamable.of(ids).map(Objects::toString).toList());
couchbaseOperations.removeById().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!");
operations.removeById(getJavaType()).inScope(getScope()).inCollection(getCollection())
.all(Streamable.of(entities).map(this::getId).toList());
couchbaseOperations.removeById().all(Streamable.of(entities).map(entityInformation::getId).toList());
}
@Override
public long count() {
return operations.findByQuery(getJavaType()).withConsistency(buildQueryScanConsistency()).inScope(getScope())
.inCollection(getCollection()).count();
return couchbaseOperations.findByQuery(entityInformation.getJavaType()).withConsistency(buildQueryScanConsistency())
.count();
}
@Override
public void deleteAll() {
operations.removeByQuery(getJavaType()).withConsistency(buildQueryScanConsistency()).inScope(getScope())
.inCollection(getCollection()).all();
couchbaseOperations.removeByQuery(entityInformation.getJavaType()).withConsistency(buildQueryScanConsistency())
.all();
}
@Override
@@ -168,6 +169,15 @@ public class SimpleCouchbaseRepository<T, ID> extends CouchbaseRepositoryBase<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.
*
@@ -175,13 +185,25 @@ public class SimpleCouchbaseRepository<T, ID> extends CouchbaseRepositoryBase<T,
* @return the list of found entities, already executed.
*/
private List<T> findAll(Query query) {
return operations.findByQuery(getJavaType()).withConsistency(buildQueryScanConsistency()).inScope(getScope())
.inCollection(getCollection()).matching(query).all();
return couchbaseOperations.findByQuery(entityInformation.getJavaType()).withConsistency(buildQueryScanConsistency())
.matching(query).all();
}
@Override
public CouchbaseOperations getOperations() {
return operations;
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;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2017-2021 the original author or authors.
* Copyright 2017-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.
@@ -35,6 +35,8 @@ 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.
*
@@ -44,17 +46,22 @@ import org.springframework.util.Assert;
* @author David Kelly
* @author Douglas Six
* @author Jens Schauder
* @author Michael Reiche
* @since 3.0
*/
public class SimpleReactiveCouchbaseRepository<T, ID> extends CouchbaseRepositoryBase<T, ID>
implements ReactiveCouchbaseRepository<T, ID> {
public class SimpleReactiveCouchbaseRepository<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.
*
@@ -62,8 +69,11 @@ public class SimpleReactiveCouchbaseRepository<T, ID> extends CouchbaseRepositor
* @param operations the reference to the reactive template used.
*/
public SimpleReactiveCouchbaseRepository(CouchbaseEntityInformation<T, String> entityInformation,
ReactiveCouchbaseOperations operations, Class<?> repositoryInterface) {
super(entityInformation, repositoryInterface);
ReactiveCouchbaseOperations operations) {
Assert.notNull(operations, "ReactiveCouchbaseOperations must not be null!");
Assert.notNull(entityInformation, "CouchbaseEntityInformation must not be null!");
this.entityInformation = entityInformation;
this.operations = operations;
}
@@ -71,16 +81,12 @@ public class SimpleReactiveCouchbaseRepository<T, ID> extends CouchbaseRepositor
@Override
public <S extends T> Mono<S> save(S entity) {
Assert.notNull(entity, "Entity must not be null!");
// if entity has non-null, non-zero version property, then replace()
Mono<S> result;
// if entity has non-null version property, then replace()
if (hasNonZeroVersionProperty(entity, operations.getConverter())) {
result = (Mono<S>) operations.replaceById(getJavaType()).inScope(getScope()).inCollection(getCollection())
.one(entity);
return (Mono<S>) operations.replaceById(entityInformation.getJavaType()).one(entity);
} else {
result = (Mono<S>) operations.upsertById(getJavaType()).inScope(getScope()).inCollection(getCollection())
.one(entity);
return (Mono<S>) operations.upsertById(entityInformation.getJavaType()).one(entity);
}
return result;
}
@Override
@@ -91,7 +97,7 @@ public class SimpleReactiveCouchbaseRepository<T, ID> extends CouchbaseRepositor
@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(e -> save(e));
return Flux.fromIterable(entities).flatMap(this::save);
}
@Override
@@ -102,7 +108,7 @@ public class SimpleReactiveCouchbaseRepository<T, ID> extends CouchbaseRepositor
@Override
public Mono<T> findById(ID id) {
return operations.findById(getJavaType()).inScope(getScope()).inCollection(getCollection()).one(id.toString());
return operations.findById(entityInformation.getJavaType()).one(id.toString());
}
@Override
@@ -114,7 +120,7 @@ public class SimpleReactiveCouchbaseRepository<T, ID> extends CouchbaseRepositor
@Override
public Mono<Boolean> existsById(ID id) {
Assert.notNull(id, "The given id must not be null!");
return operations.existsById(getJavaType()).inScope(getScope()).inCollection(getCollection()).one(id.toString());
return operations.existsById().one(id.toString());
}
@Override
@@ -133,8 +139,7 @@ public class SimpleReactiveCouchbaseRepository<T, ID> extends CouchbaseRepositor
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(getJavaType()).inScope(getScope()).inCollection(getCollection())
.all(convertedIds);
return (Flux<T>) operations.findById(entityInformation.getJavaType()).all(convertedIds);
}
@Override
@@ -145,8 +150,7 @@ public class SimpleReactiveCouchbaseRepository<T, ID> extends CouchbaseRepositor
@Override
public Mono<Void> deleteById(ID id) {
return operations.removeById(getJavaType()).inScope(getScope()).inCollection(getCollection()).one(id.toString())
.then();
return operations.removeById().one(id.toString()).then();
}
@Override
@@ -158,20 +162,17 @@ public class SimpleReactiveCouchbaseRepository<T, ID> extends CouchbaseRepositor
@Override
public Mono<Void> delete(T entity) {
Assert.notNull(entity, "Entity must not be null!");
return operations.removeById(getJavaType()).inScope(getScope()).inCollection(getCollection()).one(getId(entity))
.then();
return operations.removeById().one(entityInformation.getId(entity)).then();
}
@Override
public Mono<Void> deleteAllById(Iterable<? extends ID> ids) {
return operations.removeById(getJavaType()).inScope(getScope()).inCollection(getCollection())
.all(Streamable.of(ids).map(Object::toString).toList()).then();
return operations.removeById().all(Streamable.of(ids).map(Object::toString).toList()).then();
}
@Override
public Mono<Void> deleteAll(Iterable<? extends T> entities) {
return operations.removeById(getJavaType()).inScope(getScope()).inCollection(getCollection())
.all(Streamable.of(entities).map(this::getId).toList()).then();
return operations.removeById().all(Streamable.of(entities).map(entityInformation::getId).toList()).then();
}
@Override
@@ -182,24 +183,43 @@ public class SimpleReactiveCouchbaseRepository<T, ID> extends CouchbaseRepositor
@Override
public Mono<Long> count() {
return operations.findByQuery(getJavaType()).withConsistency(buildQueryScanConsistency()).inScope(getScope())
.inCollection(getCollection()).count();
return operations.findByQuery(entityInformation.getJavaType()).withConsistency(buildQueryScanConsistency()).count();
}
@Override
public Mono<Void> deleteAll() {
return operations.removeByQuery(getJavaType()).withConsistency(buildQueryScanConsistency()).inScope(getScope())
.inCollection(getCollection()).all().then();
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;
}
private Flux<T> findAll(Query query) {
return operations.findByQuery(getJavaType()).withConsistency(buildQueryScanConsistency()).inScope(getScope())
.inCollection(getCollection()).matching(query).all();
return operations.findByQuery(entityInformation.getJavaType()).withConsistency(buildQueryScanConsistency())
.matching(query).all();
}
@Override
public ReactiveCouchbaseOperations getOperations() {
return operations;
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;
}
}

View File

@@ -31,6 +31,7 @@ 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;
@@ -49,7 +50,6 @@ 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,7 +36,6 @@ 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;
@@ -45,19 +44,18 @@ 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;
@@ -754,51 +752,4 @@ 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,8 +16,7 @@
package org.springframework.data.couchbase.core;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.*;
import java.time.Duration;
import java.util.UUID;

View File

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

View File

@@ -16,33 +16,18 @@
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;
/**
@@ -56,11 +41,8 @@ import com.couchbase.client.java.query.QueryScanConsistency;
* @author Michael Reiche
*/
@Repository
// @Scope("repositoryScope")
// @ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
public interface AirportRepository extends CouchbaseRepository<Airport, String>, DynamicProxyable<AirportRepository> {
public interface AirportRepository extends CouchbaseRepository<Airport, String> {
// override an annotate with REQUEST_PLUS
@Override
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
List<Airport> findAll();
@@ -69,16 +51,11 @@ 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);
@@ -120,42 +97,4 @@ 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,7 +38,6 @@ 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;
@@ -53,10 +52,8 @@ import com.couchbase.client.java.json.JacksonTransformers;
*/
@Configuration
@EnableCouchbaseRepositories
@EnableReactiveCouchbaseRepositories
@EnableCouchbaseAuditing(auditorAwareRef = "auditorAwareRef", dateTimeProviderRef = "dateTimeProviderRef")
@EnableReactiveCouchbaseAuditing(auditorAwareRef = "reactiveAuditorAwareRef",
dateTimeProviderRef = "dateTimeProviderRef")
@EnableCouchbaseAuditing(auditorAwareRef="auditorAwareRef", dateTimeProviderRef="dateTimeProviderRef") // this activates auditing
@EnableReactiveCouchbaseAuditing(auditorAwareRef="reactiveAuditorAwareRef", dateTimeProviderRef="dateTimeProviderRef") // this activates auditing
public class Config extends AbstractCouchbaseConfiguration {
String bucketname = "travel-sample";

View File

@@ -16,6 +16,7 @@
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;
@@ -91,7 +92,7 @@ public interface PersonRepository extends CrudRepository<Person, String> {
<S extends Person> Iterable<S> saveAll(Iterable<S> var1);
Person findById(UUID var1);
Optional<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,8 +41,7 @@ import com.couchbase.client.java.query.QueryScanConsistency;
* @author Michael Reiche
*/
@Repository
public interface ReactiveAirportRepository
extends ReactiveCouchbaseRepository<Airport, String>, DynamicProxyable<ReactiveAirportRepository> {
public interface ReactiveAirportRepository extends ReactiveSortingRepository<Airport, String> {
@Override
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
@@ -58,9 +57,6 @@ public interface ReactiveAirportRepository
@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,7 +28,6 @@ import org.springframework.data.domain.ReactiveAuditorAware;
public class ReactiveNaiveAuditorAware implements ReactiveAuditorAware<String> {
public static final String AUDITOR = "reactive_auditor";
@Override
public Mono<String> getCurrentAuditor() {
return Mono.just(AUDITOR);

View File

@@ -1,63 +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.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

@@ -1,61 +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.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,12 +20,10 @@ 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
@@ -34,7 +32,6 @@ import com.couchbase.client.java.query.QueryScanConsistency;
* @author Michael Reiche
*/
@Repository
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
public interface UserRepository extends CouchbaseRepository<User, String> {
List<User> findByFirstname(String firstname);
@@ -54,5 +51,4 @@ 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-2021 the original author or authors.
* Copyright 2017-2019 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,12 +21,10 @@ 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;
@@ -43,22 +41,21 @@ 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;
@@ -76,14 +73,8 @@ 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;
/**
@@ -105,9 +96,6 @@ public class CouchbaseRepositoryQueryIntegrationTests extends ClusterAwareIntegr
@Autowired CouchbaseTemplate couchbaseTemplate;
String scopeName = "_default";
String collectionName = "_default";
@BeforeEach
public void beforeEach() {
try {
@@ -194,17 +182,12 @@ 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());
airport2 = airportRepository.findByIata(airports.get(0).getIata());
assertEquals(airport2.getId(), vie.getId());
Airport airport2 = airportRepository.findByIata(airports.get(0).getIata());
assertEquals(airport1.getId(), vie.getId());
} finally {
airportRepository.delete(vie);
}
@@ -218,9 +201,7 @@ public class CouchbaseRepositoryQueryIntegrationTests extends ClusterAwareIntegr
vie = airportRepository.save(vie);
List<Airport> airports = couchbaseTemplate.findByQuery(Airport.class)
.withConsistency(QueryScanConsistency.REQUEST_PLUS)
.matching(org.springframework.data.couchbase.core.query.Query
.query(QueryCriteria.where(N1QLExpression.x("_class")).is("airport")))
.all();
.matching(new Query(QueryCriteria.where(N1QLExpression.x("_class")).is("airport"))).all();
assertFalse(airports.isEmpty(), "should have found aiport");
} finally {
airportRepository.delete(vie);
@@ -233,132 +214,18 @@ public class CouchbaseRepositoryQueryIntegrationTests extends ClusterAwareIntegr
try {
vie = new Airport("airports::vie", "vie", "loww");
vie = airportRepository.save(vie);
Airport airport2 = airportRepository.findByIata(vie.getIata());
Airport airport2 = airportRepository.findByIata(Iata.vie);
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);
@@ -366,20 +233,12 @@ 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()));
@@ -526,9 +385,12 @@ 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();
@@ -568,9 +430,7 @@ 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
@@ -603,16 +463,10 @@ 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,8 +73,7 @@ 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,22 +249,6 @@ 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

@@ -1,211 +0,0 @@
/*
* 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

@@ -1,215 +0,0 @@
/*
* 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
-->
<logger name="org.springframework.data.couchbase.core" level="debug"/>"
<looger 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"/>