@@ -0,0 +1,34 @@
|
||||
/*
|
||||
/*
|
||||
* Copyright 2021-2022 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 com.couchbase.client.java.transactions;
|
||||
|
||||
import com.couchbase.client.core.annotation.Stability;
|
||||
import com.couchbase.client.core.transaction.CoreTransactionAttemptContext;
|
||||
import com.couchbase.client.java.codec.JsonSerializer;
|
||||
|
||||
/**
|
||||
* To access the ReactiveTransactionAttemptContext held by TransactionAttemptContext
|
||||
*
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
@Stability.Internal
|
||||
public class AttemptContextReactiveAccessor {
|
||||
public static ReactiveTransactionAttemptContext createReactiveTransactionAttemptContext(
|
||||
CoreTransactionAttemptContext core, JsonSerializer jsonSerializer) {
|
||||
return new ReactiveTransactionAttemptContext(core, jsonSerializer);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
/*
|
||||
* Copyright 2012-2022 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 com.querydsl.couchbase.document;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
|
||||
import org.springframework.data.couchbase.core.query.QueryCriteriaDefinition;
|
||||
|
||||
import com.querydsl.core.DefaultQueryMetadata;
|
||||
import com.querydsl.core.JoinExpression;
|
||||
import com.querydsl.core.QueryMetadata;
|
||||
import com.querydsl.core.QueryModifiers;
|
||||
import com.querydsl.core.SimpleQuery;
|
||||
import com.querydsl.core.support.QueryMixin;
|
||||
import com.querydsl.core.types.Expression;
|
||||
import com.querydsl.core.types.ExpressionUtils;
|
||||
import com.querydsl.core.types.FactoryExpression;
|
||||
import com.querydsl.core.types.Operation;
|
||||
import com.querydsl.core.types.OrderSpecifier;
|
||||
import com.querydsl.core.types.ParamExpression;
|
||||
import com.querydsl.core.types.Path;
|
||||
import com.querydsl.core.types.Predicate;
|
||||
|
||||
/**
|
||||
* renamed from AbstractCouchbaseQuery to AbstractCouchbaseQueryDSL to avoid confusion with the AbstractCouchbaseQuery
|
||||
* that is in the package com.querydsl.couchbase
|
||||
*
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
|
||||
public abstract class AbstractCouchbaseQueryDSL<Q extends AbstractCouchbaseQueryDSL<Q>> implements SimpleQuery<Q> {
|
||||
private final CouchbaseDocumentSerializer serializer;
|
||||
private final QueryMixin<Q> queryMixin;// = new QueryMixin(this, new DefaultQueryMetadata(), false);
|
||||
// TODO private ReadPreference readPreference;
|
||||
|
||||
public AbstractCouchbaseQueryDSL(CouchbaseDocumentSerializer serializer) {
|
||||
this.serializer = serializer;
|
||||
@SuppressWarnings("unchecked") // Q is this plus subclass
|
||||
Q query = (Q) this;
|
||||
this.queryMixin = new QueryMixin<Q>(query, new DefaultQueryMetadata(), false);
|
||||
}
|
||||
|
||||
/**
|
||||
* other spring-data project uses createQuery(Predicate filter) where the serializer creates the 'query' <br>
|
||||
* and then uses the result to create a BasicQuery with queryObject = result <br>
|
||||
* Couchbase Query has a 'criteria' which is a <br>
|
||||
* List<QueryCriteriaDefinition> criteria <br>
|
||||
* so we could create a List<QueryCriteriaDefinition> or an uber QueryCriteria that combines <br>
|
||||
* all the sub QueryDefinitions in the filter.
|
||||
*/
|
||||
protected QueryCriteriaDefinition createCriteria(Predicate predicate) {
|
||||
// other project use createQuery(Predicate filter) where the serializer creates the 'queryObject' of the BasicQuery
|
||||
return predicate != null ? (QueryCriteriaDefinition) this.serializer.handle(predicate) : null;
|
||||
}
|
||||
|
||||
// TODO - need later
|
||||
// public <T> JoinBuilder<Q, T> join(Path<T> ref, Path<T> target) {
|
||||
// return new JoinBuilder(this.queryMixin, ref, target);
|
||||
// }
|
||||
|
||||
// public <T> JoinBuilder<Q, T> join(CollectionPathBase<?, T, ?> ref, Path<T> target) {
|
||||
// return new JoinBuilder(this.queryMixin, ref, target);
|
||||
// }
|
||||
|
||||
// public <T> AnyEmbeddedBuilder<Q> anyEmbedded(Path<? extends Collection<T>> collection, Path<T> target) {
|
||||
// return new AnyEmbeddedBuilder(this.queryMixin, collection);
|
||||
// }
|
||||
|
||||
@Nullable
|
||||
protected Predicate createFilter(QueryMetadata metadata) {
|
||||
Predicate filter;
|
||||
if (!metadata.getJoins().isEmpty()) {
|
||||
filter = ExpressionUtils.allOf(new Predicate[] { metadata.getWhere(), this.createJoinFilter(metadata) });
|
||||
} else {
|
||||
filter = metadata.getWhere();
|
||||
}
|
||||
return filter;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected Predicate createJoinFilter(QueryMetadata metadata) {
|
||||
Map<Expression<?>, Predicate> predicates = new HashMap();
|
||||
List<JoinExpression> joins = metadata.getJoins();
|
||||
|
||||
for (int i = joins.size() - 1; i >= 0; --i) {
|
||||
JoinExpression join = (JoinExpression) joins.get(i);
|
||||
Path<?> source = (Path) ((Operation) join.getTarget()).getArg(0);
|
||||
Path<?> target = (Path) ((Operation) join.getTarget()).getArg(1);
|
||||
Predicate extraFilters = (Predicate) predicates.get(target.getRoot());
|
||||
Predicate filter = ExpressionUtils.allOf(new Predicate[] { join.getCondition(), extraFilters });
|
||||
List<? extends Object> ids = this.getIds(target.getType(), filter);
|
||||
if (ids.isEmpty()) {
|
||||
throw new AbstractCouchbaseQueryDSL.NoResults();
|
||||
}
|
||||
|
||||
Path<?> path = ExpressionUtils.path(String.class, source, "$id");
|
||||
predicates.merge(source.getRoot(),
|
||||
ExpressionUtils.in(path, (Collection) ids/* TODO was just ids without casting to Collection */),
|
||||
ExpressionUtils::and);
|
||||
}
|
||||
|
||||
Path<?> source = (Path) ((Operation) ((JoinExpression) joins.get(0)).getTarget()).getArg(0);
|
||||
return predicates.get(source.getRoot());
|
||||
}
|
||||
|
||||
private Predicate allOf(Collection<Predicate> predicates) {
|
||||
return predicates != null ? ExpressionUtils.allOf(predicates) : null;
|
||||
}
|
||||
|
||||
protected abstract List<Object> getIds(Class<?> var1, Predicate var2);
|
||||
|
||||
public Q distinct() {
|
||||
return this.queryMixin.distinct();
|
||||
}
|
||||
|
||||
public Q where(Predicate e) {
|
||||
return this.queryMixin.where(e);
|
||||
}
|
||||
|
||||
public Q where(Predicate... e) {
|
||||
return this.queryMixin.where(e);
|
||||
}
|
||||
|
||||
public Q limit(long limit) {
|
||||
return this.queryMixin.limit(limit);
|
||||
}
|
||||
|
||||
public Q offset(long offset) {
|
||||
return this.queryMixin.offset(offset);
|
||||
}
|
||||
|
||||
public Q restrict(QueryModifiers modifiers) {
|
||||
return this.queryMixin.restrict(modifiers);
|
||||
}
|
||||
|
||||
public Q orderBy(OrderSpecifier<?> o) {
|
||||
return this.queryMixin.orderBy(o);
|
||||
}
|
||||
|
||||
public Q orderBy(OrderSpecifier<?>... o) {
|
||||
return this.queryMixin.orderBy(o);
|
||||
}
|
||||
|
||||
public <T> Q set(ParamExpression<T> param, T value) {
|
||||
return this.queryMixin.set(param, value);
|
||||
}
|
||||
|
||||
protected Map<String, String> createProjection(Expression<?> projection) {
|
||||
if (projection instanceof FactoryExpression) {
|
||||
Map<String, String> obj = new HashMap();
|
||||
Iterator var3 = ((FactoryExpression) projection).getArgs().iterator();
|
||||
|
||||
while (var3.hasNext()) {
|
||||
Object expr = var3.next();
|
||||
if (expr instanceof Expression) {
|
||||
obj.put(expr.toString(), (String) this.serializer.handle((Expression) expr));
|
||||
}
|
||||
}
|
||||
return obj;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
protected CouchbaseDocument createQuery(@Nullable Predicate predicate) {
|
||||
return predicate != null ? (CouchbaseDocument) this.serializer.handle(predicate) : new CouchbaseDocument();
|
||||
}
|
||||
|
||||
// public void setReadPreference(ReadPreference readPreference) {
|
||||
// this.readPreference = readPreference;
|
||||
// }
|
||||
|
||||
protected QueryMixin<Q> getQueryMixin() {
|
||||
return this.queryMixin;
|
||||
}
|
||||
|
||||
protected CouchbaseDocumentSerializer getSerializer() {
|
||||
return this.serializer;
|
||||
}
|
||||
|
||||
// protected ReadPreference getReadPreference() {
|
||||
// return this.readPreference;
|
||||
// }
|
||||
|
||||
public CouchbaseDocument asDocument() {
|
||||
return this.createQuery(this.queryMixin.getMetadata().getWhere());
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return this.asDocument().toString();
|
||||
}
|
||||
|
||||
static class NoResults extends RuntimeException {
|
||||
NoResults() {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,403 @@
|
||||
/*
|
||||
* Copyright 2012-2022 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 com.querydsl.couchbase.document;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.springframework.data.couchbase.core.query.QueryCriteria;
|
||||
import org.springframework.data.couchbase.core.query.QueryCriteriaDefinition;
|
||||
import org.springframework.data.couchbase.repository.support.DBRef;
|
||||
import org.springframework.data.domain.Sort;
|
||||
|
||||
import com.querydsl.core.types.Constant;
|
||||
import com.querydsl.core.types.Expression;
|
||||
import com.querydsl.core.types.ExpressionUtils;
|
||||
import com.querydsl.core.types.FactoryExpression;
|
||||
import com.querydsl.core.types.Operation;
|
||||
import com.querydsl.core.types.Operator;
|
||||
import com.querydsl.core.types.Ops;
|
||||
import com.querydsl.core.types.OrderSpecifier;
|
||||
import com.querydsl.core.types.ParamExpression;
|
||||
import com.querydsl.core.types.Path;
|
||||
import com.querydsl.core.types.PathMetadata;
|
||||
import com.querydsl.core.types.PathType;
|
||||
import com.querydsl.core.types.SubQueryExpression;
|
||||
import com.querydsl.core.types.TemplateExpression;
|
||||
import com.querydsl.core.types.Visitor;
|
||||
|
||||
/**
|
||||
* Serializes the given Querydsl query to a Document query for Couchbase.
|
||||
*
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
public abstract class CouchbaseDocumentSerializer implements Visitor<Object, Void> {
|
||||
|
||||
public Object handle(Expression<?> expression) {
|
||||
return expression.accept(this, null);
|
||||
}
|
||||
|
||||
public Sort toSort(List<OrderSpecifier<?>> orderBys) {
|
||||
Sort sort = Sort.unsorted();
|
||||
for (OrderSpecifier<?> orderBy : orderBys) {
|
||||
Object key = orderBy.getTarget().accept(this, null);
|
||||
// sort.and(Sort.by(orderBy));
|
||||
// sort.append(key.toString(), orderBy.getOrder() == Order.ASC ? 1 : -1);
|
||||
}
|
||||
return sort;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object visit(Constant<?> expr, Void context) {
|
||||
if (Enum.class.isAssignableFrom(expr.getType())) {
|
||||
@SuppressWarnings("unchecked") // Guarded by previous check
|
||||
Constant<? extends Enum<?>> expectedExpr = (Constant<? extends Enum<?>>) expr;
|
||||
return expectedExpr.getConstant().name();
|
||||
} else {
|
||||
return expr.getConstant();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object visit(TemplateExpression<?> expr, Void context) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object visit(FactoryExpression<?> expr, Void context) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
protected String asDBKey(Operation<?> expr, int index) {
|
||||
return (String) asDBValue(expr, index);
|
||||
}
|
||||
|
||||
protected Object asDBValue(Operation<?> expr, int index) {
|
||||
return expr.getArg(index).accept(this, null);
|
||||
}
|
||||
|
||||
private String regexValue(Operation<?> expr, int index) {
|
||||
return Pattern.quote(expr.getArg(index).accept(this, null).toString());
|
||||
}
|
||||
|
||||
protected QueryCriteriaDefinition asDocument(String key, Object value) {
|
||||
QueryCriteria qc = null;
|
||||
if (1 == 1) {
|
||||
throw new UnsupportedOperationException("Wrong path to create this criteria " + key);
|
||||
}
|
||||
if (key.equals("$and") || key.equals("$or") /* value instanceof QueryCriteria[] */) {
|
||||
throw new UnsupportedOperationException("Wrong path to create this criteria " + key);
|
||||
} else if (key.equals("$in") /* value instanceof QueryCriteria[] */) {
|
||||
throw new RuntimeException(("not supported"));
|
||||
} else {
|
||||
qc = QueryCriteria.where(key).is(value);
|
||||
}
|
||||
|
||||
return qc;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Object visit(Operation<?> expr, Void context) {
|
||||
Operator op = expr.getOperator();
|
||||
if (op == Ops.EQ) {
|
||||
if (expr.getArg(0) instanceof Operation) {
|
||||
Operation<?> lhs = (Operation<?>) expr.getArg(0);
|
||||
if (lhs.getOperator() == Ops.COL_SIZE || lhs.getOperator() == Ops.ARRAY_SIZE
|
||||
|| lhs.getOperator() == Ops.STRING_LENGTH) {
|
||||
// return asDocument(asDBKey(lhs, 0), asDocument("$size", asDBValue(expr, 1)));
|
||||
return QueryCriteria.where(asDBKey(expr, 0)).is(asDBValue(expr, 1));
|
||||
} else {
|
||||
throw new UnsupportedOperationException("Illegal operation " + expr);
|
||||
}
|
||||
} else if (expr.getArg(0) instanceof Path) {
|
||||
/*
|
||||
Path<?> path = (Path<?>) expr.getArg(0);
|
||||
Constant<?> constant = (Constant<?>) expr.getArg(1);
|
||||
return asDocument(asDBKey(expr, 0), convert(path, constant));
|
||||
*/
|
||||
return QueryCriteria.where(asDBKey(expr, 0)).is(asDBValue(expr, 1));
|
||||
}
|
||||
} else if (op == Ops.STRING_IS_EMPTY) {
|
||||
// return asDocument(asDBKey(expr, 0), "");
|
||||
return QueryCriteria.where(asDBKey(expr, 0)).isNotValued().or(asDBKey(expr, 0)).is("");
|
||||
} else if (op == Ops.NOT) {
|
||||
// Handle the not's child
|
||||
Operation<?> subOperation = (Operation<?>) expr.getArg(0);
|
||||
Operator subOp = subOperation.getOperator();
|
||||
if (subOp == Ops.IN) {
|
||||
return visit(
|
||||
ExpressionUtils.operation(Boolean.class, Ops.NOT_IN, subOperation.getArg(0), subOperation.getArg(1)),
|
||||
context);
|
||||
} else {
|
||||
QueryCriteria arg = (QueryCriteria) handle(expr.getArg(0));
|
||||
return arg.negate(); // negate(arg);
|
||||
}
|
||||
|
||||
} else if (op == Ops.AND) {
|
||||
// return asDocument("$and", collectConnectorArgs("$and", expr));
|
||||
return collectConnectorArgs("$and", expr);
|
||||
} else if (op == Ops.OR) {
|
||||
// return asDocument("$or", collectConnectorArgs("$or", expr));
|
||||
return collectConnectorArgs("$or", expr);
|
||||
} else if (op == Ops.NE) {
|
||||
// Path<?> path = (Path<?>) expr.getArg(0);
|
||||
// Constant<?> constant = (Constant<?>) expr.getArg(1);
|
||||
// return asDocument(asDBKey(expr, 0), asDocument("$ne", convert(path, constant)));
|
||||
return QueryCriteria.where(asDBKey(expr, 0)).ne(asDBValue(expr, 1));
|
||||
} else if (op == Ops.STARTS_WITH) {
|
||||
// return asDocument(asDBKey(expr, 0), new CBRegularExpression("^" + regexValue(expr, 1)));
|
||||
return QueryCriteria.where(asDBKey(expr, 0)).startingWith(asDBValue(expr, 1));
|
||||
} else if (op == Ops.STARTS_WITH_IC) {
|
||||
// return asDocument(asDBKey(expr, 0), new CBRegularExpression("^" + regexValue(expr, 1), "i"));
|
||||
return QueryCriteria.where(asDBKey(expr, 0)).startingWith(true, asDBValue(expr, 1).toString());
|
||||
} else if (op == Ops.ENDS_WITH) {
|
||||
// return asDocument(asDBKey(expr, 0), new CBRegularExpression(regexValue(expr, 1) + "$"));
|
||||
return QueryCriteria.where(asDBKey(expr, 0)).endingWith(asDBValue(expr, 1));
|
||||
} else if (op == Ops.ENDS_WITH_IC) {
|
||||
// return asDocument(asDBKey(expr, 0), new CBRegularExpression(regexValue(expr, 1) + "$", "i"));
|
||||
return QueryCriteria.where(asDBKey(expr, 0)).endingWith(true, asDBValue(expr, 1).toString());
|
||||
} else if (op == Ops.EQ_IGNORE_CASE) {
|
||||
// return asDocument(asDBKey(expr, 0), new CBRegularExpression("^" + regexValue(expr, 1) + "$", "i"));
|
||||
return QueryCriteria.where(asDBKey(expr, 0)).eq(true, asDBValue(expr, 1).toString());
|
||||
} else if (op == Ops.STRING_CONTAINS) {
|
||||
// return asDocument(asDBKey(expr, 0), new CBRegularExpression(".*" + regexValue(expr, 1) + ".*"));
|
||||
return QueryCriteria.where(asDBKey(expr, 0)).containing(asDBValue(expr, 1));
|
||||
} else if (op == Ops.STRING_CONTAINS_IC) {
|
||||
// return asDocument(asDBKey(expr, 0), new CBRegularExpression(".*" + regexValue(expr, 1) + ".*", "i"));
|
||||
return QueryCriteria.where(asDBKey(expr, 0)).containing(true, asDBValue(expr, 1).toString());
|
||||
/*
|
||||
} else if (op == Ops.MATCHES) {
|
||||
//return asDocument(asDBKey(expr, 0), new CBRegularExpression(asDBValue(expr, 1).toString()));
|
||||
return QueryCriteria.where(asDBKey(expr, 0)).like(asDBValue(expr,1));
|
||||
} else if (op == Ops.MATCHES_IC) {
|
||||
//return asDocument(asDBKey(expr, 0), new CBRegularExpression(asDBValue(expr, 1).toString(), "i"));
|
||||
return QueryCriteria.where("UPPER("+asDBKey(expr, 0)+")").like("UPPER("+asDBValue(expr,1)+")");
|
||||
*/
|
||||
} else if (op == Ops.LIKE) {
|
||||
// String regex = ExpressionUtils.likeToRegex((Expression) expr.getArg(1)).toString();
|
||||
// return asDocument(asDBKey(expr, 0), new CBRegularExpression(regex));
|
||||
return QueryCriteria.where(asDBKey(expr, 0)).like(asDBValue(expr, 1));
|
||||
} else if (op == Ops.LIKE_IC) {
|
||||
// String regex = ExpressionUtils.likeToRegex((Expression) expr.getArg(1)).toString();
|
||||
// return asDocument(asDBKey(expr, 0), new CBRegularExpression(regex, "i"));
|
||||
return QueryCriteria.where(asDBKey(expr, 0)).like(true, asDBValue(expr, 1).toString());
|
||||
} else if (op == Ops.BETWEEN) {
|
||||
// Document value = new Document("$gte", this.asDBValue(expr, 1));
|
||||
// value.append("$lte", this.asDBValue(expr, 2));
|
||||
// return this.asDocument(this.asDBKey(expr, 0), value);
|
||||
return QueryCriteria.where(asDBKey(expr, 0)).between(asDBValue(expr, 1), asDBValue(expr, 2));
|
||||
} else if (op == Ops.IN) {
|
||||
int constIndex = 0;
|
||||
int exprIndex = 1;
|
||||
if (expr.getArg(1) instanceof Constant<?>) {
|
||||
constIndex = 1;
|
||||
exprIndex = 0;
|
||||
}
|
||||
if (Collection.class.isAssignableFrom(expr.getArg(constIndex).getType())) {
|
||||
@SuppressWarnings("unchecked") // guarded by previous check
|
||||
Collection<?> values = ((Constant<? extends Collection<?>>) expr.getArg(constIndex)).getConstant();
|
||||
// return asDocument(asDBKey(expr, exprIndex), asDocument("$in", values));
|
||||
return QueryCriteria.where(asDBKey(expr, exprIndex)).in(values);
|
||||
} else { // I think framework already converts IN to EQ if arg is not a collection
|
||||
// Path<?> path = (Path<?>) expr.getArg(exprIndex);
|
||||
// Constant<?> constant = (Constant<?>) expr.getArg(constIndex);
|
||||
// return asDocument(asDBKey(expr, exprIndex), convert(path, constant));
|
||||
Object value = expr.getArg(constIndex);
|
||||
return QueryCriteria.where(asDBKey(expr, exprIndex)).eq(value);
|
||||
}
|
||||
|
||||
} else if (op == Ops.NOT_IN) {
|
||||
int constIndex = 0;
|
||||
int exprIndex = 1;
|
||||
if (expr.getArg(1) instanceof Constant<?>) {
|
||||
constIndex = 1;
|
||||
exprIndex = 0;
|
||||
}
|
||||
if (Collection.class.isAssignableFrom(expr.getArg(constIndex).getType())) {
|
||||
@SuppressWarnings("unchecked") // guarded by previous check
|
||||
Collection<?> values = ((Constant<? extends Collection<?>>) expr.getArg(constIndex)).getConstant();
|
||||
// return asDocument(asDBKey(expr, exprIndex), asDocument("$nin", values));
|
||||
return QueryCriteria.where(asDBKey(expr, exprIndex)).notIn(values);
|
||||
} else { // I think framework already converts NOT_IN to NE if arg is not a collection
|
||||
// Path<?> path = (Path<?>) expr.getArg(exprIndex);
|
||||
// Constant<?> constant = (Constant<?>) expr.getArg(constIndex);
|
||||
// return asDocument(asDBKey(expr, exprIndex), asDocument("$ne", convert(path, constant)));
|
||||
Object value = expr.getArg(constIndex);
|
||||
return QueryCriteria.where(asDBKey(expr, exprIndex)).ne(value);
|
||||
}
|
||||
|
||||
} else if (op == Ops.COL_IS_EMPTY) {
|
||||
// List<Object> list = new ArrayList<Object>(2);
|
||||
// list.add(asDocument(asDBKey(expr, 0), new ArrayList<Object>()));
|
||||
// list.add(asDocument(asDBKey(expr, 0), asDocument("$exists", false)));
|
||||
// return asDocument("$or", list);
|
||||
return QueryCriteria.where(asDBKey(expr, 0)).isNotValued();
|
||||
} else if (op == Ops.LT) {
|
||||
// return asDocument(asDBKey(expr, 0), asDocument("$lt", asDBValue(expr, 1)));
|
||||
return QueryCriteria.where(asDBKey(expr, 0)).lt(asDBValue(expr, 1));
|
||||
} else if (op == Ops.GT) {
|
||||
// return asDocument(asDBKey(expr, 0), asDocument("$gt", asDBValue(expr, 1)));
|
||||
return QueryCriteria.where(asDBKey(expr, 0)).gt(asDBValue(expr, 1));
|
||||
} else if (op == Ops.LOE) {
|
||||
// return asDocument(asDBKey(expr, 0), asDocument("$lte", asDBValue(expr, 1)));
|
||||
return QueryCriteria.where(asDBKey(expr, 0)).lte(asDBValue(expr, 1));
|
||||
} else if (op == Ops.GOE) {
|
||||
// return asDocument(asDBKey(expr, 0), asDocument("$gte", asDBValue(expr, 1)));
|
||||
return QueryCriteria.where(asDBKey(expr, 0)).gte(asDBValue(expr, 1));
|
||||
} else if (op == Ops.IS_NULL) {
|
||||
// return asDocument(asDBKey(expr, 0), asDocument("$exists", false));
|
||||
return QueryCriteria.where(asDBKey(expr, 0)).isNull();
|
||||
} else if (op == Ops.IS_NOT_NULL) {
|
||||
// return asDocument(asDBKey(expr, 0), asDocument("$exists", true));
|
||||
return QueryCriteria.where(asDBKey(expr, 0)).isNotNull();
|
||||
} else if (op == Ops.CONTAINS_KEY) { // TODO not sure about this one
|
||||
Path<?> path = (Path<?>) expr.getArg(0);
|
||||
// Expression<?> key = expr.getArg(1);
|
||||
// return asDocument(visit(path, context) + "." + key.toString(), asDocument("$exists", true));
|
||||
return QueryCriteria.where("meta().id"/*asDBKey(expr, 0)*/).eq(asDBKey(expr, 1));
|
||||
} else if (op == Ops.STRING_LENGTH) {
|
||||
return "LENGTH(" + asDBKey(expr, 0) + ")";// QueryCriteria.where(asDBKey(expr, 0)).size();
|
||||
}
|
||||
|
||||
throw new UnsupportedOperationException("Illegal operation " + expr);
|
||||
}
|
||||
|
||||
/* TODO -- need later
|
||||
private Object negate(QueryCriteriaDefinition arg) {
|
||||
List<Object> list = new ArrayList<Object>();
|
||||
for (Map.Entry<String, Object> entry : arg.entrySet()) {
|
||||
if (entry.getKey().equals("$or")) {
|
||||
list.add(asDocument("$nor", entry.getValue()));
|
||||
|
||||
} else if (entry.getKey().equals("$and")) {
|
||||
List<Object> list2 = new ArrayList<Object>();
|
||||
for (Object o : ((Collection) entry.getValue())) {
|
||||
list2.add(negate((QueryCriteriaDefinition) o));
|
||||
}
|
||||
list.add(asDocument("$or", list2));
|
||||
|
||||
} else if (entry.getValue() instanceof Pattern || entry.getValue() instanceof CBRegularExpression) {
|
||||
list.add(asDocument(entry.getKey(), asDocument("$not", entry.getValue())));
|
||||
|
||||
} else if (entry.getValue() instanceof QueryCriteriaDefinition) {
|
||||
list.add(negate(entry.getKey(), (QueryCriteriaDefinition) entry.getValue()));
|
||||
|
||||
} else {
|
||||
list.add(asDocument(entry.getKey(), asDocument("$ne", entry.getValue())));
|
||||
}
|
||||
}
|
||||
return list.size() == 1 ? list.get(0) : asDocument("$or", list);
|
||||
}
|
||||
|
||||
private Object negate(String key, QueryCriteriaDefinition value) {
|
||||
if (value.size() == 1) {
|
||||
return asDocument(key, asDocument("$not", value));
|
||||
} else {
|
||||
List<Object> list2 = new ArrayList<Object>();
|
||||
for (Map.Entry<String, Object> entry2 : value.entrySet()) {
|
||||
list2.add(asDocument(key, asDocument("$not", asDocument(entry2.getKey(), entry2.getValue()))));
|
||||
}
|
||||
return asDocument("$or", list2);
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
/* TODO -- need later
|
||||
protected Object convert(Path<?> property, Constant<?> constant) {
|
||||
if (isReference(property)) {
|
||||
return asReference(constant.getConstant());
|
||||
} else if (isId(property)) {
|
||||
if (isReference(property.getMetadata().getParent())) {
|
||||
return asReferenceKey(property.getMetadata().getParent().getType(), constant.getConstant());
|
||||
} else if (constant.getType().equals(String.class) && isImplicitObjectIdConversion()) {
|
||||
String id = (String) constant.getConstant();
|
||||
return ObjectId.isValid(id) ? new ObjectId(id) : id;
|
||||
}
|
||||
}
|
||||
return visit(constant, null);
|
||||
}
|
||||
*/
|
||||
|
||||
protected boolean isImplicitObjectIdConversion() {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected DBRef asReferenceKey(Class<?> entity, Object id) {
|
||||
// TODO override in subclass
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
protected abstract DBRef asReference(Object constant);
|
||||
|
||||
protected abstract boolean isReference(Path<?> arg);
|
||||
|
||||
protected boolean isId(Path<?> arg) {
|
||||
// TODO override in subclass
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String visit(Path<?> expr, Void context) {
|
||||
PathMetadata metadata = expr.getMetadata();
|
||||
if (metadata.getParent() != null) {
|
||||
Path<?> parent = metadata.getParent();
|
||||
if (parent.getMetadata().getPathType() == PathType.DELEGATE) {
|
||||
parent = parent.getMetadata().getParent();
|
||||
}
|
||||
if (metadata.getPathType() == PathType.COLLECTION_ANY) {
|
||||
return visit(parent, context);
|
||||
} else if (parent.getMetadata().getPathType() != PathType.VARIABLE) {
|
||||
String rv = getKeyForPath(expr, metadata);
|
||||
String parentStr = visit(parent, context);
|
||||
return rv != null ? parentStr + "." + rv : parentStr;
|
||||
}
|
||||
}
|
||||
return getKeyForPath(expr, metadata);
|
||||
}
|
||||
|
||||
protected String getKeyForPath(Path<?> expr, PathMetadata metadata) {
|
||||
return metadata.getElement().toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object visit(SubQueryExpression<?> expr, Void context) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object visit(ParamExpression<?> expr, Void context) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
private QueryCriteriaDefinition collectConnectorArgs(String operator, Operation<?> operation) {
|
||||
QueryCriteria first = null;
|
||||
for (Expression<?> exp : operation.getArgs()) {
|
||||
QueryCriteria document = (QueryCriteria) handle(exp);
|
||||
if (first == null) {
|
||||
first = document;
|
||||
} else {
|
||||
if (operator.equals("$or")) {
|
||||
first = first.or(document);
|
||||
} else if (operator.equals("$and")) {
|
||||
first = first.and(document);
|
||||
}
|
||||
}
|
||||
}
|
||||
return first;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import java.io.Closeable;
|
||||
|
||||
import org.springframework.dao.support.PersistenceExceptionTranslator;
|
||||
|
||||
import com.couchbase.client.java.Bucket;
|
||||
import com.couchbase.client.java.Cluster;
|
||||
import com.couchbase.client.java.Collection;
|
||||
import com.couchbase.client.java.Scope;
|
||||
|
||||
/**
|
||||
* The {@link CouchbaseClientFactory} is the main way to get access to the managed SDK instance and resources.
|
||||
* <p>
|
||||
* Please note that a single factory is always bound to a {@link Bucket}, so if you need to access more than one
|
||||
* you need to initialize one factory for each.
|
||||
*/
|
||||
public interface CouchbaseClientFactory extends Closeable {
|
||||
|
||||
/**
|
||||
* Provides access to the managed SDK {@link Cluster} reference.
|
||||
*/
|
||||
Cluster getCluster();
|
||||
|
||||
/**
|
||||
* Provides access to the managed SDK {@link Bucket} reference.
|
||||
*/
|
||||
Bucket getBucket();
|
||||
|
||||
/**
|
||||
* Provides access to the managed SDK {@link Scope} reference.
|
||||
*/
|
||||
Scope getScope();
|
||||
|
||||
/**
|
||||
* Provides access to a collection (identified by its name) in managed SDK {@link Scope} reference.
|
||||
*
|
||||
* @param name the name of the collection. If null is passed in, the default collection is assumed.
|
||||
*/
|
||||
Collection getCollection(String name);
|
||||
|
||||
/**
|
||||
* Provides access to the default collection.
|
||||
*/
|
||||
Collection getDefaultCollection();
|
||||
|
||||
/**
|
||||
* Returns a new {@link CouchbaseClientFactory} set to the scope given as an argument.
|
||||
*
|
||||
* @param scopeName the name of the scope to use for all collection access.
|
||||
* @return a new client factory, bound to the other scope.
|
||||
*/
|
||||
CouchbaseClientFactory withScope(String scopeName);
|
||||
|
||||
/**
|
||||
* The exception translator used on the factory.
|
||||
*/
|
||||
PersistenceExceptionTranslator getExceptionTranslator();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* Copyright 2012-2022 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;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.springframework.dao.support.PersistenceExceptionTranslator;
|
||||
import org.springframework.data.couchbase.core.CouchbaseExceptionTranslator;
|
||||
|
||||
import com.couchbase.client.core.env.Authenticator;
|
||||
import com.couchbase.client.core.env.OwnedSupplier;
|
||||
import com.couchbase.client.core.io.CollectionIdentifier;
|
||||
import com.couchbase.client.java.Bucket;
|
||||
import com.couchbase.client.java.Cluster;
|
||||
import com.couchbase.client.java.ClusterOptions;
|
||||
import com.couchbase.client.java.Collection;
|
||||
import com.couchbase.client.java.Scope;
|
||||
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 {
|
||||
|
||||
private final Supplier<Cluster> cluster;
|
||||
private final Bucket bucket;
|
||||
private final Scope scope;
|
||||
private final PersistenceExceptionTranslator exceptionTranslator;
|
||||
|
||||
public SimpleCouchbaseClientFactory(final String connectionString, final Authenticator authenticator,
|
||||
final String bucketName) {
|
||||
this(connectionString, authenticator, bucketName, null);
|
||||
}
|
||||
|
||||
public SimpleCouchbaseClientFactory(final String connectionString, final Authenticator authenticator,
|
||||
final String bucketName, final String scopeName) {
|
||||
this(new OwnedSupplier<>(Cluster.connect(connectionString, ClusterOptions.clusterOptions(authenticator))),
|
||||
bucketName, scopeName);
|
||||
}
|
||||
|
||||
public SimpleCouchbaseClientFactory(final String connectionString, final Authenticator authenticator,
|
||||
final String bucketName, final String scopeName, final ClusterEnvironment environment) {
|
||||
this(
|
||||
new OwnedSupplier<>(
|
||||
Cluster.connect(connectionString, ClusterOptions.clusterOptions(authenticator).environment(environment))),
|
||||
bucketName, scopeName);
|
||||
}
|
||||
|
||||
public SimpleCouchbaseClientFactory(final Cluster cluster, final String bucketName, final String scopeName) {
|
||||
this(() -> cluster, bucketName, scopeName);
|
||||
}
|
||||
|
||||
private SimpleCouchbaseClientFactory(final Supplier<Cluster> cluster, final String bucketName,
|
||||
final String scopeName) {
|
||||
this.cluster = cluster;
|
||||
this.bucket = cluster.get().bucket(bucketName);
|
||||
this.scope = scopeName == null ? bucket.defaultScope() : bucket.scope(scopeName);
|
||||
this.exceptionTranslator = new CouchbaseExceptionTranslator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CouchbaseClientFactory withScope(final String scopeName) {
|
||||
return new SimpleCouchbaseClientFactory(cluster, bucket.name(), scopeName != null ? scopeName : getScope().name());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cluster getCluster() {
|
||||
return cluster.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bucket getBucket() {
|
||||
return bucket;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Scope getScope() {
|
||||
return scope;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection getCollection(final String collectionName) {
|
||||
final Scope scope = getScope();
|
||||
if (collectionName == null || CollectionIdentifier.DEFAULT_COLLECTION.equals(collectionName)) {
|
||||
if(scope != null ) {
|
||||
if (scope.name() != null && !CollectionIdentifier.DEFAULT_SCOPE.equals(scope.name())) {
|
||||
throw new IllegalStateException("A collectionName must be provided if a non-default scope is used");
|
||||
}
|
||||
}
|
||||
return getBucket().defaultCollection();
|
||||
}
|
||||
return scope.collection(collectionName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection getDefaultCollection() {
|
||||
return getCollection(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PersistenceExceptionTranslator getExceptionTranslator() {
|
||||
return exceptionTranslator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (cluster instanceof OwnedSupplier) {
|
||||
cluster.get().disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
71
src/main/java/org/springframework/data/couchbase/cache/CacheKeyPrefix.java
vendored
Normal file
71
src/main/java/org/springframework/data/couchbase/cache/CacheKeyPrefix.java
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* 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.cache;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* {@link CacheKeyPrefix} provides a hook for creating custom prefixes prepended to the actual {@literal key} stored in
|
||||
* Couchbase.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
* @author Michael Nitschinger
|
||||
* @since 4.0.0
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface CacheKeyPrefix {
|
||||
|
||||
/**
|
||||
* Default separator.
|
||||
*
|
||||
* @since 2.3
|
||||
*/
|
||||
String SEPARATOR = "::";
|
||||
|
||||
/**
|
||||
* Creates a default {@link CacheKeyPrefix} scheme that prefixes cache keys with {@code cacheName} followed by double
|
||||
* colons. A cache named {@code myCache} will prefix all cache keys with {@code myCache::}.
|
||||
*
|
||||
* @return the default {@link CacheKeyPrefix} scheme.
|
||||
*/
|
||||
static CacheKeyPrefix simple() {
|
||||
return name -> name + SEPARATOR;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link CacheKeyPrefix} scheme that prefixes cache keys with the given {@code prefix}. The prefix is
|
||||
* prepended to the {@code cacheName} followed by double colons. A prefix {@code cb-} with a cache named
|
||||
* {@code myCache} results in {@code cb-myCache::}.
|
||||
*
|
||||
* @param prefix must not be {@literal null}.
|
||||
* @return the default {@link CacheKeyPrefix} scheme.
|
||||
* @since 4.0.0
|
||||
*/
|
||||
static CacheKeyPrefix prefixed(final String prefix) {
|
||||
Assert.notNull(prefix, "Prefix must not be null!");
|
||||
return name -> prefix + name + SEPARATOR;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the prefix for the actual {@literal key} stored in Couchbase.
|
||||
*
|
||||
* @param cacheName will never be {@literal null}.
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
String compute(String cacheName);
|
||||
|
||||
}
|
||||
234
src/main/java/org/springframework/data/couchbase/cache/CouchbaseCache.java
vendored
Normal file
234
src/main/java/org/springframework/data/couchbase/cache/CouchbaseCache.java
vendored
Normal file
@@ -0,0 +1,234 @@
|
||||
/*
|
||||
* 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.cache;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.StringJoiner;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import org.springframework.cache.support.AbstractValueAdaptingCache;
|
||||
import org.springframework.cache.support.SimpleValueWrapper;
|
||||
import org.springframework.core.convert.ConversionFailedException;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
public class CouchbaseCache extends AbstractValueAdaptingCache {
|
||||
|
||||
private final String name;
|
||||
private final CouchbaseCacheWriter cacheWriter;
|
||||
private final CouchbaseCacheConfiguration cacheConfig;
|
||||
private final ConversionService conversionService;
|
||||
|
||||
protected CouchbaseCache(final String name, final CouchbaseCacheWriter cacheWriter,
|
||||
final CouchbaseCacheConfiguration cacheConfig) {
|
||||
super(cacheConfig.getAllowCacheNullValues());
|
||||
|
||||
Assert.notNull(name, "Name must not be null!");
|
||||
Assert.notNull(cacheWriter, "CacheWriter must not be null!");
|
||||
Assert.notNull(cacheConfig, "CacheConfig must not be null!");
|
||||
|
||||
this.name = name;
|
||||
this.cacheWriter = cacheWriter;
|
||||
this.cacheConfig = cacheConfig;
|
||||
this.conversionService = cacheConfig.getConversionService();
|
||||
}
|
||||
|
||||
private static <T> T valueFromLoader(Object key, Callable<T> valueLoader) {
|
||||
try {
|
||||
return valueLoader.call();
|
||||
} catch (Exception e) {
|
||||
throw new ValueRetrievalException(key, valueLoader, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CouchbaseCacheWriter getNativeCache() {
|
||||
return cacheWriter;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object lookup(final Object key) {
|
||||
return cacheWriter.get(cacheConfig.getCollectionName(), createCacheKey(key), cacheConfig.getValueTranscoder());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the configuration for this {@link CouchbaseCache}.
|
||||
*/
|
||||
public CouchbaseCacheConfiguration getCacheConfiguration() {
|
||||
return cacheConfig;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public synchronized <T> T get(final Object key, final Callable<T> valueLoader) {
|
||||
ValueWrapper result = get(key);
|
||||
|
||||
if (result != null) {
|
||||
return (T) result.get();
|
||||
}
|
||||
|
||||
T value = valueFromLoader(key, valueLoader);
|
||||
put(key, value);
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(final Object key, final Object value) {
|
||||
if (!isAllowNullValues() && value == null) {
|
||||
|
||||
throw new IllegalArgumentException(String.format(
|
||||
"Cache '%s' does not allow 'null' values. Avoid storing null via '@Cacheable(unless=\"#result == null\")' or "
|
||||
+ "configure CouchbaseCache to allow 'null' via CouchbaseCacheConfiguration.",
|
||||
name));
|
||||
}
|
||||
|
||||
cacheWriter.put(cacheConfig.getCollectionName(), createCacheKey(key), toStoreValue(value), cacheConfig.getExpiry(),
|
||||
cacheConfig.getValueTranscoder());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ValueWrapper putIfAbsent(final Object key, final Object value) {
|
||||
if (!isAllowNullValues() && value == null) {
|
||||
return get(key);
|
||||
}
|
||||
|
||||
Object result = cacheWriter.putIfAbsent(cacheConfig.getCollectionName(), createCacheKey(key), toStoreValue(value),
|
||||
cacheConfig.getExpiry(), cacheConfig.getValueTranscoder());
|
||||
|
||||
if (result == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new SimpleValueWrapper(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void evict(final Object key) {
|
||||
cacheWriter.remove(cacheConfig.getCollectionName(), createCacheKey(key));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean evictIfPresent(final Object key) {
|
||||
return cacheWriter.remove(cacheConfig.getCollectionName(), createCacheKey(key));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean invalidate() {
|
||||
return cacheWriter.clear(cacheConfig.getCollectionName(), cacheConfig.getKeyPrefixFor(name)) > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
cacheWriter.clear( cacheConfig.getCollectionName(), cacheConfig.getKeyPrefixFor(name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Customization hook for creating cache key before it gets serialized.
|
||||
*
|
||||
* @param key will never be {@literal null}.
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
protected String createCacheKey(final Object key) {
|
||||
String convertedKey = convertKey(key);
|
||||
if (!cacheConfig.usePrefix()) {
|
||||
return convertedKey;
|
||||
}
|
||||
return prefixCacheKey(convertedKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert {@code key} to a {@link String} representation used for cache key creation.
|
||||
*
|
||||
* @param key will never be {@literal null}.
|
||||
* @return never {@literal null}.
|
||||
* @throws IllegalStateException if {@code key} cannot be converted to {@link String}.
|
||||
*/
|
||||
protected String convertKey(final Object key) {
|
||||
if (key instanceof String) {
|
||||
return (String) key;
|
||||
}
|
||||
|
||||
TypeDescriptor source = TypeDescriptor.forObject(key);
|
||||
|
||||
if (conversionService.canConvert(source, TypeDescriptor.valueOf(String.class))) {
|
||||
try {
|
||||
return conversionService.convert(key, String.class);
|
||||
} catch (ConversionFailedException e) {
|
||||
// may fail if the given key is a collection
|
||||
if (isCollectionLikeOrMap(source)) {
|
||||
return convertCollectionLikeOrMapKey(key, source);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
Method toString = ReflectionUtils.findMethod(key.getClass(), "toString");
|
||||
if (toString != null && !Object.class.equals(toString.getDeclaringClass())) {
|
||||
return key.toString();
|
||||
}
|
||||
|
||||
throw new IllegalStateException(String.format(
|
||||
"Cannot convert cache key %s to String. Please register a suitable Converter via "
|
||||
+ "'CouchbaseCacheConfiguration.configureKeyConverters(...)' or override '%s.toString()'.",
|
||||
source, key.getClass().getSimpleName()));
|
||||
}
|
||||
|
||||
private String prefixCacheKey(final String key) {
|
||||
// allow contextual cache names by computing the key prefix on every call.
|
||||
return cacheConfig.getKeyPrefixFor(name) + key;
|
||||
}
|
||||
|
||||
private boolean isCollectionLikeOrMap(final TypeDescriptor source) {
|
||||
return source.isArray() || source.isCollection() || source.isMap();
|
||||
}
|
||||
|
||||
private String convertCollectionLikeOrMapKey(final Object key, final TypeDescriptor source) {
|
||||
if (source.isMap()) {
|
||||
StringBuilder target = new StringBuilder("{");
|
||||
|
||||
for (Map.Entry<?, ?> entry : ((Map<?, ?>) key).entrySet()) {
|
||||
target.append(convertKey(entry.getKey())).append("=").append(convertKey(entry.getValue()));
|
||||
}
|
||||
target.append("}");
|
||||
|
||||
return target.toString();
|
||||
} else if (source.isCollection() || source.isArray()) {
|
||||
StringJoiner sj = new StringJoiner(",");
|
||||
|
||||
Collection<?> collection = source.isCollection() ? (Collection<?>) key
|
||||
: Arrays.asList(ObjectUtils.toObjectArray(key));
|
||||
|
||||
for (Object val : collection) {
|
||||
sj.add(convertKey(val));
|
||||
}
|
||||
return "[" + sj.toString() + "]";
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException(String.format("Cannot convert cache key %s to String.", key));
|
||||
}
|
||||
|
||||
}
|
||||
206
src/main/java/org/springframework/data/couchbase/cache/CouchbaseCacheConfiguration.java
vendored
Normal file
206
src/main/java/org/springframework/data/couchbase/cache/CouchbaseCacheConfiguration.java
vendored
Normal file
@@ -0,0 +1,206 @@
|
||||
/*
|
||||
* Copyright 2012-2022 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.cache;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Duration;
|
||||
|
||||
import org.springframework.cache.Cache;
|
||||
import org.springframework.cache.interceptor.SimpleKey;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.converter.ConverterRegistry;
|
||||
import org.springframework.format.support.DefaultFormattingConversionService;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.couchbase.client.java.codec.SerializableTranscoder;
|
||||
import com.couchbase.client.java.codec.Transcoder;
|
||||
|
||||
public class CouchbaseCacheConfiguration {
|
||||
|
||||
private final Duration expiry;
|
||||
private final boolean cacheNullValues;
|
||||
private final CacheKeyPrefix keyPrefix;
|
||||
private final boolean usePrefix;
|
||||
private final Transcoder valueTranscoder;
|
||||
private final ConversionService conversionService;
|
||||
private final String collectionName;
|
||||
|
||||
private CouchbaseCacheConfiguration(final Duration expiry, final boolean cacheNullValues, final boolean usePrefix,
|
||||
final CacheKeyPrefix keyPrefix, final ConversionService conversionService, final Transcoder valueTranscoder,
|
||||
final String collectionName) {
|
||||
this.expiry = expiry;
|
||||
this.cacheNullValues = cacheNullValues;
|
||||
this.usePrefix = usePrefix;
|
||||
this.keyPrefix = keyPrefix;
|
||||
this.conversionService = conversionService;
|
||||
this.valueTranscoder = valueTranscoder;
|
||||
this.collectionName = collectionName;
|
||||
}
|
||||
|
||||
public static CouchbaseCacheConfiguration defaultCacheConfig() {
|
||||
DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();
|
||||
registerDefaultConverters(conversionService);
|
||||
|
||||
return new CouchbaseCacheConfiguration(Duration.ZERO, true, true, CacheKeyPrefix.simple(), conversionService,
|
||||
SerializableTranscoder.INSTANCE, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers default cache key converters. The following converters get registered:
|
||||
* <ul>
|
||||
* <li>{@link String} to {@link byte byte[]} using UTF-8 encoding.</li>
|
||||
* <li>{@link SimpleKey} to {@link String}</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param registry must not be {@literal null}.
|
||||
*/
|
||||
public static void registerDefaultConverters(final ConverterRegistry registry) {
|
||||
Assert.notNull(registry, "ConverterRegistry must not be null!");
|
||||
registry.addConverter(String.class, byte[].class, source -> source.getBytes(StandardCharsets.UTF_8));
|
||||
registry.addConverter(SimpleKey.class, String.class, SimpleKey::toString);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the expiry to apply for cache entries. Use {@link Duration#ZERO} to declare an eternal cache.
|
||||
*
|
||||
* @param expiry must not be {@literal null}.
|
||||
* @return new {@link CouchbaseCacheConfiguration}.
|
||||
*/
|
||||
public CouchbaseCacheConfiguration entryExpiry(final Duration expiry) {
|
||||
Assert.notNull(expiry, "Expiry duration must not be null!");
|
||||
return new CouchbaseCacheConfiguration(expiry, cacheNullValues, usePrefix, keyPrefix, conversionService,
|
||||
valueTranscoder, collectionName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the collectinName to use.
|
||||
*
|
||||
* @param collectionName must not be {@literal null}.
|
||||
* @return new {@link CouchbaseCacheConfiguration}.
|
||||
*/
|
||||
public CouchbaseCacheConfiguration collection(final String collectionName) {
|
||||
Assert.notNull(collectionName, "collectionName must not be null!");
|
||||
return new CouchbaseCacheConfiguration(expiry, cacheNullValues, usePrefix, keyPrefix, conversionService,
|
||||
valueTranscoder, collectionName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a custom transcoder to use for reads and writes.
|
||||
*
|
||||
* @param valueTranscoder the transcoder that should be used.
|
||||
* @return new {@link CouchbaseCacheConfiguration}.
|
||||
*/
|
||||
public CouchbaseCacheConfiguration valueTranscoder(final Transcoder valueTranscoder) {
|
||||
Assert.notNull(valueTranscoder, "Transcoder must not be null!");
|
||||
return new CouchbaseCacheConfiguration(expiry, cacheNullValues, usePrefix, keyPrefix, conversionService,
|
||||
valueTranscoder, collectionName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable caching {@literal null} values. <br />
|
||||
* <strong>NOTE</strong> any {@link org.springframework.cache.Cache#put(Object, Object)} operation involving
|
||||
* {@literal null} value will error. Nothing will be written to Couchbase, nothing will be removed. An already
|
||||
* existing key will still be there afterwards with the very same value as before.
|
||||
*
|
||||
* @return new {@link CouchbaseCacheConfiguration}.
|
||||
*/
|
||||
public CouchbaseCacheConfiguration disableCachingNullValues() {
|
||||
return new CouchbaseCacheConfiguration(expiry, false, usePrefix, keyPrefix, conversionService, valueTranscoder,
|
||||
collectionName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefix the {@link CouchbaseCache#getName() cache name} with the given value. <br />
|
||||
* The generated cache key will be: {@code prefix + cache name + "::" + cache entry key}.
|
||||
*
|
||||
* @param prefix the prefix to prepend to the cache name.
|
||||
* @return this.
|
||||
* @see #computePrefixWith(CacheKeyPrefix)
|
||||
* @see CacheKeyPrefix#prefixed(String)
|
||||
*/
|
||||
public CouchbaseCacheConfiguration prefixCacheNameWith(final String prefix) {
|
||||
return computePrefixWith(CacheKeyPrefix.prefixed(prefix));
|
||||
}
|
||||
|
||||
/**
|
||||
* Use the given {@link CacheKeyPrefix} to compute the prefix for the actual Couchbase {@literal key} given the
|
||||
* {@literal cache name} as function input.
|
||||
*
|
||||
* @param cacheKeyPrefix must not be {@literal null}.
|
||||
* @return new {@link CouchbaseCacheConfiguration}.
|
||||
* @see CacheKeyPrefix
|
||||
*/
|
||||
public CouchbaseCacheConfiguration computePrefixWith(CacheKeyPrefix cacheKeyPrefix) {
|
||||
Assert.notNull(cacheKeyPrefix, "Function for computing prefix must not be null!");
|
||||
return new CouchbaseCacheConfiguration(expiry, cacheNullValues, true, cacheKeyPrefix, conversionService,
|
||||
valueTranscoder, collectionName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The expiration time (ttl) for cache entries. Never {@literal null}.
|
||||
*/
|
||||
public Duration getExpiry() {
|
||||
return expiry;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@literal true} if caching {@literal null} is allowed.
|
||||
*/
|
||||
public boolean getAllowCacheNullValues() {
|
||||
return cacheNullValues;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The {@link ConversionService} used for cache key to {@link String} conversion. Never {@literal null}.
|
||||
*/
|
||||
public ConversionService getConversionService() {
|
||||
return conversionService;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@literal true} if cache keys need to be prefixed with the {@link #getKeyPrefixFor(String)} if present or
|
||||
* the default which resolves to {@link Cache#getName()}.
|
||||
*/
|
||||
public boolean usePrefix() {
|
||||
return usePrefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the computed {@literal key} prefix for a given {@literal cacheName}.
|
||||
*
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
public String getKeyPrefixFor(final String cacheName) {
|
||||
Assert.notNull(cacheName, "Cache name must not be null!");
|
||||
return keyPrefix.compute(cacheName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the transcoder for encoding and decoding cache values.
|
||||
*/
|
||||
public Transcoder getValueTranscoder() {
|
||||
return valueTranscoder;
|
||||
}
|
||||
|
||||
/**
|
||||
* The name of the collection to use for this cache - if empty uses the default collection.
|
||||
*/
|
||||
public String getCollectionName() {
|
||||
return collectionName;
|
||||
}
|
||||
|
||||
}
|
||||
278
src/main/java/org/springframework/data/couchbase/cache/CouchbaseCacheManager.java
vendored
Normal file
278
src/main/java/org/springframework/data/couchbase/cache/CouchbaseCacheManager.java
vendored
Normal file
@@ -0,0 +1,278 @@
|
||||
/*
|
||||
* Copyright 2012-2022 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.cache;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.cache.Cache;
|
||||
import org.springframework.cache.transaction.AbstractTransactionSupportingCacheManager;
|
||||
import org.springframework.data.couchbase.CouchbaseClientFactory;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
public class CouchbaseCacheManager extends AbstractTransactionSupportingCacheManager {
|
||||
|
||||
private final CouchbaseCacheWriter cacheWriter;
|
||||
private final CouchbaseCacheConfiguration defaultCacheConfig;
|
||||
private final Map<String, CouchbaseCacheConfiguration> initialCacheConfiguration;
|
||||
private final boolean allowInFlightCacheCreation;
|
||||
|
||||
/**
|
||||
* Creates new {@link CouchbaseCacheManager} using given {@link CouchbaseCacheWriter} and default
|
||||
* {@link CouchbaseCacheConfiguration}.
|
||||
*
|
||||
* @param cacheWriter must not be {@literal null}.
|
||||
* @param defaultCacheConfiguration must not be {@literal null}. Maybe just use
|
||||
* {@link CouchbaseCacheConfiguration#defaultCacheConfig()}.
|
||||
* @param allowInFlightCacheCreation allow create unconfigured caches.
|
||||
*/
|
||||
private CouchbaseCacheManager(final CouchbaseCacheWriter cacheWriter,
|
||||
final CouchbaseCacheConfiguration defaultCacheConfiguration,
|
||||
final Map<String, CouchbaseCacheConfiguration> initialCacheConfiguration,
|
||||
final boolean allowInFlightCacheCreation) {
|
||||
|
||||
Assert.notNull(cacheWriter, "CacheWriter must not be null!");
|
||||
Assert.notNull(defaultCacheConfiguration, "DefaultCacheConfiguration must not be null!");
|
||||
|
||||
this.cacheWriter = cacheWriter;
|
||||
this.defaultCacheConfig = defaultCacheConfiguration;
|
||||
this.initialCacheConfiguration = initialCacheConfiguration;
|
||||
this.allowInFlightCacheCreation = allowInFlightCacheCreation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link CouchbaseCacheManager} with defaults applied.
|
||||
*
|
||||
* @param clientFactory must not be {@literal null}.
|
||||
* @return new instance of {@link CouchbaseCacheManager}.
|
||||
*/
|
||||
public static CouchbaseCacheManager create(CouchbaseClientFactory clientFactory) {
|
||||
Assert.notNull(clientFactory, "ConnectionFactory must not be null!");
|
||||
return new CouchbaseCacheManager(new DefaultCouchbaseCacheWriter(clientFactory),
|
||||
CouchbaseCacheConfiguration.defaultCacheConfig(), new LinkedHashMap<>(), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Entry point for builder style {@link CouchbaseCacheManager} configuration.
|
||||
*
|
||||
* @param clientFactory must not be {@literal null}.
|
||||
* @return new {@link CouchbaseCacheManagerBuilder}.
|
||||
*/
|
||||
public static CouchbaseCacheManagerBuilder builder(CouchbaseClientFactory clientFactory) {
|
||||
Assert.notNull(clientFactory, "ConnectionFactory must not be null!");
|
||||
return CouchbaseCacheManagerBuilder.fromConnectionFactory(clientFactory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Entry point for builder style {@link CouchbaseCacheManager} configuration.
|
||||
*
|
||||
* @param cacheWriter must not be {@literal null}.
|
||||
* @return new {@link CouchbaseCacheManagerBuilder}.
|
||||
*/
|
||||
public static CouchbaseCacheManagerBuilder builder(CouchbaseCacheWriter cacheWriter) {
|
||||
Assert.notNull(cacheWriter, "CacheWriter must not be null!");
|
||||
return CouchbaseCacheManagerBuilder.fromCacheWriter(cacheWriter);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Collection<? extends Cache> loadCaches() {
|
||||
final List<CouchbaseCache> caches = new LinkedList<>();
|
||||
|
||||
for (Map.Entry<String, CouchbaseCacheConfiguration> entry : initialCacheConfiguration.entrySet()) {
|
||||
caches.add(createCouchbaseCache(entry.getKey(), entry.getValue()));
|
||||
}
|
||||
|
||||
return caches;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CouchbaseCache getMissingCache(final String name) {
|
||||
return allowInFlightCacheCreation ? createCouchbaseCache(name, defaultCacheConfig) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration hook for creating {@link CouchbaseCache} with given name and {@code cacheConfig}.
|
||||
*
|
||||
* @param name must not be {@literal null}.
|
||||
* @param cacheConfig can be {@literal null}.
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
protected CouchbaseCache createCouchbaseCache(final String name,
|
||||
@Nullable final CouchbaseCacheConfiguration cacheConfig) {
|
||||
return new CouchbaseCache(name, cacheWriter, cacheConfig != null ? cacheConfig : defaultCacheConfig);
|
||||
}
|
||||
|
||||
public static class CouchbaseCacheManagerBuilder {
|
||||
|
||||
private final CouchbaseCacheWriter cacheWriter;
|
||||
private final Map<String, CouchbaseCacheConfiguration> initialCaches = new LinkedHashMap<>();
|
||||
boolean allowInFlightCacheCreation = true;
|
||||
private CouchbaseCacheConfiguration defaultCacheConfiguration = CouchbaseCacheConfiguration.defaultCacheConfig();
|
||||
private boolean enableTransactions;
|
||||
|
||||
private CouchbaseCacheManagerBuilder(CouchbaseCacheWriter cacheWriter) {
|
||||
this.cacheWriter = cacheWriter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Entry point for builder style {@link CouchbaseCacheManager} configuration.
|
||||
*
|
||||
* @param clientFactory must not be {@literal null}.
|
||||
* @return new {@link CouchbaseCacheManagerBuilder}.
|
||||
*/
|
||||
public static CouchbaseCacheManagerBuilder fromConnectionFactory(CouchbaseClientFactory clientFactory) {
|
||||
Assert.notNull(clientFactory, "ConnectionFactory must not be null!");
|
||||
return builder(new DefaultCouchbaseCacheWriter(clientFactory));
|
||||
}
|
||||
|
||||
/**
|
||||
* Entry point for builder style {@link CouchbaseCacheManager} configuration.
|
||||
*
|
||||
* @param cacheWriter must not be {@literal null}.
|
||||
* @return new {@link CouchbaseCacheManagerBuilder}.
|
||||
*/
|
||||
public static CouchbaseCacheManagerBuilder fromCacheWriter(CouchbaseCacheWriter cacheWriter) {
|
||||
Assert.notNull(cacheWriter, "CacheWriter must not be null!");
|
||||
return new CouchbaseCacheManagerBuilder(cacheWriter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Define a default {@link CouchbaseCacheConfiguration} applied to dynamically created {@link CouchbaseCache}s.
|
||||
*
|
||||
* @param defaultCacheConfiguration must not be {@literal null}.
|
||||
* @return this {@link CouchbaseCacheManagerBuilder}.
|
||||
*/
|
||||
public CouchbaseCacheManagerBuilder cacheDefaults(CouchbaseCacheConfiguration defaultCacheConfiguration) {
|
||||
Assert.notNull(defaultCacheConfiguration, "DefaultCacheConfiguration must not be null!");
|
||||
this.defaultCacheConfiguration = defaultCacheConfiguration;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable {@link CouchbaseCache}s to synchronize cache put/evict operations with ongoing Spring-managed
|
||||
* transactions.
|
||||
*
|
||||
* @return this {@link CouchbaseCacheManagerBuilder}.
|
||||
*/
|
||||
public CouchbaseCacheManagerBuilder transactionAware() {
|
||||
this.enableTransactions = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Append a {@link Set} of cache names to be pre initialized with current {@link CouchbaseCacheConfiguration}.
|
||||
* <strong>NOTE:</strong> This calls depends on {@link #cacheDefaults(CouchbaseCacheConfiguration)} using whatever
|
||||
* default {@link CouchbaseCacheConfiguration} is present at the time of invoking this method.
|
||||
*
|
||||
* @param cacheNames must not be {@literal null}.
|
||||
* @return this {@link CouchbaseCacheManagerBuilder}.
|
||||
*/
|
||||
public CouchbaseCacheManagerBuilder initialCacheNames(Set<String> cacheNames) {
|
||||
Assert.notNull(cacheNames, "CacheNames must not be null!");
|
||||
cacheNames.forEach(it -> withCacheConfiguration(it, defaultCacheConfiguration));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Append a {@link Map} of cache name/{@link CouchbaseCacheConfiguration} pairs to be pre initialized.
|
||||
*
|
||||
* @param cacheConfigurations must not be {@literal null}.
|
||||
* @return this {@link CouchbaseCacheManagerBuilder}.
|
||||
*/
|
||||
public CouchbaseCacheManagerBuilder withInitialCacheConfigurations(
|
||||
Map<String, CouchbaseCacheConfiguration> cacheConfigurations) {
|
||||
|
||||
Assert.notNull(cacheConfigurations, "CacheConfigurations must not be null!");
|
||||
cacheConfigurations.forEach((cacheName, configuration) -> Assert.notNull(configuration,
|
||||
String.format("CouchbaseCacheConfiguration for cache %s must not be null!", cacheName)));
|
||||
|
||||
this.initialCaches.putAll(cacheConfigurations);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param cacheName
|
||||
* @param cacheConfiguration
|
||||
* @return this {@link CouchbaseCacheManagerBuilder}.
|
||||
*/
|
||||
public CouchbaseCacheManagerBuilder withCacheConfiguration(String cacheName,
|
||||
CouchbaseCacheConfiguration cacheConfiguration) {
|
||||
|
||||
Assert.notNull(cacheName, "CacheName must not be null!");
|
||||
Assert.notNull(cacheConfiguration, "CacheConfiguration must not be null!");
|
||||
|
||||
this.initialCaches.put(cacheName, cacheConfiguration);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable in-flight {@link org.springframework.cache.Cache} creation for unconfigured caches.
|
||||
* <p>
|
||||
* {@link CouchbaseCacheManager#getMissingCache(String)} returns {@literal null} for any unconfigured
|
||||
* {@link org.springframework.cache.Cache} instead of a new {@link CouchbaseCache} instance. This allows eg.
|
||||
* {@link org.springframework.cache.support.CompositeCacheManager} to chime in.
|
||||
*
|
||||
* @return this {@link CouchbaseCacheManagerBuilder}.
|
||||
*/
|
||||
public CouchbaseCacheManagerBuilder disableCreateOnMissingCache() {
|
||||
|
||||
this.allowInFlightCacheCreation = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link Set} of cache names for which the builder holds {@link CouchbaseCacheConfiguration configuration}.
|
||||
*
|
||||
* @return an unmodifiable {@link Set} holding the name of caches for which a {@link CouchbaseCacheConfiguration
|
||||
* configuration} has been set.
|
||||
*/
|
||||
public Set<String> getConfiguredCaches() {
|
||||
return Collections.unmodifiableSet(this.initialCaches.keySet());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link CouchbaseCacheConfiguration} for a given cache by its name.
|
||||
*
|
||||
* @param cacheName must not be {@literal null}.
|
||||
* @return {@link Optional#empty()} if no {@link CouchbaseCacheConfiguration} set for the given cache name.
|
||||
*/
|
||||
public Optional<CouchbaseCacheConfiguration> getCacheConfigurationFor(String cacheName) {
|
||||
return Optional.ofNullable(this.initialCaches.get(cacheName));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new instance of {@link CouchbaseCacheManager} with configuration options applied.
|
||||
*
|
||||
* @return new instance of {@link CouchbaseCacheManager}.
|
||||
*/
|
||||
public CouchbaseCacheManager build() {
|
||||
CouchbaseCacheManager cm = new CouchbaseCacheManager(cacheWriter, defaultCacheConfiguration, initialCaches,
|
||||
allowInFlightCacheCreation);
|
||||
cm.setTransactionAware(enableTransactions);
|
||||
return cm;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
79
src/main/java/org/springframework/data/couchbase/cache/CouchbaseCacheWriter.java
vendored
Normal file
79
src/main/java/org/springframework/data/couchbase/cache/CouchbaseCacheWriter.java
vendored
Normal file
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* 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.cache;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
import com.couchbase.client.java.codec.Transcoder;
|
||||
|
||||
public interface CouchbaseCacheWriter {
|
||||
|
||||
/**
|
||||
* Write the given key/value pair to Couchbase an set the expiration time if defined.
|
||||
*
|
||||
* @param collectionName The cache name must not be {@literal null}.
|
||||
* @param key The key for the cache entry. Must not be {@literal null}.
|
||||
* @param value The value stored for the key. Must not be {@literal null}.
|
||||
* @param expiry Optional expiration time. Can be {@literal null}.
|
||||
* @param transcoder Optional transcoder to use. Can be {@literal null}.
|
||||
*/
|
||||
void put(String collectionName, String key, Object value, @Nullable Duration expiry, @Nullable Transcoder transcoder);
|
||||
|
||||
/**
|
||||
* Write the given value to Couchbase if the key does not already exist.
|
||||
*
|
||||
* @param collectionName The cache name must not be {@literal null}.
|
||||
* @param key The key for the cache entry. Must not be {@literal null}.
|
||||
* @param value The value stored for the key. Must not be {@literal null}.
|
||||
* @param expiry Optional expiration time. Can be {@literal null}.
|
||||
* @param transcoder Optional transcoder to use. Can be {@literal null}.
|
||||
*/
|
||||
@Nullable
|
||||
Object putIfAbsent(String collectionName, String key, Object value, @Nullable Duration expiry,
|
||||
@Nullable Transcoder transcoder);
|
||||
|
||||
/**
|
||||
* Get the binary value representation from Couchbase stored for the given key.
|
||||
*
|
||||
* @param collectionName must not be {@literal null}.
|
||||
* @param key must not be {@literal null}.
|
||||
* @param transcoder Optional transcoder to use. Can be {@literal null}.
|
||||
* @return {@literal null} if key does not exist.
|
||||
*/
|
||||
@Nullable
|
||||
Object get(String collectionName, String key, @Nullable Transcoder transcoder);
|
||||
|
||||
/**
|
||||
* Remove the given key from Couchbase.
|
||||
*
|
||||
* @param collectionName The cache name must not be {@literal null}.
|
||||
* @param key The key for the cache entry. Must not be {@literal null}.
|
||||
* @return true if the document existed on removal, false otherwise.
|
||||
*/
|
||||
boolean remove(String collectionName, String key);
|
||||
|
||||
/**
|
||||
* Clears the cache with the given key pattern prefix.
|
||||
*
|
||||
* @param pattern the pattern to clear.
|
||||
* @return the number of cleared items.
|
||||
*/
|
||||
long clear(String collectionName, String pattern);
|
||||
|
||||
}
|
||||
139
src/main/java/org/springframework/data/couchbase/cache/DefaultCouchbaseCacheWriter.java
vendored
Normal file
139
src/main/java/org/springframework/data/couchbase/cache/DefaultCouchbaseCacheWriter.java
vendored
Normal file
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* 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.cache;
|
||||
|
||||
import static com.couchbase.client.core.io.CollectionIdentifier.DEFAULT_COLLECTION;
|
||||
import static com.couchbase.client.core.io.CollectionIdentifier.DEFAULT_SCOPE;
|
||||
import static com.couchbase.client.java.kv.GetOptions.*;
|
||||
import static com.couchbase.client.java.kv.InsertOptions.*;
|
||||
import static com.couchbase.client.java.kv.UpsertOptions.*;
|
||||
import static com.couchbase.client.java.query.QueryOptions.*;
|
||||
import static com.couchbase.client.java.query.QueryScanConsistency.REQUEST_PLUS;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import org.springframework.data.couchbase.CouchbaseClientFactory;
|
||||
|
||||
import com.couchbase.client.core.error.DocumentExistsException;
|
||||
import com.couchbase.client.core.error.DocumentNotFoundException;
|
||||
import com.couchbase.client.java.Collection;
|
||||
import com.couchbase.client.java.Scope;
|
||||
import com.couchbase.client.java.codec.Transcoder;
|
||||
import com.couchbase.client.java.json.JsonObject;
|
||||
import com.couchbase.client.java.kv.InsertOptions;
|
||||
import com.couchbase.client.java.kv.UpsertOptions;
|
||||
import com.couchbase.client.java.query.QueryMetrics;
|
||||
import com.couchbase.client.java.query.QueryResult;
|
||||
|
||||
public class DefaultCouchbaseCacheWriter implements CouchbaseCacheWriter {
|
||||
|
||||
private final CouchbaseClientFactory clientFactory;
|
||||
|
||||
public DefaultCouchbaseCacheWriter(final CouchbaseClientFactory clientFactory) {
|
||||
this.clientFactory = clientFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(final String collectionName, final String key, final Object value, final Duration expiry,
|
||||
final Transcoder transcoder) {
|
||||
UpsertOptions options = upsertOptions();
|
||||
|
||||
if (expiry != null) {
|
||||
options.expiry(expiry);
|
||||
}
|
||||
if (transcoder != null) {
|
||||
options.transcoder(transcoder);
|
||||
}
|
||||
|
||||
getCollection(collectionName).upsert(key, value, options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object putIfAbsent(final String collectionName, final String key, final Object value, final Duration expiry,
|
||||
final Transcoder transcoder) {
|
||||
InsertOptions options = insertOptions();
|
||||
|
||||
if (expiry != null) {
|
||||
options.expiry(expiry);
|
||||
}
|
||||
if (transcoder != null) {
|
||||
options.transcoder(transcoder);
|
||||
}
|
||||
|
||||
try {
|
||||
getCollection(collectionName).insert(key, value, options);
|
||||
return null;
|
||||
} catch (final DocumentExistsException ex) {
|
||||
// If the document exists, return the current one per contract
|
||||
return get(collectionName, key, transcoder);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object get(final String collectionName, final String key, final Transcoder transcoder) {
|
||||
// TODO .. the decoding side transcoding needs to be figured out?
|
||||
try {
|
||||
return getCollection(collectionName).get(key, getOptions().transcoder(transcoder)).contentAs(Object.class);
|
||||
} catch (DocumentNotFoundException ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(final String collectionName, final String key) {
|
||||
try {
|
||||
getCollection(collectionName).remove(key);
|
||||
return true;
|
||||
} catch (final DocumentNotFoundException ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long clear(final String collectionName, final String pattern) {
|
||||
QueryResult result;
|
||||
if (getScope() == null
|
||||
|| (DEFAULT_SCOPE.equals(getScope().name()) && DEFAULT_COLLECTION.equals(getCollection(collectionName).name()))) {
|
||||
result = clientFactory.getCluster().query(
|
||||
"DELETE FROM `" + clientFactory.getBucket().name() + "` where meta().id LIKE $pattern",
|
||||
queryOptions().scanConsistency(REQUEST_PLUS).metrics(true)
|
||||
.parameters(JsonObject.create().put("pattern", pattern + "%")));
|
||||
} else {
|
||||
result = clientFactory.getScope().query(
|
||||
"DELETE FROM `" + getCollection(collectionName).name() + "` where meta().id LIKE $pattern",
|
||||
queryOptions().scanConsistency(REQUEST_PLUS).metrics(true)
|
||||
.parameters(JsonObject.create().put("pattern", pattern + "%")));
|
||||
}
|
||||
return result.metaData().metrics().map(QueryMetrics::mutationCount).orElse(0L);
|
||||
}
|
||||
|
||||
private Collection getCollection(final String collectionName) {
|
||||
final Scope scope = clientFactory.getScope();
|
||||
if (collectionName == null) {
|
||||
if (!scope.name().equals(DEFAULT_SCOPE)) {
|
||||
throw new IllegalStateException("A collectionName must be provided if a non-default scope is used!");
|
||||
}
|
||||
return clientFactory.getBucket().defaultCollection();
|
||||
}
|
||||
return scope.collection(collectionName);
|
||||
}
|
||||
|
||||
private Scope getScope() {
|
||||
return clientFactory.getScope();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,486 @@
|
||||
/*
|
||||
* Copyright 2012-2022 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.config;
|
||||
|
||||
import static com.couchbase.client.java.ClusterOptions.clusterOptions;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Role;
|
||||
import org.springframework.core.convert.converter.GenericConverter;
|
||||
import org.springframework.core.type.filter.AnnotationTypeFilter;
|
||||
import org.springframework.data.convert.CustomConversions;
|
||||
import org.springframework.data.convert.PropertyValueConverterRegistrar;
|
||||
import org.springframework.data.convert.SimplePropertyValueConversions;
|
||||
import org.springframework.data.couchbase.CouchbaseClientFactory;
|
||||
import org.springframework.data.couchbase.SimpleCouchbaseClientFactory;
|
||||
import org.springframework.data.couchbase.core.CouchbaseTemplate;
|
||||
import org.springframework.data.couchbase.core.ReactiveCouchbaseTemplate;
|
||||
import org.springframework.data.couchbase.core.convert.CouchbaseCustomConversions;
|
||||
import org.springframework.data.couchbase.core.convert.CouchbasePropertyValueConverterFactory;
|
||||
import org.springframework.data.couchbase.core.convert.CryptoConverter;
|
||||
import org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter;
|
||||
import org.springframework.data.couchbase.core.convert.translation.JacksonTranslationService;
|
||||
import org.springframework.data.couchbase.core.convert.translation.TranslationService;
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbaseMappingContext;
|
||||
import org.springframework.data.couchbase.core.mapping.Document;
|
||||
import org.springframework.data.couchbase.repository.config.ReactiveRepositoryOperationsMapping;
|
||||
import org.springframework.data.couchbase.repository.config.RepositoryOperationsMapping;
|
||||
import org.springframework.data.couchbase.transaction.CouchbaseCallbackTransactionManager;
|
||||
import org.springframework.data.couchbase.transaction.CouchbaseTransactionInterceptor;
|
||||
import org.springframework.data.couchbase.transaction.CouchbaseTransactionalOperator;
|
||||
import org.springframework.data.mapping.model.CamelCaseAbbreviatingFieldNamingStrategy;
|
||||
import org.springframework.data.mapping.model.FieldNamingStrategy;
|
||||
import org.springframework.data.mapping.model.PropertyNameFieldNamingStrategy;
|
||||
import org.springframework.transaction.TransactionManager;
|
||||
import org.springframework.transaction.annotation.AnnotationTransactionAttributeSource;
|
||||
import org.springframework.transaction.interceptor.TransactionAttributeSource;
|
||||
import org.springframework.transaction.interceptor.TransactionInterceptor;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.couchbase.client.core.deps.com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.couchbase.client.core.encryption.CryptoManager;
|
||||
import com.couchbase.client.core.env.Authenticator;
|
||||
import com.couchbase.client.core.env.PasswordAuthenticator;
|
||||
import com.couchbase.client.core.error.CouchbaseException;
|
||||
import com.couchbase.client.java.Cluster;
|
||||
import com.couchbase.client.java.codec.JacksonJsonSerializer;
|
||||
import com.couchbase.client.java.encryption.databind.jackson.EncryptionModule;
|
||||
import com.couchbase.client.java.env.ClusterEnvironment;
|
||||
import com.couchbase.client.java.json.JacksonTransformers;
|
||||
import com.couchbase.client.java.json.JsonValueModule;
|
||||
import com.couchbase.client.java.query.QueryScanConsistency;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
/**
|
||||
* Base class for Spring Data Couchbase configuration using JavaConfig.
|
||||
*
|
||||
* @author Michael Nitschinger
|
||||
* @author Simon Baslé
|
||||
* @author Stephane Nicoll
|
||||
* @author Subhashni Balakrishnan
|
||||
* @author Jorge Rodriguez Martin
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
@Configuration
|
||||
public abstract class AbstractCouchbaseConfiguration {
|
||||
|
||||
/**
|
||||
* The connection string which allows the SDK to connect to the cluster.
|
||||
* <p>
|
||||
* Note that the connection string can take many forms, in its simplest it is just a single hostname like "127.0.0.1".
|
||||
* Please refer to the couchbase Java SDK documentation for all the different possibilities and options.
|
||||
*/
|
||||
public abstract String getConnectionString();
|
||||
|
||||
/**
|
||||
* The username of the user accessing Couchbase, configured on the cluster.
|
||||
*/
|
||||
public abstract String getUserName();
|
||||
|
||||
/**
|
||||
* The password used or the username to authenticate against the cluster.
|
||||
*/
|
||||
public abstract String getPassword();
|
||||
|
||||
/**
|
||||
* The name of the bucket that should be used (for example "travel-sample").
|
||||
*/
|
||||
public abstract String getBucketName();
|
||||
|
||||
/**
|
||||
* If a non-default scope should be used, override this method.
|
||||
*
|
||||
* @return the custom scope name or null if the default scope should be used (default).
|
||||
*/
|
||||
protected String getScopeName() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows to override the {@link Authenticator} used.
|
||||
* <p>
|
||||
* The default implementation uses the {@link PasswordAuthenticator} and takes the username and password from
|
||||
* {@link #getUserName()} and {@link #getPassword()} respectively.
|
||||
*
|
||||
* @return the authenticator to be passed into the SDK.
|
||||
*/
|
||||
protected Authenticator authenticator() {
|
||||
return PasswordAuthenticator.create(getUserName(), getPassword());
|
||||
}
|
||||
|
||||
/**
|
||||
* The {@link CouchbaseClientFactory} provides access to the lower level SDK resources.
|
||||
*
|
||||
* @param couchbaseCluster the cluster reference from the SDK.
|
||||
* @return the initialized factory.
|
||||
*/
|
||||
@Bean(name = BeanNames.COUCHBASE_CLIENT_FACTORY)
|
||||
public CouchbaseClientFactory couchbaseClientFactory(final Cluster couchbaseCluster) {
|
||||
return new SimpleCouchbaseClientFactory(couchbaseCluster, getBucketName(), getScopeName());
|
||||
}
|
||||
|
||||
@Bean(destroyMethod = "disconnect")
|
||||
public Cluster couchbaseCluster(ClusterEnvironment couchbaseClusterEnvironment) {
|
||||
return Cluster.connect(getConnectionString(),
|
||||
clusterOptions(authenticator()).environment(couchbaseClusterEnvironment));
|
||||
}
|
||||
|
||||
@Bean(destroyMethod = "shutdown")
|
||||
public ClusterEnvironment couchbaseClusterEnvironment() {
|
||||
ClusterEnvironment.Builder builder = ClusterEnvironment.builder();
|
||||
if (!nonShadowedJacksonPresent()) {
|
||||
throw new CouchbaseException("non-shadowed Jackson not present");
|
||||
}
|
||||
CryptoManager cryptoManager = cryptoManager();
|
||||
builder.jsonSerializer(JacksonJsonSerializer.create(couchbaseObjectMapper(cryptoManager)));
|
||||
builder.cryptoManager(cryptoManager);
|
||||
configureEnvironment(builder);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Can be overridden to customize the configuration of the environment before bootstrap.
|
||||
*
|
||||
* @param builder the builder that can be customized.
|
||||
*/
|
||||
protected void configureEnvironment(final ClusterEnvironment.Builder builder) {
|
||||
}
|
||||
|
||||
@Bean(name = BeanNames.COUCHBASE_TEMPLATE)
|
||||
public CouchbaseTemplate couchbaseTemplate(CouchbaseClientFactory couchbaseClientFactory,
|
||||
MappingCouchbaseConverter mappingCouchbaseConverter, TranslationService couchbaseTranslationService) {
|
||||
return new CouchbaseTemplate(couchbaseClientFactory, mappingCouchbaseConverter, couchbaseTranslationService,
|
||||
getDefaultConsistency());
|
||||
}
|
||||
|
||||
public CouchbaseTemplate couchbaseTemplate(CouchbaseClientFactory couchbaseClientFactory,
|
||||
MappingCouchbaseConverter mappingCouchbaseConverter) {
|
||||
return couchbaseTemplate(couchbaseClientFactory, mappingCouchbaseConverter, new JacksonTranslationService());
|
||||
}
|
||||
|
||||
@Bean(name = BeanNames.REACTIVE_COUCHBASE_TEMPLATE)
|
||||
public ReactiveCouchbaseTemplate reactiveCouchbaseTemplate(CouchbaseClientFactory couchbaseClientFactory,
|
||||
MappingCouchbaseConverter mappingCouchbaseConverter, TranslationService couchbaseTranslationService) {
|
||||
return new ReactiveCouchbaseTemplate(couchbaseClientFactory, mappingCouchbaseConverter, couchbaseTranslationService,
|
||||
getDefaultConsistency());
|
||||
}
|
||||
|
||||
public ReactiveCouchbaseTemplate reactiveCouchbaseTemplate(CouchbaseClientFactory couchbaseClientFactory,
|
||||
MappingCouchbaseConverter mappingCouchbaseConverter) {
|
||||
return reactiveCouchbaseTemplate(couchbaseClientFactory, mappingCouchbaseConverter,
|
||||
new JacksonTranslationService());
|
||||
}
|
||||
|
||||
@Bean(name = BeanNames.COUCHBASE_OPERATIONS_MAPPING)
|
||||
public RepositoryOperationsMapping couchbaseRepositoryOperationsMapping(CouchbaseTemplate couchbaseTemplate) {
|
||||
// create a base mapping that associates all repositories to the default template
|
||||
RepositoryOperationsMapping baseMapping = new RepositoryOperationsMapping(couchbaseTemplate);
|
||||
// let the user tune it
|
||||
configureRepositoryOperationsMapping(baseMapping);
|
||||
return baseMapping;
|
||||
}
|
||||
|
||||
/**
|
||||
* In order to customize the mapping between repositories/entity types to couchbase templates, use the provided
|
||||
* mapping's api (eg. in order to have different buckets backing different repositories).
|
||||
*
|
||||
* @param mapping the default mapping (will associate all repositories to the default template).
|
||||
*/
|
||||
protected void configureRepositoryOperationsMapping(RepositoryOperationsMapping mapping) {
|
||||
// NO_OP
|
||||
}
|
||||
|
||||
@Bean(name = BeanNames.REACTIVE_COUCHBASE_OPERATIONS_MAPPING)
|
||||
public ReactiveRepositoryOperationsMapping reactiveCouchbaseRepositoryOperationsMapping(
|
||||
ReactiveCouchbaseTemplate reactiveCouchbaseTemplate) {
|
||||
// create a base mapping that associates all repositories to the default template
|
||||
ReactiveRepositoryOperationsMapping baseMapping = new ReactiveRepositoryOperationsMapping(
|
||||
reactiveCouchbaseTemplate);
|
||||
// let the user tune it
|
||||
configureReactiveRepositoryOperationsMapping(baseMapping);
|
||||
return baseMapping;
|
||||
}
|
||||
|
||||
/**
|
||||
* In order to customize the mapping between repositories/entity types to couchbase templates, use the provided
|
||||
* mapping's api (eg. in order to have different buckets backing different repositories).
|
||||
*
|
||||
* @param mapping the default mapping (will associate all repositories to the default template).
|
||||
*/
|
||||
protected void configureReactiveRepositoryOperationsMapping(ReactiveRepositoryOperationsMapping mapping) {
|
||||
// NO_OP
|
||||
}
|
||||
|
||||
/**
|
||||
* Scans the mapping base package for classes annotated with {@link Document}.
|
||||
*
|
||||
* @throws ClassNotFoundException if initial entity sets could not be loaded.
|
||||
*/
|
||||
protected Set<Class<?>> getInitialEntitySet() throws ClassNotFoundException {
|
||||
String basePackage = getMappingBasePackage();
|
||||
Set<Class<?>> initialEntitySet = new HashSet<>();
|
||||
|
||||
if (StringUtils.hasText(basePackage)) {
|
||||
ClassPathScanningCandidateComponentProvider componentProvider = new ClassPathScanningCandidateComponentProvider(
|
||||
false);
|
||||
componentProvider.addIncludeFilter(new AnnotationTypeFilter(Document.class));
|
||||
for (BeanDefinition candidate : componentProvider.findCandidateComponents(basePackage)) {
|
||||
initialEntitySet.add(
|
||||
ClassUtils.forName(candidate.getBeanClassName(), AbstractCouchbaseConfiguration.class.getClassLoader()));
|
||||
}
|
||||
}
|
||||
return initialEntitySet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the name of the field that will store the type information for complex types when using the
|
||||
* {@link #mappingCouchbaseConverter(CouchbaseMappingContext, CouchbaseCustomConversions)}. Defaults to
|
||||
* {@value MappingCouchbaseConverter#TYPEKEY_DEFAULT}.
|
||||
*
|
||||
* @see MappingCouchbaseConverter#TYPEKEY_DEFAULT
|
||||
* @see MappingCouchbaseConverter#TYPEKEY_SYNCGATEWAY_COMPATIBLE
|
||||
*/
|
||||
public String typeKey() {
|
||||
return MappingCouchbaseConverter.TYPEKEY_DEFAULT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link MappingCouchbaseConverter} using the configured {@link #couchbaseMappingContext}.
|
||||
*/
|
||||
@Bean
|
||||
public MappingCouchbaseConverter mappingCouchbaseConverter(CouchbaseMappingContext couchbaseMappingContext,
|
||||
CouchbaseCustomConversions couchbaseCustomConversions) {
|
||||
MappingCouchbaseConverter converter = new MappingCouchbaseConverter(couchbaseMappingContext, typeKey());
|
||||
converter.setCustomConversions(couchbaseCustomConversions);
|
||||
couchbaseMappingContext.setSimpleTypeHolder(couchbaseCustomConversions.getSimpleTypeHolder());
|
||||
return converter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link TranslationService}.
|
||||
*
|
||||
* @return TranslationService, defaulting to JacksonTranslationService.
|
||||
*/
|
||||
@Bean
|
||||
public TranslationService couchbaseTranslationService() {
|
||||
final JacksonTranslationService jacksonTranslationService = new JacksonTranslationService();
|
||||
jacksonTranslationService.setObjectMapper(couchbaseObjectMapper(cryptoManager()));
|
||||
jacksonTranslationService.afterPropertiesSet();
|
||||
// for sdk3, we need to ask the mapper _it_ uses to ignore extra fields...
|
||||
JacksonTransformers.MAPPER.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||
return jacksonTranslationService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link CouchbaseMappingContext} equipped with entity classes scanned from the mapping base package.
|
||||
*/
|
||||
@Bean(BeanNames.COUCHBASE_MAPPING_CONTEXT)
|
||||
public CouchbaseMappingContext couchbaseMappingContext(CustomConversions customConversions) throws Exception {
|
||||
CouchbaseMappingContext mappingContext = new CouchbaseMappingContext();
|
||||
mappingContext.setInitialEntitySet(getInitialEntitySet());
|
||||
mappingContext.setSimpleTypeHolder(customConversions.getSimpleTypeHolder());
|
||||
mappingContext.setFieldNamingStrategy(fieldNamingStrategy());
|
||||
mappingContext.setAutoIndexCreation(autoIndexCreation());
|
||||
|
||||
return mappingContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link ObjectMapper} for the jsonSerializer of the ClusterEnvironment
|
||||
*
|
||||
* @return ObjectMapper
|
||||
*/
|
||||
private ObjectMapper couchbaseObjectMapper() {
|
||||
return couchbaseObjectMapper(cryptoManager());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link ObjectMapper} for the jsonSerializer of the ClusterEnvironment
|
||||
*
|
||||
* @param cryptoManager
|
||||
* @return ObjectMapper
|
||||
*/
|
||||
|
||||
ObjectMapper mapper;
|
||||
|
||||
public ObjectMapper couchbaseObjectMapper(CryptoManager cryptoManager) {
|
||||
if (mapper != null) {
|
||||
return mapper;
|
||||
}
|
||||
mapper = new ObjectMapper(); // or use the one from the Java SDK (?) JacksonTransformers.MAPPER
|
||||
mapper.configure(com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||
mapper.registerModule(new JsonValueModule());
|
||||
if (cryptoManager != null) {
|
||||
mapper.registerModule(new EncryptionModule(cryptoManager));
|
||||
}
|
||||
return mapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* The default blocking transaction manager. It is an implementation of CallbackPreferringTransactionManager
|
||||
* CallbackPreferringTransactionManagers do not play well with test-cases that rely
|
||||
* on @TestTransaction/@BeforeTransaction/@AfterTransaction
|
||||
*
|
||||
* @param clientFactory
|
||||
* @return
|
||||
*/
|
||||
@Bean(BeanNames.COUCHBASE_TRANSACTION_MANAGER)
|
||||
CouchbaseCallbackTransactionManager couchbaseTransactionManager(CouchbaseClientFactory clientFactory) {
|
||||
return new CouchbaseCallbackTransactionManager(clientFactory);
|
||||
}
|
||||
|
||||
/**
|
||||
* The default transaction template manager.
|
||||
*
|
||||
* @param couchbaseTransactionManager
|
||||
* @return
|
||||
*/
|
||||
@Bean(BeanNames.COUCHBASE_TRANSACTION_TEMPLATE)
|
||||
TransactionTemplate couchbaseTransactionTemplate(CouchbaseCallbackTransactionManager couchbaseTransactionManager) {
|
||||
return new TransactionTemplate(couchbaseTransactionManager);
|
||||
}
|
||||
|
||||
/**
|
||||
* The default TransactionalOperator.
|
||||
*
|
||||
* @param couchbaseCallbackTransactionManager
|
||||
* @return
|
||||
*/
|
||||
@Bean(BeanNames.COUCHBASE_TRANSACTIONAL_OPERATOR)
|
||||
public CouchbaseTransactionalOperator couchbaseTransactionalOperator(
|
||||
CouchbaseCallbackTransactionManager couchbaseCallbackTransactionManager) {
|
||||
return CouchbaseTransactionalOperator.create(couchbaseCallbackTransactionManager);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
public TransactionInterceptor transactionInterceptor(TransactionManager couchbaseTransactionManager) {
|
||||
TransactionAttributeSource transactionAttributeSource = new AnnotationTransactionAttributeSource();
|
||||
TransactionInterceptor interceptor = new CouchbaseTransactionInterceptor(couchbaseTransactionManager,
|
||||
transactionAttributeSource);
|
||||
interceptor.setTransactionAttributeSource(transactionAttributeSource);
|
||||
if (couchbaseTransactionManager != null) {
|
||||
interceptor.setTransactionManager(couchbaseTransactionManager);
|
||||
}
|
||||
return interceptor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure whether to automatically create indices for domain types by deriving the from the entity or not.
|
||||
*/
|
||||
protected boolean autoIndexCreation() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register custom Converters in a {@link CustomConversions} object if required. These {@link CustomConversions} will
|
||||
* be registered with the {@link #mappingCouchbaseConverter(CouchbaseMappingContext, CouchbaseCustomConversions)} )}
|
||||
* and {@link #couchbaseMappingContext(CustomConversions)}.
|
||||
*
|
||||
* @return must not be {@literal null}.
|
||||
*/
|
||||
@Bean(name = BeanNames.COUCHBASE_CUSTOM_CONVERSIONS)
|
||||
public CustomConversions customConversions() {
|
||||
return customConversions(cryptoManager());
|
||||
}
|
||||
|
||||
/**
|
||||
* Register custom Converters in a {@link CustomConversions} object if required. These {@link CustomConversions} will
|
||||
* be registered with the {@link #mappingCouchbaseConverter(CouchbaseMappingContext, CouchbaseCustomConversions)} )}
|
||||
* and {@link #couchbaseMappingContext(CustomConversions)}.
|
||||
*
|
||||
* @param cryptoManager
|
||||
* @return must not be {@literal null}.
|
||||
*/
|
||||
public CustomConversions customConversions(CryptoManager cryptoManager) {
|
||||
List<GenericConverter> newConverters = new ArrayList();
|
||||
CustomConversions customConversions = CouchbaseCustomConversions.create(configurationAdapter -> {
|
||||
SimplePropertyValueConversions valueConversions = new SimplePropertyValueConversions();
|
||||
valueConversions.setConverterFactory(new CouchbasePropertyValueConverterFactory(cryptoManager));
|
||||
valueConversions.setValueConverterRegistry(new PropertyValueConverterRegistrar().buildRegistry());
|
||||
configurationAdapter.setPropertyValueConversions(valueConversions);
|
||||
configurationAdapter.registerConverters(newConverters);
|
||||
});
|
||||
return customConversions;
|
||||
}
|
||||
|
||||
@Bean
|
||||
protected CryptoManager cryptoManager() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Bean
|
||||
protected CryptoConverter cryptoConverter(CryptoManager cryptoManager) {
|
||||
return cryptoManager == null ? null : new CryptoConverter(cryptoManager);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the base package to scan for mapped {@link Document}s. Will return the package name of the configuration
|
||||
* class (the concrete class, not this one here) by default.
|
||||
* <p>
|
||||
* So if you have a {@code com.acme.AppConfig} extending {@link AbstractCouchbaseConfiguration} the base package will
|
||||
* be considered {@code com.acme} unless the method is overridden to implement alternate behavior.
|
||||
*
|
||||
* @return the base package to scan for mapped {@link Document} classes or {@literal null} to not enable scanning for
|
||||
* entities.
|
||||
*/
|
||||
protected String getMappingBasePackage() {
|
||||
return getClass().getPackage().getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true if field names should be abbreviated with the {@link CamelCaseAbbreviatingFieldNamingStrategy}.
|
||||
*
|
||||
* @return true if field names should be abbreviated, default is false.
|
||||
*/
|
||||
protected boolean abbreviateFieldNames() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures a {@link FieldNamingStrategy} on the {@link CouchbaseMappingContext} instance created.
|
||||
*
|
||||
* @return the naming strategy.
|
||||
*/
|
||||
protected FieldNamingStrategy fieldNamingStrategy() {
|
||||
return abbreviateFieldNames() ? new CamelCaseAbbreviatingFieldNamingStrategy()
|
||||
: PropertyNameFieldNamingStrategy.INSTANCE;
|
||||
}
|
||||
|
||||
private boolean nonShadowedJacksonPresent() {
|
||||
try {
|
||||
JacksonJsonSerializer.preflightCheck();
|
||||
return true;
|
||||
} catch (Throwable t) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public QueryScanConsistency getDefaultConsistency() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright 2012-2022 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.config;
|
||||
|
||||
/**
|
||||
* Contains default bean names for Couchbase beans. These are the names of the beans used by Spring Data Couchbase,
|
||||
* unless an explicit id is given to the bean either in the xml configuration or the
|
||||
* {@link AbstractCouchbaseConfiguration java configuration}.
|
||||
*
|
||||
* @author Michael Nitschinger
|
||||
* @author Simon Baslé
|
||||
* @author Michael Reiche
|
||||
* @author Jorge Rodríguez Martín
|
||||
*/
|
||||
public class BeanNames {
|
||||
|
||||
public static final String COUCHBASE_TEMPLATE = "couchbaseTemplate";
|
||||
|
||||
public static final String REACTIVE_COUCHBASE_TEMPLATE = "reactiveCouchbaseTemplate";
|
||||
|
||||
public static final String COUCHBASE_CUSTOM_CONVERSIONS = "couchbaseCustomConversions";
|
||||
|
||||
/**
|
||||
* The name for the bean that stores custom mapping between repositories and their backing couchbaseOperations.
|
||||
*/
|
||||
public static final String COUCHBASE_OPERATIONS_MAPPING = "couchbaseRepositoryOperationsMapping";
|
||||
|
||||
/**
|
||||
* The name for the bean that stores custom mapping between reactive repositories and their backing
|
||||
* reactiveCouchbaseOperations.
|
||||
*/
|
||||
public static final String REACTIVE_COUCHBASE_OPERATIONS_MAPPING = "reactiveCouchbaseRepositoryOperationsMapping";
|
||||
|
||||
/**
|
||||
* The name for the bean that stores mapping metadata for entities stored in couchbase.
|
||||
*/
|
||||
public static final String COUCHBASE_MAPPING_CONTEXT = "couchbaseMappingContext";
|
||||
|
||||
/**
|
||||
* The name for the bean that will handle audit trail marking of entities.
|
||||
*/
|
||||
public static final String COUCHBASE_AUDITING_HANDLER = "couchbaseAuditingHandler";
|
||||
|
||||
/**
|
||||
* The name for the bean that will handle reactive audit trail marking of entities.
|
||||
*/
|
||||
public static final String REACTIVE_COUCHBASE_AUDITING_HANDLER = "reactiveCouchbaseAuditingHandler";
|
||||
|
||||
public static final String COUCHBASE_CLIENT_FACTORY = "couchbaseClientFactory";
|
||||
|
||||
public static final String COUCHBASE_TRANSACTION_MANAGER = "couchbaseTransactionManager";
|
||||
|
||||
public static final String COUCHBASE_TRANSACTION_TEMPLATE = "couchbaseTransactionTemplate";
|
||||
|
||||
public static final String COUCHBASE_TRANSACTIONAL_OPERATOR = "couchbaseTransactionalOperator";
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* This package contains all classes needed for specific configuration of Spring Data Couchbase.
|
||||
*/
|
||||
package org.springframework.data.couchbase.config;
|
||||
@@ -0,0 +1,234 @@
|
||||
/*
|
||||
* Copyright 2022 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;
|
||||
|
||||
import java.lang.reflect.InaccessibleObjectException;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import com.couchbase.client.core.annotation.Stability;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.data.couchbase.core.convert.CouchbaseConverter;
|
||||
import org.springframework.data.couchbase.core.convert.join.N1qlJoinResolver;
|
||||
import org.springframework.data.couchbase.core.convert.translation.TranslationService;
|
||||
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.CouchbaseMappingEvent;
|
||||
import org.springframework.data.couchbase.core.support.TemplateUtils;
|
||||
import org.springframework.data.couchbase.repository.support.MappingCouchbaseEntityInformation;
|
||||
import org.springframework.data.couchbase.transaction.CouchbaseResourceHolder;
|
||||
import org.springframework.data.mapping.PersistentPropertyAccessor;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
import com.couchbase.client.core.error.CouchbaseException;
|
||||
|
||||
|
||||
/**
|
||||
* Base shared by Reactive and non-Reactive TemplateSupport
|
||||
*
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
@Stability.Internal
|
||||
public abstract class AbstractTemplateSupport {
|
||||
|
||||
final ReactiveCouchbaseTemplate template;
|
||||
final CouchbaseConverter converter;
|
||||
final MappingContext<? extends CouchbasePersistentEntity<?>, CouchbasePersistentProperty> mappingContext;
|
||||
final TranslationService translationService;
|
||||
ApplicationContext applicationContext;
|
||||
static final Logger LOG = LoggerFactory.getLogger(AbstractTemplateSupport.class);
|
||||
|
||||
public AbstractTemplateSupport(ReactiveCouchbaseTemplate template, CouchbaseConverter converter,
|
||||
TranslationService translationService) {
|
||||
this.template = template;
|
||||
this.converter = converter;
|
||||
this.mappingContext = converter.getMappingContext();
|
||||
this.translationService = translationService;
|
||||
}
|
||||
|
||||
abstract ReactiveCouchbaseTemplate getReactiveTemplate();
|
||||
|
||||
public <T> T decodeEntityBase(Object id, String source, Long cas, Class<T> entityClass, String scope,
|
||||
String collection, Object txResultHolder, CouchbaseResourceHolder holder) {
|
||||
|
||||
// this is the entity class defined for the repository. It may not be the class of the document that was read
|
||||
// we will reset it after reading the document
|
||||
//
|
||||
// This will fail for the case where:
|
||||
// 1) The version is defined in the concrete class, but not in the abstract class; and
|
||||
// 2) The constructor takes a "long version" argument resulting in an exception would be thrown if version in
|
||||
// the source is null.
|
||||
// We could expose from the MappingCouchbaseConverter determining the persistent entity from the source,
|
||||
// but that is a lot of work to do every time just for this very rare and avoidable case.
|
||||
// TypeInformation<? extends R> typeToUse = typeMapper.readType(source, type);
|
||||
|
||||
CouchbasePersistentEntity persistentEntity = couldBePersistentEntity(entityClass);
|
||||
|
||||
if (persistentEntity == null) { // method could return a Long, Boolean, String etc.
|
||||
// QueryExecutionConverters.unwrapWrapperTypes will recursively unwrap until there is nothing left
|
||||
// to unwrap. This results in List<String[]> being unwrapped past String[] to String, so this may also be a
|
||||
// Collection (or Array) of entityClass. We have no way of knowing - so just assume it is what we are told.
|
||||
// if this is a Collection or array, only the first element will be returned.
|
||||
final CouchbaseDocument converted = new CouchbaseDocument(id);
|
||||
Set<Map.Entry<String, Object>> set = ((CouchbaseDocument) translationService.decode(source, converted))
|
||||
.getContent().entrySet();
|
||||
return (T) set.iterator().next().getValue();
|
||||
}
|
||||
|
||||
if (id == null) {
|
||||
throw new CouchbaseException(TemplateUtils.SELECT_ID + " was null. Either use #{#n1ql.selectEntity} or project "
|
||||
+ TemplateUtils.SELECT_ID);
|
||||
}
|
||||
|
||||
final CouchbaseDocument converted = new CouchbaseDocument(id);
|
||||
|
||||
// if possible, set the version property in the source so that if the constructor has a long version argument,
|
||||
// it will have a value and not fail (as null is not a valid argument for a long argument). This possible failure
|
||||
// can be avoid by defining the argument as Long instead of long.
|
||||
// persistentEntity is still the (possibly abstract) class specified in the repository definition
|
||||
// it's possible that the abstract class does not have a version property, and this won't be able to set the version
|
||||
if (persistentEntity.getVersionProperty() != null) {
|
||||
if (cas == null) {
|
||||
throw new CouchbaseException("version/cas in the entity but " + TemplateUtils.SELECT_CAS
|
||||
+ " was not in result. Either use #{#n1ql.selectEntity} or project " + TemplateUtils.SELECT_CAS);
|
||||
}
|
||||
if (cas != 0) {
|
||||
converted.put(persistentEntity.getVersionProperty().getName(), cas);
|
||||
}
|
||||
}
|
||||
|
||||
// if the constructor has an argument that is long version, then construction will fail if the 'version'
|
||||
// is not available as 'null' is not a legal value for a long. Changing the arg to "Long version" would solve this.
|
||||
// (Version doesn't come from 'source', it comes from the cas argument to decodeEntity)
|
||||
T readEntity = converter.read(entityClass, (CouchbaseDocument) translationService.decode(source, converted));
|
||||
final ConvertingPropertyAccessor<T> accessor = getPropertyAccessor(readEntity);
|
||||
|
||||
persistentEntity = couldBePersistentEntity(readEntity.getClass());
|
||||
|
||||
if (cas != null && cas != 0 && persistentEntity.getVersionProperty() != null) {
|
||||
accessor.setProperty(persistentEntity.getVersionProperty(), cas);
|
||||
}
|
||||
N1qlJoinResolver.handleProperties(persistentEntity, accessor, getReactiveTemplate(), id.toString(), scope, collection);
|
||||
|
||||
if (holder != null) {
|
||||
holder.transactionResultHolder(txResultHolder, (T) accessor.getBean());
|
||||
}
|
||||
|
||||
return accessor.getBean();
|
||||
}
|
||||
|
||||
CouchbasePersistentEntity couldBePersistentEntity(Class<?> entityClass) {
|
||||
if (ClassUtils.isPrimitiveOrWrapper(entityClass) || entityClass == String.class) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return mappingContext.getPersistentEntity(entityClass);
|
||||
} catch (InaccessibleObjectException t) {
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public <T> T applyResultBase(T entity, CouchbaseDocument converted, Object id, long cas,
|
||||
Object txResultHolder, CouchbaseResourceHolder holder) {
|
||||
ConvertingPropertyAccessor<Object> accessor = getPropertyAccessor(entity);
|
||||
|
||||
final CouchbasePersistentEntity<?> persistentEntity = converter.getMappingContext()
|
||||
.getRequiredPersistentEntity(entity.getClass());
|
||||
|
||||
final CouchbasePersistentProperty idProperty = persistentEntity.getIdProperty();
|
||||
if (idProperty != null) {
|
||||
accessor.setProperty(idProperty, id);
|
||||
}
|
||||
|
||||
final CouchbasePersistentProperty versionProperty = persistentEntity.getVersionProperty();
|
||||
if (versionProperty != null) {
|
||||
accessor.setProperty(versionProperty, cas);
|
||||
}
|
||||
|
||||
if (holder != null) {
|
||||
holder.transactionResultHolder(txResultHolder, (T) accessor.getBean());
|
||||
}
|
||||
maybeEmitEvent(new AfterSaveEvent(accessor.getBean(), converted));
|
||||
return (T) accessor.getBean();
|
||||
|
||||
}
|
||||
|
||||
public Long getCas(final Object entity) {
|
||||
final ConvertingPropertyAccessor<Object> accessor = getPropertyAccessor(entity);
|
||||
final CouchbasePersistentEntity<?> persistentEntity = mappingContext.getRequiredPersistentEntity(entity.getClass());
|
||||
final CouchbasePersistentProperty versionProperty = persistentEntity.getVersionProperty();
|
||||
long cas = 0;
|
||||
if (versionProperty != null) {
|
||||
Object casObject = accessor.getProperty(versionProperty);
|
||||
if (casObject instanceof Number) {
|
||||
cas = ((Number) casObject).longValue();
|
||||
}
|
||||
}
|
||||
return cas;
|
||||
}
|
||||
|
||||
public Object getId(final Object entity) {
|
||||
final ConvertingPropertyAccessor<Object> accessor = getPropertyAccessor(entity);
|
||||
final CouchbasePersistentEntity<?> persistentEntity = mappingContext.getRequiredPersistentEntity(entity.getClass());
|
||||
final CouchbasePersistentProperty idProperty = persistentEntity.getIdProperty();
|
||||
Object id = null;
|
||||
if (idProperty != null) {
|
||||
id = accessor.getProperty(idProperty);
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getJavaNameForEntity(final Class<?> clazz) {
|
||||
final CouchbasePersistentEntity<?> persistentEntity = mappingContext.getRequiredPersistentEntity(clazz);
|
||||
MappingCouchbaseEntityInformation<?, Object> info = new MappingCouchbaseEntityInformation<>(persistentEntity);
|
||||
return info.getJavaType().getName();
|
||||
}
|
||||
|
||||
<T> ConvertingPropertyAccessor<T> getPropertyAccessor(final T source) {
|
||||
CouchbasePersistentEntity<?> entity = mappingContext.getRequiredPersistentEntity(source.getClass());
|
||||
PersistentPropertyAccessor<T> accessor = entity.getPropertyAccessor(source);
|
||||
return new ConvertingPropertyAccessor<>(accessor, converter.getConversionService());
|
||||
}
|
||||
|
||||
public void maybeEmitEvent(CouchbaseMappingEvent<?> event) {
|
||||
if (canPublishEvent()) {
|
||||
try {
|
||||
this.applicationContext.publishEvent(event);
|
||||
} catch (Exception e) {
|
||||
LOG.warn("{} thrown during {}", e, event);
|
||||
throw e;
|
||||
}
|
||||
} else {
|
||||
LOG.info("maybeEmitEvent called, but CouchbaseTemplate not initialized with applicationContext");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private boolean canPublishEvent() {
|
||||
return this.applicationContext != null;
|
||||
}
|
||||
|
||||
public TranslationService getTranslationService() {
|
||||
return translationService;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
/**
|
||||
* Defines the callback which will be wrapped and executed on a bucket.
|
||||
*
|
||||
* @author Michael Nitschinger
|
||||
*/
|
||||
public interface CollectionCallback<T> {
|
||||
|
||||
/**
|
||||
* The enclosed body will be executed on the connected bucket.
|
||||
*
|
||||
* @return the result of the enclosed execution.
|
||||
* @throws TimeoutException if the enclosed operation timed out.
|
||||
* @throws ExecutionException if the result could not be retrieved because of a thrown exception before.
|
||||
* @throws InterruptedException if the enclosed operation was interrupted.
|
||||
*/
|
||||
T doInCollection() throws TimeoutException, ExecutionException, InterruptedException;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import org.springframework.dao.DataIntegrityViolationException;
|
||||
|
||||
/**
|
||||
* A Couchbase specific integrity violation exception, thrown as a result of failing db operations.
|
||||
*
|
||||
* @author Michael Nitschinger
|
||||
*/
|
||||
public class CouchbaseDataIntegrityViolationException extends DataIntegrityViolationException {
|
||||
|
||||
private static final long serialVersionUID = -3724991479213025850L;
|
||||
|
||||
public CouchbaseDataIntegrityViolationException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
|
||||
public CouchbaseDataIntegrityViolationException(String msg, Throwable cause) {
|
||||
super(msg, cause);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* Copyright 2012-2022 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;
|
||||
|
||||
import java.util.ConcurrentModificationException;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.dao.DataAccessResourceFailureException;
|
||||
import org.springframework.dao.DataIntegrityViolationException;
|
||||
import org.springframework.dao.DataRetrievalFailureException;
|
||||
import org.springframework.dao.DuplicateKeyException;
|
||||
import org.springframework.dao.InvalidDataAccessResourceUsageException;
|
||||
import org.springframework.dao.OptimisticLockingFailureException;
|
||||
import org.springframework.dao.QueryTimeoutException;
|
||||
import org.springframework.dao.TransientDataAccessResourceException;
|
||||
import org.springframework.dao.support.PersistenceExceptionTranslator;
|
||||
import org.springframework.data.couchbase.transaction.error.UncategorizedTransactionDataAccessException;
|
||||
|
||||
import com.couchbase.client.core.error.BucketNotFoundException;
|
||||
import com.couchbase.client.core.error.CasMismatchException;
|
||||
import com.couchbase.client.core.error.CollectionNotFoundException;
|
||||
import com.couchbase.client.core.error.ConfigException;
|
||||
import com.couchbase.client.core.error.DecodingFailureException;
|
||||
import com.couchbase.client.core.error.DesignDocumentNotFoundException;
|
||||
import com.couchbase.client.core.error.DocumentExistsException;
|
||||
import com.couchbase.client.core.error.DocumentLockedException;
|
||||
import com.couchbase.client.core.error.DocumentNotFoundException;
|
||||
import com.couchbase.client.core.error.DurabilityAmbiguousException;
|
||||
import com.couchbase.client.core.error.DurabilityImpossibleException;
|
||||
import com.couchbase.client.core.error.DurabilityLevelNotAvailableException;
|
||||
import com.couchbase.client.core.error.EncodingFailureException;
|
||||
import com.couchbase.client.core.error.ReplicaNotConfiguredException;
|
||||
import com.couchbase.client.core.error.RequestCanceledException;
|
||||
import com.couchbase.client.core.error.ScopeNotFoundException;
|
||||
import com.couchbase.client.core.error.ServiceNotAvailableException;
|
||||
import com.couchbase.client.core.error.TemporaryFailureException;
|
||||
import com.couchbase.client.core.error.ValueTooLargeException;
|
||||
import com.couchbase.client.core.error.transaction.TransactionOperationFailedException;
|
||||
|
||||
/**
|
||||
* Simple {@link PersistenceExceptionTranslator} for Couchbase.
|
||||
* <p>
|
||||
* Convert the given runtime exception to an appropriate exception from the {@code org.springframework.dao} hierarchy.
|
||||
* Return {@literal null} if no translation is appropriate: any other exception may have resulted from user code, and
|
||||
* should not be translated.
|
||||
*
|
||||
* @author Michael Nitschinger
|
||||
* @author Simon Baslé
|
||||
* @author Michael Reiche
|
||||
* @author Graham Pople
|
||||
*/
|
||||
public class CouchbaseExceptionTranslator implements PersistenceExceptionTranslator {
|
||||
|
||||
/**
|
||||
* Translate Couchbase specific exceptions to spring exceptions if possible.
|
||||
*
|
||||
* @param ex the exception to translate.
|
||||
* @return the translated exception or null.
|
||||
*/
|
||||
@Override
|
||||
public final DataAccessException translateExceptionIfPossible(final RuntimeException ex) {
|
||||
|
||||
if (ex instanceof ConfigException || ex instanceof ServiceNotAvailableException
|
||||
|| ex instanceof CollectionNotFoundException || ex instanceof ScopeNotFoundException
|
||||
|| ex instanceof BucketNotFoundException) {
|
||||
return new DataAccessResourceFailureException(ex.getMessage(), ex);
|
||||
}
|
||||
|
||||
if (ex instanceof DocumentExistsException) {
|
||||
return new DuplicateKeyException(ex.getMessage(), ex);
|
||||
}
|
||||
|
||||
if (ex instanceof DocumentNotFoundException) {
|
||||
return new DataRetrievalFailureException(ex.getMessage(), ex);
|
||||
}
|
||||
|
||||
if (ex instanceof CasMismatchException || ex instanceof ConcurrentModificationException) {
|
||||
return new OptimisticLockingFailureException(ex.getMessage(), ex);
|
||||
}
|
||||
|
||||
if (ex instanceof ReplicaNotConfiguredException || ex instanceof DurabilityLevelNotAvailableException
|
||||
|| ex instanceof DurabilityImpossibleException || ex instanceof DurabilityAmbiguousException) {
|
||||
return new DataIntegrityViolationException(ex.getMessage(), ex);
|
||||
}
|
||||
|
||||
if (ex instanceof RequestCanceledException) {
|
||||
return new OperationCancellationException(ex.getMessage(), ex);
|
||||
}
|
||||
|
||||
if (ex instanceof DesignDocumentNotFoundException || ex instanceof ValueTooLargeException) {
|
||||
return new InvalidDataAccessResourceUsageException(ex.getMessage(), ex);
|
||||
}
|
||||
|
||||
if (ex instanceof TemporaryFailureException || ex instanceof DocumentLockedException) {
|
||||
return new TransientDataAccessResourceException(ex.getMessage(), ex);
|
||||
}
|
||||
|
||||
if ((ex instanceof RuntimeException && ex.getCause() instanceof TimeoutException)) {
|
||||
return new QueryTimeoutException(ex.getMessage(), ex);
|
||||
}
|
||||
|
||||
if (ex instanceof EncodingFailureException || ex instanceof DecodingFailureException) {
|
||||
// note: the more specific CouchbaseQueryExecutionException should be thrown by the template
|
||||
// when dealing with TranscodingException in the query/n1ql methods.
|
||||
return new DataRetrievalFailureException(ex.getMessage(), ex);
|
||||
}
|
||||
|
||||
if (ex instanceof TransactionOperationFailedException) {
|
||||
// Replace the TransactionOperationFailedException, since we want the Spring operation to fail with a
|
||||
// Spring error. Internal state has already been set in the AttemptContext so the retry, rollback etc.
|
||||
// will get respected regardless of what gets propagated (or not) from the lambda.
|
||||
return new UncategorizedTransactionDataAccessException((TransactionOperationFailedException) ex);
|
||||
}
|
||||
|
||||
// Unable to translate exception, therefore just throw the original!
|
||||
throw ex;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright 2012-2022 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;
|
||||
|
||||
import org.springframework.data.couchbase.CouchbaseClientFactory;
|
||||
import org.springframework.data.couchbase.core.convert.CouchbaseConverter;
|
||||
import org.springframework.data.couchbase.core.query.Query;
|
||||
|
||||
import com.couchbase.client.java.query.QueryScanConsistency;
|
||||
|
||||
/**
|
||||
* Defines common operations on the Couchbase data source, most commonly implemented by {@link CouchbaseTemplate}.
|
||||
*
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
public interface CouchbaseOperations extends FluentCouchbaseOperations {
|
||||
|
||||
/**
|
||||
* Returns the converter used for this template/operations.
|
||||
*/
|
||||
CouchbaseConverter getConverter();
|
||||
|
||||
/**
|
||||
* The name of the bucket used.
|
||||
*/
|
||||
String getBucketName();
|
||||
|
||||
/**
|
||||
* The name of the scope used, null if the default scope is used.
|
||||
*/
|
||||
String getScopeName();
|
||||
|
||||
/**
|
||||
* Returns the underlying client factory.
|
||||
*/
|
||||
CouchbaseClientFactory getCouchbaseClientFactory();
|
||||
|
||||
/**
|
||||
* Returns the default consistency to use for queries
|
||||
*/
|
||||
QueryScanConsistency getConsistency();
|
||||
|
||||
/**
|
||||
* Save the entity to couchbase.<br>
|
||||
* If there is no version property on the entity class, and this is in a transaction, use insert. <br>
|
||||
* If there is no version property on the entity class, and this is not in a transaction, use upsert. <br>
|
||||
* If there is a version property on the entity class, and it is non-zero, then this is an existing document, use
|
||||
* replace.<br>
|
||||
* Otherwise, there is a version property for the entity, but it is zero or null, use insert. <br>
|
||||
*
|
||||
* @param entity the entity to save in couchbase
|
||||
* @param scopeAndCollection for use by repositories only. these are varargs for the scope and collection.
|
||||
* @param <T> the entity class
|
||||
* @return
|
||||
*/
|
||||
<T> T save(T entity, String... scopeAndCollection);
|
||||
|
||||
/**
|
||||
* Returns the count of documents found by the query.
|
||||
* @param query
|
||||
* @param domainType
|
||||
* @param <T>
|
||||
* @return
|
||||
*/
|
||||
<T> Long count(Query query, Class<T> domainType);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import org.springframework.dao.DataRetrievalFailureException;
|
||||
|
||||
/**
|
||||
* An {@link DataRetrievalFailureException} that denotes an error during a query (N1QL).
|
||||
*/
|
||||
public class CouchbaseQueryExecutionException extends DataRetrievalFailureException {
|
||||
|
||||
public CouchbaseQueryExecutionException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
|
||||
public CouchbaseQueryExecutionException(String msg, Throwable cause) {
|
||||
super(msg, cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,224 @@
|
||||
/*
|
||||
* Copyright 2012-2022 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;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.data.couchbase.CouchbaseClientFactory;
|
||||
import org.springframework.data.couchbase.core.convert.CouchbaseConverter;
|
||||
import org.springframework.data.couchbase.core.convert.translation.JacksonTranslationService;
|
||||
import org.springframework.data.couchbase.core.convert.translation.TranslationService;
|
||||
import org.springframework.data.couchbase.core.index.CouchbasePersistentEntityIndexCreator;
|
||||
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.query.Query;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
import com.couchbase.client.java.Collection;
|
||||
import com.couchbase.client.java.query.QueryScanConsistency;
|
||||
|
||||
/**
|
||||
* Implements lower-level couchbase operations on top of the SDK with entity mapping capabilities.
|
||||
*
|
||||
* @author Michael Nitschinger
|
||||
* @author Michael Reiche
|
||||
* @author Jorge Rodriguez Martin
|
||||
* @since 3.0
|
||||
*/
|
||||
public class CouchbaseTemplate implements CouchbaseOperations, ApplicationContextAware {
|
||||
|
||||
private final CouchbaseClientFactory clientFactory;
|
||||
private final CouchbaseConverter converter;
|
||||
private final CouchbaseTemplateSupport templateSupport;
|
||||
private final MappingContext<? extends CouchbasePersistentEntity<?>, CouchbasePersistentProperty> mappingContext;
|
||||
private final ReactiveCouchbaseTemplate reactiveCouchbaseTemplate;
|
||||
private final QueryScanConsistency scanConsistency;
|
||||
private @Nullable CouchbasePersistentEntityIndexCreator indexCreator;
|
||||
|
||||
public CouchbaseTemplate(final CouchbaseClientFactory clientFactory, final CouchbaseConverter converter) {
|
||||
this(clientFactory, converter, new JacksonTranslationService());
|
||||
}
|
||||
|
||||
public CouchbaseTemplate(final CouchbaseClientFactory clientFactory, final CouchbaseConverter converter,
|
||||
final TranslationService translationService) {
|
||||
this(clientFactory, converter, translationService, null);
|
||||
}
|
||||
|
||||
public CouchbaseTemplate(final CouchbaseClientFactory clientFactory, final CouchbaseConverter converter,
|
||||
final TranslationService translationService, QueryScanConsistency scanConsistency) {
|
||||
this.clientFactory = clientFactory;
|
||||
this.converter = converter;
|
||||
this.templateSupport = new CouchbaseTemplateSupport(this, converter, translationService);
|
||||
this.reactiveCouchbaseTemplate = new ReactiveCouchbaseTemplate(clientFactory, converter, translationService,
|
||||
scanConsistency);
|
||||
this.scanConsistency = scanConsistency;
|
||||
|
||||
this.mappingContext = this.converter.getMappingContext();
|
||||
if (mappingContext instanceof CouchbaseMappingContext) {
|
||||
CouchbaseMappingContext cmc = (CouchbaseMappingContext) mappingContext;
|
||||
if (cmc.isAutoIndexCreation()) {
|
||||
indexCreator = new CouchbasePersistentEntityIndexCreator(cmc, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T save(T entity, String... scopeAndCollection) {
|
||||
return reactive().save(entity, scopeAndCollection).block();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Long count(Query query, Class<T> domainType) {
|
||||
return findByQuery(domainType).matching(query).count();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> ExecutableUpsertById<T> upsertById(final Class<T> domainType) {
|
||||
return new ExecutableUpsertByIdOperationSupport(this).upsertById(domainType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> ExecutableInsertById<T> insertById(Class<T> domainType) {
|
||||
return new ExecutableInsertByIdOperationSupport(this).insertById(domainType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> ExecutableReplaceById<T> replaceById(Class<T> domainType) {
|
||||
return new ExecutableReplaceByIdOperationSupport(this).replaceById(domainType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> ExecutableFindById<T> findById(Class<T> domainType) {
|
||||
return new ExecutableFindByIdOperationSupport(this).findById(domainType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> ExecutableFindFromReplicasById<T> findFromReplicasById(Class<T> domainType) {
|
||||
return new ExecutableFindFromReplicasByIdOperationSupport(this).findFromReplicasById(domainType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> ExecutableFindByQuery<T> findByQuery(Class<T> domainType) {
|
||||
return new ExecutableFindByQueryOperationSupport(this).findByQuery(domainType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> ExecutableFindByAnalytics<T> findByAnalytics(Class<T> domainType) {
|
||||
return new ExecutableFindByAnalyticsOperationSupport(this).findByAnalytics(domainType);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public ExecutableRemoveById removeById() {
|
||||
return removeById(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExecutableRemoveById removeById(Class<?> domainType) {
|
||||
return new ExecutableRemoveByIdOperationSupport(this).removeById(domainType);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public ExecutableExistsById existsById() {
|
||||
return existsById(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExecutableExistsById existsById(Class<?> domainType) {
|
||||
return new ExecutableExistsByIdOperationSupport(this).existsById(domainType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> ExecutableRemoveByQuery<T> removeByQuery(Class<T> domainType) {
|
||||
return new ExecutableRemoveByQueryOperationSupport(this).removeByQuery(domainType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBucketName() {
|
||||
return clientFactory.getBucket().name();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getScopeName() {
|
||||
return clientFactory.getScope().name();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CouchbaseClientFactory getCouchbaseClientFactory() {
|
||||
return clientFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QueryScanConsistency getConsistency() {
|
||||
return scanConsistency;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides access to a {@link Collection} on the configured {@link CouchbaseClientFactory}.
|
||||
*
|
||||
* @param collectionName the name of the collection, if null is passed in the default collection is assumed.
|
||||
* @return the collection instance.
|
||||
*/
|
||||
public Collection getCollection(final String collectionName) {
|
||||
return clientFactory.getCollection(collectionName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CouchbaseConverter getConverter() {
|
||||
return converter;
|
||||
}
|
||||
|
||||
public ReactiveCouchbaseTemplate reactive() {
|
||||
return reactiveCouchbaseTemplate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(final ApplicationContext applicationContext) throws BeansException {
|
||||
prepareIndexCreator(applicationContext);
|
||||
templateSupport.setApplicationContext(applicationContext);
|
||||
reactiveCouchbaseTemplate.setApplicationContext(applicationContext);
|
||||
}
|
||||
|
||||
private void prepareIndexCreator(final ApplicationContext context) {
|
||||
String[] indexCreators = context.getBeanNamesForType(CouchbasePersistentEntityIndexCreator.class);
|
||||
|
||||
for (String creator : indexCreators) {
|
||||
CouchbasePersistentEntityIndexCreator creatorBean = context.getBean(creator,
|
||||
CouchbasePersistentEntityIndexCreator.class);
|
||||
if (creatorBean.isIndexCreatorFor(mappingContext)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (context instanceof ConfigurableApplicationContext && indexCreator != null) {
|
||||
((ConfigurableApplicationContext) context).addApplicationListener(indexCreator);
|
||||
if (mappingContext instanceof CouchbaseMappingContext) {
|
||||
CouchbaseMappingContext cmc = (CouchbaseMappingContext) mappingContext;
|
||||
cmc.setIndexCreator(indexCreator);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public TemplateSupport support() {
|
||||
return templateSupport;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* Copyright 2012-2022 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;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.data.couchbase.core.convert.CouchbaseConverter;
|
||||
import org.springframework.data.couchbase.core.convert.translation.TranslationService;
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
|
||||
import org.springframework.data.couchbase.core.mapping.event.AfterConvertCallback;
|
||||
import org.springframework.data.couchbase.core.mapping.event.BeforeConvertCallback;
|
||||
import org.springframework.data.couchbase.core.mapping.event.BeforeConvertEvent;
|
||||
import org.springframework.data.couchbase.core.mapping.event.BeforeSaveEvent;
|
||||
import org.springframework.data.couchbase.transaction.CouchbaseResourceHolder;
|
||||
import org.springframework.data.mapping.callback.EntityCallbacks;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Internal encode/decode support for CouchbaseTemplate.
|
||||
*
|
||||
* @author Michael Nitschinger
|
||||
* @author Michael Reiche
|
||||
* @author Jorge Rodriguez Martin
|
||||
* @author Carlos Espinaco
|
||||
* @since 3.0
|
||||
*/
|
||||
class CouchbaseTemplateSupport extends AbstractTemplateSupport implements ApplicationContextAware, TemplateSupport {
|
||||
|
||||
private final CouchbaseTemplate template;
|
||||
private EntityCallbacks entityCallbacks;
|
||||
|
||||
public CouchbaseTemplateSupport(final CouchbaseTemplate template, final CouchbaseConverter converter,
|
||||
final TranslationService translationService) {
|
||||
super(template.reactive(), converter, translationService);
|
||||
this.template = template;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CouchbaseDocument encodeEntity(final Object entityToEncode) {
|
||||
maybeEmitEvent(new BeforeConvertEvent<>(entityToEncode));
|
||||
Object maybeNewEntity = maybeCallBeforeConvert(entityToEncode, "");
|
||||
final CouchbaseDocument converted = new CouchbaseDocument();
|
||||
converter.write(maybeNewEntity, converted);
|
||||
maybeCallAfterConvert(entityToEncode, converted, "");
|
||||
maybeEmitEvent(new BeforeSaveEvent<>(entityToEncode, converted));
|
||||
return converted;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T decodeEntity(Object id, String source, Long cas, Class<T> entityClass, String scope, String collection,
|
||||
Object txHolder, CouchbaseResourceHolder holder) {
|
||||
return decodeEntityBase(id, source, cas, entityClass, scope, collection, txHolder, holder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T applyResult(T entity, CouchbaseDocument converted, Object id, long cas,
|
||||
Object txResultHolder, CouchbaseResourceHolder holder) {
|
||||
return applyResultBase(entity, converted, id, cas, txResultHolder, holder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Integer getTxResultHolder(T source) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
this.applicationContext = applicationContext;
|
||||
if (entityCallbacks == null) {
|
||||
setEntityCallbacks(EntityCallbacks.create(applicationContext));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link EntityCallbacks} instance to use when invoking
|
||||
* {@link org.springframework.data.mapping.callback.EntityCallback callbacks} like the {@link BeforeConvertCallback}.
|
||||
* <p>
|
||||
* Overrides potentially existing {@link EntityCallbacks}.
|
||||
*
|
||||
* @param entityCallbacks must not be {@literal null}.
|
||||
* @throws IllegalArgumentException if the given instance is {@literal null}.
|
||||
* @since 2.2
|
||||
*/
|
||||
public void setEntityCallbacks(EntityCallbacks entityCallbacks) {
|
||||
Assert.notNull(entityCallbacks, "EntityCallbacks must not be null!");
|
||||
this.entityCallbacks = entityCallbacks;
|
||||
}
|
||||
|
||||
protected <T> T maybeCallBeforeConvert(T object, String collection) {
|
||||
if (entityCallbacks != null) {
|
||||
return entityCallbacks.callback(BeforeConvertCallback.class, object, collection);
|
||||
} else {
|
||||
LOG.info("maybeCallBeforeConvert called, but CouchbaseTemplate not initialized with applicationContext");
|
||||
}
|
||||
return object;
|
||||
}
|
||||
|
||||
protected <T> T maybeCallAfterConvert(T object, CouchbaseDocument document, String collection) {
|
||||
if (null != entityCallbacks) {
|
||||
return entityCallbacks.callback(AfterConvertCallback.class, object, document, collection);
|
||||
} else {
|
||||
LOG.info("maybeCallAfterConvert called, but CouchbaseTemplate not initialized with applicationContext");
|
||||
}
|
||||
return object;
|
||||
}
|
||||
|
||||
@Override
|
||||
ReactiveCouchbaseTemplate getReactiveTemplate() {
|
||||
return template.reactive();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.data.couchbase.core.support.InCollection;
|
||||
import org.springframework.data.couchbase.core.support.InScope;
|
||||
import org.springframework.data.couchbase.core.support.OneAndAllExists;
|
||||
import org.springframework.data.couchbase.core.support.WithExistsOptions;
|
||||
|
||||
import com.couchbase.client.java.kv.ExistsOptions;
|
||||
|
||||
/**
|
||||
* Insert Operations
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.0
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
interface TerminatingExistsById extends OneAndAllExists {
|
||||
|
||||
/**
|
||||
* Performs the operation on the ID given.
|
||||
*
|
||||
* @param id the ID to perform the operation on.
|
||||
* @return true if the document exists, false otherwise.
|
||||
*/
|
||||
@Override
|
||||
boolean one(String id);
|
||||
|
||||
/**
|
||||
* Performs the operation on the collection of ids.
|
||||
*
|
||||
* @param ids the ids to check.
|
||||
* @return a map consisting of the document IDs as the keys and if they exist as the value.
|
||||
*/
|
||||
@Override
|
||||
Map<String, Boolean> all(Collection<String> ids);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fluent method to specify options.
|
||||
*
|
||||
* @param <T> the entity type to use for the results.
|
||||
*/
|
||||
interface ExistsByIdWithOptions<T> extends TerminatingExistsById, WithExistsOptions<T> {
|
||||
/**
|
||||
* Fluent method to specify options to use for execution
|
||||
*
|
||||
* @param options options to use for execution
|
||||
*/
|
||||
@Override
|
||||
TerminatingExistsById withOptions(ExistsOptions options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fluent method to specify the collection.
|
||||
*
|
||||
* @param <T> the entity type to use for the results.
|
||||
*/
|
||||
interface ExistsByIdInCollection<T> extends ExistsByIdWithOptions<T>, InCollection<T> {
|
||||
/**
|
||||
* With a different collection
|
||||
*
|
||||
* @param collection the collection to use.
|
||||
*/
|
||||
@Override
|
||||
ExistsByIdWithOptions<T> inCollection(String collection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fluent method to specify the scope.
|
||||
*
|
||||
* @param <T> the entity type to use for the results.
|
||||
*/
|
||||
interface ExistsByIdInScope<T> extends ExistsByIdInCollection<T>, InScope<T> {
|
||||
/**
|
||||
* With a different scope
|
||||
*
|
||||
* @param scope the scope to use.
|
||||
*/
|
||||
@Override
|
||||
ExistsByIdInCollection<T> inScope(String scope);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides methods for constructing KV exists operations in a fluent way.
|
||||
*/
|
||||
interface ExecutableExistsById extends ExistsByIdInScope {}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright 2012-2022 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;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.data.couchbase.core.ReactiveExistsByIdOperationSupport.ReactiveExistsByIdSupport;
|
||||
import org.springframework.data.couchbase.core.query.OptionsBuilder;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.couchbase.client.java.kv.ExistsOptions;
|
||||
|
||||
public class ExecutableExistsByIdOperationSupport implements ExecutableExistsByIdOperation {
|
||||
|
||||
private final CouchbaseTemplate template;
|
||||
|
||||
ExecutableExistsByIdOperationSupport(CouchbaseTemplate template) {
|
||||
this.template = template;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public ExecutableExistsById existsById() {
|
||||
return existsById(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExecutableExistsById existsById(Class<?> domainType) {
|
||||
return new ExecutableExistsByIdSupport(template, domainType, OptionsBuilder.getScopeFrom(domainType),
|
||||
OptionsBuilder.getCollectionFrom(domainType), null);
|
||||
}
|
||||
|
||||
static class ExecutableExistsByIdSupport implements ExecutableExistsById {
|
||||
|
||||
private final CouchbaseTemplate template;
|
||||
private final Class<?> domainType;
|
||||
private final String scope;
|
||||
private final String collection;
|
||||
private final ExistsOptions options;
|
||||
|
||||
private final ReactiveExistsByIdSupport reactiveSupport;
|
||||
|
||||
ExecutableExistsByIdSupport(final CouchbaseTemplate template, final Class<?> domainType, final String scope,
|
||||
final String collection, final ExistsOptions options) {
|
||||
this.template = template;
|
||||
this.domainType = domainType;
|
||||
this.scope = scope;
|
||||
this.collection = collection;
|
||||
this.options = options;
|
||||
this.reactiveSupport = new ReactiveExistsByIdSupport(template.reactive(), domainType, scope, collection, options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean one(final String id) {
|
||||
return reactiveSupport.one(id).block();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Boolean> all(final Collection<String> ids) {
|
||||
return reactiveSupport.all(ids).block();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExistsByIdWithOptions inCollection(final String collection) {
|
||||
return new ExecutableExistsByIdSupport(template, domainType, scope,
|
||||
collection != null ? collection : this.collection, options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TerminatingExistsById withOptions(final ExistsOptions options) {
|
||||
Assert.notNull(options, "Options must not be null.");
|
||||
return new ExecutableExistsByIdSupport(template, domainType, scope, collection, options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExistsByIdInCollection inScope(final String scope) {
|
||||
return new ExecutableExistsByIdSupport(template, domainType, scope != null ? scope : this.scope, collection,
|
||||
options);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.dao.IncorrectResultSizeDataAccessException;
|
||||
import org.springframework.data.couchbase.core.query.AnalyticsQuery;
|
||||
import org.springframework.data.couchbase.core.support.InCollection;
|
||||
import org.springframework.data.couchbase.core.support.InScope;
|
||||
import org.springframework.data.couchbase.core.support.OneAndAll;
|
||||
import org.springframework.data.couchbase.core.support.WithAnalyticsConsistency;
|
||||
import org.springframework.data.couchbase.core.support.WithAnalyticsOptions;
|
||||
import org.springframework.data.couchbase.core.support.WithAnalyticsQuery;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
import com.couchbase.client.java.analytics.AnalyticsOptions;
|
||||
import com.couchbase.client.java.analytics.AnalyticsScanConsistency;
|
||||
|
||||
/**
|
||||
* FindByAnalytics Operations
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.0
|
||||
*/public interface ExecutableFindByAnalyticsOperation {
|
||||
|
||||
/**
|
||||
* Queries the analytics service.
|
||||
*
|
||||
* @param domainType the entity type to use for the results.
|
||||
*/
|
||||
<T> ExecutableFindByAnalytics<T> findByAnalytics(Class<T> domainType);
|
||||
|
||||
interface TerminatingFindByAnalytics<T> extends OneAndAll<T> {
|
||||
|
||||
/**
|
||||
* Get exactly zero or one result.
|
||||
*
|
||||
* @return {@link Optional#empty()} if no match found.
|
||||
* @throws IncorrectResultSizeDataAccessException if more than one match found.
|
||||
*/
|
||||
default Optional<T> one() {
|
||||
return Optional.ofNullable(oneValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get exactly zero or one result.
|
||||
*
|
||||
* @return {@literal null} if no match found.
|
||||
* @throws IncorrectResultSizeDataAccessException if more than one match found.
|
||||
*/
|
||||
@Nullable
|
||||
T oneValue();
|
||||
|
||||
/**
|
||||
* Get the first or no result.
|
||||
*
|
||||
* @return {@link Optional#empty()} if no match found.
|
||||
*/
|
||||
default Optional<T> first() {
|
||||
return Optional.ofNullable(firstValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the first or no result.
|
||||
*
|
||||
* @return {@literal null} if no match found.
|
||||
*/
|
||||
@Nullable
|
||||
T firstValue();
|
||||
|
||||
/**
|
||||
* Get all matching elements.
|
||||
*
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
List<T> all();
|
||||
|
||||
/**
|
||||
* Stream all matching elements.
|
||||
*
|
||||
* @return a {@link Stream} of results. Never {@literal null}.
|
||||
*/
|
||||
Stream<T> stream();
|
||||
|
||||
/**
|
||||
* Get the number of matching elements.
|
||||
*
|
||||
* @return total number of matching elements.
|
||||
*/
|
||||
long count();
|
||||
|
||||
/**
|
||||
* Check for the presence of matching elements.
|
||||
*
|
||||
* @return {@literal true} if at least one matching element exists.
|
||||
*/
|
||||
boolean exists();
|
||||
|
||||
}
|
||||
|
||||
interface FindByAnalyticsWithQuery<T> extends TerminatingFindByAnalytics<T>, WithAnalyticsQuery<T> {
|
||||
|
||||
/**
|
||||
* Set the filter for the analytics query to be used.
|
||||
*
|
||||
* @param query must not be {@literal null}.
|
||||
* @throws IllegalArgumentException if query is {@literal null}.
|
||||
*/
|
||||
TerminatingFindByAnalytics<T> matching(AnalyticsQuery query);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Fluent method to specify options.
|
||||
*
|
||||
* @param <T> the entity type to use.
|
||||
*/
|
||||
interface FindByAnalyticsWithOptions<T> extends FindByAnalyticsWithQuery<T>, WithAnalyticsOptions<T> {
|
||||
/**
|
||||
* Fluent method to specify options to use for execution
|
||||
*
|
||||
* @param options to use for execution
|
||||
*/
|
||||
@Override
|
||||
FindByAnalyticsWithQuery<T> withOptions(AnalyticsOptions options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fluent method to specify the collection.
|
||||
*
|
||||
* @param <T> the entity type to use for the results.
|
||||
*/
|
||||
interface FindByAnalyticsInCollection<T> extends FindByAnalyticsWithOptions<T>, InCollection<T> {
|
||||
/**
|
||||
* With a different collection
|
||||
*
|
||||
* @param collection the collection to use.
|
||||
*/
|
||||
@Override
|
||||
FindByAnalyticsWithOptions<T> inCollection(String collection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fluent method to specify the scope.
|
||||
*
|
||||
* @param <T> the entity type to use for the results.
|
||||
*/
|
||||
interface FindByAnalyticsInScope<T> extends FindByAnalyticsInCollection<T>, InScope<T> {
|
||||
/**
|
||||
* With a different scope
|
||||
*
|
||||
* @param scope the scope to use.
|
||||
*/
|
||||
@Override
|
||||
FindByAnalyticsInCollection<T> inScope(String scope);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
interface FindByAnalyticsConsistentWith<T> extends FindByAnalyticsInScope<T> {
|
||||
|
||||
/**
|
||||
* Allows to override the default scan consistency.
|
||||
*
|
||||
* @param scanConsistency the custom scan consistency to use for this analytics query.
|
||||
*/
|
||||
@Deprecated
|
||||
FindByAnalyticsWithQuery<T> consistentWith(AnalyticsScanConsistency scanConsistency);
|
||||
|
||||
}
|
||||
|
||||
interface FindByAnalyticsWithConsistency<T> extends FindByAnalyticsConsistentWith<T>, WithAnalyticsConsistency<T> {
|
||||
|
||||
/**
|
||||
* Allows to override the default scan consistency.
|
||||
*
|
||||
* @param scanConsistency the custom scan consistency to use for this analytics query.
|
||||
*/
|
||||
FindByAnalyticsConsistentWith<T> withConsistency(AnalyticsScanConsistency scanConsistency);
|
||||
}
|
||||
|
||||
/**
|
||||
* Result type override (Optional).
|
||||
*/
|
||||
interface FindByAnalyticsWithProjection<T> extends FindByAnalyticsWithConsistency<T> {
|
||||
|
||||
/**
|
||||
* Define the target type fields should be mapped to. <br />
|
||||
* Skip this step if you are anyway only interested in the original domain type.
|
||||
*
|
||||
* @param returnType must not be {@literal null}.
|
||||
* @return new instance of {@link FindByAnalyticsWithConsistency}.
|
||||
* @throws IllegalArgumentException if returnType is {@literal null}.
|
||||
*/
|
||||
<R> FindByAnalyticsWithConsistency<R> as(Class<R> returnType);
|
||||
}
|
||||
|
||||
interface ExecutableFindByAnalytics<T> extends FindByAnalyticsWithProjection<T> {}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
/*
|
||||
* Copyright 2012-2022 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;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.data.couchbase.core.ReactiveFindByAnalyticsOperationSupport.ReactiveFindByAnalyticsSupport;
|
||||
import org.springframework.data.couchbase.core.query.AnalyticsQuery;
|
||||
import org.springframework.data.couchbase.core.query.OptionsBuilder;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.couchbase.client.java.analytics.AnalyticsOptions;
|
||||
import com.couchbase.client.java.analytics.AnalyticsScanConsistency;
|
||||
|
||||
public class ExecutableFindByAnalyticsOperationSupport implements ExecutableFindByAnalyticsOperation {
|
||||
|
||||
private static final AnalyticsQuery ALL_QUERY = new AnalyticsQuery();
|
||||
|
||||
private final CouchbaseTemplate template;
|
||||
|
||||
public ExecutableFindByAnalyticsOperationSupport(final CouchbaseTemplate template) {
|
||||
this.template = template;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> ExecutableFindByAnalytics<T> findByAnalytics(final Class<T> domainType) {
|
||||
return new ExecutableFindByAnalyticsSupport<>(template, domainType, domainType, ALL_QUERY, null,
|
||||
OptionsBuilder.getScopeFrom(domainType), OptionsBuilder.getCollectionFrom(domainType), null);
|
||||
}
|
||||
|
||||
static class ExecutableFindByAnalyticsSupport<T> implements ExecutableFindByAnalytics<T> {
|
||||
|
||||
private final CouchbaseTemplate template;
|
||||
private final Class<?> domainType;
|
||||
private final Class<T> returnType;
|
||||
private final ReactiveFindByAnalyticsSupport<T> reactiveSupport;
|
||||
private final AnalyticsQuery query;
|
||||
private final AnalyticsScanConsistency scanConsistency;
|
||||
private final String scope;
|
||||
private final String collection;
|
||||
private final AnalyticsOptions options;
|
||||
|
||||
ExecutableFindByAnalyticsSupport(final CouchbaseTemplate template, final Class<?> domainType,
|
||||
final Class<T> returnType, final AnalyticsQuery query, final AnalyticsScanConsistency scanConsistency,
|
||||
final String scope, final String collection, final AnalyticsOptions options) {
|
||||
this.template = template;
|
||||
this.domainType = domainType;
|
||||
this.returnType = returnType;
|
||||
this.query = query;
|
||||
this.reactiveSupport = new ReactiveFindByAnalyticsSupport<>(template.reactive(), domainType, returnType, query,
|
||||
scanConsistency, scope, collection, options, new NonReactiveSupportWrapper(template.support()));
|
||||
this.scanConsistency = scanConsistency;
|
||||
this.scope = scope;
|
||||
this.collection = collection;
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T oneValue() {
|
||||
return reactiveSupport.one().block();
|
||||
}
|
||||
|
||||
@Override
|
||||
public T firstValue() {
|
||||
return reactiveSupport.first().block();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<T> all() {
|
||||
return reactiveSupport.all().collectList().block();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TerminatingFindByAnalytics<T> matching(final AnalyticsQuery query) {
|
||||
return new ExecutableFindByAnalyticsSupport<>(template, domainType, returnType, query, scanConsistency, scope,
|
||||
collection, options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FindByAnalyticsWithQuery<T> withOptions(final AnalyticsOptions options) {
|
||||
Assert.notNull(options, "Options must not be null.");
|
||||
return new ExecutableFindByAnalyticsSupport<>(template, domainType, returnType, query, scanConsistency, scope,
|
||||
collection, options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FindByAnalyticsInCollection<T> inScope(final String scope) {
|
||||
return new ExecutableFindByAnalyticsSupport<>(template, domainType, returnType, query, scanConsistency,
|
||||
scope != null ? scope : this.scope, collection, options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FindByAnalyticsWithConsistency<T> inCollection(final String collection) {
|
||||
return new ExecutableFindByAnalyticsSupport<>(template, domainType, returnType, query, scanConsistency, scope,
|
||||
collection != null ? collection : this.collection, options);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public FindByAnalyticsWithQuery<T> consistentWith(final AnalyticsScanConsistency scanConsistency) {
|
||||
return new ExecutableFindByAnalyticsSupport<>(template, domainType, returnType, query, scanConsistency, scope,
|
||||
collection, options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FindByAnalyticsWithConsistency<T> withConsistency(final AnalyticsScanConsistency scanConsistency) {
|
||||
return new ExecutableFindByAnalyticsSupport<>(template, domainType, returnType, query, scanConsistency, scope,
|
||||
collection, options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <R> FindByAnalyticsWithConsistency<R> as(final Class<R> returnType) {
|
||||
Assert.notNull(returnType, "returnType must not be null!");
|
||||
return new ExecutableFindByAnalyticsSupport<>(template, domainType, returnType, query, scanConsistency, scope,
|
||||
collection, options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<T> stream() {
|
||||
return reactiveSupport.all().toStream();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long count() {
|
||||
return reactiveSupport.count().block();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists() {
|
||||
return count() > 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Collection;
|
||||
|
||||
import org.springframework.data.couchbase.core.support.OneAndAllId;
|
||||
import org.springframework.data.couchbase.core.support.InCollection;
|
||||
import org.springframework.data.couchbase.core.support.WithGetOptions;
|
||||
import org.springframework.data.couchbase.core.support.WithProjectionId;
|
||||
import org.springframework.data.couchbase.core.support.InScope;
|
||||
|
||||
import com.couchbase.client.java.kv.GetOptions;
|
||||
import org.springframework.data.couchbase.core.support.WithExpiry;
|
||||
|
||||
/**
|
||||
* Get Operations
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.0
|
||||
*/
|
||||
public interface ExecutableFindByIdOperation {
|
||||
|
||||
/**
|
||||
* Loads a document from a bucket.
|
||||
*
|
||||
* @param domainType the entity type to use for the results.
|
||||
*/
|
||||
<T> ExecutableFindById<T> findById(Class<T> domainType);
|
||||
|
||||
/**
|
||||
* Terminating operations invoking the actual execution.
|
||||
*
|
||||
* @param <T> the entity type to use for the results.
|
||||
*/
|
||||
interface TerminatingFindById<T> extends OneAndAllId<T> {
|
||||
|
||||
/**
|
||||
* Finds one document based on the given ID.
|
||||
*
|
||||
* @param id the document ID.
|
||||
* @return the entity if found.
|
||||
*/
|
||||
T one(String id);
|
||||
|
||||
/**
|
||||
* Finds a list of documents based on the given IDs.
|
||||
*
|
||||
* @param ids the document ID ids.
|
||||
* @return the list of found entities.
|
||||
*/
|
||||
Collection<? extends T> all(Collection<String> ids);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Fluent method to specify options.
|
||||
*
|
||||
* @param <T> the entity type to use for the results.
|
||||
*/
|
||||
interface FindByIdWithOptions<T> extends TerminatingFindById<T>, WithGetOptions<T> {
|
||||
/**
|
||||
* Fluent method to specify options to use for execution
|
||||
*
|
||||
* @param options options to use for execution
|
||||
*/
|
||||
@Override
|
||||
TerminatingFindById<T> withOptions(GetOptions options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fluent method to specify the collection.
|
||||
*
|
||||
* @param <T> the entity type to use for the results.
|
||||
*/
|
||||
interface FindByIdInCollection<T> extends FindByIdWithOptions<T>, InCollection<T> {
|
||||
/**
|
||||
* With a different collection
|
||||
*
|
||||
* @param collection the collection to use.
|
||||
*/
|
||||
@Override
|
||||
FindByIdWithOptions<T> inCollection(String collection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fluent method to specify the scope.
|
||||
*
|
||||
* @param <T> the entity type to use for the results.
|
||||
*/
|
||||
interface FindByIdInScope<T> extends FindByIdInCollection<T>, InScope<T> {
|
||||
/**
|
||||
* With a different scope
|
||||
*
|
||||
* @param scope the scope to use.
|
||||
*/
|
||||
@Override
|
||||
FindByIdInCollection<T> inScope(String scope);
|
||||
}
|
||||
|
||||
interface FindByIdWithProjection<T> extends FindByIdInScope<T>, WithProjectionId<T> {
|
||||
/**
|
||||
* Load only certain fields for the document.
|
||||
*
|
||||
* @param fields the projected fields to load.
|
||||
*/
|
||||
@Override
|
||||
FindByIdInScope<T> project(String... fields);
|
||||
}
|
||||
|
||||
interface FindByIdWithExpiry<T> extends FindByIdWithProjection<T>, WithExpiry<T> {
|
||||
/**
|
||||
* Load only certain fields for the document.
|
||||
*
|
||||
* @param expiry the projected fields to load.
|
||||
*/
|
||||
@Override
|
||||
FindByIdWithProjection<T> withExpiry(Duration expiry);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides methods for constructing query operations in a fluent way.
|
||||
*
|
||||
* @param <T> the entity type to use for the results
|
||||
*/
|
||||
interface ExecutableFindById<T> extends FindByIdWithExpiry<T> {}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Copyright 2012-2022 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;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.couchbase.core.ReactiveFindByIdOperationSupport.ReactiveFindByIdSupport;
|
||||
import org.springframework.data.couchbase.core.query.OptionsBuilder;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.couchbase.client.java.kv.GetOptions;
|
||||
|
||||
public class ExecutableFindByIdOperationSupport implements ExecutableFindByIdOperation {
|
||||
|
||||
private final CouchbaseTemplate template;
|
||||
|
||||
ExecutableFindByIdOperationSupport(CouchbaseTemplate template) {
|
||||
this.template = template;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> ExecutableFindById<T> findById(Class<T> domainType) {
|
||||
return new ExecutableFindByIdSupport<>(template, domainType, OptionsBuilder.getScopeFrom(domainType),
|
||||
OptionsBuilder.getCollectionFrom(domainType),null, null, null);
|
||||
}
|
||||
|
||||
static class ExecutableFindByIdSupport<T> implements ExecutableFindById<T> {
|
||||
|
||||
private final CouchbaseTemplate template;
|
||||
private final Class<T> domainType;
|
||||
private final String scope;
|
||||
private final String collection;
|
||||
private final GetOptions options;
|
||||
private final List<String> fields;
|
||||
private final Duration expiry;
|
||||
private final ReactiveFindByIdSupport<T> reactiveSupport;
|
||||
|
||||
ExecutableFindByIdSupport(CouchbaseTemplate template, Class<T> domainType, String scope, String collection,
|
||||
GetOptions options, List<String> fields, Duration expiry) {
|
||||
this.template = template;
|
||||
this.domainType = domainType;
|
||||
this.scope = scope;
|
||||
this.collection = collection;
|
||||
this.options = options;
|
||||
this.fields = fields;
|
||||
this.expiry = expiry;
|
||||
this.reactiveSupport = new ReactiveFindByIdSupport<>(template.reactive(), domainType, scope, collection, options,
|
||||
fields, expiry, new NonReactiveSupportWrapper(template.support()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public T one(final String id) {
|
||||
return reactiveSupport.one(id).block();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<? extends T> all(final Collection<String> ids) {
|
||||
return reactiveSupport.all(ids).collectList().block();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TerminatingFindById<T> withOptions(final GetOptions options) {
|
||||
Assert.notNull(options, "Options must not be null.");
|
||||
return new ExecutableFindByIdSupport<>(template, domainType, scope, collection, options, fields, expiry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FindByIdWithOptions<T> inCollection(final String collection) {
|
||||
return new ExecutableFindByIdSupport<>(template, domainType, scope, collection != null ? collection : this.collection, options, fields, expiry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FindByIdInCollection<T> inScope(final String scope) {
|
||||
return new ExecutableFindByIdSupport<>(template, domainType, scope != null ? scope : this.scope, collection, options, fields, expiry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FindByIdInScope<T> project(String... fields) {
|
||||
Assert.notEmpty(fields, "Fields must not be null.");
|
||||
return new ExecutableFindByIdSupport<>(template, domainType, scope, collection, options, Arrays.asList(fields), expiry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FindByIdWithProjection<T> withExpiry(final Duration expiry) {
|
||||
return new ExecutableFindByIdSupport<>(template, domainType, scope, collection, options, fields,
|
||||
expiry);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,300 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.dao.IncorrectResultSizeDataAccessException;
|
||||
import org.springframework.data.couchbase.core.query.Query;
|
||||
import org.springframework.data.couchbase.core.query.QueryCriteriaDefinition;
|
||||
import org.springframework.data.couchbase.core.support.InCollection;
|
||||
import org.springframework.data.couchbase.core.support.InScope;
|
||||
import org.springframework.data.couchbase.core.support.OneAndAll;
|
||||
import org.springframework.data.couchbase.core.support.WithConsistency;
|
||||
import org.springframework.data.couchbase.core.support.WithDistinct;
|
||||
import org.springframework.data.couchbase.core.support.WithQuery;
|
||||
import org.springframework.data.couchbase.core.support.WithQueryOptions;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
import com.couchbase.client.java.query.QueryOptions;
|
||||
import com.couchbase.client.java.query.QueryScanConsistency;
|
||||
|
||||
/**
|
||||
* Query Operations
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.0
|
||||
*/
|
||||
public interface ExecutableFindByQueryOperation {
|
||||
|
||||
/**
|
||||
* Queries the N1QL service.
|
||||
*
|
||||
* @param domainType the entity type to use for the results.
|
||||
*/
|
||||
<T> ExecutableFindByQuery<T> findByQuery(Class<T> domainType);
|
||||
|
||||
/**
|
||||
* Terminating operations invoking the actual execution.
|
||||
*/
|
||||
interface TerminatingFindByQuery<T> extends OneAndAll<T> {
|
||||
|
||||
/**
|
||||
* Get exactly zero or one result.
|
||||
*
|
||||
* @return {@link Optional#empty()} if no match found.
|
||||
* @throws IncorrectResultSizeDataAccessException if more than one match found.
|
||||
*/
|
||||
@Override
|
||||
default Optional<T> one() {
|
||||
return Optional.ofNullable(oneValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get exactly zero or one result.
|
||||
*
|
||||
* @return {@literal null} if no match found.
|
||||
* @throws IncorrectResultSizeDataAccessException if more than one match found.
|
||||
*/
|
||||
@Nullable
|
||||
@Override
|
||||
T oneValue();
|
||||
|
||||
/**
|
||||
* Get the first or no result.
|
||||
*
|
||||
* @return {@link Optional#empty()} if no match found.
|
||||
*/
|
||||
@Override
|
||||
default Optional<T> first() {
|
||||
return Optional.ofNullable(firstValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the first or no result.
|
||||
*
|
||||
* @return {@literal null} if no match found.
|
||||
*/
|
||||
@Nullable
|
||||
@Override
|
||||
T firstValue();
|
||||
|
||||
/**
|
||||
* Get all matching documents.
|
||||
*
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
@Override
|
||||
List<T> all();
|
||||
|
||||
/**
|
||||
* Stream all matching elements.
|
||||
*
|
||||
* @return a {@link Stream} of results. Never {@literal null}.
|
||||
*/
|
||||
@Override
|
||||
Stream<T> stream();
|
||||
|
||||
/**
|
||||
* Get the number of matching elements.
|
||||
*
|
||||
* @return total number of matching elements.
|
||||
*/
|
||||
@Override
|
||||
long count();
|
||||
|
||||
/**
|
||||
* Check for the presence of matching elements.
|
||||
*
|
||||
* @return {@literal true} if at least one matching element exists.
|
||||
*/
|
||||
@Override
|
||||
boolean exists();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Fluent methods to specify the query
|
||||
*
|
||||
* @param <T> the entity type to use for the results.
|
||||
*/
|
||||
interface FindByQueryWithQuery<T> extends TerminatingFindByQuery<T>, WithQuery<T> {
|
||||
|
||||
/**
|
||||
* Set the filter for the query to be used.
|
||||
*
|
||||
* @param query must not be {@literal null}.
|
||||
* @throws IllegalArgumentException if query is {@literal null}.
|
||||
*/
|
||||
@Override
|
||||
TerminatingFindByQuery<T> matching(Query query);
|
||||
|
||||
/**
|
||||
* Set the filter {@link QueryCriteriaDefinition criteria} to be used.
|
||||
*
|
||||
* @param criteria must not be {@literal null}.
|
||||
* @return new instance of {@link ExecutableFindByQuery}.
|
||||
* @throws IllegalArgumentException if criteria is {@literal null}.
|
||||
*/
|
||||
@Override
|
||||
default TerminatingFindByQuery<T> matching(QueryCriteriaDefinition criteria) {
|
||||
return matching(Query.query(criteria));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Fluent method to specify options.
|
||||
*
|
||||
* @param <T> the entity type to use for the results.
|
||||
*/
|
||||
interface FindByQueryWithOptions<T> extends FindByQueryWithQuery<T>, WithQueryOptions<T> {
|
||||
/**
|
||||
* Fluent method to specify options to use for execution
|
||||
*
|
||||
* @param options to use for execution
|
||||
*/
|
||||
@Override
|
||||
TerminatingFindByQuery<T> withOptions(QueryOptions options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fluent method to specify the collection.
|
||||
*
|
||||
* @param <T> the entity type to use for the results.
|
||||
*/
|
||||
interface FindByQueryInCollection<T> extends FindByQueryWithOptions<T>, InCollection<T> {
|
||||
/**
|
||||
* With a different collection
|
||||
*
|
||||
* @param collection the collection to use.
|
||||
*/
|
||||
@Override
|
||||
FindByQueryWithOptions<T> inCollection(String collection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fluent method to specify the scope.
|
||||
*
|
||||
* @param <T> the entity type to use for the results.
|
||||
*/
|
||||
interface FindByQueryInScope<T> extends FindByQueryInCollection<T>, InScope<T> {
|
||||
/**
|
||||
* With a different scope
|
||||
*
|
||||
* @param scope the scope to use.
|
||||
*/
|
||||
@Override
|
||||
FindByQueryInCollection<T> inScope(String scope);
|
||||
}
|
||||
|
||||
/**
|
||||
* To be removed at the next major release. use WithConsistency instead
|
||||
*
|
||||
* @param <T> the entity type to use for the results.
|
||||
*/
|
||||
@Deprecated
|
||||
interface FindByQueryConsistentWith<T> extends FindByQueryInScope<T> {
|
||||
|
||||
/**
|
||||
* Allows to override the default scan consistency.
|
||||
*
|
||||
* @param scanConsistency the custom scan consistency to use for this query.
|
||||
*/
|
||||
@Deprecated
|
||||
FindByQueryInScope<T> consistentWith(QueryScanConsistency scanConsistency);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fluent method to specify scan consistency. Scan consistency may also come from an annotation.
|
||||
*
|
||||
* @param <T> the entity type to use for the results.
|
||||
*/
|
||||
interface FindByQueryWithConsistency<T> extends FindByQueryConsistentWith<T>, WithConsistency<T> {
|
||||
|
||||
/**
|
||||
* Allows to override the default scan consistency.
|
||||
*
|
||||
* @param scanConsistency the custom scan consistency to use for this query.
|
||||
*/
|
||||
@Override
|
||||
FindByQueryConsistentWith<T> withConsistency(QueryScanConsistency scanConsistency);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fluent method to specify a return type different than the the entity type to use for the results.
|
||||
*
|
||||
* @param <T> the entity type to use for the results.
|
||||
*/
|
||||
interface FindByQueryWithProjection<T> extends FindByQueryWithConsistency<T> {
|
||||
|
||||
/**
|
||||
* Define the target type fields should be mapped to. <br />
|
||||
* Skip this step if you are only interested in the original the entity type to use for the results.
|
||||
*
|
||||
* @param returnType must not be {@literal null}.
|
||||
* @return new instance of {@link FindByQueryWithProjection}.
|
||||
* @throws IllegalArgumentException if returnType is {@literal null}.
|
||||
*/
|
||||
<R> FindByQueryWithConsistency<R> as(Class<R> returnType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fluent method to specify fields to project.
|
||||
*
|
||||
* @param <T> the entity type to use for the results.
|
||||
*/
|
||||
interface FindByQueryWithProjecting<T> extends FindByQueryWithProjection<T> {
|
||||
|
||||
/**
|
||||
* Define the target type fields should be mapped to. <br />
|
||||
* Skip this step if you are anyway only interested in the original domain type.
|
||||
*
|
||||
* @param fields to project
|
||||
* @return new instance of {@link ReactiveFindByQueryOperation.FindByQueryWithConsistency}.
|
||||
* @throws IllegalArgumentException if returnType is {@literal null}.
|
||||
*/
|
||||
FindByQueryWithProjection<T> project(String[] fields);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fluent method to specify DISTINCT fields
|
||||
*
|
||||
* @param <T> the entity type to use for the results.
|
||||
*/
|
||||
interface FindByQueryWithDistinct<T> extends FindByQueryWithProjecting<T>, WithDistinct<T> {
|
||||
|
||||
/**
|
||||
* Finds the distinct values for a specified {@literal field} across a single collection
|
||||
*
|
||||
* @param distinctFields name of the field. Must not be {@literal null}.
|
||||
* @return new instance of {@link ExecutableFindByQuery}.
|
||||
* @throws IllegalArgumentException if field is {@literal null}.
|
||||
*/
|
||||
@Override
|
||||
FindByQueryWithProjection<T> distinct(String[] distinctFields);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides methods for constructing query operations in a fluent way.
|
||||
*
|
||||
* @param <T> the entity type to use for the results
|
||||
*/
|
||||
interface ExecutableFindByQuery<T> extends FindByQueryWithDistinct<T> {}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
/*
|
||||
* Copyright 2012-2022 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;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.data.couchbase.core.ReactiveFindByQueryOperationSupport.ReactiveFindByQuerySupport;
|
||||
import org.springframework.data.couchbase.core.query.OptionsBuilder;
|
||||
import org.springframework.data.couchbase.core.query.Query;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.couchbase.client.java.query.QueryOptions;
|
||||
import com.couchbase.client.java.query.QueryScanConsistency;
|
||||
|
||||
/**
|
||||
* {@link ExecutableFindByQueryOperation} implementations for Couchbase.
|
||||
*
|
||||
* @author Michael Nitschinger
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
public class ExecutableFindByQueryOperationSupport implements ExecutableFindByQueryOperation {
|
||||
|
||||
private static final Query ALL_QUERY = new Query();
|
||||
|
||||
private final CouchbaseTemplate template;
|
||||
|
||||
public ExecutableFindByQueryOperationSupport(final CouchbaseTemplate template) {
|
||||
this.template = template;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> ExecutableFindByQuery<T> findByQuery(final Class<T> domainType) {
|
||||
return new ExecutableFindByQuerySupport<T>(template, domainType, domainType, ALL_QUERY, null,
|
||||
OptionsBuilder.getScopeFrom(domainType), OptionsBuilder.getCollectionFrom(domainType), null, null, null);
|
||||
}
|
||||
|
||||
static class ExecutableFindByQuerySupport<T> implements ExecutableFindByQuery<T> {
|
||||
|
||||
private final CouchbaseTemplate template;
|
||||
private final Class<?> domainType;
|
||||
private final Class<T> returnType;
|
||||
private final Query query;
|
||||
private final ReactiveFindByQuerySupport<T> reactiveSupport;
|
||||
private final QueryScanConsistency scanConsistency;
|
||||
private final String scope;
|
||||
private final String collection;
|
||||
private final QueryOptions options;
|
||||
private final String[] distinctFields;
|
||||
private final String[] fields;
|
||||
|
||||
ExecutableFindByQuerySupport(final CouchbaseTemplate template, final Class<?> domainType, final Class<T> returnType,
|
||||
final Query query, final QueryScanConsistency scanConsistency, final String scope, final String collection,
|
||||
final QueryOptions options, final String[] distinctFields, final String[] fields) {
|
||||
this.template = template;
|
||||
this.domainType = domainType;
|
||||
this.returnType = returnType;
|
||||
this.query = query;
|
||||
this.reactiveSupport = new ReactiveFindByQuerySupport<T>(template.reactive(), domainType, returnType, query,
|
||||
scanConsistency, scope, collection, options, distinctFields, fields,
|
||||
new NonReactiveSupportWrapper(template.support()));
|
||||
this.scanConsistency = scanConsistency;
|
||||
this.scope = scope;
|
||||
this.collection = collection;
|
||||
this.options = options;
|
||||
this.distinctFields = distinctFields;
|
||||
this.fields = fields;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T oneValue() {
|
||||
return reactiveSupport.one().block();
|
||||
}
|
||||
|
||||
@Override
|
||||
public T firstValue() {
|
||||
return reactiveSupport.first().block();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<T> all() {
|
||||
return reactiveSupport.all().collectList().block();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TerminatingFindByQuery<T> matching(final Query query) {
|
||||
QueryScanConsistency scanCons;
|
||||
if (query.getScanConsistency() != null) {
|
||||
scanCons = query.getScanConsistency();
|
||||
} else {
|
||||
scanCons = scanConsistency;
|
||||
}
|
||||
return new ExecutableFindByQuerySupport<>(template, domainType, returnType, query, scanCons, scope, collection,
|
||||
options, distinctFields, fields);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public FindByQueryInScope<T> consistentWith(final QueryScanConsistency scanConsistency) {
|
||||
return new ExecutableFindByQuerySupport<>(template, domainType, returnType, query, scanConsistency, scope,
|
||||
collection, options, distinctFields, fields);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FindByQueryConsistentWith<T> withConsistency(final QueryScanConsistency scanConsistency) {
|
||||
return new ExecutableFindByQuerySupport<>(template, domainType, returnType, query, scanConsistency, scope,
|
||||
collection, options, distinctFields, fields);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <R> FindByQueryWithConsistency<R> as(final Class<R> returnType) {
|
||||
Assert.notNull(returnType, "returnType must not be null!");
|
||||
return new ExecutableFindByQuerySupport<>(template, domainType, returnType, query, scanConsistency, scope,
|
||||
collection, options, distinctFields, fields);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FindByQueryWithProjection<T> project(String[] fields) {
|
||||
Assert.notNull(fields, "Fields must not be null");
|
||||
Assert.isNull(distinctFields, "only one of project(fields) and distinct(distinctFields) can be specified");
|
||||
return new ExecutableFindByQuerySupport<>(template, domainType, returnType, query, scanConsistency, scope,
|
||||
collection, options, distinctFields, fields);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FindByQueryWithProjection<T> distinct(final String[] distinctFields) {
|
||||
Assert.notNull(distinctFields, "distinctFields must not be null");
|
||||
Assert.isNull(fields, "only one of project(fields) and distinct(distinctFields) can be specified");
|
||||
// Coming from an annotation, this cannot be null.
|
||||
// But a non-null but empty distinctFields means distinct on all fields
|
||||
// So to indicate do not use distinct, we use {"-"} from the annotation, and here we change it to null.
|
||||
String[] dFields = distinctFields.length == 1 && "-".equals(distinctFields[0]) ? null : distinctFields;
|
||||
return new ExecutableFindByQuerySupport<>(template, domainType, returnType, query, scanConsistency, scope,
|
||||
collection, options, dFields, fields);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<T> stream() {
|
||||
return reactiveSupport.all().toStream();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long count() {
|
||||
Long l = reactiveSupport.count().block();
|
||||
if (l == null) {
|
||||
throw new CouchbaseQueryExecutionException("count query did not return a count : " + query.export());
|
||||
}
|
||||
return l;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists() {
|
||||
return count() > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TerminatingFindByQuery<T> withOptions(final QueryOptions options) {
|
||||
Assert.notNull(options, "Options must not be null.");
|
||||
return new ExecutableFindByQuerySupport<>(template, domainType, returnType, query, scanConsistency, scope,
|
||||
collection, options, distinctFields, fields);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FindByQueryInCollection<T> inScope(final String scope) {
|
||||
return new ExecutableFindByQuerySupport<>(template, domainType, returnType, query, scanConsistency,
|
||||
scope != null ? scope : this.scope, collection, options, distinctFields, fields);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FindByQueryWithConsistency<T> inCollection(final String collection) {
|
||||
return new ExecutableFindByQuerySupport<>(template, domainType, returnType, query, scanConsistency, scope,
|
||||
collection != null ? collection : this.collection, options, distinctFields, fields);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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.core;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.springframework.data.couchbase.core.support.AnyId;
|
||||
import org.springframework.data.couchbase.core.support.InCollection;
|
||||
import org.springframework.data.couchbase.core.support.InScope;
|
||||
import org.springframework.data.couchbase.core.support.WithGetAnyReplicaOptions;
|
||||
|
||||
import com.couchbase.client.java.kv.GetAnyReplicaOptions;
|
||||
|
||||
/**
|
||||
* Query Operations
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.0
|
||||
*/
|
||||
public interface ExecutableFindFromReplicasByIdOperation {
|
||||
|
||||
/**
|
||||
* Loads a document from a replica.
|
||||
*
|
||||
* @param domainType the entity type to use for the results.
|
||||
*/
|
||||
<T> ExecutableFindFromReplicasById<T> findFromReplicasById(Class<T> domainType);
|
||||
|
||||
/**
|
||||
* Terminating operations invoking the actual get execution.
|
||||
*/
|
||||
interface TerminatingFindFromReplicasById<T> extends AnyId<T> {
|
||||
/**
|
||||
* Finds one document based on the given ID.
|
||||
*
|
||||
* @param id the document ID.
|
||||
* @return the entity if found.
|
||||
*/
|
||||
@Override
|
||||
T any(String id);
|
||||
/**
|
||||
* Finds a list of documents based on the given IDs.
|
||||
*
|
||||
* @param ids the document ID ids.
|
||||
* @return the list of found entities.
|
||||
*/
|
||||
@Override
|
||||
Collection<? extends T> any(Collection<String> ids);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Fluent method to specify options.
|
||||
*
|
||||
* @param <T> the entity type to use for the results.
|
||||
*/
|
||||
interface FindFromReplicasByIdWithOptions<T> extends TerminatingFindFromReplicasById<T>, WithGetAnyReplicaOptions<T> {
|
||||
/**
|
||||
* Fluent method to specify options to use for execution
|
||||
*
|
||||
* @param options options to use for execution
|
||||
*/
|
||||
@Override
|
||||
TerminatingFindFromReplicasById<T> withOptions(GetAnyReplicaOptions options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fluent method to specify the collection.
|
||||
*
|
||||
* @param <T> the entity type to use for the results.
|
||||
*/
|
||||
interface FindFromReplicasByIdInCollection<T> extends FindFromReplicasByIdWithOptions<T>, InCollection<T> {
|
||||
/**
|
||||
* With a different collection
|
||||
*
|
||||
* @param collection the collection to use.
|
||||
*/
|
||||
@Override
|
||||
FindFromReplicasByIdWithOptions<T> inCollection(String collection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fluent method to specify the scope.
|
||||
*
|
||||
* @param <T> the entity type to use for the results.
|
||||
*/
|
||||
interface FindFromReplicasByIdInScope<T> extends FindFromReplicasByIdInCollection<T>, InScope<T> {
|
||||
/**
|
||||
* With a different scope
|
||||
*
|
||||
* @param scope the scope to use.
|
||||
*/
|
||||
@Override
|
||||
FindFromReplicasByIdInCollection<T> inScope(String scope);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides methods for constructing get operations in a fluent way.
|
||||
*
|
||||
* @param <T> the entity type to use for the results
|
||||
*/
|
||||
interface ExecutableFindFromReplicasById<T> extends FindFromReplicasByIdInScope<T> {}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright 2012-2022 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;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.springframework.data.couchbase.core.ReactiveFindFromReplicasByIdOperationSupport.ReactiveFindFromReplicasByIdSupport;
|
||||
import org.springframework.data.couchbase.core.query.OptionsBuilder;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.couchbase.client.java.kv.GetAnyReplicaOptions;
|
||||
|
||||
public class ExecutableFindFromReplicasByIdOperationSupport implements ExecutableFindFromReplicasByIdOperation {
|
||||
|
||||
private final CouchbaseTemplate template;
|
||||
|
||||
ExecutableFindFromReplicasByIdOperationSupport(CouchbaseTemplate template) {
|
||||
this.template = template;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> ExecutableFindFromReplicasById<T> findFromReplicasById(Class<T> domainType) {
|
||||
return new ExecutableFindFromReplicasByIdSupport<>(template, domainType, domainType,
|
||||
OptionsBuilder.getScopeFrom(domainType), OptionsBuilder.getCollectionFrom(domainType), null);
|
||||
}
|
||||
|
||||
static class ExecutableFindFromReplicasByIdSupport<T> implements ExecutableFindFromReplicasById<T> {
|
||||
|
||||
private final CouchbaseTemplate template;
|
||||
private final Class<?> domainType;
|
||||
private final Class<T> returnType;
|
||||
private final String scope;
|
||||
private final String collection;
|
||||
private final GetAnyReplicaOptions options;
|
||||
private final ReactiveFindFromReplicasByIdSupport<T> reactiveSupport;
|
||||
|
||||
ExecutableFindFromReplicasByIdSupport(CouchbaseTemplate template, Class<?> domainType, Class<T> returnType,
|
||||
String scope, String collection, GetAnyReplicaOptions options) {
|
||||
this.template = template;
|
||||
this.domainType = domainType;
|
||||
this.scope = scope;
|
||||
this.collection = collection;
|
||||
this.options = options;
|
||||
this.returnType = returnType;
|
||||
this.reactiveSupport = new ReactiveFindFromReplicasByIdSupport<>(template.reactive(), domainType, returnType,
|
||||
scope, collection, options, new NonReactiveSupportWrapper(template.support()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public T any(String id) {
|
||||
return reactiveSupport.any(id).block();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<? extends T> any(Collection<String> ids) {
|
||||
return reactiveSupport.any(ids).collectList().block();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TerminatingFindFromReplicasById<T> withOptions(final GetAnyReplicaOptions options) {
|
||||
Assert.notNull(options, "Options must not be null.");
|
||||
return new ExecutableFindFromReplicasByIdSupport<>(template, domainType, returnType, scope, collection, options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FindFromReplicasByIdWithOptions<T> inCollection(final String collection) {
|
||||
return new ExecutableFindFromReplicasByIdSupport<>(template, domainType, returnType, scope,
|
||||
collection != null ? collection : this.collection, options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FindFromReplicasByIdInCollection<T> inScope(final String scope) {
|
||||
return new ExecutableFindFromReplicasByIdSupport<>(template, domainType, returnType,
|
||||
scope != null ? scope : this.scope, collection, options);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
/*
|
||||
* Copyright 2012-2022 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;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Collection;
|
||||
|
||||
import org.springframework.data.couchbase.core.support.InCollection;
|
||||
import org.springframework.data.couchbase.core.support.InScope;
|
||||
import org.springframework.data.couchbase.core.support.OneAndAllEntity;
|
||||
import org.springframework.data.couchbase.core.support.WithDurability;
|
||||
import org.springframework.data.couchbase.core.support.WithExpiry;
|
||||
import org.springframework.data.couchbase.core.support.WithInsertOptions;
|
||||
|
||||
import com.couchbase.client.core.msg.kv.DurabilityLevel;
|
||||
import com.couchbase.client.java.kv.InsertOptions;
|
||||
import com.couchbase.client.java.kv.PersistTo;
|
||||
import com.couchbase.client.java.kv.ReplicateTo;
|
||||
|
||||
/**
|
||||
* Insert Operations
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.0
|
||||
*/
|
||||
public interface ExecutableInsertByIdOperation {
|
||||
|
||||
/**
|
||||
* Insert using the KV service.
|
||||
*
|
||||
* @param domainType the entity type to insert.
|
||||
*/
|
||||
<T> ExecutableInsertById<T> insertById(Class<T> domainType);
|
||||
|
||||
/**
|
||||
* Terminating operations invoking the actual execution.
|
||||
*/
|
||||
interface TerminatingInsertById<T> extends OneAndAllEntity<T> {
|
||||
|
||||
/**
|
||||
* Insert one entity.
|
||||
*
|
||||
* @return Inserted entity.
|
||||
*/
|
||||
@Override
|
||||
T one(T object);
|
||||
|
||||
/**
|
||||
* Insert a collection of entities.
|
||||
*
|
||||
* @return Inserted entities
|
||||
*/
|
||||
@Override
|
||||
Collection<? extends T> all(Collection<? extends T> objects);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Fluent method to specify options.
|
||||
*
|
||||
* @param <T> the entity type to use.
|
||||
*/
|
||||
interface InsertByIdWithOptions<T>
|
||||
extends TerminatingInsertById<T>, WithInsertOptions<T> {
|
||||
/**
|
||||
* Fluent method to specify options to use for execution.
|
||||
*
|
||||
* @param options to use for execution
|
||||
*/
|
||||
@Override
|
||||
TerminatingInsertById<T> withOptions(InsertOptions options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fluent method to specify the collection.
|
||||
*
|
||||
* @param <T> the entity type to use for the results.
|
||||
*/
|
||||
interface InsertByIdInCollection<T> extends InsertByIdWithOptions<T>, InCollection<T> {
|
||||
/**
|
||||
* With a different collection
|
||||
*
|
||||
* @param collection the collection to use.
|
||||
*/
|
||||
@Override
|
||||
InsertByIdWithOptions<T> inCollection(String collection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fluent method to specify the scope.
|
||||
*
|
||||
* @param <T> the entity type to use for the results.
|
||||
*/
|
||||
interface InsertByIdInScope<T> extends InsertByIdInCollection<T>, InScope<T> {
|
||||
/**
|
||||
* With a different scope
|
||||
*
|
||||
* @param scope the scope to use.
|
||||
*/
|
||||
@Override
|
||||
InsertByIdInCollection<T> inScope(String scope);
|
||||
}
|
||||
|
||||
interface InsertByIdWithDurability<T> extends InsertByIdInScope<T>, WithDurability<T> {
|
||||
|
||||
@Override
|
||||
InsertByIdInScope<T> withDurability(DurabilityLevel durabilityLevel);
|
||||
|
||||
@Override
|
||||
InsertByIdInScope<T> withDurability(PersistTo persistTo, ReplicateTo replicateTo);
|
||||
|
||||
}
|
||||
|
||||
interface InsertByIdWithExpiry<T> extends InsertByIdWithDurability<T>, WithExpiry<T> {
|
||||
|
||||
@Override
|
||||
InsertByIdWithDurability<T> withExpiry(Duration expiry);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides methods for constructing KV insert operations in a fluent way.
|
||||
*
|
||||
* @param <T> the entity type to insert
|
||||
*/
|
||||
interface ExecutableInsertById<T> extends InsertByIdWithExpiry<T> {}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* Copyright 2012-2022 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;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Collection;
|
||||
|
||||
import org.springframework.data.couchbase.core.ReactiveInsertByIdOperationSupport.ReactiveInsertByIdSupport;
|
||||
import org.springframework.data.couchbase.core.query.OptionsBuilder;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.couchbase.client.core.msg.kv.DurabilityLevel;
|
||||
import com.couchbase.client.java.kv.InsertOptions;
|
||||
import com.couchbase.client.java.kv.PersistTo;
|
||||
import com.couchbase.client.java.kv.ReplicateTo;
|
||||
|
||||
public class ExecutableInsertByIdOperationSupport implements ExecutableInsertByIdOperation {
|
||||
|
||||
private final CouchbaseTemplate template;
|
||||
|
||||
public ExecutableInsertByIdOperationSupport(final CouchbaseTemplate template) {
|
||||
this.template = template;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> ExecutableInsertById<T> insertById(final Class<T> domainType) {
|
||||
Assert.notNull(domainType, "DomainType must not be null!");
|
||||
return new ExecutableInsertByIdSupport<>(template, domainType, OptionsBuilder.getScopeFrom(domainType),
|
||||
OptionsBuilder.getCollectionFrom(domainType), null, PersistTo.NONE, ReplicateTo.NONE, DurabilityLevel.NONE,
|
||||
null);
|
||||
}
|
||||
|
||||
static class ExecutableInsertByIdSupport<T> implements ExecutableInsertById<T> {
|
||||
|
||||
private final CouchbaseTemplate template;
|
||||
private final Class<T> domainType;
|
||||
private final String scope;
|
||||
private final String collection;
|
||||
private final InsertOptions options;
|
||||
private final PersistTo persistTo;
|
||||
private final ReplicateTo replicateTo;
|
||||
private final DurabilityLevel durabilityLevel;
|
||||
private final Duration expiry;
|
||||
private final ReactiveInsertByIdSupport<T> reactiveSupport;
|
||||
|
||||
ExecutableInsertByIdSupport(final CouchbaseTemplate template, final Class<T> domainType, final String scope,
|
||||
final String collection, final InsertOptions options, final PersistTo persistTo, final ReplicateTo replicateTo,
|
||||
final DurabilityLevel durabilityLevel, final Duration expiry) {
|
||||
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.expiry = expiry;
|
||||
this.reactiveSupport = new ReactiveInsertByIdSupport<>(template.reactive(), domainType, scope, collection,
|
||||
options, persistTo, replicateTo, durabilityLevel, expiry, new NonReactiveSupportWrapper(template.support()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public T one(final T object) {
|
||||
return reactiveSupport.one(object).block();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<? extends T> all(Collection<? extends T> objects) {
|
||||
return reactiveSupport.all(objects).collectList().block();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TerminatingInsertById<T> withOptions(final InsertOptions options) {
|
||||
Assert.notNull(options, "Options must not be null.");
|
||||
return new ExecutableInsertByIdSupport<>(template, domainType, scope, collection, options, persistTo, replicateTo,
|
||||
durabilityLevel, expiry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InsertByIdInCollection<T> inScope(final String scope) {
|
||||
return new ExecutableInsertByIdSupport<>(template, domainType, scope != null ? scope : this.scope, collection,
|
||||
options, persistTo, replicateTo, durabilityLevel, expiry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InsertByIdWithOptions<T> inCollection(final String collection) {
|
||||
return new ExecutableInsertByIdSupport<>(template, domainType, scope,
|
||||
collection != null ? collection : this.collection, options, persistTo, replicateTo, durabilityLevel, expiry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InsertByIdInScope<T> withDurability(final DurabilityLevel durabilityLevel) {
|
||||
Assert.notNull(durabilityLevel, "Durability Level must not be null.");
|
||||
return new ExecutableInsertByIdSupport<>(template, domainType, scope, collection, options, persistTo, replicateTo,
|
||||
durabilityLevel, expiry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InsertByIdInScope<T> 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 ExecutableInsertByIdSupport<>(template, domainType, scope, collection, options, persistTo, replicateTo,
|
||||
durabilityLevel, expiry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InsertByIdWithDurability<T> withExpiry(final Duration expiry) {
|
||||
Assert.notNull(expiry, "expiry must not be null.");
|
||||
return new ExecutableInsertByIdSupport<>(template, domainType, scope, collection, options, persistTo, replicateTo,
|
||||
durabilityLevel, expiry);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* Copyright 2012-2022 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;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.couchbase.core.support.InCollection;
|
||||
import org.springframework.data.couchbase.core.support.InScope;
|
||||
import org.springframework.data.couchbase.core.support.OneAndAllId;
|
||||
import org.springframework.data.couchbase.core.support.WithDurability;
|
||||
import org.springframework.data.couchbase.core.support.WithRemoveOptions;
|
||||
|
||||
import com.couchbase.client.core.msg.kv.DurabilityLevel;
|
||||
import com.couchbase.client.java.kv.PersistTo;
|
||||
import com.couchbase.client.java.kv.RemoveOptions;
|
||||
import com.couchbase.client.java.kv.ReplicateTo;
|
||||
|
||||
/**
|
||||
* Remove Operations on KV service.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author Michael Reiche
|
||||
* @since 2.0
|
||||
*/
|
||||
public interface ExecutableRemoveByIdOperation {
|
||||
/**
|
||||
* Removes a document.
|
||||
*/
|
||||
ExecutableRemoveById removeById(Class<?> domainType);
|
||||
|
||||
/**
|
||||
* Removes a document.
|
||||
*/
|
||||
@Deprecated
|
||||
ExecutableRemoveById removeById();
|
||||
|
||||
/**
|
||||
* Terminating operations invoking the actual execution.
|
||||
*/
|
||||
interface TerminatingRemoveById extends OneAndAllId<RemoveResult> {
|
||||
|
||||
/**
|
||||
* Remove one document based on the given ID.
|
||||
*
|
||||
* @param id the document ID.
|
||||
* @return result of the remove
|
||||
*/
|
||||
@Override
|
||||
RemoveResult one(String id);
|
||||
|
||||
/**
|
||||
* Remove one document based on the entity. Transactions need the entity for the cas.
|
||||
*
|
||||
* @param entity the document ID.
|
||||
* @return result of the remove
|
||||
*/
|
||||
RemoveResult oneEntity(Object entity);
|
||||
|
||||
/**
|
||||
* Remove the documents in the collection.
|
||||
*
|
||||
* @param ids the document IDs.
|
||||
* @return result of the removes.
|
||||
*/
|
||||
@Override
|
||||
List<RemoveResult> all(Collection<String> ids);
|
||||
|
||||
/**
|
||||
* Remove documents based on the entities. Transactions need the entity for the cas.
|
||||
*
|
||||
* @param entities to remove.
|
||||
* @return result of the remove
|
||||
*/
|
||||
List<RemoveResult> allEntities(Collection<Object> entities);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Fluent method to specify options.
|
||||
*/
|
||||
interface RemoveByIdWithOptions extends TerminatingRemoveById, WithRemoveOptions<RemoveResult> {
|
||||
/**
|
||||
* Fluent method to specify options to use for execution
|
||||
*
|
||||
* @param options options to use for execution
|
||||
*/
|
||||
@Override
|
||||
TerminatingRemoveById withOptions(RemoveOptions options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fluent method to specify the collection.
|
||||
*/
|
||||
interface RemoveByIdInCollection extends RemoveByIdWithOptions, InCollection<Object> {
|
||||
/**
|
||||
* With a different collection
|
||||
*
|
||||
* @param collection the collection to use.
|
||||
*/
|
||||
@Override
|
||||
RemoveByIdWithOptions inCollection(String collection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fluent method to specify the scope.
|
||||
*/
|
||||
interface RemoveByIdInScope extends RemoveByIdInCollection, InScope<Object> {
|
||||
/**
|
||||
* With a different scope
|
||||
*
|
||||
* @param scope the scope to use.
|
||||
*/
|
||||
@Override
|
||||
RemoveByIdInCollection inScope(String scope);
|
||||
}
|
||||
|
||||
interface RemoveByIdWithDurability extends RemoveByIdInScope, WithDurability<RemoveResult> {
|
||||
|
||||
@Override
|
||||
RemoveByIdInScope withDurability(DurabilityLevel durabilityLevel);
|
||||
|
||||
@Override
|
||||
RemoveByIdInScope withDurability(PersistTo persistTo, ReplicateTo replicateTo);
|
||||
|
||||
}
|
||||
|
||||
interface RemoveByIdWithCas extends RemoveByIdWithDurability {
|
||||
|
||||
RemoveByIdWithDurability withCas(Long cas);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides methods for constructing remove operations in a fluent way.
|
||||
*/
|
||||
interface ExecutableRemoveById extends RemoveByIdWithCas {}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
/*
|
||||
* Copyright 2012-2022 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;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.couchbase.core.ReactiveRemoveByIdOperationSupport.ReactiveRemoveByIdSupport;
|
||||
import org.springframework.data.couchbase.core.query.OptionsBuilder;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.couchbase.client.core.msg.kv.DurabilityLevel;
|
||||
import com.couchbase.client.java.kv.PersistTo;
|
||||
import com.couchbase.client.java.kv.RemoveOptions;
|
||||
import com.couchbase.client.java.kv.ReplicateTo;
|
||||
|
||||
/**
|
||||
* {@link ExecutableRemoveByIdOperation} implementations for Couchbase.
|
||||
*
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
public class ExecutableRemoveByIdOperationSupport implements ExecutableRemoveByIdOperation {
|
||||
|
||||
private final CouchbaseTemplate template;
|
||||
|
||||
public ExecutableRemoveByIdOperationSupport(final CouchbaseTemplate template) {
|
||||
this.template = template;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public ExecutableRemoveById removeById() {
|
||||
return removeById(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExecutableRemoveById removeById(Class<?> domainType) {
|
||||
|
||||
return new ExecutableRemoveByIdSupport(template, domainType, OptionsBuilder.getScopeFrom(domainType),
|
||||
OptionsBuilder.getCollectionFrom(domainType), 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;
|
||||
private final PersistTo persistTo;
|
||||
private final ReplicateTo replicateTo;
|
||||
private final DurabilityLevel durabilityLevel;
|
||||
private final Long cas;
|
||||
private final ReactiveRemoveByIdSupport reactiveRemoveByIdSupport;
|
||||
|
||||
ExecutableRemoveByIdSupport(final CouchbaseTemplate template, final Class<?> domainType, final String scope,
|
||||
final String collection, final RemoveOptions options, final PersistTo persistTo, final ReplicateTo replicateTo,
|
||||
final DurabilityLevel durabilityLevel, Long cas) {
|
||||
this.template = template;
|
||||
this.domainType = domainType;
|
||||
this.scope = scope;
|
||||
this.collection = collection;
|
||||
this.options = options;
|
||||
this.persistTo = persistTo;
|
||||
this.replicateTo = replicateTo;
|
||||
this.durabilityLevel = durabilityLevel;
|
||||
this.reactiveRemoveByIdSupport = new ReactiveRemoveByIdSupport(template.reactive(), domainType, scope, collection,
|
||||
options, persistTo, replicateTo, durabilityLevel, cas);
|
||||
this.cas = cas;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RemoveResult one(final String id) {
|
||||
return reactiveRemoveByIdSupport.one(id).block();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RemoveResult oneEntity(final Object entity) {
|
||||
return reactiveRemoveByIdSupport.oneEntity(entity).block();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<RemoveResult> all(final Collection<String> ids) {
|
||||
return reactiveRemoveByIdSupport.all(ids).collectList().block();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<RemoveResult> allEntities(final Collection<Object> entities) {
|
||||
return reactiveRemoveByIdSupport.allEntities(entities).collectList().block();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RemoveByIdWithOptions inCollection(final String collection) {
|
||||
return new ExecutableRemoveByIdSupport(template, domainType, scope,
|
||||
collection != null ? collection : this.collection, options, persistTo, replicateTo, durabilityLevel, cas);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RemoveByIdInScope withDurability(final DurabilityLevel durabilityLevel) {
|
||||
Assert.notNull(durabilityLevel, "Durability Level must not be null.");
|
||||
return new ExecutableRemoveByIdSupport(template, domainType, scope, collection, options, persistTo, replicateTo,
|
||||
durabilityLevel, cas);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RemoveByIdInScope withDurability(final PersistTo persistTo, final ReplicateTo replicateTo) {
|
||||
Assert.notNull(persistTo, "PersistTo must not be null.");
|
||||
Assert.notNull(replicateTo, "ReplicateTo must not be null.");
|
||||
return new ExecutableRemoveByIdSupport(template, domainType, scope, collection, options, persistTo, replicateTo,
|
||||
durabilityLevel, cas);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TerminatingRemoveById withOptions(final RemoveOptions options) {
|
||||
Assert.notNull(options, "Options must not be null.");
|
||||
return new ExecutableRemoveByIdSupport(template, domainType, scope, collection, options, persistTo, replicateTo,
|
||||
durabilityLevel, cas);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RemoveByIdInCollection inScope(final String scope) {
|
||||
return new ExecutableRemoveByIdSupport(template, domainType, scope != null ? scope : this.scope, collection,
|
||||
options, persistTo, replicateTo, durabilityLevel, cas);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RemoveByIdWithDurability withCas(Long cas) {
|
||||
return new ExecutableRemoveByIdSupport(template, domainType, scope, collection, options, persistTo, replicateTo,
|
||||
durabilityLevel, cas);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.couchbase.core.query.Query;
|
||||
import org.springframework.data.couchbase.core.query.QueryCriteriaDefinition;
|
||||
import org.springframework.data.couchbase.core.support.InCollection;
|
||||
import org.springframework.data.couchbase.core.support.InScope;
|
||||
import org.springframework.data.couchbase.core.support.WithConsistency;
|
||||
import org.springframework.data.couchbase.core.support.WithQuery;
|
||||
import org.springframework.data.couchbase.core.support.WithQueryOptions;
|
||||
|
||||
import com.couchbase.client.java.query.QueryOptions;
|
||||
import com.couchbase.client.java.query.QueryScanConsistency;
|
||||
|
||||
/**
|
||||
* RemoveBy Query Operations
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.0
|
||||
*/
|
||||
public interface ExecutableRemoveByQueryOperation {
|
||||
|
||||
/**
|
||||
* Remove via the query service.
|
||||
*/
|
||||
<T> ExecutableRemoveByQuery<T> removeByQuery(Class<T> domainType);
|
||||
|
||||
/**
|
||||
* Terminating operations invoking the actual execution.
|
||||
*/
|
||||
interface TerminatingRemoveByQuery<T> {
|
||||
|
||||
/**
|
||||
* Remove all matching documents.
|
||||
*
|
||||
* @return RemoveResult for each matching document
|
||||
*/
|
||||
List<RemoveResult> all();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Fluent methods to specify the query
|
||||
*
|
||||
* @param <T> the entity type.
|
||||
*/
|
||||
interface RemoveByQueryWithQuery<T> extends TerminatingRemoveByQuery<T>, WithQuery<T> {
|
||||
|
||||
TerminatingRemoveByQuery<T> matching(Query query);
|
||||
|
||||
default TerminatingRemoveByQuery<T> matching(QueryCriteriaDefinition criteria) {
|
||||
return matching(Query.query(criteria));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Fluent method to specify options.
|
||||
*
|
||||
* @param <T> the entity type to use for the results.
|
||||
*/
|
||||
interface RemoveByQueryWithOptions<T> extends RemoveByQueryWithQuery<T>, WithQueryOptions<RemoveResult> {
|
||||
/**
|
||||
* Fluent method to specify options to use for execution
|
||||
*
|
||||
* @param options to use for execution
|
||||
*/
|
||||
RemoveByQueryWithQuery<T> withOptions(QueryOptions options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fluent method to specify the collection.
|
||||
*
|
||||
* @param <T> the entity type to use for the results.
|
||||
*/
|
||||
interface RemoveByQueryInCollection<T> extends RemoveByQueryWithOptions<T>, InCollection<Object> {
|
||||
/**
|
||||
* With a different collection
|
||||
*
|
||||
* @param collection the collection to use.
|
||||
*/
|
||||
RemoveByQueryWithOptions<T> inCollection(String collection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fluent method to specify the scope.
|
||||
*
|
||||
* @param <T> the entity type to use for the results.
|
||||
*/
|
||||
interface RemoveByQueryInScope<T> extends RemoveByQueryInCollection<T>, InScope<Object> {
|
||||
/**
|
||||
* With a different scope
|
||||
*
|
||||
* @param scope the scope to use.
|
||||
*/
|
||||
RemoveByQueryInCollection<T> inScope(String scope);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
interface RemoveByQueryConsistentWith<T> extends RemoveByQueryInScope<T> {
|
||||
|
||||
@Deprecated
|
||||
RemoveByQueryInScope<T> consistentWith(QueryScanConsistency scanConsistency);
|
||||
|
||||
}
|
||||
|
||||
interface RemoveByQueryWithConsistency<T> extends RemoveByQueryConsistentWith<T>, WithConsistency<T> {
|
||||
@Override
|
||||
RemoveByQueryConsistentWith<T> withConsistency(QueryScanConsistency scanConsistency);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides methods for constructing query operations in a fluent way.
|
||||
*
|
||||
* @param <T> the entity type.
|
||||
*/
|
||||
interface ExecutableRemoveByQuery<T> extends RemoveByQueryWithConsistency<T> {}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Copyright 2012-2022 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;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.couchbase.core.ReactiveRemoveByQueryOperationSupport.ReactiveRemoveByQuerySupport;
|
||||
import org.springframework.data.couchbase.core.query.OptionsBuilder;
|
||||
import org.springframework.data.couchbase.core.query.Query;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.couchbase.client.java.query.QueryOptions;
|
||||
import com.couchbase.client.java.query.QueryScanConsistency;
|
||||
|
||||
public class ExecutableRemoveByQueryOperationSupport implements ExecutableRemoveByQueryOperation {
|
||||
|
||||
private static final Query ALL_QUERY = new Query();
|
||||
|
||||
private final CouchbaseTemplate template;
|
||||
|
||||
public ExecutableRemoveByQueryOperationSupport(final CouchbaseTemplate template) {
|
||||
this.template = template;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> ExecutableRemoveByQuery<T> removeByQuery(Class<T> domainType) {
|
||||
return new ExecutableRemoveByQuerySupport<>(template, domainType, ALL_QUERY, null,
|
||||
OptionsBuilder.getScopeFrom(domainType), OptionsBuilder.getCollectionFrom(domainType), null);
|
||||
}
|
||||
|
||||
static class ExecutableRemoveByQuerySupport<T> implements ExecutableRemoveByQuery<T> {
|
||||
|
||||
private final CouchbaseTemplate template;
|
||||
private final Class<T> domainType;
|
||||
private final Query query;
|
||||
private final ReactiveRemoveByQuerySupport<T> reactiveSupport;
|
||||
private final QueryScanConsistency scanConsistency;
|
||||
private final String scope;
|
||||
private final String collection;
|
||||
private final QueryOptions options;
|
||||
|
||||
ExecutableRemoveByQuerySupport(final CouchbaseTemplate template, final Class<T> domainType, final Query query,
|
||||
final QueryScanConsistency scanConsistency, String scope, String collection, QueryOptions options) {
|
||||
this.template = template;
|
||||
this.domainType = domainType;
|
||||
this.query = query;
|
||||
this.reactiveSupport = new ReactiveRemoveByQuerySupport<>(template.reactive(), domainType, query, scanConsistency,
|
||||
scope, collection, options);
|
||||
this.scanConsistency = scanConsistency;
|
||||
this.scope = scope;
|
||||
this.collection = collection;
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<RemoveResult> all() {
|
||||
return reactiveSupport.all().collectList().block();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TerminatingRemoveByQuery<T> matching(final Query query) {
|
||||
return new ExecutableRemoveByQuerySupport<>(template, domainType, query, scanConsistency, scope, collection,
|
||||
options);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public RemoveByQueryInScope<T> consistentWith(final QueryScanConsistency scanConsistency) {
|
||||
return new ExecutableRemoveByQuerySupport<>(template, domainType, query, scanConsistency, scope, collection,
|
||||
options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RemoveByQueryConsistentWith<T> withConsistency(final QueryScanConsistency scanConsistency) {
|
||||
return new ExecutableRemoveByQuerySupport<>(template, domainType, query, scanConsistency, scope, collection,
|
||||
options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RemoveByQueryWithConsistency<T> inCollection(final String collection) {
|
||||
return new ExecutableRemoveByQuerySupport<>(template, domainType, query, scanConsistency, scope,
|
||||
collection != null ? collection : this.collection, options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RemoveByQueryWithQuery<T> withOptions(final QueryOptions options) {
|
||||
Assert.notNull(options, "Options must not be null.");
|
||||
return new ExecutableRemoveByQuerySupport<>(template, domainType, query, scanConsistency, scope, collection,
|
||||
options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RemoveByQueryInCollection<T> inScope(final String scope) {
|
||||
return new ExecutableRemoveByQuerySupport<>(template, domainType, query, scanConsistency,
|
||||
scope != null ? scope : this.scope, collection, options);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Collection;
|
||||
|
||||
import org.springframework.data.couchbase.core.support.InCollection;
|
||||
import org.springframework.data.couchbase.core.support.InScope;
|
||||
import org.springframework.data.couchbase.core.support.OneAndAllEntity;
|
||||
import org.springframework.data.couchbase.core.support.WithDurability;
|
||||
import org.springframework.data.couchbase.core.support.WithExpiry;
|
||||
import org.springframework.data.couchbase.core.support.WithReplaceOptions;
|
||||
|
||||
import com.couchbase.client.core.msg.kv.DurabilityLevel;
|
||||
import com.couchbase.client.java.kv.PersistTo;
|
||||
import com.couchbase.client.java.kv.ReplaceOptions;
|
||||
import com.couchbase.client.java.kv.ReplicateTo;
|
||||
|
||||
/**
|
||||
* Replace Operations
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.0
|
||||
*/
|
||||
public interface ExecutableReplaceByIdOperation {
|
||||
|
||||
/**
|
||||
* Replace using the KV service.
|
||||
*
|
||||
* @param domainType the entity type to replace.
|
||||
*/
|
||||
<T> ExecutableReplaceById<T> replaceById(Class<T> domainType);
|
||||
|
||||
/**
|
||||
* Terminating operations invoking the actual execution.
|
||||
*/
|
||||
interface TerminatingReplaceById<T> extends OneAndAllEntity<T> {
|
||||
|
||||
/**
|
||||
* Replace one entity.
|
||||
*
|
||||
* @return Replaced entity.
|
||||
*/
|
||||
@Override
|
||||
T one(T object);
|
||||
|
||||
/**
|
||||
* Replace a collection of entities.
|
||||
*
|
||||
* @return Replaced entities
|
||||
*/
|
||||
@Override
|
||||
Collection<? extends T> all(Collection<? extends T> objects);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Fluent method to specify options.
|
||||
*
|
||||
* @param <T> the entity type to use for the results.
|
||||
*/
|
||||
interface ReplaceByIdWithOptions<T> extends TerminatingReplaceById<T>, WithReplaceOptions<T> {
|
||||
/**
|
||||
* Fluent method to specify options to use for execution
|
||||
*
|
||||
* @param options to use for execution
|
||||
*/
|
||||
@Override
|
||||
TerminatingReplaceById<T> withOptions(ReplaceOptions options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fluent method to specify the collection.
|
||||
*
|
||||
* @param <T> the entity type to use for the results.
|
||||
*/
|
||||
interface ReplaceByIdInCollection<T> extends ReplaceByIdWithOptions<T>, InCollection<T> {
|
||||
/**
|
||||
* With a different collection
|
||||
*
|
||||
* @param collection the collection to use.
|
||||
*/
|
||||
@Override
|
||||
ReplaceByIdWithOptions<T> inCollection(String collection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fluent method to specify the scope.
|
||||
*
|
||||
* @param <T> the entity type to use for the results.
|
||||
*/
|
||||
interface ReplaceByIdInScope<T> extends ReplaceByIdInCollection<T>, InScope<T> {
|
||||
/**
|
||||
* With a different scope
|
||||
*
|
||||
* @param scope the scope to use.
|
||||
*/
|
||||
@Override
|
||||
ReplaceByIdInCollection<T> inScope(String scope);
|
||||
}
|
||||
|
||||
interface ReplaceByIdWithDurability<T> extends ReplaceByIdInScope<T>, WithDurability<T> {
|
||||
@Override
|
||||
ReplaceByIdInScope<T> withDurability(DurabilityLevel durabilityLevel);
|
||||
@Override
|
||||
ReplaceByIdInScope<T> withDurability(PersistTo persistTo, ReplicateTo replicateTo);
|
||||
|
||||
}
|
||||
|
||||
interface ReplaceByIdWithExpiry<T> extends ReplaceByIdWithDurability<T>, WithExpiry<T> {
|
||||
@Override
|
||||
ReplaceByIdWithDurability<T> withExpiry(final Duration expiry);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides methods for constructing KV replace operations in a fluent way.
|
||||
*
|
||||
* @param <T> the entity type to replace
|
||||
*/
|
||||
interface ExecutableReplaceById<T> extends ReplaceByIdWithExpiry<T> {}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* Copyright 2012-2022 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;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Collection;
|
||||
|
||||
import org.springframework.data.couchbase.core.ReactiveReplaceByIdOperationSupport.ReactiveReplaceByIdSupport;
|
||||
import org.springframework.data.couchbase.core.query.OptionsBuilder;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.couchbase.client.core.msg.kv.DurabilityLevel;
|
||||
import com.couchbase.client.java.kv.PersistTo;
|
||||
import com.couchbase.client.java.kv.ReplaceOptions;
|
||||
import com.couchbase.client.java.kv.ReplicateTo;
|
||||
|
||||
public class ExecutableReplaceByIdOperationSupport implements ExecutableReplaceByIdOperation {
|
||||
|
||||
private final CouchbaseTemplate template;
|
||||
|
||||
public ExecutableReplaceByIdOperationSupport(final CouchbaseTemplate template) {
|
||||
this.template = template;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> ExecutableReplaceById<T> replaceById(final Class<T> domainType) {
|
||||
Assert.notNull(domainType, "DomainType must not be null!");
|
||||
return new ExecutableReplaceByIdSupport<>(template, domainType, OptionsBuilder.getScopeFrom(domainType),
|
||||
OptionsBuilder.getCollectionFrom(domainType), null, PersistTo.NONE, ReplicateTo.NONE, DurabilityLevel.NONE,
|
||||
null);
|
||||
}
|
||||
|
||||
static class ExecutableReplaceByIdSupport<T> implements ExecutableReplaceById<T> {
|
||||
|
||||
private final CouchbaseTemplate template;
|
||||
private final Class<T> domainType;
|
||||
private final String scope;
|
||||
private final String collection;
|
||||
private final ReplaceOptions options;
|
||||
private final PersistTo persistTo;
|
||||
private final ReplicateTo replicateTo;
|
||||
private final DurabilityLevel durabilityLevel;
|
||||
private final Duration expiry;
|
||||
private final ReactiveReplaceByIdSupport<T> reactiveSupport;
|
||||
|
||||
ExecutableReplaceByIdSupport(final CouchbaseTemplate template, final Class<T> domainType, final String scope,
|
||||
final String collection, ReplaceOptions options, final PersistTo persistTo, final ReplicateTo replicateTo,
|
||||
final DurabilityLevel durabilityLevel, final Duration expiry) {
|
||||
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.expiry = expiry;
|
||||
this.reactiveSupport = new ReactiveReplaceByIdSupport<>(template.reactive(), domainType, scope, collection,
|
||||
options, persistTo, replicateTo, durabilityLevel, expiry, new NonReactiveSupportWrapper(template.support()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public T one(final T object) {
|
||||
return reactiveSupport.one(object).block();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<? extends T> all(Collection<? extends T> objects) {
|
||||
return reactiveSupport.all(objects).collectList().block();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReplaceByIdWithOptions<T> inCollection(final String collection) {
|
||||
return new ExecutableReplaceByIdSupport<>(template, domainType, scope,
|
||||
collection != null ? collection : this.collection, options, persistTo, replicateTo, durabilityLevel, expiry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReplaceByIdInScope<T> withDurability(final DurabilityLevel durabilityLevel) {
|
||||
Assert.notNull(durabilityLevel, "Durability Level must not be null.");
|
||||
return new ExecutableReplaceByIdSupport<>(template, domainType, scope, collection, options, persistTo,
|
||||
replicateTo, durabilityLevel, expiry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReplaceByIdInScope<T> 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 ExecutableReplaceByIdSupport<>(template, domainType, scope, collection, options, persistTo,
|
||||
replicateTo, durabilityLevel, expiry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReplaceByIdWithDurability<T> withExpiry(final Duration expiry) {
|
||||
Assert.notNull(expiry, "expiry must not be null.");
|
||||
return new ExecutableReplaceByIdSupport<>(template, domainType, scope, collection, options, persistTo,
|
||||
replicateTo, durabilityLevel, expiry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TerminatingReplaceById<T> withOptions(final ReplaceOptions options) {
|
||||
Assert.notNull(options, "Options must not be null.");
|
||||
return new ExecutableReplaceByIdSupport<>(template, domainType, scope, collection, options, persistTo,
|
||||
replicateTo, durabilityLevel, expiry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReplaceByIdInCollection<T> inScope(final String scope) {
|
||||
return new ExecutableReplaceByIdSupport<>(template, domainType, scope != null ? scope : this.scope, collection,
|
||||
options, persistTo, replicateTo, durabilityLevel, expiry);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Collection;
|
||||
|
||||
import org.springframework.data.couchbase.core.support.InCollection;
|
||||
import org.springframework.data.couchbase.core.support.InScope;
|
||||
import org.springframework.data.couchbase.core.support.OneAndAllEntity;
|
||||
import org.springframework.data.couchbase.core.support.WithDurability;
|
||||
import org.springframework.data.couchbase.core.support.WithExpiry;
|
||||
import org.springframework.data.couchbase.core.support.WithUpsertOptions;
|
||||
|
||||
import com.couchbase.client.core.msg.kv.DurabilityLevel;
|
||||
import com.couchbase.client.java.kv.PersistTo;
|
||||
import com.couchbase.client.java.kv.ReplicateTo;
|
||||
import com.couchbase.client.java.kv.UpsertOptions;
|
||||
|
||||
/**
|
||||
* Insert Operations
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.0
|
||||
*/
|
||||
public interface ExecutableUpsertByIdOperation {
|
||||
|
||||
/**
|
||||
* Upsert using the KV service.
|
||||
*
|
||||
* @param domainType the entity type to upsert.
|
||||
*/
|
||||
<T> ExecutableUpsertById<T> upsertById(Class<T> domainType);
|
||||
|
||||
/**
|
||||
* Terminating operations invoking the actual execution.
|
||||
*/
|
||||
interface TerminatingUpsertById<T> extends OneAndAllEntity<T> {
|
||||
|
||||
/**
|
||||
* Upsert one entity.
|
||||
*
|
||||
* @return Upserted entity.
|
||||
*/
|
||||
@Override
|
||||
T one(T object);
|
||||
|
||||
/**
|
||||
* Insert a collection of entities.
|
||||
*
|
||||
* @return Inserted entities
|
||||
*/
|
||||
@Override
|
||||
Collection<? extends T> all(Collection<? extends T> objects);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Fluent method to specify options.
|
||||
*
|
||||
* @param <T> the entity type to use.
|
||||
*/
|
||||
interface UpsertByIdWithOptions<T> extends TerminatingUpsertById<T>, WithUpsertOptions<T> {
|
||||
/**
|
||||
* Fluent method to specify options to use for execution
|
||||
*
|
||||
* @param options to use for execution
|
||||
*/
|
||||
@Override
|
||||
TerminatingUpsertById<T> withOptions(UpsertOptions options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fluent method to specify the collection.
|
||||
*
|
||||
* @param <T> the entity type to use for the results.
|
||||
*/
|
||||
interface UpsertByIdInCollection<T> extends UpsertByIdWithOptions<T>, InCollection<T> {
|
||||
/**
|
||||
* With a different collection
|
||||
*
|
||||
* @param collection the collection to use.
|
||||
*/
|
||||
@Override
|
||||
UpsertByIdWithOptions<T> inCollection(String collection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fluent method to specify the scope.
|
||||
*
|
||||
* @param <T> the entity type to use for the results.
|
||||
*/
|
||||
interface UpsertByIdInScope<T> extends UpsertByIdInCollection<T>, InScope<T> {
|
||||
/**
|
||||
* With a different scope
|
||||
*
|
||||
* @param scope the scope to use.
|
||||
*/
|
||||
@Override
|
||||
UpsertByIdInCollection<T> inScope(String scope);
|
||||
}
|
||||
|
||||
interface UpsertByIdWithDurability<T> extends UpsertByIdInScope<T>, WithDurability<T> {
|
||||
@Override
|
||||
UpsertByIdInScope<T> withDurability(DurabilityLevel durabilityLevel);
|
||||
|
||||
@Override
|
||||
UpsertByIdInScope<T> withDurability(PersistTo persistTo, ReplicateTo replicateTo);
|
||||
|
||||
}
|
||||
|
||||
interface UpsertByIdWithExpiry<T> extends UpsertByIdWithDurability<T>, WithExpiry<T> {
|
||||
@Override
|
||||
UpsertByIdWithDurability<T> withExpiry(Duration expiry);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides methods for constructing KV operations in a fluent way.
|
||||
*
|
||||
* @param <T> the entity type to upsert
|
||||
*/
|
||||
interface ExecutableUpsertById<T> extends UpsertByIdWithExpiry<T> {}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* Copyright 2012-2022 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;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Collection;
|
||||
|
||||
import org.springframework.data.couchbase.core.ReactiveUpsertByIdOperationSupport.ReactiveUpsertByIdSupport;
|
||||
import org.springframework.data.couchbase.core.query.OptionsBuilder;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.couchbase.client.core.msg.kv.DurabilityLevel;
|
||||
import com.couchbase.client.java.kv.PersistTo;
|
||||
import com.couchbase.client.java.kv.ReplicateTo;
|
||||
import com.couchbase.client.java.kv.UpsertOptions;
|
||||
|
||||
public class ExecutableUpsertByIdOperationSupport implements ExecutableUpsertByIdOperation {
|
||||
|
||||
private final CouchbaseTemplate template;
|
||||
|
||||
public ExecutableUpsertByIdOperationSupport(final CouchbaseTemplate template) {
|
||||
this.template = template;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> ExecutableUpsertById<T> upsertById(final Class<T> domainType) {
|
||||
Assert.notNull(domainType, "DomainType must not be null!");
|
||||
return new ExecutableUpsertByIdSupport<>(template, domainType, OptionsBuilder.getScopeFrom(domainType),
|
||||
OptionsBuilder.getCollectionFrom(domainType), null, PersistTo.NONE, ReplicateTo.NONE, DurabilityLevel.NONE,
|
||||
null);
|
||||
}
|
||||
|
||||
static class ExecutableUpsertByIdSupport<T> implements ExecutableUpsertById<T> {
|
||||
|
||||
private final CouchbaseTemplate template;
|
||||
private final Class<T> domainType;
|
||||
private final String scope;
|
||||
private final String collection;
|
||||
private final UpsertOptions options;
|
||||
private final PersistTo persistTo;
|
||||
private final ReplicateTo replicateTo;
|
||||
private final DurabilityLevel durabilityLevel;
|
||||
private final Duration expiry;
|
||||
private final ReactiveUpsertByIdSupport<T> reactiveSupport;
|
||||
|
||||
ExecutableUpsertByIdSupport(final CouchbaseTemplate template, final Class<T> domainType, final String scope,
|
||||
final String collection, final UpsertOptions options, final PersistTo persistTo, final ReplicateTo replicateTo,
|
||||
final DurabilityLevel durabilityLevel, final Duration expiry) {
|
||||
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.expiry = expiry;
|
||||
this.reactiveSupport = new ReactiveUpsertByIdSupport<>(template.reactive(), domainType, scope, collection,
|
||||
options, persistTo, replicateTo, durabilityLevel, expiry, new NonReactiveSupportWrapper(template.support()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public T one(final T object) {
|
||||
return reactiveSupport.one(object).block();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<? extends T> all(Collection<? extends T> objects) {
|
||||
return reactiveSupport.all(objects).collectList().block();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TerminatingUpsertById<T> withOptions(final UpsertOptions options) {
|
||||
Assert.notNull(options, "Options must not be null.");
|
||||
return new ExecutableUpsertByIdSupport<>(template, domainType, scope, collection, options, persistTo, replicateTo,
|
||||
durabilityLevel, expiry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UpsertByIdInCollection<T> inScope(final String scope) {
|
||||
return new ExecutableUpsertByIdSupport<>(template, domainType, scope != null ? scope : this.scope, collection,
|
||||
options, persistTo, replicateTo, durabilityLevel, expiry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UpsertByIdWithOptions<T> inCollection(final String collection) {
|
||||
return new ExecutableUpsertByIdSupport<>(template, domainType, scope,
|
||||
collection != null ? collection : this.collection, options, persistTo, replicateTo, durabilityLevel, expiry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UpsertByIdInScope<T> withDurability(final DurabilityLevel durabilityLevel) {
|
||||
Assert.notNull(durabilityLevel, "Durability Level must not be null.");
|
||||
return new ExecutableUpsertByIdSupport<>(template, domainType, scope, collection, options, persistTo, replicateTo,
|
||||
durabilityLevel, expiry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UpsertByIdInScope<T> 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 ExecutableUpsertByIdSupport<>(template, domainType, scope, collection, options, persistTo, replicateTo,
|
||||
durabilityLevel, expiry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UpsertByIdWithDurability<T> withExpiry(final Duration expiry) {
|
||||
Assert.notNull(expiry, "expiry must not be null.");
|
||||
return new ExecutableUpsertByIdSupport<>(template, domainType, scope, collection, options, persistTo, replicateTo,
|
||||
durabilityLevel, expiry);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* The fluent couchbase operations combines all different possible operations for simplicity reasons.
|
||||
*/
|
||||
public interface FluentCouchbaseOperations extends ExecutableUpsertByIdOperation, ExecutableInsertByIdOperation,
|
||||
ExecutableReplaceByIdOperation, ExecutableFindByIdOperation, ExecutableFindFromReplicasByIdOperation,
|
||||
ExecutableFindByQueryOperation, ExecutableFindByAnalyticsOperation, ExecutableExistsByIdOperation,
|
||||
ExecutableRemoveByIdOperation, ExecutableRemoveByQueryOperation {}
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright 2021-2022 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;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.data.couchbase.core.convert.translation.TranslationService;
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
|
||||
import org.springframework.data.couchbase.transaction.CouchbaseResourceHolder;
|
||||
|
||||
/**
|
||||
* Wrapper of {@link TemplateSupport} methods to adapt them to {@link ReactiveTemplateSupport}.
|
||||
*
|
||||
* @author Carlos Espinaco
|
||||
* @author Michael Reiche
|
||||
* @since 4.2
|
||||
*/
|
||||
public class NonReactiveSupportWrapper implements ReactiveTemplateSupport {
|
||||
|
||||
private final TemplateSupport support;
|
||||
|
||||
public NonReactiveSupportWrapper(TemplateSupport support) {
|
||||
this.support = support;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<CouchbaseDocument> encodeEntity(Object entityToEncode) {
|
||||
return Mono.fromSupplier(() -> support.encodeEntity(entityToEncode));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Mono<T> decodeEntity(Object id, String source, Long cas, Class<T> entityClass, String scope, String collection,
|
||||
Object txResultHolder, CouchbaseResourceHolder holder) {
|
||||
return Mono.fromSupplier(() -> support.decodeEntity(id, source, cas, entityClass, scope, collection, txResultHolder, holder));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Mono<T> applyResult(T entity, CouchbaseDocument converted, Object id, Long cas,
|
||||
Object txResultHolder, CouchbaseResourceHolder holder) {
|
||||
return Mono.fromSupplier(() -> support.applyResult(entity, converted, id, cas, txResultHolder, holder));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Long getCas(Object entity) {
|
||||
return support.getCas(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getId(Object entity) {
|
||||
return support.getId(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getJavaNameForEntity(Class<?> clazz) {
|
||||
return support.getJavaNameForEntity(clazz);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TranslationService getTranslationService() {
|
||||
return support.getTranslationService();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import org.springframework.dao.TransientDataAccessException;
|
||||
|
||||
/**
|
||||
* Data Access Exception that identifies Operations cancelled while being processed.
|
||||
*
|
||||
* @author Michael Nitschinger
|
||||
*/
|
||||
public class OperationCancellationException extends TransientDataAccessException {
|
||||
|
||||
/**
|
||||
* Constructor for OperationCancellationException.
|
||||
*
|
||||
* @param msg the detail message
|
||||
*/
|
||||
public OperationCancellationException(final String msg) {
|
||||
super(msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for OperationCancellationException.
|
||||
*
|
||||
* @param msg the detail message
|
||||
* @param cause the root cause from the data access API in use
|
||||
*/
|
||||
public OperationCancellationException(final String msg, final Throwable cause) {
|
||||
super(msg, cause);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import org.springframework.dao.TransientDataAccessException;
|
||||
|
||||
/**
|
||||
* Data Access Exception that identifies Operations interrupted while being processed.
|
||||
*
|
||||
* @author Michael Nitschinger
|
||||
*/
|
||||
public class OperationInterruptedException extends TransientDataAccessException {
|
||||
|
||||
/**
|
||||
* Constructor for OperationInterruptedException.
|
||||
*
|
||||
* @param msg the detail message
|
||||
*/
|
||||
public OperationInterruptedException(final String msg) {
|
||||
super(msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for OperationInterruptedException.
|
||||
*
|
||||
* @param msg the detail message
|
||||
* @param cause the root cause from the data access API in use
|
||||
*/
|
||||
public OperationInterruptedException(final String msg, final Throwable cause) {
|
||||
super(msg, cause);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright 2012-2022 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;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.data.couchbase.CouchbaseClientFactory;
|
||||
import org.springframework.data.couchbase.core.convert.CouchbaseConverter;
|
||||
import org.springframework.data.couchbase.core.query.Query;
|
||||
|
||||
import com.couchbase.client.java.query.QueryScanConsistency;
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
|
||||
/**
|
||||
* Returns the converter used for this template/operations.
|
||||
*/
|
||||
CouchbaseConverter getConverter();
|
||||
|
||||
/**
|
||||
* The name of the bucket used.
|
||||
*/
|
||||
String getBucketName();
|
||||
|
||||
/**
|
||||
* The name of the scope used, null if the default scope is used.
|
||||
*/
|
||||
String getScopeName();
|
||||
|
||||
/**
|
||||
* Returns the underlying client factory.
|
||||
*/
|
||||
CouchbaseClientFactory getCouchbaseClientFactory();
|
||||
|
||||
/**
|
||||
* Save the entity to couchbase.<br>
|
||||
* If there is no version property on the entity class, and this is in a transaction, use insert. <br>
|
||||
* If there is no version property on the entity class, and this is not in a transaction, use upsert. <br>
|
||||
* If there is a version property on the entity class, and it is non-zero, then this is an existing document, use
|
||||
* replace.<br>
|
||||
* Otherwise, there is a version property for the entity, but it is zero or null, use insert. <br>
|
||||
*
|
||||
* @param entity the entity to save in couchbase
|
||||
* @param scopeAndCollection for use by repositories only. these are varargs for the scope and collection.
|
||||
* @param <T> the entity class
|
||||
* @return
|
||||
*/
|
||||
<T> Mono<T> save(T entity, String... scopeAndCollection);
|
||||
|
||||
/**
|
||||
* Returns the count of documents found by the query.
|
||||
* @param query
|
||||
* @param domainType
|
||||
* @param <T>
|
||||
* @return
|
||||
*/
|
||||
<T> Mono<Long> count(Query query, Class<T> domainType);
|
||||
|
||||
/**
|
||||
* @return the default consistency to use for queries
|
||||
*/
|
||||
QueryScanConsistency getConsistency();
|
||||
}
|
||||
@@ -0,0 +1,260 @@
|
||||
/*
|
||||
* Copyright 2012-2022 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;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.dao.support.PersistenceExceptionTranslator;
|
||||
import org.springframework.data.couchbase.CouchbaseClientFactory;
|
||||
import org.springframework.data.couchbase.core.convert.CouchbaseConverter;
|
||||
import org.springframework.data.couchbase.core.convert.translation.JacksonTranslationService;
|
||||
import org.springframework.data.couchbase.core.convert.translation.TranslationService;
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentEntity;
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty;
|
||||
import org.springframework.data.couchbase.core.query.Query;
|
||||
import org.springframework.data.couchbase.core.support.PseudoArgs;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
import com.couchbase.client.java.Collection;
|
||||
import com.couchbase.client.java.query.QueryScanConsistency;
|
||||
|
||||
/**
|
||||
* template class for Reactive Couchbase operations
|
||||
*
|
||||
* @author Michael Nitschinger
|
||||
* @author Michael Reiche
|
||||
* @author Jorge Rodriguez Martin
|
||||
* @author Carlos Espinaco
|
||||
*/
|
||||
public class ReactiveCouchbaseTemplate implements ReactiveCouchbaseOperations, ApplicationContextAware {
|
||||
|
||||
private final CouchbaseClientFactory clientFactory;
|
||||
private final CouchbaseConverter converter;
|
||||
private final PersistenceExceptionTranslator exceptionTranslator;
|
||||
private final ReactiveCouchbaseTemplateSupport templateSupport;
|
||||
private ThreadLocal<PseudoArgs<?>> threadLocalArgs = new ThreadLocal<>();
|
||||
private final QueryScanConsistency scanConsistency;
|
||||
|
||||
public ReactiveCouchbaseTemplate(final CouchbaseClientFactory clientFactory, final CouchbaseConverter converter) {
|
||||
this(clientFactory, converter, new JacksonTranslationService(), null);
|
||||
}
|
||||
|
||||
public ReactiveCouchbaseTemplate(final CouchbaseClientFactory clientFactory, final CouchbaseConverter converter,
|
||||
final TranslationService translationService) {
|
||||
this(clientFactory, converter, translationService, null);
|
||||
}
|
||||
|
||||
public ReactiveCouchbaseTemplate(final CouchbaseClientFactory clientFactory, final CouchbaseConverter converter,
|
||||
final TranslationService translationService, final QueryScanConsistency scanConsistency) {
|
||||
this.clientFactory = clientFactory;
|
||||
this.converter = converter;
|
||||
this.exceptionTranslator = clientFactory.getExceptionTranslator();
|
||||
this.templateSupport = new ReactiveCouchbaseTemplateSupport(this, converter, translationService);
|
||||
this.scanConsistency = scanConsistency;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Mono<T> save(T entity, String... scopeAndCollection) {
|
||||
Assert.notNull(entity, "Entity must not be null!");
|
||||
|
||||
String scope = scopeAndCollection.length > 0 ? scopeAndCollection[0] : null;
|
||||
String collection = scopeAndCollection.length > 1 ? scopeAndCollection[1] : null;
|
||||
return Mono.defer(() -> {
|
||||
final CouchbasePersistentEntity<?> mapperEntity = getConverter().getMappingContext()
|
||||
.getPersistentEntity(entity.getClass());
|
||||
final CouchbasePersistentProperty versionProperty = mapperEntity.getVersionProperty();
|
||||
final boolean versionPresent = versionProperty != null;
|
||||
final Long version = versionProperty == null || versionProperty.getField() == null ? null
|
||||
: (Long) ReflectionUtils.getField(versionProperty.getField(),
|
||||
entity);
|
||||
final boolean existingDocument = version != null && version > 0;
|
||||
|
||||
Class clazz = entity.getClass();
|
||||
|
||||
if (!versionPresent) { // the entity doesn't have a version property
|
||||
// No version field - no cas
|
||||
// If in a transaction, insert is the only thing that will work
|
||||
return TransactionalSupport.checkForTransactionInThreadLocalStorage()
|
||||
.flatMap(ctx -> {
|
||||
if (ctx.isPresent()) {
|
||||
return (Mono<T>) insertById(clazz).inScope(scope)
|
||||
.inCollection(collection)
|
||||
.one(entity);
|
||||
} else { // if not in a tx, then upsert will work
|
||||
return (Mono<T>) upsertById(clazz).inScope(scope)
|
||||
.inCollection(collection)
|
||||
.one(entity);
|
||||
}
|
||||
});
|
||||
} else if (existingDocument) { // there is a version property, and it is non-zero
|
||||
// Updating existing document with cas
|
||||
return (Mono<T>) replaceById(clazz).inScope(scope)
|
||||
.inCollection(collection)
|
||||
.one(entity);
|
||||
} else { // there is a version property, but it's zero or not set.
|
||||
// Creating new document
|
||||
return (Mono<T>) insertById(clazz).inScope(scope)
|
||||
.inCollection(collection)
|
||||
.one(entity);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public <T> Mono<Long> count(Query query, Class<T> domainType) {
|
||||
return findByQuery(domainType).matching(query).all().count();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> ReactiveFindById<T> findById(Class<T> domainType) {
|
||||
return new ReactiveFindByIdOperationSupport(this).findById(domainType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReactiveExistsById existsById() {
|
||||
return existsById(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReactiveExistsById existsById(Class<?> domainType) {
|
||||
return new ReactiveExistsByIdOperationSupport(this).existsById(domainType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> ReactiveFindByAnalytics<T> findByAnalytics(Class<T> domainType) {
|
||||
return new ReactiveFindByAnalyticsOperationSupport(this).findByAnalytics(domainType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> ReactiveFindByQuery<T> findByQuery(Class<T> domainType) {
|
||||
return new ReactiveFindByQueryOperationSupport(this).findByQuery(domainType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> ReactiveFindFromReplicasById<T> findFromReplicasById(Class<T> domainType) {
|
||||
return new ReactiveFindFromReplicasByIdOperationSupport(this).findFromReplicasById(domainType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> ReactiveInsertById<T> insertById(Class<T> domainType) {
|
||||
return new ReactiveInsertByIdOperationSupport(this).insertById(domainType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReactiveRemoveById removeById() {
|
||||
return removeById(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReactiveRemoveById removeById(Class<?> domainType) {
|
||||
return new ReactiveRemoveByIdOperationSupport(this).removeById(domainType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> ReactiveRemoveByQuery<T> removeByQuery(Class<T> domainType) {
|
||||
return new ReactiveRemoveByQueryOperationSupport(this).removeByQuery(domainType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> ReactiveReplaceById<T> replaceById(Class<T> domainType) {
|
||||
return new ReactiveReplaceByIdOperationSupport(this).replaceById(domainType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> ReactiveUpsertById<T> upsertById(Class<T> domainType) {
|
||||
return new ReactiveUpsertByIdOperationSupport(this).upsertById(domainType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBucketName() {
|
||||
return clientFactory.getBucket().name();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getScopeName() {
|
||||
return clientFactory.getScope().name();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CouchbaseClientFactory getCouchbaseClientFactory() {
|
||||
return clientFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides access to a {@link Collection} on the configured {@link CouchbaseClientFactory}.
|
||||
*
|
||||
* @param collectionName the name of the collection, if null is passed in the default collection is assumed.
|
||||
* @return the collection instance.
|
||||
*/
|
||||
public Collection getCollection(final String collectionName) {
|
||||
return clientFactory.getCollection(collectionName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CouchbaseConverter getConverter() {
|
||||
return converter;
|
||||
}
|
||||
|
||||
public ReactiveTemplateSupport support() {
|
||||
return templateSupport;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to convert the given {@link RuntimeException} into a {@link DataAccessException} but returns the original
|
||||
* exception if the conversation failed. Thus allows safe re-throwing of the return value.
|
||||
*
|
||||
* @param ex the exception to translate
|
||||
*/
|
||||
RuntimeException potentiallyConvertRuntimeException(final RuntimeException ex) {
|
||||
RuntimeException resolved = exceptionTranslator != null ? exceptionTranslator.translateExceptionIfPossible(ex)
|
||||
: null;
|
||||
return resolved == null ? ex : resolved;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(final ApplicationContext applicationContext) throws BeansException {
|
||||
templateSupport.setApplicationContext(applicationContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the pseudoArgs from the ThreadLocal field
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public QueryScanConsistency getConsistency() {
|
||||
return scanConsistency;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
* Copyright 2012-2022 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;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.data.couchbase.core.convert.CouchbaseConverter;
|
||||
import org.springframework.data.couchbase.core.convert.translation.TranslationService;
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
|
||||
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.ReactiveAfterConvertCallback;
|
||||
import org.springframework.data.couchbase.core.mapping.event.ReactiveBeforeConvertCallback;
|
||||
import org.springframework.data.couchbase.transaction.CouchbaseResourceHolder;
|
||||
import org.springframework.data.mapping.callback.EntityCallbacks;
|
||||
import org.springframework.data.mapping.callback.ReactiveEntityCallbacks;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Internal encode/decode support for {@link ReactiveCouchbaseTemplate}.
|
||||
*
|
||||
* @author Carlos Espinaco
|
||||
* @author Michael Reiche
|
||||
* @since 4.2
|
||||
*/
|
||||
class ReactiveCouchbaseTemplateSupport extends AbstractTemplateSupport
|
||||
implements ApplicationContextAware, ReactiveTemplateSupport {
|
||||
|
||||
private final ReactiveCouchbaseTemplate template;
|
||||
private ReactiveEntityCallbacks reactiveEntityCallbacks;
|
||||
|
||||
public ReactiveCouchbaseTemplateSupport(final ReactiveCouchbaseTemplate template, final CouchbaseConverter converter,
|
||||
final TranslationService translationService) {
|
||||
super(template, converter, translationService);
|
||||
this.template = template;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<CouchbaseDocument> encodeEntity(final Object entityToEncode) {
|
||||
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 BeforeSaveEvent<>(entityToEncode, converted)));
|
||||
}
|
||||
|
||||
@Override
|
||||
ReactiveCouchbaseTemplate getReactiveTemplate() {
|
||||
return template;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Mono<T> decodeEntity(Object id, String source, Long cas, Class<T> entityClass, String scope,
|
||||
String collection, Object txResultHolder, CouchbaseResourceHolder holder) {
|
||||
return Mono
|
||||
.fromSupplier(() -> decodeEntityBase(id, source, cas, entityClass, scope, collection, txResultHolder, holder));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Mono<T> applyResult(T entity, CouchbaseDocument converted, Object id, Long cas,
|
||||
Object txResultHolder, CouchbaseResourceHolder holder) {
|
||||
return Mono.fromSupplier(() -> applyResultBase(entity, converted, id, cas, txResultHolder, holder));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
this.applicationContext = applicationContext;
|
||||
if (reactiveEntityCallbacks == null) {
|
||||
setReactiveEntityCallbacks(ReactiveEntityCallbacks.create(applicationContext));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link ReactiveEntityCallbacks} instance to use when invoking
|
||||
* {@link org.springframework.data.mapping.callback.ReactiveEntityCallbacks callbacks} like the
|
||||
* {@link ReactiveBeforeConvertCallback}.
|
||||
* <p>
|
||||
* Overrides potentially existing {@link EntityCallbacks}.
|
||||
*
|
||||
* @param reactiveEntityCallbacks must not be {@literal null}.
|
||||
* @throws IllegalArgumentException if the given instance is {@literal null}.
|
||||
*/
|
||||
public void setReactiveEntityCallbacks(ReactiveEntityCallbacks reactiveEntityCallbacks) {
|
||||
Assert.notNull(reactiveEntityCallbacks, "EntityCallbacks must not be null!");
|
||||
this.reactiveEntityCallbacks = reactiveEntityCallbacks;
|
||||
}
|
||||
|
||||
protected <T> Mono<T> maybeCallBeforeConvert(T object, String collection) {
|
||||
if (reactiveEntityCallbacks != null) {
|
||||
return reactiveEntityCallbacks.callback(ReactiveBeforeConvertCallback.class, object, collection);
|
||||
} else {
|
||||
LOG.info("maybeCallBeforeConvert called, but ReactiveCouchbaseTemplate not initialized with applicationContext");
|
||||
}
|
||||
return Mono.just(object);
|
||||
}
|
||||
|
||||
protected <T> Mono<T> maybeCallAfterConvert(T object, CouchbaseDocument document, String collection) {
|
||||
if (null != reactiveEntityCallbacks) {
|
||||
return reactiveEntityCallbacks.callback(ReactiveAfterConvertCallback.class, object, document, collection);
|
||||
} else {
|
||||
LOG.info("maybeCallAfterConvert called, but ReactiveCouchbaseTemplate not initialized with applicationContext");
|
||||
}
|
||||
return Mono.just(object);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.data.couchbase.core.support.InCollection;
|
||||
import org.springframework.data.couchbase.core.support.InScope;
|
||||
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
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.0
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
interface TerminatingExistsById extends OneAndAllExistsReactive {
|
||||
|
||||
/**
|
||||
* Performs the operation on the ID given.
|
||||
*
|
||||
* @param id the ID to perform the operation on.
|
||||
* @return true if the document exists, false otherwise.
|
||||
*/
|
||||
@Override
|
||||
Mono<Boolean> one(String id);
|
||||
|
||||
/**
|
||||
* Performs the operation on the collection of ids.
|
||||
*
|
||||
* @param ids the ids to check.
|
||||
* @return a map consisting of the document IDs as the keys and if they exist as the value.
|
||||
*/
|
||||
@Override
|
||||
Mono<Map<String, Boolean>> all(Collection<String> ids);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Fluent method to specify options.
|
||||
*/
|
||||
interface ExistsByIdWithOptions extends TerminatingExistsById, WithExistsOptions {
|
||||
/**
|
||||
* Fluent method to specify options to use for execution.
|
||||
*
|
||||
* @param options to use for execution
|
||||
*/
|
||||
@Override
|
||||
TerminatingExistsById withOptions(ExistsOptions options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fluent method to specify the collection.
|
||||
*/
|
||||
interface ExistsByIdInCollection extends ExistsByIdWithOptions, InCollection {
|
||||
/**
|
||||
* With a different collection
|
||||
*
|
||||
* @param collection the collection to use.
|
||||
*/
|
||||
@Override
|
||||
ExistsByIdWithOptions inCollection(String collection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fluent method to specify the scope.
|
||||
*/
|
||||
interface ExistsByIdInScope extends ExistsByIdInCollection, InScope {
|
||||
/**
|
||||
* With a different scope
|
||||
*
|
||||
* @param scope the scope to use.
|
||||
*/
|
||||
@Override
|
||||
ExistsByIdInCollection inScope(String scope);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides methods for constructing KV exists operations in a fluent way.
|
||||
*/
|
||||
interface ReactiveExistsById extends ExistsByIdInScope {}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* Copyright 2012-2022 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;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.util.function.Tuple2;
|
||||
import reactor.util.function.Tuples;
|
||||
|
||||
import java.util.Collection;
|
||||
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;
|
||||
|
||||
import com.couchbase.client.java.kv.ExistsOptions;
|
||||
import com.couchbase.client.java.kv.ExistsResult;
|
||||
|
||||
/**
|
||||
* ReactiveExistsById Support
|
||||
*
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
public class ReactiveExistsByIdOperationSupport implements ReactiveExistsByIdOperation {
|
||||
|
||||
private final ReactiveCouchbaseTemplate template;
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ReactiveExistsByIdOperationSupport.class);
|
||||
|
||||
ReactiveExistsByIdOperationSupport(ReactiveCouchbaseTemplate template) {
|
||||
this.template = template;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public ReactiveExistsById existsById() {
|
||||
return existsById(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReactiveExistsById existsById(Class<?> domainType) {
|
||||
return new ReactiveExistsByIdSupport(template, domainType, OptionsBuilder.getScopeFrom(domainType),
|
||||
OptionsBuilder.getCollectionFrom(domainType), null);
|
||||
}
|
||||
|
||||
static class ReactiveExistsByIdSupport implements ReactiveExistsById {
|
||||
|
||||
private final ReactiveCouchbaseTemplate template;
|
||||
private final Class<?> domainType;
|
||||
private final String scope;
|
||||
private final String collection;
|
||||
private final ExistsOptions options;
|
||||
|
||||
ReactiveExistsByIdSupport(final ReactiveCouchbaseTemplate template, final Class<?> domainType, final String scope,
|
||||
final String collection, final ExistsOptions options) {
|
||||
this.template = template;
|
||||
this.domainType = domainType;
|
||||
this.scope = scope;
|
||||
this.collection = collection;
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Boolean> one(final String id) {
|
||||
PseudoArgs<ExistsOptions> pArgs = new PseudoArgs<>(template, scope, collection, options, domainType);
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("existsById key={} {}", id, pArgs);
|
||||
}
|
||||
return TransactionalSupport.verifyNotInTransaction("existsById").then(Mono.just(id))
|
||||
.flatMap(docId -> template.getCouchbaseClientFactory().withScope(pArgs.getScope())
|
||||
.getCollection(pArgs.getCollection()).reactive().exists(id, buildOptions(pArgs.getOptions()))
|
||||
.map(ExistsResult::exists))
|
||||
.onErrorMap(throwable -> {
|
||||
if (throwable instanceof RuntimeException) {
|
||||
return template.potentiallyConvertRuntimeException((RuntimeException) throwable);
|
||||
} else {
|
||||
return throwable;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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)))
|
||||
.collectMap(Tuple2::getT1, Tuple2::getT2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExistsByIdWithOptions inCollection(final String collection) {
|
||||
return new ReactiveExistsByIdSupport(template, domainType, scope,
|
||||
collection != null ? collection : this.collection, options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TerminatingExistsById withOptions(final ExistsOptions options) {
|
||||
Assert.notNull(options, "Options must not be null.");
|
||||
return new ReactiveExistsByIdSupport(template, domainType, scope, collection, options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExistsByIdInCollection inScope(final String scope) {
|
||||
return new ReactiveExistsByIdSupport(template, domainType, scope != null ? scope : this.scope, collection,
|
||||
options);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.dao.IncorrectResultSizeDataAccessException;
|
||||
import org.springframework.data.couchbase.core.query.AnalyticsQuery;
|
||||
import org.springframework.data.couchbase.core.support.InCollection;
|
||||
import org.springframework.data.couchbase.core.support.InScope;
|
||||
import org.springframework.data.couchbase.core.support.OneAndAllReactive;
|
||||
import org.springframework.data.couchbase.core.support.WithAnalyticsConsistency;
|
||||
import org.springframework.data.couchbase.core.support.WithAnalyticsOptions;
|
||||
import org.springframework.data.couchbase.core.support.WithAnalyticsQuery;
|
||||
|
||||
import com.couchbase.client.java.analytics.AnalyticsOptions;
|
||||
import com.couchbase.client.java.analytics.AnalyticsScanConsistency;
|
||||
|
||||
/**
|
||||
* FindByAnalytics Operations
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.0
|
||||
*/
|
||||
public interface ReactiveFindByAnalyticsOperation {
|
||||
|
||||
/**
|
||||
* Queries the analytics service.
|
||||
*
|
||||
* @param domainType the entity type to use for the results.
|
||||
*/
|
||||
<T> ReactiveFindByAnalytics<T> findByAnalytics(Class<T> domainType);
|
||||
|
||||
/**
|
||||
* Terminating operations invoking the actual execution.
|
||||
*/
|
||||
interface TerminatingFindByAnalytics<T> extends OneAndAllReactive {
|
||||
|
||||
/**
|
||||
* Get exactly zero or one result.
|
||||
*
|
||||
* @return a mono with the match if found (an empty one otherwise).
|
||||
* @throws IncorrectResultSizeDataAccessException if more than one match found.
|
||||
*/
|
||||
Mono<T> one();
|
||||
|
||||
/**
|
||||
* Get the first or no result.
|
||||
*
|
||||
* @return the first or an empty mono if none found.
|
||||
*/
|
||||
Mono<T> first();
|
||||
|
||||
/**
|
||||
* Get all matching elements.
|
||||
*
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
Flux<T> all();
|
||||
|
||||
/**
|
||||
* Get the number of matching elements.
|
||||
*
|
||||
* @return total number of matching elements.
|
||||
*/
|
||||
Mono<Long> count();
|
||||
|
||||
/**
|
||||
* Check for the presence of matching elements.
|
||||
*
|
||||
* @return {@literal true} if at least one matching element exists.
|
||||
*/
|
||||
Mono<Boolean> exists();
|
||||
|
||||
}
|
||||
|
||||
interface FindByAnalyticsWithQuery<T> extends TerminatingFindByAnalytics<T>, WithAnalyticsQuery<T> {
|
||||
|
||||
/**
|
||||
* Set the filter for the analytics query to be used.
|
||||
*
|
||||
* @param query must not be {@literal null}.
|
||||
* @throws IllegalArgumentException if query is {@literal null}.
|
||||
*/
|
||||
TerminatingFindByAnalytics<T> matching(AnalyticsQuery query);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Fluent method to specify options.
|
||||
*
|
||||
* @param <T> the entity type to use.
|
||||
*/
|
||||
interface FindByAnalyticsWithOptions<T> extends FindByAnalyticsWithQuery<T>, WithAnalyticsOptions<T> {
|
||||
/**
|
||||
* Fluent method to specify options to use for execution
|
||||
*
|
||||
* @param options to use for execution
|
||||
*/
|
||||
@Override
|
||||
TerminatingFindByAnalytics<T> withOptions(AnalyticsOptions options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fluent method to specify the collection.
|
||||
*
|
||||
* @param <T> the entity type to use for the results.
|
||||
*/
|
||||
interface FindByAnalyticsInCollection<T> extends FindByAnalyticsWithOptions<T>, InCollection<T> {
|
||||
/**
|
||||
* With a different collection
|
||||
*
|
||||
* @param collection the collection to use.
|
||||
*/
|
||||
@Override
|
||||
FindByAnalyticsWithOptions<T> inCollection(String collection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fluent method to specify the scope.
|
||||
*
|
||||
* @param <T> the entity type to use for the results.
|
||||
*/
|
||||
interface FindByAnalyticsInScope<T> extends FindByAnalyticsInCollection<T>, InScope<T> {
|
||||
/**
|
||||
* With a different scope
|
||||
*
|
||||
* @param scope the scope to use.
|
||||
*/
|
||||
@Override
|
||||
FindByAnalyticsInCollection<T> inScope(String scope);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
interface FindByAnalyticsConsistentWith<T> extends FindByAnalyticsInScope<T> {
|
||||
|
||||
/**
|
||||
* Allows to override the default scan consistency.
|
||||
*
|
||||
* @param scanConsistency the custom scan consistency to use for this analytics query.
|
||||
*/
|
||||
@Deprecated
|
||||
FindByAnalyticsWithQuery<T> consistentWith(AnalyticsScanConsistency scanConsistency);
|
||||
|
||||
}
|
||||
|
||||
interface FindByAnalyticsWithConsistency<T> extends FindByAnalyticsInScope<T>, WithAnalyticsConsistency<T> {
|
||||
|
||||
/**
|
||||
* Allows to override the default scan consistency.
|
||||
*
|
||||
* @param scanConsistency the custom scan consistency to use for this analytics query.
|
||||
*/
|
||||
@Override
|
||||
FindByAnalyticsWithQuery<T> withConsistency(AnalyticsScanConsistency scanConsistency);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Result type override (Optional).
|
||||
*/
|
||||
interface FindByAnalyticsWithProjection<T> extends FindByAnalyticsWithConsistency<T> {
|
||||
|
||||
/**
|
||||
* Define the target type fields should be mapped to. <br />
|
||||
* Skip this step if you are anyway only interested in the original domain type.
|
||||
*
|
||||
* @param returnType must not be {@literal null}.
|
||||
* @return new instance of {@link FindByAnalyticsWithConsistency}.
|
||||
* @throws IllegalArgumentException if returnType is {@literal null}.
|
||||
*/
|
||||
<R> FindByAnalyticsWithConsistency<R> as(Class<R> returnType);
|
||||
}
|
||||
|
||||
interface ReactiveFindByAnalytics<T> extends FindByAnalyticsWithProjection<T>, FindByAnalyticsConsistentWith<T> {}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
/*
|
||||
* Copyright 2012-2022 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;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.data.couchbase.core.query.AnalyticsQuery;
|
||||
import org.springframework.data.couchbase.core.query.OptionsBuilder;
|
||||
import org.springframework.data.couchbase.core.support.TemplateUtils;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.couchbase.client.java.analytics.AnalyticsOptions;
|
||||
import com.couchbase.client.java.analytics.AnalyticsScanConsistency;
|
||||
import com.couchbase.client.java.analytics.ReactiveAnalyticsResult;
|
||||
|
||||
public class ReactiveFindByAnalyticsOperationSupport implements ReactiveFindByAnalyticsOperation {
|
||||
|
||||
private static final AnalyticsQuery ALL_QUERY = new AnalyticsQuery();
|
||||
|
||||
private final ReactiveCouchbaseTemplate template;
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ReactiveFindByAnalyticsOperationSupport.class);
|
||||
|
||||
public ReactiveFindByAnalyticsOperationSupport(final ReactiveCouchbaseTemplate template) {
|
||||
this.template = template;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> ReactiveFindByAnalytics<T> findByAnalytics(final Class<T> domainType) {
|
||||
return new ReactiveFindByAnalyticsSupport<>(template, domainType, domainType, ALL_QUERY, null,
|
||||
OptionsBuilder.getScopeFrom(domainType), OptionsBuilder.getCollectionFrom(domainType), null,
|
||||
template.support());
|
||||
}
|
||||
|
||||
static class ReactiveFindByAnalyticsSupport<T> implements ReactiveFindByAnalytics<T> {
|
||||
|
||||
private final ReactiveCouchbaseTemplate template;
|
||||
private final Class<?> domainType;
|
||||
private final Class<T> returnType;
|
||||
private final AnalyticsQuery query;
|
||||
private final AnalyticsScanConsistency scanConsistency;
|
||||
private final String scope;
|
||||
private final String collection;
|
||||
private final AnalyticsOptions options;
|
||||
private final ReactiveTemplateSupport support;
|
||||
|
||||
ReactiveFindByAnalyticsSupport(final ReactiveCouchbaseTemplate template, final Class<?> domainType,
|
||||
final Class<T> returnType, final AnalyticsQuery query, final AnalyticsScanConsistency scanConsistency,
|
||||
String scope, String collection, AnalyticsOptions options, ReactiveTemplateSupport support) {
|
||||
this.template = template;
|
||||
this.domainType = domainType;
|
||||
this.returnType = returnType;
|
||||
this.query = query;
|
||||
this.scanConsistency = scanConsistency;
|
||||
this.scope = scope;
|
||||
this.collection = collection;
|
||||
this.options = options;
|
||||
this.support = support;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TerminatingFindByAnalytics<T> matching(AnalyticsQuery query) {
|
||||
return new ReactiveFindByAnalyticsSupport<>(template, domainType, returnType, query, scanConsistency, scope,
|
||||
collection, options, support);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public FindByAnalyticsWithQuery<T> consistentWith(AnalyticsScanConsistency scanConsistency) {
|
||||
return new ReactiveFindByAnalyticsSupport<>(template, domainType, returnType, query, scanConsistency, scope,
|
||||
collection, options, support);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FindByAnalyticsWithQuery<T> withConsistency(AnalyticsScanConsistency scanConsistency) {
|
||||
return new ReactiveFindByAnalyticsSupport<>(template, domainType, returnType, query, scanConsistency, scope,
|
||||
collection, options, support);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <R> FindByAnalyticsWithConsistency<R> as(final Class<R> returnType) {
|
||||
Assert.notNull(returnType, "returnType must not be null!");
|
||||
return new ReactiveFindByAnalyticsSupport<>(template, domainType, returnType, query, scanConsistency, scope,
|
||||
collection, options, support);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<T> one() {
|
||||
return all().singleOrEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<T> first() {
|
||||
return all().next();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<T> all() {
|
||||
return Flux.defer(() -> {
|
||||
String statement = assembleEntityQuery(false);
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("findByAnalytics statement: {}", statement);
|
||||
}
|
||||
return TransactionalSupport.verifyNotInTransaction("findByAnalytics").then(template.getCouchbaseClientFactory()
|
||||
.getCluster().reactive().analyticsQuery(statement, buildAnalyticsOptions())).onErrorMap(throwable -> {
|
||||
if (throwable instanceof RuntimeException) {
|
||||
return template.potentiallyConvertRuntimeException((RuntimeException) throwable);
|
||||
} else {
|
||||
return throwable;
|
||||
}
|
||||
}).flatMapMany(ReactiveAnalyticsResult::rowsAsObject).flatMap(row -> {
|
||||
String id = null;
|
||||
Long cas = null;
|
||||
id = row.getString(TemplateUtils.SELECT_ID);
|
||||
if (id == null) {
|
||||
id = row.getString(TemplateUtils.SELECT_ID_3x);
|
||||
row.removeKey(TemplateUtils.SELECT_ID_3x);
|
||||
}
|
||||
cas = row.getLong(TemplateUtils.SELECT_CAS);
|
||||
if (cas == null) {
|
||||
cas = row.getLong(TemplateUtils.SELECT_CAS_3x);
|
||||
row.removeKey(TemplateUtils.SELECT_CAS_3x);
|
||||
}
|
||||
row.removeKey(TemplateUtils.SELECT_ID);
|
||||
row.removeKey(TemplateUtils.SELECT_CAS);
|
||||
return support.decodeEntity(id, row.toString(), cas, returnType, null, null, null, null);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Long> count() {
|
||||
return Mono.defer(() -> {
|
||||
String statement = assembleEntityQuery(true);
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("findByAnalytics statement: {}", statement);
|
||||
}
|
||||
return TransactionalSupport.verifyNotInTransaction("findByAnalytics").then(template.getCouchbaseClientFactory()
|
||||
.getCluster().reactive().analyticsQuery(statement, buildAnalyticsOptions())).onErrorMap(throwable -> {
|
||||
if (throwable instanceof RuntimeException) {
|
||||
return template.potentiallyConvertRuntimeException((RuntimeException) throwable);
|
||||
} else {
|
||||
return throwable;
|
||||
}
|
||||
}).flatMapMany(ReactiveAnalyticsResult::rowsAsObject)
|
||||
.map(row -> row.getLong(row.getNames().iterator().next())).next();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Boolean> exists() {
|
||||
return count().map(count -> count > 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TerminatingFindByAnalytics<T> withOptions(final AnalyticsOptions options) {
|
||||
Assert.notNull(options, "Options must not be null.");
|
||||
return new ReactiveFindByAnalyticsSupport<>(template, domainType, returnType, query, scanConsistency, scope,
|
||||
collection, options, support);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FindByAnalyticsInCollection<T> inScope(final String scope) {
|
||||
return new ReactiveFindByAnalyticsSupport<>(template, domainType, returnType, query, scanConsistency,
|
||||
scope != null ? scope : this.scope, collection, options, support);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FindByAnalyticsWithConsistency<T> inCollection(final String collection) {
|
||||
return new ReactiveFindByAnalyticsSupport<>(template, domainType, returnType, query, scanConsistency, scope,
|
||||
collection != null ? collection : this.collection, options, support);
|
||||
}
|
||||
|
||||
private String assembleEntityQuery(final boolean count) {
|
||||
final String bucket = "`" + template.getBucketName() + "`";
|
||||
|
||||
final StringBuilder statement = new StringBuilder("SELECT ");
|
||||
if (count) {
|
||||
statement.append("count(*) as __count");
|
||||
} else {
|
||||
statement.append("meta().id as __id, meta().cas as __cas, ").append(bucket).append(".*");
|
||||
}
|
||||
|
||||
final String dataset = support.getJavaNameForEntity(domainType);
|
||||
statement.append(" FROM ").append(dataset);
|
||||
|
||||
query.appendSort(statement);
|
||||
query.appendSkipAndLimit(statement);
|
||||
return statement.toString();
|
||||
}
|
||||
|
||||
private AnalyticsOptions buildAnalyticsOptions() {
|
||||
final AnalyticsOptions options = AnalyticsOptions.analyticsOptions();
|
||||
if (scanConsistency != null) {
|
||||
options.scanConsistency(scanConsistency);
|
||||
}
|
||||
return options;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Collection;
|
||||
|
||||
import org.springframework.data.couchbase.core.support.InCollection;
|
||||
import org.springframework.data.couchbase.core.support.InScope;
|
||||
import org.springframework.data.couchbase.core.support.OneAndAllIdReactive;
|
||||
import org.springframework.data.couchbase.core.support.WithExpiry;
|
||||
import org.springframework.data.couchbase.core.support.WithGetOptions;
|
||||
import org.springframework.data.couchbase.core.support.WithProjectionId;
|
||||
|
||||
import com.couchbase.client.java.kv.GetOptions;
|
||||
|
||||
/**
|
||||
* Get Operations
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.0
|
||||
*/
|
||||
public interface ReactiveFindByIdOperation {
|
||||
|
||||
/**
|
||||
* Loads a document from a bucket.
|
||||
*
|
||||
* @param domainType the entity type to use for the results.
|
||||
*/
|
||||
<T> ReactiveFindById<T> findById(Class<T> domainType);
|
||||
|
||||
/**
|
||||
* Terminating operations invoking the actual execution.
|
||||
*
|
||||
* @param <T> the entity type to use for the results.
|
||||
*/
|
||||
interface TerminatingFindById<T> extends OneAndAllIdReactive<T> {
|
||||
|
||||
/**
|
||||
* Finds one document based on the given ID.
|
||||
*
|
||||
* @param id the document ID.
|
||||
* @return the entity if found.
|
||||
*/
|
||||
Mono<T> one(Object id);
|
||||
|
||||
/**
|
||||
* Finds a list of documents based on the given IDs.
|
||||
*
|
||||
* @param ids the document ID ids.
|
||||
* @return the list of found entities.
|
||||
*/
|
||||
Flux<? extends T> all(Collection<String> ids);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Fluent method to specify options.
|
||||
*
|
||||
* @param <T> the entity type to use for the results.
|
||||
*/
|
||||
interface FindByIdWithOptions<T> extends TerminatingFindById<T>, WithGetOptions<T> {
|
||||
/**
|
||||
* Fluent method to specify options to use for execution
|
||||
*
|
||||
* @param options options to use for execution
|
||||
*/
|
||||
@Override
|
||||
TerminatingFindById<T> withOptions(GetOptions options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fluent method to specify the collection.
|
||||
*
|
||||
* @param <T> the entity type to use for the results.
|
||||
*/
|
||||
interface FindByIdInCollection<T> extends FindByIdWithOptions<T>, InCollection<T> {
|
||||
/**
|
||||
* With a different collection
|
||||
*
|
||||
* @param collection the collection to use.
|
||||
*/
|
||||
@Override
|
||||
FindByIdWithOptions<T> inCollection(String collection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fluent method to specify the scope.
|
||||
*
|
||||
* @param <T> the entity type to use for the results.
|
||||
*/
|
||||
interface FindByIdInScope<T> extends FindByIdInCollection<T>, InScope<T> {
|
||||
/**
|
||||
* With a different scope
|
||||
*
|
||||
* @param scope the scope to use.
|
||||
*/
|
||||
@Override
|
||||
FindByIdInCollection<T> inScope(String scope);
|
||||
}
|
||||
|
||||
interface FindByIdWithProjection<T> extends FindByIdInScope<T>, WithProjectionId<T> {
|
||||
|
||||
/**
|
||||
* Load only certain fields for the document.
|
||||
*
|
||||
* @param fields the projected fields to load.
|
||||
*/
|
||||
FindByIdInCollection<T> project(String... fields);
|
||||
|
||||
}
|
||||
|
||||
interface FindByIdWithExpiry<T> extends FindByIdWithProjection<T>, WithExpiry<T> {
|
||||
/**
|
||||
* Load only certain fields for the document.
|
||||
*
|
||||
* @param expiry the projected fields to load.
|
||||
*/
|
||||
@Override
|
||||
FindByIdWithProjection<T> withExpiry(Duration expiry);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides methods for constructing query operations in a fluent way.
|
||||
*
|
||||
* @param <T> the entity type to use for the results
|
||||
*/
|
||||
interface ReactiveFindById<T> extends FindByIdWithExpiry<T> {}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
/*
|
||||
* Copyright 2012-2022 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;
|
||||
|
||||
import static com.couchbase.client.java.kv.GetAndTouchOptions.getAndTouchOptions;
|
||||
import static com.couchbase.client.java.transactions.internal.ConverterUtil.makeCollectionIdentifier;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Duration;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentEntity;
|
||||
import org.springframework.data.couchbase.core.query.OptionsBuilder;
|
||||
import org.springframework.data.couchbase.core.support.PseudoArgs;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.couchbase.client.core.error.DocumentNotFoundException;
|
||||
import com.couchbase.client.java.CommonOptions;
|
||||
import com.couchbase.client.java.ReactiveCollection;
|
||||
import com.couchbase.client.java.codec.RawJsonTranscoder;
|
||||
import com.couchbase.client.java.kv.GetAndTouchOptions;
|
||||
import com.couchbase.client.java.kv.GetOptions;
|
||||
|
||||
/**
|
||||
* {@link ReactiveFindByIdOperation} implementations for Couchbase.
|
||||
*
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
public class ReactiveFindByIdOperationSupport implements ReactiveFindByIdOperation {
|
||||
|
||||
private final ReactiveCouchbaseTemplate template;
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ReactiveFindByIdOperationSupport.class);
|
||||
|
||||
ReactiveFindByIdOperationSupport(ReactiveCouchbaseTemplate template) {
|
||||
this.template = template;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> ReactiveFindById<T> findById(Class<T> domainType) {
|
||||
return new ReactiveFindByIdSupport<>(template, domainType, OptionsBuilder.getScopeFrom(domainType),
|
||||
OptionsBuilder.getCollectionFrom(domainType), null, null, null, template.support());
|
||||
}
|
||||
|
||||
static class ReactiveFindByIdSupport<T> implements ReactiveFindById<T> {
|
||||
|
||||
private final ReactiveCouchbaseTemplate template;
|
||||
private final Class<T> domainType;
|
||||
private final String scope;
|
||||
private final String collection;
|
||||
private final CommonOptions<?> options;
|
||||
private final List<String> fields;
|
||||
private final ReactiveTemplateSupport support;
|
||||
private final Duration expiry;
|
||||
|
||||
ReactiveFindByIdSupport(ReactiveCouchbaseTemplate template, Class<T> domainType, String scope, String collection,
|
||||
CommonOptions<?> options, List<String> fields, Duration expiry, ReactiveTemplateSupport support) {
|
||||
this.template = template;
|
||||
this.domainType = domainType;
|
||||
this.scope = scope;
|
||||
this.collection = collection;
|
||||
this.options = options;
|
||||
this.fields = fields;
|
||||
this.expiry = expiry;
|
||||
this.support = support;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<T> one(final Object id) {
|
||||
|
||||
CommonOptions<?> gOptions = initGetOptions();
|
||||
PseudoArgs<?> pArgs = new PseudoArgs(template, scope, collection, gOptions, domainType);
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("findById key={} {}", id, pArgs);
|
||||
}
|
||||
ReactiveCollection rc = template.getCouchbaseClientFactory().withScope(pArgs.getScope())
|
||||
.getCollection(pArgs.getCollection()).reactive();
|
||||
|
||||
Mono<T> reactiveEntity = TransactionalSupport.checkForTransactionInThreadLocalStorage().flatMap(ctxOpt -> {
|
||||
if (!ctxOpt.isPresent()) {
|
||||
if (pArgs.getOptions() instanceof GetAndTouchOptions) {
|
||||
return rc.getAndTouch(id.toString(), expiryToUse(), (GetAndTouchOptions) pArgs.getOptions())
|
||||
.flatMap(result -> support.decodeEntity(id, result.contentAs(String.class), result.cas(), domainType,
|
||||
pArgs.getScope(), pArgs.getCollection(), null, null));
|
||||
} else {
|
||||
return rc.get(id.toString(), (GetOptions) pArgs.getOptions())
|
||||
.flatMap(result -> support.decodeEntity(id, result.contentAs(String.class), result.cas(), domainType,
|
||||
pArgs.getScope(), pArgs.getCollection(), null, null));
|
||||
}
|
||||
} else {
|
||||
rejectInvalidTransactionalOptions();
|
||||
return ctxOpt.get().getCore().get(makeCollectionIdentifier(rc.async()), id.toString())
|
||||
.flatMap(result -> support.decodeEntity(id, new String(result.contentAsBytes(), StandardCharsets.UTF_8),
|
||||
result.cas(), domainType, pArgs.getScope(), pArgs.getCollection(),
|
||||
null, null));
|
||||
}
|
||||
});
|
||||
|
||||
return reactiveEntity.onErrorResume(throwable -> {
|
||||
if (throwable instanceof DocumentNotFoundException) {
|
||||
return Mono.empty();
|
||||
}
|
||||
return Mono.error(throwable);
|
||||
}).onErrorMap(throwable -> {
|
||||
if (throwable instanceof RuntimeException) {
|
||||
return template.potentiallyConvertRuntimeException((RuntimeException) throwable);
|
||||
} else {
|
||||
return throwable;
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private void rejectInvalidTransactionalOptions() {
|
||||
if (this.expiry != null) {
|
||||
throw new IllegalArgumentException("withExpiry is not supported in a transaction");
|
||||
}
|
||||
if (this.options != null) {
|
||||
throw new IllegalArgumentException("withOptions is not supported in a transaction");
|
||||
}
|
||||
if (this.fields != null) {
|
||||
throw new IllegalArgumentException("project is not supported in a transaction");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<? extends T> all(final Collection<String> ids) {
|
||||
return Flux.fromIterable(ids).flatMap(this::one);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FindByIdInScope<T> withOptions(final GetOptions options) {
|
||||
Assert.notNull(options, "Options must not be null.");
|
||||
return new ReactiveFindByIdSupport<>(template, domainType, scope, collection, options, fields, expiry, support);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FindByIdWithOptions<T> inCollection(final String collection) {
|
||||
return new ReactiveFindByIdSupport<>(template, domainType, scope,
|
||||
collection != null ? collection : this.collection, options, fields, expiry, support);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FindByIdInCollection<T> inScope(final String scope) {
|
||||
return new ReactiveFindByIdSupport<>(template, domainType, scope != null ? scope : this.scope, collection,
|
||||
options, fields, expiry, support);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FindByIdInCollection<T> project(String... fields) {
|
||||
Assert.notNull(fields, "Fields must not be null");
|
||||
return new ReactiveFindByIdSupport<>(template, domainType, scope, collection, options, Arrays.asList(fields),
|
||||
expiry, support);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FindByIdWithProjection<T> withExpiry(final Duration expiry) {
|
||||
return new ReactiveFindByIdSupport<>(template, domainType, scope, collection, options, fields, expiry, support);
|
||||
}
|
||||
|
||||
private CommonOptions<?> initGetOptions() {
|
||||
CommonOptions<?> getOptions;
|
||||
if (expiry != null || options instanceof GetAndTouchOptions) {
|
||||
GetAndTouchOptions gOptions = options != null ? (GetAndTouchOptions) options : getAndTouchOptions();
|
||||
if (gOptions.build().transcoder() == null) {
|
||||
gOptions.transcoder(RawJsonTranscoder.INSTANCE);
|
||||
}
|
||||
getOptions = gOptions;
|
||||
} else {
|
||||
GetOptions gOptions = options != null ? (GetOptions) options : GetOptions.getOptions();
|
||||
if (gOptions.build().transcoder() == null) {
|
||||
gOptions.transcoder(RawJsonTranscoder.INSTANCE);
|
||||
}
|
||||
if (fields != null && !fields.isEmpty()) {
|
||||
gOptions.project(fields);
|
||||
}
|
||||
getOptions = gOptions;
|
||||
}
|
||||
return getOptions;
|
||||
}
|
||||
|
||||
private Duration expiryToUse() {
|
||||
Duration expiryToUse = expiry;
|
||||
if (expiryToUse != null || options instanceof GetAndTouchOptions) {
|
||||
if (expiryToUse == null) { // GetAndTouchOptions without specifying expiry -> get expiry from annoation
|
||||
final CouchbasePersistentEntity<?> entity = template.getConverter().getMappingContext()
|
||||
.getRequiredPersistentEntity(domainType);
|
||||
expiryToUse = entity.getExpiryDuration();
|
||||
}
|
||||
}
|
||||
return expiryToUse;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,246 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.dao.IncorrectResultSizeDataAccessException;
|
||||
import org.springframework.data.couchbase.core.query.Query;
|
||||
import org.springframework.data.couchbase.core.query.QueryCriteriaDefinition;
|
||||
import org.springframework.data.couchbase.core.support.InCollection;
|
||||
import org.springframework.data.couchbase.core.support.InScope;
|
||||
import org.springframework.data.couchbase.core.support.OneAndAllReactive;
|
||||
import org.springframework.data.couchbase.core.support.WithConsistency;
|
||||
import org.springframework.data.couchbase.core.support.WithDistinct;
|
||||
import org.springframework.data.couchbase.core.support.WithQuery;
|
||||
import org.springframework.data.couchbase.core.support.WithQueryOptions;
|
||||
|
||||
import com.couchbase.client.java.query.QueryOptions;
|
||||
import com.couchbase.client.java.query.QueryScanConsistency;
|
||||
|
||||
/**
|
||||
* ReactiveFindByQueryOperation<br>
|
||||
* Queries the N1QL service.
|
||||
*
|
||||
* @author Michael Nitschinger
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
public interface ReactiveFindByQueryOperation {
|
||||
|
||||
/**
|
||||
* Create the operation for the domainType
|
||||
*
|
||||
* @param domainType the entity type to use for the results.
|
||||
*/
|
||||
<T> ReactiveFindByQuery<T> findByQuery(Class<T> domainType);
|
||||
|
||||
/**
|
||||
* Compose find execution by calling one of the terminating methods.
|
||||
*/
|
||||
interface TerminatingFindByQuery<T> extends OneAndAllReactive<T> {
|
||||
|
||||
/**
|
||||
* Get exactly zero or one result.
|
||||
*
|
||||
* @return a mono with the match if found (an empty one otherwise).
|
||||
* @throws IncorrectResultSizeDataAccessException if more than one match found.
|
||||
*/
|
||||
Mono<T> one();
|
||||
|
||||
/**
|
||||
* Get the first or no result.
|
||||
*
|
||||
* @return the first or an empty mono if none found.
|
||||
*/
|
||||
Mono<T> first();
|
||||
|
||||
/**
|
||||
* Get all matching elements.
|
||||
*
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
Flux<T> all();
|
||||
|
||||
/**
|
||||
* Get the number of matching elements.
|
||||
*
|
||||
* @return total number of matching elements.
|
||||
*/
|
||||
Mono<Long> count();
|
||||
|
||||
/**
|
||||
* Check for the presence of matching elements.
|
||||
*
|
||||
* @return {@literal true} if at least one matching element exists.
|
||||
*/
|
||||
Mono<Boolean> exists();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Fluent methods to filter by query
|
||||
*
|
||||
* @param <T> the entity type to use for the results.
|
||||
*/
|
||||
interface FindByQueryWithQuery<T> extends TerminatingFindByQuery<T>, WithQuery<T> {
|
||||
|
||||
/**
|
||||
* Set the filter {@link Query} to be used.
|
||||
*
|
||||
* @param query must not be {@literal null}.
|
||||
* @throws IllegalArgumentException if query is {@literal null}.
|
||||
*/
|
||||
TerminatingFindByQuery<T> matching(Query query);
|
||||
|
||||
/**
|
||||
* Set the filter {@link QueryCriteriaDefinition criteria} to be used.
|
||||
*
|
||||
* @param criteria must not be {@literal null}.
|
||||
* @return new instance of {@link TerminatingFindByQuery}.
|
||||
* @throws IllegalArgumentException if criteria is {@literal null}.
|
||||
*/
|
||||
default TerminatingFindByQuery<T> matching(QueryCriteriaDefinition criteria) {
|
||||
return matching(Query.query(criteria));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Fluent method to specify options.
|
||||
*
|
||||
* @param <T> the entity type to use for the results.
|
||||
*/
|
||||
interface FindByQueryWithOptions<T> extends FindByQueryWithQuery<T>, WithQueryOptions<T> {
|
||||
/**
|
||||
* @param options options to use for execution
|
||||
*/
|
||||
TerminatingFindByQuery<T> withOptions(QueryOptions options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fluent method to specify the collection
|
||||
*
|
||||
* @param <T> the entity type to use for the results.
|
||||
*/
|
||||
interface FindByQueryInCollection<T> extends FindByQueryWithOptions<T>, InCollection<T> {
|
||||
FindByQueryWithOptions<T> inCollection(String collection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fluent method to specify the scope
|
||||
*
|
||||
* @param <T> the entity type to use for the results.
|
||||
*/
|
||||
interface FindByQueryInScope<T> extends FindByQueryInCollection<T>, InScope<T> {
|
||||
FindByQueryInCollection<T> inScope(String scope);
|
||||
}
|
||||
|
||||
/**
|
||||
* To be removed at the next major release. use WithConsistency instead
|
||||
*
|
||||
* @param <T> the entity type to use for the results.
|
||||
*/
|
||||
@Deprecated
|
||||
interface FindByQueryConsistentWith<T> extends FindByQueryInScope<T> {
|
||||
|
||||
/**
|
||||
* Allows to override the default scan consistency.
|
||||
*
|
||||
* @param scanConsistency the custom scan consistency to use for this query.
|
||||
*/
|
||||
@Deprecated
|
||||
FindByQueryInScope<T> consistentWith(QueryScanConsistency scanConsistency);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Fluent method to specify scan consistency. Scan consistency may also come from an annotation.
|
||||
*
|
||||
* @param <T> the entity type to use for the results.
|
||||
*/
|
||||
interface FindByQueryWithConsistency<T> extends FindByQueryConsistentWith<T>, WithConsistency<T> {
|
||||
|
||||
/**
|
||||
* Allows to override the default scan consistency.
|
||||
*
|
||||
* @param scanConsistency the custom scan consistency to use for this query.
|
||||
*/
|
||||
FindByQueryConsistentWith<T> withConsistency(QueryScanConsistency scanConsistency);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Fluent method to specify a return type different than the the entity type to use for the results.
|
||||
*
|
||||
* @param <T> the entity type to use for the results.
|
||||
*/
|
||||
interface FindByQueryWithProjection<T> extends FindByQueryWithConsistency<T> {
|
||||
|
||||
/**
|
||||
* Define the target type fields should be mapped to. <br />
|
||||
* Skip this step if you are anyway only interested in the original domain type.
|
||||
*
|
||||
* @param returnType must not be {@literal null}.
|
||||
* @return new instance of {@link FindByQueryWithProjection}.
|
||||
* @throws IllegalArgumentException if returnType is {@literal null}.
|
||||
*/
|
||||
<R> FindByQueryWithConsistency<R> as(Class<R> returnType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fluent method to specify fields to project.
|
||||
*
|
||||
* @param <T> the entity type to use for the results.
|
||||
*/
|
||||
interface FindByQueryWithProjecting<T> extends FindByQueryWithProjection<T> {
|
||||
|
||||
/**
|
||||
* Define the target type fields should be mapped to. <br />
|
||||
* Skip this step if you are anyway only interested in the original domain type.
|
||||
*
|
||||
* @param fields to project
|
||||
* @return new instance of {@link FindByQueryWithConsistency}.
|
||||
* @throws IllegalArgumentException if returnType is {@literal null}.
|
||||
*/
|
||||
FindByQueryWithProjection<T> project(String[] fields);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fluent method to specify DISTINCT fields
|
||||
*
|
||||
* @param <T> the entity type to use for the results.
|
||||
*/
|
||||
interface FindByQueryWithDistinct<T> extends FindByQueryWithProjecting<T>, WithDistinct<T> {
|
||||
|
||||
/**
|
||||
* Finds the distinct values for a specified {@literal field} across a single {@link } or view.
|
||||
*
|
||||
* @param distinctFields name of the field. Must not be {@literal null}.
|
||||
* @return new instance of {@link ReactiveFindByQuery}.
|
||||
* @throws IllegalArgumentException if field is {@literal null}.
|
||||
*/
|
||||
FindByQueryWithProjection<T> distinct(String[] distinctFields);
|
||||
}
|
||||
|
||||
/**
|
||||
* provides methods for constructing query operations in a fluent way.
|
||||
*
|
||||
* @param <T> the entity type to use for the results
|
||||
*/
|
||||
interface ReactiveFindByQuery<T> extends FindByQueryWithDistinct<T> {}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,285 @@
|
||||
/*
|
||||
* Copyright 2012-2022 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;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.data.couchbase.CouchbaseClientFactory;
|
||||
import org.springframework.data.couchbase.core.query.OptionsBuilder;
|
||||
import org.springframework.data.couchbase.core.query.Query;
|
||||
import org.springframework.data.couchbase.core.support.PseudoArgs;
|
||||
import org.springframework.data.couchbase.core.support.TemplateUtils;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.couchbase.client.core.io.CollectionIdentifier;
|
||||
import com.couchbase.client.java.ReactiveScope;
|
||||
import com.couchbase.client.java.codec.JsonSerializer;
|
||||
import com.couchbase.client.java.query.QueryOptions;
|
||||
import com.couchbase.client.java.query.QueryScanConsistency;
|
||||
import com.couchbase.client.java.query.ReactiveQueryResult;
|
||||
import com.couchbase.client.java.transactions.AttemptContextReactiveAccessor;
|
||||
import com.couchbase.client.java.transactions.TransactionQueryOptions;
|
||||
import com.couchbase.client.java.transactions.TransactionQueryResult;
|
||||
|
||||
/**
|
||||
* {@link ReactiveFindByQueryOperation} implementations for Couchbase.
|
||||
*
|
||||
* @author Michael Nitschinger
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
public class ReactiveFindByQueryOperationSupport implements ReactiveFindByQueryOperation {
|
||||
|
||||
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) {
|
||||
this.template = template;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> ReactiveFindByQuery<T> findByQuery(final Class<T> domainType) {
|
||||
return new ReactiveFindByQuerySupport<>(template, domainType, domainType, ALL_QUERY, null,
|
||||
OptionsBuilder.getScopeFrom(domainType), OptionsBuilder.getCollectionFrom(domainType), null, null, null,
|
||||
template.support());
|
||||
}
|
||||
|
||||
static class ReactiveFindByQuerySupport<T> implements ReactiveFindByQuery<T> {
|
||||
|
||||
private final ReactiveCouchbaseTemplate template;
|
||||
private final Class<?> domainType;
|
||||
private final Class<T> returnType;
|
||||
private final Query query;
|
||||
private final QueryScanConsistency scanConsistency;
|
||||
private final String collection;
|
||||
private final String scope;
|
||||
private final String[] distinctFields;
|
||||
private final String[] fields;
|
||||
private final QueryOptions options;
|
||||
private final ReactiveTemplateSupport support;
|
||||
|
||||
ReactiveFindByQuerySupport(final ReactiveCouchbaseTemplate template, final Class<?> domainType,
|
||||
final Class<T> returnType, final Query query, final QueryScanConsistency scanConsistency, final String scope,
|
||||
final String collection, final QueryOptions options, final String[] distinctFields, final String[] fields,
|
||||
final ReactiveTemplateSupport support) {
|
||||
Assert.notNull(domainType, "domainType must not be null!");
|
||||
Assert.notNull(returnType, "returnType must not be null!");
|
||||
this.template = template;
|
||||
this.domainType = domainType;
|
||||
this.returnType = returnType;
|
||||
this.query = query;
|
||||
this.scanConsistency = scanConsistency;
|
||||
this.scope = scope;
|
||||
this.collection = collection;
|
||||
this.options = options;
|
||||
this.distinctFields = distinctFields;
|
||||
this.fields = fields;
|
||||
this.support = support;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FindByQueryWithQuery<T> matching(Query query) {
|
||||
QueryScanConsistency scanCons;
|
||||
if (query.getScanConsistency() != null) { // redundant, since buildQueryOptions() will use
|
||||
// query.getScanConsistency()
|
||||
scanCons = query.getScanConsistency();
|
||||
} else {
|
||||
scanCons = scanConsistency;
|
||||
}
|
||||
return new ReactiveFindByQuerySupport<>(template, domainType, returnType, query, scanCons, scope, collection,
|
||||
options, distinctFields, fields, support);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TerminatingFindByQuery<T> withOptions(final QueryOptions options) {
|
||||
Assert.notNull(options, "Options must not be null.");
|
||||
return new ReactiveFindByQuerySupport<>(template, domainType, returnType, query, scanConsistency, scope,
|
||||
collection, options, distinctFields, fields, support);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FindByQueryInCollection<T> inScope(final String scope) {
|
||||
return new ReactiveFindByQuerySupport<>(template, domainType, returnType, query, scanConsistency,
|
||||
scope != null ? scope : this.scope, collection, options, distinctFields, fields, support);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FindByQueryWithConsistency<T> inCollection(final String collection) {
|
||||
return new ReactiveFindByQuerySupport<>(template, domainType, returnType, query, scanConsistency, scope,
|
||||
collection != null ? collection : this.collection, options, distinctFields, fields, support);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public FindByQueryConsistentWith<T> consistentWith(QueryScanConsistency scanConsistency) {
|
||||
return new ReactiveFindByQuerySupport<>(template, domainType, returnType, query, scanConsistency, scope,
|
||||
collection, options, distinctFields, fields, support);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FindByQueryWithConsistency<T> withConsistency(QueryScanConsistency scanConsistency) {
|
||||
return new ReactiveFindByQuerySupport<>(template, domainType, returnType, query, scanConsistency, scope,
|
||||
collection, options, distinctFields, fields, support);
|
||||
}
|
||||
|
||||
public <R> FindByQueryWithConsistency<R> as(Class<R> returnType) {
|
||||
Assert.notNull(returnType, "returnType must not be null!");
|
||||
return new ReactiveFindByQuerySupport<>(template, domainType, returnType, query, scanConsistency, scope,
|
||||
collection, options, distinctFields, fields, support);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FindByQueryWithProjection<T> project(String[] fields) {
|
||||
Assert.notNull(fields, "Fields must not be null");
|
||||
Assert.isNull(distinctFields, "only one of project(fields) and distinct(distinctFields) can be specified");
|
||||
return new ReactiveFindByQuerySupport<>(template, domainType, returnType, query, scanConsistency, scope,
|
||||
collection, options, distinctFields, fields, support);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FindByQueryWithDistinct<T> distinct(final String[] distinctFields) {
|
||||
Assert.notNull(distinctFields, "distinctFields must not be null");
|
||||
Assert.isNull(fields, "only one of project(fields) and distinct(distinctFields) can be specified");
|
||||
// Coming from an annotation, this cannot be null.
|
||||
// But a non-null but empty distinctFields means distinct on all fields
|
||||
// So to indicate do not use distinct, we use {"-"} from the annotation, and here we change it to null.
|
||||
String[] dFields = distinctFields.length == 1 && "-".equals(distinctFields[0]) ? null : distinctFields;
|
||||
return new ReactiveFindByQuerySupport<>(template, domainType, returnType, query, scanConsistency, scope,
|
||||
collection, options, dFields, fields, support);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<T> one() {
|
||||
return all().singleOrEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<T> first() {
|
||||
return all().next();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<T> all() {
|
||||
PseudoArgs<QueryOptions> pArgs = new PseudoArgs(template, scope, collection, options, domainType);
|
||||
String statement = assembleEntityQuery(false, distinctFields, pArgs.getScope(), pArgs.getCollection());
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("findByQuery {} statement: {}", pArgs, statement);
|
||||
}
|
||||
CouchbaseClientFactory clientFactory = template.getCouchbaseClientFactory();
|
||||
ReactiveScope rs = clientFactory.withScope(pArgs.getScope()).getScope().reactive();
|
||||
|
||||
Mono<Object> allResult = TransactionalSupport.checkForTransactionInThreadLocalStorage().flatMap(s -> {
|
||||
if (!s.isPresent()) {
|
||||
QueryOptions opts = buildOptions(pArgs.getOptions());
|
||||
return pArgs.getScope() == null ? clientFactory.getCluster().reactive().query(statement, opts)
|
||||
: rs.query(statement, opts);
|
||||
} else {
|
||||
TransactionQueryOptions opts = buildTransactionOptions(pArgs.getOptions());
|
||||
JsonSerializer jSer = clientFactory.getCluster().environment().jsonSerializer();
|
||||
return AttemptContextReactiveAccessor.createReactiveTransactionAttemptContext(s.get().getCore(), jSer)
|
||||
.query(rs.name().equals(CollectionIdentifier.DEFAULT_SCOPE) ? null : rs, statement, opts);
|
||||
}
|
||||
});
|
||||
|
||||
return allResult.onErrorMap(throwable -> {
|
||||
if (throwable instanceof RuntimeException) {
|
||||
return template.potentiallyConvertRuntimeException((RuntimeException) throwable);
|
||||
} else {
|
||||
return throwable;
|
||||
}
|
||||
}).flatMapMany(o -> o instanceof ReactiveQueryResult ? ((ReactiveQueryResult) o).rowsAsObject()
|
||||
: Flux.fromIterable(((TransactionQueryResult) o).rowsAsObject())).flatMap(row -> {
|
||||
String id = "";
|
||||
Long cas = Long.valueOf(0);
|
||||
if (!query.isDistinct() && distinctFields == null) {
|
||||
id = row.getString(TemplateUtils.SELECT_ID);
|
||||
if (id == null) {
|
||||
id = row.getString(TemplateUtils.SELECT_ID_3x);
|
||||
row.removeKey(TemplateUtils.SELECT_ID_3x);
|
||||
}
|
||||
cas = row.getLong(TemplateUtils.SELECT_CAS);
|
||||
if (cas == null) {
|
||||
cas = row.getLong(TemplateUtils.SELECT_CAS_3x);
|
||||
row.removeKey(TemplateUtils.SELECT_CAS_3x);
|
||||
}
|
||||
row.removeKey(TemplateUtils.SELECT_ID);
|
||||
row.removeKey(TemplateUtils.SELECT_CAS);
|
||||
}
|
||||
return support.decodeEntity(id, row.toString(), cas, returnType, pArgs.getScope(), pArgs.getCollection(),
|
||||
null, null);
|
||||
});
|
||||
}
|
||||
|
||||
public QueryOptions buildOptions(QueryOptions options) {
|
||||
QueryScanConsistency qsc = scanConsistency != null ? scanConsistency : template.getConsistency();
|
||||
return query.buildQueryOptions(options, qsc);
|
||||
}
|
||||
|
||||
private TransactionQueryOptions buildTransactionOptions(QueryOptions options) {
|
||||
TransactionQueryOptions opts = OptionsBuilder.buildTransactionQueryOptions(buildOptions(options));
|
||||
return opts;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Long> count() {
|
||||
PseudoArgs<QueryOptions> pArgs = new PseudoArgs(template, scope, collection, options, domainType);
|
||||
String statement = assembleEntityQuery(true, distinctFields, pArgs.getScope(), pArgs.getCollection());
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("findByQuery {} statement: {}", pArgs, statement);
|
||||
}
|
||||
|
||||
CouchbaseClientFactory clientFactory = template.getCouchbaseClientFactory();
|
||||
ReactiveScope rs = clientFactory.withScope(pArgs.getScope()).getScope().reactive();
|
||||
|
||||
Mono<Object> allResult = TransactionalSupport.checkForTransactionInThreadLocalStorage().flatMap(s -> {
|
||||
if (!s.isPresent()) {
|
||||
QueryOptions opts = buildOptions(pArgs.getOptions());
|
||||
return pArgs.getScope() == null ? clientFactory.getCluster().reactive().query(statement, opts)
|
||||
: rs.query(statement, opts);
|
||||
} else {
|
||||
TransactionQueryOptions opts = buildTransactionOptions(pArgs.getOptions());
|
||||
return (AttemptContextReactiveAccessor.createReactiveTransactionAttemptContext(s.get().getCore(),
|
||||
clientFactory.getCluster().environment().jsonSerializer())).query(statement, opts);
|
||||
}
|
||||
});
|
||||
|
||||
return allResult.onErrorMap(throwable -> {
|
||||
if (throwable instanceof RuntimeException) {
|
||||
return template.potentiallyConvertRuntimeException((RuntimeException) throwable);
|
||||
} else {
|
||||
return throwable;
|
||||
}
|
||||
}).flatMapMany(o -> o instanceof ReactiveQueryResult ? ((ReactiveQueryResult) o).rowsAsObject()
|
||||
: Flux.fromIterable(((TransactionQueryResult) o).rowsAsObject()))
|
||||
.map(row -> row.getLong(row.getNames().iterator().next())).next();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Boolean> exists() {
|
||||
return count().map(count -> count > 0); // not efficient, just need the first one
|
||||
}
|
||||
|
||||
private String assembleEntityQuery(final boolean count, String[] distinctFields, String scope, String collection) {
|
||||
return query.toN1qlSelectString(template.getConverter(), template.getBucketName(), scope, collection,
|
||||
this.domainType, this.returnType, count,
|
||||
query.getDistinctFields() != null ? query.getDistinctFields() : distinctFields, fields);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.springframework.data.couchbase.core.support.AnyIdReactive;
|
||||
import org.springframework.data.couchbase.core.support.InCollection;
|
||||
import org.springframework.data.couchbase.core.support.InScope;
|
||||
import org.springframework.data.couchbase.core.support.WithGetAnyReplicaOptions;
|
||||
|
||||
import com.couchbase.client.java.kv.GetAnyReplicaOptions;
|
||||
|
||||
/**
|
||||
* Find by id from replicas Operations
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.0
|
||||
*/
|
||||
public interface ReactiveFindFromReplicasByIdOperation {
|
||||
|
||||
/**
|
||||
* Loads a document from a replica.
|
||||
*
|
||||
* @param domainType the entity type to use for the results.
|
||||
*/
|
||||
<T> ReactiveFindFromReplicasById<T> findFromReplicasById(Class<T> domainType);
|
||||
|
||||
/**
|
||||
* Terminating operations invoking the actual get execution.
|
||||
*/
|
||||
interface TerminatingFindFromReplicasById<T> extends AnyIdReactive<T> {
|
||||
|
||||
/**
|
||||
* Finds one document based on the given ID.
|
||||
*
|
||||
* @param id the document ID.
|
||||
* @return the entity if found.
|
||||
*/
|
||||
Mono<T> any(String id);
|
||||
|
||||
/**
|
||||
* Finds a list of documents based on the given IDs.
|
||||
*
|
||||
* @param ids the document ID ids.
|
||||
* @return the list of found entities.
|
||||
*/
|
||||
Flux<? extends T> any(Collection<String> ids);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Fluent method to specify options.
|
||||
*
|
||||
* @param <T> the entity type to use for the results.
|
||||
*/
|
||||
interface FindFromReplicasByIdWithOptions<T> extends TerminatingFindFromReplicasById<T>, WithGetAnyReplicaOptions<T> {
|
||||
/**
|
||||
* Fluent method to specify options to use for execution
|
||||
*
|
||||
* @param options options to use for execution
|
||||
*/
|
||||
@Override
|
||||
TerminatingFindFromReplicasById<T> withOptions(GetAnyReplicaOptions options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fluent method to specify the collection.
|
||||
*
|
||||
* @param <T> the entity type to use for the results.
|
||||
*/
|
||||
interface FindFromReplicasByIdInCollection<T> extends FindFromReplicasByIdWithOptions<T>, InCollection<T> {
|
||||
/**
|
||||
* With a different collection
|
||||
*
|
||||
* @param collection the collection to use.
|
||||
*/
|
||||
@Override
|
||||
FindFromReplicasByIdWithOptions<T> inCollection(String collection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fluent method to specify the scope.
|
||||
*
|
||||
* @param <T> the entity type to use for the results.
|
||||
*/
|
||||
interface FindFromReplicasByIdInScope<T> extends FindFromReplicasByIdInCollection<T>, InScope<T> {
|
||||
/**
|
||||
* With a different scope
|
||||
*
|
||||
* @param scope the scope to use.
|
||||
*/
|
||||
@Override
|
||||
FindFromReplicasByIdInCollection<T> inScope(String scope);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides methods for constructing get operations in a fluent way.
|
||||
*
|
||||
* @param <T> the entity type to use for the results
|
||||
*/
|
||||
interface ReactiveFindFromReplicasById<T> extends FindFromReplicasByIdInScope<T> {}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* Copyright 2012-2022 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;
|
||||
|
||||
import static com.couchbase.client.java.kv.GetAnyReplicaOptions.getAnyReplicaOptions;
|
||||
|
||||
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;
|
||||
|
||||
import com.couchbase.client.java.codec.RawJsonTranscoder;
|
||||
import com.couchbase.client.java.kv.GetAnyReplicaOptions;
|
||||
|
||||
/**
|
||||
* {@link ReactiveFindFromReplicasByIdOperation} implementations for Couchbase.
|
||||
*
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
public class ReactiveFindFromReplicasByIdOperationSupport implements ReactiveFindFromReplicasByIdOperation {
|
||||
|
||||
private final ReactiveCouchbaseTemplate template;
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ReactiveFindFromReplicasByIdOperationSupport.class);
|
||||
|
||||
ReactiveFindFromReplicasByIdOperationSupport(ReactiveCouchbaseTemplate template) {
|
||||
this.template = template;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> ReactiveFindFromReplicasById<T> findFromReplicasById(Class<T> domainType) {
|
||||
return new ReactiveFindFromReplicasByIdSupport<>(template, domainType, domainType,
|
||||
OptionsBuilder.getScopeFrom(domainType), OptionsBuilder.getCollectionFrom(domainType), null,
|
||||
template.support());
|
||||
}
|
||||
|
||||
static class ReactiveFindFromReplicasByIdSupport<T> implements ReactiveFindFromReplicasById<T> {
|
||||
|
||||
private final ReactiveCouchbaseTemplate template;
|
||||
private final Class<?> domainType;
|
||||
private final Class<T> returnType;
|
||||
private final String scope;
|
||||
private final String collection;
|
||||
private final GetAnyReplicaOptions options;
|
||||
private final ReactiveTemplateSupport support;
|
||||
|
||||
ReactiveFindFromReplicasByIdSupport(ReactiveCouchbaseTemplate template, Class<?> domainType, Class<T> returnType,
|
||||
String scope, String collection, GetAnyReplicaOptions options, ReactiveTemplateSupport support) {
|
||||
this.template = template;
|
||||
this.domainType = domainType;
|
||||
this.returnType = returnType;
|
||||
this.scope = scope;
|
||||
this.collection = collection;
|
||||
this.options = options;
|
||||
this.support = support;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<T> any(final String id) {
|
||||
GetAnyReplicaOptions garOptions = options != null ? options : getAnyReplicaOptions();
|
||||
if (garOptions.build().transcoder() == null) {
|
||||
garOptions.transcoder(RawJsonTranscoder.INSTANCE);
|
||||
}
|
||||
PseudoArgs<GetAnyReplicaOptions> pArgs = new PseudoArgs<>(template, scope, collection, garOptions, domainType);
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("getAnyReplica key={} {}", id, pArgs);
|
||||
}
|
||||
return TransactionalSupport.verifyNotInTransaction("findFromReplicasById").then(Mono.just(id))
|
||||
.flatMap(docId -> template.getCouchbaseClientFactory().withScope(pArgs.getScope())
|
||||
.getCollection(pArgs.getCollection()).reactive().getAnyReplica(docId, pArgs.getOptions()))
|
||||
.flatMap(result -> support.decodeEntity(id, result.contentAs(String.class), result.cas(), returnType,
|
||||
pArgs.getScope(), pArgs.getCollection(), null, null))
|
||||
.onErrorMap(throwable -> {
|
||||
if (throwable instanceof RuntimeException) {
|
||||
return template.potentiallyConvertRuntimeException((RuntimeException) throwable);
|
||||
} else {
|
||||
return throwable;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<? extends T> any(Collection<String> ids) {
|
||||
return Flux.fromIterable(ids).flatMap(this::any);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TerminatingFindFromReplicasById<T> withOptions(final GetAnyReplicaOptions options) {
|
||||
Assert.notNull(options, "Options must not be null.");
|
||||
return new ReactiveFindFromReplicasByIdSupport<>(template, domainType, returnType, scope, collection, options,
|
||||
support);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FindFromReplicasByIdWithOptions<T> inCollection(final String collection) {
|
||||
return new ReactiveFindFromReplicasByIdSupport<>(template, domainType, returnType, scope,
|
||||
collection != null ? collection : this.collection, options, support);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FindFromReplicasByIdInCollection<T> inScope(final String scope) {
|
||||
return new ReactiveFindFromReplicasByIdSupport<>(template, domainType, returnType,
|
||||
scope != null ? scope : this.scope, collection, options, support);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* The fluent couchbase operations combines all different possible operations for simplicity reasons.
|
||||
*/
|
||||
public interface ReactiveFluentCouchbaseOperations extends ReactiveUpsertByIdOperation, ReactiveInsertByIdOperation,
|
||||
ReactiveReplaceByIdOperation, ReactiveFindByIdOperation, ReactiveExistsByIdOperation,
|
||||
ReactiveFindByAnalyticsOperation, ReactiveFindFromReplicasByIdOperation, ReactiveFindByQueryOperation,
|
||||
ReactiveRemoveByIdOperation, ReactiveRemoveByQueryOperation {}
|
||||
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
* Copyright 2012-2022 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;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Collection;
|
||||
|
||||
import org.springframework.data.couchbase.core.support.InCollection;
|
||||
import org.springframework.data.couchbase.core.support.InScope;
|
||||
import org.springframework.data.couchbase.core.support.OneAndAllEntityReactive;
|
||||
import org.springframework.data.couchbase.core.support.WithDurability;
|
||||
import org.springframework.data.couchbase.core.support.WithExpiry;
|
||||
import org.springframework.data.couchbase.core.support.WithInsertOptions;
|
||||
|
||||
import com.couchbase.client.core.msg.kv.DurabilityLevel;
|
||||
import com.couchbase.client.java.kv.InsertOptions;
|
||||
import com.couchbase.client.java.kv.PersistTo;
|
||||
import com.couchbase.client.java.kv.ReplicateTo;
|
||||
|
||||
/**
|
||||
* Insert Operations
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.0
|
||||
*/
|
||||
public interface ReactiveInsertByIdOperation {
|
||||
|
||||
/**
|
||||
* Insert using the KV service.
|
||||
*
|
||||
* @param domainType the entity type to insert.
|
||||
*/
|
||||
<T> ReactiveInsertById<T> insertById(Class<T> domainType);
|
||||
|
||||
/**
|
||||
* Terminating operations invoking the actual execution.
|
||||
*/
|
||||
interface TerminatingInsertById<T> extends OneAndAllEntityReactive<T> {
|
||||
|
||||
/**
|
||||
* Insert one entity.
|
||||
*
|
||||
* @return Inserted entity.
|
||||
*/
|
||||
@Override
|
||||
Mono<T> one(T object);
|
||||
|
||||
/**
|
||||
* Insert a collection of entities.
|
||||
*
|
||||
* @return Inserted entities
|
||||
*/
|
||||
@Override
|
||||
Flux<? extends T> all(Collection<? extends T> objects);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Fluent method to specify options.
|
||||
*/
|
||||
interface InsertByIdWithOptions<T> extends TerminatingInsertById<T>, WithInsertOptions<T> {
|
||||
/**
|
||||
* Fluent method to specify options to use for execution.
|
||||
*
|
||||
* @param options to use for execution
|
||||
*/
|
||||
@Override
|
||||
TerminatingInsertById<T> withOptions(InsertOptions options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fluent method to specify the collection.
|
||||
*/
|
||||
interface InsertByIdInCollection<T> extends InsertByIdWithOptions<T>, InCollection<T> {
|
||||
/**
|
||||
* With a different collection
|
||||
*
|
||||
* @param collection the collection to use.
|
||||
*/
|
||||
@Override
|
||||
InsertByIdWithOptions<T> inCollection(String collection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fluent method to specify the scope.
|
||||
*/
|
||||
interface InsertByIdInScope<T> extends InsertByIdInCollection<T>, InScope<T> {
|
||||
/**
|
||||
* With a different scope
|
||||
*
|
||||
* @param scope the scope to use.
|
||||
*/
|
||||
@Override
|
||||
InsertByIdInCollection<T> inScope(String scope);
|
||||
}
|
||||
|
||||
interface InsertByIdWithDurability<T> extends InsertByIdInScope<T>, WithDurability<T> {
|
||||
|
||||
@Override
|
||||
InsertByIdInScope<T> withDurability(DurabilityLevel durabilityLevel);
|
||||
|
||||
@Override
|
||||
InsertByIdInScope<T> withDurability(PersistTo persistTo, ReplicateTo replicateTo);
|
||||
|
||||
}
|
||||
|
||||
interface InsertByIdWithExpiry<T> extends InsertByIdWithDurability<T>, WithExpiry<T> {
|
||||
|
||||
@Override
|
||||
InsertByIdWithDurability<T> withExpiry(Duration expiry);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides methods for constructing KV insert operations in a fluent way.
|
||||
*
|
||||
* @param <T> the entity type to insert
|
||||
*/
|
||||
interface ReactiveInsertById<T> extends InsertByIdWithExpiry<T> {}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
/*
|
||||
* Copyright 2012-2022 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;
|
||||
|
||||
import static com.couchbase.client.java.transactions.internal.ConverterUtil.makeCollectionIdentifier;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
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;
|
||||
|
||||
import com.couchbase.client.core.msg.kv.DurabilityLevel;
|
||||
import com.couchbase.client.java.kv.InsertOptions;
|
||||
import com.couchbase.client.java.kv.PersistTo;
|
||||
import com.couchbase.client.java.kv.ReplicateTo;
|
||||
|
||||
/**
|
||||
* {@link ReactiveInsertByIdOperation} implementations for Couchbase.
|
||||
*
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
public class ReactiveInsertByIdOperationSupport implements ReactiveInsertByIdOperation {
|
||||
|
||||
private final ReactiveCouchbaseTemplate template;
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ReactiveInsertByIdOperationSupport.class);
|
||||
|
||||
public ReactiveInsertByIdOperationSupport(final ReactiveCouchbaseTemplate template) {
|
||||
this.template = template;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> ReactiveInsertById<T> insertById(final Class<T> domainType) {
|
||||
Assert.notNull(domainType, "DomainType must not be null!");
|
||||
return new ReactiveInsertByIdSupport<>(template, domainType, OptionsBuilder.getScopeFrom(domainType),
|
||||
OptionsBuilder.getCollectionFrom(domainType), null, PersistTo.NONE, ReplicateTo.NONE, DurabilityLevel.NONE,
|
||||
null, template.support());
|
||||
}
|
||||
|
||||
static class ReactiveInsertByIdSupport<T> implements ReactiveInsertById<T> {
|
||||
|
||||
private final ReactiveCouchbaseTemplate template;
|
||||
private final Class<T> domainType;
|
||||
private final String scope;
|
||||
private final String collection;
|
||||
private final InsertOptions options;
|
||||
private final PersistTo persistTo;
|
||||
private final ReplicateTo replicateTo;
|
||||
private final DurabilityLevel durabilityLevel;
|
||||
private final Duration expiry;
|
||||
private final ReactiveTemplateSupport support;
|
||||
|
||||
ReactiveInsertByIdSupport(final ReactiveCouchbaseTemplate template, final Class<T> domainType, final String scope,
|
||||
final String collection, final InsertOptions options, final PersistTo persistTo, final ReplicateTo replicateTo,
|
||||
final DurabilityLevel durabilityLevel, Duration expiry, ReactiveTemplateSupport support) {
|
||||
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.expiry = expiry;
|
||||
this.support = support;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<T> one(T object) {
|
||||
PseudoArgs<InsertOptions> pArgs = new PseudoArgs(template, scope, collection, options, domainType);
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("insertById object={} {}", object, pArgs);
|
||||
}
|
||||
return Mono
|
||||
.just(template.getCouchbaseClientFactory().withScope(pArgs.getScope()).getCollection(pArgs.getCollection()))
|
||||
.flatMap(collection -> support.encodeEntity(object)
|
||||
.flatMap(converted -> TransactionalSupport.checkForTransactionInThreadLocalStorage().flatMap(ctxOpt -> {
|
||||
if (!ctxOpt.isPresent()) {
|
||||
return collection.reactive()
|
||||
.insert(converted.getId().toString(), converted.export(), buildOptions(pArgs.getOptions(), converted))
|
||||
.flatMap(result -> this.support.applyResult(object, converted, converted.getId(), result.cas(),
|
||||
null, null));
|
||||
} else {
|
||||
rejectInvalidTransactionalOptions();
|
||||
return ctxOpt.get().getCore()
|
||||
.insert(makeCollectionIdentifier(collection.async()), converted.getId().toString(),
|
||||
template.getCouchbaseClientFactory().getCluster().environment().transcoder()
|
||||
.encode(converted.export()).encoded())
|
||||
.flatMap(result -> this.support.applyResult(object, converted, converted.getId(), result.cas(),
|
||||
null, null));
|
||||
}
|
||||
})).onErrorMap(throwable -> {
|
||||
if (throwable instanceof RuntimeException) {
|
||||
return template.potentiallyConvertRuntimeException((RuntimeException) throwable);
|
||||
} else {
|
||||
return throwable;
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private void rejectInvalidTransactionalOptions() {
|
||||
if ((this.persistTo != null && this.persistTo != PersistTo.NONE)
|
||||
|| (this.replicateTo != null && this.replicateTo != ReplicateTo.NONE)) {
|
||||
throw new IllegalArgumentException(
|
||||
"withDurability PersistTo and ReplicateTo overload is not supported in a transaction");
|
||||
}
|
||||
if (this.expiry != null) {
|
||||
throw new IllegalArgumentException("withExpiry is not supported in a transaction");
|
||||
}
|
||||
if (this.durabilityLevel != null && this.durabilityLevel != DurabilityLevel.NONE) {
|
||||
throw new IllegalArgumentException("withDurability is not supported in a transaction");
|
||||
}
|
||||
if (this.options != null) {
|
||||
throw new IllegalArgumentException("withOptions is not supported in a transaction");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<? extends T> all(Collection<? extends T> objects) {
|
||||
return Flux.fromIterable(objects).flatMap(this::one);
|
||||
}
|
||||
|
||||
public InsertOptions buildOptions(InsertOptions options, CouchbaseDocument doc) { // CouchbaseDocument converted
|
||||
return OptionsBuilder.buildInsertOptions(options, persistTo, replicateTo, durabilityLevel, expiry, doc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TerminatingInsertById<T> withOptions(final InsertOptions options) {
|
||||
Assert.notNull(options, "Options must not be null.");
|
||||
return new ReactiveInsertByIdSupport<>(template, domainType, scope, collection, options, persistTo, replicateTo,
|
||||
durabilityLevel, expiry, support);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InsertByIdInCollection<T> inScope(final String scope) {
|
||||
return new ReactiveInsertByIdSupport<>(template, domainType, scope != null ? scope : this.scope, collection,
|
||||
options, persistTo, replicateTo, durabilityLevel, expiry, support);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InsertByIdWithOptions<T> inCollection(final String collection) {
|
||||
return new ReactiveInsertByIdSupport<>(template, domainType, scope,
|
||||
collection != null ? collection : this.collection, options, persistTo, replicateTo, durabilityLevel, expiry,
|
||||
support);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InsertByIdInScope<T> withDurability(final DurabilityLevel durabilityLevel) {
|
||||
Assert.notNull(durabilityLevel, "Durability Level must not be null.");
|
||||
return new ReactiveInsertByIdSupport<>(template, domainType, scope, collection, options, persistTo, replicateTo,
|
||||
durabilityLevel, expiry, support);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InsertByIdInScope<T> 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 ReactiveInsertByIdSupport<>(template, domainType, scope, collection, options, persistTo, replicateTo,
|
||||
durabilityLevel, expiry, support);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InsertByIdWithDurability<T> withExpiry(final Duration expiry) {
|
||||
Assert.notNull(expiry, "expiry must not be null.");
|
||||
return new ReactiveInsertByIdSupport<>(template, domainType, scope, collection, options, persistTo, replicateTo,
|
||||
durabilityLevel, expiry, support);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
/*
|
||||
* Copyright 2012-2022 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;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.springframework.data.couchbase.core.support.InCollection;
|
||||
import org.springframework.data.couchbase.core.support.InScope;
|
||||
import org.springframework.data.couchbase.core.support.OneAndAllIdReactive;
|
||||
import org.springframework.data.couchbase.core.support.WithDurability;
|
||||
import org.springframework.data.couchbase.core.support.WithRemoveOptions;
|
||||
|
||||
import com.couchbase.client.core.msg.kv.DurabilityLevel;
|
||||
import com.couchbase.client.java.kv.PersistTo;
|
||||
import com.couchbase.client.java.kv.RemoveOptions;
|
||||
import com.couchbase.client.java.kv.ReplicateTo;
|
||||
|
||||
/**
|
||||
* Remove Operations on KV service.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author Michael Reiche
|
||||
* @since 2.0
|
||||
*/
|
||||
public interface ReactiveRemoveByIdOperation {
|
||||
/**
|
||||
* Removes a document.
|
||||
*/
|
||||
@Deprecated
|
||||
ReactiveRemoveById removeById();
|
||||
|
||||
/**
|
||||
* Removes a document.
|
||||
*/
|
||||
ReactiveRemoveById removeById(Class<?> domainType);
|
||||
|
||||
/**
|
||||
* Terminating operations invoking the actual execution.
|
||||
*/
|
||||
interface TerminatingRemoveById extends OneAndAllIdReactive<RemoveResult> {
|
||||
|
||||
/**
|
||||
* Remove one document based on the given ID.
|
||||
*
|
||||
* @param id the document ID.
|
||||
* @return result of the remove
|
||||
*/
|
||||
@Override
|
||||
Mono<RemoveResult> one(Object id);
|
||||
|
||||
/**
|
||||
* Remove one document. Requires whole entity for transaction to have the cas.
|
||||
*
|
||||
* @param entity the entity
|
||||
* @return result of the remove
|
||||
*/
|
||||
Mono<RemoveResult> oneEntity(Object entity);
|
||||
|
||||
/**
|
||||
* Remove the documents in the collection.
|
||||
*
|
||||
* @param ids the document IDs.
|
||||
* @return result of the removes.
|
||||
*/
|
||||
@Override
|
||||
Flux<RemoveResult> all(Collection<String> ids);
|
||||
|
||||
/**
|
||||
* Remove the documents in the collection. Requires whole entity for transaction to have the cas.
|
||||
*
|
||||
* @param entities the entities to remove.
|
||||
* @return result of the removes.
|
||||
*/
|
||||
Flux<RemoveResult> allEntities(Collection<Object> entities);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Fluent method to specify options.
|
||||
*/
|
||||
interface RemoveByIdWithOptions extends TerminatingRemoveById, WithRemoveOptions<RemoveResult> {
|
||||
/**
|
||||
* Fluent method to specify options to use for execution
|
||||
*
|
||||
* @param options options to use for execution
|
||||
*/
|
||||
TerminatingRemoveById withOptions(RemoveOptions options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fluent method to specify the collection.
|
||||
*/
|
||||
interface RemoveByIdInCollection extends RemoveByIdWithOptions, InCollection<Object> {
|
||||
/**
|
||||
* With a different collection
|
||||
*
|
||||
* @param collection the collection to use.
|
||||
*/
|
||||
RemoveByIdWithOptions inCollection(String collection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fluent method to specify the scope.
|
||||
*/
|
||||
interface RemoveByIdInScope extends RemoveByIdInCollection, InScope<Object> {
|
||||
/**
|
||||
* With a different scope
|
||||
*
|
||||
* @param scope the scope to use.
|
||||
*/
|
||||
RemoveByIdInCollection inScope(String scope);
|
||||
}
|
||||
|
||||
interface RemoveByIdWithDurability extends RemoveByIdInScope, WithDurability<RemoveResult> {
|
||||
@Override
|
||||
RemoveByIdInScope withDurability(DurabilityLevel durabilityLevel);
|
||||
|
||||
@Override
|
||||
RemoveByIdInScope withDurability(PersistTo persistTo, ReplicateTo replicateTo);
|
||||
|
||||
}
|
||||
|
||||
interface RemoveByIdWithCas extends RemoveByIdWithDurability {
|
||||
|
||||
RemoveByIdWithDurability withCas(Long cas);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides methods for constructing remove operations in a fluent way.
|
||||
*/
|
||||
interface ReactiveRemoveById extends RemoveByIdWithCas {}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
/*
|
||||
* Copyright 2012-2022 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;
|
||||
|
||||
import static com.couchbase.client.java.transactions.internal.ConverterUtil.makeCollectionIdentifier;
|
||||
|
||||
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.CouchbaseClientFactory;
|
||||
import org.springframework.data.couchbase.core.query.OptionsBuilder;
|
||||
import org.springframework.data.couchbase.core.support.PseudoArgs;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.couchbase.client.core.msg.kv.DurabilityLevel;
|
||||
import com.couchbase.client.core.transaction.CoreTransactionAttemptContext;
|
||||
import com.couchbase.client.core.transaction.CoreTransactionGetResult;
|
||||
import com.couchbase.client.java.ReactiveCollection;
|
||||
import com.couchbase.client.java.kv.PersistTo;
|
||||
import com.couchbase.client.java.kv.RemoveOptions;
|
||||
import com.couchbase.client.java.kv.ReplicateTo;
|
||||
|
||||
/**
|
||||
* {@link ReactiveRemoveByIdOperation} implementations for Couchbase.
|
||||
*
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
public class ReactiveRemoveByIdOperationSupport implements ReactiveRemoveByIdOperation {
|
||||
|
||||
private final ReactiveCouchbaseTemplate template;
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ReactiveRemoveByIdOperationSupport.class);
|
||||
|
||||
public ReactiveRemoveByIdOperationSupport(final ReactiveCouchbaseTemplate template) {
|
||||
this.template = template;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public ReactiveRemoveById removeById() {
|
||||
return removeById(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReactiveRemoveById removeById(Class<?> domainType) {
|
||||
return new ReactiveRemoveByIdSupport(template, domainType, OptionsBuilder.getScopeFrom(domainType),
|
||||
OptionsBuilder.getCollectionFrom(domainType), 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;
|
||||
private final PersistTo persistTo;
|
||||
private final ReplicateTo replicateTo;
|
||||
private final DurabilityLevel durabilityLevel;
|
||||
private final Long cas;
|
||||
|
||||
ReactiveRemoveByIdSupport(final ReactiveCouchbaseTemplate template, final Class<?> domainType, final String scope,
|
||||
final String collection, final RemoveOptions options, final PersistTo persistTo, final ReplicateTo replicateTo,
|
||||
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.cas = cas;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<RemoveResult> one(final Object id) {
|
||||
PseudoArgs<RemoveOptions> pArgs = new PseudoArgs<>(template, scope, collection, options, domainType);
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("removeById key={} {}", id, pArgs);
|
||||
}
|
||||
CouchbaseClientFactory clientFactory = template.getCouchbaseClientFactory();
|
||||
ReactiveCollection rc = clientFactory.withScope(pArgs.getScope()).getCollection(pArgs.getCollection()).reactive();
|
||||
|
||||
return TransactionalSupport.checkForTransactionInThreadLocalStorage().flatMap(s -> {
|
||||
if (!s.isPresent()) {
|
||||
return rc.remove(id.toString(), buildRemoveOptions(pArgs.getOptions())).map(r -> RemoveResult.from(id.toString(), r));
|
||||
} else {
|
||||
rejectInvalidTransactionalOptions();
|
||||
|
||||
if (cas == null || cas == 0) {
|
||||
throw new IllegalArgumentException("cas must be supplied for tx remove");
|
||||
}
|
||||
CoreTransactionAttemptContext ctx = s.get().getCore();
|
||||
Mono<CoreTransactionGetResult> gr = ctx.get(makeCollectionIdentifier(rc.async()), id.toString());
|
||||
|
||||
return gr.flatMap(getResult -> {
|
||||
if (getResult.cas() != cas) {
|
||||
return Mono.error(TransactionalSupport.retryTransactionOnCasMismatch(ctx, getResult.cas(), cas));
|
||||
}
|
||||
return ctx.remove(getResult).map(r -> new RemoveResult(id.toString(), 0, null));
|
||||
});
|
||||
|
||||
}
|
||||
}).onErrorMap(throwable -> {
|
||||
if (throwable instanceof RuntimeException) {
|
||||
return template.potentiallyConvertRuntimeException((RuntimeException) throwable);
|
||||
} else {
|
||||
return throwable;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void rejectInvalidTransactionalOptions() {
|
||||
if ((this.persistTo != null && this.persistTo != PersistTo.NONE)
|
||||
|| (this.replicateTo != null && this.replicateTo != ReplicateTo.NONE)) {
|
||||
throw new IllegalArgumentException(
|
||||
"withDurability PersistTo and ReplicateTo overload is not supported in a transaction");
|
||||
}
|
||||
if (this.durabilityLevel != null && this.durabilityLevel != DurabilityLevel.NONE) {
|
||||
throw new IllegalArgumentException("withDurability is not supported in a transaction");
|
||||
}
|
||||
if (this.options != null) {
|
||||
throw new IllegalArgumentException("withOptions is not supported in a transaction");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<RemoveResult> oneEntity(Object entity) {
|
||||
ReactiveRemoveByIdSupport op = new ReactiveRemoveByIdSupport(template, domainType, scope, collection, options,
|
||||
persistTo, replicateTo, durabilityLevel, template.support().getCas(entity));
|
||||
return op.one(template.support().getId(entity).toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<RemoveResult> all(final Collection<String> ids) {
|
||||
return Flux.fromIterable(ids).flatMap(this::one);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<RemoveResult> allEntities(Collection<Object> entities) {
|
||||
return Flux.fromIterable(entities).flatMap(this::oneEntity);
|
||||
}
|
||||
|
||||
private RemoveOptions buildRemoveOptions(RemoveOptions options) {
|
||||
return OptionsBuilder.buildRemoveOptions(options, persistTo, replicateTo, durabilityLevel, cas);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RemoveByIdInScope withDurability(final DurabilityLevel durabilityLevel) {
|
||||
Assert.notNull(durabilityLevel, "Durability Level must not be null.");
|
||||
return new ReactiveRemoveByIdSupport(template, domainType, scope, collection, options, persistTo, replicateTo,
|
||||
durabilityLevel, cas);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RemoveByIdInScope withDurability(final PersistTo persistTo, final ReplicateTo replicateTo) {
|
||||
Assert.notNull(persistTo, "PersistTo must not be null.");
|
||||
Assert.notNull(replicateTo, "ReplicateTo must not be null.");
|
||||
return new ReactiveRemoveByIdSupport(template, domainType, scope, collection, options, persistTo, replicateTo,
|
||||
durabilityLevel, cas);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RemoveByIdWithDurability inCollection(final String collection) {
|
||||
return new ReactiveRemoveByIdSupport(template, domainType, scope,
|
||||
collection != null ? collection : this.collection, options, persistTo, replicateTo, durabilityLevel, cas);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RemoveByIdInCollection inScope(final String scope) {
|
||||
return new ReactiveRemoveByIdSupport(template, domainType, scope != null ? scope : this.scope, collection,
|
||||
options, persistTo, replicateTo, durabilityLevel, cas);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TerminatingRemoveById withOptions(final RemoveOptions options) {
|
||||
Assert.notNull(options, "Options must not be null.");
|
||||
return new ReactiveRemoveByIdSupport(template, domainType, scope, collection, options, persistTo, replicateTo,
|
||||
durabilityLevel, cas);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RemoveByIdWithDurability withCas(Long cas) {
|
||||
return new ReactiveRemoveByIdSupport(template, domainType, scope, collection, options, persistTo, replicateTo,
|
||||
durabilityLevel, cas);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import org.springframework.data.couchbase.core.query.Query;
|
||||
import org.springframework.data.couchbase.core.query.QueryCriteriaDefinition;
|
||||
import org.springframework.data.couchbase.core.support.InCollection;
|
||||
import org.springframework.data.couchbase.core.support.InScope;
|
||||
import org.springframework.data.couchbase.core.support.WithConsistency;
|
||||
import org.springframework.data.couchbase.core.support.WithQuery;
|
||||
import org.springframework.data.couchbase.core.support.WithQueryOptions;
|
||||
|
||||
import com.couchbase.client.java.query.QueryOptions;
|
||||
import com.couchbase.client.java.query.QueryScanConsistency;
|
||||
|
||||
/**
|
||||
* RemoveBy Query Operations
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.0
|
||||
*/
|
||||
public interface ReactiveRemoveByQueryOperation {
|
||||
|
||||
/**
|
||||
* Remove via the query service.
|
||||
*/
|
||||
<T> ReactiveRemoveByQuery<T> removeByQuery(Class<T> domainType);
|
||||
|
||||
/**
|
||||
* Terminating operations invoking the actual execution.
|
||||
*/
|
||||
interface TerminatingRemoveByQuery<T> {
|
||||
/**
|
||||
* Remove all matching documents.
|
||||
*
|
||||
* @return RemoveResult for each matching document
|
||||
*/
|
||||
Flux<RemoveResult> all();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fluent methods to specify the query
|
||||
*
|
||||
* @param <T> the entity type.
|
||||
*/
|
||||
interface RemoveByQueryWithQuery<T> extends TerminatingRemoveByQuery<T>, WithQuery<RemoveResult> {
|
||||
|
||||
TerminatingRemoveByQuery<T> matching(Query query);
|
||||
|
||||
default TerminatingRemoveByQuery<T> matching(QueryCriteriaDefinition criteria) {
|
||||
return matching(Query.query(criteria));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fluent method to specify options.
|
||||
*
|
||||
* @param <T> the entity type to use for the results.
|
||||
*/
|
||||
interface RemoveByQueryWithOptions<T> extends RemoveByQueryWithQuery<T>, WithQueryOptions<RemoveResult> {
|
||||
/**
|
||||
* Fluent method to specify options to use for execution
|
||||
*
|
||||
* @param options to use for execution
|
||||
*/
|
||||
RemoveByQueryWithQuery<T> withOptions(QueryOptions options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fluent method to specify the collection.
|
||||
*
|
||||
* @param <T> the entity type to use for the results.
|
||||
*/
|
||||
interface RemoveByQueryInCollection<T> extends RemoveByQueryWithOptions<T>, InCollection<Object> {
|
||||
/**
|
||||
* With a different collection
|
||||
*
|
||||
* @param collection the collection to use.
|
||||
*/
|
||||
RemoveByQueryWithOptions<T> inCollection(String collection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fluent method to specify the scope.
|
||||
*
|
||||
* @param <T> the entity type to use for the results.
|
||||
*/
|
||||
interface RemoveByQueryInScope<T> extends RemoveByQueryInCollection<T>, InScope<Object> {
|
||||
/**
|
||||
* With a different scope
|
||||
*
|
||||
* @param scope the scope to use.
|
||||
*/
|
||||
RemoveByQueryInCollection<T> inScope(String scope);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
interface RemoveByQueryConsistentWith<T> extends RemoveByQueryInScope<T> {
|
||||
|
||||
@Deprecated
|
||||
RemoveByQueryInScope<T> consistentWith(QueryScanConsistency scanConsistency);
|
||||
|
||||
}
|
||||
|
||||
interface RemoveByQueryWithConsistency<T> extends RemoveByQueryConsistentWith<T>, WithConsistency<RemoveResult> {
|
||||
@Override
|
||||
RemoveByQueryConsistentWith<T> withConsistency(QueryScanConsistency scanConsistency);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides methods for constructing query operations in a fluent way.
|
||||
*
|
||||
* @param <T> the entity type.
|
||||
*/
|
||||
interface ReactiveRemoveByQuery<T> extends RemoveByQueryWithConsistency<T> {}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
/*
|
||||
* Copyright 2012-2022 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;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.data.couchbase.CouchbaseClientFactory;
|
||||
import org.springframework.data.couchbase.core.query.OptionsBuilder;
|
||||
import org.springframework.data.couchbase.core.query.Query;
|
||||
import org.springframework.data.couchbase.core.support.PseudoArgs;
|
||||
import org.springframework.data.couchbase.core.support.TemplateUtils;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.couchbase.client.core.deps.com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import com.couchbase.client.java.ReactiveScope;
|
||||
import com.couchbase.client.java.json.JsonObject;
|
||||
import com.couchbase.client.java.query.QueryOptions;
|
||||
import com.couchbase.client.java.query.QueryScanConsistency;
|
||||
import com.couchbase.client.java.query.ReactiveQueryResult;
|
||||
import com.couchbase.client.java.transactions.TransactionQueryOptions;
|
||||
|
||||
/**
|
||||
* {@link ReactiveRemoveByQueryOperation} implementations for Couchbase.
|
||||
*
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
public class ReactiveRemoveByQueryOperationSupport implements ReactiveRemoveByQueryOperation {
|
||||
|
||||
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) {
|
||||
this.template = template;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> ReactiveRemoveByQuery<T> removeByQuery(Class<T> domainType) {
|
||||
return new ReactiveRemoveByQuerySupport<>(template, domainType, ALL_QUERY, null,
|
||||
OptionsBuilder.getScopeFrom(domainType), OptionsBuilder.getCollectionFrom(domainType), null);
|
||||
}
|
||||
|
||||
static class ReactiveRemoveByQuerySupport<T> implements ReactiveRemoveByQuery<T> {
|
||||
|
||||
private final ReactiveCouchbaseTemplate template;
|
||||
private final Class<T> domainType;
|
||||
private final Query query;
|
||||
private final QueryScanConsistency scanConsistency;
|
||||
private final String scope;
|
||||
private final String collection;
|
||||
private final QueryOptions options;
|
||||
|
||||
ReactiveRemoveByQuerySupport(final ReactiveCouchbaseTemplate template, final Class<T> domainType, final Query query,
|
||||
final QueryScanConsistency scanConsistency, String scope, String collection, QueryOptions options) {
|
||||
this.template = template;
|
||||
this.domainType = domainType;
|
||||
this.query = query;
|
||||
this.scanConsistency = scanConsistency;
|
||||
this.scope = scope;
|
||||
this.collection = collection;
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<RemoveResult> all() {
|
||||
PseudoArgs<QueryOptions> pArgs = new PseudoArgs<>(template, scope, collection, options, domainType);
|
||||
String statement = assembleDeleteQuery(pArgs.getScope(), pArgs.getCollection());
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("removeByQuery {} statement: {}", pArgs, statement);
|
||||
}
|
||||
CouchbaseClientFactory clientFactory = template.getCouchbaseClientFactory();
|
||||
ReactiveScope rs = clientFactory.withScope(pArgs.getScope()).getScope().reactive();
|
||||
|
||||
return TransactionalSupport.checkForTransactionInThreadLocalStorage().flatMapMany(transactionContext -> {
|
||||
|
||||
if (!transactionContext.isPresent()) {
|
||||
QueryOptions opts = buildQueryOptions(pArgs.getOptions());
|
||||
return (pArgs.getScope() == null ? clientFactory.getCluster().reactive().query(statement, opts)
|
||||
: rs.query(statement, opts)).flatMapMany(ReactiveQueryResult::rowsAsObject)
|
||||
.map(row -> new RemoveResult(row.getString(TemplateUtils.SELECT_ID),
|
||||
row.getLong(TemplateUtils.SELECT_CAS), Optional.empty()));
|
||||
} else {
|
||||
TransactionQueryOptions opts = OptionsBuilder
|
||||
.buildTransactionQueryOptions(buildQueryOptions(pArgs.getOptions()));
|
||||
ObjectNode convertedOptions = com.couchbase.client.java.transactions.internal.OptionsUtil
|
||||
.createTransactionOptions(pArgs.getScope() == null ? null : rs, statement, opts);
|
||||
return transactionContext.get().getCore()
|
||||
.queryBlocking(statement, template.getBucketName(), pArgs.getScope(), convertedOptions, false)
|
||||
.flatMapIterable(result -> result.rows).map(row -> {
|
||||
JsonObject json = JsonObject.fromJson(row.data());
|
||||
return new RemoveResult(json.getString(TemplateUtils.SELECT_ID), json.getLong(TemplateUtils.SELECT_CAS),
|
||||
Optional.empty());
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private QueryOptions buildQueryOptions(QueryOptions options) {
|
||||
QueryScanConsistency qsc = scanConsistency != null ? scanConsistency : template.getConsistency();
|
||||
return query.buildQueryOptions(options, qsc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TerminatingRemoveByQuery<T> matching(final Query query) {
|
||||
return new ReactiveRemoveByQuerySupport<>(template, domainType, query, scanConsistency, scope, collection,
|
||||
options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RemoveByQueryWithConsistency<T> inCollection(final String collection) {
|
||||
return new ReactiveRemoveByQuerySupport<>(template, domainType, query, scanConsistency, scope,
|
||||
collection != null ? collection : this.collection, options);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public RemoveByQueryInScope<T> consistentWith(final QueryScanConsistency scanConsistency) {
|
||||
return new ReactiveRemoveByQuerySupport<>(template, domainType, query, scanConsistency, scope, collection,
|
||||
options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RemoveByQueryConsistentWith<T> withConsistency(final QueryScanConsistency scanConsistency) {
|
||||
return new ReactiveRemoveByQuerySupport<>(template, domainType, query, scanConsistency, scope, collection,
|
||||
options);
|
||||
}
|
||||
|
||||
private String assembleDeleteQuery(String scope, String collection) {
|
||||
return query.toN1qlRemoveString(template.getConverter(), template.getBucketName(), scope, collection,
|
||||
this.domainType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RemoveByQueryWithQuery<T> withOptions(final QueryOptions options) {
|
||||
Assert.notNull(options, "Options must not be null.");
|
||||
return new ReactiveRemoveByQuerySupport<>(template, domainType, query, scanConsistency, scope, collection,
|
||||
options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RemoveByQueryInCollection<T> inScope(final String scope) {
|
||||
return new ReactiveRemoveByQuerySupport<>(template, domainType, query, scanConsistency,
|
||||
scope != null ? scope : this.scope, collection, options);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
* Copyright 2012-2022 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;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Collection;
|
||||
|
||||
import org.springframework.data.couchbase.core.support.InCollection;
|
||||
import org.springframework.data.couchbase.core.support.InScope;
|
||||
import org.springframework.data.couchbase.core.support.OneAndAllEntityReactive;
|
||||
import org.springframework.data.couchbase.core.support.WithDurability;
|
||||
import org.springframework.data.couchbase.core.support.WithExpiry;
|
||||
import org.springframework.data.couchbase.core.support.WithReplaceOptions;
|
||||
|
||||
import com.couchbase.client.core.msg.kv.DurabilityLevel;
|
||||
import com.couchbase.client.java.kv.PersistTo;
|
||||
import com.couchbase.client.java.kv.ReplaceOptions;
|
||||
import com.couchbase.client.java.kv.ReplicateTo;
|
||||
|
||||
/**
|
||||
* ReplaceOperations
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.0
|
||||
*/
|
||||
public interface ReactiveReplaceByIdOperation {
|
||||
|
||||
/**
|
||||
* Replace using the KV service.
|
||||
*
|
||||
* @param domainType the entity type to replace.
|
||||
*/
|
||||
<T> ReactiveReplaceById<T> replaceById(Class<T> domainType);
|
||||
|
||||
/**
|
||||
* Terminating operations invoking the actual execution.
|
||||
*/
|
||||
interface TerminatingReplaceById<T> extends OneAndAllEntityReactive<T> {
|
||||
|
||||
/**
|
||||
* Replace one entity.
|
||||
*
|
||||
* @return Replaced entity.
|
||||
*/
|
||||
Mono<T> one(T object);
|
||||
|
||||
/**
|
||||
* Replace a collection of entities.
|
||||
*
|
||||
* @return Replaced entities
|
||||
*/
|
||||
Flux<? extends T> all(Collection<? extends T> objects);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Fluent method to specify options.
|
||||
*
|
||||
* @param <T> the entity type to use for the results.
|
||||
*/
|
||||
interface ReplaceByIdWithOptions<T> extends TerminatingReplaceById<T>, WithReplaceOptions<RemoveResult> {
|
||||
/**
|
||||
* Fluent method to specify options to use for execution
|
||||
*
|
||||
* @param options to use for execution
|
||||
*/
|
||||
@Override
|
||||
TerminatingReplaceById<T> withOptions(ReplaceOptions options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fluent method to specify the collection.
|
||||
*
|
||||
* @param <T> the entity type to use for the results.
|
||||
*/
|
||||
interface ReplaceByIdInCollection<T> extends ReplaceByIdWithOptions<T>, InCollection<Object> {
|
||||
/**
|
||||
* With a different collection
|
||||
*
|
||||
* @param collection the collection to use.
|
||||
*/
|
||||
@Override
|
||||
ReplaceByIdWithOptions<T> inCollection(String collection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fluent method to specify the scope.
|
||||
*
|
||||
* @param <T> the entity type to use for the results.
|
||||
*/
|
||||
interface ReplaceByIdInScope<T> extends ReplaceByIdInCollection<T>, InScope<Object> {
|
||||
/**
|
||||
* With a different scope
|
||||
*
|
||||
* @param scope the scope to use.
|
||||
*/
|
||||
@Override
|
||||
ReplaceByIdInCollection<T> inScope(String scope);
|
||||
}
|
||||
|
||||
interface ReplaceByIdWithDurability<T> extends ReplaceByIdInScope<T>, WithDurability<T> {
|
||||
|
||||
ReplaceByIdInScope<T> withDurability(DurabilityLevel durabilityLevel);
|
||||
|
||||
ReplaceByIdInScope<T> withDurability(PersistTo persistTo, ReplicateTo replicateTo);
|
||||
|
||||
}
|
||||
|
||||
interface ReplaceByIdWithExpiry<T> extends ReplaceByIdWithDurability<T>, WithExpiry<T> {
|
||||
|
||||
ReplaceByIdWithDurability<T> withExpiry(final Duration expiry);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides methods for constructing KV replace operations in a fluent way.
|
||||
*
|
||||
* @param <T> the entity type to replace
|
||||
*/
|
||||
interface ReactiveReplaceById<T> extends ReplaceByIdWithExpiry<T> {}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
/*
|
||||
* Copyright 2012-2022 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;
|
||||
|
||||
import static com.couchbase.client.java.transactions.internal.ConverterUtil.makeCollectionIdentifier;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
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;
|
||||
|
||||
import com.couchbase.client.core.io.CollectionIdentifier;
|
||||
import com.couchbase.client.core.msg.kv.DurabilityLevel;
|
||||
import com.couchbase.client.core.transaction.CoreTransactionAttemptContext;
|
||||
import com.couchbase.client.core.transaction.CoreTransactionGetResult;
|
||||
import com.couchbase.client.core.transaction.util.DebugUtil;
|
||||
import com.couchbase.client.java.kv.PersistTo;
|
||||
import com.couchbase.client.java.kv.ReplaceOptions;
|
||||
import com.couchbase.client.java.kv.ReplicateTo;
|
||||
|
||||
/**
|
||||
* {@link ReactiveReplaceByIdOperation} implementations for Couchbase.
|
||||
*
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
public class ReactiveReplaceByIdOperationSupport implements ReactiveReplaceByIdOperation {
|
||||
|
||||
private final ReactiveCouchbaseTemplate template;
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ReactiveReplaceByIdOperationSupport.class);
|
||||
|
||||
public ReactiveReplaceByIdOperationSupport(final ReactiveCouchbaseTemplate template) {
|
||||
this.template = template;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> ReactiveReplaceById<T> replaceById(final Class<T> domainType) {
|
||||
Assert.notNull(domainType, "DomainType must not be null!");
|
||||
return new ReactiveReplaceByIdSupport<>(template, domainType, OptionsBuilder.getScopeFrom(domainType),
|
||||
OptionsBuilder.getCollectionFrom(domainType), null, PersistTo.NONE, ReplicateTo.NONE, DurabilityLevel.NONE,
|
||||
null, template.support());
|
||||
}
|
||||
|
||||
static class ReactiveReplaceByIdSupport<T> implements ReactiveReplaceById<T> {
|
||||
|
||||
private final ReactiveCouchbaseTemplate template;
|
||||
private final Class<T> domainType;
|
||||
private final String scope;
|
||||
private final String collection;
|
||||
private final ReplaceOptions options;
|
||||
private final PersistTo persistTo;
|
||||
private final ReplicateTo replicateTo;
|
||||
private final DurabilityLevel durabilityLevel;
|
||||
private final Duration expiry;
|
||||
private final ReactiveTemplateSupport support;
|
||||
|
||||
ReactiveReplaceByIdSupport(final ReactiveCouchbaseTemplate template, final Class<T> domainType, final String scope,
|
||||
final String collection, final ReplaceOptions options, final PersistTo persistTo, final ReplicateTo replicateTo,
|
||||
final DurabilityLevel durabilityLevel, final Duration expiry, ReactiveTemplateSupport support) {
|
||||
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.expiry = expiry;
|
||||
this.support = support;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<T> one(T object) {
|
||||
PseudoArgs<ReplaceOptions> pArgs = new PseudoArgs<>(template, scope, collection, options, domainType);
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("replaceById object={} {}", object, pArgs);
|
||||
}
|
||||
return Mono
|
||||
.just(template.getCouchbaseClientFactory().withScope(pArgs.getScope()).getCollection(pArgs.getCollection()))
|
||||
.flatMap(collection -> support.encodeEntity(object)
|
||||
.flatMap(converted -> TransactionalSupport.checkForTransactionInThreadLocalStorage().flatMap(ctxOpt -> {
|
||||
if (!ctxOpt.isPresent()) {
|
||||
return collection.reactive()
|
||||
.replace(converted.getId().toString(), converted.export(),
|
||||
buildReplaceOptions(pArgs.getOptions(), object, converted))
|
||||
.flatMap(result -> support.applyResult(object, converted, converted.getId(), result.cas(), null,
|
||||
null));
|
||||
} else {
|
||||
rejectInvalidTransactionalOptions();
|
||||
|
||||
Long cas = support.getCas(object);
|
||||
if (cas == null || cas == 0) {
|
||||
throw new IllegalArgumentException(
|
||||
"cas must be supplied in object for tx replace. object=" + object);
|
||||
}
|
||||
|
||||
CollectionIdentifier collId = makeCollectionIdentifier(collection.async());
|
||||
CoreTransactionAttemptContext ctx = ctxOpt.get().getCore();
|
||||
ctx.logger().info(ctx.attemptId(), "refetching %s for Spring replace",
|
||||
DebugUtil.docId(collId, converted.getId().toString()));
|
||||
Mono<CoreTransactionGetResult> gr = ctx.get(collId, converted.getId().toString());
|
||||
|
||||
return gr.flatMap(getResult -> {
|
||||
if (getResult.cas() != cas) {
|
||||
return Mono.error(TransactionalSupport.retryTransactionOnCasMismatch(ctx, getResult.cas(), cas));
|
||||
}
|
||||
return ctx.replace(getResult, template.getCouchbaseClientFactory().getCluster().environment()
|
||||
.transcoder().encode(converted.export()).encoded());
|
||||
}).flatMap(result -> support.applyResult(object, converted, converted.getId(), result.cas(), null, null));
|
||||
}
|
||||
})).onErrorMap(throwable -> {
|
||||
if (throwable instanceof RuntimeException) {
|
||||
return template.potentiallyConvertRuntimeException((RuntimeException) throwable);
|
||||
} else {
|
||||
return throwable;
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private void rejectInvalidTransactionalOptions() {
|
||||
if ((this.persistTo != null && this.persistTo != PersistTo.NONE)
|
||||
|| (this.replicateTo != null && this.replicateTo != ReplicateTo.NONE)) {
|
||||
throw new IllegalArgumentException(
|
||||
"withDurability PersistTo and ReplicateTo overload is not supported in a transaction");
|
||||
}
|
||||
if (this.expiry != null) {
|
||||
throw new IllegalArgumentException("withExpiry is not supported in a transaction");
|
||||
}
|
||||
if (this.durabilityLevel != null && this.durabilityLevel != DurabilityLevel.NONE) {
|
||||
throw new IllegalArgumentException("withDurability is not supported in a transaction");
|
||||
}
|
||||
if (this.options != null) {
|
||||
throw new IllegalArgumentException("withOptions is not supported in a transaction");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<? extends T> all(Collection<? extends T> objects) {
|
||||
return Flux.fromIterable(objects).flatMap(this::one);
|
||||
}
|
||||
|
||||
private ReplaceOptions buildReplaceOptions(ReplaceOptions options, T object, CouchbaseDocument doc) {
|
||||
return OptionsBuilder.buildReplaceOptions(options, persistTo, replicateTo, durabilityLevel, expiry,
|
||||
support.getCas(object), doc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TerminatingReplaceById<T> withOptions(final ReplaceOptions options) {
|
||||
Assert.notNull(options, "Options must not be null.");
|
||||
return new ReactiveReplaceByIdSupport<>(template, domainType, scope, collection, options, persistTo, replicateTo,
|
||||
durabilityLevel, expiry, support);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReplaceByIdWithDurability<T> inCollection(final String collection) {
|
||||
return new ReactiveReplaceByIdSupport<>(template, domainType, scope,
|
||||
collection != null ? collection : this.collection, options, persistTo, replicateTo, durabilityLevel, expiry,
|
||||
support);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReplaceByIdInCollection<T> inScope(final String scope) {
|
||||
return new ReactiveReplaceByIdSupport<>(template, domainType, scope != null ? scope : this.scope, collection,
|
||||
options, persistTo, replicateTo, durabilityLevel, expiry, support);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReplaceByIdInScope<T> withDurability(final DurabilityLevel durabilityLevel) {
|
||||
Assert.notNull(durabilityLevel, "Durability Level must not be null.");
|
||||
return new ReactiveReplaceByIdSupport<>(template, domainType, scope, collection, options, persistTo, replicateTo,
|
||||
durabilityLevel, expiry, support);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReplaceByIdInScope<T> 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 ReactiveReplaceByIdSupport<>(template, domainType, scope, collection, options, persistTo, replicateTo,
|
||||
durabilityLevel, expiry, support);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReplaceByIdWithDurability<T> withExpiry(final Duration expiry) {
|
||||
Assert.notNull(expiry, "expiry must not be null.");
|
||||
return new ReactiveReplaceByIdSupport<>(template, domainType, scope, collection, options, persistTo, replicateTo,
|
||||
durabilityLevel, expiry, support);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2021-2022 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;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.data.couchbase.core.convert.translation.TranslationService;
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
|
||||
import org.springframework.data.couchbase.transaction.CouchbaseResourceHolder;
|
||||
|
||||
/**
|
||||
* ReactiveTemplateSupport
|
||||
*
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
public interface ReactiveTemplateSupport {
|
||||
|
||||
Mono<CouchbaseDocument> encodeEntity(Object entityToEncode);
|
||||
|
||||
<T> Mono<T> decodeEntity(Object id, String source, Long cas, Class<T> entityClass, String scope, String collection,
|
||||
Object txResultHolder, CouchbaseResourceHolder holder);
|
||||
|
||||
<T> Mono<T> applyResult(T entity, CouchbaseDocument converted, Object id, Long cas,
|
||||
Object txResultHolder, CouchbaseResourceHolder holder);
|
||||
|
||||
Long getCas(Object entity);
|
||||
|
||||
Object getId(Object entity);
|
||||
|
||||
String getJavaNameForEntity(Class<?> clazz);
|
||||
|
||||
TranslationService getTranslationService();
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
/*
|
||||
* Copyright 2012-2022 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;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Collection;
|
||||
|
||||
import org.springframework.data.couchbase.core.support.InCollection;
|
||||
import org.springframework.data.couchbase.core.support.InScope;
|
||||
import org.springframework.data.couchbase.core.support.OneAndAllEntityReactive;
|
||||
import org.springframework.data.couchbase.core.support.WithDurability;
|
||||
import org.springframework.data.couchbase.core.support.WithExpiry;
|
||||
import org.springframework.data.couchbase.core.support.WithUpsertOptions;
|
||||
|
||||
import com.couchbase.client.core.msg.kv.DurabilityLevel;
|
||||
import com.couchbase.client.java.kv.PersistTo;
|
||||
import com.couchbase.client.java.kv.ReplicateTo;
|
||||
import com.couchbase.client.java.kv.UpsertOptions;
|
||||
|
||||
/**
|
||||
* Upsert Operations
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.0
|
||||
*/
|
||||
public interface ReactiveUpsertByIdOperation {
|
||||
|
||||
/**
|
||||
* Upsert using the KV service.
|
||||
*
|
||||
* @param domainType the entity type to upsert.
|
||||
*/
|
||||
<T> ReactiveUpsertById<T> upsertById(Class<T> domainType);
|
||||
|
||||
/**
|
||||
* Terminating operations invoking the actual execution.
|
||||
*/
|
||||
interface TerminatingUpsertById<T> extends OneAndAllEntityReactive<T> {
|
||||
|
||||
/**
|
||||
* Upsert one entity.
|
||||
*
|
||||
* @return Upserted entity.
|
||||
*/
|
||||
@Override
|
||||
Mono<T> one(T object);
|
||||
|
||||
/**
|
||||
* Insert a collection of entities.
|
||||
*
|
||||
* @return Inserted entities
|
||||
*/
|
||||
@Override
|
||||
Flux<? extends T> all(Collection<? extends T> objects);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Fluent method to specify options.
|
||||
*
|
||||
* @param <T> the entity type to use.
|
||||
*/
|
||||
interface UpsertByIdWithOptions<T> extends TerminatingUpsertById<T>, WithUpsertOptions<T> {
|
||||
/**
|
||||
* Fluent method to specify options to use for execution
|
||||
*
|
||||
* @param options to use for execution
|
||||
*/
|
||||
@Override
|
||||
TerminatingUpsertById<T> withOptions(UpsertOptions options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fluent method to specify the collection.
|
||||
*
|
||||
* @param <T> the entity type to use for the results.
|
||||
*/
|
||||
interface UpsertByIdInCollection<T> extends UpsertByIdWithOptions<T>, InCollection<Object> {
|
||||
/**
|
||||
* With a different collection
|
||||
*
|
||||
* @param collection the collection to use.
|
||||
*/
|
||||
@Override
|
||||
UpsertByIdWithOptions<T> inCollection(String collection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fluent method to specify the scope.
|
||||
*
|
||||
* @param <T> the entity type to use for the results.
|
||||
*/
|
||||
interface UpsertByIdInScope<T> extends UpsertByIdInCollection<T>, InScope<Object> {
|
||||
/**
|
||||
* With a different scope
|
||||
*
|
||||
* @param scope the scope to use.
|
||||
*/
|
||||
@Override
|
||||
UpsertByIdInCollection<T> inScope(String scope);
|
||||
}
|
||||
|
||||
interface UpsertByIdWithDurability<T> extends UpsertByIdInScope<T>, WithDurability<T> {
|
||||
@Override
|
||||
UpsertByIdInScope<T> withDurability(DurabilityLevel durabilityLevel);
|
||||
|
||||
@Override
|
||||
UpsertByIdInScope<T> withDurability(PersistTo persistTo, ReplicateTo replicateTo);
|
||||
|
||||
}
|
||||
|
||||
interface UpsertByIdWithExpiry<T> extends UpsertByIdWithDurability<T>, WithExpiry<T> {
|
||||
@Override
|
||||
UpsertByIdWithDurability<T> withExpiry(Duration expiry);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides methods for constructing KV operations in a fluent way.
|
||||
*
|
||||
* @param <T> the entity type to upsert
|
||||
*/
|
||||
interface ReactiveUpsertById<T> extends UpsertByIdWithExpiry<T> {}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
/*
|
||||
* Copyright 2012-2022 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;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
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;
|
||||
|
||||
import com.couchbase.client.core.msg.kv.DurabilityLevel;
|
||||
import com.couchbase.client.java.kv.PersistTo;
|
||||
import com.couchbase.client.java.kv.ReplicateTo;
|
||||
import com.couchbase.client.java.kv.UpsertOptions;
|
||||
|
||||
/**
|
||||
* {@link ReactiveUpsertByIdOperation} implementations for Couchbase.
|
||||
*
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> ReactiveUpsertById<T> upsertById(final Class<T> domainType) {
|
||||
Assert.notNull(domainType, "DomainType must not be null!");
|
||||
return new ReactiveUpsertByIdSupport<>(template, domainType, OptionsBuilder.getScopeFrom(domainType),
|
||||
OptionsBuilder.getCollectionFrom(domainType), null, PersistTo.NONE, ReplicateTo.NONE, DurabilityLevel.NONE,
|
||||
null, template.support());
|
||||
}
|
||||
|
||||
static class ReactiveUpsertByIdSupport<T> implements ReactiveUpsertById<T> {
|
||||
|
||||
private final ReactiveCouchbaseTemplate template;
|
||||
private final Class<T> domainType;
|
||||
private final String scope;
|
||||
private final String collection;
|
||||
private final UpsertOptions options;
|
||||
private final PersistTo persistTo;
|
||||
private final ReplicateTo replicateTo;
|
||||
private final DurabilityLevel durabilityLevel;
|
||||
private final Duration expiry;
|
||||
private final ReactiveTemplateSupport support;
|
||||
|
||||
ReactiveUpsertByIdSupport(final ReactiveCouchbaseTemplate template, final Class<T> domainType, final String scope,
|
||||
final String collection, final UpsertOptions options, final PersistTo persistTo, final ReplicateTo replicateTo,
|
||||
final DurabilityLevel durabilityLevel, final Duration expiry, ReactiveTemplateSupport support) {
|
||||
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.expiry = expiry;
|
||||
this.support = support;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<T> one(T object) {
|
||||
PseudoArgs<UpsertOptions> pArgs = new PseudoArgs(template, scope, collection, options, domainType);
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("upsertById object={} {}", object, pArgs);
|
||||
}
|
||||
Mono<T> reactiveEntity = TransactionalSupport.verifyNotInTransaction("upsertById")
|
||||
.then(support.encodeEntity(object)).flatMap(converted -> {
|
||||
return Mono
|
||||
.just(template.getCouchbaseClientFactory().withScope(pArgs.getScope())
|
||||
.getCollection(pArgs.getCollection()))
|
||||
.flatMap(collection -> collection.reactive()
|
||||
.upsert(converted.getId().toString(), converted.export(), buildUpsertOptions(pArgs.getOptions(), converted))
|
||||
.flatMap(
|
||||
result -> support.applyResult(object, converted, converted.getId(), result.cas(), null, null)));
|
||||
});
|
||||
|
||||
return reactiveEntity.onErrorMap(throwable -> {
|
||||
if (throwable instanceof RuntimeException) {
|
||||
return template.potentiallyConvertRuntimeException((RuntimeException) throwable);
|
||||
} else {
|
||||
return throwable;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<? extends T> all(Collection<? extends T> objects) {
|
||||
return Flux.fromIterable(objects).flatMap(this::one);
|
||||
}
|
||||
|
||||
private UpsertOptions buildUpsertOptions(UpsertOptions options, CouchbaseDocument doc) {
|
||||
return OptionsBuilder.buildUpsertOptions(options, persistTo, replicateTo, durabilityLevel, expiry, doc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TerminatingUpsertById<T> withOptions(final UpsertOptions options) {
|
||||
Assert.notNull(options, "Options must not be null.");
|
||||
return new ReactiveUpsertByIdSupport(template, domainType, scope, collection, options, persistTo, replicateTo,
|
||||
durabilityLevel, expiry, support);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UpsertByIdWithDurability<T> inCollection(final String collection) {
|
||||
return new ReactiveUpsertByIdSupport<>(template, domainType, scope,
|
||||
collection != null ? collection : this.collection, options, persistTo, replicateTo, durabilityLevel, expiry,
|
||||
support);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UpsertByIdInCollection<T> inScope(final String scope) {
|
||||
return new ReactiveUpsertByIdSupport<>(template, domainType, scope != null ? scope : this.scope, collection,
|
||||
options, persistTo, replicateTo, durabilityLevel, expiry, support);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UpsertByIdInScope<T> withDurability(final DurabilityLevel durabilityLevel) {
|
||||
Assert.notNull(durabilityLevel, "Durability Level must not be null.");
|
||||
return new ReactiveUpsertByIdSupport<>(template, domainType, scope, collection, options, persistTo, replicateTo,
|
||||
durabilityLevel, expiry, support);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UpsertByIdInScope<T> 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 ReactiveUpsertByIdSupport<>(template, domainType, scope, collection, options, persistTo, replicateTo,
|
||||
durabilityLevel, expiry, support);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UpsertByIdWithDurability<T> withExpiry(final Duration expiry) {
|
||||
Assert.notNull(expiry, "expiry must not be null.");
|
||||
return new ReactiveUpsertByIdSupport<>(template, domainType, scope, collection, options, persistTo, replicateTo,
|
||||
durabilityLevel, expiry, support);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
import com.couchbase.client.core.msg.kv.MutationToken;
|
||||
import com.couchbase.client.java.kv.MutationResult;
|
||||
|
||||
public class RemoveResult {
|
||||
|
||||
private final String id;
|
||||
private final long cas;
|
||||
private final Optional<MutationToken> mutationToken;
|
||||
|
||||
public RemoveResult(String id, long cas, Optional<MutationToken> mutationToken) {
|
||||
this.id = id;
|
||||
this.cas = cas;
|
||||
this.mutationToken = mutationToken;
|
||||
}
|
||||
|
||||
public static RemoveResult from(final String id, final MutationResult result) {
|
||||
return new RemoveResult(id, result.cas(), result.mutationToken());
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public long getCas() {
|
||||
return cas;
|
||||
}
|
||||
|
||||
public Optional<MutationToken> getMutationToken() {
|
||||
return mutationToken;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "RemoveResult{" + "id='" + id + '\'' + ", cas=" + cas + ", mutationToken=" + mutationToken + '}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o)
|
||||
return true;
|
||||
if (o == null || getClass() != o.getClass())
|
||||
return false;
|
||||
RemoveResult that = (RemoveResult) o;
|
||||
return cas == that.cas && Objects.equals(id, that.id) && Objects.equals(mutationToken, that.mutationToken);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(id, cas, mutationToken);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2021-2022 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;
|
||||
|
||||
import org.springframework.data.couchbase.core.convert.translation.TranslationService;
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
|
||||
import org.springframework.data.couchbase.transaction.CouchbaseResourceHolder;
|
||||
|
||||
/**
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
public interface TemplateSupport {
|
||||
|
||||
CouchbaseDocument encodeEntity(Object entityToEncode);
|
||||
|
||||
<T> T decodeEntity(Object id, String source, Long cas, Class<T> entityClass, String scope, String collection,
|
||||
Object txResultHolder, CouchbaseResourceHolder holder);
|
||||
|
||||
<T> T applyResult(T entity, CouchbaseDocument converted, Object id, long cas, Object txResultHolder,
|
||||
CouchbaseResourceHolder holder);
|
||||
|
||||
Long getCas(Object entity);
|
||||
|
||||
Object getId(Object entity);
|
||||
|
||||
String getJavaNameForEntity(Class<?> clazz);
|
||||
|
||||
<T> Integer getTxResultHolder(T source);
|
||||
|
||||
TranslationService getTranslationService();
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright 2022 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;
|
||||
|
||||
import com.couchbase.client.core.transaction.threadlocal.TransactionMarker;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.data.couchbase.transaction.CouchbaseResourceHolder;
|
||||
|
||||
import com.couchbase.client.core.annotation.Stability;
|
||||
import com.couchbase.client.core.error.CasMismatchException;
|
||||
import com.couchbase.client.core.error.transaction.TransactionOperationFailedException;
|
||||
import com.couchbase.client.core.transaction.CoreTransactionAttemptContext;
|
||||
import com.couchbase.client.core.transaction.threadlocal.TransactionMarkerOwner;
|
||||
import reactor.util.context.ContextView;
|
||||
|
||||
/**
|
||||
* Utility methods to support transactions.
|
||||
*
|
||||
* @author Graham Pople
|
||||
*/
|
||||
@Stability.Internal
|
||||
public class TransactionalSupport {
|
||||
|
||||
/**
|
||||
* Returns non-empty iff in a transaction. It determines this from thread-local storage and/or reactive context.
|
||||
* <p>
|
||||
* The user could be doing a reactive operation (with .block()) inside a blocking transaction (like @Transactional).
|
||||
* Or a blocking operation inside a ReactiveTransactionsWrapper transaction (which would be a bad idea). So, need to
|
||||
* check both thread-local storage and reactive context.
|
||||
*/
|
||||
public static Mono<Optional<CouchbaseResourceHolder>> checkForTransactionInThreadLocalStorage() {
|
||||
return TransactionMarkerOwner.get().flatMap(markerOpt -> {
|
||||
Optional<CouchbaseResourceHolder> out = markerOpt
|
||||
.flatMap(marker -> Optional.of(new CouchbaseResourceHolder(marker.context())));
|
||||
return Mono.just(out);
|
||||
});
|
||||
}
|
||||
|
||||
public static Mono<Void> verifyNotInTransaction(String methodName) {
|
||||
return checkForTransactionInThreadLocalStorage().flatMap(s -> {
|
||||
if (s.isPresent()) {
|
||||
return Mono.error(new IllegalArgumentException(methodName + " can not be used inside a transaction"));
|
||||
} else {
|
||||
return Mono.empty();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static RuntimeException retryTransactionOnCasMismatch(CoreTransactionAttemptContext ctx, long cas1,
|
||||
long cas2) {
|
||||
try {
|
||||
ctx.logger().info(ctx.attemptId(), "Spring CAS mismatch %s != %s, retrying transaction", cas1, cas2);
|
||||
TransactionOperationFailedException err = TransactionOperationFailedException.Builder.createError()
|
||||
.retryTransaction().cause(new CasMismatchException(null)).build();
|
||||
return ctx.operationFailed(err);
|
||||
} catch (Throwable err) {
|
||||
return new RuntimeException(err);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
import org.springframework.dao.NonTransientDataAccessException;
|
||||
|
||||
import com.couchbase.client.core.service.ServiceType;
|
||||
|
||||
/**
|
||||
* A {@link NonTransientDataAccessException} that denotes that a particular feature is expected on the server side but
|
||||
* is not available.
|
||||
*/
|
||||
public class UnsupportedCouchbaseFeatureException extends InvalidDataAccessApiUsageException {
|
||||
|
||||
private final ServiceType feature;
|
||||
|
||||
public UnsupportedCouchbaseFeatureException(String msg, ServiceType feature) {
|
||||
super(msg);
|
||||
this.feature = feature;
|
||||
}
|
||||
|
||||
public UnsupportedCouchbaseFeatureException(String msg, ServiceType feature, Throwable cause) {
|
||||
super(msg, cause);
|
||||
this.feature = feature;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the {@link ServiceType} that was missing (could be null if not a registered CouchbaseFeature, in which case
|
||||
* see {@link #getMessage()}).
|
||||
*/
|
||||
public ServiceType getFeature() {
|
||||
return feature;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
/*
|
||||
* Copyright 2012-2022 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.convert;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import com.couchbase.client.java.query.QueryScanConsistency;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.core.convert.support.GenericConversionService;
|
||||
import org.springframework.data.convert.CustomConversions;
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty;
|
||||
import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
|
||||
import org.springframework.data.mapping.model.EntityInstantiators;
|
||||
|
||||
/**
|
||||
* An abstract {@link CouchbaseConverter} that provides the basics for the {@link MappingCouchbaseConverter}.
|
||||
*
|
||||
* @author Michael Nitschinger
|
||||
* @author Mark Paluch
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
public abstract class AbstractCouchbaseConverter implements CouchbaseConverter, InitializingBean {
|
||||
|
||||
/**
|
||||
* Contains the conversion service.
|
||||
*/
|
||||
protected final GenericConversionService conversionService;
|
||||
|
||||
/**
|
||||
* Contains the entity instantiators.
|
||||
*/
|
||||
protected EntityInstantiators instantiators = new EntityInstantiators();
|
||||
|
||||
/**
|
||||
* Holds the custom conversions.
|
||||
*/
|
||||
protected CustomConversions conversions = new CouchbaseCustomConversions(Collections.emptyList());
|
||||
|
||||
/**
|
||||
* Create a new converter and hand it over the {@link ConversionService}
|
||||
*
|
||||
* @param conversionService the conversion service to use.
|
||||
*/
|
||||
protected AbstractCouchbaseConverter(final GenericConversionService conversionService) {
|
||||
this.conversionService = conversionService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the conversion service.
|
||||
*
|
||||
* @return the conversion service.
|
||||
*/
|
||||
@Override
|
||||
public ConversionService getConversionService() {
|
||||
return conversionService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the custom conversions. Note that updating conversions requires a subsequent call to register them with the
|
||||
* conversionService: conversions.registerConvertersIn(conversionService)
|
||||
*
|
||||
* @param conversions the conversions.
|
||||
*/
|
||||
public void setCustomConversions(final CustomConversions conversions) {
|
||||
this.conversions = conversions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the entity instantiators.
|
||||
*
|
||||
* @param instantiators the instantiators.
|
||||
*/
|
||||
public void setInstantiators(final EntityInstantiators instantiators) {
|
||||
this.instantiators = instantiators;
|
||||
}
|
||||
|
||||
/**
|
||||
* Do nothing after the properties set on the bean.
|
||||
*/
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
conversions.registerConvertersIn(conversionService);
|
||||
}
|
||||
|
||||
/**
|
||||
* This convertForWriteIfNeeded takes a property and accessor so that the annotations can be accessed (ie. @Encrypted)
|
||||
*
|
||||
* @param prop the property to be converted to the class that would actually be stored.
|
||||
* @param accessor the property accessor
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public Object convertForWriteIfNeeded(CouchbasePersistentProperty prop, ConvertingPropertyAccessor<Object> accessor,
|
||||
boolean processValueConverter) {
|
||||
Object value = accessor.getProperty(prop, prop.getType());
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
if (processValueConverter && conversions.hasValueConverter(prop)) {
|
||||
CouchbaseDocument encrypted = (CouchbaseDocument) conversions.getPropertyValueConversions()
|
||||
.getValueConverter(prop)
|
||||
.write(value, new CouchbaseConversionContext(prop, (MappingCouchbaseConverter) this, accessor));
|
||||
return encrypted;
|
||||
}
|
||||
Class<?> targetClass = this.conversions.getCustomWriteTarget(value.getClass()).orElse(null);
|
||||
|
||||
boolean canConvert = targetClass == null ? false
|
||||
: this.conversionService.canConvert(new TypeDescriptor(prop.getField()), TypeDescriptor.valueOf(targetClass));
|
||||
if (canConvert) {
|
||||
return this.conversionService.convert(value, new TypeDescriptor(prop.getField()),
|
||||
TypeDescriptor.valueOf(targetClass));
|
||||
}
|
||||
|
||||
Object result = this.conversions.getCustomWriteTarget(prop.getType()) //
|
||||
.map(it -> this.conversionService.convert(value, new TypeDescriptor(prop.getField()),
|
||||
TypeDescriptor.valueOf(it))) //
|
||||
.orElseGet(() -> Enum.class.isAssignableFrom(value.getClass()) ? ((Enum<?>) value).name() : value);
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This convertForWriteIfNeed takes only the value to convert. It cannot access the annotations of the Field being
|
||||
* converted.
|
||||
*
|
||||
* @param value the value to be converted to the class that would actually be stored.
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public Object convertForWriteIfNeeded(Object value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.conversions.getCustomWriteTarget(value.getClass()) //
|
||||
.map(it -> (Object) this.conversionService.convert(value, it)) //
|
||||
.orElseGet(() -> Enum.class.isAssignableFrom(value.getClass()) ? ((Enum<?>) value).name() : value);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getWriteClassFor(Class<?> clazz) {
|
||||
return this.conversions.getCustomWriteTarget(clazz).orElse(clazz);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CustomConversions getConversions() {
|
||||
return conversions;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* 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.convert;
|
||||
|
||||
import org.springframework.core.convert.converter.GenericConverter.ConvertiblePair;
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbaseSimpleTypes;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Conversion registration information.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Michael Nitschinger
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
class ConverterRegistration {
|
||||
|
||||
private final ConvertiblePair convertiblePair;
|
||||
private final boolean reading;
|
||||
private final boolean writing;
|
||||
|
||||
/**
|
||||
* Creates a new {@link ConverterRegistration}.
|
||||
*
|
||||
* @param convertiblePair must not be {@literal null}.
|
||||
* @param isReading whether to force to consider the converter for reading.
|
||||
* @param isWriting whether to force to consider the converter for reading.
|
||||
*/
|
||||
public ConverterRegistration(ConvertiblePair convertiblePair, boolean isReading, boolean isWriting) {
|
||||
Assert.notNull(convertiblePair, "ConvertiblePair must not be null!");
|
||||
|
||||
this.convertiblePair = convertiblePair;
|
||||
reading = isReading;
|
||||
writing = isWriting;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link ConverterRegistration} from the given source and target type and read/write flags.
|
||||
*
|
||||
* @param source the source type to be converted from, must not be {@literal null}.
|
||||
* @param target the target type to be converted to, must not be {@literal null}.
|
||||
* @param isReading whether to force to consider the converter for reading.
|
||||
* @param isWriting whether to force to consider the converter for writing.
|
||||
*/
|
||||
public ConverterRegistration(Class<?> source, Class<?> target, boolean isReading, boolean isWriting) {
|
||||
this(new ConvertiblePair(source, target), isReading, isWriting);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given type is a type that Couchbase can handle basically.
|
||||
*
|
||||
* @param type
|
||||
* @return
|
||||
*/
|
||||
private static boolean isCouchbaseBasicType(Class<?> type) {
|
||||
return CouchbaseSimpleTypes.JSON_TYPES.isSimpleType(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the converter shall be used for writing.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean isWriting() {
|
||||
return writing == true || (!reading && isSimpleTargetType());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the converter shall be used for reading.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean isReading() {
|
||||
return reading == true || (!writing && isSimpleSourceType());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the actual conversion pair.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public ConvertiblePair getConvertiblePair() {
|
||||
return convertiblePair;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the source type is a Couchbase simple one.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean isSimpleSourceType() {
|
||||
return isCouchbaseBasicType(convertiblePair.getSourceType());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the target type is a Couchbase simple one.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean isSimpleTargetType() {
|
||||
return isCouchbaseBasicType(convertiblePair.getTargetType());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright 2022 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.convert;
|
||||
|
||||
import org.springframework.data.convert.ValueConversionContext;
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty;
|
||||
import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* {@link ValueConversionContext} that allows to delegate read/write to an underlying {@link CouchbaseConverter}.
|
||||
*
|
||||
* @author Michael Reiche
|
||||
* @since 5.0
|
||||
*/
|
||||
public class CouchbaseConversionContext implements ValueConversionContext<CouchbasePersistentProperty> {
|
||||
|
||||
private final CouchbasePersistentProperty persistentProperty;
|
||||
private final MappingCouchbaseConverter couchbaseConverter;
|
||||
private final ConvertingPropertyAccessor propertyAccessor;
|
||||
|
||||
public CouchbaseConversionContext(CouchbasePersistentProperty persistentProperty,
|
||||
MappingCouchbaseConverter couchbaseConverter, ConvertingPropertyAccessor accessor) {
|
||||
|
||||
this.persistentProperty = persistentProperty;
|
||||
this.couchbaseConverter = couchbaseConverter;
|
||||
this.propertyAccessor = accessor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CouchbasePersistentProperty getProperty() {
|
||||
return persistentProperty;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T write(@Nullable Object value, TypeInformation<T> target) {
|
||||
return (T) ValueConversionContext.super.write(value, target);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T read(@Nullable Object value, TypeInformation<T> target) {
|
||||
return ValueConversionContext.super.read(value, target);
|
||||
}
|
||||
|
||||
public MappingCouchbaseConverter getConverter() {
|
||||
return couchbaseConverter;
|
||||
}
|
||||
|
||||
public ConvertingPropertyAccessor getAccessor() {
|
||||
return propertyAccessor;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Copyright 2012-2022 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.convert;
|
||||
|
||||
import org.springframework.data.convert.CustomConversions;
|
||||
import org.springframework.data.convert.EntityConverter;
|
||||
import org.springframework.data.convert.EntityReader;
|
||||
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.mapping.Alias;
|
||||
import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
|
||||
/**
|
||||
* Marker interface for the converter, identifying the types to and from that can be converted.
|
||||
*
|
||||
* @author Michael Nitschinger
|
||||
* @author Simon Baslé
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
public interface CouchbaseConverter
|
||||
extends EntityConverter<CouchbasePersistentEntity<?>, CouchbasePersistentProperty, Object, CouchbaseDocument>,
|
||||
CouchbaseWriter<Object, CouchbaseDocument>, EntityReader<Object, CouchbaseDocument> {
|
||||
|
||||
/**
|
||||
* Convert the value if necessary to the class that would actually be stored, or leave it as is if no conversion
|
||||
* needed. This method cannot access the annotations of the field.
|
||||
*
|
||||
* @param value the value to be converted to the class that would actually be stored.
|
||||
* @return the converted value (or the same value if no conversion necessary).
|
||||
*/
|
||||
Object convertForWriteIfNeeded(Object value);
|
||||
|
||||
/**
|
||||
* Convert the value if necessary to the class that would actually be stored, or leave it as is if no conversion
|
||||
* needed. This method can access the annotations of the field.
|
||||
*
|
||||
* @param source the property to be converted to the class that would actually be stored.
|
||||
* @param accessor the property accessor
|
||||
* @return the converted value (or the same value if no conversion necessary).
|
||||
*/
|
||||
Object convertForWriteIfNeeded(final CouchbasePersistentProperty source,
|
||||
final ConvertingPropertyAccessor<Object> accessor, boolean processValueConverter);
|
||||
|
||||
/**
|
||||
* Return the Class that would actually be stored for a given Class.
|
||||
*
|
||||
* @param clazz the source class.
|
||||
* @return the target class that would actually be stored.
|
||||
* @see #convertForWriteIfNeeded(Object)
|
||||
*/
|
||||
Class<?> getWriteClassFor(Class<?> clazz);
|
||||
|
||||
/**
|
||||
* @return the name of the field that will hold type information.
|
||||
*/
|
||||
String getTypeKey();
|
||||
|
||||
/**
|
||||
* @return the alias value for the type
|
||||
*/
|
||||
Alias getTypeAlias(TypeInformation<?> info);
|
||||
|
||||
/**
|
||||
* return the conversions
|
||||
*
|
||||
* @return conversions
|
||||
*/
|
||||
CustomConversions getConversions();
|
||||
}
|
||||
@@ -0,0 +1,329 @@
|
||||
/*
|
||||
* Copyright 2017-2022 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.convert;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.time.ZoneId;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.core.convert.converter.ConverterFactory;
|
||||
import org.springframework.core.convert.converter.GenericConverter;
|
||||
import org.springframework.data.convert.PropertyValueConversions;
|
||||
import org.springframework.data.convert.PropertyValueConverter;
|
||||
import org.springframework.data.convert.PropertyValueConverterFactory;
|
||||
import org.springframework.data.convert.PropertyValueConverterRegistrar;
|
||||
import org.springframework.data.convert.SimplePropertyValueConversions;
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty;
|
||||
import org.springframework.data.mapping.PersistentProperty;
|
||||
import org.springframework.data.mapping.model.SimpleTypeHolder;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.couchbase.client.java.encryption.annotation.Encrypted;
|
||||
|
||||
/**
|
||||
* Value object to capture custom conversion.
|
||||
* <p>
|
||||
* Types that can be mapped directly onto JSON are considered simple ones, because they neither need deeper inspection
|
||||
* nor nested conversion.
|
||||
*
|
||||
* @author Michael Nitschinger
|
||||
* @author Oliver Gierke
|
||||
* @author Mark Paluch
|
||||
* @author Subhashni Balakrishnan
|
||||
* @Michael Reiche
|
||||
* @see org.springframework.data.convert.CustomConversions
|
||||
* @see SimpleTypeHolder
|
||||
* @since 2.0
|
||||
*/
|
||||
public class CouchbaseCustomConversions extends org.springframework.data.convert.CustomConversions {
|
||||
|
||||
private static final StoreConversions STORE_CONVERSIONS;
|
||||
|
||||
private static final List<Object> STORE_CONVERTERS;
|
||||
|
||||
static {
|
||||
|
||||
List<Object> converters = new ArrayList<>();
|
||||
|
||||
converters.addAll(DateConverters.getConvertersToRegister());
|
||||
converters.addAll(CouchbaseJsr310Converters.getConvertersToRegister());
|
||||
converters.addAll(OtherConverters.getConvertersToRegister());
|
||||
|
||||
STORE_CONVERTERS = Collections.unmodifiableList(converters);
|
||||
STORE_CONVERSIONS = StoreConversions.of(SimpleTypeHolder.DEFAULT, STORE_CONVERTERS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link CouchbaseCustomConversions} instance registering the given converters.
|
||||
*
|
||||
* @param converters must not be {@literal null}.
|
||||
*/
|
||||
public CouchbaseCustomConversions(List<?> converters) {
|
||||
this(CouchbaseConverterConfigurationAdapter.from(converters));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link CouchbaseCustomConversions} given {@link CouchbaseConverterConfigurationAdapter}.
|
||||
*
|
||||
* @param conversionConfiguration must not be {@literal null}.
|
||||
*/
|
||||
protected CouchbaseCustomConversions(CouchbaseConverterConfigurationAdapter conversionConfiguration) {
|
||||
super(conversionConfiguration.createConverterConfiguration());
|
||||
}
|
||||
|
||||
/**
|
||||
* Functional style {@link org.springframework.data.convert.CustomConversions} creation giving users a convenient way
|
||||
* of configuring store specific capabilities by providing deferred hooks to what will be configured when creating the
|
||||
* {@link org.springframework.data.convert.CustomConversions#CustomConversions(ConverterConfiguration) instance}.
|
||||
*
|
||||
* @param configurer must not be {@literal null}.
|
||||
*/
|
||||
public static CouchbaseCustomConversions create(Consumer<CouchbaseConverterConfigurationAdapter> configurer) {
|
||||
CouchbaseConverterConfigurationAdapter adapter = new CouchbaseConverterConfigurationAdapter();
|
||||
configurer.accept(adapter);
|
||||
return new CouchbaseCustomConversions(adapter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasValueConverter(PersistentProperty<?> property) {
|
||||
if (property.findAnnotation(Encrypted.class) != null) {
|
||||
return true;
|
||||
}
|
||||
return super.hasValueConverter(property);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link CouchbaseConverterConfigurationAdapter} encapsulates creation of
|
||||
* {@link org.springframework.data.convert.CustomConversions.ConverterConfiguration} with CouchbaseDB specifics.
|
||||
*/
|
||||
public static class CouchbaseConverterConfigurationAdapter {
|
||||
|
||||
/**
|
||||
* List of {@literal java.time} types having different representation when rendered
|
||||
*/
|
||||
private static final Set<Class<?>> JAVA_DRIVER_TIME_SIMPLE_TYPES = new HashSet<>(
|
||||
Arrays.asList(LocalDate.class, LocalTime.class, LocalDateTime.class));
|
||||
|
||||
private boolean useNativeDriverJavaTimeCodecs = false;
|
||||
private final List<Object> customConverters = new ArrayList<>();
|
||||
private final PropertyValueConversions internalValueConversion = PropertyValueConversions.simple(it -> {});
|
||||
private PropertyValueConversions propertyValueConversions = internalValueConversion;
|
||||
|
||||
/**
|
||||
* Create a {@link CouchbaseConverterConfigurationAdapter} using the provided {@code converters} and our own codecs
|
||||
* for JSR-310 types.
|
||||
*
|
||||
* @param converters must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public static CouchbaseConverterConfigurationAdapter from(List<?> converters) {
|
||||
|
||||
Assert.notNull(converters, "Converters must not be null");
|
||||
|
||||
CouchbaseConverterConfigurationAdapter converterConfigurationAdapter = new CouchbaseConverterConfigurationAdapter();
|
||||
converterConfigurationAdapter.registerConverters(converters);
|
||||
return converterConfigurationAdapter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a custom {@link Converter} implementation.
|
||||
*
|
||||
* @param converter must not be {@literal null}.
|
||||
* @return this.
|
||||
*/
|
||||
public CouchbaseConverterConfigurationAdapter registerConverter(Converter<?, ?> converter) {
|
||||
|
||||
Assert.notNull(converter, "Converter must not be null!");
|
||||
customConverters.add(converter);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gateway to register property specific converters.
|
||||
*
|
||||
* @param configurationAdapter must not be {@literal null}.
|
||||
* @return this.
|
||||
*/
|
||||
public CouchbaseConverterConfigurationAdapter configurePropertyConversions(
|
||||
Consumer<PropertyValueConverterRegistrar<CouchbasePersistentProperty>> configurationAdapter) {
|
||||
|
||||
Assert.state(valueConversions() instanceof SimplePropertyValueConversions,
|
||||
"Configured PropertyValueConversions does not allow setting custom ConverterRegistry");
|
||||
|
||||
PropertyValueConverterRegistrar propertyValueConverterRegistrar = new PropertyValueConverterRegistrar();
|
||||
configurationAdapter.accept(propertyValueConverterRegistrar);
|
||||
|
||||
((SimplePropertyValueConversions) valueConversions())
|
||||
.setValueConverterRegistry(propertyValueConverterRegistrar.buildRegistry());
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a custom {@link ConverterFactory} implementation.
|
||||
*
|
||||
* @param converterFactory must not be {@literal null}.
|
||||
* @return this.
|
||||
*/
|
||||
public CouchbaseConverterConfigurationAdapter registerConverterFactory(ConverterFactory<?, ?> converterFactory) {
|
||||
|
||||
Assert.notNull(converterFactory, "ConverterFactory must not be null");
|
||||
customConverters.add(converterFactory);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add {@link Converter converters}, {@link ConverterFactory factories},
|
||||
* {@link org.springframework.data.convert.ConverterBuilder.ConverterAware converter-aware objects}, and
|
||||
* {@link GenericConverter generic converters}.
|
||||
*
|
||||
* @param converters must not be {@literal null} nor contain {@literal null} values.
|
||||
* @return this.
|
||||
*/
|
||||
public CouchbaseConverterConfigurationAdapter registerConverters(Collection<?> converters) {
|
||||
|
||||
Assert.notNull(converters, "Converters must not be null");
|
||||
Assert.noNullElements(converters, "Converters must not be null nor contain null values");
|
||||
|
||||
customConverters.addAll(converters);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a custom/default {@link PropertyValueConverterFactory} implementation used to serve
|
||||
* {@link PropertyValueConverter}.
|
||||
*
|
||||
* @param converterFactory must not be {@literal null}.
|
||||
* @return this.
|
||||
*/
|
||||
public CouchbaseConverterConfigurationAdapter registerPropertyValueConverterFactory(
|
||||
PropertyValueConverterFactory converterFactory) {
|
||||
|
||||
Assert.state(valueConversions() instanceof SimplePropertyValueConversions,
|
||||
"Configured PropertyValueConversions does not allow setting custom ConverterRegistry");
|
||||
|
||||
((SimplePropertyValueConversions) valueConversions()).setConverterFactory(converterFactory);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Optionally set the {@link PropertyValueConversions} to be applied during mapping.
|
||||
* <p>
|
||||
* Use this method if {@link #configurePropertyConversions(Consumer)} and
|
||||
* {@link #registerPropertyValueConverterFactory(PropertyValueConverterFactory)} are not sufficient.
|
||||
*
|
||||
* @param valueConversions must not be {@literal null}.
|
||||
* @return this.
|
||||
*/
|
||||
public CouchbaseConverterConfigurationAdapter setPropertyValueConversions(
|
||||
PropertyValueConversions valueConversions) {
|
||||
|
||||
Assert.notNull(valueConversions, "PropertyValueConversions must not be null");
|
||||
this.propertyValueConversions = valueConversions;
|
||||
return this;
|
||||
}
|
||||
|
||||
PropertyValueConversions valueConversions() {
|
||||
|
||||
if (this.propertyValueConversions == null) {
|
||||
this.propertyValueConversions = internalValueConversion;
|
||||
}
|
||||
|
||||
return this.propertyValueConversions;
|
||||
}
|
||||
|
||||
ConverterConfiguration createConverterConfiguration() {
|
||||
|
||||
if (hasDefaultPropertyValueConversions()
|
||||
&& propertyValueConversions instanceof SimplePropertyValueConversions svc) {
|
||||
svc.init();
|
||||
}
|
||||
|
||||
if (!useNativeDriverJavaTimeCodecs) {
|
||||
return new ConverterConfiguration(STORE_CONVERSIONS, this.customConverters, convertiblePair -> true,
|
||||
this.propertyValueConversions);
|
||||
}
|
||||
|
||||
/*
|
||||
* We need to have those converters using UTC as the default ones would go on with the systemDefault.
|
||||
*/
|
||||
List<Object> converters = new ArrayList<>(STORE_CONVERTERS.size() + 3);
|
||||
converters.add(DateToUtcLocalDateConverter.INSTANCE);
|
||||
converters.add(DateToUtcLocalTimeConverter.INSTANCE);
|
||||
converters.add(DateToUtcLocalDateTimeConverter.INSTANCE);
|
||||
converters.addAll(STORE_CONVERTERS);
|
||||
|
||||
StoreConversions storeConversions = StoreConversions.of(new SimpleTypeHolder(JAVA_DRIVER_TIME_SIMPLE_TYPES,
|
||||
SimpleTypeHolder.DEFAULT /* CouchbaseSimpleTypes.HOLDER */), converters);
|
||||
|
||||
return new ConverterConfiguration(storeConversions, this.customConverters, convertiblePair -> {
|
||||
|
||||
// Avoid default registrations
|
||||
|
||||
if (JAVA_DRIVER_TIME_SIMPLE_TYPES.contains(convertiblePair.getSourceType())
|
||||
&& Date.class.isAssignableFrom(convertiblePair.getTargetType())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}, this.propertyValueConversions);
|
||||
}
|
||||
|
||||
private enum DateToUtcLocalDateTimeConverter implements Converter<Date, LocalDateTime> {
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public LocalDateTime convert(Date source) {
|
||||
return LocalDateTime.ofInstant(Instant.ofEpochMilli(source.getTime()), ZoneId.of("UTC"));
|
||||
}
|
||||
}
|
||||
|
||||
private enum DateToUtcLocalTimeConverter implements Converter<Date, LocalTime> {
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public LocalTime convert(Date source) {
|
||||
return DateToUtcLocalDateTimeConverter.INSTANCE.convert(source).toLocalTime();
|
||||
}
|
||||
}
|
||||
|
||||
private enum DateToUtcLocalDateConverter implements Converter<Date, LocalDate> {
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public LocalDate convert(Date source) {
|
||||
return DateToUtcLocalDateTimeConverter.INSTANCE.convert(source).toLocalDate();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasDefaultPropertyValueConversions() {
|
||||
return propertyValueConversions == internalValueConversion;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* 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.convert;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.context.expression.MapAccessor;
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.TypedValue;
|
||||
|
||||
/**
|
||||
* A property accessor for document properties.
|
||||
*
|
||||
* @author Michael Nitschinger
|
||||
*/
|
||||
public class CouchbaseDocumentPropertyAccessor extends MapAccessor {
|
||||
|
||||
/**
|
||||
* Contains the static instance of thi accessor.
|
||||
*/
|
||||
static final MapAccessor INSTANCE = new CouchbaseDocumentPropertyAccessor();
|
||||
|
||||
/**
|
||||
* Returns the target classes of the properties.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public Class<?>[] getSpecificTargetClasses() {
|
||||
return new Class[] { CouchbaseDocument.class };
|
||||
}
|
||||
|
||||
/**
|
||||
* It can always read from those properties.
|
||||
*
|
||||
* @param context the evaluation context.
|
||||
* @param target the target object.
|
||||
* @param name the name of the property.
|
||||
* @return always true.
|
||||
*/
|
||||
@Override
|
||||
public boolean canRead(final EvaluationContext context, final Object target, final String name) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the value from the property.
|
||||
*
|
||||
* @param context the evaluation context.
|
||||
* @param target the target object.
|
||||
* @param name the name of the property.
|
||||
* @return the typed value of the content to be read.
|
||||
*/
|
||||
@Override
|
||||
public TypedValue read(final EvaluationContext context, final Object target, final String name) {
|
||||
Map<String, Object> source = (Map<String, Object>) target;
|
||||
|
||||
Object value = source.get(name);
|
||||
return value == null ? TypedValue.NULL : new TypedValue(value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,272 @@
|
||||
/*
|
||||
* Copyright 2017-2022 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.convert;
|
||||
|
||||
import static java.time.Instant.*;
|
||||
import static java.time.LocalDateTime.*;
|
||||
import static java.time.ZoneId.*;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.time.Period;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.data.convert.ReadingConverter;
|
||||
import org.springframework.data.convert.WritingConverter;
|
||||
|
||||
/**
|
||||
* Helper class to register JSR-310 specific {@link Converter} implementations.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Barak Schoster
|
||||
* @author Christoph Strobl
|
||||
* @author Subhashni Balakrishnan
|
||||
*/
|
||||
public final class CouchbaseJsr310Converters {
|
||||
|
||||
private CouchbaseJsr310Converters() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the converters to be registered
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static Collection<Converter<?, ?>> getConvertersToRegister() {
|
||||
List<Converter<?, ?>> converters = new ArrayList<>();
|
||||
converters.add(NumberToLocalDateTimeConverter.INSTANCE);
|
||||
converters.add(LocalDateTimeToLongConverter.INSTANCE);
|
||||
converters.add(NumberToLocalDateConverter.INSTANCE);
|
||||
converters.add(LocalDateToLongConverter.INSTANCE);
|
||||
converters.add(NumberToLocalTimeConverter.INSTANCE);
|
||||
converters.add(LocalTimeToLongConverter.INSTANCE);
|
||||
converters.add(NumberToInstantConverter.INSTANCE);
|
||||
converters.add(InstantToLongConverter.INSTANCE);
|
||||
converters.add(ZoneIdToStringConverter.INSTANCE);
|
||||
converters.add(StringToZoneIdConverter.INSTANCE);
|
||||
converters.add(DurationToStringConverter.INSTANCE);
|
||||
converters.add(StringToDurationConverter.INSTANCE);
|
||||
converters.add(PeriodToStringConverter.INSTANCE);
|
||||
converters.add(StringToPeriodConverter.INSTANCE);
|
||||
converters.add(ZonedDateTimeToLongConverter.INSTANCE);
|
||||
converters.add(NumberToZonedDateTimeConverter.INSTANCE);
|
||||
|
||||
return converters;
|
||||
}
|
||||
|
||||
@ReadingConverter
|
||||
public enum NumberToLocalDateTimeConverter implements Converter<Number, LocalDateTime> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public LocalDateTime convert(Number source) {
|
||||
return source == null ? null
|
||||
: ofInstant(DateConverters.SerializedObjectToDateConverter.INSTANCE.convert(source).toInstant(),
|
||||
systemDefault());
|
||||
}
|
||||
}
|
||||
|
||||
@WritingConverter
|
||||
public enum LocalDateTimeToLongConverter implements Converter<LocalDateTime, Long> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public Long convert(LocalDateTime source) {
|
||||
return source == null ? null
|
||||
: DateConverters.DateToLongConverter.INSTANCE.convert(Date.from(source.atZone(systemDefault()).toInstant()));
|
||||
}
|
||||
}
|
||||
|
||||
@ReadingConverter
|
||||
public enum NumberToZonedDateTimeConverter implements Converter<Number, ZonedDateTime> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public ZonedDateTime convert(Number source) {
|
||||
return source == null ? null
|
||||
: ZonedDateTime.ofInstant(DateConverters.SerializedObjectToDateConverter.INSTANCE.convert(source).toInstant(),
|
||||
systemDefault());
|
||||
}
|
||||
}
|
||||
|
||||
@WritingConverter
|
||||
public enum ZonedDateTimeToLongConverter implements Converter<ZonedDateTime, Long> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public Long convert(ZonedDateTime source) {
|
||||
return source == null ? null
|
||||
: DateConverters.DateToLongConverter.INSTANCE.convert(Date.from(source.toInstant()));
|
||||
}
|
||||
}
|
||||
|
||||
@ReadingConverter
|
||||
public enum NumberToLocalDateConverter implements Converter<Number, LocalDate> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public LocalDate convert(Number source) {
|
||||
return source == null ? null
|
||||
: ofInstant(ofEpochMilli(DateConverters.SerializedObjectToDateConverter.INSTANCE.convert(source).getTime()),
|
||||
systemDefault()).toLocalDate();
|
||||
}
|
||||
}
|
||||
|
||||
@WritingConverter
|
||||
public enum LocalDateToLongConverter implements Converter<LocalDate, Long> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public Long convert(LocalDate source) {
|
||||
return source == null ? null
|
||||
: DateConverters.DateToLongConverter.INSTANCE
|
||||
.convert(Date.from(source.atStartOfDay(systemDefault()).toInstant()));
|
||||
}
|
||||
}
|
||||
|
||||
@ReadingConverter
|
||||
public enum NumberToLocalTimeConverter implements Converter<Number, LocalTime> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public LocalTime convert(Number source) {
|
||||
return source == null ? null
|
||||
: ofInstant(ofEpochMilli(DateConverters.SerializedObjectToDateConverter.INSTANCE.convert(source).getTime()),
|
||||
systemDefault()).toLocalTime();
|
||||
}
|
||||
}
|
||||
|
||||
@WritingConverter
|
||||
public enum LocalTimeToLongConverter implements Converter<LocalTime, Long> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public Long convert(LocalTime source) {
|
||||
return source == null ? null
|
||||
: DateConverters.DateToLongConverter.INSTANCE
|
||||
.convert(Date.from(source.atDate(LocalDate.now()).atZone(systemDefault()).toInstant()));
|
||||
}
|
||||
}
|
||||
|
||||
@ReadingConverter
|
||||
public enum NumberToInstantConverter implements Converter<Number, Instant> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public Instant convert(Number source) {
|
||||
return source == null ? null
|
||||
: DateConverters.SerializedObjectToDateConverter.INSTANCE.convert(source).toInstant();
|
||||
}
|
||||
}
|
||||
|
||||
@WritingConverter
|
||||
public enum InstantToLongConverter implements Converter<Instant, Long> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public Long convert(Instant source) {
|
||||
return source == null ? null
|
||||
: DateConverters.DateToLongConverter.INSTANCE.convert(Date.from(source.atZone(systemDefault()).toInstant()));
|
||||
}
|
||||
}
|
||||
|
||||
@WritingConverter
|
||||
public enum ZoneIdToStringConverter implements Converter<ZoneId, String> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public String convert(ZoneId source) {
|
||||
return source.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@ReadingConverter
|
||||
public enum StringToZoneIdConverter implements Converter<String, ZoneId> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public ZoneId convert(String source) {
|
||||
return ZoneId.of(source);
|
||||
}
|
||||
}
|
||||
|
||||
@WritingConverter
|
||||
public enum DurationToStringConverter implements Converter<Duration, String> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public String convert(Duration duration) {
|
||||
return duration.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@ReadingConverter
|
||||
public enum StringToDurationConverter implements Converter<String, Duration> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public Duration convert(String s) {
|
||||
return Duration.parse(s);
|
||||
}
|
||||
}
|
||||
|
||||
@WritingConverter
|
||||
public enum PeriodToStringConverter implements Converter<Period, String> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public String convert(Period period) {
|
||||
return period.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@ReadingConverter
|
||||
public enum StringToPeriodConverter implements Converter<String, Period> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public Period convert(String s) {
|
||||
return Period.parse(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Copyright 2012-2022 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.convert;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.data.convert.PropertyValueConverter;
|
||||
import org.springframework.data.convert.PropertyValueConverterFactory;
|
||||
import org.springframework.data.convert.ValueConversionContext;
|
||||
import org.springframework.data.mapping.PersistentProperty;
|
||||
|
||||
import com.couchbase.client.core.encryption.CryptoManager;
|
||||
import com.couchbase.client.java.encryption.annotation.Encrypted;
|
||||
|
||||
/**
|
||||
* Accept the Couchbase @Encrypted annotation in addition to @ValueConverter
|
||||
*
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
public class CouchbasePropertyValueConverterFactory implements PropertyValueConverterFactory {
|
||||
|
||||
CryptoManager cryptoManager;
|
||||
Map<Class<? extends PropertyValueConverter<?, ?, ?>>, PropertyValueConverter<?, ?, ?>> converterCache = new HashMap<>();
|
||||
|
||||
public CouchbasePropertyValueConverterFactory(CryptoManager cryptoManager) {
|
||||
this.cryptoManager = cryptoManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <DV, SV, P extends ValueConversionContext<?>> PropertyValueConverter<DV, SV, P> getConverter(
|
||||
PersistentProperty<?> property) {
|
||||
PropertyValueConverter<DV, SV, P> valueConverter = PropertyValueConverterFactory.super.getConverter(property);
|
||||
if (valueConverter != null) {
|
||||
return valueConverter;
|
||||
}
|
||||
Encrypted encryptedAnn = property.findAnnotation(Encrypted.class);
|
||||
if (encryptedAnn != null) {
|
||||
Class cryptoConverterClass = CryptoConverter.class;
|
||||
return getConverter((Class<PropertyValueConverter<DV, SV, P>>) cryptoConverterClass);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <DV, SV, P extends ValueConversionContext<?>> PropertyValueConverter<DV, SV, P> getConverter(
|
||||
Class<? extends PropertyValueConverter<DV, SV, P>> converterType) {
|
||||
|
||||
PropertyValueConverter<?, ?, ?> converter = converterCache.get(converterType);
|
||||
if (converter != null) {
|
||||
return (PropertyValueConverter<DV, SV, P>) converter;
|
||||
}
|
||||
|
||||
if (CryptoConverter.class.isAssignableFrom(converterType)) {
|
||||
converter = new CryptoConverter(cryptoManager);
|
||||
} else {
|
||||
try {
|
||||
Constructor constructor = converterType.getConstructor();
|
||||
converter = (PropertyValueConverter<?, ?, ?>) constructor.newInstance();
|
||||
} catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
converterCache.put((Class<? extends PropertyValueConverter<DV, SV, P>>) converter.getClass(), converter);
|
||||
return (PropertyValueConverter<DV, SV, P>) converter;
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* 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.convert;
|
||||
|
||||
import org.springframework.data.convert.TypeMapper;
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
|
||||
import org.springframework.data.mapping.Alias;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
|
||||
/**
|
||||
* Marker interface for the TypeMapper.
|
||||
*
|
||||
* @author Michael Nitschinger
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
public interface CouchbaseTypeMapper extends TypeMapper<CouchbaseDocument> {
|
||||
|
||||
String getTypeKey();
|
||||
|
||||
Alias getTypeAlias(TypeInformation<?> info);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* 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.convert;
|
||||
|
||||
import org.springframework.data.convert.EntityWriter;
|
||||
|
||||
/**
|
||||
* Marker interface for the Couchbase {@link EntityWriter}.
|
||||
*
|
||||
* @author Michael Nitschinger
|
||||
*/
|
||||
public interface CouchbaseWriter<T, ConvertedCouchbaseDocument> extends EntityWriter<T, ConvertedCouchbaseDocument> {}
|
||||
@@ -0,0 +1,276 @@
|
||||
/*
|
||||
* Copyright 2022 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.convert;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.core.convert.ConversionFailedException;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.ConverterNotFoundException;
|
||||
import org.springframework.data.convert.CustomConversions;
|
||||
import org.springframework.data.convert.PropertyValueConverter;
|
||||
import org.springframework.data.convert.ValueConversionContext;
|
||||
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.mapping.PersistentProperty;
|
||||
import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.couchbase.client.core.encryption.CryptoManager;
|
||||
import com.couchbase.client.core.error.InvalidArgumentException;
|
||||
import com.couchbase.client.java.json.JsonArray;
|
||||
import com.couchbase.client.java.json.JsonObject;
|
||||
import com.couchbase.client.java.json.JsonValue;
|
||||
|
||||
/**
|
||||
* Encrypt/Decrypted properties annotated with
|
||||
*
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
public class CryptoConverter implements
|
||||
PropertyValueConverter<Object, CouchbaseDocument, ValueConversionContext<? extends PersistentProperty<?>>> {
|
||||
|
||||
CryptoManager cryptoManager;
|
||||
|
||||
public CryptoConverter(CryptoManager cryptoManager) {
|
||||
this.cryptoManager = cryptoManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object read(CouchbaseDocument value, ValueConversionContext<? extends PersistentProperty<?>> context) {
|
||||
byte[] decrypted = cryptoManager().decrypt(value.export());
|
||||
if (decrypted == null) {
|
||||
return null;
|
||||
}
|
||||
// it's decrypted to byte[]. Now figure out how to convert to the property type.
|
||||
return coerceToValueRead(decrypted, (CouchbaseConversionContext) context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CouchbaseDocument write(Object value, ValueConversionContext<? extends PersistentProperty<?>> context) {
|
||||
CouchbaseConversionContext ctx = (CouchbaseConversionContext) context;
|
||||
CouchbasePersistentProperty property = ctx.getProperty();
|
||||
byte[] plainText = coerceToBytesWrite(property, ctx.getAccessor(), ctx);
|
||||
Map<String, Object> encrypted = cryptoManager().encrypt(plainText, CryptoManager.DEFAULT_ENCRYPTER_ALIAS);
|
||||
return new CouchbaseDocument().setContent(encrypted);
|
||||
}
|
||||
|
||||
private Object coerceToValueRead(byte[] decrypted, CouchbaseConversionContext context) {
|
||||
CouchbasePersistentProperty property = context.getProperty();
|
||||
|
||||
CustomConversions cnvs = context.getConverter().getConversions();
|
||||
ConversionService svc = context.getConverter().getConversionService();
|
||||
Class<?> type = property.getType();
|
||||
|
||||
String decryptedString = new String(decrypted);
|
||||
if ("null".equals(decryptedString)) {
|
||||
return null;
|
||||
}
|
||||
/* this what we would do if we could use a JsonParser with a beanPropertyTypeRef
|
||||
final JsonParser plaintextParser = p.getCodec().getFactory().createParser(plaintext);
|
||||
plaintextParser.setCodec(p.getCodec());
|
||||
|
||||
return plaintextParser.readValueAs(beanPropertyTypeRef);
|
||||
*/
|
||||
|
||||
if (!cnvs.isSimpleType(type) && !type.isArray()) {
|
||||
JsonObject jo = JsonObject.fromJson(decryptedString);
|
||||
CouchbaseDocument source = new CouchbaseDocument().setContent(jo);
|
||||
return context.getConverter().read(property.getTypeInformation(), source);
|
||||
} else {
|
||||
String jsonString = "{\"" + property.getFieldName() + "\":" + decryptedString + "}";
|
||||
try {
|
||||
CouchbaseDocument decryptedDoc = new CouchbaseDocument().setContent(JsonObject.fromJson(jsonString));
|
||||
return context.getConverter().getPotentiallyConvertedSimpleRead(decryptedDoc.get(property.getFieldName()),
|
||||
property);
|
||||
} catch (InvalidArgumentException | ConverterNotFoundException | ConversionFailedException e) {
|
||||
throw new RuntimeException(decryptedString, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] coerceToBytesWrite(CouchbasePersistentProperty property, ConvertingPropertyAccessor accessor,
|
||||
CouchbaseConversionContext context) {
|
||||
byte[] plainText;
|
||||
CustomConversions cnvs = context.getConverter().getConversions();
|
||||
|
||||
Class<?> sourceType = property.getType();
|
||||
Class<?> targetType = cnvs.getCustomWriteTarget(property.getType()).orElse(null);
|
||||
Object value = context.getConverter().getPotentiallyConvertedSimpleWrite(property, accessor, false);
|
||||
if (value == null) { // null
|
||||
plainText = "null".getBytes(StandardCharsets.UTF_8);
|
||||
} else if (value.getClass().isArray()) { // array
|
||||
JsonArray ja;
|
||||
if (value.getClass().getComponentType().isPrimitive()) {
|
||||
ja = jaFromPrimitiveArray(value);
|
||||
} else {
|
||||
ja = jaFromObjectArray(value, context.getConverter());
|
||||
}
|
||||
plainText = ja.toBytes();
|
||||
} else if (cnvs.isSimpleType(sourceType)) { // simpleType
|
||||
String plainString = value != null ? value.toString() : null;
|
||||
if ((sourceType == String.class || targetType == String.class) || sourceType == Character.class
|
||||
|| sourceType == char.class || Enum.class.isAssignableFrom(sourceType)
|
||||
|| Locale.class.isAssignableFrom(sourceType)) {
|
||||
// TODO use jackson serializer here
|
||||
plainString = "\"" + plainString.replaceAll("\"", "\\\"") + "\"";
|
||||
}
|
||||
plainText = plainString.getBytes(StandardCharsets.UTF_8);
|
||||
} else { // an entity
|
||||
plainText = JsonObject.fromJson(context.read(value).toString().getBytes(StandardCharsets.UTF_8)).toBytes();
|
||||
}
|
||||
return plainText;
|
||||
}
|
||||
|
||||
CryptoManager cryptoManager() {
|
||||
Assert.notNull(cryptoManager,
|
||||
"cryptoManager needed to encrypt/decrypt but it is null. Override needed for cryptoManager() method of "
|
||||
+ AbstractCouchbaseConverter.class.getName());
|
||||
return cryptoManager;
|
||||
}
|
||||
|
||||
JsonArray jaFromObjectArray(Object value, MappingCouchbaseConverter converter) {
|
||||
CustomConversions cnvs = converter.getConversions();
|
||||
ConversionService svc = converter.getConversionService();
|
||||
JsonArray ja = JsonArray.ja();
|
||||
for (Object o : (Object[]) value) {
|
||||
ja.add(coerceToJson(o, cnvs, svc));
|
||||
}
|
||||
return ja;
|
||||
}
|
||||
|
||||
JsonArray jaFromPrimitiveArray(Object value) {
|
||||
Class<?> component = value.getClass().getComponentType();
|
||||
JsonArray jArray;
|
||||
if (Long.TYPE.isAssignableFrom(component)) {
|
||||
jArray = ja_long((long[]) value);
|
||||
} else if (Integer.TYPE.isAssignableFrom(component)) {
|
||||
jArray = ja_int((int[]) value);
|
||||
} else if (Double.TYPE.isAssignableFrom(component)) {
|
||||
jArray = ja_double((double[]) value);
|
||||
} else if (Float.TYPE.isAssignableFrom(component)) {
|
||||
jArray = ja_float((float[]) value);
|
||||
} else if (Boolean.TYPE.isAssignableFrom(component)) {
|
||||
jArray = ja_boolean((boolean[]) value);
|
||||
} else if (Short.TYPE.isAssignableFrom(component)) {
|
||||
jArray = ja_short((short[]) value);
|
||||
} else if (Byte.TYPE.isAssignableFrom(component)) {
|
||||
jArray = ja_byte((byte[]) value);
|
||||
} else if (Character.TYPE.isAssignableFrom(component)) {
|
||||
jArray = ja_char((char[]) value);
|
||||
} else {
|
||||
throw new RuntimeException("unhandled primitive array: " + component.getName());
|
||||
}
|
||||
return jArray;
|
||||
}
|
||||
|
||||
JsonArray ja_long(long[] array) {
|
||||
JsonArray ja = JsonArray.ja();
|
||||
for (long t : array) {
|
||||
ja.add(t);
|
||||
}
|
||||
return ja;
|
||||
}
|
||||
|
||||
JsonArray ja_int(int[] array) {
|
||||
JsonArray ja = JsonArray.ja();
|
||||
for (int t : array) {
|
||||
ja.add(t);
|
||||
}
|
||||
return ja;
|
||||
}
|
||||
|
||||
JsonArray ja_double(double[] array) {
|
||||
JsonArray ja = JsonArray.ja();
|
||||
for (double t : array) {
|
||||
ja.add(t);
|
||||
}
|
||||
return ja;
|
||||
}
|
||||
|
||||
JsonArray ja_float(float[] array) {
|
||||
JsonArray ja = JsonArray.ja();
|
||||
for (float t : array) {
|
||||
ja.add(t);
|
||||
}
|
||||
return ja;
|
||||
}
|
||||
|
||||
JsonArray ja_boolean(boolean[] array) {
|
||||
JsonArray ja = JsonArray.ja();
|
||||
for (boolean t : array) {
|
||||
ja.add(t);
|
||||
}
|
||||
return ja;
|
||||
}
|
||||
|
||||
JsonArray ja_short(short[] array) {
|
||||
JsonArray ja = JsonArray.ja();
|
||||
for (short t : array) {
|
||||
ja.add(t);
|
||||
}
|
||||
return ja;
|
||||
}
|
||||
|
||||
JsonArray ja_byte(byte[] array) {
|
||||
JsonArray ja = JsonArray.ja();
|
||||
for (byte t : array) {
|
||||
ja.add(t);
|
||||
}
|
||||
return ja;
|
||||
}
|
||||
|
||||
JsonArray ja_char(char[] array) {
|
||||
JsonArray ja = JsonArray.ja();
|
||||
for (char t : array) {
|
||||
ja.add(String.valueOf(t));
|
||||
}
|
||||
return ja;
|
||||
}
|
||||
|
||||
Object coerceToJson(Object o, CustomConversions cnvs, ConversionService svc) {
|
||||
if (o != null && o.getClass() == Optional.class) {
|
||||
o = ((Optional<?>) o).isEmpty() ? null : ((Optional) o).get();
|
||||
}
|
||||
Optional<Class<?>> clazz;
|
||||
if (o == null) {
|
||||
o = JsonValue.NULL;
|
||||
} else if ((clazz = cnvs.getCustomWriteTarget(o.getClass())).isPresent()) {
|
||||
o = svc.convert(o, clazz.get());
|
||||
} else if (JsonObject.checkType(o)) {
|
||||
// The object is of an acceptable type
|
||||
} else if (Number.class.isAssignableFrom(o.getClass())) {
|
||||
if (o.toString().contains(".")) {
|
||||
o = ((Number) o).doubleValue();
|
||||
} else {
|
||||
o = ((Number) o).longValue();
|
||||
}
|
||||
} else if (Character.class.isAssignableFrom(o.getClass())) {
|
||||
o = ((Character) o).toString();
|
||||
} else if (Enum.class.isAssignableFrom(o.getClass())) {
|
||||
o = ((Enum) o).name();
|
||||
} else { // punt
|
||||
o = o.toString();
|
||||
}
|
||||
return o;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2012-2022 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.convert;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Value object to capture custom conversion.
|
||||
* <p>
|
||||
* Types that can be mapped directly onto JSON are considered simple ones, because they neither need deeper inspection
|
||||
* nor nested conversion.
|
||||
*
|
||||
* @author Michael Nitschinger
|
||||
* @author Oliver Gierke
|
||||
* @author Mark Paluch
|
||||
* @author Subhashni Balakrishnan
|
||||
* @deprecated since 2.0, use {@link CouchbaseCustomConversions}.
|
||||
*/
|
||||
@Deprecated
|
||||
public class CustomConversions extends CouchbaseCustomConversions {
|
||||
|
||||
/**
|
||||
* Create a new instance with a given list of conversers.
|
||||
*
|
||||
* @param converters the list of custom converters.
|
||||
*/
|
||||
public CustomConversions(final List<?> converters) {
|
||||
super(converters);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
/*
|
||||
* 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.convert;
|
||||
|
||||
import static java.time.ZoneId.systemDefault;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.DateTimeZone;
|
||||
import org.joda.time.LocalDate;
|
||||
import org.joda.time.LocalDateTime;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.data.convert.ReadingConverter;
|
||||
import org.springframework.data.convert.WritingConverter;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* Out of the box conversions for java dates and calendars.
|
||||
*
|
||||
* @author Michael Nitschinger
|
||||
* @author Subhashni Balakrishnan
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public final class DateConverters {
|
||||
|
||||
private static final boolean JODA_TIME_IS_PRESENT = ClassUtils.isPresent("org.joda.time.LocalDate", null);
|
||||
|
||||
private DateConverters() {}
|
||||
|
||||
/**
|
||||
* Returns all converters by this class that can be registered.
|
||||
*
|
||||
* @return the list of converters to register.
|
||||
*/
|
||||
public static Collection<Converter<?, ?>> getConvertersToRegister() {
|
||||
List<Converter<?, ?>> converters = new ArrayList<Converter<?, ?>>();
|
||||
|
||||
boolean useISOStringConverterForDate = Boolean
|
||||
.parseBoolean(System.getProperty("org.springframework.data.couchbase.useISOStringConverterForDate", "false"));
|
||||
|
||||
if (useISOStringConverterForDate) {
|
||||
converters.add(DateToStringConverter.INSTANCE);
|
||||
} else {
|
||||
converters.add(DateToLongConverter.INSTANCE);
|
||||
}
|
||||
|
||||
converters.add(SerializedObjectToDateConverter.INSTANCE);
|
||||
|
||||
converters.add(CalendarToLongConverter.INSTANCE);
|
||||
converters.add(NumberToCalendarConverter.INSTANCE);
|
||||
|
||||
if (JODA_TIME_IS_PRESENT) {
|
||||
converters.add(LocalDateToLongConverter.INSTANCE);
|
||||
converters.add(LocalDateTimeToLongConverter.INSTANCE);
|
||||
converters.add(DateTimeToLongConverter.INSTANCE);
|
||||
converters.add(NumberToLocalDateConverter.INSTANCE);
|
||||
converters.add(NumberToLocalDateTimeConverter.INSTANCE);
|
||||
converters.add(NumberToDateTimeConverter.INSTANCE);
|
||||
}
|
||||
|
||||
return converters;
|
||||
}
|
||||
|
||||
@WritingConverter
|
||||
public enum DateToStringConverter implements Converter<Date, String> {
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public String convert(Date source) {
|
||||
return source == null ? null : source.toInstant().toString();
|
||||
}
|
||||
}
|
||||
|
||||
@ReadingConverter
|
||||
public enum SerializedObjectToDateConverter implements Converter<Object, Date> {
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public Date convert(Object source) {
|
||||
if (source == null) {
|
||||
return null;
|
||||
}
|
||||
if (source instanceof Number) {
|
||||
Date date = new Date();
|
||||
date.setTime(((Number) source).longValue());
|
||||
return date;
|
||||
} else if (source instanceof String) {
|
||||
return Date.from(Instant.parse((String) source).atZone(systemDefault()).toInstant());
|
||||
} else {
|
||||
// Unsupported serialized object
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@WritingConverter
|
||||
public enum DateToLongConverter implements Converter<Date, Long> {
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public Long convert(Date source) {
|
||||
return source == null ? null : source.getTime();
|
||||
}
|
||||
}
|
||||
|
||||
@WritingConverter
|
||||
public enum CalendarToLongConverter implements Converter<Calendar, Long> {
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public Long convert(Calendar source) {
|
||||
return source == null ? null : source.getTimeInMillis() / 1000;
|
||||
}
|
||||
}
|
||||
|
||||
@ReadingConverter
|
||||
public enum NumberToCalendarConverter implements Converter<Number, Calendar> {
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public Calendar convert(Number source) {
|
||||
if (source == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
calendar.setTimeInMillis(source.longValue() * 1000);
|
||||
return calendar;
|
||||
}
|
||||
}
|
||||
|
||||
@WritingConverter
|
||||
public enum LocalDateToLongConverter implements Converter<LocalDate, Long> {
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public Long convert(LocalDate source) {
|
||||
return source == null ? null : source.toDate().getTime();
|
||||
}
|
||||
}
|
||||
|
||||
@WritingConverter
|
||||
public enum LocalDateTimeToLongConverter implements Converter<LocalDateTime, Long> {
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public Long convert(LocalDateTime source) {
|
||||
return source == null ? null : source.toDate().getTime();
|
||||
}
|
||||
}
|
||||
|
||||
@WritingConverter
|
||||
public enum DateTimeToLongConverter implements Converter<DateTime, Long> {
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public Long convert(DateTime source) {
|
||||
return source == null ? null : source.toDate().getTime();
|
||||
}
|
||||
}
|
||||
|
||||
@ReadingConverter
|
||||
public enum NumberToLocalDateConverter implements Converter<Number, LocalDate> {
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public LocalDate convert(Number source) {
|
||||
return source == null ? null : new LocalDate(source.longValue());
|
||||
}
|
||||
}
|
||||
|
||||
@ReadingConverter
|
||||
public enum NumberToLocalDateTimeConverter implements Converter<Number, LocalDateTime> {
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public LocalDateTime convert(Number source) {
|
||||
return source == null ? null : new LocalDateTime(source.longValue());
|
||||
}
|
||||
}
|
||||
|
||||
@ReadingConverter
|
||||
public enum NumberToDateTimeConverter implements Converter<Number, DateTime> {
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public DateTime convert(Number source) {
|
||||
return source == null ? null : new DateTime(source.longValue(), DateTimeZone.UTC);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* 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.convert;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import org.springframework.data.convert.DefaultTypeMapper;
|
||||
import org.springframework.data.convert.TypeAliasAccessor;
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
|
||||
import org.springframework.data.mapping.Alias;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
|
||||
/**
|
||||
* The Couchbase Type Mapper.
|
||||
*
|
||||
* @author Michael Nitschinger
|
||||
* @author Mark Paluch
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
public class DefaultCouchbaseTypeMapper extends DefaultTypeMapper<CouchbaseDocument> implements CouchbaseTypeMapper {
|
||||
|
||||
/**
|
||||
* The type key to use if a complex type was identified.
|
||||
*/
|
||||
public static final String DEFAULT_TYPE_KEY = "_class";
|
||||
|
||||
private final String typeKey;
|
||||
|
||||
/**
|
||||
* Create a new type mapper with the type key.
|
||||
*
|
||||
* @param typeKey the typeKey to use.
|
||||
*/
|
||||
public DefaultCouchbaseTypeMapper(final String typeKey) {
|
||||
super(new CouchbaseDocumentTypeAliasAccessor(typeKey), (MappingContext) null,
|
||||
Collections.singletonList(new TypeAwareTypeInformationMapper()));
|
||||
this.typeKey = typeKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTypeKey() {
|
||||
return this.typeKey;
|
||||
}
|
||||
|
||||
public static final class CouchbaseDocumentTypeAliasAccessor implements TypeAliasAccessor<CouchbaseDocument> {
|
||||
|
||||
private final String typeKey;
|
||||
|
||||
public CouchbaseDocumentTypeAliasAccessor(final String typeKey) {
|
||||
this.typeKey = typeKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Alias readAliasFrom(final CouchbaseDocument source) {
|
||||
return Alias.ofNullable(source.get(typeKey));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTypeTo(final CouchbaseDocument sink, final Object alias) {
|
||||
if (typeKey != null) {
|
||||
sink.put(typeKey, alias);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Alias getTypeAlias(TypeInformation<?> info) {
|
||||
return getAliasFor(info);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,190 @@
|
||||
/*
|
||||
* Copyright 2021-2022 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.convert;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.data.convert.ReadingConverter;
|
||||
import org.springframework.data.convert.WritingConverter;
|
||||
import org.springframework.util.Base64Utils;
|
||||
|
||||
/**
|
||||
* Out of the box conversions for java dates and calendars.
|
||||
*
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
public final class OtherConverters {
|
||||
|
||||
private OtherConverters() {}
|
||||
|
||||
/**
|
||||
* Returns all converters by this class that can be registered.
|
||||
*
|
||||
* @return the list of converters to register.
|
||||
*/
|
||||
public static Collection<Converter<?, ?>> getConvertersToRegister() {
|
||||
List<Converter<?, ?>> converters = new ArrayList<Converter<?, ?>>();
|
||||
|
||||
converters.add(UuidToString.INSTANCE);
|
||||
converters.add(StringToUuid.INSTANCE);
|
||||
converters.add(BigIntegerToString.INSTANCE);
|
||||
converters.add(StringToBigInteger.INSTANCE);
|
||||
converters.add(BigDecimalToString.INSTANCE);
|
||||
converters.add(StringToBigDecimal.INSTANCE);
|
||||
converters.add(ByteArrayToString.INSTANCE);
|
||||
converters.add(StringToByteArray.INSTANCE);
|
||||
converters.add(CharArrayToString.INSTANCE);
|
||||
converters.add(StringToCharArray.INSTANCE);
|
||||
converters.add(ClassToString.INSTANCE);
|
||||
converters.add(StringToClass.INSTANCE);
|
||||
|
||||
return converters;
|
||||
}
|
||||
|
||||
@WritingConverter
|
||||
public enum UuidToString implements Converter<UUID, String> {
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public String convert(UUID source) {
|
||||
return source == null ? null : source.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@ReadingConverter
|
||||
public enum StringToUuid implements Converter<String, UUID> {
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public UUID convert(String source) {
|
||||
return source == null ? null : UUID.fromString(source);
|
||||
}
|
||||
}
|
||||
|
||||
@WritingConverter
|
||||
public enum BigIntegerToString implements Converter<BigInteger, String> {
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public String convert(BigInteger source) {
|
||||
return source == null ? null : source.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@ReadingConverter
|
||||
public enum StringToBigInteger implements Converter<String, BigInteger> {
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public BigInteger convert(String source) {
|
||||
return source == null ? null : new BigInteger(source);
|
||||
}
|
||||
}
|
||||
|
||||
@WritingConverter
|
||||
public enum BigDecimalToString implements Converter<BigDecimal, String> {
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public String convert(BigDecimal source) {
|
||||
return source == null ? null : source.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@ReadingConverter
|
||||
public enum StringToBigDecimal implements Converter<String, BigDecimal> {
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public BigDecimal convert(String source) {
|
||||
return source == null ? null : new BigDecimal(source);
|
||||
}
|
||||
}
|
||||
|
||||
@WritingConverter
|
||||
public enum ByteArrayToString implements Converter<byte[], String> {
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public String convert(byte[] source) {
|
||||
return source == null ? null : Base64Utils.encodeToString(source);
|
||||
}
|
||||
}
|
||||
|
||||
@ReadingConverter
|
||||
public enum StringToByteArray implements Converter<String, byte[]> {
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public byte[] convert(String source) {
|
||||
return source == null ? null : Base64Utils.decode(source.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
}
|
||||
|
||||
@WritingConverter
|
||||
public enum CharArrayToString implements Converter<char[], String> {
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public String convert(char[] source) {
|
||||
return source == null ? null : new String(source) ;
|
||||
}
|
||||
}
|
||||
|
||||
@ReadingConverter
|
||||
public enum StringToCharArray implements Converter<String, char[]> {
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public char[] convert(String source) {
|
||||
return source == null ? null : source.toCharArray();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@WritingConverter
|
||||
public enum ClassToString implements Converter<Class<?>, String> {
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public String convert(Class<?> source) {
|
||||
return source == null ? null : source.getClass().getName() ;
|
||||
}
|
||||
}
|
||||
|
||||
@ReadingConverter
|
||||
public enum StringToClass implements Converter<String, Class<?>> {
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public Class<?> convert(String source) {
|
||||
try {
|
||||
return source == null ? null : Class.forName(source);
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* 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.convert;
|
||||
|
||||
import org.springframework.data.annotation.TypeAlias;
|
||||
import org.springframework.data.convert.SimpleTypeInformationMapper;
|
||||
import org.springframework.data.mapping.Alias;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
|
||||
/**
|
||||
* TypeAwareTypeInformationMapper - leverages @TypeAlias
|
||||
*
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
public class TypeAwareTypeInformationMapper extends SimpleTypeInformationMapper {
|
||||
|
||||
@Override
|
||||
public Alias createAliasFor(TypeInformation<?> type) {
|
||||
TypeAlias[] typeAlias = type.getType().getAnnotationsByType(TypeAlias.class);
|
||||
|
||||
if (typeAlias.length == 1) {
|
||||
return Alias.of(typeAlias[0].value());
|
||||
}
|
||||
|
||||
return super.createAliasFor(type);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,317 @@
|
||||
/*
|
||||
* Copyright 2018-2022 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.convert.join;
|
||||
|
||||
import static org.springframework.data.couchbase.core.query.N1QLExpression.i;
|
||||
import static org.springframework.data.couchbase.core.query.N1QLExpression.x;
|
||||
import static org.springframework.data.couchbase.core.support.TemplateUtils.SELECT_CAS;
|
||||
import static org.springframework.data.couchbase.core.support.TemplateUtils.SELECT_ID;
|
||||
|
||||
import java.lang.reflect.AnnotatedElement;
|
||||
import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.data.couchbase.core.ReactiveCouchbaseTemplate;
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentEntity;
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty;
|
||||
import org.springframework.data.couchbase.core.query.FetchType;
|
||||
import org.springframework.data.couchbase.core.query.HashSide;
|
||||
import org.springframework.data.couchbase.core.query.N1QLExpression;
|
||||
import org.springframework.data.couchbase.core.query.N1QLQuery;
|
||||
import org.springframework.data.couchbase.core.query.N1qlJoin;
|
||||
import org.springframework.data.couchbase.core.query.OptionsBuilder;
|
||||
import org.springframework.data.couchbase.core.query.Query;
|
||||
import org.springframework.data.couchbase.repository.Collection;
|
||||
import org.springframework.data.couchbase.repository.Scope;
|
||||
import org.springframework.data.couchbase.repository.query.StringBasedN1qlQueryParser;
|
||||
import org.springframework.data.mapping.PropertyHandler;
|
||||
import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.couchbase.client.core.io.CollectionIdentifier;
|
||||
import com.couchbase.client.java.query.QueryOptions;
|
||||
|
||||
/**
|
||||
* N1qlJoinResolver resolves by converting the join definition to query statement and executing using CouchbaseTemplate
|
||||
*
|
||||
* @author Subhashni Balakrishnan
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
public class N1qlJoinResolver {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(N1qlJoinResolver.class);
|
||||
|
||||
public static <L, R> String buildQuery(ReactiveCouchbaseTemplate template, String scope, String collection,
|
||||
N1qlJoinResolverParameters parameters) {
|
||||
String joinType = "JOIN";
|
||||
String selectEntity = "SELECT META(rks).id AS " + SELECT_ID + ", META(rks).cas AS " + SELECT_CAS + ", (rks).* ";
|
||||
|
||||
StringBuilder useLKSBuilder = new StringBuilder();
|
||||
if (parameters.getJoinDefinition().index().length() > 0) {
|
||||
useLKSBuilder.append("INDEX(" + parameters.getJoinDefinition().index() + ")");
|
||||
}
|
||||
String useLKS = useLKSBuilder.length() > 0 ? "USE " + useLKSBuilder.toString() + " " : "";
|
||||
|
||||
KeySpacePair keySpacePair = getKeySpacePair(template.getBucketName(), scope, collection, parameters);
|
||||
|
||||
String from = "FROM " + keySpacePair.lhs.keyspace + " lks " + useLKS + joinType + " " + keySpacePair.rhs.keyspace
|
||||
+ " rks";
|
||||
|
||||
StringBasedN1qlQueryParser.N1qlSpelValues n1qlL = Query.getN1qlSpelValues(template.getConverter(), null, scope,
|
||||
keySpacePair.lhs.collection, parameters.getEntityTypeInfo().getType(), parameters.getEntityTypeInfo().getType(),
|
||||
false, null, null);
|
||||
String onLks = "lks." + n1qlL.filter;
|
||||
|
||||
StringBasedN1qlQueryParser.N1qlSpelValues n1qlR = Query.getN1qlSpelValues(template.getConverter(), null, scope,
|
||||
keySpacePair.rhs.collection, parameters.getAssociatedEntityTypeInfo().getType(),
|
||||
parameters.getAssociatedEntityTypeInfo().getType(), false, null, null);
|
||||
String onRks = "rks." + n1qlR.filter;
|
||||
|
||||
StringBuilder useRKSBuilder = new StringBuilder();
|
||||
if (parameters.getJoinDefinition().rightIndex().length() > 0) {
|
||||
useRKSBuilder.append("INDEX(" + parameters.getJoinDefinition().rightIndex() + ")");
|
||||
}
|
||||
if (!parameters.getJoinDefinition().hashside().equals(HashSide.NONE)) {
|
||||
if (useRKSBuilder.length() > 0)
|
||||
useRKSBuilder.append(" ");
|
||||
useRKSBuilder.append("HASH(" + parameters.getJoinDefinition().hashside().getValue() + ")");
|
||||
}
|
||||
if (parameters.getJoinDefinition().keys().length > 0) {
|
||||
if (useRKSBuilder.length() > 0)
|
||||
useRKSBuilder.append(" ");
|
||||
useRKSBuilder.append("KEYS [");
|
||||
String[] keys = parameters.getJoinDefinition().keys();
|
||||
|
||||
for (int i = 0; i < keys.length; i++) {
|
||||
if (i != 0)
|
||||
useRKSBuilder.append(",");
|
||||
useRKSBuilder.append("\"" + keys[i] + "\"");
|
||||
}
|
||||
useRKSBuilder.append("]");
|
||||
}
|
||||
|
||||
String on = "ON " + parameters.getJoinDefinition().on().concat(" AND " + onLks).concat(" AND " + onRks);
|
||||
|
||||
String where = "WHERE META(lks).id=\"" + parameters.getLksId() + "\"";
|
||||
where += ((parameters.getJoinDefinition().where().length() > 0) ? " AND " + parameters.getJoinDefinition().where()
|
||||
: "");
|
||||
|
||||
StringBuilder statementSb = new StringBuilder();
|
||||
statementSb.append(selectEntity);
|
||||
statementSb.append(" " + from);
|
||||
statementSb.append((useRKSBuilder.length() > 0 ? " USE " + useRKSBuilder.toString() : ""));
|
||||
statementSb.append(" " + on);
|
||||
statementSb.append(" " + where);
|
||||
return statementSb.toString();
|
||||
}
|
||||
|
||||
static KeySpacePair getKeySpacePair(String bucketName, String scope, String collection,
|
||||
N1qlJoinResolverParameters parameters) {
|
||||
Class<?> lhsClass = parameters.getEntityTypeInfo().getActualType().getType();
|
||||
String lhScope = scope != null ? scope : getScope(lhsClass);
|
||||
String lhCollection = collection != null ? collection : getCollection(lhsClass);
|
||||
Class<?> rhsClass = parameters.getAssociatedEntityTypeInfo().getActualType().getType();
|
||||
String rhScope = getScope(rhsClass);
|
||||
String rhCollection = getCollection(rhsClass);
|
||||
if (lhCollection != null && rhCollection != null) {
|
||||
// they both have non-default collections
|
||||
// It's possible that the scope for the lhs was set with an annotation on a repository method,
|
||||
// the entity class or the repository class or a query option. Since there is no means to set
|
||||
// the scope of the associated class by the method, repository class or query option (only
|
||||
// the annotation) we assume that the (possibly) dynamic scope of the entity would be a better
|
||||
// choice as it is logical to put collections to be joined in the same scope. Note that lhScope
|
||||
// is used for both keyspaces.
|
||||
return new KeySpacePair(lhCollection, x(i(bucketName) + "." + i(lhScope) + "." + i(lhCollection)), //
|
||||
rhCollection, x(i(bucketName) + "." + i(lhScope) + "." + i(rhCollection)));
|
||||
} else if (lhCollection != null && rhCollection == null) {
|
||||
// the lhs has a collection (and therefore a scope as well), but the rhs does not have a collection.
|
||||
// Use the lhScope and lhCollection for the entity. The rhs is just the bucket.
|
||||
return new KeySpacePair(lhCollection, x(i(bucketName) + "." + i(lhScope) + "." + i(lhCollection)), //
|
||||
null, i(bucketName));
|
||||
} else if (lhCollection != null && rhCollection == null) {
|
||||
// the lhs does not have a collection (or scope), but rhs does have a collection
|
||||
// Using the same (default) scope for the rhs would mean specifying a
|
||||
// non-default collection in a default scope - which is not allowed.
|
||||
// So use the scope and collection from the associated class.
|
||||
return new KeySpacePair(null, i(bucketName), //
|
||||
rhCollection, x(i(bucketName) + "." + i(rhScope) + "." + i(rhCollection)));
|
||||
} else { // neither have collections, just use the bucket.
|
||||
return new KeySpacePair(null, i(bucketName), null, i(bucketName));
|
||||
}
|
||||
}
|
||||
|
||||
static class KeySpacePair {
|
||||
KeySpaceInfo lhs;
|
||||
KeySpaceInfo rhs;
|
||||
|
||||
public KeySpacePair(String lhsCollection, N1QLExpression lhsKeyspace, String rhsCollection,
|
||||
N1QLExpression rhsKeyspace) {
|
||||
this.lhs = new KeySpaceInfo(lhsCollection, lhsKeyspace);
|
||||
this.rhs = new KeySpaceInfo(rhsCollection, rhsKeyspace);
|
||||
}
|
||||
|
||||
static class KeySpaceInfo {
|
||||
String collection;
|
||||
N1QLExpression keyspace;
|
||||
|
||||
public KeySpaceInfo(String collection, N1QLExpression keyspace) {
|
||||
this.collection = collection;
|
||||
this.keyspace = keyspace;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* from CouchbaseQueryMethod.getCollection()
|
||||
*
|
||||
* @param targetClass
|
||||
* @return
|
||||
*/
|
||||
static String getCollection(Class<?> targetClass) {
|
||||
// Could try the repository method, then the targetClass, then the repository class, then the entity class
|
||||
// but we don't have the repository method nor the repositoryMetdata at this point.
|
||||
AnnotatedElement[] annotated = new AnnotatedElement[] { targetClass };
|
||||
return OptionsBuilder.annotationString(Collection.class, CollectionIdentifier.DEFAULT_COLLECTION, annotated);
|
||||
}
|
||||
|
||||
/**
|
||||
* from CouchbaseQueryMethod.getScope()
|
||||
*
|
||||
* @param targetClass
|
||||
* @return
|
||||
*/
|
||||
static String getScope(Class<?> targetClass) {
|
||||
// Could try the repository method, then the targetClass, then the repository class, then the entity class
|
||||
// but we don't have the repository method nor the repositoryMetdata at this point.
|
||||
AnnotatedElement[] annotated = new AnnotatedElement[] { targetClass };
|
||||
return OptionsBuilder.annotationString(Scope.class, CollectionIdentifier.DEFAULT_SCOPE, annotated);
|
||||
}
|
||||
|
||||
public static <R> List<R> doResolve(ReactiveCouchbaseTemplate template, String scopeName, String collectionName,
|
||||
N1qlJoinResolverParameters parameters, Class<R> associatedEntityClass) {
|
||||
|
||||
String statement = buildQuery(template, scopeName, collectionName, parameters);
|
||||
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("Join query executed " + statement);
|
||||
}
|
||||
|
||||
N1QLQuery query = new N1QLQuery(N1QLExpression.x(statement), QueryOptions.queryOptions());
|
||||
List<R> result = template.findByQuery(associatedEntityClass).matching(query).all().collectList().block();
|
||||
return result.isEmpty() ? null : result;
|
||||
}
|
||||
|
||||
public static boolean isLazyJoin(N1qlJoin joinDefinition) {
|
||||
return joinDefinition.fetchType().equals(FetchType.LAZY);
|
||||
}
|
||||
|
||||
public static void handleProperties(CouchbasePersistentEntity<?> persistentEntity,
|
||||
ConvertingPropertyAccessor<?> accessor, ReactiveCouchbaseTemplate template, String id, String scope,
|
||||
String collection) {
|
||||
persistentEntity.doWithProperties((PropertyHandler<CouchbasePersistentProperty>) prop -> {
|
||||
if (prop.isAnnotationPresent(N1qlJoin.class)) {
|
||||
N1qlJoin definition = prop.findAnnotation(N1qlJoin.class);
|
||||
TypeInformation type = prop.getTypeInformation().getActualType();
|
||||
Class clazz = type.getType();
|
||||
N1qlJoinResolver.N1qlJoinResolverParameters parameters = new N1qlJoinResolver.N1qlJoinResolverParameters(
|
||||
definition, id, persistentEntity.getTypeInformation(), type, scope, collection);
|
||||
if (N1qlJoinResolver.isLazyJoin(definition)) {
|
||||
N1qlJoinResolver.N1qlJoinProxy proxy = new N1qlJoinResolver.N1qlJoinProxy(template, parameters);
|
||||
accessor.setProperty(prop,
|
||||
java.lang.reflect.Proxy.newProxyInstance(List.class.getClassLoader(), new Class[] { List.class }, proxy));
|
||||
} else {
|
||||
// clazz needs to be passes instead of just using
|
||||
// parameters.associatedType.getTypeInformation().getActualType().getType
|
||||
// to keep the compiler happy for the call template.findByQuery(associatedEntityClass)
|
||||
accessor.setProperty(prop, N1qlJoinResolver.doResolve(template, scope, collection, parameters, clazz));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static public class N1qlJoinProxy implements InvocationHandler {
|
||||
private final ReactiveCouchbaseTemplate reactiveTemplate;
|
||||
private final String collectionName = null;
|
||||
private final String scopeName = null;
|
||||
private final N1qlJoinResolverParameters params;
|
||||
private List<?> resolved = null;
|
||||
|
||||
public N1qlJoinProxy(ReactiveCouchbaseTemplate template, N1qlJoinResolverParameters params) {
|
||||
this.reactiveTemplate = template;
|
||||
this.params = params;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
|
||||
if (this.resolved == null) {
|
||||
this.resolved = doResolve(this.reactiveTemplate, this.params.getScopeName(), this.params.getCollectionName(),
|
||||
this.params, this.params.associatedEntityTypeInfo.getType());
|
||||
}
|
||||
return method.invoke(this.resolved, args);
|
||||
}
|
||||
}
|
||||
|
||||
static public class N1qlJoinResolverParameters {
|
||||
private N1qlJoin joinDefinition;
|
||||
private String lksId;
|
||||
private TypeInformation<?> entityTypeInfo;
|
||||
private TypeInformation<?> associatedEntityTypeInfo;
|
||||
private String scopeName;
|
||||
private String collectionName;
|
||||
|
||||
public N1qlJoinResolverParameters(N1qlJoin joinDefinition, String lksId, TypeInformation<?> entityTypeInfo,
|
||||
TypeInformation<?> associatedEntityTypeInfo, String scopeName, String collectionName) {
|
||||
Assert.notNull(joinDefinition, "The join definition is required");
|
||||
Assert.notNull(entityTypeInfo, "The entity type information is required");
|
||||
Assert.notNull(associatedEntityTypeInfo, "The associated entity type information is required");
|
||||
|
||||
this.joinDefinition = joinDefinition;
|
||||
this.lksId = lksId;
|
||||
this.entityTypeInfo = entityTypeInfo;
|
||||
this.associatedEntityTypeInfo = associatedEntityTypeInfo;
|
||||
this.scopeName = scopeName;
|
||||
this.collectionName = collectionName;
|
||||
}
|
||||
|
||||
public N1qlJoin getJoinDefinition() {
|
||||
return joinDefinition;
|
||||
}
|
||||
|
||||
public String getLksId() {
|
||||
return lksId;
|
||||
}
|
||||
|
||||
public TypeInformation getEntityTypeInfo() {
|
||||
return entityTypeInfo;
|
||||
}
|
||||
|
||||
public TypeInformation getAssociatedEntityTypeInfo() {
|
||||
return associatedEntityTypeInfo;
|
||||
}
|
||||
|
||||
public String getScopeName() {
|
||||
return scopeName;
|
||||
}
|
||||
|
||||
public String getCollectionName() {
|
||||
return collectionName;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* This package contains classes used for entity-to-JSON conversions, type mapping and writing.
|
||||
*/
|
||||
package org.springframework.data.couchbase.core.convert;
|
||||
@@ -0,0 +1,252 @@
|
||||
/*
|
||||
* 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.convert.translation;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.StringWriter;
|
||||
import java.io.Writer;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbaseList;
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbaseStorable;
|
||||
import org.springframework.data.mapping.MappingException;
|
||||
import org.springframework.data.mapping.model.SimpleTypeHolder;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonFactory;
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.core.JsonToken;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
/**
|
||||
* A Jackson JSON Translator that implements the {@link TranslationService} contract.
|
||||
*
|
||||
* @author Michael Nitschinger
|
||||
* @author Simon Baslé
|
||||
* @author Anastasiia Smirnova
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class JacksonTranslationService implements TranslationService, InitializingBean {
|
||||
|
||||
/**
|
||||
* Jackson Object Mapper;
|
||||
*/
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
/**
|
||||
* Type holder to help easily identify simple types.
|
||||
*/
|
||||
private SimpleTypeHolder simpleTypeHolder = SimpleTypeHolder.DEFAULT;
|
||||
|
||||
/**
|
||||
* JSON factory for Jackson.
|
||||
*/
|
||||
private JsonFactory factory = new JsonFactory();
|
||||
|
||||
/**
|
||||
* Encode a {@link CouchbaseStorable} to a JSON string.
|
||||
*
|
||||
* @param source the source document to encode.
|
||||
* @return the encoded JSON String.
|
||||
*/
|
||||
@Override
|
||||
public final String encode(final CouchbaseStorable source) {
|
||||
Writer writer = new StringWriter();
|
||||
|
||||
try {
|
||||
JsonGenerator generator = factory.createGenerator(writer);
|
||||
encodeRecursive(source, generator);
|
||||
generator.close();
|
||||
writer.close();
|
||||
} catch (IOException ex) {
|
||||
throw new RuntimeException("Could not encode JSON", ex);
|
||||
}
|
||||
|
||||
return writer.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively iterates through the sources and adds it to the JSON generator.
|
||||
*
|
||||
* @param source the source document
|
||||
* @param generator the JSON generator.
|
||||
* @throws IOException
|
||||
*/
|
||||
private void encodeRecursive(final CouchbaseStorable source, final JsonGenerator generator) throws IOException {
|
||||
generator.writeStartObject();
|
||||
|
||||
for (Map.Entry<String, Object> entry : ((CouchbaseDocument) source).export().entrySet()) {
|
||||
String key = entry.getKey();
|
||||
Object value = entry.getValue();
|
||||
generator.writeFieldName(key);
|
||||
if (value instanceof CouchbaseDocument) {
|
||||
encodeRecursive((CouchbaseDocument) value, generator);
|
||||
continue;
|
||||
}
|
||||
|
||||
final Class<?> clazz = value.getClass();
|
||||
|
||||
if (simpleTypeHolder.isSimpleType(clazz) && !isEnumOrClass(clazz)) {
|
||||
generator.writeObject(value);
|
||||
} else {
|
||||
objectMapper.writeValue(generator, value);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
generator.writeEndObject();
|
||||
}
|
||||
|
||||
private boolean isEnumOrClass(final Class<?> clazz) {
|
||||
return Enum.class.isAssignableFrom(clazz) || Class.class.isAssignableFrom(clazz);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode a JSON string into the {@link CouchbaseStorable} structure.
|
||||
*
|
||||
* @param source the source formatted document.
|
||||
* @param target the target of the populated data.
|
||||
* @return the decoded structure.
|
||||
*/
|
||||
@Override
|
||||
public final CouchbaseStorable decode(final String source, final CouchbaseStorable target) {
|
||||
try {
|
||||
JsonParser parser = factory.createParser((String) source);
|
||||
while (parser.nextToken() != null) {
|
||||
JsonToken currentToken = parser.getCurrentToken();
|
||||
|
||||
if (currentToken == JsonToken.START_OBJECT) {
|
||||
return decodeObject(parser, (CouchbaseDocument) target);
|
||||
} else if (currentToken == JsonToken.START_ARRAY) {
|
||||
return decodeArray(parser, new CouchbaseList());
|
||||
} else {
|
||||
throw new MappingException("JSON to decode needs to start as array or object!");
|
||||
}
|
||||
}
|
||||
parser.close();
|
||||
} catch (IOException ex) {
|
||||
throw new RuntimeException("Could not decode JSON", ex);
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to decode an object recursively.
|
||||
*
|
||||
* @param parser the JSON parser with the content.
|
||||
* @param target the target where the content should be stored.
|
||||
* @throws IOException
|
||||
* @returns the decoded object.
|
||||
*/
|
||||
private CouchbaseDocument decodeObject(final JsonParser parser, final CouchbaseDocument target) throws IOException {
|
||||
JsonToken currentToken = parser.nextToken();
|
||||
|
||||
String fieldName = "";
|
||||
while (currentToken != null && currentToken != JsonToken.END_OBJECT) {
|
||||
if (currentToken == JsonToken.START_OBJECT) {
|
||||
target.put(fieldName, decodeObject(parser, new CouchbaseDocument()));
|
||||
} else if (currentToken == JsonToken.START_ARRAY) {
|
||||
target.put(fieldName, decodeArray(parser, new CouchbaseList()));
|
||||
} else if (currentToken == JsonToken.FIELD_NAME) {
|
||||
fieldName = parser.getCurrentName();
|
||||
} else {
|
||||
target.put(fieldName, decodePrimitive(currentToken, parser));
|
||||
}
|
||||
|
||||
currentToken = parser.nextToken();
|
||||
}
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to decode an array recusrively.
|
||||
*
|
||||
* @param parser the JSON parser with the content.
|
||||
* @param target the target where the content should be stored.
|
||||
* @throws IOException
|
||||
* @returns the decoded list.
|
||||
*/
|
||||
private CouchbaseList decodeArray(final JsonParser parser, final CouchbaseList target) throws IOException {
|
||||
JsonToken currentToken = parser.nextToken();
|
||||
|
||||
while (currentToken != null && currentToken != JsonToken.END_ARRAY) {
|
||||
if (currentToken == JsonToken.START_OBJECT) {
|
||||
target.put(decodeObject(parser, new CouchbaseDocument()));
|
||||
} else if (currentToken == JsonToken.START_ARRAY) {
|
||||
target.put(decodeArray(parser, new CouchbaseList()));
|
||||
} else {
|
||||
target.put(decodePrimitive(currentToken, parser));
|
||||
}
|
||||
|
||||
currentToken = parser.nextToken();
|
||||
}
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to decode and assign a primitive.
|
||||
*
|
||||
* @param token the type of token.
|
||||
* @param parser the parser with the content.
|
||||
* @return the decoded primitve.
|
||||
* @throws IOException
|
||||
*/
|
||||
private Object decodePrimitive(final JsonToken token, final JsonParser parser) throws IOException {
|
||||
switch (token) {
|
||||
case VALUE_TRUE:
|
||||
case VALUE_FALSE:
|
||||
return parser.getBooleanValue();
|
||||
case VALUE_STRING:
|
||||
return parser.getValueAsString();
|
||||
case VALUE_NUMBER_INT:
|
||||
return parser.getNumberValue();
|
||||
case VALUE_NUMBER_FLOAT:
|
||||
return parser.getDoubleValue();
|
||||
case VALUE_NULL:
|
||||
return null;
|
||||
default:
|
||||
throw new MappingException("Could not decode primitive value " + token);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T decodeFragment(String source, Class<T> target) {
|
||||
try {
|
||||
return objectMapper.readValue(source, target);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Cannot decode ad-hoc JSON", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void setObjectMapper(final ObjectMapper objectMapper) {
|
||||
this.objectMapper = objectMapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
if (objectMapper == null) {
|
||||
objectMapper = new ObjectMapper();
|
||||
}
|
||||
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* 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.convert.translation;
|
||||
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbaseStorable;
|
||||
|
||||
/**
|
||||
* Defines a translation service to encode/decode responses into the {@link CouchbaseStorable} format.
|
||||
*
|
||||
* @author Michael Nitschinger
|
||||
*/
|
||||
public interface TranslationService {
|
||||
|
||||
/**
|
||||
* Encodes a JSON String into the target format.
|
||||
*
|
||||
* @param source the source contents to encode.
|
||||
* @return the encoded document representation.
|
||||
*/
|
||||
String encode(CouchbaseStorable source);
|
||||
|
||||
/**
|
||||
* Decodes the target format into a {@link CouchbaseDocument}
|
||||
*
|
||||
* @param source the source formatted document.
|
||||
* @param target the target of the populated data.
|
||||
* @return a properly populated document to work with.
|
||||
*/
|
||||
CouchbaseStorable decode(String source, CouchbaseStorable target);
|
||||
|
||||
/**
|
||||
* Decodes an ad-hoc JSON object into a corresponding "case" class.
|
||||
*
|
||||
* @param source the JSON for the ad-hoc JSON object (from a N1QL query for instance).
|
||||
* @param target the target class information.
|
||||
* @param <T> the target class.
|
||||
* @return an ad-hoc instance of the decoded JSON into the corresponding "case" class.
|
||||
*/
|
||||
<T> T decodeFragment(String source, Class<T> target);
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
/**
|
||||
* This package contains a service interface to translate entities to a Couchbase storable format, and its
|
||||
* implementations.
|
||||
*/
|
||||
package org.springframework.data.couchbase.core.convert.translation;
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* 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.index;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Repeatable;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Target({ ElementType.TYPE })
|
||||
@Documented
|
||||
@Repeatable(CompositeQueryIndexes.class)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface CompositeQueryIndex {
|
||||
|
||||
String[] fields();
|
||||
|
||||
String name() default "";
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* 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.index;
|
||||
|
||||
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;
|
||||
|
||||
@Target({ ElementType.TYPE })
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface CompositeQueryIndexes {
|
||||
|
||||
CompositeQueryIndex[] value();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
* 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.index;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.dao.DataIntegrityViolationException;
|
||||
import org.springframework.data.couchbase.core.CouchbaseOperations;
|
||||
import org.springframework.data.couchbase.core.index.CouchbasePersistentEntityIndexResolver.IndexDefinitionHolder;
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbaseMappingContext;
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentEntity;
|
||||
import org.springframework.data.couchbase.core.mapping.Document;
|
||||
import org.springframework.data.mapping.PersistentEntity;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.mapping.context.MappingContextEvent;
|
||||
|
||||
import com.couchbase.client.core.error.IndexExistsException;
|
||||
import com.couchbase.client.java.Cluster;
|
||||
|
||||
/**
|
||||
* @author Michael Nitschinger
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
public class CouchbasePersistentEntityIndexCreator implements ApplicationListener<MappingContextEvent<?, ?>> {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(CouchbasePersistentEntityIndexCreator.class);
|
||||
|
||||
private final Map<Class<?>, Boolean> classesSeen = new ConcurrentHashMap<>();
|
||||
private final CouchbaseMappingContext mappingContext;
|
||||
private final QueryIndexResolver indexResolver;
|
||||
private final CouchbaseOperations couchbaseOperations;
|
||||
|
||||
public CouchbasePersistentEntityIndexCreator(final CouchbaseMappingContext mappingContext,
|
||||
final CouchbaseOperations operations) {
|
||||
this.mappingContext = mappingContext;
|
||||
this.couchbaseOperations = operations;
|
||||
this.indexResolver = QueryIndexResolver.create(mappingContext, operations);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(final MappingContextEvent<?, ?> event) {
|
||||
if (!event.wasEmittedBy(mappingContext)) {
|
||||
return;
|
||||
}
|
||||
|
||||
PersistentEntity<?, ?> entity = event.getPersistentEntity();
|
||||
|
||||
// Double check type as Spring infrastructure does not consider nested generics
|
||||
if (entity instanceof CouchbasePersistentEntity) {
|
||||
checkForIndexes((CouchbasePersistentEntity<?>) entity);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkForIndexes(final CouchbasePersistentEntity<?> entity) {
|
||||
Class<?> type = entity.getType();
|
||||
|
||||
if (!classesSeen.containsKey(type)) {
|
||||
this.classesSeen.put(type, Boolean.TRUE);
|
||||
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("Analyzing class " + type + " for index information.");
|
||||
}
|
||||
|
||||
checkForAndCreateIndexes(entity);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkForAndCreateIndexes(final CouchbasePersistentEntity<?> entity) {
|
||||
if (entity.isAnnotationPresent(Document.class)) {
|
||||
|
||||
for (IndexDefinition indexDefinition : indexResolver.resolveIndexFor(entity.getTypeInformation())) {
|
||||
IndexDefinitionHolder indexToCreate = indexDefinition instanceof IndexDefinitionHolder
|
||||
? (IndexDefinitionHolder) indexDefinition
|
||||
: new IndexDefinitionHolder(indexDefinition.getIndexFields(), indexDefinition.getIndexName(),
|
||||
indexDefinition.getIndexPredicate());
|
||||
|
||||
createIndex(indexToCreate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void createIndex(final IndexDefinitionHolder indexToCreate) {
|
||||
Cluster cluster = couchbaseOperations.getCouchbaseClientFactory().getCluster();
|
||||
|
||||
StringBuilder statement = new StringBuilder("CREATE INDEX `")
|
||||
.append(indexToCreate.getIndexName()).append("` ON `")
|
||||
.append(couchbaseOperations.getBucketName()).append("` (")
|
||||
.append(String.join(",", indexToCreate.getIndexFields())).append(")");
|
||||
|
||||
if (indexToCreate.getIndexPredicate() != null && !indexToCreate.getIndexPredicate().isEmpty()) {
|
||||
statement.append(" WHERE ").append(indexToCreate.getIndexPredicate());
|
||||
}
|
||||
|
||||
try {
|
||||
cluster.query(statement.toString());
|
||||
} catch (IndexExistsException ex) {
|
||||
// ignored on purpose, rest is propagated
|
||||
LOGGER.debug("Index \"" + indexToCreate.getIndexName() + "\" already exists, ignoring.");
|
||||
} catch (Exception ex) {
|
||||
throw new DataIntegrityViolationException("Could not auto-create index with statement: " + statement.toString(),
|
||||
ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the current index creator was registered for the given {@link MappingContext}.
|
||||
*/
|
||||
public boolean isIndexCreatorFor(final MappingContext<?, ?> context) {
|
||||
return this.mappingContext.equals(context);
|
||||
}
|
||||
|
||||
public boolean hasSeen(CouchbasePersistentEntity<?> entity) {
|
||||
return classesSeen.containsKey(entity.getType());
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user