Browse Source

DATAMONGO-1848 - Import Document-based Querydsl support.

Original Pull Request: #579
pull/587/merge
Mark Paluch 8 years ago committed by Christoph Strobl
parent
commit
b7755e71f6
  1. 181
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/AbstractMongodbQuery.java
  2. 47
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/AnyEmbeddedBuilder.java
  3. 260
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/FetchableMongodbQuery.java
  4. 51
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/JoinBuilder.java
  5. 361
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/MongodbDocumentSerializer.java
  6. 61
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/MongodbExpressions.java

181
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/AbstractMongodbQuery.java

@ -0,0 +1,181 @@ @@ -0,0 +1,181 @@
/*
* Copyright 2018 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
*
* http://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.mongodb.repository.support;
import java.util.Collection;
import javax.annotation.Nullable;
import org.bson.Document;
import com.querydsl.core.DefaultQueryMetadata;
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.FactoryExpression;
import com.querydsl.core.types.OrderSpecifier;
import com.querydsl.core.types.ParamExpression;
import com.querydsl.core.types.Path;
import com.querydsl.core.types.Predicate;
import com.querydsl.core.types.dsl.CollectionPathBase;
/**
* {@code AbstractMongodbQuery} provides a base class for general Querydsl query implementation.
*
* @author Mark Paluch
* @param <Q> concrete subtype
*/
abstract class AbstractMongodbQuery<Q extends AbstractMongodbQuery<Q>> implements SimpleQuery<Q> {
@SuppressWarnings("serial")
static class NoResults extends RuntimeException {}
private final MongodbDocumentSerializer serializer;
private final QueryMixin<Q> queryMixin;
/**
* Create a new MongodbQuery instance
*
* @param serializer serializer
*/
@SuppressWarnings("unchecked")
public AbstractMongodbQuery(MongodbDocumentSerializer serializer) {
@SuppressWarnings("unchecked") // Q is this plus subclass
Q query = (Q) this;
this.queryMixin = new QueryMixin<Q>(query, new DefaultQueryMetadata(), false);
this.serializer = serializer;
}
/**
* Define a join
*
* @param ref reference
* @param target join target
* @return join builder
*/
public <T> JoinBuilder<Q, T> join(Path<T> ref, Path<T> target) {
return new JoinBuilder<Q, T>(queryMixin, ref, target);
}
/**
* Define a join
*
* @param ref reference
* @param target join target
* @return join builder
*/
public <T> JoinBuilder<Q, T> join(CollectionPathBase<?, T, ?> ref, Path<T> target) {
return new JoinBuilder<Q, T>(queryMixin, ref, target);
}
/**
* Define a constraint for an embedded object
*
* @param collection collection
* @param target target
* @return builder
*/
public <T> AnyEmbeddedBuilder<Q> anyEmbedded(Path<? extends Collection<T>> collection, Path<T> target) {
return new AnyEmbeddedBuilder<Q>(queryMixin, collection);
}
@Override
public Q distinct() {
return queryMixin.distinct();
}
public Q where(Predicate e) {
return queryMixin.where(e);
}
@Override
public Q where(Predicate... e) {
return queryMixin.where(e);
}
@Override
public Q limit(long limit) {
return queryMixin.limit(limit);
}
@Override
public Q offset(long offset) {
return queryMixin.offset(offset);
}
@Override
public Q restrict(QueryModifiers modifiers) {
return queryMixin.restrict(modifiers);
}
public Q orderBy(OrderSpecifier<?> o) {
return queryMixin.orderBy(o);
}
@Override
public Q orderBy(OrderSpecifier<?>... o) {
return queryMixin.orderBy(o);
}
@Override
public <T> Q set(ParamExpression<T> param, T value) {
return queryMixin.set(param, value);
}
protected Document createProjection(Expression<?> projection) {
if (projection instanceof FactoryExpression) {
Document obj = new Document();
for (Object expr : ((FactoryExpression) projection).getArgs()) {
if (expr instanceof Expression) {
obj.put((String) serializer.handle((Expression) expr), 1);
}
}
return obj;
}
return null;
}
protected Document createQuery(@Nullable Predicate predicate) {
if (predicate != null) {
return (Document) serializer.handle(predicate);
} else {
return new Document();
}
}
QueryMixin<Q> getQueryMixin() {
return queryMixin;
}
MongodbDocumentSerializer getSerializer() {
return serializer;
}
/**
* Get the where definition as a Document instance
*
* @return
*/
public Document asDocument() {
return createQuery(queryMixin.getMetadata().getWhere());
}
@Override
public String toString() {
return asDocument().toString();
}
}

47
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/AnyEmbeddedBuilder.java

@ -0,0 +1,47 @@ @@ -0,0 +1,47 @@
/*
* Copyright 2018 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
*
* http://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.mongodb.repository.support;
import java.util.Collection;
import com.querydsl.core.support.QueryMixin;
import com.querydsl.core.types.ExpressionUtils;
import com.querydsl.core.types.Path;
import com.querydsl.core.types.Predicate;
import com.querydsl.mongodb.MongodbOps;
/**
* {@code AnyEmbeddedBuilder} is a builder for constraints on embedded objects
*
* @param <Q> query type
* @author Mark Paluch
*/
class AnyEmbeddedBuilder<Q extends AbstractMongodbQuery<Q>> {
private final QueryMixin<Q> queryMixin;
private final Path<? extends Collection<?>> collection;
public AnyEmbeddedBuilder(QueryMixin<Q> queryMixin, Path<? extends Collection<?>> collection) {
this.queryMixin = queryMixin;
this.collection = collection;
}
public Q on(Predicate... conditions) {
return queryMixin
.where(ExpressionUtils.predicate(MongodbOps.ELEM_MATCH, collection, ExpressionUtils.allOf(conditions)));
}
}

260
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/FetchableMongodbQuery.java

@ -0,0 +1,260 @@ @@ -0,0 +1,260 @@
/*
* Copyright 2018 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
*
* http://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.mongodb.repository.support;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import javax.annotation.Nullable;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.query.BasicQuery;
import org.springframework.data.mongodb.core.query.Query;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import com.mysema.commons.lang.CloseableIterator;
import com.querydsl.core.Fetchable;
import com.querydsl.core.JoinExpression;
import com.querydsl.core.QueryMetadata;
import com.querydsl.core.QueryModifiers;
import com.querydsl.core.QueryResults;
import com.querydsl.core.types.Expression;
import com.querydsl.core.types.ExpressionUtils;
import com.querydsl.core.types.Operation;
import com.querydsl.core.types.OrderSpecifier;
import com.querydsl.core.types.Path;
import com.querydsl.core.types.Predicate;
/**
* {@link Fetchable} Mongodb query with a pluggable Document to Bean transformation.
*
* @param <K> result type
* @param <Q> concrete subtype
* @author Mark Paluch
*/
class FetchableMongodbQuery<K> extends AbstractMongodbQuery<FetchableMongodbQuery<K>> implements Fetchable<K> {
private final Class<K> entityClass;
private final String collection;
private final MongoOperations mongoOperations;
public FetchableMongodbQuery(MongodbDocumentSerializer serializer, Class<? extends K> entityClass,
MongoOperations mongoOperations) {
super(serializer);
this.entityClass = (Class<K>) entityClass;
this.collection = mongoOperations.getCollectionName(entityClass);
this.mongoOperations = mongoOperations;
}
public FetchableMongodbQuery(MongodbDocumentSerializer serializer, Class<? extends K> entityClass, String collection,
MongoOperations mongoOperations) {
super(serializer);
this.entityClass = (Class<K>) entityClass;
this.collection = collection;
this.mongoOperations = mongoOperations;
}
/**
* Iterate with the specific fields
*
* @param paths fields to return
* @return iterator
*/
public CloseableIterator<K> iterate(Path<?>... paths) {
getQueryMixin().setProjection(paths);
return iterate();
}
@Override
public CloseableIterator<K> iterate() {
org.springframework.data.util.CloseableIterator<? extends K> stream = mongoOperations.stream(createQuery(),
entityClass, collection);
return new CloseableIterator<K>() {
@Override
public boolean hasNext() {
return stream.hasNext();
}
@Override
public K next() {
return stream.next();
}
@Override
public void remove() {
}
@Override
public void close() {
stream.close();
}
};
}
/**
* Fetch with the specific fields
*
* @param paths fields to return
* @return results
*/
public List<K> fetch(Path<?>... paths) {
getQueryMixin().setProjection(paths);
return fetch();
}
@Override
public List<K> fetch() {
return mongoOperations.query(entityClass).matching(createQuery()).all();
}
/**
* Fetch first with the specific fields
*
* @param paths fields to return
* @return first result
*/
public K fetchFirst(Path<?>... paths) {
getQueryMixin().setProjection(paths);
return fetchFirst();
}
@Override
public K fetchFirst() {
return mongoOperations.query(entityClass).matching(createQuery()).firstValue();
}
/**
* Fetch one with the specific fields
*
* @param paths fields to return
* @return first result
*/
public K fetchOne(Path<?>... paths) {
getQueryMixin().setProjection(paths);
return fetchOne();
}
@Override
public K fetchOne() {
return mongoOperations.query(entityClass).matching(createQuery()).oneValue();
}
/**
* Fetch results with the specific fields
*
* @param paths fields to return
* @return results
*/
public QueryResults<K> fetchResults(Path<?>... paths) {
getQueryMixin().setProjection(paths);
return fetchResults();
}
@Override
public QueryResults<K> fetchResults() {
long total = fetchCount();
if (total > 0L) {
return new QueryResults<>(fetch(), getQueryMixin().getMetadata().getModifiers(), total);
} else {
return QueryResults.emptyResults();
}
}
@Override
public long fetchCount() {
return mongoOperations.query(entityClass).matching(createQuery()).count();
}
protected org.springframework.data.mongodb.core.query.Query createQuery() {
QueryMetadata metadata = getQueryMixin().getMetadata();
Predicate filter = createFilter(metadata);
return createQuery(filter, metadata.getProjection(), metadata.getModifiers(), metadata.getOrderBy());
}
protected org.springframework.data.mongodb.core.query.Query createQuery(@Nullable Predicate where,
Expression<?> projection, QueryModifiers modifiers, List<OrderSpecifier<?>> orderBy) {
BasicQuery basicQuery = new BasicQuery(createQuery(where), createProjection(projection));
Integer limit = modifiers.getLimitAsInteger();
Integer offset = modifiers.getOffsetAsInteger();
if (limit != null) {
basicQuery.limit(limit);
}
if (offset != null) {
basicQuery.skip(offset);
}
if (orderBy.size() > 0) {
basicQuery.setSortObject(getSerializer().toSort(orderBy));
}
return basicQuery;
}
@Nullable
protected Predicate createFilter(QueryMetadata metadata) {
Predicate filter;
if (!metadata.getJoins().isEmpty()) {
filter = ExpressionUtils.allOf(metadata.getWhere(), createJoinFilter(metadata));
} else {
filter = metadata.getWhere();
}
return filter;
}
@SuppressWarnings("unchecked")
@Nullable
protected Predicate createJoinFilter(QueryMetadata metadata) {
Multimap<Expression<?>, Predicate> predicates = HashMultimap.create();
List<JoinExpression> joins = metadata.getJoins();
for (int i = joins.size() - 1; i >= 0; i--) {
JoinExpression join = joins.get(i);
Path<?> source = (Path) ((Operation<?>) join.getTarget()).getArg(0);
Path<?> target = (Path) ((Operation<?>) join.getTarget()).getArg(1);
Collection<Predicate> extraFilters = predicates.get(target.getRoot());
Predicate filter = ExpressionUtils.allOf(join.getCondition(), allOf(extraFilters));
List<? extends Object> ids = getIds(target.getType(), filter);
if (ids.isEmpty()) {
throw new NoResults();
}
Path<?> path = ExpressionUtils.path(String.class, source, "$id");
predicates.put(source.getRoot(), ExpressionUtils.in((Path<Object>) path, ids));
}
Path<?> source = (Path) ((Operation) joins.get(0).getTarget()).getArg(0);
return allOf(predicates.get(source.getRoot()));
}
private Predicate allOf(Collection<Predicate> predicates) {
return predicates != null ? ExpressionUtils.allOf(predicates) : null;
}
protected List<Object> getIds(Class<?> targetType, Predicate condition) {
// TODO : fetch only ids
Query query = createQuery(condition, null, QueryModifiers.EMPTY, Collections.emptyList());
return mongoOperations.findDistinct(query, "_id", targetType, Object.class);
}
}

51
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/JoinBuilder.java

@ -0,0 +1,51 @@ @@ -0,0 +1,51 @@
/*
* Copyright 2018 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
*
* http://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.mongodb.repository.support;
import com.querydsl.core.JoinType;
import com.querydsl.core.support.QueryMixin;
import com.querydsl.core.types.ExpressionUtils;
import com.querydsl.core.types.Path;
import com.querydsl.core.types.Predicate;
/**
* {@code JoinBuilder} is a builder for join constraints
*
* @author Mark Paluch
* @param <Q>
* @param <T>
*/
class JoinBuilder<Q extends AbstractMongodbQuery<Q>, T> {
private final QueryMixin<Q> queryMixin;
private final Path<?> ref;
private final Path<T> target;
public JoinBuilder(QueryMixin<Q> queryMixin, Path<?> ref, Path<T> target) {
this.queryMixin = queryMixin;
this.ref = ref;
this.target = target;
}
@SuppressWarnings("unchecked")
public Q on(Predicate... conditions) {
queryMixin.addJoin(JoinType.JOIN, ExpressionUtils.as((Path) ref, target));
queryMixin.on(conditions);
return queryMixin.getSelf();
}
}

361
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/MongodbDocumentSerializer.java

@ -0,0 +1,361 @@ @@ -0,0 +1,361 @@
/*
* Copyright 2018 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
*
* http://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.mongodb.repository.support;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import org.bson.Document;
import org.bson.types.ObjectId;
import com.google.common.collect.Sets;
import com.mongodb.DBRef;
import com.querydsl.core.types.*;
import com.querydsl.mongodb.MongodbOps;
/**
* Serializes the given Querydsl query to a Document query for MongoDB.
*
* @author Mark Paluch
*/
abstract class MongodbDocumentSerializer implements Visitor<Object, Void> {
public Object handle(Expression<?> expression) {
return expression.accept(this, null);
}
public Document toSort(List<OrderSpecifier<?>> orderBys) {
Document sort = new Document();
for (OrderSpecifier<?> orderBy : orderBys) {
Object key = orderBy.getTarget().accept(this, null);
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 Document asDocument(String key, Object value) {
return new Document(key, value);
}
@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) {
return asDocument(asDBKey(lhs, 0), asDocument("$size", 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));
}
} else if (op == Ops.STRING_IS_EMPTY) {
return asDocument(asDBKey(expr, 0), "");
} else if (op == Ops.AND) {
Map<Object, Object> lhs = (Map<Object, Object>) handle(expr.getArg(0));
Map<Object, Object> rhs = (Map<Object, Object>) handle(expr.getArg(1));
if (Sets.intersection(lhs.keySet(), rhs.keySet()).isEmpty()) {
lhs.putAll(rhs);
return lhs;
} else {
List<Object> list = new ArrayList<Object>(2);
list.add(handle(expr.getArg(0)));
list.add(handle(expr.getArg(1)));
return asDocument("$and", list);
}
} 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 {
Document arg = (Document) handle(expr.getArg(0));
return negate(arg);
}
} else if (op == Ops.OR) {
List<Object> list = new ArrayList<Object>(2);
list.add(handle(expr.getArg(0)));
list.add(handle(expr.getArg(1)));
return asDocument("$or", list);
} 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)));
} else if (op == Ops.STARTS_WITH) {
return asDocument(asDBKey(expr, 0), Pattern.compile("^" + regexValue(expr, 1)));
} else if (op == Ops.STARTS_WITH_IC) {
return asDocument(asDBKey(expr, 0), Pattern.compile("^" + regexValue(expr, 1), Pattern.CASE_INSENSITIVE));
} else if (op == Ops.ENDS_WITH) {
return asDocument(asDBKey(expr, 0), Pattern.compile(regexValue(expr, 1) + "$"));
} else if (op == Ops.ENDS_WITH_IC) {
return asDocument(asDBKey(expr, 0), Pattern.compile(regexValue(expr, 1) + "$", Pattern.CASE_INSENSITIVE));
} else if (op == Ops.EQ_IGNORE_CASE) {
return asDocument(asDBKey(expr, 0), Pattern.compile("^" + regexValue(expr, 1) + "$", Pattern.CASE_INSENSITIVE));
} else if (op == Ops.STRING_CONTAINS) {
return asDocument(asDBKey(expr, 0), Pattern.compile(".*" + regexValue(expr, 1) + ".*"));
} else if (op == Ops.STRING_CONTAINS_IC) {
return asDocument(asDBKey(expr, 0), Pattern.compile(".*" + regexValue(expr, 1) + ".*", Pattern.CASE_INSENSITIVE));
} else if (op == Ops.MATCHES) {
return asDocument(asDBKey(expr, 0), Pattern.compile(asDBValue(expr, 1).toString()));
} else if (op == Ops.MATCHES_IC) {
return asDocument(asDBKey(expr, 0), Pattern.compile(asDBValue(expr, 1).toString(), Pattern.CASE_INSENSITIVE));
} else if (op == Ops.LIKE) {
String regex = ExpressionUtils.likeToRegex((Expression) expr.getArg(1)).toString();
return asDocument(asDBKey(expr, 0), Pattern.compile(regex));
} else if (op == Ops.BETWEEN) {
Document value = new Document("$gte", asDBValue(expr, 1));
value.append("$lte", asDBValue(expr, 2));
return asDocument(asDBKey(expr, 0), value);
} 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));
} else {
Path<?> path = (Path<?>) expr.getArg(exprIndex);
Constant<?> constant = (Constant<?>) expr.getArg(constIndex);
return asDocument(asDBKey(expr, exprIndex), convert(path, constant));
}
} 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));
} else {
Path<?> path = (Path<?>) expr.getArg(exprIndex);
Constant<?> constant = (Constant<?>) expr.getArg(constIndex);
return asDocument(asDBKey(expr, exprIndex), asDocument("$ne", convert(path, constant)));
}
} 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);
} else if (op == Ops.LT) {
return asDocument(asDBKey(expr, 0), asDocument("$lt", asDBValue(expr, 1)));
} else if (op == Ops.GT) {
return asDocument(asDBKey(expr, 0), asDocument("$gt", asDBValue(expr, 1)));
} else if (op == Ops.LOE) {
return asDocument(asDBKey(expr, 0), asDocument("$lte", asDBValue(expr, 1)));
} else if (op == Ops.GOE) {
return asDocument(asDBKey(expr, 0), asDocument("$gte", asDBValue(expr, 1)));
} else if (op == Ops.IS_NULL) {
return asDocument(asDBKey(expr, 0), asDocument("$exists", false));
} else if (op == Ops.IS_NOT_NULL) {
return asDocument(asDBKey(expr, 0), asDocument("$exists", true));
} else if (op == Ops.CONTAINS_KEY) {
Path<?> path = (Path<?>) expr.getArg(0);
Expression<?> key = expr.getArg(1);
return asDocument(visit(path, context) + "." + key.toString(), asDocument("$exists", true));
} else if (op == MongodbOps.NEAR) {
return asDocument(asDBKey(expr, 0), asDocument("$near", asDBValue(expr, 1)));
} else if (op == MongodbOps.NEAR_SPHERE) {
return asDocument(asDBKey(expr, 0), asDocument("$nearSphere", asDBValue(expr, 1)));
} else if (op == MongodbOps.ELEM_MATCH) {
return asDocument(asDBKey(expr, 0), asDocument("$elemMatch", asDBValue(expr, 1)));
}
throw new UnsupportedOperationException("Illegal operation " + expr);
}
private Object negate(Document 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((Document) o));
}
list.add(asDocument("$or", list2));
} else if (entry.getValue() instanceof Pattern) {
list.add(asDocument(entry.getKey(), asDocument("$not", entry.getValue())));
} else if (entry.getValue() instanceof Document) {
list.add(negate(entry.getKey(), (Document) 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, Document 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);
}
}
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();
}
}

61
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/MongodbExpressions.java

@ -0,0 +1,61 @@ @@ -0,0 +1,61 @@
/*
* Copyright 2018 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
*
* http://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.mongodb.repository.support;
import java.util.Arrays;
import com.querydsl.core.types.ConstantImpl;
import com.querydsl.core.types.Expression;
import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.core.types.dsl.Expressions;
import com.querydsl.mongodb.MongodbOps;
/**
* Mongodb Document-API-specific operations.
*
* @author tiwe
* @author Mark Paluch
*/
class MongodbExpressions {
private MongodbExpressions() {}
/**
* Finds the closest points relative to the given location and orders the results with decreasing proximity
*
* @param expr location
* @param latVal latitude
* @param longVal longitude
* @return predicate
*/
public static BooleanExpression near(Expression<Double[]> expr, double latVal, double longVal) {
return Expressions.booleanOperation(MongodbOps.NEAR, expr, ConstantImpl.create(Arrays.asList(latVal, longVal)));
}
/**
* Finds the closest points relative to the given location on a sphere and orders the results with decreasing
* proximity
*
* @param expr location
* @param latVal latitude
* @param longVal longitude
* @return predicate
*/
public static BooleanExpression nearSphere(Expression<Double[]> expr, double latVal, double longVal) {
return Expressions.booleanOperation(MongodbOps.NEAR_SPHERE, expr,
ConstantImpl.create(Arrays.asList(latVal, longVal)));
}
}
Loading…
Cancel
Save