Scopes and collections for repositories (#1149)
* Add support for scopes and collections for repositories. Adds DynamicProxyable and DynamicInvocationHandler to set scope/collection/options on PseudoArgs when calling operations via repository interfaces. Closes #963. Co-authored-by: mikereiche <michael.reiche@couchbase.com>
This commit is contained in:
committed by
mikereiche
parent
03dcde4cb8
commit
1581712765
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2020 the original author or authors
|
||||
* Copyright 2012-2021 the original author or authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -13,7 +13,6 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.data.couchbase;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
@@ -33,6 +32,9 @@ import com.couchbase.client.java.env.ClusterEnvironment;
|
||||
|
||||
/**
|
||||
* The default implementation of a {@link CouchbaseClientFactory}.
|
||||
*
|
||||
* @author Michael Nitschinger
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
public class SimpleCouchbaseClientFactory implements CouchbaseClientFactory {
|
||||
|
||||
@@ -74,7 +76,7 @@ public class SimpleCouchbaseClientFactory implements CouchbaseClientFactory {
|
||||
|
||||
@Override
|
||||
public CouchbaseClientFactory withScope(final String scopeName) {
|
||||
return new SimpleCouchbaseClientFactory(cluster, bucket.name(), scopeName);
|
||||
return new SimpleCouchbaseClientFactory(cluster, bucket.name(), scopeName != null ? scopeName : getScope().name());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -22,7 +22,6 @@ import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
|
||||
|
||||
@@ -28,7 +28,6 @@ import org.springframework.data.couchbase.core.index.CouchbasePersistentEntityIn
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbaseMappingContext;
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentEntity;
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty;
|
||||
import org.springframework.data.couchbase.core.support.PseudoArgs;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
@@ -107,13 +106,25 @@ public class CouchbaseTemplate implements CouchbaseOperations, ApplicationContex
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public ExecutableRemoveById removeById() {
|
||||
return new ExecutableRemoveByIdOperationSupport(this).removeById();
|
||||
return removeById(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExecutableRemoveById removeById(Class<?> domainType) {
|
||||
return new ExecutableRemoveByIdOperationSupport(this).removeById(domainType);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public ExecutableExistsById existsById() {
|
||||
return new ExecutableExistsByIdOperationSupport(this).existsById();
|
||||
return existsById(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExecutableExistsById existsById(Class<?> domainType) {
|
||||
return new ExecutableExistsByIdOperationSupport(this).existsById(domainType);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
|
||||
package org.springframework.data.couchbase.core;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
@@ -38,9 +40,6 @@ import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Internal encode/decode support for CouchbaseTemplate.
|
||||
*
|
||||
|
||||
@@ -36,8 +36,14 @@ public interface ExecutableExistsByIdOperation {
|
||||
/**
|
||||
* Checks if the document exists in the bucket.
|
||||
*/
|
||||
@Deprecated
|
||||
ExecutableExistsById existsById();
|
||||
|
||||
/**
|
||||
* Checks if the document exists in the bucket.
|
||||
*/
|
||||
ExecutableExistsById existsById(Class<?> domainType);
|
||||
|
||||
/**
|
||||
* Terminating operations invoking the actual execution.
|
||||
*/
|
||||
@@ -78,7 +84,6 @@ public interface ExecutableExistsByIdOperation {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Fluent method to specify the collection.
|
||||
*
|
||||
* @param <T> the entity type to use for the results.
|
||||
|
||||
@@ -32,26 +32,34 @@ public class ExecutableExistsByIdOperationSupport implements ExecutableExistsByI
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public ExecutableExistsById existsById() {
|
||||
return new ExecutableExistsByIdSupport(template, null, null, null);
|
||||
return existsById(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExecutableExistsById existsById(Class<?> domainType) {
|
||||
return new ExecutableExistsByIdSupport(template, domainType, null, null, null);
|
||||
}
|
||||
|
||||
static class ExecutableExistsByIdSupport implements ExecutableExistsById {
|
||||
|
||||
private final CouchbaseTemplate template;
|
||||
private final Class<?> domainType;
|
||||
private final String scope;
|
||||
private final String collection;
|
||||
private final ExistsOptions options;
|
||||
|
||||
private final ReactiveExistsByIdSupport reactiveSupport;
|
||||
|
||||
ExecutableExistsByIdSupport(final CouchbaseTemplate template, final String scope, final String collection,
|
||||
final ExistsOptions options) {
|
||||
ExecutableExistsByIdSupport(final CouchbaseTemplate template, final Class<?> domainType, final String scope,
|
||||
final String collection, final ExistsOptions options) {
|
||||
this.template = template;
|
||||
this.domainType = domainType;
|
||||
this.scope = scope;
|
||||
this.collection = collection;
|
||||
this.options = options;
|
||||
this.reactiveSupport = new ReactiveExistsByIdSupport(template.reactive(), scope, collection, options);
|
||||
this.reactiveSupport = new ReactiveExistsByIdSupport(template.reactive(), domainType, scope, collection, options);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -66,20 +74,18 @@ public class ExecutableExistsByIdOperationSupport implements ExecutableExistsByI
|
||||
|
||||
@Override
|
||||
public ExistsByIdWithOptions inCollection(final String collection) {
|
||||
Assert.hasText(collection, "Collection must not be null nor empty.");
|
||||
return new ExecutableExistsByIdSupport(template, scope, collection, options);
|
||||
return new ExecutableExistsByIdSupport(template, domainType, scope, collection, options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TerminatingExistsById withOptions(final ExistsOptions options) {
|
||||
Assert.notNull(options, "Options must not be null.");
|
||||
return new ExecutableExistsByIdSupport(template, scope, collection, options);
|
||||
return new ExecutableExistsByIdSupport(template, domainType, scope, collection, options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExistsByIdInCollection inScope(final String scope) {
|
||||
Assert.hasText(scope, "Scope must not be null nor empty.");
|
||||
return new ExecutableExistsByIdSupport(template, scope, collection, options);
|
||||
return new ExecutableExistsByIdSupport(template, domainType, scope, collection, options);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -97,14 +97,12 @@ public class ExecutableFindByAnalyticsOperationSupport implements ExecutableFind
|
||||
|
||||
@Override
|
||||
public FindByAnalyticsInCollection<T> inScope(final String scope) {
|
||||
Assert.hasText(scope, "Scope must not be null nor empty.");
|
||||
return new ExecutableFindByAnalyticsSupport<>(template, domainType, returnType, query, scanConsistency, scope,
|
||||
collection, options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FindByAnalyticsWithConsistency<T> inCollection(final String collection) {
|
||||
Assert.hasText(collection, "Collection must not be null nor empty.");
|
||||
return new ExecutableFindByAnalyticsSupport<>(template, domainType, returnType, query, scanConsistency, scope,
|
||||
collection, options);
|
||||
}
|
||||
|
||||
@@ -77,19 +77,17 @@ public class ExecutableFindByIdOperationSupport implements ExecutableFindByIdOpe
|
||||
|
||||
@Override
|
||||
public FindByIdWithOptions<T> inCollection(final String collection) {
|
||||
Assert.hasText(collection, "Collection must not be null nor empty.");
|
||||
return new ExecutableFindByIdSupport<>(template, domainType, scope, collection, options, fields);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FindByIdInCollection<T> inScope(final String scope) {
|
||||
Assert.hasText(scope, "Scope must not be null nor empty.");
|
||||
return new ExecutableFindByIdSupport<>(template, domainType, scope, collection, options, fields);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FindByIdInScope<T> project(String... fields) {
|
||||
Assert.notEmpty(fields, "Fields must not be null nor empty.");
|
||||
Assert.notEmpty(fields, "Fields must not be null.");
|
||||
return new ExecutableFindByIdSupport<>(template, domainType, scope, collection, options, Arrays.asList(fields));
|
||||
}
|
||||
|
||||
|
||||
@@ -68,7 +68,8 @@ public class ExecutableFindByQueryOperationSupport implements ExecutableFindByQu
|
||||
this.returnType = returnType;
|
||||
this.query = query;
|
||||
this.reactiveSupport = new ReactiveFindByQuerySupport<T>(template.reactive(), domainType, returnType, query,
|
||||
scanConsistency, scope, collection, options, distinctFields, new NonReactiveSupportWrapper(template.support()));
|
||||
scanConsistency, scope, collection, options, distinctFields,
|
||||
new NonReactiveSupportWrapper(template.support()));
|
||||
this.scanConsistency = scanConsistency;
|
||||
this.scope = scope;
|
||||
this.collection = collection;
|
||||
@@ -126,8 +127,12 @@ public class ExecutableFindByQueryOperationSupport implements ExecutableFindByQu
|
||||
@Override
|
||||
public FindByQueryWithProjection<T> distinct(final String[] distinctFields) {
|
||||
Assert.notNull(distinctFields, "distinctFields must not be null!");
|
||||
// Coming from an annotation, this cannot be null.
|
||||
// But a non-null but empty distinctFields means distinct on all fields
|
||||
// So to indicate do not use distinct, we use {"-"} from the annotation, and here we change it to null.
|
||||
String[] dFields = distinctFields.length == 1 && "-".equals(distinctFields[0]) ? null : distinctFields;
|
||||
return new ExecutableFindByQuerySupport<>(template, domainType, returnType, query, scanConsistency, scope,
|
||||
collection, options, distinctFields);
|
||||
collection, options, dFields);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -154,14 +159,12 @@ public class ExecutableFindByQueryOperationSupport implements ExecutableFindByQu
|
||||
|
||||
@Override
|
||||
public FindByQueryInCollection<T> inScope(final String scope) {
|
||||
Assert.hasText(scope, "Scope must not be null nor empty.");
|
||||
return new ExecutableFindByQuerySupport<>(template, domainType, returnType, query, scanConsistency, scope,
|
||||
collection, options, distinctFields);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FindByQueryWithConsistency<T> inCollection(final String collection) {
|
||||
Assert.hasText(collection, "Collection must not be null nor empty.");
|
||||
return new ExecutableFindByQuerySupport<>(template, domainType, returnType, query, scanConsistency, scope,
|
||||
collection, options, distinctFields);
|
||||
}
|
||||
|
||||
@@ -18,9 +18,9 @@ package org.springframework.data.couchbase.core;
|
||||
import java.util.Collection;
|
||||
|
||||
import org.springframework.data.couchbase.core.ReactiveFindFromReplicasByIdOperationSupport.ReactiveFindFromReplicasByIdSupport;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.couchbase.client.java.kv.GetAnyReplicaOptions;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
public class ExecutableFindFromReplicasByIdOperationSupport implements ExecutableFindFromReplicasByIdOperation {
|
||||
|
||||
@@ -54,7 +54,7 @@ public class ExecutableFindFromReplicasByIdOperationSupport implements Executabl
|
||||
this.options = options;
|
||||
this.returnType = returnType;
|
||||
this.reactiveSupport = new ReactiveFindFromReplicasByIdSupport<>(template.reactive(), domainType, returnType,
|
||||
scope, collection, options, new NonReactiveSupportWrapper(template.support()));
|
||||
scope, collection, options, new NonReactiveSupportWrapper(template.support()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -75,13 +75,11 @@ public class ExecutableFindFromReplicasByIdOperationSupport implements Executabl
|
||||
|
||||
@Override
|
||||
public FindFromReplicasByIdWithOptions<T> inCollection(final String collection) {
|
||||
Assert.hasText(collection, "Collection must not be null nor empty.");
|
||||
return new ExecutableFindFromReplicasByIdSupport<>(template, domainType, returnType, scope, collection, options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FindFromReplicasByIdInCollection<T> inScope(final String scope) {
|
||||
Assert.hasText(scope, "Scope must not be null nor empty.");
|
||||
return new ExecutableFindFromReplicasByIdSupport<>(template, domainType, returnType, scope, collection, options);
|
||||
}
|
||||
|
||||
|
||||
@@ -89,14 +89,12 @@ public class ExecutableInsertByIdOperationSupport implements ExecutableInsertByI
|
||||
|
||||
@Override
|
||||
public InsertByIdInCollection<T> inScope(final String scope) {
|
||||
Assert.hasText(scope, "Scope must not be null nor empty.");
|
||||
return new ExecutableInsertByIdSupport<>(template, domainType, scope, collection, options, persistTo, replicateTo,
|
||||
durabilityLevel, expiry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InsertByIdWithOptions<T> inCollection(final String collection) {
|
||||
Assert.hasText(collection, "Collection must not be null nor empty.");
|
||||
return new ExecutableInsertByIdSupport<>(template, domainType, scope, collection, options, persistTo, replicateTo,
|
||||
durabilityLevel, expiry);
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ package org.springframework.data.couchbase.core;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.couchbase.core.query.WithConsistency;
|
||||
import org.springframework.data.couchbase.core.support.InCollection;
|
||||
import org.springframework.data.couchbase.core.support.InScope;
|
||||
import org.springframework.data.couchbase.core.support.OneAndAllId;
|
||||
@@ -39,6 +38,12 @@ public interface ExecutableRemoveByIdOperation {
|
||||
/**
|
||||
* Removes a document.
|
||||
*/
|
||||
ExecutableRemoveById removeById(Class<?> domainType);
|
||||
|
||||
/**
|
||||
* Removes a document.
|
||||
*/
|
||||
@Deprecated
|
||||
ExecutableRemoveById removeById();
|
||||
|
||||
/**
|
||||
|
||||
@@ -35,14 +35,21 @@ public class ExecutableRemoveByIdOperationSupport implements ExecutableRemoveByI
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public ExecutableRemoveById removeById() {
|
||||
return new ExecutableRemoveByIdSupport(template, null, null, null, PersistTo.NONE, ReplicateTo.NONE,
|
||||
return removeById(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExecutableRemoveById removeById(Class<?> domainType) {
|
||||
return new ExecutableRemoveByIdSupport(template, domainType, null, null, null, PersistTo.NONE, ReplicateTo.NONE,
|
||||
DurabilityLevel.NONE, null);
|
||||
}
|
||||
|
||||
static class ExecutableRemoveByIdSupport implements ExecutableRemoveById {
|
||||
|
||||
private final CouchbaseTemplate template;
|
||||
private final Class<?> domainType;
|
||||
private final String scope;
|
||||
private final String collection;
|
||||
private final RemoveOptions options;
|
||||
@@ -52,18 +59,19 @@ public class ExecutableRemoveByIdOperationSupport implements ExecutableRemoveByI
|
||||
private final Long cas;
|
||||
private final ReactiveRemoveByIdSupport reactiveRemoveByIdSupport;
|
||||
|
||||
ExecutableRemoveByIdSupport(final CouchbaseTemplate template, final String scope, final String collection,
|
||||
final RemoveOptions options, final PersistTo persistTo, final ReplicateTo replicateTo,
|
||||
ExecutableRemoveByIdSupport(final CouchbaseTemplate template, final Class<?> domainType, final String scope,
|
||||
final String collection, final RemoveOptions options, final PersistTo persistTo, final ReplicateTo replicateTo,
|
||||
final DurabilityLevel durabilityLevel, Long cas) {
|
||||
this.template = template;
|
||||
this.domainType = domainType;
|
||||
this.scope = scope;
|
||||
this.collection = collection;
|
||||
this.options = options;
|
||||
this.persistTo = persistTo;
|
||||
this.replicateTo = replicateTo;
|
||||
this.durabilityLevel = durabilityLevel;
|
||||
this.reactiveRemoveByIdSupport = new ReactiveRemoveByIdSupport(template.reactive(), scope, collection, options,
|
||||
persistTo, replicateTo, durabilityLevel, cas);
|
||||
this.reactiveRemoveByIdSupport = new ReactiveRemoveByIdSupport(template.reactive(), domainType, scope, collection,
|
||||
options, persistTo, replicateTo, durabilityLevel, cas);
|
||||
this.cas = cas;
|
||||
}
|
||||
|
||||
@@ -79,15 +87,14 @@ public class ExecutableRemoveByIdOperationSupport implements ExecutableRemoveByI
|
||||
|
||||
@Override
|
||||
public RemoveByIdWithOptions inCollection(final String collection) {
|
||||
Assert.hasText(collection, "Collection must not be null nor empty.");
|
||||
return new ExecutableRemoveByIdSupport(template, scope, collection, options, persistTo, replicateTo,
|
||||
return new ExecutableRemoveByIdSupport(template, domainType, scope, collection, options, persistTo, replicateTo,
|
||||
durabilityLevel, cas);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RemoveByIdInCollection withDurability(final DurabilityLevel durabilityLevel) {
|
||||
Assert.notNull(durabilityLevel, "Durability Level must not be null.");
|
||||
return new ExecutableRemoveByIdSupport(template, scope, collection, options, persistTo, replicateTo,
|
||||
return new ExecutableRemoveByIdSupport(template, domainType, scope, collection, options, persistTo, replicateTo,
|
||||
durabilityLevel, cas);
|
||||
}
|
||||
|
||||
@@ -95,27 +102,26 @@ public class ExecutableRemoveByIdOperationSupport implements ExecutableRemoveByI
|
||||
public RemoveByIdInCollection withDurability(final PersistTo persistTo, final ReplicateTo replicateTo) {
|
||||
Assert.notNull(persistTo, "PersistTo must not be null.");
|
||||
Assert.notNull(replicateTo, "ReplicateTo must not be null.");
|
||||
return new ExecutableRemoveByIdSupport(template, scope, collection, options, persistTo, replicateTo,
|
||||
return new ExecutableRemoveByIdSupport(template, domainType, scope, collection, options, persistTo, replicateTo,
|
||||
durabilityLevel, cas);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TerminatingRemoveById withOptions(final RemoveOptions options) {
|
||||
Assert.notNull(options, "Options must not be null.");
|
||||
return new ExecutableRemoveByIdSupport(template, scope, collection, options, persistTo, replicateTo,
|
||||
return new ExecutableRemoveByIdSupport(template, domainType, scope, collection, options, persistTo, replicateTo,
|
||||
durabilityLevel, cas);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RemoveByIdInCollection inScope(final String scope) {
|
||||
Assert.hasText(scope, "Scope must not be null nor empty.");
|
||||
return new ExecutableRemoveByIdSupport(template, scope, collection, options, persistTo, replicateTo,
|
||||
return new ExecutableRemoveByIdSupport(template, domainType, scope, collection, options, persistTo, replicateTo,
|
||||
durabilityLevel, cas);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RemoveByIdWithDurability withCas(Long cas) {
|
||||
return new ExecutableRemoveByIdSupport(template, scope, collection, options, persistTo, replicateTo,
|
||||
return new ExecutableRemoveByIdSupport(template, domainType, scope, collection, options, persistTo, replicateTo,
|
||||
durabilityLevel, cas);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,10 +19,10 @@ import java.util.List;
|
||||
|
||||
import org.springframework.data.couchbase.core.ReactiveRemoveByQueryOperationSupport.ReactiveRemoveByQuerySupport;
|
||||
import org.springframework.data.couchbase.core.query.Query;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.couchbase.client.java.query.QueryOptions;
|
||||
import com.couchbase.client.java.query.QueryScanConsistency;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
public class ExecutableRemoveByQueryOperationSupport implements ExecutableRemoveByQueryOperation {
|
||||
|
||||
@@ -36,8 +36,7 @@ public class ExecutableRemoveByQueryOperationSupport implements ExecutableRemove
|
||||
|
||||
@Override
|
||||
public <T> ExecutableRemoveByQuery<T> removeByQuery(Class<T> domainType) {
|
||||
return new ExecutableRemoveByQuerySupport<>(template, domainType, ALL_QUERY, null, null,
|
||||
null, null);
|
||||
return new ExecutableRemoveByQuerySupport<>(template, domainType, ALL_QUERY, null, null, null, null);
|
||||
}
|
||||
|
||||
static class ExecutableRemoveByQuerySupport<T> implements ExecutableRemoveByQuery<T> {
|
||||
@@ -90,7 +89,6 @@ public class ExecutableRemoveByQueryOperationSupport implements ExecutableRemove
|
||||
|
||||
@Override
|
||||
public RemoveByQueryWithConsistency<T> inCollection(final String collection) {
|
||||
Assert.hasText(collection, "Collection must not be null nor empty.");
|
||||
return new ExecutableRemoveByQuerySupport<>(template, domainType, query, scanConsistency, scope, collection,
|
||||
options);
|
||||
}
|
||||
@@ -104,7 +102,6 @@ public class ExecutableRemoveByQueryOperationSupport implements ExecutableRemove
|
||||
|
||||
@Override
|
||||
public RemoveByQueryInCollection<T> inScope(final String scope) {
|
||||
Assert.hasText(scope, "Scope must not be null nor empty.");
|
||||
return new ExecutableRemoveByQuerySupport<>(template, domainType, query, scanConsistency, scope, collection,
|
||||
options);
|
||||
}
|
||||
|
||||
@@ -82,7 +82,6 @@ public class ExecutableReplaceByIdOperationSupport implements ExecutableReplaceB
|
||||
|
||||
@Override
|
||||
public ReplaceByIdWithOptions<T> inCollection(final String collection) {
|
||||
Assert.hasText(collection, "Collection must not be null nor empty.");
|
||||
return new ExecutableReplaceByIdSupport<>(template, domainType, scope, collection, options, persistTo,
|
||||
replicateTo, durabilityLevel, expiry);
|
||||
}
|
||||
@@ -118,7 +117,6 @@ public class ExecutableReplaceByIdOperationSupport implements ExecutableReplaceB
|
||||
|
||||
@Override
|
||||
public ReplaceByIdInCollection<T> inScope(final String scope) {
|
||||
Assert.hasText(scope, "Scope must not be null nor empty.");
|
||||
return new ExecutableReplaceByIdSupport<>(template, domainType, scope, collection, options, persistTo,
|
||||
replicateTo, durabilityLevel, expiry);
|
||||
}
|
||||
|
||||
@@ -89,14 +89,12 @@ public class ExecutableUpsertByIdOperationSupport implements ExecutableUpsertByI
|
||||
|
||||
@Override
|
||||
public UpsertByIdInCollection<T> inScope(final String scope) {
|
||||
Assert.hasText(scope, "Scope must not be null nor empty.");
|
||||
return new ExecutableUpsertByIdSupport<>(template, domainType, scope, collection, options, persistTo, replicateTo,
|
||||
durabilityLevel, expiry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UpsertByIdWithOptions<T> inCollection(final String collection) {
|
||||
Assert.hasText(collection, "Collection must not be null nor empty.");
|
||||
return new ExecutableUpsertByIdSupport<>(template, domainType, scope, collection, options, persistTo, replicateTo,
|
||||
durabilityLevel, expiry);
|
||||
}
|
||||
|
||||
@@ -15,11 +15,11 @@
|
||||
*/
|
||||
package org.springframework.data.couchbase.core;
|
||||
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
|
||||
|
||||
import org.springframework.data.couchbase.core.mapping.event.CouchbaseMappingEvent;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
|
||||
import org.springframework.data.couchbase.core.mapping.event.CouchbaseMappingEvent;
|
||||
|
||||
/**
|
||||
* Wrapper of {@link TemplateSupport} methods to adapt them to {@link ReactiveTemplateSupport}.
|
||||
*
|
||||
|
||||
@@ -13,16 +13,17 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.data.couchbase.core;
|
||||
|
||||
import org.springframework.data.couchbase.CouchbaseClientFactory;
|
||||
import org.springframework.data.couchbase.core.convert.CouchbaseConverter;
|
||||
import org.springframework.data.couchbase.core.support.PseudoArgs;
|
||||
|
||||
/**
|
||||
* Defines common operations on the Couchbase data source, most commonly implemented by
|
||||
* {@link ReactiveCouchbaseTemplate}.
|
||||
*
|
||||
* @author Michael Nitschinger
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
public interface ReactiveCouchbaseOperations extends ReactiveFluentCouchbaseOperations {
|
||||
|
||||
@@ -46,9 +47,4 @@ public interface ReactiveCouchbaseOperations extends ReactiveFluentCouchbaseOper
|
||||
*/
|
||||
CouchbaseClientFactory getCouchbaseClientFactory();
|
||||
|
||||
/**
|
||||
* @@return the pseudoArgs from the ThreadLocal field of the CouchbaseOperations
|
||||
*/
|
||||
PseudoArgs<?> getPseudoArgs();
|
||||
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ public class ReactiveCouchbaseTemplate implements ReactiveCouchbaseOperations, A
|
||||
private final CouchbaseConverter converter;
|
||||
private final PersistenceExceptionTranslator exceptionTranslator;
|
||||
private final ReactiveCouchbaseTemplateSupport templateSupport;
|
||||
private ThreadLocal<PseudoArgs<?>> threadLocalArgs = new ThreadLocal<>();
|
||||
private ThreadLocal<PseudoArgs<?>> threadLocalArgs = null;
|
||||
|
||||
public ReactiveCouchbaseTemplate(final CouchbaseClientFactory clientFactory, final CouchbaseConverter converter) {
|
||||
this(clientFactory, converter, new JacksonTranslationService());
|
||||
@@ -64,7 +64,12 @@ public class ReactiveCouchbaseTemplate implements ReactiveCouchbaseOperations, A
|
||||
|
||||
@Override
|
||||
public ReactiveExistsById existsById() {
|
||||
return new ReactiveExistsByIdOperationSupport(this).existsById();
|
||||
return existsById(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReactiveExistsById existsById(Class<?> domainType) {
|
||||
return new ReactiveExistsByIdOperationSupport(this).existsById(domainType);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -89,7 +94,12 @@ public class ReactiveCouchbaseTemplate implements ReactiveCouchbaseOperations, A
|
||||
|
||||
@Override
|
||||
public ReactiveRemoveById removeById() {
|
||||
return new ReactiveRemoveByIdOperationSupport(this).removeById();
|
||||
return removeById(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReactiveRemoveById removeById(Class<?> domainType) {
|
||||
return new ReactiveRemoveByIdOperationSupport(this).removeById(domainType);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -158,11 +168,18 @@ public class ReactiveCouchbaseTemplate implements ReactiveCouchbaseOperations, A
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* @return the pseudoArgs from the ThreadLocal field
|
||||
*/
|
||||
@Override
|
||||
public PseudoArgs<?> getPseudoArgs() {
|
||||
return threadLocalArgs == null ? null : threadLocalArgs.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* set the ThreadLocal field
|
||||
*/
|
||||
public void setPseudoArgs(PseudoArgs<?> threadLocalArgs) {
|
||||
this.threadLocalArgs = new ThreadLocal<>();
|
||||
this.threadLocalArgs.set(threadLocalArgs);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,8 +16,6 @@
|
||||
|
||||
package org.springframework.data.couchbase.core;
|
||||
|
||||
import org.springframework.data.couchbase.core.mapping.event.AfterSaveEvent;
|
||||
import org.springframework.data.couchbase.core.mapping.event.ReactiveAfterSaveEvent;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
@@ -30,11 +28,12 @@ import org.springframework.data.couchbase.core.convert.translation.TranslationSe
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentEntity;
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty;
|
||||
import org.springframework.data.couchbase.core.mapping.event.AfterSaveEvent;
|
||||
import org.springframework.data.couchbase.core.mapping.event.BeforeConvertEvent;
|
||||
import org.springframework.data.couchbase.core.mapping.event.BeforeSaveEvent;
|
||||
import org.springframework.data.couchbase.core.mapping.event.CouchbaseMappingEvent;
|
||||
import org.springframework.data.couchbase.core.mapping.event.ReactiveAfterConvertCallback;
|
||||
import org.springframework.data.couchbase.core.mapping.event.ReactiveBeforeConvertCallback;
|
||||
import org.springframework.data.couchbase.core.mapping.event.ReactiveBeforeConvertEvent;
|
||||
import org.springframework.data.couchbase.core.mapping.event.ReactiveBeforeSaveEvent;
|
||||
import org.springframework.data.couchbase.repository.support.MappingCouchbaseEntityInformation;
|
||||
import org.springframework.data.mapping.PersistentPropertyAccessor;
|
||||
import org.springframework.data.mapping.callback.EntityCallbacks;
|
||||
@@ -68,13 +67,13 @@ class ReactiveCouchbaseTemplateSupport implements ApplicationContextAware, React
|
||||
|
||||
@Override
|
||||
public Mono<CouchbaseDocument> encodeEntity(final Object entityToEncode) {
|
||||
return Mono.just(entityToEncode).doOnNext(entity -> maybeEmitEvent(new ReactiveBeforeConvertEvent<>(entity)))
|
||||
return Mono.just(entityToEncode).doOnNext(entity -> maybeEmitEvent(new BeforeConvertEvent<>(entity)))
|
||||
.flatMap(entity -> maybeCallBeforeConvert(entity, "")).map(maybeNewEntity -> {
|
||||
final CouchbaseDocument converted = new CouchbaseDocument();
|
||||
converter.write(maybeNewEntity, converted);
|
||||
return converted;
|
||||
}).flatMap(converted -> maybeCallAfterConvert(entityToEncode, converted, "").thenReturn(converted))
|
||||
.doOnNext(converted -> maybeEmitEvent(new ReactiveBeforeSaveEvent<>(entityToEncode, converted)));
|
||||
.doOnNext(converted -> maybeEmitEvent(new BeforeSaveEvent<>(entityToEncode, converted)));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -112,7 +111,7 @@ class ReactiveCouchbaseTemplateSupport implements ApplicationContextAware, React
|
||||
} else {
|
||||
returnValue = entity;
|
||||
}
|
||||
maybeEmitEvent(new ReactiveAfterSaveEvent(returnValue, converted));
|
||||
maybeEmitEvent(new AfterSaveEvent(returnValue, converted));
|
||||
return returnValue;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ import org.springframework.data.couchbase.core.support.OneAndAllExistsReactive;
|
||||
import org.springframework.data.couchbase.core.support.WithExistsOptions;
|
||||
|
||||
import com.couchbase.client.java.kv.ExistsOptions;
|
||||
|
||||
/**
|
||||
* Exists Operations
|
||||
*
|
||||
@@ -37,8 +38,14 @@ public interface ReactiveExistsByIdOperation {
|
||||
/**
|
||||
* Checks if the document exists in the bucket.
|
||||
*/
|
||||
@Deprecated
|
||||
ReactiveExistsById existsById();
|
||||
|
||||
/**
|
||||
* Checks if the document exists in the bucket.
|
||||
*/
|
||||
ReactiveExistsById existsById(Class<?> domainType);
|
||||
|
||||
/**
|
||||
* Terminating operations invoking the actual execution.
|
||||
*/
|
||||
|
||||
@@ -25,6 +25,7 @@ import java.util.Map;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.data.couchbase.core.query.OptionsBuilder;
|
||||
import org.springframework.data.couchbase.core.support.PseudoArgs;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@@ -41,20 +42,28 @@ public class ReactiveExistsByIdOperationSupport implements ReactiveExistsByIdOpe
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public ReactiveExistsById existsById() {
|
||||
return new ReactiveExistsByIdSupport(template, null, null, null);
|
||||
return existsById(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReactiveExistsById existsById(Class<?> domainType) {
|
||||
return new ReactiveExistsByIdSupport(template, domainType, null, null, null);
|
||||
}
|
||||
|
||||
static class ReactiveExistsByIdSupport implements ReactiveExistsById {
|
||||
|
||||
private final ReactiveCouchbaseTemplate template;
|
||||
private final Class<?> domainType;
|
||||
private final String scope;
|
||||
private final String collection;
|
||||
private final ExistsOptions options;
|
||||
|
||||
ReactiveExistsByIdSupport(final ReactiveCouchbaseTemplate template, final String scope, final String collection,
|
||||
final ExistsOptions options) {
|
||||
ReactiveExistsByIdSupport(final ReactiveCouchbaseTemplate template, final Class<?> domainType, final String scope,
|
||||
final String collection, final ExistsOptions options) {
|
||||
this.template = template;
|
||||
this.domainType = domainType;
|
||||
this.scope = scope;
|
||||
this.collection = collection;
|
||||
this.options = options;
|
||||
@@ -62,12 +71,12 @@ public class ReactiveExistsByIdOperationSupport implements ReactiveExistsByIdOpe
|
||||
|
||||
@Override
|
||||
public Mono<Boolean> one(final String id) {
|
||||
PseudoArgs<ExistsOptions> pArgs = new PseudoArgs<>(template, scope, collection,
|
||||
options != null ? options : ExistsOptions.existsOptions());
|
||||
LOG.trace("statement: {} scope: {} collection: {}", "exitsById", pArgs.getScope(), pArgs.getCollection());
|
||||
PseudoArgs<ExistsOptions> pArgs = new PseudoArgs<>(template, scope, collection, options, domainType);
|
||||
LOG.trace("existsById {}", pArgs);
|
||||
return Mono.just(id)
|
||||
.flatMap(docId -> template.getCouchbaseClientFactory().withScope(pArgs.getScope())
|
||||
.getCollection(pArgs.getCollection()).reactive().exists(id, pArgs.getOptions()).map(ExistsResult::exists))
|
||||
.getCollection(pArgs.getCollection()).reactive().exists(id, buildOptions(pArgs.getOptions()))
|
||||
.map(ExistsResult::exists))
|
||||
.onErrorMap(throwable -> {
|
||||
if (throwable instanceof RuntimeException) {
|
||||
return template.potentiallyConvertRuntimeException((RuntimeException) throwable);
|
||||
@@ -77,6 +86,10 @@ public class ReactiveExistsByIdOperationSupport implements ReactiveExistsByIdOpe
|
||||
});
|
||||
}
|
||||
|
||||
private ExistsOptions buildOptions(ExistsOptions options) {
|
||||
return OptionsBuilder.buildExistsOptions(options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Map<String, Boolean>> all(final Collection<String> ids) {
|
||||
return Flux.fromIterable(ids).flatMap(id -> one(id).map(result -> Tuples.of(id, result)))
|
||||
@@ -85,20 +98,18 @@ public class ReactiveExistsByIdOperationSupport implements ReactiveExistsByIdOpe
|
||||
|
||||
@Override
|
||||
public ExistsByIdWithOptions inCollection(final String collection) {
|
||||
Assert.hasText(collection, "Collection must not be null nor empty.");
|
||||
return new ReactiveExistsByIdSupport(template, scope, collection, options);
|
||||
return new ReactiveExistsByIdSupport(template, domainType, scope, collection, options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TerminatingExistsById withOptions(final ExistsOptions options) {
|
||||
Assert.notNull(options, "Options must not be null.");
|
||||
return new ReactiveExistsByIdSupport(template, scope, collection, options);
|
||||
return new ReactiveExistsByIdSupport(template, domainType, scope, collection, options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExistsByIdInCollection inScope(final String scope) {
|
||||
Assert.hasText(scope, "Scope must not be null nor empty.");
|
||||
return new ReactiveExistsByIdSupport(template, scope, collection, options);
|
||||
return new ReactiveExistsByIdSupport(template, domainType, scope, collection, options);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -167,14 +167,12 @@ public class ReactiveFindByAnalyticsOperationSupport implements ReactiveFindByAn
|
||||
|
||||
@Override
|
||||
public FindByAnalyticsInCollection<T> inScope(final String scope) {
|
||||
Assert.hasText(scope, "Scope must not be null nor empty.");
|
||||
return new ReactiveFindByAnalyticsSupport<>(template, domainType, returnType, query, scanConsistency, scope,
|
||||
collection, options, support);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FindByAnalyticsWithConsistency<T> inCollection(final String collection) {
|
||||
Assert.hasText(collection, "Collection must not be null nor empty.");
|
||||
return new ReactiveFindByAnalyticsSupport<>(template, domainType, returnType, query, scanConsistency, scope,
|
||||
collection, options, support);
|
||||
}
|
||||
|
||||
@@ -36,7 +36,6 @@ import com.couchbase.client.java.kv.GetOptions;
|
||||
public class ReactiveFindByIdOperationSupport implements ReactiveFindByIdOperation {
|
||||
|
||||
private final ReactiveCouchbaseTemplate template;
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ReactiveFindByIdOperationSupport.class);
|
||||
|
||||
ReactiveFindByIdOperationSupport(ReactiveCouchbaseTemplate template) {
|
||||
@@ -71,19 +70,19 @@ public class ReactiveFindByIdOperationSupport implements ReactiveFindByIdOperati
|
||||
|
||||
@Override
|
||||
public Mono<T> one(final String id) {
|
||||
return Mono.just(id).flatMap(docId -> {
|
||||
GetOptions gOptions = options != null ? options : getOptions();
|
||||
if (gOptions.build().transcoder() == null) {
|
||||
gOptions.transcoder(RawJsonTranscoder.INSTANCE);
|
||||
}
|
||||
if (fields != null && !fields.isEmpty()) {
|
||||
gOptions.project(fields);
|
||||
}
|
||||
PseudoArgs<GetOptions> pArgs = new PseudoArgs(template, scope, collection, gOptions);
|
||||
LOG.trace("statement: {} scope: {} collection: {}", "findById", pArgs.getScope(), pArgs.getCollection());
|
||||
return template.getCouchbaseClientFactory().withScope(pArgs.getScope()).getCollection(pArgs.getCollection())
|
||||
.reactive().get(docId, pArgs.getOptions());
|
||||
}).flatMap(result -> support.decodeEntity(id, result.contentAs(String.class), result.cas(), domainType))
|
||||
GetOptions gOptions = options != null ? options : getOptions();
|
||||
if (gOptions.build().transcoder() == null) {
|
||||
gOptions.transcoder(RawJsonTranscoder.INSTANCE);
|
||||
}
|
||||
if (fields != null && !fields.isEmpty()) {
|
||||
gOptions.project(fields);
|
||||
}
|
||||
PseudoArgs<GetOptions> pArgs = new PseudoArgs(template, scope, collection, gOptions, domainType);
|
||||
LOG.trace("findById {}", pArgs);
|
||||
return Mono.just(id)
|
||||
.flatMap(docId -> template.getCouchbaseClientFactory().withScope(pArgs.getScope())
|
||||
.getCollection(pArgs.getCollection()).reactive().get(docId, pArgs.getOptions()))
|
||||
.flatMap(result -> support.decodeEntity(id, result.contentAs(String.class), result.cas(), domainType))
|
||||
.onErrorResume(throwable -> {
|
||||
if (throwable instanceof RuntimeException) {
|
||||
if (throwable instanceof DocumentNotFoundException) {
|
||||
@@ -113,19 +112,17 @@ public class ReactiveFindByIdOperationSupport implements ReactiveFindByIdOperati
|
||||
|
||||
@Override
|
||||
public FindByIdWithOptions<T> inCollection(final String collection) {
|
||||
Assert.hasText(collection, "Collection must not be null nor empty.");
|
||||
return new ReactiveFindByIdSupport<>(template, domainType, scope, collection, options, fields, support);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FindByIdInCollection<T> inScope(final String scope) {
|
||||
Assert.hasText(scope, "Scope must not be null nor empty.");
|
||||
return new ReactiveFindByIdSupport<>(template, domainType, scope, collection, options, fields, support);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FindByIdInScope<T> project(String... fields) {
|
||||
Assert.notEmpty(fields, "Fields must not be null nor empty.");
|
||||
Assert.notNull(fields, "Fields must not be null");
|
||||
return new ReactiveFindByIdSupport<>(template, domainType, scope, collection, options, Arrays.asList(fields),
|
||||
support);
|
||||
}
|
||||
|
||||
@@ -89,8 +89,6 @@ public interface ReactiveFindByQueryOperation {
|
||||
*/
|
||||
Mono<Boolean> exists();
|
||||
|
||||
QueryOptions buildOptions(QueryOptions options);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -170,7 +168,7 @@ public interface ReactiveFindByQueryOperation {
|
||||
}
|
||||
|
||||
/**
|
||||
* Fluent method to specify scan consistency. Scan consistency may also come from an annotation.
|
||||
* Fluent method to specify scan consistency. Scan consistency may also come from an annotation.
|
||||
*
|
||||
* @param <T> the entity type to use for the results.
|
||||
*/
|
||||
|
||||
@@ -41,7 +41,6 @@ public class ReactiveFindByQueryOperationSupport implements ReactiveFindByQueryO
|
||||
private static final Query ALL_QUERY = new Query();
|
||||
|
||||
private final ReactiveCouchbaseTemplate template;
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ReactiveFindByQueryOperationSupport.class);
|
||||
|
||||
public ReactiveFindByQueryOperationSupport(final ReactiveCouchbaseTemplate template) {
|
||||
@@ -62,11 +61,8 @@ public class ReactiveFindByQueryOperationSupport implements ReactiveFindByQueryO
|
||||
private final Query query;
|
||||
private final QueryScanConsistency scanConsistency;
|
||||
private final String collection;
|
||||
private String scope;
|
||||
private final String scope;
|
||||
private final String[] distinctFields;
|
||||
// this would hold scanConsistency etc. from the fluent api if they were converted from standalone fields
|
||||
// withScope(scopeName) could put raw("query_context",default:<bucket>.<scope>)
|
||||
// this is not the options argument in save( entity, options ). That becomes query.getCouchbaseOptions()
|
||||
private final QueryOptions options;
|
||||
private final ReactiveTemplateSupport support;
|
||||
|
||||
@@ -91,7 +87,8 @@ public class ReactiveFindByQueryOperationSupport implements ReactiveFindByQueryO
|
||||
@Override
|
||||
public FindByQueryWithQuery<T> matching(Query query) {
|
||||
QueryScanConsistency scanCons;
|
||||
if (query.getScanConsistency() != null) {
|
||||
if (query.getScanConsistency() != null) { // redundant, since buildQueryOptions() will use
|
||||
// query.getScanConsistency()
|
||||
scanCons = query.getScanConsistency();
|
||||
} else {
|
||||
scanCons = scanConsistency;
|
||||
@@ -109,14 +106,12 @@ public class ReactiveFindByQueryOperationSupport implements ReactiveFindByQueryO
|
||||
|
||||
@Override
|
||||
public FindByQueryInCollection<T> inScope(final String scope) {
|
||||
Assert.hasText(scope, "Scope must not be null nor empty.");
|
||||
return new ReactiveFindByQuerySupport<>(template, domainType, returnType, query, scanConsistency, scope,
|
||||
collection, options, distinctFields, support);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FindByQueryWithConsistency<T> inCollection(final String collection) {
|
||||
Assert.hasText(collection, "Collection must not be null nor empty.");
|
||||
return new ReactiveFindByQuerySupport<>(template, domainType, returnType, query, scanConsistency, scope,
|
||||
collection, options, distinctFields, support);
|
||||
}
|
||||
@@ -142,10 +137,14 @@ public class ReactiveFindByQueryOperationSupport implements ReactiveFindByQueryO
|
||||
}
|
||||
|
||||
@Override
|
||||
public FindByQueryWithDistinct<T> distinct(String[] distinctFields) {
|
||||
public FindByQueryWithDistinct<T> distinct(final String[] distinctFields) {
|
||||
Assert.notNull(distinctFields, "distinctFields must not be null!");
|
||||
// Coming from an annotation, this cannot be null.
|
||||
// But a non-null but empty distinctFields means distinct on all fields
|
||||
// So to indicate do not use distinct, we use {"-"} from the annotation, and here we change it to null.
|
||||
String[] dFields = distinctFields.length == 1 && "-".equals(distinctFields[0]) ? null : distinctFields;
|
||||
return new ReactiveFindByQuerySupport<>(template, domainType, returnType, query, scanConsistency, scope,
|
||||
collection, options, distinctFields, support);
|
||||
collection, options, dFields, support);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -160,72 +159,65 @@ public class ReactiveFindByQueryOperationSupport implements ReactiveFindByQueryO
|
||||
|
||||
@Override
|
||||
public Flux<T> all() {
|
||||
return Flux.defer(() -> {
|
||||
PseudoArgs<QueryOptions> pArgs = new PseudoArgs(template, scope, collection, options);
|
||||
String statement = assembleEntityQuery(false, distinctFields, pArgs.getCollection());
|
||||
LOG.trace("statement: {} {}", "findByQuery", statement);
|
||||
Mono<ReactiveQueryResult> allResult = pArgs.getScope() == null
|
||||
? template.getCouchbaseClientFactory().getCluster().reactive().query(statement,
|
||||
buildOptions(pArgs.getOptions()))
|
||||
: template.getCouchbaseClientFactory().withScope(pArgs.getScope()).getScope().reactive().query(statement,
|
||||
buildOptions(pArgs.getOptions()));
|
||||
return allResult.onErrorMap(throwable -> {
|
||||
if (throwable instanceof RuntimeException) {
|
||||
return template.potentiallyConvertRuntimeException((RuntimeException) throwable);
|
||||
} else {
|
||||
return throwable;
|
||||
PseudoArgs<QueryOptions> pArgs = new PseudoArgs(template, scope, collection, options, domainType);
|
||||
String statement = assembleEntityQuery(false, distinctFields, pArgs.getCollection());
|
||||
LOG.trace("findByQuery {} statement: {}", pArgs, statement);
|
||||
Mono<ReactiveQueryResult> allResult = pArgs.getScope() == null
|
||||
? template.getCouchbaseClientFactory().getCluster().reactive().query(statement,
|
||||
buildOptions(pArgs.getOptions()))
|
||||
: template.getCouchbaseClientFactory().withScope(pArgs.getScope()).getScope().reactive().query(statement,
|
||||
buildOptions(pArgs.getOptions()));
|
||||
return Flux.defer(() -> allResult.onErrorMap(throwable -> {
|
||||
if (throwable instanceof RuntimeException) {
|
||||
return template.potentiallyConvertRuntimeException((RuntimeException) throwable);
|
||||
} else {
|
||||
return throwable;
|
||||
}
|
||||
}).flatMapMany(ReactiveQueryResult::rowsAsObject).flatMap(row -> {
|
||||
String id = "";
|
||||
long cas = 0;
|
||||
if (distinctFields == null) {
|
||||
if (row.getString(TemplateUtils.SELECT_ID) == null) {
|
||||
return Flux.error(new CouchbaseException(
|
||||
"query did not project " + TemplateUtils.SELECT_ID + ". Either use #{#n1ql.selectEntity} or project "
|
||||
+ TemplateUtils.SELECT_ID + " and " + TemplateUtils.SELECT_CAS + " : " + statement));
|
||||
}
|
||||
}).flatMapMany(ReactiveQueryResult::rowsAsObject).flatMap(row -> {
|
||||
String id = "";
|
||||
long cas = 0;
|
||||
if (distinctFields == null) {
|
||||
if (row.getString(TemplateUtils.SELECT_ID) == null) {
|
||||
return Flux.error(new CouchbaseException(
|
||||
"query did not project " + TemplateUtils.SELECT_ID + ". Either use #{#n1ql.selectEntity} or project "
|
||||
+ TemplateUtils.SELECT_ID + " and " + TemplateUtils.SELECT_CAS + " : " + statement));
|
||||
}
|
||||
id = row.getString(TemplateUtils.SELECT_ID);
|
||||
if (row.getLong(TemplateUtils.SELECT_CAS) == null) {
|
||||
return Flux.error(new CouchbaseException(
|
||||
"query did not project " + TemplateUtils.SELECT_CAS + ". Either use #{#n1ql.selectEntity} or project "
|
||||
+ TemplateUtils.SELECT_ID + " and " + TemplateUtils.SELECT_CAS + " : " + statement));
|
||||
}
|
||||
cas = row.getLong(TemplateUtils.SELECT_CAS);
|
||||
row.removeKey(TemplateUtils.SELECT_ID);
|
||||
row.removeKey(TemplateUtils.SELECT_CAS);
|
||||
id = row.getString(TemplateUtils.SELECT_ID);
|
||||
if (row.getLong(TemplateUtils.SELECT_CAS) == null) {
|
||||
return Flux.error(new CouchbaseException(
|
||||
"query did not project " + TemplateUtils.SELECT_CAS + ". Either use #{#n1ql.selectEntity} or project "
|
||||
+ TemplateUtils.SELECT_ID + " and " + TemplateUtils.SELECT_CAS + " : " + statement));
|
||||
}
|
||||
return support.decodeEntity(id, row.toString(), cas, returnType);
|
||||
});
|
||||
});
|
||||
cas = row.getLong(TemplateUtils.SELECT_CAS);
|
||||
row.removeKey(TemplateUtils.SELECT_ID);
|
||||
row.removeKey(TemplateUtils.SELECT_CAS);
|
||||
}
|
||||
return support.decodeEntity(id, row.toString(), cas, returnType);
|
||||
}));
|
||||
}
|
||||
|
||||
@Override
|
||||
public QueryOptions buildOptions(QueryOptions options) {
|
||||
private QueryOptions buildOptions(QueryOptions options) {
|
||||
QueryOptions opts = query.buildQueryOptions(options, scanConsistency);
|
||||
return opts;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Long> count() {
|
||||
return Mono.defer(() -> {
|
||||
PseudoArgs<QueryOptions> pArgs = new PseudoArgs(template, scope, collection, options);
|
||||
String statement = assembleEntityQuery(true, distinctFields, pArgs.getCollection());
|
||||
LOG.trace("statement: {} {}", "findByQuery", statement);
|
||||
Mono<ReactiveQueryResult> countResult = this.collection == null
|
||||
? template.getCouchbaseClientFactory().getCluster().reactive().query(statement,
|
||||
buildOptions(pArgs.getOptions()))
|
||||
: template.getCouchbaseClientFactory().withScope(pArgs.getScope()).getScope().reactive().query(statement,
|
||||
buildOptions(pArgs.getOptions()));
|
||||
return countResult.onErrorMap(throwable -> {
|
||||
if (throwable instanceof RuntimeException) {
|
||||
return template.potentiallyConvertRuntimeException((RuntimeException) throwable);
|
||||
} else {
|
||||
return throwable;
|
||||
}
|
||||
}).flatMapMany(ReactiveQueryResult::rowsAsObject).map(row -> {
|
||||
return row.getLong(TemplateUtils.SELECT_COUNT);
|
||||
}).next();
|
||||
});
|
||||
PseudoArgs<QueryOptions> pArgs = new PseudoArgs(template, scope, collection, options, domainType);
|
||||
String statement = assembleEntityQuery(true, distinctFields, pArgs.getCollection());
|
||||
LOG.trace("findByQuery {} statement: {}", pArgs, statement);
|
||||
Mono<ReactiveQueryResult> countResult = pArgs.getScope() == null
|
||||
? template.getCouchbaseClientFactory().getCluster().reactive().query(statement,
|
||||
buildOptions(pArgs.getOptions()))
|
||||
: template.getCouchbaseClientFactory().withScope(pArgs.getScope()).getScope().reactive().query(statement,
|
||||
buildOptions(pArgs.getOptions()));
|
||||
return Mono.defer(() -> countResult.onErrorMap(throwable -> {
|
||||
if (throwable instanceof RuntimeException) {
|
||||
return template.potentiallyConvertRuntimeException((RuntimeException) throwable);
|
||||
} else {
|
||||
return throwable;
|
||||
}
|
||||
}).flatMapMany(ReactiveQueryResult::rowsAsObject).map(row -> row.getLong(TemplateUtils.SELECT_COUNT)).next());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -68,16 +68,16 @@ public class ReactiveFindFromReplicasByIdOperationSupport implements ReactiveFin
|
||||
|
||||
@Override
|
||||
public Mono<T> any(final String id) {
|
||||
return Mono.just(id).flatMap(docId -> {
|
||||
GetAnyReplicaOptions garOptions = options != null ? options : getAnyReplicaOptions();
|
||||
if (garOptions.build().transcoder() == null) {
|
||||
garOptions.transcoder(RawJsonTranscoder.INSTANCE);
|
||||
}
|
||||
PseudoArgs<GetAnyReplicaOptions> pArgs = new PseudoArgs<>(template, scope, collection, garOptions);
|
||||
LOG.trace("statement: {} scope: {} collection: {}", "getAnyReplica", pArgs.getScope(), pArgs.getCollection());
|
||||
return template.getCouchbaseClientFactory().withScope(pArgs.getScope()).getCollection(pArgs.getCollection())
|
||||
.reactive().getAnyReplica(docId, pArgs.getOptions());
|
||||
}).flatMap(result -> support.decodeEntity(id, result.contentAs(String.class), result.cas(), returnType))
|
||||
GetAnyReplicaOptions garOptions = options != null ? options : getAnyReplicaOptions();
|
||||
if (garOptions.build().transcoder() == null) {
|
||||
garOptions.transcoder(RawJsonTranscoder.INSTANCE);
|
||||
}
|
||||
PseudoArgs<GetAnyReplicaOptions> pArgs = new PseudoArgs<>(template, scope, collection, garOptions, domainType);
|
||||
LOG.trace("getAnyReplica {}", pArgs);
|
||||
return Mono.just(id)
|
||||
.flatMap(docId -> template.getCouchbaseClientFactory().withScope(pArgs.getScope())
|
||||
.getCollection(pArgs.getCollection()).reactive().getAnyReplica(docId, pArgs.getOptions()))
|
||||
.flatMap(result -> support.decodeEntity(id, result.contentAs(String.class), result.cas(), returnType))
|
||||
.onErrorMap(throwable -> {
|
||||
if (throwable instanceof RuntimeException) {
|
||||
return template.potentiallyConvertRuntimeException((RuntimeException) throwable);
|
||||
@@ -101,14 +101,12 @@ public class ReactiveFindFromReplicasByIdOperationSupport implements ReactiveFin
|
||||
|
||||
@Override
|
||||
public FindFromReplicasByIdWithOptions<T> inCollection(final String collection) {
|
||||
Assert.hasText(collection, "Collection must not be null nor empty.");
|
||||
return new ReactiveFindFromReplicasByIdSupport<>(template, domainType, returnType, scope, collection, options,
|
||||
support);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FindFromReplicasByIdInCollection<T> inScope(final String scope) {
|
||||
Assert.hasText(scope, "Scope must not be null nor empty.");
|
||||
return new ReactiveFindFromReplicasByIdSupport<>(template, domainType, returnType, scope, collection, options,
|
||||
support);
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ import reactor.core.publisher.Mono;
|
||||
import java.time.Duration;
|
||||
import java.util.Collection;
|
||||
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
|
||||
import org.springframework.data.couchbase.core.support.InCollection;
|
||||
import org.springframework.data.couchbase.core.support.InScope;
|
||||
import org.springframework.data.couchbase.core.support.OneAndAllEntityReactive;
|
||||
@@ -68,8 +67,6 @@ public interface ReactiveInsertByIdOperation {
|
||||
@Override
|
||||
Flux<? extends T> all(Collection<? extends T> objects);
|
||||
|
||||
InsertOptions buildOptions(InsertOptions options, CouchbaseDocument doc);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -24,6 +24,7 @@ import java.util.Collection;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
|
||||
import org.springframework.data.couchbase.core.query.OptionsBuilder;
|
||||
import org.springframework.data.couchbase.core.support.PseudoArgs;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@@ -34,8 +35,8 @@ import com.couchbase.client.java.kv.ReplicateTo;
|
||||
|
||||
public class ReactiveInsertByIdOperationSupport implements ReactiveInsertByIdOperation {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ReactiveInsertByIdOperationSupport.class);
|
||||
private final ReactiveCouchbaseTemplate template;
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ReactiveInsertByIdOperationSupport.class);
|
||||
|
||||
public ReactiveInsertByIdOperationSupport(final ReactiveCouchbaseTemplate template) {
|
||||
this.template = template;
|
||||
@@ -78,14 +79,12 @@ public class ReactiveInsertByIdOperationSupport implements ReactiveInsertByIdOpe
|
||||
|
||||
@Override
|
||||
public Mono<T> one(T object) {
|
||||
PseudoArgs<InsertOptions> pArgs = new PseudoArgs(template, scope, collection,
|
||||
options != null ? options : InsertOptions.insertOptions());
|
||||
LOG.trace("statement: {} scope: {} collection: {} options: {}", "insertById", pArgs.getScope(),
|
||||
pArgs.getCollection(), pArgs.getOptions());
|
||||
PseudoArgs<InsertOptions> pArgs = new PseudoArgs(template, scope, collection, options, domainType);
|
||||
LOG.trace("insertById {}", pArgs);
|
||||
return Mono.just(object).flatMap(support::encodeEntity)
|
||||
.flatMap(converted -> template.getCouchbaseClientFactory().withScope(pArgs.getScope())
|
||||
.getCollection(pArgs.getCollection()).reactive()
|
||||
.insert(converted.getId(), converted.export(), buildOptions(pArgs.getOptions(), converted))
|
||||
.flatMap(converted -> template.getCouchbaseClientFactory().withScope(pArgs.getScope())
|
||||
.getCollection(pArgs.getCollection()).reactive()
|
||||
.insert(converted.getId(), converted.export(), buildOptions(pArgs.getOptions(), converted))
|
||||
.flatMap(result -> support.applyUpdatedId(object, converted.getId())
|
||||
.flatMap(updatedObject -> support.applyUpdatedCas(updatedObject, converted, result.cas()))))
|
||||
.onErrorMap(throwable -> {
|
||||
@@ -102,20 +101,8 @@ public class ReactiveInsertByIdOperationSupport implements ReactiveInsertByIdOpe
|
||||
return Flux.fromIterable(objects).flatMap(this::one);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InsertOptions buildOptions(InsertOptions options, CouchbaseDocument doc) { // CouchbaseDocument converted
|
||||
options = options != null ? options : InsertOptions.insertOptions();
|
||||
if (persistTo != PersistTo.NONE || replicateTo != ReplicateTo.NONE) {
|
||||
options.durability(persistTo, replicateTo);
|
||||
} else if (durabilityLevel != DurabilityLevel.NONE) {
|
||||
options.durability(durabilityLevel);
|
||||
}
|
||||
if (expiry != null) {
|
||||
options.expiry(expiry);
|
||||
} else if (doc.getExpiration() != 0) {
|
||||
options.expiry(Duration.ofSeconds(doc.getExpiration()));
|
||||
}
|
||||
return options;
|
||||
return OptionsBuilder.buildInsertOptions(options, persistTo, replicateTo, durabilityLevel, expiry, doc);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -127,14 +114,12 @@ public class ReactiveInsertByIdOperationSupport implements ReactiveInsertByIdOpe
|
||||
|
||||
@Override
|
||||
public InsertByIdInCollection<T> inScope(final String scope) {
|
||||
Assert.hasText(scope, "Scope must not be null nor empty.");
|
||||
return new ReactiveInsertByIdSupport<>(template, domainType, scope, collection, options, persistTo, replicateTo,
|
||||
durabilityLevel, expiry, support);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InsertByIdWithOptions<T> inCollection(final String collection) {
|
||||
Assert.hasText(collection, "Collection must not be null nor empty.");
|
||||
return new ReactiveInsertByIdSupport<>(template, domainType, scope, collection, options, persistTo, replicateTo,
|
||||
durabilityLevel, expiry, support);
|
||||
}
|
||||
|
||||
@@ -40,8 +40,14 @@ public interface ReactiveRemoveByIdOperation {
|
||||
/**
|
||||
* Removes a document.
|
||||
*/
|
||||
@Deprecated
|
||||
ReactiveRemoveById removeById();
|
||||
|
||||
/**
|
||||
* Removes a document.
|
||||
*/
|
||||
ReactiveRemoveById removeById(Class<?> domainType);
|
||||
|
||||
/**
|
||||
* Terminating operations invoking the actual execution.
|
||||
*/
|
||||
@@ -106,6 +112,7 @@ public interface ReactiveRemoveByIdOperation {
|
||||
interface RemoveByIdWithDurability extends RemoveByIdInScope, WithDurability<RemoveResult> {
|
||||
@Override
|
||||
RemoveByIdInCollection withDurability(DurabilityLevel durabilityLevel);
|
||||
|
||||
@Override
|
||||
RemoveByIdInCollection withDurability(PersistTo persistTo, ReplicateTo replicateTo);
|
||||
|
||||
|
||||
@@ -15,13 +15,14 @@
|
||||
*/
|
||||
package org.springframework.data.couchbase.core;
|
||||
|
||||
import org.springframework.data.couchbase.core.mapping.event.ReactiveAfterDeleteEvent;
|
||||
import org.springframework.data.couchbase.core.mapping.event.ReactiveBeforeDeleteEvent;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.data.couchbase.core.query.OptionsBuilder;
|
||||
import org.springframework.data.couchbase.core.support.PseudoArgs;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@@ -33,20 +34,28 @@ import com.couchbase.client.java.kv.ReplicateTo;
|
||||
public class ReactiveRemoveByIdOperationSupport implements ReactiveRemoveByIdOperation {
|
||||
|
||||
private final ReactiveCouchbaseTemplate template;
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ReactiveRemoveByIdOperationSupport.class);
|
||||
|
||||
public ReactiveRemoveByIdOperationSupport(final ReactiveCouchbaseTemplate template) {
|
||||
this.template = template;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public ReactiveRemoveById removeById() {
|
||||
return new ReactiveRemoveByIdSupport(template, null, null, null, PersistTo.NONE, ReplicateTo.NONE,
|
||||
return removeById(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReactiveRemoveById removeById(Class<?> domainType) {
|
||||
return new ReactiveRemoveByIdSupport(template, domainType, null, null, null, PersistTo.NONE, ReplicateTo.NONE,
|
||||
DurabilityLevel.NONE, null);
|
||||
}
|
||||
|
||||
static class ReactiveRemoveByIdSupport implements ReactiveRemoveById {
|
||||
|
||||
private final ReactiveCouchbaseTemplate template;
|
||||
private final Class<?> domainType;
|
||||
private final String scope;
|
||||
private final String collection;
|
||||
private final RemoveOptions options;
|
||||
@@ -55,10 +64,11 @@ public class ReactiveRemoveByIdOperationSupport implements ReactiveRemoveByIdOpe
|
||||
private final DurabilityLevel durabilityLevel;
|
||||
private final Long cas;
|
||||
|
||||
ReactiveRemoveByIdSupport(final ReactiveCouchbaseTemplate template, final String scope, final String collection,
|
||||
final RemoveOptions options, final PersistTo persistTo, final ReplicateTo replicateTo,
|
||||
ReactiveRemoveByIdSupport(final ReactiveCouchbaseTemplate template, final Class<?> domainType, final String scope,
|
||||
final String collection, final RemoveOptions options, final PersistTo persistTo, final ReplicateTo replicateTo,
|
||||
final DurabilityLevel durabilityLevel, Long cas) {
|
||||
this.template = template;
|
||||
this.domainType = domainType;
|
||||
this.scope = scope;
|
||||
this.collection = collection;
|
||||
this.options = options;
|
||||
@@ -70,22 +80,19 @@ public class ReactiveRemoveByIdOperationSupport implements ReactiveRemoveByIdOpe
|
||||
|
||||
@Override
|
||||
public Mono<RemoveResult> one(final String id) {
|
||||
PseudoArgs<RemoveOptions> pArgs = new PseudoArgs(template, scope, collection,
|
||||
options != null ? options : RemoveOptions.removeOptions());
|
||||
return Mono.just(id).map(r -> {
|
||||
template.support().maybeEmitEvent(new ReactiveBeforeDeleteEvent<>(r));
|
||||
return r;
|
||||
}).flatMap(docId -> template.getCouchbaseClientFactory().withScope(pArgs.getScope())
|
||||
.getCollection(pArgs.getCollection()).reactive().remove(id, buildRemoveOptions(pArgs.getOptions())).map(r -> {
|
||||
template.support().maybeEmitEvent(new ReactiveAfterDeleteEvent<>(r));
|
||||
return RemoveResult.from(docId, r);
|
||||
})).onErrorMap(throwable -> {
|
||||
if (throwable instanceof RuntimeException) {
|
||||
return template.potentiallyConvertRuntimeException((RuntimeException) throwable);
|
||||
} else {
|
||||
return throwable;
|
||||
}
|
||||
});
|
||||
PseudoArgs<RemoveOptions> pArgs = new PseudoArgs<>(template, scope, collection, options, domainType);
|
||||
LOG.trace("removeById {}", pArgs);
|
||||
return Mono.just(id)
|
||||
.flatMap(docId -> template.getCouchbaseClientFactory().withScope(pArgs.getScope())
|
||||
.getCollection(pArgs.getCollection()).reactive().remove(id, buildRemoveOptions(pArgs.getOptions()))
|
||||
.map(r -> RemoveResult.from(docId, r)))
|
||||
.onErrorMap(throwable -> {
|
||||
if (throwable instanceof RuntimeException) {
|
||||
return template.potentiallyConvertRuntimeException((RuntimeException) throwable);
|
||||
} else {
|
||||
return throwable;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -94,22 +101,13 @@ public class ReactiveRemoveByIdOperationSupport implements ReactiveRemoveByIdOpe
|
||||
}
|
||||
|
||||
private RemoveOptions buildRemoveOptions(RemoveOptions options) {
|
||||
options = options != null ? options : RemoveOptions.removeOptions();
|
||||
if (persistTo != PersistTo.NONE || replicateTo != ReplicateTo.NONE) {
|
||||
options.durability(persistTo, replicateTo);
|
||||
} else if (durabilityLevel != DurabilityLevel.NONE) {
|
||||
options.durability(durabilityLevel);
|
||||
}
|
||||
if (cas != null) {
|
||||
options.cas(cas);
|
||||
}
|
||||
return options;
|
||||
return OptionsBuilder.buildRemoveOptions(options, persistTo, replicateTo, durabilityLevel, cas);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RemoveByIdInCollection withDurability(final DurabilityLevel durabilityLevel) {
|
||||
Assert.notNull(durabilityLevel, "Durability Level must not be null.");
|
||||
return new ReactiveRemoveByIdSupport(template, scope, collection, options, persistTo, replicateTo,
|
||||
return new ReactiveRemoveByIdSupport(template, domainType, scope, collection, options, persistTo, replicateTo,
|
||||
durabilityLevel, cas);
|
||||
}
|
||||
|
||||
@@ -117,34 +115,32 @@ public class ReactiveRemoveByIdOperationSupport implements ReactiveRemoveByIdOpe
|
||||
public RemoveByIdInCollection withDurability(final PersistTo persistTo, final ReplicateTo replicateTo) {
|
||||
Assert.notNull(persistTo, "PersistTo must not be null.");
|
||||
Assert.notNull(replicateTo, "ReplicateTo must not be null.");
|
||||
return new ReactiveRemoveByIdSupport(template, scope, collection, options, persistTo, replicateTo,
|
||||
return new ReactiveRemoveByIdSupport(template, domainType, scope, collection, options, persistTo, replicateTo,
|
||||
durabilityLevel, cas);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RemoveByIdWithDurability inCollection(final String collection) {
|
||||
Assert.hasText(collection, "Collection must not be null nor empty.");
|
||||
return new ReactiveRemoveByIdSupport(template, scope, collection, options, persistTo, replicateTo,
|
||||
return new ReactiveRemoveByIdSupport(template, domainType, scope, collection, options, persistTo, replicateTo,
|
||||
durabilityLevel, cas);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RemoveByIdInCollection inScope(final String scope) {
|
||||
Assert.hasText(scope, "Scope must not be null nor empty.");
|
||||
return new ReactiveRemoveByIdSupport(template, scope, collection, options, persistTo, replicateTo,
|
||||
return new ReactiveRemoveByIdSupport(template, domainType, scope, collection, options, persistTo, replicateTo,
|
||||
durabilityLevel, cas);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TerminatingRemoveById withOptions(final RemoveOptions options) {
|
||||
Assert.notNull(options, "Options must not be null.");
|
||||
return new ReactiveRemoveByIdSupport(template, scope, collection, options, persistTo, replicateTo,
|
||||
return new ReactiveRemoveByIdSupport(template, domainType, scope, collection, options, persistTo, replicateTo,
|
||||
durabilityLevel, cas);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RemoveByIdWithDurability withCas(Long cas) {
|
||||
return new ReactiveRemoveByIdSupport(template, scope, collection, options, persistTo, replicateTo,
|
||||
return new ReactiveRemoveByIdSupport(template, domainType, scope, collection, options, persistTo, replicateTo,
|
||||
durabilityLevel, cas);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
*/
|
||||
package org.springframework.data.couchbase.core;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@@ -26,6 +25,7 @@ import org.slf4j.LoggerFactory;
|
||||
import org.springframework.data.couchbase.core.query.Query;
|
||||
import org.springframework.data.couchbase.core.support.PseudoArgs;
|
||||
import org.springframework.data.couchbase.core.support.TemplateUtils;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.couchbase.client.java.query.QueryOptions;
|
||||
import com.couchbase.client.java.query.QueryScanConsistency;
|
||||
@@ -36,7 +36,6 @@ public class ReactiveRemoveByQueryOperationSupport implements ReactiveRemoveByQu
|
||||
private static final Query ALL_QUERY = new Query();
|
||||
|
||||
private final ReactiveCouchbaseTemplate template;
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ReactiveRemoveByQueryOperationSupport.class);
|
||||
|
||||
public ReactiveRemoveByQueryOperationSupport(final ReactiveCouchbaseTemplate template) {
|
||||
@@ -45,8 +44,7 @@ public class ReactiveRemoveByQueryOperationSupport implements ReactiveRemoveByQu
|
||||
|
||||
@Override
|
||||
public <T> ReactiveRemoveByQuery<T> removeByQuery(Class<T> domainType) {
|
||||
return new ReactiveRemoveByQuerySupport<>(template, domainType, ALL_QUERY,null, null,
|
||||
null, null);
|
||||
return new ReactiveRemoveByQuerySupport<>(template, domainType, ALL_QUERY, null, null, null, null);
|
||||
}
|
||||
|
||||
static class ReactiveRemoveByQuerySupport<T> implements ReactiveRemoveByQuery<T> {
|
||||
@@ -72,25 +70,23 @@ public class ReactiveRemoveByQueryOperationSupport implements ReactiveRemoveByQu
|
||||
|
||||
@Override
|
||||
public Flux<RemoveResult> all() {
|
||||
return Flux.defer(() -> {
|
||||
PseudoArgs<QueryOptions> pArgs = new PseudoArgs<>(template, scope, collection, options);
|
||||
String statement = assembleDeleteQuery(pArgs.getCollection());
|
||||
LOG.trace("statement: {}", statement);
|
||||
Mono<ReactiveQueryResult> allResult = pArgs.getCollection() == null
|
||||
? template.getCouchbaseClientFactory().getCluster().reactive().query(statement,
|
||||
buildQueryOptions(pArgs.getOptions()))
|
||||
: template.getCouchbaseClientFactory().withScope(pArgs.getScope()).getScope().reactive().query(statement,
|
||||
buildQueryOptions(pArgs.getOptions()));
|
||||
return allResult.onErrorMap(throwable -> {
|
||||
if (throwable instanceof RuntimeException) {
|
||||
return template.potentiallyConvertRuntimeException((RuntimeException) throwable);
|
||||
} else {
|
||||
return throwable;
|
||||
}
|
||||
}).flatMapMany(ReactiveQueryResult::rowsAsObject)
|
||||
.map(row -> new RemoveResult(row.getString(TemplateUtils.SELECT_ID), row.getLong(TemplateUtils.SELECT_CAS),
|
||||
Optional.empty()));
|
||||
});
|
||||
PseudoArgs<QueryOptions> pArgs = new PseudoArgs<>(template, scope, collection, options, domainType);
|
||||
String statement = assembleDeleteQuery(pArgs.getCollection());
|
||||
LOG.trace("removeByQuery {} statement: {}", pArgs, statement);
|
||||
Mono<ReactiveQueryResult> allResult = pArgs.getScope() == null
|
||||
? template.getCouchbaseClientFactory().getCluster().reactive().query(statement,
|
||||
buildQueryOptions(pArgs.getOptions()))
|
||||
: template.getCouchbaseClientFactory().withScope(pArgs.getScope()).getScope().reactive().query(statement,
|
||||
buildQueryOptions(pArgs.getOptions()));
|
||||
return Flux.defer(() -> allResult.onErrorMap(throwable -> {
|
||||
if (throwable instanceof RuntimeException) {
|
||||
return template.potentiallyConvertRuntimeException((RuntimeException) throwable);
|
||||
} else {
|
||||
return throwable;
|
||||
}
|
||||
}).flatMapMany(ReactiveQueryResult::rowsAsObject)
|
||||
.map(row -> new RemoveResult(row.getString(TemplateUtils.SELECT_ID), row.getLong(TemplateUtils.SELECT_CAS),
|
||||
Optional.empty())));
|
||||
}
|
||||
|
||||
private QueryOptions buildQueryOptions(QueryOptions options) {
|
||||
@@ -105,7 +101,6 @@ public class ReactiveRemoveByQueryOperationSupport implements ReactiveRemoveByQu
|
||||
|
||||
@Override
|
||||
public RemoveByQueryWithConsistency<T> inCollection(final String collection) {
|
||||
Assert.hasText(collection, "Collection must not be null nor empty.");
|
||||
return new ReactiveRemoveByQuerySupport<>(template, domainType, query, scanConsistency, scope, collection,
|
||||
options);
|
||||
}
|
||||
@@ -136,7 +131,6 @@ public class ReactiveRemoveByQueryOperationSupport implements ReactiveRemoveByQu
|
||||
|
||||
@Override
|
||||
public RemoveByQueryInCollection<T> inScope(final String scope) {
|
||||
Assert.hasText(scope, "Scope must not be null nor empty.");
|
||||
return new ReactiveRemoveByQuerySupport<>(template, domainType, query, scanConsistency, scope, collection,
|
||||
options);
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import java.util.Collection;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
|
||||
import org.springframework.data.couchbase.core.query.OptionsBuilder;
|
||||
import org.springframework.data.couchbase.core.support.PseudoArgs;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@@ -34,8 +35,8 @@ import com.couchbase.client.java.kv.ReplicateTo;
|
||||
|
||||
public class ReactiveReplaceByIdOperationSupport implements ReactiveReplaceByIdOperation {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ReactiveReplaceByIdOperationSupport.class);
|
||||
private final ReactiveCouchbaseTemplate template;
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ReactiveReplaceByIdOperationSupport.class);
|
||||
|
||||
public ReactiveReplaceByIdOperationSupport(final ReactiveCouchbaseTemplate template) {
|
||||
this.template = template;
|
||||
@@ -78,13 +79,13 @@ public class ReactiveReplaceByIdOperationSupport implements ReactiveReplaceByIdO
|
||||
|
||||
@Override
|
||||
public Mono<T> one(T object) {
|
||||
PseudoArgs<ReplaceOptions> pArgs = new PseudoArgs<>(template, scope, collection,
|
||||
options != null ? options : ReplaceOptions.replaceOptions());
|
||||
LOG.trace("statement: {} pArgs: {}", "replaceById", pArgs);
|
||||
PseudoArgs<ReplaceOptions> pArgs = new PseudoArgs<>(template, scope, collection, options, domainType);
|
||||
LOG.trace("replaceById {}", pArgs);
|
||||
return Mono.just(object).flatMap(support::encodeEntity)
|
||||
.flatMap(converted -> template.getCouchbaseClientFactory().withScope(pArgs.getScope())
|
||||
.getCollection(pArgs.getCollection()).reactive()
|
||||
.replace(converted.getId(), converted.export(), buildReplaceOptions(pArgs.getOptions(), object, converted))
|
||||
.flatMap(converted -> template.getCouchbaseClientFactory().withScope(pArgs.getScope())
|
||||
.getCollection(pArgs.getCollection()).reactive()
|
||||
.replace(converted.getId(), converted.export(),
|
||||
buildReplaceOptions(pArgs.getOptions(), object, converted))
|
||||
.flatMap(result -> support.applyUpdatedCas(object, converted, result.cas())))
|
||||
.onErrorMap(throwable -> {
|
||||
if (throwable instanceof RuntimeException) {
|
||||
@@ -101,20 +102,8 @@ public class ReactiveReplaceByIdOperationSupport implements ReactiveReplaceByIdO
|
||||
}
|
||||
|
||||
private ReplaceOptions buildReplaceOptions(ReplaceOptions options, T object, CouchbaseDocument doc) {
|
||||
options = options != null ? options : ReplaceOptions.replaceOptions();
|
||||
if (persistTo != PersistTo.NONE || replicateTo != ReplicateTo.NONE) {
|
||||
options.durability(persistTo, replicateTo);
|
||||
} else if (durabilityLevel != DurabilityLevel.NONE) {
|
||||
options.durability(durabilityLevel);
|
||||
}
|
||||
if (expiry != null) {
|
||||
options.expiry(expiry);
|
||||
} else if (doc.getExpiration() != 0) {
|
||||
options.expiry(Duration.ofSeconds(doc.getExpiration()));
|
||||
}
|
||||
long cas = support.getCas(object);
|
||||
options.cas(cas);
|
||||
return options;
|
||||
return OptionsBuilder.buildReplaceOptions(options, persistTo, replicateTo, durabilityLevel, expiry,
|
||||
support.getCas(object), doc);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -126,14 +115,12 @@ public class ReactiveReplaceByIdOperationSupport implements ReactiveReplaceByIdO
|
||||
|
||||
@Override
|
||||
public ReplaceByIdWithDurability<T> inCollection(final String collection) {
|
||||
Assert.hasText(collection, "Collection must not be null nor empty.");
|
||||
return new ReactiveReplaceByIdSupport<>(template, domainType, scope, collection, options, persistTo, replicateTo,
|
||||
durabilityLevel, expiry, support);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReplaceByIdInCollection<T> inScope(final String scope) {
|
||||
Assert.hasText(scope, "Scope must not be null nor empty.");
|
||||
return new ReactiveReplaceByIdSupport<>(template, domainType, scope, collection, options, persistTo, replicateTo,
|
||||
durabilityLevel, expiry, support);
|
||||
}
|
||||
|
||||
@@ -15,24 +15,24 @@
|
||||
*/
|
||||
package org.springframework.data.couchbase.core;
|
||||
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
|
||||
|
||||
import org.springframework.data.couchbase.core.mapping.event.CouchbaseMappingEvent;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
|
||||
import org.springframework.data.couchbase.core.mapping.event.CouchbaseMappingEvent;
|
||||
|
||||
public interface ReactiveTemplateSupport {
|
||||
|
||||
Mono<CouchbaseDocument> encodeEntity(Object entityToEncode);
|
||||
Mono<CouchbaseDocument> encodeEntity(Object entityToEncode);
|
||||
|
||||
<T> Mono<T> decodeEntity(String id, String source, long cas, Class<T> entityClass);
|
||||
<T> Mono<T> decodeEntity(String id, String source, long cas, Class<T> entityClass);
|
||||
|
||||
<T> Mono<T> applyUpdatedCas(T entity, CouchbaseDocument converted, long cas);
|
||||
<T> Mono<T> applyUpdatedCas(T entity, CouchbaseDocument converted, long cas);
|
||||
|
||||
<T> Mono<T> applyUpdatedId(T entity, Object id);
|
||||
<T> Mono<T> applyUpdatedId(T entity, Object id);
|
||||
|
||||
Long getCas(Object entity);
|
||||
Long getCas(Object entity);
|
||||
|
||||
String getJavaNameForEntity(Class<?> clazz);
|
||||
String getJavaNameForEntity(Class<?> clazz);
|
||||
|
||||
void maybeEmitEvent(CouchbaseMappingEvent<?> event);
|
||||
void maybeEmitEvent(CouchbaseMappingEvent<?> event);
|
||||
}
|
||||
|
||||
@@ -21,7 +21,10 @@ import reactor.core.publisher.Mono;
|
||||
import java.time.Duration;
|
||||
import java.util.Collection;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
|
||||
import org.springframework.data.couchbase.core.query.OptionsBuilder;
|
||||
import org.springframework.data.couchbase.core.support.PseudoArgs;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@@ -33,6 +36,7 @@ import com.couchbase.client.java.kv.UpsertOptions;
|
||||
public class ReactiveUpsertByIdOperationSupport implements ReactiveUpsertByIdOperation {
|
||||
|
||||
private final ReactiveCouchbaseTemplate template;
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ReactiveUpsertByIdOperationSupport.class);
|
||||
|
||||
public ReactiveUpsertByIdOperationSupport(final ReactiveCouchbaseTemplate template) {
|
||||
this.template = template;
|
||||
@@ -75,12 +79,12 @@ public class ReactiveUpsertByIdOperationSupport implements ReactiveUpsertByIdOpe
|
||||
|
||||
@Override
|
||||
public Mono<T> one(T object) {
|
||||
PseudoArgs<UpsertOptions> pArgs = new PseudoArgs<>(template, scope, collection,
|
||||
options != null ? options : UpsertOptions.upsertOptions());
|
||||
PseudoArgs<UpsertOptions> pArgs = new PseudoArgs(template, scope, collection, options, domainType);
|
||||
LOG.trace("upsertById {}", pArgs);
|
||||
return Mono.just(object).flatMap(support::encodeEntity)
|
||||
.flatMap(converted -> template.getCouchbaseClientFactory().withScope(pArgs.getScope())
|
||||
.getCollection(pArgs.getCollection()).reactive()
|
||||
.upsert(converted.getId(), converted.export(), buildUpsertOptions(pArgs.getOptions(), converted))
|
||||
.flatMap(converted -> template.getCouchbaseClientFactory().withScope(pArgs.getScope())
|
||||
.getCollection(pArgs.getCollection()).reactive()
|
||||
.upsert(converted.getId(), converted.export(), buildUpsertOptions(pArgs.getOptions(), converted))
|
||||
.flatMap(result -> support.applyUpdatedId(object, converted.getId())
|
||||
.flatMap(updatedObject -> support.applyUpdatedCas(updatedObject, converted, result.cas()))))
|
||||
.onErrorMap(throwable -> {
|
||||
@@ -98,18 +102,7 @@ public class ReactiveUpsertByIdOperationSupport implements ReactiveUpsertByIdOpe
|
||||
}
|
||||
|
||||
private UpsertOptions buildUpsertOptions(UpsertOptions options, CouchbaseDocument doc) {
|
||||
options = options != null ? options : UpsertOptions.upsertOptions();
|
||||
if (persistTo != PersistTo.NONE || replicateTo != ReplicateTo.NONE) {
|
||||
options.durability(persistTo, replicateTo);
|
||||
} else if (durabilityLevel != DurabilityLevel.NONE) {
|
||||
options.durability(durabilityLevel);
|
||||
}
|
||||
if (expiry != null) {
|
||||
options.expiry(expiry);
|
||||
} else if (doc.getExpiration() != 0) {
|
||||
options.expiry(Duration.ofSeconds(doc.getExpiration()));
|
||||
}
|
||||
return options;
|
||||
return OptionsBuilder.buildUpsertOptions(options, persistTo, replicateTo, durabilityLevel, expiry, doc);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -121,14 +114,12 @@ public class ReactiveUpsertByIdOperationSupport implements ReactiveUpsertByIdOpe
|
||||
|
||||
@Override
|
||||
public UpsertByIdWithDurability<T> inCollection(final String collection) {
|
||||
Assert.hasText(collection, "Collection must not be null nor empty.");
|
||||
return new ReactiveUpsertByIdSupport<>(template, domainType, scope, collection, options, persistTo, replicateTo,
|
||||
durabilityLevel, expiry, support);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UpsertByIdInCollection<T> inScope(final String scope) {
|
||||
Assert.hasText(scope, "Scope must not be null nor empty.");
|
||||
return new ReactiveUpsertByIdSupport<>(template, domainType, scope, collection, options, persistTo, replicateTo,
|
||||
durabilityLevel, expiry, support);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.core.CollectionFactory;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.support.DefaultConversionService;
|
||||
import org.springframework.data.annotation.Transient;
|
||||
import org.springframework.data.convert.EntityInstantiator;
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbaseList;
|
||||
@@ -532,6 +533,10 @@ public class MappingCouchbaseConverter extends AbstractCouchbaseConverter implem
|
||||
idAttributes.put(order, convertToString(propertyObj));
|
||||
}
|
||||
|
||||
if (prop.isAnnotationPresent(Transient.class)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!conversions.isSimpleType(propertyObj.getClass())) {
|
||||
writePropertyInternal(propertyObj, target, prop, false);
|
||||
} else {
|
||||
|
||||
@@ -21,10 +21,9 @@ import java.util.TimeZone;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.springframework.context.EnvironmentAware;
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.mapping.MappingException;
|
||||
import org.springframework.data.mapping.PersistentProperty;
|
||||
import org.springframework.data.mapping.model.BasicPersistentEntity;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -98,11 +97,14 @@ public class BasicCouchbasePersistentEntity<T> extends BasicPersistentEntity<T,
|
||||
|
||||
@Override
|
||||
public int getExpiry() {
|
||||
Document annotation = getType().getAnnotation(Document.class);
|
||||
return getExpiry(AnnotatedElementUtils.findMergedAnnotation(getType(), Expiry.class), environment);
|
||||
}
|
||||
|
||||
public static int getExpiry(Expiry annotation, Environment environment) {
|
||||
if (annotation == null)
|
||||
return 0;
|
||||
|
||||
int expiryValue = getExpiryValue(annotation);
|
||||
int expiryValue = getExpiryValue(annotation, environment);
|
||||
|
||||
long secondsShift = annotation.expiryUnit().toSeconds(expiryValue);
|
||||
if (secondsShift > TTL_IN_SECONDS_INCLUSIVE_END) {
|
||||
@@ -121,7 +123,7 @@ public class BasicCouchbasePersistentEntity<T> extends BasicPersistentEntity<T,
|
||||
}
|
||||
}
|
||||
|
||||
private int getExpiryValue(Document annotation) {
|
||||
private static int getExpiryValue(Expiry annotation, Environment environment) {
|
||||
int expiryValue = annotation.expiry();
|
||||
String expiryExpressionString = annotation.expiryExpression();
|
||||
if (StringUtils.hasLength(expiryExpressionString)) {
|
||||
|
||||
@@ -23,7 +23,13 @@ import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.springframework.core.annotation.AliasFor;
|
||||
import org.springframework.data.annotation.Persistent;
|
||||
import org.springframework.data.couchbase.repository.Collection;
|
||||
import org.springframework.data.couchbase.repository.ScanConsistency;
|
||||
import org.springframework.data.couchbase.repository.Scope;
|
||||
|
||||
import com.couchbase.client.java.query.QueryScanConsistency;
|
||||
|
||||
/**
|
||||
* Identifies a domain object to be persisted to Couchbase.
|
||||
@@ -35,12 +41,15 @@ import org.springframework.data.annotation.Persistent;
|
||||
@Inherited
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ ElementType.TYPE })
|
||||
@Expiry
|
||||
@ScanConsistency
|
||||
public @interface Document {
|
||||
|
||||
/**
|
||||
* An optional expiry time for the document. Default is no expiry. Only one of two might might be set at the same
|
||||
* time: either {@link #expiry()} or {@link #expiryExpression()}
|
||||
*/
|
||||
@AliasFor(annotation = Expiry.class, attribute = "expiry")
|
||||
int expiry() default 0;
|
||||
|
||||
/**
|
||||
@@ -55,11 +64,13 @@ public @interface Document {
|
||||
* <br />
|
||||
* SpEL is NOT supported.
|
||||
*/
|
||||
@AliasFor(annotation = Expiry.class, attribute = "expiryExpression")
|
||||
String expiryExpression() default "";
|
||||
|
||||
/**
|
||||
* An optional time unit for the document's {@link #expiry()}, if set. Default is {@link TimeUnit#SECONDS}.
|
||||
*/
|
||||
@AliasFor(annotation = Expiry.class, attribute = "expiryUnit")
|
||||
TimeUnit expiryUnit() default TimeUnit.SECONDS;
|
||||
|
||||
/**
|
||||
@@ -68,4 +79,9 @@ public @interface Document {
|
||||
*/
|
||||
boolean touchOnRead() default false;
|
||||
|
||||
/**
|
||||
* An optional string indicating the query scan consistency
|
||||
*/
|
||||
@AliasFor(annotation = ScanConsistency.class, attribute = "query")
|
||||
QueryScanConsistency queryScanConsistency() default QueryScanConsistency.NOT_BOUNDED;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright 2012-2021 the original author or authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.data.couchbase.core.mapping;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.springframework.data.annotation.Persistent;
|
||||
|
||||
/**
|
||||
* Expiry annotation
|
||||
*
|
||||
* @author Michael Nitschinger
|
||||
*/
|
||||
@Persistent
|
||||
@Inherited
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ ElementType.TYPE })
|
||||
public @interface Expiry {
|
||||
|
||||
/**
|
||||
* An optional expiry time for the document. Default is no expiry. Only one of two might might be set at the same
|
||||
* time: either {@link #expiry()} or {@link #expiryExpression()}
|
||||
*/
|
||||
int expiry() default 0;
|
||||
|
||||
/**
|
||||
* Same as {@link #expiry} but allows the actual value to be set using standard Spring property sources mechanism.
|
||||
* Only one might be set at the same time: either {@link #expiry()} or {@link #expiryExpression()}. <br />
|
||||
* Syntax is the same as for {@link org.springframework.core.env.Environment#resolveRequiredPlaceholders(String)}.
|
||||
* <br />
|
||||
* <br />
|
||||
* The value will be recalculated for every {@link org.springframework.data.couchbase.core.CouchbaseTemplate}
|
||||
* save/insert/update call, thus allowing actual expiration to reflect changes on-the-fly as soon as property sources
|
||||
* change. <br />
|
||||
* <br />
|
||||
* SpEL is NOT supported.
|
||||
*/
|
||||
String expiryExpression() default "";
|
||||
|
||||
/**
|
||||
* An optional time unit for the document's {@link #expiry()}, if set. Default is {@link TimeUnit#SECONDS}.
|
||||
*/
|
||||
TimeUnit expiryUnit() default TimeUnit.SECONDS;
|
||||
|
||||
}
|
||||
@@ -50,13 +50,9 @@ public class AbstractCouchbaseEventListener<E> implements ApplicationListener<Co
|
||||
|
||||
if (event instanceof BeforeDeleteEvent) {
|
||||
onBeforeDelete(event.getSource(), event.getDocument());
|
||||
return;
|
||||
} else if (event instanceof AfterDeleteEvent) {
|
||||
onAfterDelete(event.getSource(), event.getDocument());
|
||||
return;
|
||||
}
|
||||
|
||||
if (event instanceof BeforeConvertEvent) {
|
||||
} else if (event instanceof BeforeConvertEvent) {
|
||||
onBeforeConvert(source);
|
||||
} else if (event instanceof BeforeSaveEvent) {
|
||||
onBeforeSave(source, event.getDocument());
|
||||
@@ -66,32 +62,32 @@ public class AbstractCouchbaseEventListener<E> implements ApplicationListener<Co
|
||||
}
|
||||
|
||||
public void onBeforeConvert(E source) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("onBeforeConvert({})", source);
|
||||
if (LOG.isTraceEnabled()) {
|
||||
LOG.trace("onBeforeConvert({})", source);
|
||||
}
|
||||
}
|
||||
|
||||
public void onBeforeSave(E source, CouchbaseDocument doc) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("onBeforeSave({}, {})", source, doc);
|
||||
if (LOG.isTraceEnabled()) {
|
||||
LOG.trace("onBeforeSave({}, {})", source, doc);
|
||||
}
|
||||
}
|
||||
|
||||
public void onAfterSave(E source, CouchbaseDocument doc) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("onAfterSave({}, {})", source, doc);
|
||||
if (LOG.isTraceEnabled()) {
|
||||
LOG.trace("onAfterSave({}, {})", source, doc);
|
||||
}
|
||||
}
|
||||
|
||||
public void onAfterDelete(Object source, CouchbaseDocument doc) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("onAfterConvert({})", doc);
|
||||
if (LOG.isTraceEnabled()) {
|
||||
LOG.trace("onAfterConvert({})", doc);
|
||||
}
|
||||
}
|
||||
|
||||
public void onBeforeDelete(Object source, CouchbaseDocument doc) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("onAfterConvert({})", doc);
|
||||
if (LOG.isTraceEnabled()) {
|
||||
LOG.trace("onAfterConvert({})", doc);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ public class AuditingEntityCallback implements BeforeConvertCallback<Object>, Af
|
||||
*/
|
||||
@Override
|
||||
public Object onBeforeConvert(Object entity, String collection) {
|
||||
//LOG.debug("onBeforeConvert " + entity);
|
||||
// LOG.trace("onBeforeConvert " + entity);
|
||||
return entity; // markAudited called in AuditingEventListener.onApplicationEvent()
|
||||
// auditingHandlerFactory.getObject().markAudited(entity);
|
||||
}
|
||||
@@ -65,7 +65,7 @@ public class AuditingEntityCallback implements BeforeConvertCallback<Object>, Af
|
||||
*/
|
||||
@Override
|
||||
public Object onAfterConvert(Object entity, CouchbaseDocument document, String collection) {
|
||||
//LOG.debug("onAfterConvert " + document);
|
||||
// LOG.trace("onAfterConvert " + document);
|
||||
return entity;
|
||||
}
|
||||
|
||||
|
||||
@@ -65,24 +65,14 @@ public class AuditingEventListener implements ApplicationListener<CouchbaseMappi
|
||||
if (event instanceof BeforeConvertEvent) {
|
||||
Optional.ofNullable(event.getSource())//
|
||||
.ifPresent(it -> auditingHandlerFactory.getObject().markAudited(it));
|
||||
// LOG.info(event.getClass().getSimpleName() + " " + event);
|
||||
}
|
||||
if (event instanceof BeforeSaveEvent) {
|
||||
// LOG.info(event.getClass().getSimpleName() + " " + event);
|
||||
}
|
||||
if (event instanceof AfterSaveEvent) {
|
||||
// LOG.info(event.getClass().getSimpleName() + " " + event);
|
||||
}
|
||||
if (event instanceof BeforeDeleteEvent) {
|
||||
// LOG.info(event.getClass().getSimpleName() + " " + event);
|
||||
}
|
||||
if (event instanceof AfterDeleteEvent) {
|
||||
// LOG.info(event.getClass().getSimpleName() + " " + event);
|
||||
}
|
||||
if (!event.getClass().getSimpleName().startsWith("Reactive")) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug(event.getClass().getSimpleName() + " " + event.getSource());
|
||||
}
|
||||
if (event instanceof BeforeSaveEvent) {}
|
||||
if (event instanceof AfterSaveEvent) {}
|
||||
if (event instanceof BeforeDeleteEvent) {}
|
||||
if (event instanceof AfterDeleteEvent) {}
|
||||
|
||||
if (LOG.isTraceEnabled()) {
|
||||
LOG.trace("{} {}", event.getClass().getSimpleName(), event.getSource());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2020 the original author or authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.data.couchbase.core.mapping.event;
|
||||
|
||||
/**
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
public class ReactiveAfterDeleteEvent<E> extends CouchbaseMappingEvent<E> {
|
||||
|
||||
public ReactiveAfterDeleteEvent(E source) {
|
||||
super(source, null);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -63,7 +63,9 @@ public class ReactiveAuditingEntityCallback
|
||||
*/
|
||||
@Override
|
||||
public Publisher<Object> onBeforeConvert(final Object entity, final String collection) {
|
||||
LOG.debug("onBeforeConvert " + entity.toString());
|
||||
if (LOG.isTraceEnabled()) {
|
||||
LOG.trace("onBeforeConvert {}", entity.toString());
|
||||
}
|
||||
return this.auditingHandlerFactory.getObject().markAudited(entity);
|
||||
}
|
||||
|
||||
@@ -76,9 +78,12 @@ public class ReactiveAuditingEntityCallback
|
||||
*/
|
||||
@Override
|
||||
public Publisher<Object> onAfterConvert(Object entity, CouchbaseDocument document, String collection) {
|
||||
LOG.debug("onAfterConvert " + document.toString());
|
||||
if (LOG.isTraceEnabled()) {
|
||||
LOG.trace("onAfterConvert {}", document.toString());
|
||||
}
|
||||
return Mono.just(entity);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
|
||||
@@ -1,87 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2021 the original author or authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.data.couchbase.core.mapping.event;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.ObjectFactory;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.data.auditing.AuditingHandler;
|
||||
import org.springframework.data.auditing.IsNewAwareAuditingHandler;
|
||||
import org.springframework.data.auditing.ReactiveIsNewAwareAuditingHandler;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Reactive Event listener to populate auditing related fields on an entity about to be saved.
|
||||
*
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
public class ReactiveAuditingEventListener implements ApplicationListener<CouchbaseMappingEvent<Object>> {
|
||||
|
||||
private final ObjectFactory<ReactiveIsNewAwareAuditingHandler> auditingHandlerFactory;
|
||||
|
||||
public ReactiveAuditingEventListener() {
|
||||
this.auditingHandlerFactory = null;
|
||||
}
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ReactiveAuditingEventListener.class);
|
||||
|
||||
/**
|
||||
* Creates a new {@link ReactiveAuditingEventListener} using the given {@link MappingContext} and
|
||||
* {@link AuditingHandler} provided by the given {@link ObjectFactory}. Registered in CouchbaseAuditingRegistrar
|
||||
*
|
||||
* @param auditingHandlerFactory must not be {@literal null}.
|
||||
*/
|
||||
public ReactiveAuditingEventListener(ObjectFactory<ReactiveIsNewAwareAuditingHandler> auditingHandlerFactory) {
|
||||
Assert.notNull(auditingHandlerFactory, "IsNewAwareAuditingHandler must not be null!");
|
||||
this.auditingHandlerFactory = auditingHandlerFactory;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.context.ApplicationListener#onApplicationEvent(org.springframework.context.ApplicationEvent)
|
||||
*/
|
||||
@Override
|
||||
public void onApplicationEvent(CouchbaseMappingEvent<Object> event) {
|
||||
if (event instanceof ReactiveBeforeConvertEvent) {
|
||||
Optional.ofNullable(event.getSource())//
|
||||
.ifPresent(it -> auditingHandlerFactory.getObject().markAudited(it));
|
||||
// LOG.info(event.getClass().getSimpleName() + " " + event);
|
||||
}
|
||||
if (event instanceof ReactiveBeforeSaveEvent) {
|
||||
// LOG.info(event.getClass().getSimpleName() + " " + event);
|
||||
}
|
||||
if (event instanceof ReactiveAfterSaveEvent) {
|
||||
// LOG.info(event.getClass().getSimpleName() + " " + event);
|
||||
}
|
||||
if (event instanceof ReactiveBeforeDeleteEvent) {
|
||||
// LOG.info(event.getClass().getSimpleName() + " " + event);
|
||||
}
|
||||
if (event instanceof ReactiveAfterDeleteEvent) {
|
||||
// LOG.info(event.getClass().getSimpleName() + " " + event);
|
||||
}
|
||||
if (event.getClass().getSimpleName().startsWith("Reactive")) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug(event.getClass().getSimpleName() + " " + event.getSource());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2021 the original author or authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.data.couchbase.core.mapping.event;
|
||||
|
||||
/**
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
public class ReactiveBeforeConvertEvent<E> extends CouchbaseMappingEvent<E> {
|
||||
|
||||
public ReactiveBeforeConvertEvent(E source) {
|
||||
super(source, null);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2020 the original author or authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.data.couchbase.core.mapping.event;
|
||||
|
||||
/**
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
public class ReactiveBeforeDeleteEvent<E> extends CouchbaseMappingEvent<E> {
|
||||
|
||||
public ReactiveBeforeDeleteEvent(E source) {
|
||||
super(source, null);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2021 the original author or authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.data.couchbase.core.mapping.event;
|
||||
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
|
||||
|
||||
/**
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
public class ReactiveBeforeSaveEvent<E> extends CouchbaseMappingEvent<E> {
|
||||
|
||||
public ReactiveBeforeSaveEvent(E source, CouchbaseDocument document) {
|
||||
super(source, document);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -60,7 +60,6 @@ public class ValidatingCouchbaseEventListener extends AbstractCouchbaseEventList
|
||||
Set violations = validator.validate(source);
|
||||
|
||||
if (!violations.isEmpty()) {
|
||||
|
||||
LOG.info("During object: {} validation violations found: {}", source, violations);
|
||||
throw new ConstraintViolationException(violations);
|
||||
}
|
||||
|
||||
@@ -32,8 +32,9 @@ import org.springframework.util.StringUtils;
|
||||
*/
|
||||
public class Meta {
|
||||
|
||||
private enum MetaKey {
|
||||
EXAMPLE("$example");
|
||||
public enum MetaKey {
|
||||
SCAN_CONSISTENCY("scan_consistency"), SCOPE("scope"), COLLECTION("collection"), EXPIRY("expiry"), EXPIRY_UNIT(
|
||||
"expiry_unit"), EXPIRY_EXPRESSION("expiry_expression"), TIMEOUT("timeout"), RETRY_STRATEGY("retry_strategy");
|
||||
|
||||
private String key;
|
||||
|
||||
@@ -42,7 +43,7 @@ public class Meta {
|
||||
}
|
||||
}
|
||||
|
||||
private final Map<String, Object> values = new LinkedHashMap<>(2);
|
||||
private final Map<MetaKey, Object> values = new LinkedHashMap<>(2);
|
||||
|
||||
public Meta() {}
|
||||
|
||||
@@ -68,7 +69,7 @@ public class Meta {
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Iterable<Entry<String, Object>> values() {
|
||||
public Iterable<Entry<MetaKey, Object>> values() {
|
||||
return Collections.unmodifiableSet(this.values.entrySet());
|
||||
}
|
||||
|
||||
@@ -78,10 +79,26 @@ public class Meta {
|
||||
* @param key must not be {@literal null} or empty.
|
||||
* @param value
|
||||
*/
|
||||
void setValue(String key, @Nullable Object value) {
|
||||
public void setValue(String key, @Nullable Object value) {
|
||||
|
||||
Assert.hasText(key, "Meta key must not be 'null' or blank.");
|
||||
|
||||
if (value == null || (value instanceof String && !StringUtils.hasText((String) value))) {
|
||||
this.values.remove(MetaKey.valueOf(key));
|
||||
}
|
||||
this.values.put(MetaKey.valueOf(key), value);
|
||||
}
|
||||
|
||||
public void setValue(MetaKey key, @Nullable Object value) {
|
||||
|
||||
if (value == null || (value instanceof String && !StringUtils.hasText((String) value))) {
|
||||
this.values.remove(key);
|
||||
}
|
||||
this.values.put(key, value);
|
||||
}
|
||||
|
||||
public void set(MetaKey key, @Nullable Object value) {
|
||||
|
||||
if (value == null || (value instanceof String && !StringUtils.hasText((String) value))) {
|
||||
this.values.remove(key);
|
||||
}
|
||||
@@ -90,11 +107,15 @@ public class Meta {
|
||||
|
||||
@Nullable
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> T getValue(String key) {
|
||||
public <T> T getValue(String key) {
|
||||
return (T) this.values.get(MetaKey.valueOf(key));
|
||||
}
|
||||
|
||||
public <T> T get(MetaKey key) {
|
||||
return (T) this.values.get(key);
|
||||
}
|
||||
|
||||
private <T> T getValue(String key, T defaultValue) {
|
||||
public <T> T getValue(String key, T defaultValue) {
|
||||
|
||||
T value = getValue(key);
|
||||
return value != null ? value : defaultValue;
|
||||
|
||||
@@ -0,0 +1,425 @@
|
||||
/*
|
||||
* Copyright 2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.couchbase.core.query;
|
||||
|
||||
import static org.springframework.data.couchbase.core.query.Meta.MetaKey.RETRY_STRATEGY;
|
||||
import static org.springframework.data.couchbase.core.query.Meta.MetaKey.SCAN_CONSISTENCY;
|
||||
import static org.springframework.data.couchbase.core.query.Meta.MetaKey.TIMEOUT;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.AnnotatedElement;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.time.Duration;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
|
||||
import org.springframework.data.couchbase.repository.Collection;
|
||||
import org.springframework.data.couchbase.repository.ScanConsistency;
|
||||
import org.springframework.data.couchbase.repository.Scope;
|
||||
import org.springframework.data.couchbase.repository.query.CouchbaseQueryMethod;
|
||||
|
||||
import com.couchbase.client.core.io.CollectionIdentifier;
|
||||
import com.couchbase.client.core.msg.kv.DurabilityLevel;
|
||||
import com.couchbase.client.core.retry.RetryStrategy;
|
||||
import com.couchbase.client.java.json.JsonArray;
|
||||
import com.couchbase.client.java.json.JsonObject;
|
||||
import com.couchbase.client.java.kv.ExistsOptions;
|
||||
import com.couchbase.client.java.kv.InsertOptions;
|
||||
import com.couchbase.client.java.kv.PersistTo;
|
||||
import com.couchbase.client.java.kv.RemoveOptions;
|
||||
import com.couchbase.client.java.kv.ReplaceOptions;
|
||||
import com.couchbase.client.java.kv.ReplicateTo;
|
||||
import com.couchbase.client.java.kv.UpsertOptions;
|
||||
import com.couchbase.client.java.query.QueryOptions;
|
||||
import com.couchbase.client.java.query.QueryScanConsistency;
|
||||
|
||||
public class OptionsBuilder {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(OptionsBuilder.class);
|
||||
|
||||
static QueryOptions buildQueryOptions(Query query, QueryOptions options, QueryScanConsistency scanConsistency) {
|
||||
options = options != null ? options : QueryOptions.queryOptions();
|
||||
if (query.getParameters() != null) {
|
||||
if (query.getParameters() instanceof JsonArray) {
|
||||
options.parameters((JsonArray) query.getParameters());
|
||||
} else {
|
||||
options.parameters((JsonObject) query.getParameters());
|
||||
}
|
||||
}
|
||||
|
||||
Meta meta = query.getMeta() != null ? query.getMeta() : new Meta();
|
||||
QueryOptions.Built optsBuilt = options.build();
|
||||
JsonObject optsJson = getQueryOpts(optsBuilt);
|
||||
QueryScanConsistency metaQueryScanConsistency = meta.get(SCAN_CONSISTENCY) != null
|
||||
? ((ScanConsistency) meta.get(SCAN_CONSISTENCY)).query()
|
||||
: null;
|
||||
QueryScanConsistency qsc = fromFirst(QueryScanConsistency.NOT_BOUNDED, getScanConsistency(optsJson),
|
||||
scanConsistency, metaQueryScanConsistency);
|
||||
Duration timeout = fromFirst(Duration.ofSeconds(0), getTimeout(optsBuilt), meta.get(TIMEOUT));
|
||||
RetryStrategy retryStrategy = fromFirst(null, getRetryStrategy(optsBuilt), meta.get(RETRY_STRATEGY));
|
||||
|
||||
if (qsc != null) {
|
||||
options.scanConsistency(qsc);
|
||||
}
|
||||
if (timeout != null) {
|
||||
options.timeout(timeout);
|
||||
}
|
||||
if (retryStrategy != null) {
|
||||
options.retryStrategy(retryStrategy);
|
||||
}
|
||||
if (LOG.isTraceEnabled()) {
|
||||
LOG.trace("query options: {}", getQueryOpts(options.build()));
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
public static ExistsOptions buildExistsOptions(ExistsOptions options) {
|
||||
options = options != null ? options : ExistsOptions.existsOptions();
|
||||
return options;
|
||||
}
|
||||
|
||||
public static InsertOptions buildInsertOptions(InsertOptions options, PersistTo persistTo, ReplicateTo replicateTo,
|
||||
DurabilityLevel durabilityLevel, Duration expiry, CouchbaseDocument doc) {
|
||||
options = options != null ? options : InsertOptions.insertOptions();
|
||||
if (persistTo != PersistTo.NONE || replicateTo != ReplicateTo.NONE) {
|
||||
options.durability(persistTo, replicateTo);
|
||||
} else if (durabilityLevel != DurabilityLevel.NONE) {
|
||||
options.durability(durabilityLevel);
|
||||
}
|
||||
if (expiry != null) {
|
||||
options.expiry(expiry);
|
||||
} else if (doc.getExpiration() != 0) {
|
||||
options.expiry(Duration.ofSeconds(doc.getExpiration()));
|
||||
}
|
||||
if (LOG.isTraceEnabled()) {
|
||||
LOG.trace("insert options: {}" + toString(options));
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
public static UpsertOptions buildUpsertOptions(UpsertOptions options, PersistTo persistTo, ReplicateTo replicateTo,
|
||||
DurabilityLevel durabilityLevel, Duration expiry, CouchbaseDocument doc) {
|
||||
options = options != null ? options : UpsertOptions.upsertOptions();
|
||||
if (persistTo != PersistTo.NONE || replicateTo != ReplicateTo.NONE) {
|
||||
options.durability(persistTo, replicateTo);
|
||||
} else if (durabilityLevel != DurabilityLevel.NONE) {
|
||||
options.durability(durabilityLevel);
|
||||
}
|
||||
if (expiry != null) {
|
||||
options.expiry(expiry);
|
||||
} else if (doc.getExpiration() != 0) {
|
||||
options.expiry(Duration.ofSeconds(doc.getExpiration()));
|
||||
}
|
||||
if (LOG.isTraceEnabled()) {
|
||||
LOG.trace("upsert options: {}" + toString(options));
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
public static ReplaceOptions buildReplaceOptions(ReplaceOptions options, PersistTo persistTo, ReplicateTo replicateTo,
|
||||
DurabilityLevel durabilityLevel, Duration expiry, Long cas, CouchbaseDocument doc) {
|
||||
options = options != null ? options : ReplaceOptions.replaceOptions();
|
||||
if (persistTo != PersistTo.NONE || replicateTo != ReplicateTo.NONE) {
|
||||
options.durability(persistTo, replicateTo);
|
||||
} else if (durabilityLevel != DurabilityLevel.NONE) {
|
||||
options.durability(durabilityLevel);
|
||||
}
|
||||
if (expiry != null) {
|
||||
options.expiry(expiry);
|
||||
} else if (doc.getExpiration() != 0) {
|
||||
options.expiry(Duration.ofSeconds(doc.getExpiration()));
|
||||
}
|
||||
if (cas != null) {
|
||||
options.cas(cas);
|
||||
}
|
||||
if (LOG.isTraceEnabled()) {
|
||||
LOG.trace("replace options: {}" + toString(options));
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
public static RemoveOptions buildRemoveOptions(RemoveOptions options, PersistTo persistTo, ReplicateTo replicateTo,
|
||||
DurabilityLevel durabilityLevel, Long cas) {
|
||||
options = options != null ? options : RemoveOptions.removeOptions();
|
||||
if (persistTo != PersistTo.NONE || replicateTo != ReplicateTo.NONE) {
|
||||
options.durability(persistTo, replicateTo);
|
||||
} else if (durabilityLevel != DurabilityLevel.NONE) {
|
||||
options.durability(durabilityLevel);
|
||||
}
|
||||
RemoveOptions.Built optsBuilt = options.build();
|
||||
Duration timeout = fromFirst(Duration.ofSeconds(0), optsBuilt.timeout());
|
||||
RetryStrategy retryStrategy = fromFirst(null, optsBuilt.retryStrategy());
|
||||
|
||||
if (timeout != null) {
|
||||
options.timeout(timeout);
|
||||
}
|
||||
if (retryStrategy != null) {
|
||||
options.retryStrategy(retryStrategy);
|
||||
}
|
||||
if (cas != null) {
|
||||
options.cas(cas);
|
||||
}
|
||||
if (LOG.isTraceEnabled()) {
|
||||
LOG.trace("remove options: {}" + toString(options));
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
/**
|
||||
* scope annotation could be a
|
||||
*
|
||||
* @param domainType
|
||||
* @return
|
||||
*/
|
||||
public static String getScopeFrom(Class<?> domainType) {
|
||||
if (domainType == null) {
|
||||
return null;
|
||||
}
|
||||
Scope ann = AnnotatedElementUtils.findMergedAnnotation(domainType, Scope.class);
|
||||
if (ann != null && !CollectionIdentifier.DEFAULT_COLLECTION.equals(ann.value())) {
|
||||
return ann.value();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static String getCollectionFrom(Class<?> domainType) {
|
||||
if (domainType == null) {
|
||||
return null;
|
||||
}
|
||||
Collection ann = AnnotatedElementUtils.findMergedAnnotation(domainType, Collection.class);
|
||||
if (ann != null && !CollectionIdentifier.DEFAULT_COLLECTION.equals(ann.value())) {
|
||||
return ann.value();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static String toString(InsertOptions o) {
|
||||
StringBuilder s = new StringBuilder();
|
||||
InsertOptions.Built b = o.build();
|
||||
s.append("{");
|
||||
s.append("durabilityLevel: " + b.durabilityLevel());
|
||||
s.append(", persistTo: " + b.persistTo());
|
||||
s.append(", replicateTo: " + b.replicateTo());
|
||||
s.append(", timeout: " + b.timeout());
|
||||
s.append(", retryStrategy: " + b.retryStrategy());
|
||||
s.append(", clientContext: " + b.clientContext());
|
||||
s.append(", parentSpan: " + b.parentSpan());
|
||||
s.append("}");
|
||||
return s.toString();
|
||||
}
|
||||
|
||||
static String toString(UpsertOptions o) {
|
||||
StringBuilder s = new StringBuilder();
|
||||
UpsertOptions.Built b = o.build();
|
||||
s.append("{");
|
||||
s.append("durabilityLevel: " + b.durabilityLevel());
|
||||
s.append(", persistTo: " + b.persistTo());
|
||||
s.append(", replicateTo: " + b.replicateTo());
|
||||
s.append(", timeout: " + b.timeout());
|
||||
s.append(", retryStrategy: " + b.retryStrategy());
|
||||
s.append(", clientContext: " + b.clientContext());
|
||||
s.append(", parentSpan: " + b.parentSpan());
|
||||
s.append("}");
|
||||
return s.toString();
|
||||
}
|
||||
|
||||
static String toString(ReplaceOptions o) {
|
||||
StringBuilder s = new StringBuilder();
|
||||
ReplaceOptions.Built b = o.build();
|
||||
s.append("{");
|
||||
s.append("cas: " + b.cas());
|
||||
s.append(", durabilityLevel: " + b.durabilityLevel());
|
||||
s.append(", persistTo: " + b.persistTo());
|
||||
s.append(", replicateTo: " + b.replicateTo());
|
||||
s.append(", timeout: " + b.timeout());
|
||||
s.append(", retryStrategy: " + b.retryStrategy());
|
||||
s.append(", clientContext: " + b.clientContext());
|
||||
s.append(", parentSpan: " + b.parentSpan());
|
||||
s.append("}");
|
||||
return s.toString();
|
||||
}
|
||||
|
||||
static String toString(RemoveOptions o) {
|
||||
StringBuilder s = new StringBuilder();
|
||||
RemoveOptions.Built b = o.build();
|
||||
s.append("{");
|
||||
s.append("cas: " + b.cas());
|
||||
s.append(", durabilityLevel: " + b.durabilityLevel());
|
||||
s.append(", persistTo: " + b.persistTo());
|
||||
s.append(", replicateTo: " + b.replicateTo());
|
||||
s.append(", timeout: " + b.timeout());
|
||||
s.append(", retryStrategy: " + b.retryStrategy());
|
||||
s.append(", clientContext: " + b.clientContext());
|
||||
s.append(", parentSpan: " + b.parentSpan());
|
||||
s.append("}");
|
||||
return s.toString();
|
||||
}
|
||||
|
||||
private static JsonObject getQueryOpts(QueryOptions.Built optsBuilt) {
|
||||
JsonObject jo = JsonObject.create();
|
||||
optsBuilt.injectParams(jo);
|
||||
return jo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the most-specific
|
||||
*
|
||||
* @param deflt the default value, which we treat as not set
|
||||
* @param choice array of values or Optional<values>, 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);
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,7 @@ import org.slf4j.LoggerFactory;
|
||||
import org.springframework.data.couchbase.core.ReactiveCouchbaseTemplate;
|
||||
import org.springframework.data.couchbase.core.convert.CouchbaseConverter;
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentEntity;
|
||||
import org.springframework.data.couchbase.repository.query.CouchbaseQueryMethod;
|
||||
import org.springframework.data.couchbase.repository.query.StringBasedN1qlQueryParser;
|
||||
import org.springframework.data.couchbase.repository.support.MappingCouchbaseEntityInformation;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
@@ -52,6 +53,7 @@ public class Query {
|
||||
private int limit;
|
||||
private Sort sort = Sort.unsorted();
|
||||
private QueryScanConsistency queryScanConsistency;
|
||||
private Meta meta;
|
||||
|
||||
static private final Pattern WHERE_PATTERN = Pattern.compile("\\sWHERE\\s");
|
||||
private static final Logger LOG = LoggerFactory.getLogger(Query.class);
|
||||
@@ -326,28 +328,22 @@ public class Query {
|
||||
* @return QueryOptions
|
||||
*/
|
||||
public QueryOptions buildQueryOptions(QueryOptions options, QueryScanConsistency scanConsistency) {
|
||||
if (options == null) { // add/override what we got from PseudoArgs
|
||||
options = QueryOptions.queryOptions();
|
||||
}
|
||||
if (getParameters() != null) {
|
||||
if (getParameters() instanceof JsonArray) {
|
||||
options.parameters((JsonArray) getParameters());
|
||||
} else {
|
||||
options.parameters((JsonObject) getParameters());
|
||||
}
|
||||
}
|
||||
if (scanConsistency == null
|
||||
|| scanConsistency == QueryScanConsistency.NOT_BOUNDED && getScanConsistency() != null) {
|
||||
scanConsistency = getScanConsistency();
|
||||
}
|
||||
if (scanConsistency != null) {
|
||||
options.scanConsistency(scanConsistency);
|
||||
}
|
||||
return options;
|
||||
return OptionsBuilder.buildQueryOptions(this, options, scanConsistency);
|
||||
}
|
||||
|
||||
public void setMeta(Meta metaAnnotation) {
|
||||
Meta meta = metaAnnotation;
|
||||
/**
|
||||
* this collections annotations from the method, repository class and possibly the entity class to be used as options.
|
||||
* This will find annotations included in composed annotations as well. Ideally
|
||||
*
|
||||
* @param method representing the query.
|
||||
* @return the query with the annotations applied
|
||||
*/
|
||||
public void setMeta(CouchbaseQueryMethod method, Class<?> typeToRead) {
|
||||
meta = OptionsBuilder.buildMeta(method, typeToRead);
|
||||
}
|
||||
|
||||
public Meta getMeta() {
|
||||
return meta;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -15,9 +15,14 @@
|
||||
*/
|
||||
package org.springframework.data.couchbase.core.support;
|
||||
|
||||
import com.couchbase.client.core.io.CollectionIdentifier;
|
||||
import static org.springframework.data.couchbase.core.query.OptionsBuilder.fromFirst;
|
||||
import static org.springframework.data.couchbase.core.query.OptionsBuilder.getCollectionFrom;
|
||||
import static org.springframework.data.couchbase.core.query.OptionsBuilder.getScopeFrom;
|
||||
|
||||
import org.springframework.data.couchbase.core.ReactiveCouchbaseTemplate;
|
||||
|
||||
import com.couchbase.client.core.io.CollectionIdentifier;
|
||||
|
||||
public class PseudoArgs<OPTS> {
|
||||
private final OPTS options;
|
||||
private final String scopeName;
|
||||
@@ -35,24 +40,32 @@ public class PseudoArgs<OPTS> {
|
||||
* 2) values from dynamic proxy (via template threadLocal)<br>
|
||||
* 3) the values from the couchbaseClientFactory<br>
|
||||
*
|
||||
* @param template to hold
|
||||
* @param scope
|
||||
* @param collection
|
||||
* @param options
|
||||
* @param template which holds ThreadLocal pseudo args
|
||||
* @param scope - from calling operation
|
||||
* @param collection - from calling operation
|
||||
* @param options - from calling operation
|
||||
* @param domainType - entity that may have annotations
|
||||
*/
|
||||
public PseudoArgs(ReactiveCouchbaseTemplate template, String scope, String collection, OPTS options) {
|
||||
public PseudoArgs(ReactiveCouchbaseTemplate template, String scope, String collection, OPTS options,
|
||||
Class<?> domainType) {
|
||||
|
||||
// 1) values from the args (fluent api)
|
||||
String scopeForQuery = null;
|
||||
String collectionForQuery = null;
|
||||
OPTS optionsForQuery = null;
|
||||
|
||||
String scopeForQuery = scope;
|
||||
String collectionForQuery = collection;
|
||||
OPTS optionsForQuery = options;
|
||||
// 1) repository from DynamicProxy via template threadLocal - has precedence over annotation
|
||||
|
||||
// 2) from DynamicProxy via template threadLocal
|
||||
PseudoArgs<OPTS> threadLocal = (PseudoArgs<OPTS>) template.getPseudoArgs();
|
||||
template.setPseudoArgs(null);
|
||||
if (threadLocal != null) {
|
||||
scopeForQuery = threadLocal.getScope();
|
||||
collectionForQuery = threadLocal.getCollection();
|
||||
optionsForQuery = threadLocal.getOptions();
|
||||
}
|
||||
|
||||
scopeForQuery = scopeForQuery != null ? scopeForQuery : getThreadLocalScopeName(template);
|
||||
collectionForQuery = collectionForQuery != null ? collectionForQuery : getThreadLocalCollectionName(template);
|
||||
optionsForQuery = optionsForQuery != null ? optionsForQuery : getThreadLocalOptions(template);
|
||||
scopeForQuery = fromFirst(null, scopeForQuery, scope, getScopeFrom(domainType));
|
||||
collectionForQuery = fromFirst(null, collectionForQuery, collection, getCollectionFrom(domainType));
|
||||
optionsForQuery = fromFirst(null, options, optionsForQuery);
|
||||
|
||||
// if a collection was specified but no scope, use the scope from the clientFactory
|
||||
|
||||
@@ -62,56 +75,42 @@ public class PseudoArgs<OPTS> {
|
||||
|
||||
// specifying scope and collection = _default is not necessary and will fail if server doesn't have collections
|
||||
|
||||
if ((scopeForQuery == null || CollectionIdentifier.DEFAULT_SCOPE.equals(scopeForQuery))
|
||||
&& (collectionForQuery == null || CollectionIdentifier.DEFAULT_COLLECTION.equals(collectionForQuery))) {
|
||||
scopeForQuery = null;
|
||||
collectionForQuery = null;
|
||||
if (scopeForQuery == null || CollectionIdentifier.DEFAULT_SCOPE.equals(scopeForQuery)) {
|
||||
if (collectionForQuery == null || CollectionIdentifier.DEFAULT_COLLECTION.equals(collectionForQuery)) {
|
||||
collectionForQuery = null;
|
||||
scopeForQuery = null;
|
||||
}
|
||||
}
|
||||
|
||||
this.scopeName = scopeForQuery;
|
||||
this.collectionName = collectionForQuery;
|
||||
this.options = optionsForQuery;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @@return the options
|
||||
* @return the options
|
||||
*/
|
||||
public OPTS getOptions() {
|
||||
return this.options;
|
||||
}
|
||||
|
||||
/**
|
||||
* @@return the scope name
|
||||
* @return the scope name
|
||||
*/
|
||||
public String getScope() {
|
||||
return this.scopeName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @@return the collection name
|
||||
* @return the collection name
|
||||
*/
|
||||
public String getCollection() {
|
||||
return this.collectionName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @@return the options from the ThreadLocal field of the template
|
||||
*/
|
||||
private OPTS getThreadLocalOptions(ReactiveCouchbaseTemplate template) {
|
||||
return template.getPseudoArgs() == null ? null : (OPTS) (template.getPseudoArgs().getOptions());
|
||||
}
|
||||
|
||||
/**
|
||||
* @@return the scope name from the ThreadLocal field of the template
|
||||
*/
|
||||
private String getThreadLocalScopeName(ReactiveCouchbaseTemplate template) {
|
||||
return template.getPseudoArgs() == null ? null : template.getPseudoArgs().getScope();
|
||||
}
|
||||
|
||||
/**
|
||||
* @@return the collection name from the ThreadLocal field of the template
|
||||
*/
|
||||
private String getThreadLocalCollectionName(ReactiveCouchbaseTemplate template) {
|
||||
return template.getPseudoArgs() == null ? null : template.getPseudoArgs().getCollection();
|
||||
@Override
|
||||
public String toString() {
|
||||
return "scope: " + getScope() + " collection: " + getCollection() + " options: " + getOptions();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2012-2021 the original author or authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.couchbase.repository;
|
||||
|
||||
import static com.couchbase.client.core.io.CollectionIdentifier.DEFAULT_COLLECTION;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.data.annotation.QueryAnnotation;
|
||||
|
||||
/**
|
||||
* Collection Annotation
|
||||
*
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE })
|
||||
@Documented
|
||||
@QueryAnnotation
|
||||
public @interface Collection {
|
||||
|
||||
/**
|
||||
* Specifies the collection name
|
||||
*
|
||||
* @return the collection name configured, defaults to not DEFAULT_COLLECTION.
|
||||
*/
|
||||
String value() default DEFAULT_COLLECTION;
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2020 the original author or authors.
|
||||
* Copyright 2013-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -18,16 +18,20 @@ package org.springframework.data.couchbase.repository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.couchbase.client.java.query.QueryScanConsistency;
|
||||
import org.springframework.data.couchbase.core.CouchbaseOperations;
|
||||
import org.springframework.data.couchbase.repository.query.CouchbaseEntityInformation;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.repository.NoRepositoryBean;
|
||||
import org.springframework.data.repository.PagingAndSortingRepository;
|
||||
import org.springframework.data.repository.Repository;
|
||||
|
||||
import com.couchbase.client.java.query.QueryScanConsistency;
|
||||
|
||||
/**
|
||||
* Couchbase specific {@link Repository} interface.
|
||||
*
|
||||
* @author Michael Nitschinger
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
@NoRepositoryBean
|
||||
public interface CouchbaseRepository<T, ID> extends PagingAndSortingRepository<T, ID> {
|
||||
@@ -43,4 +47,8 @@ public interface CouchbaseRepository<T, ID> extends PagingAndSortingRepository<T
|
||||
@Override
|
||||
List<T> findAllById(Iterable<ID> iterable);
|
||||
|
||||
CouchbaseEntityInformation<T, String> getEntityInformation();
|
||||
|
||||
CouchbaseOperations getOperations();
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright 2017-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.data.couchbase.repository;
|
||||
|
||||
import java.lang.reflect.Proxy;
|
||||
|
||||
import org.springframework.data.couchbase.repository.query.CouchbaseEntityInformation;
|
||||
import org.springframework.data.couchbase.repository.support.DynamicInvocationHandler;
|
||||
|
||||
import com.couchbase.client.java.CommonOptions;
|
||||
|
||||
/**
|
||||
* The generic parameter needs to be REPO which is either a CouchbaseRepository parameterized on T,ID or a
|
||||
* ReactiveCouchbaseRepository parameterized on T,ID. i.e.: interface AirportRepository extends
|
||||
* CouchbaseRepository<Airport, String>, DynamicProxyable<AirportRepository>
|
||||
*
|
||||
* @param <REPO>
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
public interface DynamicProxyable<REPO> {
|
||||
|
||||
CouchbaseEntityInformation getEntityInformation();
|
||||
|
||||
Object getOperations();
|
||||
|
||||
/**
|
||||
* Support for Couchbase-specific options, scope and collections The three "with" methods will return a new proxy
|
||||
* instance with the specified options, scope, or collections set. The setters are called with the corresponding
|
||||
* options, scope and collection to set the ThreadLocal fields on the CouchbaseOperations of the repository just
|
||||
* before the call is made to the repository, and called again with 'null' just after the call is made. The repository
|
||||
* method will fetch those values to use in the call.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param options - the options to set on the returned repository object
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
default REPO withOptions(CommonOptions<?> options) {
|
||||
REPO proxyInstance = (REPO) Proxy.newProxyInstance(this.getClass().getClassLoader(),
|
||||
this.getClass().getInterfaces(), new DynamicInvocationHandler(this, options, null, (String) null));
|
||||
return proxyInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param scope - the scope to set on the returned repository object
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
default REPO withScope(String scope) {
|
||||
REPO proxyInstance = (REPO) Proxy.newProxyInstance(this.getClass().getClassLoader(),
|
||||
this.getClass().getInterfaces(), new DynamicInvocationHandler<>(this, null, null, scope));
|
||||
return proxyInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param collection - the collection to set on the returned repository object
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
default REPO withCollection(String collection) {
|
||||
REPO proxyInstance = (REPO) Proxy.newProxyInstance(this.getClass().getClassLoader(),
|
||||
this.getClass().getInterfaces(), new DynamicInvocationHandler<>(this, null, collection, null));
|
||||
return proxyInstance;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* Copyright 2012-2021 the original author or authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.couchbase.repository;
|
||||
|
||||
import static com.couchbase.client.core.io.CollectionIdentifier.DEFAULT_COLLECTION;
|
||||
import static com.couchbase.client.core.io.CollectionIdentifier.DEFAULT_SCOPE;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.springframework.data.annotation.QueryAnnotation;
|
||||
|
||||
import com.couchbase.client.java.analytics.AnalyticsScanConsistency;
|
||||
import com.couchbase.client.java.query.QueryScanConsistency;
|
||||
|
||||
/**
|
||||
* Scope Annotation
|
||||
*
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE })
|
||||
@Documented
|
||||
@QueryAnnotation
|
||||
public @interface Options {
|
||||
|
||||
/**
|
||||
* Specifies the scope name
|
||||
*
|
||||
* @return the scope name configured, defaults to not DEFAULT_SCOPE.
|
||||
*/
|
||||
String scope() default DEFAULT_SCOPE;
|
||||
|
||||
/**
|
||||
* Specifies the scope name
|
||||
*
|
||||
* @return the scope name configured, defaults to not DEFAULT_SCOPE.
|
||||
*/
|
||||
String collection() default DEFAULT_COLLECTION;
|
||||
|
||||
/**
|
||||
* Specifies a custom scan consistency for N1QL queries.
|
||||
*
|
||||
* @return the scan consistency configured, defaults to not bounded.
|
||||
*/
|
||||
QueryScanConsistency query() default QueryScanConsistency.NOT_BOUNDED;
|
||||
|
||||
/**
|
||||
* Specifies a custom scan consistency for analytics queries.
|
||||
*
|
||||
* @return the scan consistency configured, defaults to not bounded.
|
||||
*/
|
||||
AnalyticsScanConsistency analytics() default AnalyticsScanConsistency.NOT_BOUNDED;
|
||||
|
||||
/**
|
||||
* Specifies a custom projection.
|
||||
*
|
||||
* @return the projection configured, defaults to an empty array (project everything).
|
||||
*/
|
||||
String[] project() default {};
|
||||
|
||||
/**
|
||||
* Specifies a custom array of distinct fields.
|
||||
*
|
||||
* @return the projection configured, we need to do something tricky with the default. We need to default to<br>
|
||||
* no distinct, which is specified by a null array (an empty array means distinct on everything). We'll make
|
||||
* an array of a single element "-" mean no distinct.
|
||||
*/
|
||||
String[] distinct() default { "-" };
|
||||
|
||||
/**
|
||||
* An optional expiry time for the document. Default is no expiry. Only one of two might might be set at the same
|
||||
* time: either {@link #expiry()} or {@link #expiryExpression()}
|
||||
*/
|
||||
int expiry() default 0;
|
||||
|
||||
/**
|
||||
* Same as {@link #expiry} but allows the actual value to be set using standard Spring property sources mechanism.
|
||||
* Only one might be set at the same time: either {@link #expiry()} or {@link #expiryExpression()}. <br />
|
||||
* Syntax is the same as for {@link org.springframework.core.env.Environment#resolveRequiredPlaceholders(String)}.
|
||||
* <br />
|
||||
* <br />
|
||||
* The value will be recalculated for every {@link org.springframework.data.couchbase.core.CouchbaseTemplate}
|
||||
* save/insert/update call, thus allowing actual expiration to reflect changes on-the-fly as soon as property sources
|
||||
* change. <br />
|
||||
* <br />
|
||||
* SpEL is NOT supported.
|
||||
*/
|
||||
String expiryExpression() default "";
|
||||
|
||||
/**
|
||||
* An optional time unit for the document's {@link #expiry()}, if set. Default is {@link TimeUnit#SECONDS}.
|
||||
*/
|
||||
TimeUnit expiryUnit() default TimeUnit.SECONDS;
|
||||
|
||||
/**
|
||||
* An timeout for the operation. Default is no timeout.
|
||||
*/
|
||||
long timeoutMs() default 0;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2017-2020 the original author or authors.
|
||||
* Copyright 2017-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -15,6 +15,8 @@
|
||||
*/
|
||||
package org.springframework.data.couchbase.repository;
|
||||
|
||||
import org.springframework.data.couchbase.core.ReactiveCouchbaseOperations;
|
||||
import org.springframework.data.couchbase.repository.query.CouchbaseEntityInformation;
|
||||
import org.springframework.data.repository.NoRepositoryBean;
|
||||
import org.springframework.data.repository.reactive.ReactiveSortingRepository;
|
||||
|
||||
@@ -22,9 +24,12 @@ import org.springframework.data.repository.reactive.ReactiveSortingRepository;
|
||||
* Couchbase-specific {@link ReactiveSortingRepository} implementation.
|
||||
*
|
||||
* @author Subhashni Balakrishnan
|
||||
* @author Michael Reiche
|
||||
* @since 3.0
|
||||
*/
|
||||
@NoRepositoryBean
|
||||
public interface ReactiveCouchbaseRepository<T, ID> extends ReactiveSortingRepository<T, ID> {
|
||||
ReactiveCouchbaseOperations getOperations();
|
||||
|
||||
CouchbaseEntityInformation<T, String> getEntityInformation();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2020 the original author or authors
|
||||
* Copyright 2012-2021 the original author or authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -24,8 +24,13 @@ import java.lang.annotation.Target;
|
||||
import com.couchbase.client.java.analytics.AnalyticsScanConsistency;
|
||||
import com.couchbase.client.java.query.QueryScanConsistency;
|
||||
|
||||
/**
|
||||
* Scan Consistency Annotation
|
||||
*
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
|
||||
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE })
|
||||
@Documented
|
||||
public @interface ScanConsistency {
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2020 the original author or authors.
|
||||
* Copyright 2012-2021 the original author or authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@@ -15,6 +15,8 @@
|
||||
*/
|
||||
package org.springframework.data.couchbase.repository;
|
||||
|
||||
import static com.couchbase.client.core.io.CollectionIdentifier.DEFAULT_SCOPE;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
@@ -24,13 +26,21 @@ import java.lang.annotation.Target;
|
||||
import org.springframework.data.annotation.QueryAnnotation;
|
||||
|
||||
/**
|
||||
* Scope Annotation
|
||||
*
|
||||
* @author Michael Reiche
|
||||
* @since 4.1
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
|
||||
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE })
|
||||
@Documented
|
||||
@QueryAnnotation
|
||||
public @interface Meta {
|
||||
public @interface Scope {
|
||||
|
||||
/**
|
||||
* Specifies the scope name
|
||||
*
|
||||
* @return the scope name configured, defaults to DEFAULT_SCOPE.
|
||||
*/
|
||||
String value() default DEFAULT_SCOPE;
|
||||
|
||||
}
|
||||
@@ -1,3 +1,18 @@
|
||||
/*
|
||||
* Copyright 2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.couchbase.repository.auditing;
|
||||
|
||||
import static org.springframework.data.couchbase.config.BeanNames.REACTIVE_COUCHBASE_AUDITING_HANDLER;
|
||||
@@ -13,7 +28,6 @@ import org.springframework.data.auditing.config.AuditingBeanDefinitionRegistrarS
|
||||
import org.springframework.data.auditing.config.AuditingConfiguration;
|
||||
import org.springframework.data.config.ParsingUtils;
|
||||
import org.springframework.data.couchbase.core.mapping.event.ReactiveAuditingEntityCallback;
|
||||
import org.springframework.data.couchbase.core.mapping.event.ReactiveAuditingEventListener;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
@@ -82,17 +96,6 @@ class ReactiveCouchbaseAuditingRegistrar extends AuditingBeanDefinitionRegistrar
|
||||
registerInfrastructureBeanWithId(builder.getBeanDefinition(), ReactiveAuditingEntityCallback.class.getName(),
|
||||
registry);
|
||||
|
||||
// Register the AuditingEventListener
|
||||
|
||||
BeanDefinitionBuilder builder2 = BeanDefinitionBuilder.rootBeanDefinition(ReactiveAuditingEventListener.class);
|
||||
|
||||
builder2
|
||||
.addConstructorArgValue(ParsingUtils.getObjectFactoryBeanDefinition(getAuditingHandlerBeanName(), registry));
|
||||
builder.getRawBeanDefinition().setSource(auditingHandlerDefinition.getSource());
|
||||
|
||||
registerInfrastructureBeanWithId(builder2.getBeanDefinition(), ReactiveAuditingEventListener.class.getName(),
|
||||
registry);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 the original author or authors
|
||||
* Copyright 2020-2021 the original author or authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -15,7 +15,6 @@
|
||||
*/
|
||||
package org.springframework.data.couchbase.repository.query;
|
||||
|
||||
import com.couchbase.client.core.io.CollectionIdentifier;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.data.couchbase.core.CouchbaseOperations;
|
||||
import org.springframework.data.couchbase.core.ExecutableFindByQueryOperation.ExecutableFindByQuery;
|
||||
@@ -60,10 +59,11 @@ public abstract class AbstractCouchbaseQuery extends AbstractCouchbaseQueryBase<
|
||||
Assert.notNull(operations, "ReactiveCouchbaseOperations must not be null!");
|
||||
Assert.notNull(expressionParser, "SpelExpressionParser must not be null!");
|
||||
Assert.notNull(evaluationContextProvider, "QueryMethodEvaluationContextProvider must not be null!");
|
||||
// this.operations = operations;
|
||||
EntityMetadata<?> metadata = method.getEntityInformation();
|
||||
Class<?> type = metadata.getJavaType();
|
||||
this.findOperationWithProjection = operations.findByQuery(type);
|
||||
ExecutableFindByQuery<?> findOp = operations.findByQuery(type);
|
||||
findOp = (ExecutableFindByQuery<?>) (findOp.inScope(method.getScope()).inCollection(method.getCollection()));
|
||||
this.findOperationWithProjection = findOp;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -80,18 +80,14 @@ public abstract class AbstractCouchbaseQuery extends AbstractCouchbaseQueryBase<
|
||||
ParametersParameterAccessor accessor, @Nullable Class<?> typeToRead) {
|
||||
|
||||
Query query = createQuery(accessor);
|
||||
|
||||
query = applyAnnotatedConsistencyIfPresent(query);
|
||||
// query = applyAnnotatedCollationIfPresent(query, accessor); // not yet implemented
|
||||
query = applyQueryMetaAttributesIfPresent(query, typeToRead);
|
||||
|
||||
ExecutableFindByQuery<?> find = typeToRead == null ? findOperationWithProjection //
|
||||
: findOperationWithProjection; // not yet implemented in core .as(typeToRead);
|
||||
|
||||
String collection = null;
|
||||
ExecutableFindByQuery<?> find = findOperationWithProjection;
|
||||
|
||||
CouchbaseQueryExecution execution = getExecution(accessor,
|
||||
new ResultProcessingConverter<>(processor, getOperations(), getInstantiators()), find);
|
||||
return execution.execute(query, processor.getReturnedType().getDomainType(), collection);
|
||||
return execution.execute(query, processor.getReturnedType().getDomainType(), null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -114,8 +110,7 @@ public abstract class AbstractCouchbaseQuery extends AbstractCouchbaseQueryBase<
|
||||
* @param operation must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
private CouchbaseQueryExecution getExecutionToWrap(ParameterAccessor accessor,
|
||||
ExecutableFindByQuery<?> operation) {
|
||||
private CouchbaseQueryExecution getExecutionToWrap(ParameterAccessor accessor, ExecutableFindByQuery<?> operation) {
|
||||
|
||||
if (isDeleteQuery()) {
|
||||
return new DeleteExecution(getOperations(), getQueryMethod());
|
||||
@@ -123,6 +118,8 @@ public abstract class AbstractCouchbaseQuery extends AbstractCouchbaseQueryBase<
|
||||
return (q, t, c) -> operation.matching(q.with(accessor.getPageable())).all(); // s/b tail() instead of all()
|
||||
} else if (getQueryMethod().isCollectionQuery()) {
|
||||
return (q, t, c) -> operation.matching(q.with(accessor.getPageable())).all();
|
||||
} else if (getQueryMethod().isStreamQuery()) {
|
||||
return (q, t, c) -> operation.matching(q.with(accessor.getPageable())).stream();
|
||||
} else if (isCountQuery()) {
|
||||
return (q, t, c) -> operation.matching(q).count();
|
||||
} else if (isExistsQuery()) {
|
||||
@@ -140,18 +137,4 @@ public abstract class AbstractCouchbaseQuery extends AbstractCouchbaseQueryBase<
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply Meta annotation to query
|
||||
*
|
||||
* @param query must not be {@literal null}.
|
||||
* @return Query
|
||||
*/
|
||||
Query applyQueryMetaAttributesWhenPresent(Query query) {
|
||||
|
||||
if (getQueryMethod().hasQueryMetaAttributes()) {
|
||||
query.setMeta(getQueryMethod().getQueryMetaAttributes());
|
||||
}
|
||||
|
||||
return query;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,18 +146,14 @@ public abstract class AbstractCouchbaseQueryBase<CouchbaseOperationsType> implem
|
||||
ParametersParameterAccessor accessor, @Nullable Class<?> typeToRead);
|
||||
|
||||
/**
|
||||
* Add a scan consistency from {@link org.springframework.data.couchbase.repository.ScanConsistency} to the given
|
||||
* {@link Query} if present.
|
||||
* Apply Meta annotation to query
|
||||
*
|
||||
* @param query the {@link Query} to potentially apply the sort to.
|
||||
* @return the query with potential scan consistency applied.
|
||||
* @since 4.1
|
||||
* @param query must not be {@literal null}.
|
||||
* @return Query
|
||||
*/
|
||||
Query applyAnnotatedConsistencyIfPresent(Query query) {
|
||||
if (!method.hasScanConsistencyAnnotation()) {
|
||||
return query;
|
||||
}
|
||||
return query.scanConsistency(method.getScanConsistencyAnnotation().query());
|
||||
Query applyQueryMetaAttributesIfPresent(Query query, Class<?> typeToRead) {
|
||||
query.setMeta(getQueryMethod(), typeToRead);
|
||||
return query;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 the original author or authors.
|
||||
* Copyright 2020-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -15,7 +15,6 @@
|
||||
*/
|
||||
package org.springframework.data.couchbase.repository.query;
|
||||
|
||||
import com.couchbase.client.core.io.CollectionIdentifier;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.data.couchbase.core.ReactiveCouchbaseOperations;
|
||||
import org.springframework.data.couchbase.core.ReactiveFindByQueryOperation;
|
||||
@@ -63,7 +62,9 @@ public abstract class AbstractReactiveCouchbaseQuery extends AbstractCouchbaseQu
|
||||
|
||||
EntityMetadata<?> metadata = method.getEntityInformation();
|
||||
Class<?> type = metadata.getJavaType();
|
||||
this.findOperationWithProjection = operations.findByQuery(type);
|
||||
ReactiveFindByQuery<?> findOp = operations.findByQuery(type);
|
||||
findOp = (ReactiveFindByQuery<?>) (findOp.inScope(method.getScope()).inCollection(method.getCollection()));
|
||||
this.findOperationWithProjection = findOp;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -79,18 +80,14 @@ public abstract class AbstractReactiveCouchbaseQuery extends AbstractCouchbaseQu
|
||||
ParametersParameterAccessor accessor, @Nullable Class<?> typeToRead) {
|
||||
|
||||
Query query = createQuery(accessor);
|
||||
query = applyAnnotatedConsistencyIfPresent(query);
|
||||
// query = applyAnnotatedCollationIfPresent(query, accessor); // not yet implemented
|
||||
query = applyQueryMetaAttributesIfPresent(query, typeToRead);
|
||||
|
||||
ReactiveFindByQuery<?> find = typeToRead == null //
|
||||
? findOperationWithProjection //
|
||||
: findOperationWithProjection; // note yet implemented in core .as(typeToRead);
|
||||
|
||||
String collection = null;
|
||||
ReactiveFindByQuery<?> find = findOperationWithProjection;
|
||||
|
||||
ReactiveCouchbaseQueryExecution execution = getExecution(accessor,
|
||||
new ResultProcessingConverter<>(processor, getOperations(), getInstantiators()), find);
|
||||
return execution.execute(query, processor.getReturnedType().getDomainType(), collection);
|
||||
return execution.execute(query, processor.getReturnedType().getDomainType(), null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -121,6 +118,8 @@ public abstract class AbstractReactiveCouchbaseQuery extends AbstractCouchbaseQu
|
||||
return (q, t, c) -> operation.matching(q.with(accessor.getPageable())).all(); // s/b tail() instead of all()
|
||||
} else if (getQueryMethod().isCollectionQuery()) {
|
||||
return (q, t, c) -> operation.matching(q.with(accessor.getPageable())).all();
|
||||
// } else if (getQueryMethod().isStreamQuery()) {
|
||||
// return (q, t, c) -> operation.matching(q.with(accessor.getPageable())).all().toStream();
|
||||
} else if (isCountQuery()) {
|
||||
return (q, t, c) -> operation.matching(q).count();
|
||||
} else if (isExistsQuery()) {
|
||||
@@ -133,18 +132,4 @@ public abstract class AbstractReactiveCouchbaseQuery extends AbstractCouchbaseQu
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply Meta annotation to query
|
||||
*
|
||||
* @param query must not be {@literal null}.
|
||||
* @return Query
|
||||
*/
|
||||
Query applyQueryMetaAttributesWhenPresent(Query query) {
|
||||
|
||||
if (getQueryMethod().hasQueryMetaAttributes()) {
|
||||
query.setMeta(getQueryMethod().getQueryMetaAttributes());
|
||||
}
|
||||
|
||||
return query;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2020 the original author or authors.
|
||||
* Copyright 2013-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,28 +16,33 @@
|
||||
|
||||
package org.springframework.data.couchbase.repository.query;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.AnnotatedElement;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentEntity;
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty;
|
||||
import org.springframework.data.couchbase.core.query.Dimensional;
|
||||
import org.springframework.data.couchbase.core.query.OptionsBuilder;
|
||||
import org.springframework.data.couchbase.core.query.View;
|
||||
import org.springframework.data.couchbase.core.query.WithConsistency;
|
||||
import org.springframework.data.couchbase.repository.Meta;
|
||||
import org.springframework.data.couchbase.repository.Collection;
|
||||
import org.springframework.data.couchbase.repository.Query;
|
||||
import org.springframework.data.couchbase.repository.ScanConsistency;
|
||||
import org.springframework.data.couchbase.repository.Scope;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.projection.ProjectionFactory;
|
||||
import org.springframework.data.repository.core.RepositoryMetadata;
|
||||
import org.springframework.data.repository.query.Parameter;
|
||||
import org.springframework.data.repository.query.QueryMethod;
|
||||
import org.springframework.data.repository.util.ReactiveWrapperConverters;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.couchbase.client.core.io.CollectionIdentifier;
|
||||
|
||||
/**
|
||||
* Represents a query method with couchbase extensions, allowing to discover if View-based query or N1QL-based query
|
||||
* must be used.
|
||||
@@ -50,13 +55,13 @@ import org.springframework.util.StringUtils;
|
||||
public class CouchbaseQueryMethod extends QueryMethod {
|
||||
|
||||
private final Method method;
|
||||
private final RepositoryMetadata repositoryMetadata;
|
||||
|
||||
public CouchbaseQueryMethod(Method method, RepositoryMetadata metadata, ProjectionFactory factory,
|
||||
MappingContext<? extends CouchbasePersistentEntity<?>, CouchbasePersistentProperty> mappingContext) {
|
||||
super(method, metadata, factory);
|
||||
|
||||
this.method = method;
|
||||
|
||||
this.repositoryMetadata = metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -176,39 +181,46 @@ public class CouchbaseQueryMethod extends QueryMethod {
|
||||
* @return the @ScanConsistency annotation
|
||||
*/
|
||||
public ScanConsistency getScanConsistencyAnnotation() {
|
||||
return method.getAnnotation(ScanConsistency.class);
|
||||
AnnotatedElement[] annotated = new AnnotatedElement[] { method, method.getDeclaringClass(),
|
||||
repositoryMetadata.getRepositoryInterface(), repositoryMetadata.getDomainType() };
|
||||
return OptionsBuilder.annotation(ScanConsistency.class, "query", CollectionIdentifier.DEFAULT_COLLECTION,
|
||||
annotated);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return return true if {@link Meta} annotation is available.
|
||||
* Caution: findMergedAnnotation() will return the default if there are any annotations but not this annotation
|
||||
*
|
||||
* @return annotation
|
||||
*/
|
||||
public boolean hasQueryMetaAttributes() {
|
||||
return getMetaAnnotation() != null;
|
||||
public <A extends Annotation> A getAnnotation(Class<A> annotationClass) {
|
||||
return AnnotatedElementUtils.findMergedAnnotation(method, annotationClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return return {@link Meta} annotation
|
||||
* Caution: findMergedAnnotation() will return the default if there are any annotations but not this annotation
|
||||
*
|
||||
* @return annotation
|
||||
*/
|
||||
private Meta getMetaAnnotation() {
|
||||
return method.getAnnotation(Meta.class);
|
||||
public <A extends Annotation> A getClassAnnotation(Class<A> annotationClass) {
|
||||
return AnnotatedElementUtils.findMergedAnnotation(method.getDeclaringClass(), annotationClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link org.springframework.data.couchbase.core.query.Meta} attributes to be applied.
|
||||
*
|
||||
* @return never {@literal null}.
|
||||
* Caution: findMergedAnnotation() will return the default if there are any annotations but not this annotation
|
||||
*
|
||||
* @return annotation
|
||||
*/
|
||||
@Nullable
|
||||
public org.springframework.data.couchbase.core.query.Meta getQueryMetaAttributes() {
|
||||
public <A extends Annotation> A getEntityAnnotation(Class<A> annotationClass) {
|
||||
return AnnotatedElementUtils.findMergedAnnotation(getEntityInformation().getJavaType(), annotationClass);
|
||||
}
|
||||
|
||||
Meta meta = getMetaAnnotation();
|
||||
if (meta == null) {
|
||||
return new org.springframework.data.couchbase.core.query.Meta();
|
||||
}
|
||||
|
||||
org.springframework.data.couchbase.core.query.Meta metaAttributes = new org.springframework.data.couchbase.core.query.Meta();
|
||||
|
||||
return metaAttributes;
|
||||
/**
|
||||
* Caution: findMergedAnnotation() will return the default if there are any annotations but not this annotation
|
||||
*
|
||||
* @return annotation
|
||||
*/
|
||||
public <A extends Annotation> A getRepositoryAnnotation(Class<A> annotationClass) {
|
||||
return AnnotatedElementUtils.findMergedAnnotation(repositoryMetadata.getRepositoryInterface(), annotationClass);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -264,4 +276,18 @@ public class CouchbaseQueryMethod extends QueryMethod {
|
||||
return false;
|
||||
}
|
||||
|
||||
public String getCollection() {
|
||||
// Try the repository method, then the repository class, then the entity class
|
||||
AnnotatedElement[] annotated = new AnnotatedElement[] { method, method.getDeclaringClass(),
|
||||
repositoryMetadata.getRepositoryInterface(), repositoryMetadata.getDomainType() };
|
||||
return OptionsBuilder.annotationString(Collection.class, CollectionIdentifier.DEFAULT_COLLECTION, annotated);
|
||||
}
|
||||
|
||||
public String getScope() {
|
||||
// Try the repository method, then the repository class, then the entity class
|
||||
AnnotatedElement[] annotated = new AnnotatedElement[] { method, method.getDeclaringClass(),
|
||||
repositoryMetadata.getRepositoryInterface(), repositoryMetadata.getDomainType() };
|
||||
return OptionsBuilder.annotationString(Scope.class, CollectionIdentifier.DEFAULT_SCOPE, annotated);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ public class ReactiveStringBasedCouchbaseQuery extends AbstractReactiveCouchbase
|
||||
|
||||
@Override
|
||||
protected Query createCountQuery(ParametersParameterAccessor accessor) {
|
||||
return applyQueryMetaAttributesWhenPresent(createQuery(accessor));
|
||||
return applyQueryMetaAttributesIfPresent(createQuery(accessor), null);
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -79,8 +79,8 @@ public class StringBasedCouchbaseQuery extends AbstractCouchbaseQuery {
|
||||
namedQueries);
|
||||
Query query = creator.createQuery();
|
||||
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Created query " + query.export());
|
||||
if (LOG.isTraceEnabled()) {
|
||||
LOG.trace("Created query " + query.export());
|
||||
}
|
||||
|
||||
return query;
|
||||
@@ -88,7 +88,7 @@ public class StringBasedCouchbaseQuery extends AbstractCouchbaseQuery {
|
||||
|
||||
@Override
|
||||
protected Query createCountQuery(ParametersParameterAccessor accessor) {
|
||||
return applyQueryMetaAttributesWhenPresent(createQuery(accessor));
|
||||
return applyQueryMetaAttributesIfPresent(createQuery(accessor), null);
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -22,7 +22,6 @@ import static org.springframework.data.couchbase.core.support.TemplateUtils.SELE
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
@@ -30,11 +29,7 @@ import java.util.regex.Pattern;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.core.convert.support.DefaultConversionService;
|
||||
import org.springframework.core.convert.support.GenericConversionService;
|
||||
import org.springframework.data.convert.CustomConversions;
|
||||
import org.springframework.data.couchbase.core.convert.CouchbaseConverter;
|
||||
import org.springframework.data.couchbase.core.convert.CouchbaseCustomConversions;
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty;
|
||||
import org.springframework.data.couchbase.core.query.N1QLExpression;
|
||||
import org.springframework.data.couchbase.repository.Query;
|
||||
@@ -131,8 +126,9 @@ public class StringBasedN1qlQueryParser {
|
||||
this.statement = statement;
|
||||
this.queryMethod = queryMethod;
|
||||
this.couchbaseConverter = couchbaseConverter;
|
||||
this.statementContext = createN1qlSpelValues(bucketName, null, null, null, typeField, typeValue, false, null);
|
||||
this.countContext = createN1qlSpelValues(bucketName, null, null, null, typeField, typeValue, true, null);
|
||||
String collection = queryMethod.getCollection();
|
||||
this.statementContext = createN1qlSpelValues(bucketName, collection, null, null, typeField, typeValue, false, null);
|
||||
this.countContext = createN1qlSpelValues(bucketName, collection, null, null, typeField, typeValue, true, null);
|
||||
this.parsedExpression = getExpression(accessor, getParameters(accessor), null, parser, evaluationContextProvider);
|
||||
checkPlaceholders(this.parsedExpression.toString());
|
||||
}
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Copyright 2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.data.couchbase.repository.support;
|
||||
|
||||
import java.lang.reflect.AnnotatedElement;
|
||||
|
||||
import org.springframework.data.couchbase.core.query.OptionsBuilder;
|
||||
import org.springframework.data.couchbase.repository.Collection;
|
||||
import org.springframework.data.couchbase.repository.ScanConsistency;
|
||||
import org.springframework.data.couchbase.repository.Scope;
|
||||
import org.springframework.data.couchbase.repository.query.CouchbaseEntityInformation;
|
||||
|
||||
import com.couchbase.client.core.io.CollectionIdentifier;
|
||||
import com.couchbase.client.java.query.QueryScanConsistency;
|
||||
|
||||
public class CouchbaseRepositoryBase<T, ID> {
|
||||
|
||||
/**
|
||||
* Contains information about the entity being used in this repository.
|
||||
*/
|
||||
private final CouchbaseEntityInformation<T, String> entityInformation;
|
||||
private final Class<?> repositoryInterface;
|
||||
private CrudMethodMetadata crudMethodMetadata;
|
||||
|
||||
public CouchbaseRepositoryBase(CouchbaseEntityInformation<T, String> entityInformation,
|
||||
Class<?> repositoryInterface) {
|
||||
this.entityInformation = entityInformation;
|
||||
this.repositoryInterface = repositoryInterface;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the information for the underlying template.
|
||||
*
|
||||
* @return the underlying entity information.
|
||||
*/
|
||||
public CouchbaseEntityInformation<T, String> getEntityInformation() {
|
||||
return entityInformation;
|
||||
}
|
||||
|
||||
Class<T> getJavaType() {
|
||||
return getEntityInformation().getJavaType();
|
||||
}
|
||||
|
||||
<S extends T> String getId(S entity) {
|
||||
return getEntityInformation().getId(entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Scope from <br>
|
||||
* 1. The repository<br>
|
||||
* 2. The entity<br>
|
||||
* 3. otherwise null<br>
|
||||
* This can be overriden in the operation method by<br>
|
||||
* 1. repository.withCollection() 2. Annotation on the method
|
||||
*/
|
||||
|
||||
String getScope() {
|
||||
String fromAnnotation = OptionsBuilder.annotationString(Scope.class, CollectionIdentifier.DEFAULT_SCOPE,
|
||||
new AnnotatedElement[] { getJavaType(), repositoryInterface });
|
||||
String fromMetadata = crudMethodMetadata.getScope();
|
||||
return OptionsBuilder.fromFirst(CollectionIdentifier.DEFAULT_SCOPE, fromMetadata, fromAnnotation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Collection from <br>
|
||||
* 1. The repository<br>
|
||||
* 2. The entity<br>
|
||||
* 3. otherwise null<br>
|
||||
* This can be overriden in the operation method by<br>
|
||||
* 1. repository.withCollection()
|
||||
*/
|
||||
String getCollection() {
|
||||
String fromAnnotation = OptionsBuilder.annotationString(Collection.class, CollectionIdentifier.DEFAULT_COLLECTION,
|
||||
new AnnotatedElement[] { getJavaType(), repositoryInterface });
|
||||
String fromMetadata = crudMethodMetadata.getCollection();
|
||||
return OptionsBuilder.fromFirst(CollectionIdentifier.DEFAULT_COLLECTION, fromMetadata, fromAnnotation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the QueryScanConsistency from <br>
|
||||
* 1. The method annotation (method *could* be available from crudMethodMetadata)<br>
|
||||
* 2. The repository<br>
|
||||
* 3. The entity<br>
|
||||
* 4. otherwise null<br>
|
||||
* This can be overriden in the operation method by<br>
|
||||
* 1. Options.scanConsistency (?)<br>
|
||||
* AbstractCouchbaseQueryBase.applyAnnotatedConsistencyIfPresent() <br>
|
||||
* CouchbaseRepository get picked up? If I have the following, will the annotation be picked up?<br>
|
||||
* Only via crudMethodMetadata<br>
|
||||
* \@ScanConsistency(query=QueryScanConsistency.REQUEST_PLUS)<br>
|
||||
* List<T> findAll();<br>
|
||||
*/
|
||||
QueryScanConsistency buildQueryScanConsistency() {
|
||||
ScanConsistency sc = crudMethodMetadata.getScanConsistency();
|
||||
QueryScanConsistency fromMeta = sc != null ? sc.query() : null;
|
||||
QueryScanConsistency fromAnnotation = OptionsBuilder.annotationAttribute(ScanConsistency.class, "query",
|
||||
QueryScanConsistency.NOT_BOUNDED, new AnnotatedElement[] { getJavaType(), repositoryInterface });
|
||||
return OptionsBuilder.fromFirst(QueryScanConsistency.NOT_BOUNDED, fromMeta, fromAnnotation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for the repository metadata, contains annotations on the overidden methods.
|
||||
*
|
||||
* @param crudMethodMetadata the injected repository metadata.
|
||||
*/
|
||||
void setRepositoryMethodMetadata(CrudMethodMetadata crudMethodMetadata) {
|
||||
this.crudMethodMetadata = crudMethodMetadata;
|
||||
}
|
||||
}
|
||||
@@ -90,14 +90,14 @@ public class CouchbaseRepositoryFactory extends RepositoryFactorySupport {
|
||||
* Returns entity information based on the domain class.
|
||||
*
|
||||
* @param domainClass the class for the entity.
|
||||
* @param <T> the value type
|
||||
* @param <ID> the id type.
|
||||
* @param <T> the value type
|
||||
* @param <ID> the id type.
|
||||
* @return entity information for that domain class.
|
||||
*/
|
||||
@Override
|
||||
public <T, ID> CouchbaseEntityInformation<T, ID> getEntityInformation(Class<T> domainClass) {
|
||||
CouchbasePersistentEntity<T> entity = (CouchbasePersistentEntity<T>) mappingContext.getRequiredPersistentEntity(
|
||||
domainClass);
|
||||
CouchbasePersistentEntity<T> entity = (CouchbasePersistentEntity<T>) mappingContext
|
||||
.getRequiredPersistentEntity(domainClass);
|
||||
return new MappingCouchbaseEntityInformation<>(entity);
|
||||
}
|
||||
|
||||
@@ -116,7 +116,7 @@ public class CouchbaseRepositoryFactory extends RepositoryFactorySupport {
|
||||
metadata.getDomainType());
|
||||
CouchbaseEntityInformation<?, Serializable> entityInformation = getEntityInformation(metadata.getDomainType());
|
||||
SimpleCouchbaseRepository repository = getTargetRepositoryViaReflection(metadata, entityInformation,
|
||||
couchbaseOperations);
|
||||
couchbaseOperations, metadata.getRepositoryInterface());
|
||||
repository.setRepositoryMethodMetadata(crudMethodMetadataPostProcessor.getCrudMethodMetadata());
|
||||
return repository;
|
||||
}
|
||||
@@ -153,8 +153,8 @@ public class CouchbaseRepositoryFactory extends RepositoryFactorySupport {
|
||||
@Override
|
||||
public RepositoryQuery resolveQuery(final Method method, final RepositoryMetadata metadata,
|
||||
final ProjectionFactory factory, final NamedQueries namedQueries) {
|
||||
final CouchbaseOperations couchbaseOperations = couchbaseOperationsMapping.resolve(
|
||||
metadata.getRepositoryInterface(), metadata.getDomainType());
|
||||
final CouchbaseOperations couchbaseOperations = couchbaseOperationsMapping
|
||||
.resolve(metadata.getRepositoryInterface(), metadata.getDomainType());
|
||||
|
||||
CouchbaseQueryMethod queryMethod = new CouchbaseQueryMethod(method, metadata, factory, mappingContext);
|
||||
|
||||
|
||||
@@ -31,4 +31,8 @@ public interface CrudMethodMetadata {
|
||||
*/
|
||||
ScanConsistency getScanConsistency();
|
||||
|
||||
String getScope();
|
||||
|
||||
String getCollection();
|
||||
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
package org.springframework.data.couchbase.repository.support;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.AnnotatedElement;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
@@ -28,7 +28,10 @@ import org.springframework.aop.TargetSource;
|
||||
import org.springframework.aop.framework.ProxyFactory;
|
||||
import org.springframework.beans.factory.BeanClassLoaderAware;
|
||||
import org.springframework.core.NamedThreadLocal;
|
||||
import org.springframework.data.couchbase.core.query.OptionsBuilder;
|
||||
import org.springframework.data.couchbase.repository.Collection;
|
||||
import org.springframework.data.couchbase.repository.ScanConsistency;
|
||||
import org.springframework.data.couchbase.repository.Scope;
|
||||
import org.springframework.data.repository.core.RepositoryInformation;
|
||||
import org.springframework.data.repository.core.support.RepositoryProxyPostProcessor;
|
||||
import org.springframework.lang.Nullable;
|
||||
@@ -37,6 +40,9 @@ import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
import com.couchbase.client.core.io.CollectionIdentifier;
|
||||
import com.couchbase.client.java.query.QueryScanConsistency;
|
||||
|
||||
/**
|
||||
* {@link RepositoryProxyPostProcessor} that sets up interceptors to read metadata information from the invoked method.
|
||||
* This is necessary to allow redeclaration of CRUD methods in repository interfaces and configure locking information
|
||||
@@ -96,8 +102,10 @@ class CrudMethodMetadataPostProcessor implements RepositoryProxyPostProcessor, B
|
||||
|
||||
private final ConcurrentMap<Method, CrudMethodMetadata> metadataCache = new ConcurrentHashMap<>();
|
||||
private final Set<Method> implementations = new HashSet<>();
|
||||
private final RepositoryInformation repositoryInformation;
|
||||
|
||||
CrudMethodMetadataPopulatingMethodInterceptor(RepositoryInformation repositoryInformation) {
|
||||
this.repositoryInformation = repositoryInformation;
|
||||
ReflectionUtils.doWithMethods(repositoryInformation.getRepositoryInterface(), implementations::add,
|
||||
method -> !repositoryInformation.isQueryMethod(method));
|
||||
}
|
||||
@@ -148,7 +156,7 @@ class CrudMethodMetadataPostProcessor implements RepositoryProxyPostProcessor, B
|
||||
|
||||
if (methodMetadata == null) {
|
||||
|
||||
methodMetadata = new DefaultCrudMethodMetadata(method);
|
||||
methodMetadata = new DefaultCrudMethodMetadata(method, repositoryInformation);
|
||||
CrudMethodMetadata tmp = metadataCache.putIfAbsent(method, methodMetadata);
|
||||
|
||||
if (tmp != null) {
|
||||
@@ -161,10 +169,10 @@ class CrudMethodMetadataPostProcessor implements RepositoryProxyPostProcessor, B
|
||||
try {
|
||||
return invocation.proceed();
|
||||
} finally {
|
||||
TransactionSynchronizationManager.unbindResource(method);
|
||||
// TransactionSynchronizationManager.unbindResource(method);
|
||||
}
|
||||
} finally {
|
||||
currentInvocation.set(oldInvocation);
|
||||
// currentInvocation.set(oldInvocation);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -179,23 +187,38 @@ class CrudMethodMetadataPostProcessor implements RepositoryProxyPostProcessor, B
|
||||
|
||||
private final Method method;
|
||||
private final ScanConsistency scanConsistency;
|
||||
private final RepositoryInformation repositoryInformation;
|
||||
private final String scope;
|
||||
private final String collection;
|
||||
|
||||
/**
|
||||
* Creates a new {@link DefaultCrudMethodMetadata} for the given {@link Method}.
|
||||
*
|
||||
* Creates a new {@link DefaultCrudMethodMetadata} for the given {@link Method}. This collects data from implemented
|
||||
* methods (save(), findById() etc) that would be collected in query.setMeta() for unimplemented methods. There may
|
||||
* be annotations if the methods were overriden in the repository.
|
||||
*
|
||||
* @param method must not be {@literal null}.
|
||||
*/
|
||||
DefaultCrudMethodMetadata(Method method) {
|
||||
DefaultCrudMethodMetadata(Method method, RepositoryInformation repositoryInformation) {
|
||||
Assert.notNull(method, "Method must not be null!");
|
||||
this.method = method;
|
||||
|
||||
ScanConsistency scanConsistency = null;
|
||||
for (Annotation ann : method.getAnnotations()) {
|
||||
if (ann instanceof ScanConsistency) {
|
||||
scanConsistency = ((ScanConsistency) ann);
|
||||
}
|
||||
this.repositoryInformation = repositoryInformation;
|
||||
String n = method.getName();
|
||||
// internal methods
|
||||
if (n.equals("getEntityInformation") || n.equals("getOperations") || n.equals("withOptions")
|
||||
|| n.equals("withOptions") || n.equals("withScope")) {
|
||||
this.scanConsistency = null;
|
||||
this.scope = null;
|
||||
this.collection = null;
|
||||
return;
|
||||
}
|
||||
this.scanConsistency = scanConsistency;
|
||||
|
||||
AnnotatedElement[] annotated = new AnnotatedElement[] { method, method.getDeclaringClass(),
|
||||
repositoryInformation.getRepositoryInterface(), repositoryInformation.getDomainType() };
|
||||
this.scanConsistency = OptionsBuilder.annotation(ScanConsistency.class, "query", QueryScanConsistency.NOT_BOUNDED,
|
||||
annotated);
|
||||
this.scope = OptionsBuilder.annotationString(Scope.class, CollectionIdentifier.DEFAULT_SCOPE, annotated);
|
||||
this.collection = OptionsBuilder.annotationString(Collection.class, CollectionIdentifier.DEFAULT_COLLECTION,
|
||||
annotated);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -211,6 +234,17 @@ class CrudMethodMetadataPostProcessor implements RepositoryProxyPostProcessor, B
|
||||
public ScanConsistency getScanConsistency() {
|
||||
return scanConsistency;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getScope() {
|
||||
return scope;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCollection() {
|
||||
return collection;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class ThreadBoundTargetSource implements TargetSource {
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Copyright 2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.couchbase.repository.support;
|
||||
|
||||
import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.springframework.data.couchbase.core.CouchbaseTemplate;
|
||||
import org.springframework.data.couchbase.core.ReactiveCouchbaseTemplate;
|
||||
import org.springframework.data.couchbase.core.support.PseudoArgs;
|
||||
import org.springframework.data.couchbase.repository.CouchbaseRepository;
|
||||
import org.springframework.data.couchbase.repository.ReactiveCouchbaseRepository;
|
||||
import org.springframework.data.couchbase.repository.query.CouchbaseEntityInformation;
|
||||
|
||||
import com.couchbase.client.java.CommonOptions;
|
||||
|
||||
/**
|
||||
* Invocation Handler for scope/collection/options proxy for repositories
|
||||
*
|
||||
* @param <T>
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
public class DynamicInvocationHandler<T> implements InvocationHandler {
|
||||
final T target;
|
||||
final Class<?> repositoryClass;
|
||||
// needed only to detect parameters of this type to look for methods with parameter of java.lang.Object
|
||||
final CouchbaseEntityInformation<?, String> entityInformation;
|
||||
final ReactiveCouchbaseTemplate reactiveTemplate;
|
||||
CommonOptions<?> options;
|
||||
String collection;
|
||||
String scope;;
|
||||
|
||||
public DynamicInvocationHandler(T target, CommonOptions<?> options, String collection, String scope) {
|
||||
this.target = target;
|
||||
if (target instanceof CouchbaseRepository) {
|
||||
reactiveTemplate = ((CouchbaseTemplate) ((CouchbaseRepository) target).getOperations()).reactive();
|
||||
this.entityInformation = ((CouchbaseRepository<?, String>) target).getEntityInformation();
|
||||
} else if (target instanceof ReactiveCouchbaseRepository) {
|
||||
reactiveTemplate = (ReactiveCouchbaseTemplate) ((ReactiveCouchbaseRepository) target).getOperations();
|
||||
this.entityInformation = ((ReactiveCouchbaseRepository<?, String>) target).getEntityInformation();
|
||||
} else {
|
||||
throw new RuntimeException("Unknown target type: " + target.getClass());
|
||||
}
|
||||
this.options = options;
|
||||
this.collection = collection;
|
||||
this.scope = scope;
|
||||
this.repositoryClass = target.getClass();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
|
||||
|
||||
if ("toString".equals(method.getName())) {
|
||||
return "proxy -> target:" + target;
|
||||
}
|
||||
/* Cannot fall-through to use these methods on target, as they will not retain
|
||||
* the scope, collection and options that may already be set on the proxy
|
||||
*/
|
||||
|
||||
if (method.getName().equals("withOptions")) {
|
||||
return Proxy.newProxyInstance(repositoryClass.getClassLoader(), target.getClass().getInterfaces(),
|
||||
new DynamicInvocationHandler<>(target, (CommonOptions) args[0], collection, scope));
|
||||
}
|
||||
|
||||
if (method.getName().equals("withScope")) {
|
||||
return Proxy.newProxyInstance(repositoryClass.getClassLoader(), target.getClass().getInterfaces(),
|
||||
new DynamicInvocationHandler<>(target, options, collection, (String) args[0]));
|
||||
}
|
||||
|
||||
if (method.getName().equals("withCollection")) {
|
||||
return Proxy.newProxyInstance(repositoryClass.getClassLoader(), target.getClass().getInterfaces(),
|
||||
new DynamicInvocationHandler<>(target, options, (String) args[0], scope));
|
||||
}
|
||||
|
||||
Class<?>[] paramTypes = null;
|
||||
if (args != null) {
|
||||
// the CouchbaseRepository methods - save(entity) etc - will have a parameter type of Object instead of entityType
|
||||
// so change the paramType to match
|
||||
paramTypes = Arrays.stream(args)
|
||||
.map(o -> o == null ? null : (o.getClass() == entityInformation.getJavaType() ? Object.class : o.getClass()))
|
||||
.toArray(Class<?>[]::new);
|
||||
// the CouchbaseRepository methods - findById(id) etc - will have a parameter type of Object instead of ID
|
||||
if (method.getName().endsWith("ById") && args.length == 1) {
|
||||
paramTypes[0] = Object.class;
|
||||
}
|
||||
}
|
||||
|
||||
Method theMethod = repositoryClass.getMethod(method.getName(), paramTypes);
|
||||
Object result;
|
||||
|
||||
try {
|
||||
setThreadLocal();
|
||||
result = theMethod.invoke(target, args);
|
||||
} catch (InvocationTargetException ite) {
|
||||
throw ite.getCause();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private void setThreadLocal() {
|
||||
if (reactiveTemplate.getPseudoArgs() != null) {
|
||||
throw new RuntimeException("pseudoArgs not yet consumed by previous caller");
|
||||
}
|
||||
reactiveTemplate.setPseudoArgs(new PseudoArgs(this.scope, this.collection, this.options));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2017-2020 the original author or authors.
|
||||
* Copyright 2017-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -66,7 +66,7 @@ public class ReactiveCouchbaseRepositoryFactory extends ReactiveRepositoryFactor
|
||||
* @param couchbaseOperationsMapping the template for the underlying actions.
|
||||
*/
|
||||
public ReactiveCouchbaseRepositoryFactory(final ReactiveRepositoryOperationsMapping couchbaseOperationsMapping) {
|
||||
Assert.notNull(couchbaseOperationsMapping);
|
||||
Assert.notNull(couchbaseOperationsMapping, "couchbaseOperationsMapping");
|
||||
|
||||
this.couchbaseOperationsMapping = couchbaseOperationsMapping;
|
||||
this.crudMethodMetadataPostProcessor = new CrudMethodMetadataPostProcessor();
|
||||
@@ -85,14 +85,14 @@ public class ReactiveCouchbaseRepositoryFactory extends ReactiveRepositoryFactor
|
||||
* Returns entity information based on the domain class.
|
||||
*
|
||||
* @param domainClass the class for the entity.
|
||||
* @param <T> the value type
|
||||
* @param <ID> the id type.
|
||||
* @param <T> the value type
|
||||
* @param <ID> the id type.
|
||||
* @return entity information for that domain class.
|
||||
*/
|
||||
@Override
|
||||
public <T, ID> CouchbaseEntityInformation<T, ID> getEntityInformation(Class<T> domainClass) {
|
||||
CouchbasePersistentEntity<T> entity = (CouchbasePersistentEntity<T>) mappingContext.getRequiredPersistentEntity(
|
||||
domainClass);
|
||||
CouchbasePersistentEntity<T> entity = (CouchbasePersistentEntity<T>) mappingContext
|
||||
.getRequiredPersistentEntity(domainClass);
|
||||
return new MappingCouchbaseEntityInformation<>(entity);
|
||||
}
|
||||
|
||||
@@ -107,11 +107,11 @@ public class ReactiveCouchbaseRepositoryFactory extends ReactiveRepositoryFactor
|
||||
*/
|
||||
@Override
|
||||
protected final Object getTargetRepository(final RepositoryInformation metadata) {
|
||||
ReactiveCouchbaseOperations couchbaseOperations = couchbaseOperationsMapping.resolve(
|
||||
metadata.getRepositoryInterface(), metadata.getDomainType());
|
||||
ReactiveCouchbaseOperations couchbaseOperations = couchbaseOperationsMapping
|
||||
.resolve(metadata.getRepositoryInterface(), metadata.getDomainType());
|
||||
CouchbaseEntityInformation<?, Serializable> entityInformation = getEntityInformation(metadata.getDomainType());
|
||||
SimpleReactiveCouchbaseRepository repository = getTargetRepositoryViaReflection(metadata, entityInformation,
|
||||
couchbaseOperations);
|
||||
couchbaseOperations, metadata.getRepositoryInterface());
|
||||
repository.setRepositoryMethodMetadata(crudMethodMetadataPostProcessor.getCrudMethodMetadata());
|
||||
return repository;
|
||||
}
|
||||
@@ -150,8 +150,8 @@ public class ReactiveCouchbaseRepositoryFactory extends ReactiveRepositoryFactor
|
||||
@Override
|
||||
public RepositoryQuery resolveQuery(Method method, RepositoryMetadata metadata, ProjectionFactory factory,
|
||||
NamedQueries namedQueries) {
|
||||
final ReactiveCouchbaseOperations couchbaseOperations = couchbaseOperationsMapping.resolve(
|
||||
metadata.getRepositoryInterface(), metadata.getDomainType());
|
||||
final ReactiveCouchbaseOperations couchbaseOperations = couchbaseOperationsMapping
|
||||
.resolve(metadata.getRepositoryInterface(), metadata.getDomainType());
|
||||
ReactiveCouchbaseQueryMethod queryMethod = new ReactiveCouchbaseQueryMethod(method, metadata, factory,
|
||||
mappingContext);
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2020 the original author or authors.
|
||||
* Copyright 2013-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -44,34 +44,27 @@ import com.couchbase.client.java.query.QueryScanConsistency;
|
||||
* @author Michael Nitschinger
|
||||
* @author Mark Paluch
|
||||
* @author Jens Schauder
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
public class SimpleCouchbaseRepository<T, ID> implements CouchbaseRepository<T, ID> {
|
||||
public class SimpleCouchbaseRepository<T, ID> extends CouchbaseRepositoryBase<T, ID>
|
||||
implements CouchbaseRepository<T, ID> {
|
||||
|
||||
/**
|
||||
* Holds the reference to the {@link org.springframework.data.couchbase.core.CouchbaseTemplate}.
|
||||
*/
|
||||
private final CouchbaseOperations couchbaseOperations;
|
||||
|
||||
/**
|
||||
* Contains information about the entity being used in this repository.
|
||||
*/
|
||||
private final CouchbaseEntityInformation<T, String> entityInformation;
|
||||
|
||||
private CrudMethodMetadata crudMethodMetadata;
|
||||
private final CouchbaseOperations operations;
|
||||
|
||||
/**
|
||||
* Create a new Repository.
|
||||
*
|
||||
* @param entityInformation the Metadata for the entity.
|
||||
* @param couchbaseOperations the reference to the template used.
|
||||
* @param repositoryInterface the repository interface being fronted
|
||||
*/
|
||||
public SimpleCouchbaseRepository(CouchbaseEntityInformation<T, String> entityInformation,
|
||||
CouchbaseOperations couchbaseOperations) {
|
||||
Assert.notNull(entityInformation, "CouchbaseEntityInformation must not be null!");
|
||||
Assert.notNull(couchbaseOperations, "CouchbaseOperations must not be null!");
|
||||
|
||||
this.entityInformation = entityInformation;
|
||||
this.couchbaseOperations = couchbaseOperations;
|
||||
CouchbaseOperations couchbaseOperations, Class<?> repositoryInterface) {
|
||||
super(entityInformation, repositoryInterface);
|
||||
this.operations = couchbaseOperations;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -79,11 +72,13 @@ public class SimpleCouchbaseRepository<T, ID> implements CouchbaseRepository<T,
|
||||
public <S extends T> S save(S entity) {
|
||||
Assert.notNull(entity, "Entity must not be null!");
|
||||
// if entity has non-null, non-zero version property, then replace()
|
||||
if (hasNonZeroVersionProperty(entity, couchbaseOperations.getConverter())) {
|
||||
return (S) couchbaseOperations.replaceById(entityInformation.getJavaType()).one(entity);
|
||||
S result;
|
||||
if (hasNonZeroVersionProperty(entity, operations.getConverter())) {
|
||||
result = (S) operations.replaceById(getJavaType()).inScope(getScope()).inCollection(getCollection()).one(entity);
|
||||
} else {
|
||||
return (S) couchbaseOperations.upsertById(entityInformation.getJavaType()).one(entity);
|
||||
result = (S) operations.upsertById(getJavaType()).inScope(getScope()).inCollection(getCollection()).one(entity);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -95,57 +90,61 @@ public class SimpleCouchbaseRepository<T, ID> implements CouchbaseRepository<T,
|
||||
@Override
|
||||
public Optional<T> findById(ID id) {
|
||||
Assert.notNull(id, "The given id must not be null!");
|
||||
return Optional.ofNullable(couchbaseOperations.findById(entityInformation.getJavaType()).one(id.toString()));
|
||||
return Optional.ofNullable(
|
||||
operations.findById(getJavaType()).inScope(getScope()).inCollection(getCollection()).one(id.toString()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<T> findAllById(Iterable<ID> ids) {
|
||||
Assert.notNull(ids, "The given Iterable of ids must not be null!");
|
||||
List<String> convertedIds = Streamable.of(ids).stream().map(Objects::toString).collect(Collectors.toList());
|
||||
Collection<? extends T> all = couchbaseOperations.findById(entityInformation.getJavaType()).all(convertedIds);
|
||||
Collection<? extends T> all = operations.findById(getJavaType()).inScope(getScope()).inCollection(getCollection())
|
||||
.all(convertedIds);
|
||||
return Streamable.of(all).stream().collect(StreamUtils.toUnmodifiableList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean existsById(ID id) {
|
||||
Assert.notNull(id, "The given id must not be null!");
|
||||
return couchbaseOperations.existsById().one(id.toString());
|
||||
return operations.existsById(getJavaType()).inScope(getScope()).inCollection(getCollection()).one(id.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteById(ID id) {
|
||||
Assert.notNull(id, "The given id must not be null!");
|
||||
couchbaseOperations.removeById().one(id.toString());
|
||||
operations.removeById(getJavaType()).inScope(getScope()).inCollection(getCollection()).one(id.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(T entity) {
|
||||
Assert.notNull(entity, "Entity must not be null!");
|
||||
couchbaseOperations.removeById().one(entityInformation.getId(entity));
|
||||
operations.removeById(getJavaType()).inScope(getScope()).inCollection(getCollection()).one(getId(entity));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteAllById(Iterable<? extends ID> ids) {
|
||||
Assert.notNull(ids, "The given Iterable of ids must not be null!");
|
||||
couchbaseOperations.removeById().all(Streamable.of(ids).map(Objects::toString).toList());
|
||||
operations.removeById(getJavaType()).inScope(getScope()).inCollection(getCollection())
|
||||
.all(Streamable.of(ids).map(Objects::toString).toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteAll(Iterable<? extends T> entities) {
|
||||
Assert.notNull(entities, "The given Iterable of entities must not be null!");
|
||||
couchbaseOperations.removeById().all(Streamable.of(entities).map(entityInformation::getId).toList());
|
||||
operations.removeById(getJavaType()).inScope(getScope()).inCollection(getCollection())
|
||||
.all(Streamable.of(entities).map(this::getId).toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public long count() {
|
||||
return couchbaseOperations.findByQuery(entityInformation.getJavaType()).withConsistency(buildQueryScanConsistency())
|
||||
.count();
|
||||
return operations.findByQuery(getJavaType()).withConsistency(buildQueryScanConsistency()).inScope(getScope())
|
||||
.inCollection(getCollection()).count();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteAll() {
|
||||
couchbaseOperations.removeByQuery(entityInformation.getJavaType()).withConsistency(buildQueryScanConsistency())
|
||||
.all();
|
||||
operations.removeByQuery(getJavaType()).withConsistency(buildQueryScanConsistency()).inScope(getScope())
|
||||
.inCollection(getCollection()).all();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -169,15 +168,6 @@ public class SimpleCouchbaseRepository<T, ID> implements CouchbaseRepository<T,
|
||||
return new PageImpl<>(results, pageable, count());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the information for the underlying template.
|
||||
*
|
||||
* @return the underlying entity information.
|
||||
*/
|
||||
protected CouchbaseEntityInformation<T, String> getEntityInformation() {
|
||||
return entityInformation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to assemble a n1ql find all query, taking annotations into acocunt.
|
||||
*
|
||||
@@ -185,25 +175,13 @@ public class SimpleCouchbaseRepository<T, ID> implements CouchbaseRepository<T,
|
||||
* @return the list of found entities, already executed.
|
||||
*/
|
||||
private List<T> findAll(Query query) {
|
||||
return couchbaseOperations.findByQuery(entityInformation.getJavaType()).withConsistency(buildQueryScanConsistency())
|
||||
.matching(query).all();
|
||||
return operations.findByQuery(getJavaType()).withConsistency(buildQueryScanConsistency()).inScope(getScope())
|
||||
.inCollection(getCollection()).matching(query).all();
|
||||
}
|
||||
|
||||
private QueryScanConsistency buildQueryScanConsistency() {
|
||||
QueryScanConsistency scanConsistency = QueryScanConsistency.NOT_BOUNDED;
|
||||
if (crudMethodMetadata.getScanConsistency() != null) {
|
||||
scanConsistency = crudMethodMetadata.getScanConsistency().query();
|
||||
}
|
||||
return scanConsistency;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for the repository metadata, contains annotations on the overidden methods.
|
||||
*
|
||||
* @param crudMethodMetadata the injected repository metadata.
|
||||
*/
|
||||
void setRepositoryMethodMetadata(CrudMethodMetadata crudMethodMetadata) {
|
||||
this.crudMethodMetadata = crudMethodMetadata;
|
||||
@Override
|
||||
public CouchbaseOperations getOperations() {
|
||||
return operations;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2017-2020 the original author or authors.
|
||||
* Copyright 2017-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -35,8 +35,6 @@ import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.util.Streamable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.couchbase.client.java.query.QueryScanConsistency;
|
||||
|
||||
/**
|
||||
* Reactive repository base implementation for Couchbase.
|
||||
*
|
||||
@@ -46,22 +44,17 @@ import com.couchbase.client.java.query.QueryScanConsistency;
|
||||
* @author David Kelly
|
||||
* @author Douglas Six
|
||||
* @author Jens Schauder
|
||||
* @author Michael Reiche
|
||||
* @since 3.0
|
||||
*/
|
||||
public class SimpleReactiveCouchbaseRepository<T, ID> implements ReactiveCouchbaseRepository<T, ID> {
|
||||
public class SimpleReactiveCouchbaseRepository<T, ID> extends CouchbaseRepositoryBase<T, ID>
|
||||
implements ReactiveCouchbaseRepository<T, ID> {
|
||||
|
||||
/**
|
||||
* Holds the reference to the {@link CouchbaseOperations}.
|
||||
*/
|
||||
private final ReactiveCouchbaseOperations operations;
|
||||
|
||||
/**
|
||||
* Contains information about the entity being used in this repository.
|
||||
*/
|
||||
private final CouchbaseEntityInformation<T, String> entityInformation;
|
||||
|
||||
private CrudMethodMetadata crudMethodMetadata;
|
||||
|
||||
/**
|
||||
* Create a new Repository.
|
||||
*
|
||||
@@ -69,11 +62,8 @@ public class SimpleReactiveCouchbaseRepository<T, ID> implements ReactiveCouchba
|
||||
* @param operations the reference to the reactive template used.
|
||||
*/
|
||||
public SimpleReactiveCouchbaseRepository(CouchbaseEntityInformation<T, String> entityInformation,
|
||||
ReactiveCouchbaseOperations operations) {
|
||||
Assert.notNull(operations, "ReactiveCouchbaseOperations must not be null!");
|
||||
Assert.notNull(entityInformation, "CouchbaseEntityInformation must not be null!");
|
||||
|
||||
this.entityInformation = entityInformation;
|
||||
ReactiveCouchbaseOperations operations, Class<?> repositoryInterface) {
|
||||
super(entityInformation, repositoryInterface);
|
||||
this.operations = operations;
|
||||
}
|
||||
|
||||
@@ -81,12 +71,16 @@ public class SimpleReactiveCouchbaseRepository<T, ID> implements ReactiveCouchba
|
||||
@Override
|
||||
public <S extends T> Mono<S> save(S entity) {
|
||||
Assert.notNull(entity, "Entity must not be null!");
|
||||
// if entity has non-null version property, then replace()
|
||||
// if entity has non-null, non-zero version property, then replace()
|
||||
Mono<S> result;
|
||||
if (hasNonZeroVersionProperty(entity, operations.getConverter())) {
|
||||
return (Mono<S>) operations.replaceById(entityInformation.getJavaType()).one(entity);
|
||||
result = (Mono<S>) operations.replaceById(getJavaType()).inScope(getScope()).inCollection(getCollection())
|
||||
.one(entity);
|
||||
} else {
|
||||
return (Mono<S>) operations.upsertById(entityInformation.getJavaType()).one(entity);
|
||||
result = (Mono<S>) operations.upsertById(getJavaType()).inScope(getScope()).inCollection(getCollection())
|
||||
.one(entity);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -97,7 +91,7 @@ public class SimpleReactiveCouchbaseRepository<T, ID> implements ReactiveCouchba
|
||||
@Override
|
||||
public <S extends T> Flux<S> saveAll(Iterable<S> entities) {
|
||||
Assert.notNull(entities, "The given Iterable of entities must not be null!");
|
||||
return Flux.fromIterable(entities).flatMap(this::save);
|
||||
return Flux.fromIterable(entities).flatMap(e -> save(e));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -108,7 +102,7 @@ public class SimpleReactiveCouchbaseRepository<T, ID> implements ReactiveCouchba
|
||||
|
||||
@Override
|
||||
public Mono<T> findById(ID id) {
|
||||
return operations.findById(entityInformation.getJavaType()).one(id.toString());
|
||||
return operations.findById(getJavaType()).inScope(getScope()).inCollection(getCollection()).one(id.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -120,7 +114,7 @@ public class SimpleReactiveCouchbaseRepository<T, ID> implements ReactiveCouchba
|
||||
@Override
|
||||
public Mono<Boolean> existsById(ID id) {
|
||||
Assert.notNull(id, "The given id must not be null!");
|
||||
return operations.existsById().one(id.toString());
|
||||
return operations.existsById(getJavaType()).inScope(getScope()).inCollection(getCollection()).one(id.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -139,7 +133,8 @@ public class SimpleReactiveCouchbaseRepository<T, ID> implements ReactiveCouchba
|
||||
public Flux<T> findAllById(Iterable<ID> ids) {
|
||||
Assert.notNull(ids, "The given Iterable of ids must not be null!");
|
||||
List<String> convertedIds = Streamable.of(ids).stream().map(Objects::toString).collect(Collectors.toList());
|
||||
return (Flux<T>) operations.findById(entityInformation.getJavaType()).all(convertedIds);
|
||||
return (Flux<T>) operations.findById(getJavaType()).inScope(getScope()).inCollection(getCollection())
|
||||
.all(convertedIds);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -150,7 +145,8 @@ public class SimpleReactiveCouchbaseRepository<T, ID> implements ReactiveCouchba
|
||||
|
||||
@Override
|
||||
public Mono<Void> deleteById(ID id) {
|
||||
return operations.removeById().one(id.toString()).then();
|
||||
return operations.removeById(getJavaType()).inScope(getScope()).inCollection(getCollection()).one(id.toString())
|
||||
.then();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -162,17 +158,20 @@ public class SimpleReactiveCouchbaseRepository<T, ID> implements ReactiveCouchba
|
||||
@Override
|
||||
public Mono<Void> delete(T entity) {
|
||||
Assert.notNull(entity, "Entity must not be null!");
|
||||
return operations.removeById().one(entityInformation.getId(entity)).then();
|
||||
return operations.removeById(getJavaType()).inScope(getScope()).inCollection(getCollection()).one(getId(entity))
|
||||
.then();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> deleteAllById(Iterable<? extends ID> ids) {
|
||||
return operations.removeById().all(Streamable.of(ids).map(Object::toString).toList()).then();
|
||||
return operations.removeById(getJavaType()).inScope(getScope()).inCollection(getCollection())
|
||||
.all(Streamable.of(ids).map(Object::toString).toList()).then();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> deleteAll(Iterable<? extends T> entities) {
|
||||
return operations.removeById().all(Streamable.of(entities).map(entityInformation::getId).toList()).then();
|
||||
return operations.removeById(getJavaType()).inScope(getScope()).inCollection(getCollection())
|
||||
.all(Streamable.of(entities).map(this::getId).toList()).then();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -183,43 +182,24 @@ public class SimpleReactiveCouchbaseRepository<T, ID> implements ReactiveCouchba
|
||||
|
||||
@Override
|
||||
public Mono<Long> count() {
|
||||
return operations.findByQuery(entityInformation.getJavaType()).withConsistency(buildQueryScanConsistency()).count();
|
||||
return operations.findByQuery(getJavaType()).withConsistency(buildQueryScanConsistency()).inScope(getScope())
|
||||
.inCollection(getCollection()).count();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> deleteAll() {
|
||||
return operations.removeByQuery(entityInformation.getJavaType()).withConsistency(buildQueryScanConsistency()).all().then();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the information for the underlying template.
|
||||
*
|
||||
* @return the underlying entity information.
|
||||
*/
|
||||
protected CouchbaseEntityInformation<T, String> getEntityInformation() {
|
||||
return entityInformation;
|
||||
return operations.removeByQuery(getJavaType()).withConsistency(buildQueryScanConsistency()).inScope(getScope())
|
||||
.inCollection(getCollection()).all().then();
|
||||
}
|
||||
|
||||
private Flux<T> findAll(Query query) {
|
||||
return operations.findByQuery(entityInformation.getJavaType()).withConsistency(buildQueryScanConsistency())
|
||||
.matching(query).all();
|
||||
return operations.findByQuery(getJavaType()).withConsistency(buildQueryScanConsistency()).inScope(getScope())
|
||||
.inCollection(getCollection()).matching(query).all();
|
||||
}
|
||||
|
||||
private QueryScanConsistency buildQueryScanConsistency() {
|
||||
QueryScanConsistency scanConsistency = QueryScanConsistency.NOT_BOUNDED;
|
||||
if (crudMethodMetadata.getScanConsistency() != null) {
|
||||
scanConsistency = crudMethodMetadata.getScanConsistency().query();
|
||||
}
|
||||
return scanConsistency;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for the repository metadata, contains annotations on the overidden methods.
|
||||
*
|
||||
* @param crudMethodMetadata the injected repository metadata.
|
||||
*/
|
||||
void setRepositoryMethodMetadata(CrudMethodMetadata crudMethodMetadata) {
|
||||
this.crudMethodMetadata = crudMethodMetadata;
|
||||
@Override
|
||||
public ReactiveCouchbaseOperations getOperations() {
|
||||
return operations;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -31,7 +31,6 @@ import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
import com.couchbase.client.java.query.QueryScanConsistency;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.dao.DataIntegrityViolationException;
|
||||
@@ -50,6 +49,7 @@ import org.springframework.data.couchbase.util.JavaIntegrationTests;
|
||||
|
||||
import com.couchbase.client.java.kv.PersistTo;
|
||||
import com.couchbase.client.java.kv.ReplicateTo;
|
||||
import com.couchbase.client.java.query.QueryScanConsistency;
|
||||
|
||||
;
|
||||
|
||||
@@ -121,7 +121,7 @@ class CouchbaseTemplateKeyValueIntegrationTests extends JavaIntegrationTests {
|
||||
returned = (User) operator.one(user);
|
||||
break;
|
||||
} catch (Exception ofe) {
|
||||
System.out.println(""+i+" caught: "+ofe);
|
||||
System.out.println("" + i + " caught: " + ofe);
|
||||
couchbaseTemplate.removeByQuery(User.class).withConsistency(QueryScanConsistency.REQUEST_PLUS).all();
|
||||
if (i == 4) {
|
||||
throw ofe;
|
||||
@@ -259,7 +259,7 @@ class CouchbaseTemplateKeyValueIntegrationTests extends JavaIntegrationTests {
|
||||
.one(user);
|
||||
break;
|
||||
} catch (Exception ofe) {
|
||||
System.out.println(""+i+" caught: "+ofe);
|
||||
System.out.println("" + i + " caught: " + ofe);
|
||||
couchbaseTemplate.removeByQuery(User.class).withConsistency(QueryScanConsistency.REQUEST_PLUS).all();
|
||||
if (i == 4) {
|
||||
throw ofe;
|
||||
|
||||
@@ -36,6 +36,7 @@ import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.dao.DataRetrievalFailureException;
|
||||
import org.springframework.data.couchbase.core.query.Query;
|
||||
import org.springframework.data.couchbase.core.query.QueryCriteria;
|
||||
import org.springframework.data.couchbase.domain.Address;
|
||||
@@ -44,18 +45,19 @@ import org.springframework.data.couchbase.domain.Course;
|
||||
import org.springframework.data.couchbase.domain.NaiveAuditorAware;
|
||||
import org.springframework.data.couchbase.domain.Submission;
|
||||
import org.springframework.data.couchbase.domain.User;
|
||||
import org.springframework.data.couchbase.domain.UserCol;
|
||||
import org.springframework.data.couchbase.domain.UserJustLastName;
|
||||
import org.springframework.data.couchbase.domain.UserSubmission;
|
||||
import org.springframework.data.couchbase.domain.UserSubmissionProjected;
|
||||
import org.springframework.data.couchbase.domain.time.AuditingDateTimeProvider;
|
||||
import org.springframework.data.couchbase.util.Capabilities;
|
||||
import org.springframework.data.couchbase.util.ClusterAwareIntegrationTests;
|
||||
import org.springframework.data.couchbase.util.ClusterType;
|
||||
import org.springframework.data.couchbase.util.CollectionAwareIntegrationTests;
|
||||
import org.springframework.data.couchbase.util.IgnoreWhen;
|
||||
|
||||
import com.couchbase.client.core.error.AmbiguousTimeoutException;
|
||||
import com.couchbase.client.core.error.UnambiguousTimeoutException;
|
||||
import com.couchbase.client.core.io.CollectionIdentifier;
|
||||
import com.couchbase.client.java.analytics.AnalyticsOptions;
|
||||
import com.couchbase.client.java.kv.ExistsOptions;
|
||||
import com.couchbase.client.java.kv.GetAnyReplicaOptions;
|
||||
@@ -752,4 +754,51 @@ class CouchbaseTemplateQueryCollectionIntegrationTests extends CollectionAwareIn
|
||||
.inCollection(otherCollection).withOptions(options).one(vie));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testScopeCollectionAnnotation() {
|
||||
UserCol user = new UserCol("1", "Dave", "Wilson");
|
||||
Query query = Query.query(QueryCriteria.where("firstname").is(user.getFirstname()));
|
||||
try {
|
||||
UserCol saved = couchbaseTemplate.insertById(UserCol.class).inScope(scopeName).inCollection(collectionName)
|
||||
.one(user);
|
||||
List<UserCol> found = couchbaseTemplate.findByQuery(UserCol.class)
|
||||
.withConsistency(QueryScanConsistency.REQUEST_PLUS).inScope(scopeName).inCollection(collectionName)
|
||||
.matching(query).all();
|
||||
assertEquals(saved, found.get(0), "should have found what was saved");
|
||||
List<UserCol> notfound = couchbaseTemplate.findByQuery(UserCol.class).inScope(CollectionIdentifier.DEFAULT_SCOPE)
|
||||
.inCollection(CollectionIdentifier.DEFAULT_COLLECTION).matching(query).all();
|
||||
assertEquals(0, notfound.size(), "should not have found what was saved");
|
||||
couchbaseTemplate.removeByQuery(UserCol.class).inScope(scopeName).inCollection(collectionName).matching(query)
|
||||
.all();
|
||||
} finally {
|
||||
try {
|
||||
couchbaseTemplate.removeByQuery(UserCol.class).inScope(scopeName).inCollection(collectionName).matching(query)
|
||||
.all();
|
||||
} catch (DataRetrievalFailureException drfe) {}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testScopeCollectionRepoWith() {
|
||||
UserCol user = new UserCol("1", "Dave", "Wilson");
|
||||
Query query = Query.query(QueryCriteria.where("firstname").is(user.getFirstname()));
|
||||
try {
|
||||
UserCol saved = couchbaseTemplate.insertById(UserCol.class).inScope(scopeName).inCollection(collectionName)
|
||||
.one(user);
|
||||
List<UserCol> found = couchbaseTemplate.findByQuery(UserCol.class)
|
||||
.withConsistency(QueryScanConsistency.REQUEST_PLUS).inScope(scopeName).inCollection(collectionName)
|
||||
.matching(query).all();
|
||||
assertEquals(saved, found.get(0), "should have found what was saved");
|
||||
List<UserCol> notfound = couchbaseTemplate.findByQuery(UserCol.class).inScope(CollectionIdentifier.DEFAULT_SCOPE)
|
||||
.inCollection(CollectionIdentifier.DEFAULT_COLLECTION).matching(query).all();
|
||||
assertEquals(0, notfound.size(), "should not have found what was saved");
|
||||
couchbaseTemplate.removeByQuery(UserCol.class).inScope(scopeName).inCollection(collectionName).matching(query)
|
||||
.all();
|
||||
} finally {
|
||||
try {
|
||||
couchbaseTemplate.removeByQuery(UserCol.class).inScope(scopeName).inCollection(collectionName).matching(query)
|
||||
.all();
|
||||
} catch (DataRetrievalFailureException drfe) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,8 @@
|
||||
|
||||
package org.springframework.data.couchbase.core;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.UUID;
|
||||
|
||||
@@ -150,7 +150,8 @@ class ReactiveCouchbaseTemplateKeyValueIntegrationTests extends JavaIntegrationT
|
||||
}
|
||||
|
||||
// if replace or remove, we need to insert a document to replace
|
||||
if (operator instanceof ReactiveReplaceByIdOperation.ReactiveReplaceById || operator instanceof ExecutableRemoveById) {
|
||||
if (operator instanceof ReactiveReplaceByIdOperation.ReactiveReplaceById
|
||||
|| operator instanceof ExecutableRemoveById) {
|
||||
reactiveCouchbaseTemplate.insertById(User.class).one(user).block();
|
||||
}
|
||||
// call to insert/replace/update
|
||||
|
||||
@@ -19,8 +19,8 @@ package org.springframework.data.couchbase.domain;
|
||||
import org.springframework.data.annotation.CreatedBy;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.annotation.PersistenceConstructor;
|
||||
import org.springframework.data.annotation.Version;
|
||||
import org.springframework.data.annotation.TypeAlias;
|
||||
import org.springframework.data.annotation.Version;
|
||||
import org.springframework.data.couchbase.core.mapping.Document;
|
||||
|
||||
/**
|
||||
@@ -42,7 +42,6 @@ public class Airport extends ComparableEntity {
|
||||
|
||||
@CreatedBy private String createdBy;
|
||||
|
||||
|
||||
@PersistenceConstructor
|
||||
public Airport(String id, String iata, String icao) {
|
||||
this.id = id;
|
||||
@@ -78,6 +77,7 @@ public class Airport extends ComparableEntity {
|
||||
version = Long.valueOf(0);
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getCreatedBy() {
|
||||
return createdBy;
|
||||
}
|
||||
|
||||
@@ -16,18 +16,33 @@
|
||||
|
||||
package org.springframework.data.couchbase.domain;
|
||||
|
||||
import static com.couchbase.client.core.io.CollectionIdentifier.DEFAULT_COLLECTION;
|
||||
import static com.couchbase.client.core.io.CollectionIdentifier.DEFAULT_SCOPE;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.springframework.core.annotation.AliasFor;
|
||||
import org.springframework.data.couchbase.core.RemoveResult;
|
||||
import org.springframework.data.couchbase.core.mapping.Expiry;
|
||||
import org.springframework.data.couchbase.repository.Collection;
|
||||
import org.springframework.data.couchbase.repository.CouchbaseRepository;
|
||||
import org.springframework.data.couchbase.repository.DynamicProxyable;
|
||||
import org.springframework.data.couchbase.repository.Options;
|
||||
import org.springframework.data.couchbase.repository.Query;
|
||||
import org.springframework.data.couchbase.repository.ScanConsistency;
|
||||
import org.springframework.data.couchbase.repository.Scope;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import com.couchbase.client.java.analytics.AnalyticsScanConsistency;
|
||||
import com.couchbase.client.java.query.QueryScanConsistency;
|
||||
|
||||
/**
|
||||
@@ -41,8 +56,11 @@ import com.couchbase.client.java.query.QueryScanConsistency;
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
@Repository
|
||||
public interface AirportRepository extends CouchbaseRepository<Airport, String> {
|
||||
// @Scope("repositoryScope")
|
||||
// @ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
|
||||
public interface AirportRepository extends CouchbaseRepository<Airport, String>, DynamicProxyable<AirportRepository> {
|
||||
|
||||
// override an annotate with REQUEST_PLUS
|
||||
@Override
|
||||
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
|
||||
List<Airport> findAll();
|
||||
@@ -51,11 +69,16 @@ public interface AirportRepository extends CouchbaseRepository<Airport, String>
|
||||
List<Airport> findAllByIata(String iata);
|
||||
|
||||
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
|
||||
@ComposedMetaAnnotation(collection = "_default", timeoutMs = 1000)
|
||||
Airport findByIata(String iata);
|
||||
|
||||
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
|
||||
Airport findByIata(Iata iata);
|
||||
|
||||
// NOT_BOUNDED to test ScanConsistency
|
||||
// @ScanConsistency(query = QueryScanConsistency.NOT_BOUNDED)
|
||||
Airport iata(String iata);
|
||||
|
||||
@Query("#{#n1ql.selectEntity} where iata = $1")
|
||||
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
|
||||
List<Airport> getAllByIata(String iata);
|
||||
@@ -97,4 +120,42 @@ public interface AirportRepository extends CouchbaseRepository<Airport, String>
|
||||
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
|
||||
Optional<Airport> findByIdAndIata(String id, String iata);
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ ElementType.METHOD, ElementType.TYPE })
|
||||
// @Meta
|
||||
@Scope
|
||||
@Collection
|
||||
@ScanConsistency
|
||||
@Expiry
|
||||
@Options
|
||||
public @interface ComposedMetaAnnotation {
|
||||
|
||||
// @AliasFor(annotation = Meta.class, attribute = "maxExecutionTimeMs")
|
||||
// long execTime() default -1;
|
||||
|
||||
@AliasFor(annotation = ScanConsistency.class, attribute = "query")
|
||||
QueryScanConsistency query() default QueryScanConsistency.NOT_BOUNDED;
|
||||
|
||||
@AliasFor(annotation = ScanConsistency.class, attribute = "analytics")
|
||||
AnalyticsScanConsistency analytics() default AnalyticsScanConsistency.NOT_BOUNDED;
|
||||
|
||||
@AliasFor(annotation = Scope.class, attribute = "value")
|
||||
String scope() default DEFAULT_SCOPE;
|
||||
|
||||
@AliasFor(annotation = Collection.class, attribute = "value")
|
||||
String collection() default DEFAULT_COLLECTION;
|
||||
|
||||
@AliasFor(annotation = Expiry.class, attribute = "expiry")
|
||||
int expiry() default 0;
|
||||
|
||||
@AliasFor(annotation = Expiry.class, attribute = "expiryUnit")
|
||||
TimeUnit expiryUnit() default TimeUnit.SECONDS;
|
||||
|
||||
@AliasFor(annotation = Expiry.class, attribute = "expiryExpression")
|
||||
String expiryExpression() default "";
|
||||
|
||||
@AliasFor(annotation = Options.class, attribute = "timeoutMs")
|
||||
long timeoutMs() default 0;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ import org.springframework.data.couchbase.domain.time.AuditingDateTimeProvider;
|
||||
import org.springframework.data.couchbase.repository.auditing.EnableCouchbaseAuditing;
|
||||
import org.springframework.data.couchbase.repository.auditing.EnableReactiveCouchbaseAuditing;
|
||||
import org.springframework.data.couchbase.repository.config.EnableCouchbaseRepositories;
|
||||
import org.springframework.data.couchbase.repository.config.EnableReactiveCouchbaseRepositories;
|
||||
import org.springframework.data.couchbase.repository.config.ReactiveRepositoryOperationsMapping;
|
||||
import org.springframework.data.couchbase.repository.config.RepositoryOperationsMapping;
|
||||
|
||||
@@ -52,8 +53,10 @@ import com.couchbase.client.java.json.JacksonTransformers;
|
||||
*/
|
||||
@Configuration
|
||||
@EnableCouchbaseRepositories
|
||||
@EnableCouchbaseAuditing(auditorAwareRef="auditorAwareRef", dateTimeProviderRef="dateTimeProviderRef") // this activates auditing
|
||||
@EnableReactiveCouchbaseAuditing(auditorAwareRef="reactiveAuditorAwareRef", dateTimeProviderRef="dateTimeProviderRef") // this activates auditing
|
||||
@EnableReactiveCouchbaseRepositories
|
||||
@EnableCouchbaseAuditing(auditorAwareRef = "auditorAwareRef", dateTimeProviderRef = "dateTimeProviderRef")
|
||||
@EnableReactiveCouchbaseAuditing(auditorAwareRef = "reactiveAuditorAwareRef",
|
||||
dateTimeProviderRef = "dateTimeProviderRef")
|
||||
|
||||
public class Config extends AbstractCouchbaseConfiguration {
|
||||
String bucketname = "travel-sample";
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
package org.springframework.data.couchbase.domain;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.springframework.data.couchbase.repository.Query;
|
||||
@@ -92,7 +91,7 @@ public interface PersonRepository extends CrudRepository<Person, String> {
|
||||
|
||||
<S extends Person> Iterable<S> saveAll(Iterable<S> var1);
|
||||
|
||||
Optional<Person> findById(UUID var1);
|
||||
Person findById(UUID var1);
|
||||
|
||||
boolean existsById(UUID var1);
|
||||
|
||||
|
||||
@@ -16,19 +16,19 @@
|
||||
|
||||
package org.springframework.data.couchbase.domain;
|
||||
|
||||
import org.springframework.data.couchbase.core.RemoveResult;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import org.springframework.data.couchbase.repository.DynamicProxyable;
|
||||
import org.springframework.data.couchbase.repository.Query;
|
||||
import org.springframework.data.couchbase.repository.ReactiveCouchbaseRepository;
|
||||
import org.springframework.data.couchbase.repository.ScanConsistency;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageImpl;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.repository.reactive.ReactiveSortingRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import com.couchbase.client.java.json.JsonArray;
|
||||
@@ -41,7 +41,8 @@ import com.couchbase.client.java.query.QueryScanConsistency;
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
@Repository
|
||||
public interface ReactiveAirportRepository extends ReactiveSortingRepository<Airport, String> {
|
||||
public interface ReactiveAirportRepository
|
||||
extends ReactiveCouchbaseRepository<Airport, String>, DynamicProxyable<ReactiveAirportRepository> {
|
||||
|
||||
@Override
|
||||
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
|
||||
@@ -57,6 +58,9 @@ public interface ReactiveAirportRepository extends ReactiveSortingRepository<Air
|
||||
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
|
||||
Flux<Airport> findAllByIata(String iata);
|
||||
|
||||
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
|
||||
Mono<Airport> iata(String iata);
|
||||
|
||||
@Query("#{#n1ql.selectEntity} WHERE #{#n1ql.filter}")
|
||||
Flux<Airport> findAllPoliciesByApplicableTypes(String state, JsonArray applicableTypes);
|
||||
|
||||
|
||||
@@ -15,9 +15,9 @@
|
||||
*/
|
||||
package org.springframework.data.couchbase.domain;
|
||||
|
||||
import org.springframework.data.domain.ReactiveAuditorAware;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.data.domain.ReactiveAuditorAware;
|
||||
|
||||
/**
|
||||
* This class returns a string that represents the current user
|
||||
@@ -28,6 +28,7 @@ import reactor.core.publisher.Mono;
|
||||
public class ReactiveNaiveAuditorAware implements ReactiveAuditorAware<String> {
|
||||
|
||||
public static final String AUDITOR = "reactive_auditor";
|
||||
|
||||
@Override
|
||||
public Mono<String> getCurrentAuditor() {
|
||||
return Mono.just(AUDITOR);
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright 2012-2020 the original author or authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.data.couchbase.domain;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.data.couchbase.repository.DynamicProxyable;
|
||||
import org.springframework.data.couchbase.repository.Query;
|
||||
import org.springframework.data.couchbase.repository.ReactiveCouchbaseRepository;
|
||||
import org.springframework.data.couchbase.repository.ScanConsistency;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import com.couchbase.client.java.json.JsonArray;
|
||||
import com.couchbase.client.java.query.QueryScanConsistency;
|
||||
|
||||
/**
|
||||
* User Repository for tests
|
||||
*
|
||||
* @author Michael Nitschinger
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
@Repository
|
||||
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
|
||||
public interface ReactiveUserColRepository
|
||||
extends ReactiveCouchbaseRepository<UserCol, String>, DynamicProxyable<ReactiveUserColRepository> {
|
||||
|
||||
<S extends UserCol> Mono<S> save(S var1);
|
||||
|
||||
Flux<UserCol> findByFirstname(String firstname);
|
||||
|
||||
Flux<UserCol> findByFirstnameIn(String... firstnames);
|
||||
|
||||
Flux<UserCol> findByFirstnameIn(JsonArray firstnames);
|
||||
|
||||
Flux<UserCol> findByFirstnameAndLastname(String firstname, String lastname);
|
||||
|
||||
@Query("#{#n1ql.selectEntity} where #{#n1ql.filter} and firstname = $1 and lastname = $2")
|
||||
Flux<UserCol> getByFirstnameAndLastname(String firstname, String lastname);
|
||||
|
||||
@Query("#{#n1ql.selectEntity} where #{#n1ql.filter} and (firstname = $first or lastname = $last)")
|
||||
Flux<UserCol> getByFirstnameOrLastname(@Param("first") String firstname, @Param("last") String lastname);
|
||||
|
||||
Flux<UserCol> findByIdIsNotNullAndFirstnameEquals(String firstname);
|
||||
|
||||
Flux<UserCol> findByVersionEqualsAndFirstnameEquals(Long version, String firstname);
|
||||
|
||||
}
|
||||
@@ -14,17 +14,28 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.data.couchbase.core.mapping.event;
|
||||
package org.springframework.data.couchbase.domain;
|
||||
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
|
||||
import org.springframework.data.annotation.PersistenceConstructor;
|
||||
import org.springframework.data.couchbase.core.mapping.Document;
|
||||
import org.springframework.data.couchbase.repository.Collection;
|
||||
import org.springframework.data.couchbase.repository.Scope;
|
||||
|
||||
/**
|
||||
* User entity for tests
|
||||
*
|
||||
* @author Michael Nitschinger
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
public class ReactiveAfterSaveEvent<E> extends CouchbaseMappingEvent<E> {
|
||||
|
||||
public ReactiveAfterSaveEvent(E source, CouchbaseDocument document) {
|
||||
super(source, document);
|
||||
@Document
|
||||
@Scope("other_scope")
|
||||
@Collection("other_collection")
|
||||
public class UserCol extends User {
|
||||
|
||||
@PersistenceConstructor
|
||||
public UserCol(final String id, final String firstname, final String lastname) {
|
||||
super(id, firstname, lastname);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright 2012-2020 the original author or authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.data.couchbase.domain;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.couchbase.repository.CouchbaseRepository;
|
||||
import org.springframework.data.couchbase.repository.DynamicProxyable;
|
||||
import org.springframework.data.couchbase.repository.Query;
|
||||
import org.springframework.data.couchbase.repository.ScanConsistency;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import com.couchbase.client.java.json.JsonArray;
|
||||
import com.couchbase.client.java.query.QueryScanConsistency;
|
||||
|
||||
/**
|
||||
* User Repository for tests
|
||||
*
|
||||
* @author Michael Nitschinger
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
@Repository
|
||||
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
|
||||
public interface UserColRepository extends CouchbaseRepository<UserCol, String>, DynamicProxyable<UserColRepository> {
|
||||
|
||||
<S extends UserCol> S save(S var1);
|
||||
|
||||
List<UserCol> findByFirstname(String firstname);
|
||||
|
||||
List<UserCol> findByFirstnameIn(String... firstnames);
|
||||
|
||||
List<UserCol> findByFirstnameIn(JsonArray firstnames);
|
||||
|
||||
List<UserCol> findByFirstnameAndLastname(String firstname, String lastname);
|
||||
|
||||
@Query("#{#n1ql.selectEntity} where #{#n1ql.filter} and firstname = $1 and lastname = $2")
|
||||
List<UserCol> getByFirstnameAndLastname(String firstname, String lastname);
|
||||
|
||||
@Query("#{#n1ql.selectEntity} where #{#n1ql.filter} and (firstname = $first or lastname = $last)")
|
||||
List<UserCol> getByFirstnameOrLastname(@Param("first") String firstname, @Param("last") String lastname);
|
||||
|
||||
List<UserCol> findByIdIsNotNullAndFirstnameEquals(String firstname);
|
||||
|
||||
List<UserCol> findByVersionEqualsAndFirstnameEquals(Long version, String firstname);
|
||||
|
||||
}
|
||||
@@ -20,10 +20,12 @@ import java.util.List;
|
||||
|
||||
import org.springframework.data.couchbase.repository.CouchbaseRepository;
|
||||
import org.springframework.data.couchbase.repository.Query;
|
||||
import org.springframework.data.couchbase.repository.ScanConsistency;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import com.couchbase.client.java.json.JsonArray;
|
||||
import com.couchbase.client.java.query.QueryScanConsistency;
|
||||
|
||||
/**
|
||||
* User Repository for tests
|
||||
@@ -32,6 +34,7 @@ import com.couchbase.client.java.json.JsonArray;
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
@Repository
|
||||
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
|
||||
public interface UserRepository extends CouchbaseRepository<User, String> {
|
||||
|
||||
List<User> findByFirstname(String firstname);
|
||||
@@ -51,4 +54,5 @@ public interface UserRepository extends CouchbaseRepository<User, String> {
|
||||
List<User> findByIdIsNotNullAndFirstnameEquals(String firstname);
|
||||
|
||||
List<User> findByVersionEqualsAndFirstnameEquals(Long version, String firstname);
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2017-2019 the original author or authors.
|
||||
* Copyright 2017-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -21,10 +21,12 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
@@ -41,21 +43,22 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.dao.DataIntegrityViolationException;
|
||||
import org.springframework.dao.DataRetrievalFailureException;
|
||||
import org.springframework.data.auditing.DateTimeProvider;
|
||||
import org.springframework.data.couchbase.CouchbaseClientFactory;
|
||||
import org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration;
|
||||
import org.springframework.data.couchbase.core.CouchbaseTemplate;
|
||||
import org.springframework.data.couchbase.core.RemoveResult;
|
||||
import org.springframework.data.couchbase.core.query.N1QLExpression;
|
||||
import org.springframework.data.couchbase.core.query.Query;
|
||||
import org.springframework.data.couchbase.core.query.QueryCriteria;
|
||||
import org.springframework.data.couchbase.domain.Address;
|
||||
import org.springframework.data.couchbase.domain.Airport;
|
||||
import org.springframework.data.couchbase.domain.AirportRepository;
|
||||
import org.springframework.data.couchbase.domain.Iata;
|
||||
import org.springframework.data.couchbase.domain.NaiveAuditorAware;
|
||||
import org.springframework.data.couchbase.domain.Person;
|
||||
import org.springframework.data.couchbase.domain.PersonRepository;
|
||||
import org.springframework.data.couchbase.domain.User;
|
||||
import org.springframework.data.couchbase.domain.UserAnnotated;
|
||||
import org.springframework.data.couchbase.domain.UserRepository;
|
||||
import org.springframework.data.couchbase.domain.time.AuditingDateTimeProvider;
|
||||
import org.springframework.data.couchbase.repository.auditing.EnableCouchbaseAuditing;
|
||||
@@ -73,8 +76,14 @@ import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
|
||||
import org.springframework.data.repository.core.support.DefaultRepositoryMetadata;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
|
||||
import com.couchbase.client.core.error.AmbiguousTimeoutException;
|
||||
import com.couchbase.client.core.error.CouchbaseException;
|
||||
import com.couchbase.client.core.error.IndexExistsException;
|
||||
import com.couchbase.client.core.error.IndexFailureException;
|
||||
import com.couchbase.client.java.env.ClusterEnvironment;
|
||||
import com.couchbase.client.java.json.JsonArray;
|
||||
import com.couchbase.client.java.kv.MutationState;
|
||||
import com.couchbase.client.java.query.QueryOptions;
|
||||
import com.couchbase.client.java.query.QueryScanConsistency;
|
||||
|
||||
/**
|
||||
@@ -96,6 +105,9 @@ public class CouchbaseRepositoryQueryIntegrationTests extends ClusterAwareIntegr
|
||||
|
||||
@Autowired CouchbaseTemplate couchbaseTemplate;
|
||||
|
||||
String scopeName = "_default";
|
||||
String collectionName = "_default";
|
||||
|
||||
@BeforeEach
|
||||
public void beforeEach() {
|
||||
try {
|
||||
@@ -182,12 +194,17 @@ public class CouchbaseRepositoryQueryIntegrationTests extends ClusterAwareIntegr
|
||||
try {
|
||||
vie = new Airport("airports::vie", "vie", "low6");
|
||||
vie = airportRepository.save(vie);
|
||||
Airport airport2 = airportRepository
|
||||
.withOptions(QueryOptions.queryOptions().scanConsistency(QueryScanConsistency.REQUEST_PLUS))
|
||||
.findByIata(vie.getIata());
|
||||
assertEquals(airport2, vie);
|
||||
|
||||
List<Airport> airports = airportRepository.findAllByIata("vie");
|
||||
assertEquals(1, airports.size());
|
||||
Airport airport1 = airportRepository.findById(airports.get(0).getId()).get();
|
||||
assertEquals(airport1.getIata(), vie.getIata());
|
||||
Airport airport2 = airportRepository.findByIata(airports.get(0).getIata());
|
||||
assertEquals(airport1.getId(), vie.getId());
|
||||
airport2 = airportRepository.findByIata(airports.get(0).getIata());
|
||||
assertEquals(airport2.getId(), vie.getId());
|
||||
} finally {
|
||||
airportRepository.delete(vie);
|
||||
}
|
||||
@@ -201,7 +218,9 @@ public class CouchbaseRepositoryQueryIntegrationTests extends ClusterAwareIntegr
|
||||
vie = airportRepository.save(vie);
|
||||
List<Airport> airports = couchbaseTemplate.findByQuery(Airport.class)
|
||||
.withConsistency(QueryScanConsistency.REQUEST_PLUS)
|
||||
.matching(new Query(QueryCriteria.where(N1QLExpression.x("_class")).is("airport"))).all();
|
||||
.matching(org.springframework.data.couchbase.core.query.Query
|
||||
.query(QueryCriteria.where(N1QLExpression.x("_class")).is("airport")))
|
||||
.all();
|
||||
assertFalse(airports.isEmpty(), "should have found aiport");
|
||||
} finally {
|
||||
airportRepository.delete(vie);
|
||||
@@ -214,18 +233,132 @@ public class CouchbaseRepositoryQueryIntegrationTests extends ClusterAwareIntegr
|
||||
try {
|
||||
vie = new Airport("airports::vie", "vie", "loww");
|
||||
vie = airportRepository.save(vie);
|
||||
Airport airport2 = airportRepository.findByIata(Iata.vie);
|
||||
Airport airport2 = airportRepository.findByIata(vie.getIata());
|
||||
assertNotNull(airport2, "should have found " + vie);
|
||||
assertEquals(airport2.getId(), vie.getId());
|
||||
|
||||
} finally {
|
||||
airportRepository.delete(vie);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* can test against _default._default without setting up additional scope/collection and also test for collections and
|
||||
* scopes that do not exist These same tests should be repeated on non-default scope and collection in a test that
|
||||
* supports collections
|
||||
*/
|
||||
@Test
|
||||
@IgnoreWhen(missesCapabilities = { Capabilities.QUERY, Capabilities.COLLECTIONS }, clusterTypes = ClusterType.MOCKED)
|
||||
void findBySimplePropertyWithCollection() {
|
||||
|
||||
Airport vie = new Airport("airports::vie", "vie", "low7");
|
||||
try {
|
||||
Airport saved = airportRepository.withScope(scopeName).withCollection(collectionName).save(vie);
|
||||
// given collection (on scope used by template)
|
||||
Airport airport2 = airportRepository.withCollection(collectionName)
|
||||
.withOptions(QueryOptions.queryOptions().scanConsistency(QueryScanConsistency.REQUEST_PLUS))
|
||||
.iata(vie.getIata());
|
||||
assertEquals(saved, airport2);
|
||||
|
||||
// given scope and collection
|
||||
|
||||
Airport airport3 = airportRepository.withScope(scopeName).withCollection(collectionName)
|
||||
.withOptions(QueryOptions.queryOptions().scanConsistency(QueryScanConsistency.REQUEST_PLUS))
|
||||
.iata(vie.getIata());
|
||||
assertEquals(saved, airport3);
|
||||
|
||||
// given bad collection
|
||||
assertThrows(IndexFailureException.class,
|
||||
() -> airportRepository.withCollection("bogusCollection").iata(vie.getIata()));
|
||||
|
||||
// given bad scope
|
||||
assertThrows(IndexFailureException.class, () -> airportRepository.withScope("bogusScope").iata(vie.getIata()));
|
||||
|
||||
} finally {
|
||||
airportRepository.delete(vie);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@IgnoreWhen(hasCapabilities = { Capabilities.COLLECTIONS }, clusterTypes = ClusterType.MOCKED)
|
||||
void findBySimplePropertyWithCollectionFail() {
|
||||
// can test against _default._default without setting up additional scope/collection
|
||||
// the server will throw an exception if it doesn't support COLLECTIONS
|
||||
Airport vie = new Airport("airports::vie", "vie", "low8");
|
||||
try {
|
||||
|
||||
Airport saved = airportRepository.save(vie);
|
||||
|
||||
assertThrows(CouchbaseException.class, () -> airportRepository.withScope("non_default_scope_name")
|
||||
.withCollection(collectionName).iata(vie.getIata()));
|
||||
|
||||
} finally {
|
||||
airportRepository.delete(vie);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void findBySimplePropertyWithOptions() {
|
||||
|
||||
Airport vie = new Airport("airports::vie", "vie", "low9");
|
||||
JsonArray positionalParams = JsonArray.create().add("this parameter will be overridden");
|
||||
// JsonObject namedParams = JsonObject.create().put("$1", vie.getIata());
|
||||
try {
|
||||
Airport saved = airportRepository.save(vie);
|
||||
// Duration of 1 nano-second will cause timeout
|
||||
assertThrows(AmbiguousTimeoutException.class, () -> airportRepository
|
||||
.withOptions(QueryOptions.queryOptions().timeout(Duration.ofNanos(1))).iata(vie.getIata()));
|
||||
|
||||
Airport airport3 = airportRepository.withOptions(
|
||||
QueryOptions.queryOptions().scanConsistency(QueryScanConsistency.REQUEST_PLUS).parameters(positionalParams))
|
||||
.iata(vie.getIata());
|
||||
assertEquals(saved, airport3);
|
||||
|
||||
} finally {
|
||||
airportRepository.delete(vie);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void saveNotBounded() {
|
||||
// save() followed by query with NOT_BOUNDED will result in not finding the document
|
||||
Airport vie = new Airport("airports::vie", "vie", "low9");
|
||||
Airport airport2 = null;
|
||||
for (int i = 1; i <= 100; i++) {
|
||||
// set version == 0 so save() will be an upsert, not a replace
|
||||
Airport saved = airportRepository.save(vie.clearVersion());
|
||||
try {
|
||||
airport2 = airportRepository
|
||||
.withOptions(QueryOptions.queryOptions().scanConsistency(QueryScanConsistency.NOT_BOUNDED))
|
||||
.iata(saved.getIata());
|
||||
if (airport2 == null) {
|
||||
break;
|
||||
}
|
||||
} catch (DataRetrievalFailureException drfe) {
|
||||
airport2 = null; //
|
||||
} finally {
|
||||
// airportRepository.delete(vie);
|
||||
// instead of delete, use removeResult to test QueryOptions.consistentWith()
|
||||
RemoveResult removeResult = couchbaseTemplate.removeById().one(vie.getId());
|
||||
assertEquals(vie.getId(), removeResult.getId());
|
||||
assertTrue(removeResult.getCas() != 0);
|
||||
assertTrue(removeResult.getMutationToken().isPresent());
|
||||
Airport airport3 = airportRepository
|
||||
.withOptions(QueryOptions.queryOptions().scanConsistency(QueryScanConsistency.REQUEST_PLUS)
|
||||
.consistentWith(MutationState.from(removeResult.getMutationToken().get())))
|
||||
.iata(vie.getIata());
|
||||
assertNull(airport3, "should have been removed");
|
||||
}
|
||||
}
|
||||
assertNull(airport2, "airport2 should have likely been null at least once");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCas() {
|
||||
User user = new User("1", "Dave", "Wilson");
|
||||
userRepository.save(user);
|
||||
userRepository.findByFirstname("Dave");
|
||||
user.setVersion(user.getVersion() - 1);
|
||||
assertThrows(DataIntegrityViolationException.class, () -> userRepository.save(user));
|
||||
user.setVersion(0);
|
||||
@@ -233,12 +366,20 @@ public class CouchbaseRepositoryQueryIntegrationTests extends ClusterAwareIntegr
|
||||
userRepository.delete(user);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExpiryAnnotation() {
|
||||
UserAnnotated user = new UserAnnotated("1", "Dave", "Wilson");
|
||||
userRepository.save(user);
|
||||
userRepository.findByFirstname("Dave");
|
||||
sleep(2000);
|
||||
assertThrows(DataRetrievalFailureException.class, () -> userRepository.delete(user));
|
||||
}
|
||||
|
||||
@Test
|
||||
void count() {
|
||||
String[] iatas = { "JFK", "IAD", "SFO", "SJC", "SEA", "LAX", "PHX" };
|
||||
|
||||
try {
|
||||
|
||||
airportRepository.saveAll(
|
||||
Arrays.stream(iatas).map((iata) -> new Airport("airports::" + iata, iata, iata.toLowerCase(Locale.ROOT)))
|
||||
.collect(Collectors.toSet()));
|
||||
@@ -385,12 +526,9 @@ public class CouchbaseRepositoryQueryIntegrationTests extends ClusterAwareIntegr
|
||||
Airport vienna = new Airport("airports::vie", "vie", "LOWW");
|
||||
Airport frankfurt = new Airport("airports::fra", "fra", "EDDF");
|
||||
Airport losAngeles = new Airport("airports::lax", "lax", "KLAX");
|
||||
|
||||
try {
|
||||
airportRepository.saveAll(asList(vienna, frankfurt, losAngeles));
|
||||
|
||||
airportRepository.deleteAllById(asList(vienna.getId(), losAngeles.getId()));
|
||||
|
||||
assertThat(airportRepository.findAll()).containsExactly(frankfurt);
|
||||
} finally {
|
||||
airportRepository.deleteAll();
|
||||
@@ -430,7 +568,9 @@ public class CouchbaseRepositoryQueryIntegrationTests extends ClusterAwareIntegr
|
||||
private void sleep(int millis) {
|
||||
try {
|
||||
Thread.sleep(millis); // so they are executed out-of-order
|
||||
} catch (InterruptedException ie) {}
|
||||
} catch (InterruptedException ie) {
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@@ -463,10 +603,16 @@ public class CouchbaseRepositoryQueryIntegrationTests extends ClusterAwareIntegr
|
||||
return new NaiveAuditorAware();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configureEnvironment(final ClusterEnvironment.Builder builder) {
|
||||
builder.ioConfig().maxHttpConnections(11).idleHttpConnectionTimeout(Duration.ofSeconds(4));
|
||||
return;
|
||||
}
|
||||
|
||||
@Bean(name = "dateTimeProviderRef")
|
||||
public DateTimeProvider testDateTimeProvider() {
|
||||
return new AuditingDateTimeProvider();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,7 +73,8 @@ public class ReactiveCouchbaseRepositoryKeyValueIntegrationTests extends Cluster
|
||||
Airport saved = airportRepository.save(vie).block();
|
||||
Airport airport1 = airportRepository.findById(saved.getId()).block();
|
||||
assertEquals(airport1, saved);
|
||||
assertEquals(saved.getCreatedBy(), ReactiveNaiveAuditorAware.AUDITOR); // ReactiveNaiveAuditorAware will provide this
|
||||
assertEquals(saved.getCreatedBy(), ReactiveNaiveAuditorAware.AUDITOR); // ReactiveNaiveAuditorAware will provide
|
||||
// this
|
||||
} finally {
|
||||
airportRepository.delete(vie).block();
|
||||
}
|
||||
|
||||
@@ -130,17 +130,17 @@ public class ReactiveCouchbaseRepositoryQueryIntegrationTests extends JavaIntegr
|
||||
Airport vie = new Airport("airports::vie", "vie", "low3");
|
||||
Airport saved1 = airportRepository.save(vie).block();
|
||||
Airport saved2 = airportRepository.save(vie.withId(UUID.randomUUID().toString())).block();
|
||||
try {
|
||||
airportRepository.findAll().collectList().block(); // findAll has QueryScanConsistency;
|
||||
Mono<Airport> airport = airportRepository.findPolicySnapshotByPolicyIdAndEffectiveDateTime("any", 0);
|
||||
System.out.println("------------------------------");
|
||||
System.out.println(airport.block());
|
||||
System.out.println("------------------------------");
|
||||
Flux<Airport> airports = airportRepository.findPolicySnapshotAll();
|
||||
System.out.println(airports.collectList().block());
|
||||
System.out.println("------------------------------");
|
||||
Mono<Airport> ap = getPolicyByIdAndEffectiveDateTime("x", Instant.now());
|
||||
System.out.println(ap.block());
|
||||
try {
|
||||
airportRepository.findAll().collectList().block(); // findAll has QueryScanConsistency;
|
||||
Mono<Airport> airport = airportRepository.findPolicySnapshotByPolicyIdAndEffectiveDateTime("any", 0);
|
||||
System.out.println("------------------------------");
|
||||
System.out.println(airport.block());
|
||||
System.out.println("------------------------------");
|
||||
Flux<Airport> airports = airportRepository.findPolicySnapshotAll();
|
||||
System.out.println(airports.collectList().block());
|
||||
System.out.println("------------------------------");
|
||||
Mono<Airport> ap = getPolicyByIdAndEffectiveDateTime("x", Instant.now());
|
||||
System.out.println(ap.block());
|
||||
} finally {
|
||||
airportRepository.delete(saved1).block();
|
||||
airportRepository.delete(saved2).block();
|
||||
@@ -249,6 +249,22 @@ public class ReactiveCouchbaseRepositoryQueryIntegrationTests extends JavaIntegr
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void deleteOne() {
|
||||
|
||||
Airport vienna = new Airport("airports::vie", "vie", "LOWW");
|
||||
|
||||
try {
|
||||
Airport ap = airportRepository.save(vienna).block();
|
||||
assertEquals(vienna.getId(), ap.getId(), "should have saved what was provided");
|
||||
airportRepository.delete(vienna).as(StepVerifier::create).verifyComplete();
|
||||
|
||||
airportRepository.findAll().as(StepVerifier::create).verifyComplete();
|
||||
} finally {
|
||||
airportRepository.deleteAll().block();
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableReactiveCouchbaseRepositories("org.springframework.data.couchbase")
|
||||
static class Config extends AbstractCouchbaseConfiguration {
|
||||
|
||||
@@ -0,0 +1,211 @@
|
||||
/*
|
||||
* Copyright 2017-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.couchbase.repository.query;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.dao.DataRetrievalFailureException;
|
||||
import org.springframework.data.couchbase.domain.Airport;
|
||||
import org.springframework.data.couchbase.domain.AirportRepository;
|
||||
import org.springframework.data.couchbase.domain.Config;
|
||||
import org.springframework.data.couchbase.domain.User;
|
||||
import org.springframework.data.couchbase.domain.UserCol;
|
||||
import org.springframework.data.couchbase.domain.UserColRepository;
|
||||
import org.springframework.data.couchbase.util.Capabilities;
|
||||
import org.springframework.data.couchbase.util.ClusterType;
|
||||
import org.springframework.data.couchbase.util.CollectionAwareIntegrationTests;
|
||||
import org.springframework.data.couchbase.util.IgnoreWhen;
|
||||
|
||||
import com.couchbase.client.core.error.IndexFailureException;
|
||||
import com.couchbase.client.core.io.CollectionIdentifier;
|
||||
import com.couchbase.client.java.json.JsonArray;
|
||||
import com.couchbase.client.java.query.QueryOptions;
|
||||
import com.couchbase.client.java.query.QueryScanConsistency;
|
||||
|
||||
@IgnoreWhen(missesCapabilities = { Capabilities.QUERY, Capabilities.COLLECTIONS }, clusterTypes = ClusterType.MOCKED)
|
||||
public class CouchbaseRepositoryQueryCollectionIntegrationTests extends CollectionAwareIntegrationTests {
|
||||
|
||||
@Autowired AirportRepository airportRepository;
|
||||
@Autowired UserColRepository userColRepository;
|
||||
|
||||
@BeforeAll
|
||||
public static void beforeAll() {
|
||||
// first call the super method
|
||||
callSuperBeforeAll(new Object() {});
|
||||
// then do processing for this class
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public static void afterAll() {
|
||||
// first do the processing for this class
|
||||
// no-op
|
||||
// then call the super method
|
||||
callSuperAfterAll(new Object() {});
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
@Override
|
||||
public void beforeEach() {
|
||||
// first call the super method
|
||||
super.beforeEach();
|
||||
// then do processing for this class
|
||||
couchbaseTemplate.removeByQuery(User.class).inCollection(collectionName).all();
|
||||
couchbaseTemplate.removeByQuery(UserCol.class).inScope(otherScope).inCollection(otherCollection).all();
|
||||
ApplicationContext ac = new AnnotationConfigApplicationContext(Config.class);
|
||||
// seems that @Autowired is not adequate, so ...
|
||||
airportRepository = (AirportRepository) ac.getBean("airportRepository");
|
||||
userColRepository = (UserColRepository) ac.getBean("userColRepository");
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
@Override
|
||||
public void afterEach() {
|
||||
// first do processing for this class
|
||||
// no-op
|
||||
// then call the super method
|
||||
super.afterEach();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void myTest() {
|
||||
|
||||
AirportRepository ar = airportRepository.withScope(scopeName).withCollection(collectionName);
|
||||
Airport vie = new Airport("airports::vie", "vie", "loww");
|
||||
try {
|
||||
Airport saved = ar.save(vie);
|
||||
Airport airport2 = ar.save(saved);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
throw e;
|
||||
} finally {
|
||||
ar.delete(vie);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* can test against _default._default without setting up additional scope/collection and also test for collections and
|
||||
* scopes that do not exist These same tests should be repeated on non-default scope and collection in a test that
|
||||
* supports collections
|
||||
*/
|
||||
@Test
|
||||
@IgnoreWhen(missesCapabilities = { Capabilities.QUERY, Capabilities.COLLECTIONS }, clusterTypes = ClusterType.MOCKED)
|
||||
void findBySimplePropertyWithCollection() {
|
||||
|
||||
Airport vie = new Airport("airports::vie", "vie", "loww");
|
||||
// create proxy with scope, collection
|
||||
AirportRepository ar = airportRepository.withScope(scopeName).withCollection(collectionName);
|
||||
try {
|
||||
Airport saved = ar.save(vie);
|
||||
|
||||
// valid scope, collection in options
|
||||
Airport airport2 = ar.withCollection(collectionName)
|
||||
.withOptions(QueryOptions.queryOptions().scanConsistency(QueryScanConsistency.REQUEST_PLUS))
|
||||
.iata(vie.getIata());
|
||||
assertEquals(saved, airport2);
|
||||
|
||||
// given bad collectionName in fluent
|
||||
assertThrows(IndexFailureException.class, () -> ar.withCollection("bogusCollection").iata(vie.getIata()));
|
||||
|
||||
// given bad scopeName in fluent
|
||||
assertThrows(IndexFailureException.class, () -> ar.withScope("bogusScope").iata(vie.getIata()));
|
||||
|
||||
Airport airport6 = ar.withOptions(QueryOptions.queryOptions().scanConsistency(QueryScanConsistency.REQUEST_PLUS))
|
||||
.iata(vie.getIata());
|
||||
assertEquals(saved, airport6);
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
throw e;
|
||||
} finally {
|
||||
ar.deleteAll();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void findBySimplePropertyWithOptions() {
|
||||
|
||||
AirportRepository ar = airportRepository.withScope(scopeName).withCollection(collectionName);
|
||||
Airport vie = new Airport("airports::vie", "vie", "loww");
|
||||
JsonArray positionalParams = JsonArray.create().add("\"this parameter will be overridden\"");
|
||||
try {
|
||||
Airport saved = ar.save(vie);
|
||||
|
||||
Airport airport3 = ar.withOptions(
|
||||
QueryOptions.queryOptions().scanConsistency(QueryScanConsistency.REQUEST_PLUS).parameters(positionalParams))
|
||||
.iata(vie.getIata());
|
||||
assertEquals(saved, airport3);
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
throw e;
|
||||
} finally {
|
||||
ar.delete(vie);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testScopeCollectionAnnotation() {
|
||||
// template default scope is my_scope
|
||||
// UserCol annotation scope is other_scope
|
||||
UserCol user = new UserCol("1", "Dave", "Wilson");
|
||||
try {
|
||||
UserCol saved = userColRepository.withCollection(otherCollection).save(user); // should use UserCol annotation
|
||||
// scope
|
||||
List<UserCol> found = userColRepository.withCollection(otherCollection).findByFirstname(user.getFirstname());
|
||||
assertEquals(saved, found.get(0), "should have found what was saved");
|
||||
List<UserCol> notfound = userColRepository.withScope(CollectionIdentifier.DEFAULT_SCOPE)
|
||||
.withCollection(CollectionIdentifier.DEFAULT_COLLECTION).findByFirstname(user.getFirstname());
|
||||
assertEquals(0, notfound.size(), "should not have found what was saved");
|
||||
} finally {
|
||||
try {
|
||||
userColRepository.withScope(otherScope).withCollection(otherCollection).delete(user);
|
||||
} catch (DataRetrievalFailureException drfe) {}
|
||||
}
|
||||
}
|
||||
|
||||
// template default scope is my_scope
|
||||
// UserCol annotation scope is other_scope
|
||||
@Test
|
||||
public void testScopeCollectionRepoWith() {
|
||||
UserCol user = new UserCol("1", "Dave", "Wilson");
|
||||
try {
|
||||
UserCol saved = userColRepository.withScope(scopeName).withCollection(collectionName).save(user);
|
||||
List<UserCol> found = userColRepository.withScope(scopeName).withCollection(collectionName)
|
||||
.findByFirstname(user.getFirstname());
|
||||
assertEquals(saved, found.get(0), "should have found what was saved");
|
||||
List<UserCol> notfound = userColRepository.withScope(CollectionIdentifier.DEFAULT_SCOPE)
|
||||
.withCollection(CollectionIdentifier.DEFAULT_COLLECTION).findByFirstname(user.getFirstname());
|
||||
assertEquals(0, notfound.size(), "should not have found what was saved");
|
||||
userColRepository.withScope(scopeName).withCollection(collectionName).delete(user);
|
||||
} finally {
|
||||
try {
|
||||
userColRepository.withScope(scopeName).withCollection(collectionName).delete(user);
|
||||
} catch (DataRetrievalFailureException drfe) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
/*
|
||||
* Copyright 2017-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.couchbase.repository.query;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.dao.DataRetrievalFailureException;
|
||||
import org.springframework.data.couchbase.domain.Airport;
|
||||
import org.springframework.data.couchbase.domain.Config;
|
||||
import org.springframework.data.couchbase.domain.ReactiveAirportRepository;
|
||||
import org.springframework.data.couchbase.domain.ReactiveUserColRepository;
|
||||
import org.springframework.data.couchbase.domain.User;
|
||||
import org.springframework.data.couchbase.domain.UserCol;
|
||||
import org.springframework.data.couchbase.util.Capabilities;
|
||||
import org.springframework.data.couchbase.util.ClusterType;
|
||||
import org.springframework.data.couchbase.util.CollectionAwareIntegrationTests;
|
||||
import org.springframework.data.couchbase.util.IgnoreWhen;
|
||||
|
||||
import com.couchbase.client.core.error.IndexFailureException;
|
||||
import com.couchbase.client.core.io.CollectionIdentifier;
|
||||
import com.couchbase.client.java.json.JsonArray;
|
||||
import com.couchbase.client.java.query.QueryOptions;
|
||||
import com.couchbase.client.java.query.QueryScanConsistency;
|
||||
|
||||
@IgnoreWhen(missesCapabilities = { Capabilities.QUERY, Capabilities.COLLECTIONS }, clusterTypes = ClusterType.MOCKED)
|
||||
public class ReactiveCouchbaseRepositoryQueryCollectionIntegrationTests extends CollectionAwareIntegrationTests {
|
||||
|
||||
@Autowired ReactiveAirportRepository airportRepository;
|
||||
@Autowired ReactiveUserColRepository userColRepository;
|
||||
|
||||
@BeforeAll
|
||||
public static void beforeAll() {
|
||||
// first call the super method
|
||||
callSuperBeforeAll(new Object() {});
|
||||
// then do processing for this class
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public static void afterAll() {
|
||||
// first do the processing for this class
|
||||
// no-op
|
||||
// then call the super method
|
||||
callSuperAfterAll(new Object() {});
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
@Override
|
||||
public void beforeEach() {
|
||||
// first call the super method
|
||||
super.beforeEach();
|
||||
// then do processing for this class
|
||||
couchbaseTemplate.removeByQuery(User.class).inCollection(collectionName).all();
|
||||
couchbaseTemplate.removeByQuery(UserCol.class).inScope(otherScope).inCollection(otherCollection).all();
|
||||
|
||||
ApplicationContext ac = new AnnotationConfigApplicationContext(Config.class);
|
||||
// seems that @Autowired is not adequate, so ...
|
||||
airportRepository = (ReactiveAirportRepository) ac.getBean("reactiveAirportRepository");
|
||||
userColRepository = (ReactiveUserColRepository) ac.getBean("reactiveUserColRepository");
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
@Override
|
||||
public void afterEach() {
|
||||
// first do processing for this class
|
||||
// no-op
|
||||
// then call the super method
|
||||
super.afterEach();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void myTest() {
|
||||
|
||||
ReactiveAirportRepository ar = airportRepository.withScope(scopeName).withCollection(collectionName);
|
||||
Airport vie = new Airport("airports::vie", "vie", "loww");
|
||||
try {
|
||||
Airport saved = ar.save(vie).block();
|
||||
Airport airport2 = ar.save(saved).block();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
throw e;
|
||||
} finally {
|
||||
ar.delete(vie).block();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* can test against _default._default without setting up additional scope/collection and also test for collections and
|
||||
* scopes that do not exist These same tests should be repeated on non-default scope and collection in a test that
|
||||
* supports collections
|
||||
*/
|
||||
@Test
|
||||
@IgnoreWhen(missesCapabilities = { Capabilities.QUERY, Capabilities.COLLECTIONS }, clusterTypes = ClusterType.MOCKED)
|
||||
void findBySimplePropertyWithCollection() {
|
||||
|
||||
Airport vie = new Airport("airports::vie", "vie", "loww");
|
||||
// create proxy with scope, collection
|
||||
ReactiveAirportRepository ar = airportRepository.withScope(scopeName).withCollection(collectionName);
|
||||
try {
|
||||
Airport saved = ar.save(vie).block();
|
||||
|
||||
// valid scope, collection in options
|
||||
Airport airport2 = ar.withOptions(QueryOptions.queryOptions().scanConsistency(QueryScanConsistency.REQUEST_PLUS))
|
||||
.iata(vie.getIata()).block();
|
||||
assertEquals(saved, airport2);
|
||||
|
||||
// given bad collectionName in fluent
|
||||
assertThrows(IndexFailureException.class, () -> ar.withCollection("bogusCollection").iata(vie.getIata()).block());
|
||||
|
||||
// given bad scopeName in fluent
|
||||
assertThrows(IndexFailureException.class, () -> ar.withScope("bogusScope").iata(vie.getIata()).block());
|
||||
|
||||
Airport airport6 = ar.withOptions(QueryOptions.queryOptions().scanConsistency(QueryScanConsistency.REQUEST_PLUS))
|
||||
.iata(vie.getIata()).block();
|
||||
assertEquals(saved, airport6);
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
throw e;
|
||||
} finally {
|
||||
ar.deleteAll().block();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void findBySimplePropertyWithOptions() {
|
||||
|
||||
Airport vie = new Airport("airports::vie", "vie", "loww");
|
||||
ReactiveAirportRepository ar = airportRepository.withScope(scopeName).withCollection(collectionName);
|
||||
JsonArray positionalParams = JsonArray.create().add("\"this parameter will be overridden\"");
|
||||
try {
|
||||
Airport saved = ar.save(vie).block();
|
||||
|
||||
Airport airport3 = ar.withOptions(
|
||||
QueryOptions.queryOptions().scanConsistency(QueryScanConsistency.REQUEST_PLUS).parameters(positionalParams))
|
||||
.iata(vie.getIata()).block();
|
||||
assertEquals(saved, airport3);
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
throw e;
|
||||
} finally {
|
||||
ar.delete(vie).block();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testScopeCollectionAnnotation() {
|
||||
// template default scope is my_scope
|
||||
// UserCol annotation scope is other_scope
|
||||
UserCol user = new UserCol("1", "Dave", "Wilson");
|
||||
try {
|
||||
UserCol saved = userColRepository.withCollection(otherCollection).save(user).block(); // should use UserCol
|
||||
// annotation
|
||||
// scope
|
||||
List<UserCol> found = userColRepository.withCollection(otherCollection).findByFirstname(user.getFirstname())
|
||||
.collectList().block();
|
||||
assertEquals(saved, found.get(0), "should have found what was saved");
|
||||
List<UserCol> notfound = userColRepository.withScope(CollectionIdentifier.DEFAULT_SCOPE)
|
||||
.withCollection(CollectionIdentifier.DEFAULT_COLLECTION).findByFirstname(user.getFirstname()).collectList()
|
||||
.block();
|
||||
assertEquals(0, notfound.size(), "should not have found what was saved");
|
||||
} finally {
|
||||
try {
|
||||
userColRepository.withScope(otherScope).withCollection(otherCollection).delete(user);
|
||||
} catch (DataRetrievalFailureException drfe) {}
|
||||
}
|
||||
}
|
||||
|
||||
// template default scope is my_scope
|
||||
// UserCol annotation scope is other_scope
|
||||
@Test
|
||||
public void testScopeCollectionRepoWith() {
|
||||
UserCol user = new UserCol("1", "Dave", "Wilson");
|
||||
try {
|
||||
UserCol saved = userColRepository.withScope(scopeName).withCollection(collectionName).save(user).block();
|
||||
List<UserCol> found = userColRepository.withScope(scopeName).withCollection(collectionName)
|
||||
.findByFirstname(user.getFirstname()).collectList().block();
|
||||
assertEquals(saved, found.get(0), "should have found what was saved");
|
||||
List<UserCol> notfound = userColRepository.withScope(CollectionIdentifier.DEFAULT_SCOPE)
|
||||
.withCollection(CollectionIdentifier.DEFAULT_COLLECTION).findByFirstname(user.getFirstname()).collectList()
|
||||
.block();
|
||||
assertEquals(0, notfound.size(), "should not have found what was saved");
|
||||
userColRepository.withScope(scopeName).withCollection(collectionName).delete(user).block();
|
||||
} finally {
|
||||
try {
|
||||
userColRepository.withScope(scopeName).withCollection(collectionName).delete(user).block();
|
||||
} catch (DataRetrievalFailureException drfe) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,7 @@
|
||||
- log additional debug info during automatic index creation
|
||||
-->
|
||||
|
||||
<looger name="org.springframework.data.couchbase.core" level="debug"/>"
|
||||
<logger name="org.springframework.data.couchbase.core" level="debug"/>"
|
||||
<logger name="org.springframework.data.couchbase.repository.query" level="debug"/>
|
||||
<logger name="org.springframework.data.couchbase.repository.query.SpatialViewQueryCreator" level="trace"/>
|
||||
<logger name="org.springframework.data.couchbase.repository.query.StringN1qlBasedQuery" level="trace"/>
|
||||
|
||||
Reference in New Issue
Block a user