diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/MongoSimpleTypes.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/MongoSimpleTypes.java index 6b2202db4..73e9ecdb3 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/MongoSimpleTypes.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/MongoSimpleTypes.java @@ -22,15 +22,13 @@ import java.util.Set; import java.util.UUID; import java.util.regex.Pattern; -import org.bson.BsonObjectId; +import org.bson.*; import org.bson.types.Binary; import org.bson.types.CodeWScope; import org.bson.types.CodeWithScope; import org.bson.types.Decimal128; import org.bson.types.ObjectId; import org.springframework.data.mapping.model.SimpleTypeHolder; -import org.springframework.data.mongodb.util.MongoClientVersion; -import org.springframework.util.ClassUtils; import com.mongodb.DBRef; @@ -62,6 +60,24 @@ public abstract class MongoSimpleTypes { simpleTypes.add(Binary.class); simpleTypes.add(UUID.class); simpleTypes.add(Decimal128.class); + + simpleTypes.add(BsonBinary.class); + simpleTypes.add(BsonBoolean.class); + simpleTypes.add(BsonDateTime.class); + simpleTypes.add(BsonDbPointer.class); + simpleTypes.add(BsonDecimal128.class); + simpleTypes.add(BsonDocument.class); + simpleTypes.add(BsonDocument.class); + simpleTypes.add(BsonDouble.class); + simpleTypes.add(BsonInt32.class); + simpleTypes.add(BsonInt64.class); + simpleTypes.add(BsonJavaScript.class); + simpleTypes.add(BsonJavaScriptWithScope.class); + simpleTypes.add(BsonObjectId.class); + simpleTypes.add(BsonRegularExpression.class); + simpleTypes.add(BsonString.class); + simpleTypes.add(BsonTimestamp.class); + MONGO_SIMPLE_TYPES = Collections.unmodifiableSet(simpleTypes); } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/AbstractMongodbQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/AbstractMongodbQuery.java deleted file mode 100644 index 4f425da98..000000000 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/AbstractMongodbQuery.java +++ /dev/null @@ -1,181 +0,0 @@ -/* - * 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 concrete subtype - */ -abstract class AbstractMongodbQuery> implements SimpleQuery { - - @SuppressWarnings("serial") - static class NoResults extends RuntimeException {} - - private final MongodbDocumentSerializer serializer; - private final QueryMixin 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(query, new DefaultQueryMetadata(), false); - this.serializer = serializer; - } - - /** - * Define a join - * - * @param ref reference - * @param target join target - * @return join builder - */ - public JoinBuilder join(Path ref, Path target) { - return new JoinBuilder(queryMixin, ref, target); - } - - /** - * Define a join - * - * @param ref reference - * @param target join target - * @return join builder - */ - public JoinBuilder join(CollectionPathBase ref, Path target) { - return new JoinBuilder(queryMixin, ref, target); - } - - /** - * Define a constraint for an embedded object - * - * @param collection collection - * @param target target - * @return builder - */ - public AnyEmbeddedBuilder anyEmbedded(Path> collection, Path target) { - return new AnyEmbeddedBuilder(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 Q set(ParamExpression 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 new Document(); - } - - protected Document createQuery(@Nullable Predicate predicate) { - if (predicate != null) { - return (Document) serializer.handle(predicate); - } else { - return new Document(); - } - } - - QueryMixin 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(); - } -} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/MongodbDocumentSerializer.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/MongodbDocumentSerializer.java index eb125d7dd..048233d5b 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/MongodbDocumentSerializer.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/MongodbDocumentSerializer.java @@ -17,80 +17,154 @@ package org.springframework.data.mongodb.repository.support; import java.util.ArrayList; import java.util.Collection; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.regex.Pattern; +import org.bson.BsonJavaScript; +import org.bson.BsonRegularExpression; import org.bson.Document; import org.bson.types.ObjectId; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; -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. + *

+ *

+ * Original implementation source {@link com.querydsl.mongodb.MongodbSerializer} by {@literal The Querydsl Team} + * (http://www.querydsl.com/team) licensed under the Apache License, Version + * 2.0. + *

+ * Modified to use {@link Document} instead of {@link com.mongodb.DBObject}, updated nullable types and code format. Use + * Bson specific types and add {@link QuerydslMongoOps#NO_MATCH}. * + * @author laimw * @author Mark Paluch + * @author Christoph Strobl + * @since 2.1 */ abstract class MongodbDocumentSerializer implements Visitor { - public Object handle(Expression expression) { + @Nullable + Object handle(Expression expression) { return expression.accept(this, null); } - public Document toSort(List> 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); + /** + * Create the MongoDB specific query document. + * + * @param predicate must not be {@literal null}. + * @return empty {@link Document} by default. + */ + Document toQuery(Predicate predicate) { + + Object value = handle(predicate); + + if (value == null) { + return new Document(); } + + Assert.isInstanceOf(Document.class, value, + () -> String.format("Invalid type. Expected Document but found %s", value.getClass())); + + return (Document) value; + } + + /** + * Create the MongoDB specific sort document. + * + * @param orderBys must not be {@literal null}. + * @return empty {@link Document} by default. + */ + Document toSort(List> orderBys) { + + Document sort = new Document(); + + orderBys.forEach(orderSpecifier -> { + + Object key = orderSpecifier.getTarget().accept(this, null); + + Assert.notNull(key, () -> String.format("Mapped sort key for %s must not be null!", orderSpecifier)); + sort.append(key.toString(), orderSpecifier.getOrder() == Order.ASC ? 1 : -1); + }); + return sort; } + /* + * (non-Javadoc) + * @see com.querydsl.core.types.Visitor#visit(com.querydsl.core.types.Constant, java.lang.Void) + */ @Override public Object visit(Constant expr, Void context) { - if (Enum.class.isAssignableFrom(expr.getType())) { - @SuppressWarnings("unchecked") // Guarded by previous check - Constant> expectedExpr = (Constant>) expr; - return expectedExpr.getConstant().name(); - } else { + + if (!Enum.class.isAssignableFrom(expr.getType())) { return expr.getConstant(); } + + @SuppressWarnings("unchecked") // Guarded by previous check + Constant> expectedExpr = (Constant>) expr; + return expectedExpr.getConstant().name(); } + /* + * (non-Javadoc) + * @see com.querydsl.core.types.Visitor#visit(com.querydsl.core.types.TemplateExpression, java.lang.Void) + */ @Override public Object visit(TemplateExpression expr, Void context) { throw new UnsupportedOperationException(); } + /* + * (non-Javadoc) + * @see com.querydsl.core.types.Visitor#visit(com.querydsl.core.types.FactoryExpression, java.lang.Void) + */ @Override public Object visit(FactoryExpression expr, Void context) { throw new UnsupportedOperationException(); } protected String asDBKey(Operation expr, int index) { - return (String) asDBValue(expr, index); + + String key = (String) asDBValue(expr, index); + + Assert.hasText(key, () -> String.format("Mapped key must not be null nor empty for expression %s.", expr)); + return key; } + @Nullable 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()); + + Object value = expr.getArg(index).accept(this, null); + + Assert.notNull(value, () -> String.format("Regex for %s must not be null.", expr)); + return Pattern.quote(value.toString()); } - protected Document asDocument(String key, Object value) { + protected Document asDocument(String key, @Nullable 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) { @@ -105,15 +179,19 @@ abstract class MongodbDocumentSerializer implements Visitor { } } else if (op == Ops.STRING_IS_EMPTY) { return asDocument(asDBKey(expr, 0), ""); - } else if (op == Ops.AND) { + Map lhs = (Map) handle(expr.getArg(0)); Map rhs = (Map) handle(expr.getArg(1)); - if (Sets.intersection(lhs.keySet(), rhs.keySet()).isEmpty()) { + + LinkedHashSet> lhs2 = new LinkedHashSet<>(lhs.entrySet()); + lhs2.retainAll(rhs.entrySet()); + + if (lhs2.isEmpty()) { lhs.putAll(rhs); return lhs; } else { - List list = new ArrayList(2); + List list = new ArrayList<>(2); list.add(handle(expr.getArg(0))); list.add(handle(expr.getArg(1))); return asDocument("$and", list); @@ -133,53 +211,47 @@ abstract class MongodbDocumentSerializer implements Visitor { } } else if (op == Ops.OR) { - List list = new ArrayList(2); + + List list = new ArrayList<>(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))); - + return asDocument(asDBKey(expr, 0), new BsonRegularExpression("^" + regexValue(expr, 1))); } else if (op == Ops.STARTS_WITH_IC) { - return asDocument(asDBKey(expr, 0), Pattern.compile("^" + regexValue(expr, 1), Pattern.CASE_INSENSITIVE)); - + return asDocument(asDBKey(expr, 0), new BsonRegularExpression("^" + regexValue(expr, 1), "i")); } else if (op == Ops.ENDS_WITH) { - return asDocument(asDBKey(expr, 0), Pattern.compile(regexValue(expr, 1) + "$")); - + return asDocument(asDBKey(expr, 0), new BsonRegularExpression(regexValue(expr, 1) + "$")); } else if (op == Ops.ENDS_WITH_IC) { - return asDocument(asDBKey(expr, 0), Pattern.compile(regexValue(expr, 1) + "$", Pattern.CASE_INSENSITIVE)); - + return asDocument(asDBKey(expr, 0), new BsonRegularExpression(regexValue(expr, 1) + "$", "i")); } else if (op == Ops.EQ_IGNORE_CASE) { - return asDocument(asDBKey(expr, 0), Pattern.compile("^" + regexValue(expr, 1) + "$", Pattern.CASE_INSENSITIVE)); - + return asDocument(asDBKey(expr, 0), new BsonRegularExpression("^" + regexValue(expr, 1) + "$", "i")); } else if (op == Ops.STRING_CONTAINS) { - return asDocument(asDBKey(expr, 0), Pattern.compile(".*" + regexValue(expr, 1) + ".*")); - + return asDocument(asDBKey(expr, 0), new BsonRegularExpression(".*" + regexValue(expr, 1) + ".*")); } else if (op == Ops.STRING_CONTAINS_IC) { - return asDocument(asDBKey(expr, 0), Pattern.compile(".*" + regexValue(expr, 1) + ".*", Pattern.CASE_INSENSITIVE)); - + return asDocument(asDBKey(expr, 0), new BsonRegularExpression(".*" + regexValue(expr, 1) + ".*", "i")); } else if (op == Ops.MATCHES) { - return asDocument(asDBKey(expr, 0), Pattern.compile(asDBValue(expr, 1).toString())); - + return asDocument(asDBKey(expr, 0), new BsonRegularExpression(asDBValue(expr, 1).toString())); } else if (op == Ops.MATCHES_IC) { - return asDocument(asDBKey(expr, 0), Pattern.compile(asDBValue(expr, 1).toString(), Pattern.CASE_INSENSITIVE)); - + return asDocument(asDBKey(expr, 0), new BsonRegularExpression(asDBValue(expr, 1).toString(), "i")); } else if (op == Ops.LIKE) { - String regex = ExpressionUtils.likeToRegex((Expression) expr.getArg(1)).toString(); - return asDocument(asDBKey(expr, 0), Pattern.compile(regex)); + String regex = ExpressionUtils.likeToRegex((Expression) expr.getArg(1)).toString(); + return asDocument(asDBKey(expr, 0), new BsonRegularExpression(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) { @@ -195,85 +267,80 @@ abstract class MongodbDocumentSerializer implements Visitor { 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>) 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 list = new ArrayList(2); + + List list = new ArrayList<>(2); list.add(asDocument(asDBKey(expr, 0), new ArrayList())); 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))); + } else if (op == QuerydslMongoOps.NO_MATCH) { + return new Document("$where", new BsonJavaScript("function() { return false }")); } throw new UnsupportedOperationException("Illegal operation " + expr); } private Object negate(Document arg) { - List list = new ArrayList(); + + List list = new ArrayList<>(); for (Map.Entry entry : arg.entrySet()) { + if (entry.getKey().equals("$or")) { list.add(asDocument("$nor", entry.getValue())); - } else if (entry.getKey().equals("$and")) { - List list2 = new ArrayList(); + + List list2 = new ArrayList<>(); for (Object o : ((Collection) entry.getValue())) { list2.add(negate((Document) o)); } list.add(asDocument("$or", list2)); - - } else if (entry.getValue() instanceof Pattern) { + } else if (entry.getValue() instanceof Pattern || entry.getValue() instanceof BsonRegularExpression) { 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()))); } @@ -282,25 +349,30 @@ abstract class MongodbDocumentSerializer implements Visitor { } private Object negate(String key, Document value) { + if (value.size() == 1) { return asDocument(key, asDocument("$not", value)); - } else { - List list2 = new ArrayList(); + + List list2 = new ArrayList<>(); for (Map.Entry 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; } @@ -319,7 +391,7 @@ abstract class MongodbDocumentSerializer implements Visitor { protected abstract DBRef asReference(Object constant); - protected abstract boolean isReference(Path arg); + protected abstract boolean isReference(@Nullable Path arg); protected boolean isId(Path arg) { // TODO override in subclass @@ -328,8 +400,11 @@ abstract class MongodbDocumentSerializer implements Visitor { @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(); @@ -337,6 +412,7 @@ abstract class MongodbDocumentSerializer implements Visitor { 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; diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/MongodbExpressions.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/MongodbExpressions.java deleted file mode 100644 index 79fd892a3..000000000 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/MongodbExpressions.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * 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 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 expr, double latVal, double longVal) { - return Expressions.booleanOperation(MongodbOps.NEAR_SPHERE, expr, - ConstantImpl.create(Arrays.asList(latVal, longVal))); - } -} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/QuerydslAbstractMongodbQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/QuerydslAbstractMongodbQuery.java new file mode 100644 index 000000000..74ff1789b --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/QuerydslAbstractMongodbQuery.java @@ -0,0 +1,202 @@ +/* + * 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.List; + +import org.bson.Document; +import org.springframework.lang.Nullable; + +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.Predicate; + +/** + * {@code QuerydslAbstractMongodbQuery} provides a base class for general Querydsl query implementation. + *

+ * Original implementation source {@link com.querydsl.mongodb.AbstractMongodbQuery} by {@literal The Querydsl Team} + * (http://www.querydsl.com/team) licensed under the Apache License, Version + * 2.0. + *

+ * Modified for usage with {@link MongodbDocumentSerializer}. + * + * @param concrete subtype + * @author laimw + * @author Mark Paluch + * @author Christoph Strobl + * @since 2.1 + */ +public abstract class QuerydslAbstractMongodbQuery> + implements SimpleQuery { + + private final MongodbDocumentSerializer serializer; + private final QueryMixin queryMixin; + + /** + * Create a new MongodbQuery instance + * + * @param serializer serializer + */ + @SuppressWarnings("unchecked") + QuerydslAbstractMongodbQuery(MongodbDocumentSerializer serializer) { + + this.queryMixin = new QueryMixin<>((Q) this, new DefaultQueryMetadata(), false); + this.serializer = serializer; + } + + /* + * (non-Javadoc) + * @see com.querydsl.core.SimpleQuery#distinct() + */ + @Override + public Q distinct() { + return queryMixin.distinct(); + } + + /* + * (non-Javadoc) + * @see com.querydsl.core.FilteredClause#where(com.querydsl.core.types.Predicate[]) + */ + @Override + public Q where(Predicate... e) { + return queryMixin.where(e); + } + + /* + * (non-Javadoc) + * @see com.querydsl.core.SimpleQuery#limit(long) + */ + @Override + public Q limit(long limit) { + return queryMixin.limit(limit); + } + + /* + * (non-Javadoc) + * @see com.querydsl.core.SimpleQuery#offset() + */ + @Override + public Q offset(long offset) { + return queryMixin.offset(offset); + } + + /* + * (non-Javadoc) + * @see com.querydsl.core.SimpleQuery#restrict(com.querydsl.core.QueryModifiers) + */ + @Override + public Q restrict(QueryModifiers modifiers) { + return queryMixin.restrict(modifiers); + } + + /* + * (non-Javadoc) + * @see com.querydsl.core.SimpleQuery#orderBy(com.querydsl.core.types.OrderSpecifier) + */ + @Override + public Q orderBy(OrderSpecifier... o) { + return queryMixin.orderBy(o); + } + + /* + * (non-Javadoc) + * @see com.querydsl.core.SimpleQuery#set(com.querydsl.core.types.ParamExpression, Object) + */ + @Override + public Q set(ParamExpression param, T value) { + return queryMixin.set(param, value); + } + + /** + * Compute the actual projection {@link Document} from a given projectionExpression by serializing the contained + * {@link Expression expressions} individually. + * + * @param projectionExpression the computed projection {@link Document}. + * @return never {@literal null}. An empty {@link Document} by default. + * @see MongodbDocumentSerializer#handle(Expression) + */ + protected Document createProjection(@Nullable Expression projectionExpression) { + + if (!(projectionExpression instanceof FactoryExpression)) { + return new Document(); + } + + Document projection = new Document(); + ((FactoryExpression) projectionExpression).getArgs().stream() // + .filter(Expression.class::isInstance) // + .map(Expression.class::cast) // + .map(serializer::handle) // + .forEach(it -> projection.append(it.toString(), 1)); + + return projection; + } + + /** + * Compute the filer {@link Document} from the given {@link Predicate}. + * + * @param predicate can be {@literal null}. + * @return an empty {@link Document} if predicate is {@literal null}. + * @see MongodbDocumentSerializer#toQuery(Predicate) + */ + protected Document createQuery(@Nullable Predicate predicate) { + + if (predicate == null) { + return new Document(); + } + + return serializer.toQuery(predicate); + } + + /** + * Compute the sort {@link Document} from the given list of {@link OrderSpecifier order specifiers}. + * + * @param orderSpecifiers can be {@literal null}. + * @return an empty {@link Document} if predicate is {@literal null}. + * @see MongodbDocumentSerializer#toSort(List) + */ + protected Document createSort(List> orderSpecifiers) { + return serializer.toSort(orderSpecifiers); + } + + /** + * Get the actual {@link QueryMixin} delegate. + * + * @return + */ + QueryMixin getQueryMixin() { + return queryMixin; + } + + /** + * Get the where definition as a Document instance + * + * @return + */ + Document asDocument() { + return createQuery(queryMixin.getMetadata().getWhere()); + } + + @Override + public String toString() { + return asDocument().toString(); + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/AnyEmbeddedBuilder.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/QuerydslAnyEmbeddedBuilder.java similarity index 61% rename from spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/AnyEmbeddedBuilder.java rename to spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/QuerydslAnyEmbeddedBuilder.java index e2260fd27..af9380ba2 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/AnyEmbeddedBuilder.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/QuerydslAnyEmbeddedBuilder.java @@ -24,23 +24,40 @@ import com.querydsl.core.types.Predicate; import com.querydsl.mongodb.MongodbOps; /** - * {@code AnyEmbeddedBuilder} is a builder for constraints on embedded objects - * + * {@code QuerydslAnyEmbeddedBuilder} is a builder for constraints on embedded objects. + *

+ * Original implementation source {@link com.querydsl.mongodb.AnyEmbeddedBuilder} by {@literal The Querydsl Team} + * (http://www.querydsl.com/team) licensed under the Apache License, Version + * 2.0. + *

+ * Modified for usage with {@link QuerydslAbstractMongodbQuery}. + * * @param query type + * @author tiwe * @author Mark Paluch + * @author Christoph Strobl + * @since 2.1 */ -class AnyEmbeddedBuilder> { +public class QuerydslAnyEmbeddedBuilder, K> { private final QueryMixin queryMixin; - private final Path> collection; - public AnyEmbeddedBuilder(QueryMixin queryMixin, Path> collection) { + QuerydslAnyEmbeddedBuilder(QueryMixin queryMixin, Path> collection) { + this.queryMixin = queryMixin; this.collection = collection; } + /** + * Add the given where conditions. + * + * @param conditions must not be {@literal null}. + * @return the target {@link QueryMixin}. + * @see QueryMixin#where(Predicate) + */ public Q on(Predicate... conditions) { + return queryMixin .where(ExpressionUtils.predicate(MongodbOps.ELEM_MATCH, collection, ExpressionUtils.allOf(conditions))); } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/FetchableMongodbQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/QuerydslFetchableMongodbQuery.java similarity index 57% rename from spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/FetchableMongodbQuery.java rename to spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/QuerydslFetchableMongodbQuery.java index dc1cbe0a3..e9f1b0986 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/FetchableMongodbQuery.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/QuerydslFetchableMongodbQuery.java @@ -19,14 +19,13 @@ import java.util.Collection; import java.util.Collections; import java.util.List; -import javax.annotation.Nullable; - +import org.springframework.data.mongodb.core.ExecutableFindOperation.FindWithProjection; import org.springframework.data.mongodb.core.MongoOperations; import org.springframework.data.mongodb.core.query.BasicQuery; import org.springframework.data.mongodb.core.query.Query; +import org.springframework.lang.Nullable; +import org.springframework.util.LinkedMultiValueMap; -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; @@ -39,32 +38,26 @@ import com.querydsl.core.types.Operation; import com.querydsl.core.types.OrderSpecifier; import com.querydsl.core.types.Path; import com.querydsl.core.types.Predicate; +import com.querydsl.core.types.dsl.CollectionPathBase; /** - * {@link Fetchable} Mongodb query with a pluggable Document to Bean transformation. + * {@link Fetchable} MongoDB query with utilizing {@link MongoOperations} for command execution. * * @param result type * @param concrete subtype * @author Mark Paluch + * @author Christoph Strobl + * @since 2.1 */ -abstract class FetchableMongodbQuery> extends AbstractMongodbQuery - implements Fetchable { +abstract class QuerydslFetchableMongodbQuery> + extends QuerydslAbstractMongodbQuery implements Fetchable { private final Class entityClass; private final String collection; private final MongoOperations mongoOperations; + private final FindWithProjection find; - public FetchableMongodbQuery(MongodbDocumentSerializer serializer, Class entityClass, - MongoOperations mongoOperations) { - - super(serializer); - - this.entityClass = (Class) entityClass; - this.collection = mongoOperations.getCollectionName(entityClass); - this.mongoOperations = mongoOperations; - } - - public FetchableMongodbQuery(MongodbDocumentSerializer serializer, Class entityClass, String collection, + QuerydslFetchableMongodbQuery(MongodbDocumentSerializer serializer, Class entityClass, String collection, MongoOperations mongoOperations) { super(serializer); @@ -72,19 +65,13 @@ abstract class FetchableMongodbQuery> e this.entityClass = (Class) entityClass; this.collection = collection; this.mongoOperations = mongoOperations; + find = mongoOperations.query(this.entityClass).inCollection(collection); } - /** - * Iterate with the specific fields - * - * @param paths fields to return - * @return iterator + /* + * (non-Javadoc) + * @see com.querydsl.core.Fetchable#iterable() */ - public CloseableIterator iterate(Path... paths) { - getQueryMixin().setProjection(paths); - return iterate(); - } - @Override public CloseableIterator iterate() { @@ -92,6 +79,7 @@ abstract class FetchableMongodbQuery> e entityClass, collection); return new CloseableIterator() { + @Override public boolean hasNext() { return stream.hasNext(); @@ -104,7 +92,7 @@ abstract class FetchableMongodbQuery> e @Override public void remove() { - + throw new UnsupportedOperationException("Cannot remove from iterator while streaming data."); } @Override @@ -114,91 +102,99 @@ abstract class FetchableMongodbQuery> e }; } - /** - * Fetch with the specific fields - * - * @param paths fields to return - * @return results + /* + * (non-Javadoc) + * @see com.querydsl.core.Fetchable#fetch() */ - public List fetch(Path... paths) { - getQueryMixin().setProjection(paths); - return fetch(); - } - @Override public List fetch() { - return mongoOperations.query(entityClass).matching(createQuery()).all(); + return find.matching(createQuery()).all(); } - /** - * Fetch first with the specific fields - * - * @param paths fields to return - * @return first result + /* + * (non-Javadoc) + * @see com.querydsl.core.Fetchable#fetchFirst() */ - public K fetchFirst(Path... paths) { - getQueryMixin().setProjection(paths); - return fetchFirst(); - } - @Override public K fetchFirst() { - return mongoOperations.query(entityClass).matching(createQuery()).firstValue(); + return find.matching(createQuery()).firstValue(); } - /** - * Fetch one with the specific fields - * - * @param paths fields to return - * @return first result + /* + * (non-Javadoc) + * @see com.querydsl.core.Fetchable#fetchOne() */ - public K fetchOne(Path... paths) { - getQueryMixin().setProjection(paths); - return fetchOne(); - } - @Override public K fetchOne() { - return mongoOperations.query(entityClass).matching(createQuery()).oneValue(); + return find.matching(createQuery()).oneValue(); + } + /* + * (non-Javadoc) + * @see com.querydsl.core.Fetchable#fetchResults() + */ + @Override + public QueryResults fetchResults() { + + long total = fetchCount(); + return total > 0L ? new QueryResults<>(fetch(), getQueryMixin().getMetadata().getModifiers(), total) + : QueryResults.emptyResults(); + } + + /* + * (non-Javadoc) + * @see com.querydsl.core.Fetchable#fetchCount() + */ + @Override + public long fetchCount() { + return find.matching(createQuery()).count(); } /** - * Fetch results with the specific fields + * Define a join. * - * @param paths fields to return - * @return results + * @param ref reference + * @param target join target + * @return new instance of {@link QuerydslJoinBuilder}. */ - public QueryResults fetchResults(Path... paths) { - getQueryMixin().setProjection(paths); - return fetchResults(); + public QuerydslJoinBuilder join(Path ref, Path target) { + return new QuerydslJoinBuilder<>(getQueryMixin(), ref, target); } - @Override - public QueryResults fetchResults() { - long total = fetchCount(); - if (total > 0L) { - return new QueryResults<>(fetch(), getQueryMixin().getMetadata().getModifiers(), total); - } else { - return QueryResults.emptyResults(); - } + /** + * Define a join. + * + * @param ref reference + * @param target join target + * @return new instance of {@link QuerydslJoinBuilder}. + */ + public QuerydslJoinBuilder join(CollectionPathBase ref, Path target) { + return new QuerydslJoinBuilder<>(getQueryMixin(), ref, target); } - @Override - public long fetchCount() { - return mongoOperations.query(entityClass).matching(createQuery()).count(); + /** + * Define a constraint for an embedded object. + * + * @param collection collection must not be {@literal null}. + * @param target target must not be {@literal null}. + * @return new instance of {@link QuerydslAnyEmbeddedBuilder}. + */ + public QuerydslAnyEmbeddedBuilder anyEmbedded(Path> collection, Path target) { + return new QuerydslAnyEmbeddedBuilder<>(getQueryMixin(), collection); } 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()); + + return createQuery(createFilter(metadata), metadata.getProjection(), metadata.getModifiers(), + metadata.getOrderBy()); } - protected org.springframework.data.mongodb.core.query.Query createQuery(@Nullable Predicate where, - Expression projection, QueryModifiers modifiers, List> orderBy) { + protected org.springframework.data.mongodb.core.query.Query createQuery(@Nullable Predicate filter, + @Nullable Expression projection, QueryModifiers modifiers, List> orderBy) { - BasicQuery basicQuery = new BasicQuery(createQuery(where), createProjection(projection)); + BasicQuery basicQuery = new BasicQuery(createQuery(filter), createProjection(projection)); Integer limit = modifiers.getLimitAsInteger(); Integer offset = modifiers.getOffsetAsInteger(); @@ -210,13 +206,15 @@ abstract class FetchableMongodbQuery> e basicQuery.skip(offset); } if (orderBy.size() > 0) { - basicQuery.setSortObject(getSerializer().toSort(orderBy)); + basicQuery.setSortObject(createSort(orderBy)); } + return basicQuery; } @Nullable protected Predicate createFilter(QueryMetadata metadata) { + Predicate filter; if (!metadata.getJoins().isEmpty()) { filter = ExpressionUtils.allOf(metadata.getWhere(), createJoinFilter(metadata)); @@ -229,21 +227,28 @@ abstract class FetchableMongodbQuery> e @SuppressWarnings("unchecked") @Nullable protected Predicate createJoinFilter(QueryMetadata metadata) { - Multimap, Predicate> predicates = HashMultimap.create(); + + LinkedMultiValueMap, Predicate> predicates = new LinkedMultiValueMap<>(); List 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 extraFilters = predicates.get(target.getRoot()); Predicate filter = ExpressionUtils.allOf(join.getCondition(), allOf(extraFilters)); + List ids = getIds(target.getType(), filter); + if (ids.isEmpty()) { - throw new NoResults(); + return ExpressionUtils.predicate(QuerydslMongoOps.NO_MATCH, source); } + Path path = ExpressionUtils.path(String.class, source, "$id"); - predicates.put(source.getRoot(), ExpressionUtils.in((Path) path, ids)); + predicates.add(source.getRoot(), ExpressionUtils.in((Path) path, ids)); } + Path source = (Path) ((Operation) joins.get(0).getTarget()).getArg(0); return allOf(predicates.get(source.getRoot())); } @@ -252,10 +257,16 @@ abstract class FetchableMongodbQuery> e return predicates != null ? ExpressionUtils.allOf(predicates) : null; } + /** + * Fetch the list of ids matching a given condition. + * + * @param targetType must not be {@literal null}. + * @param condition must not be {@literal null}. + * @return empty {@link List} if none found. + */ protected List getIds(Class targetType, Predicate condition) { - // TODO : fetch only ids - Query query = createQuery(condition, null, QueryModifiers.EMPTY, Collections.emptyList()); + Query query = createQuery(condition, null, QueryModifiers.EMPTY, Collections.emptyList()); return mongoOperations.findDistinct(query, "_id", targetType, Object.class); } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/JoinBuilder.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/QuerydslJoinBuilder.java similarity index 63% rename from spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/JoinBuilder.java rename to spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/QuerydslJoinBuilder.java index 4459ab1ed..ac4b4bf6e 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/JoinBuilder.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/QuerydslJoinBuilder.java @@ -22,28 +22,44 @@ import com.querydsl.core.types.Path; import com.querydsl.core.types.Predicate; /** - * {@code JoinBuilder} is a builder for join constraints + * {@code QuerydslJoinBuilder} is a builder for join constraints. + *

+ * Original implementation source {@link com.querydsl.mongodb.JoinBuilder} by {@literal The Querydsl Team} + * (http://www.querydsl.com/team) licensed under the Apache License, Version + * 2.0. + *

+ * Modified for usage with {@link QuerydslAbstractMongodbQuery}. * - * @author Mark Paluch * @param * @param + * @author tiwe + * @author Mark Paluch + * @author Christoph Strobl + * @since 2.1 */ -class JoinBuilder, T> { +public class QuerydslJoinBuilder, K, T> { private final QueryMixin queryMixin; - private final Path ref; - private final Path target; - public JoinBuilder(QueryMixin queryMixin, Path ref, Path target) { + QuerydslJoinBuilder(QueryMixin queryMixin, Path ref, Path target) { + this.queryMixin = queryMixin; this.ref = ref; this.target = target; } + /** + * Add the given join conditions. + * + * @param conditions must not be {@literal null}. + * @return the target {@link QueryMixin}. + * @see QueryMixin#on(Predicate) + */ @SuppressWarnings("unchecked") public Q on(Predicate... conditions) { + queryMixin.addJoin(JoinType.JOIN, ExpressionUtils.as((Path) ref, target)); queryMixin.on(conditions); return queryMixin.getSelf(); diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/SimpleFetchableQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/QuerydslMongoOps.java similarity index 61% rename from spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/SimpleFetchableQuery.java rename to spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/QuerydslMongoOps.java index 1edb489b2..cae326188 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/SimpleFetchableQuery.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/QuerydslMongoOps.java @@ -15,13 +15,29 @@ */ package org.springframework.data.mongodb.repository.support; -import com.querydsl.core.Fetchable; -import com.querydsl.core.SimpleQuery; +import com.querydsl.core.types.Operator; /** - * Interface that combines {@link Fetchable} and {@link SimpleQuery}. + * Spring Data specific {@link Operator operators} for usage with Querydsl and MongoDB. * - * @author Mark Paluch + * @author Christoph Strobl * @since 2.1 */ -public interface SimpleFetchableQuery extends Fetchable, SimpleQuery> {} +enum QuerydslMongoOps implements Operator { + + /** + * {@link Operator} always evaluating to {@literal false}. + */ + NO_MATCH(Boolean.class); + + private final Class type; + + QuerydslMongoOps(Class type) { + this.type = type; + } + + @Override + public Class getType() { + return type; + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/QuerydslMongoPredicateExecutor.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/QuerydslMongoPredicateExecutor.java index dba3c34c5..0ec332850 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/QuerydslMongoPredicateExecutor.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/QuerydslMongoPredicateExecutor.java @@ -165,7 +165,7 @@ public class QuerydslMongoPredicateExecutor implements QuerydslPredicateExecu Assert.notNull(predicate, "Predicate must not be null!"); Assert.notNull(pageable, "Pageable must not be null!"); - SimpleFetchableQuery query = createQueryFor(predicate); + SpringDataMongodbQuery query = createQueryFor(predicate); return PageableExecutionUtils.getPage(applyPagination(query, pageable).fetch(), pageable, query::fetchCount); } @@ -195,45 +195,45 @@ public class QuerydslMongoPredicateExecutor implements QuerydslPredicateExecu } /** - * Creates a {@link AbstractMongodbQuery} for the given {@link Predicate}. + * Creates a {@link SpringDataMongodbQuery} for the given {@link Predicate}. * * @param predicate * @return */ - private SimpleFetchableQuery createQueryFor(Predicate predicate) { + private SpringDataMongodbQuery createQueryFor(Predicate predicate) { return createQuery().where(predicate); } /** - * Creates a {@link AbstractMongodbQuery}. + * Creates a {@link SpringDataMongodbQuery}. * * @return */ - private SimpleFetchableQuery createQuery() { + private SpringDataMongodbQuery createQuery() { return new SpringDataMongodbQuery<>(mongoOperations, entityInformation.getJavaType()); } /** - * Applies the given {@link Pageable} to the given {@link MongodbQuery}. + * Applies the given {@link Pageable} to the given {@link SpringDataMongodbQuery}. * * @param query * @param pageable * @return */ - private SimpleFetchableQuery applyPagination(SimpleFetchableQuery query, Pageable pageable) { + private SpringDataMongodbQuery applyPagination(SpringDataMongodbQuery query, Pageable pageable) { query = query.offset(pageable.getOffset()).limit(pageable.getPageSize()); return applySorting(query, pageable.getSort()); } /** - * Applies the given {@link Sort} to the given {@link MongodbQuery}. + * Applies the given {@link Sort} to the given {@link SpringDataMongodbQuery}. * * @param query * @param sort * @return */ - private SimpleFetchableQuery applySorting(SimpleFetchableQuery query, Sort sort) { + private SpringDataMongodbQuery applySorting(SpringDataMongodbQuery query, Sort sort) { // TODO: find better solution than instanceof check if (sort instanceof QSort) { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/QuerydslRepositorySupport.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/QuerydslRepositorySupport.java index 30ac168a9..5b89e85a1 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/QuerydslRepositorySupport.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/QuerydslRepositorySupport.java @@ -47,7 +47,7 @@ public abstract class QuerydslRepositorySupport { } /** - * Returns a {@link MongodbQuery} for the given {@link EntityPath}. The collection being queried is derived from the + * Returns a {@link SpringDataMongodbQuery} for the given {@link EntityPath}. The collection being queried is derived from the * entity metadata. * * @param path @@ -61,7 +61,7 @@ public abstract class QuerydslRepositorySupport { } /** - * Returns a {@link MongodbQuery} for the given {@link EntityPath} querying the given collection. + * Returns a {@link SpringDataMongodbQuery} for the given {@link EntityPath} querying the given collection. * * @param path must not be {@literal null} * @param collection must not be blank or {@literal null} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/SpringDataMongodbQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/SpringDataMongodbQuery.java index 296d0f5d1..a5662147a 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/SpringDataMongodbQuery.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/SpringDataMongodbQuery.java @@ -15,28 +15,17 @@ */ package org.springframework.data.mongodb.repository.support; -import java.util.List; - import org.springframework.data.mongodb.core.MongoOperations; -import com.mysema.commons.lang.CloseableIterator; -import com.querydsl.core.NonUniqueResultException; -import com.querydsl.core.QueryModifiers; -import com.querydsl.core.QueryResults; -import com.querydsl.core.types.OrderSpecifier; -import com.querydsl.core.types.ParamExpression; -import com.querydsl.core.types.Predicate; -import com.querydsl.mongodb.AbstractMongodbQuery; - /** - * Spring Data specific {@link AbstractMongodbQuery} implementation. + * Spring Data specific simple {@link com.querydsl.core.Fetchable} {@link com.querydsl.core.SimpleQuery Query} + * implementation. * * @author Oliver Gierke * @author Mark Paluch + * @author Christoph Strobl */ -public class SpringDataMongodbQuery implements SimpleFetchableQuery { - - private final OperationsMongodbQuery query; +public class SpringDataMongodbQuery extends QuerydslFetchableMongodbQuery> { /** * Creates a new {@link SpringDataMongodbQuery}. @@ -58,144 +47,6 @@ public class SpringDataMongodbQuery implements SimpleFetchableQuery { public SpringDataMongodbQuery(final MongoOperations operations, final Class type, String collectionName) { - query = new OperationsMongodbQuery<>(new SpringDataMongodbSerializer(operations.getConverter()), type, - collectionName, operations); - } - - /* - * (non-Javadoc) - * @see com.querydsl.core.Fetchable#fetch() - */ - @Override - public List fetch() { - return query.fetch(); - } - - /* - * (non-Javadoc) - * @see com.querydsl.core.Fetchable#fetchFirst() - */ - @Override - public T fetchFirst() { - return query.fetchFirst(); - } - - /* - * (non-Javadoc) - * @see com.querydsl.core.Fetchable#fetchOne() - */ - @Override - public T fetchOne() throws NonUniqueResultException { - return query.fetchOne(); - } - - /* - * (non-Javadoc) - * @see com.querydsl.core.Fetchable#iterate() - */ - @Override - public CloseableIterator iterate() { - return query.iterate(); - } - - /* - * (non-Javadoc) - * @see com.querydsl.core.Fetchable#fetchResults() - */ - @Override - public QueryResults fetchResults() { - return query.fetchResults(); - } - - /* - * (non-Javadoc) - * @see com.querydsl.core.Fetchable#fetchCount() - */ - @Override - public long fetchCount() { - return query.fetchCount(); - } - - /* - * (non-Javadoc) - * @see com.querydsl.core.SimpleQuery#limit(long) - */ - @Override - public SpringDataMongodbQuery limit(long limit) { - query.limit(limit); - return this; - } - - /* - * (non-Javadoc) - * @see com.querydsl.core.SimpleQuery#offset(long) - */ - @Override - public SpringDataMongodbQuery offset(long offset) { - query.offset(offset); - return this; - } - - /* - * (non-Javadoc) - * @see com.querydsl.core.SimpleQuery#restrict(com.querydsl.core.QueryModifiers) - */ - @Override - public SpringDataMongodbQuery restrict(QueryModifiers modifiers) { - query.restrict(modifiers); - return this; - } - - /* - * (non-Javadoc) - * @see com.querydsl.core.SimpleQuery#orderBy(com.querydsl.core.types.OrderSpecifier[]) - */ - @Override - public SpringDataMongodbQuery orderBy(OrderSpecifier... o) { - query.orderBy(o); - return this; - } - - /* - * (non-Javadoc) - * @see com.querydsl.core.SimpleQuery#set(com.querydsl.core.types.ParamExpression, java.lang.Object) - */ - @Override - public SpringDataMongodbQuery set(ParamExpression param, V value) { - query.set(param, value); - return this; - } - - /* - * (non-Javadoc) - * @see com.querydsl.core.SimpleQuery#distinct() - */ - @Override - public SpringDataMongodbQuery distinct() { - query.distinct(); - return this; - } - - /* - * (non-Javadoc) - * @see com.querydsl.core.FilteredClause#where(com.querydsl.core.types.Predicate[]) - */ - @Override - public SpringDataMongodbQuery where(Predicate... o) { - query.where(o); - return this; - } - - /** - * Concrete implementation of {@link FetchableMongodbQuery}. - * - * @param - */ - static class OperationsMongodbQuery extends FetchableMongodbQuery> { - - public OperationsMongodbQuery(MongodbDocumentSerializer serializer, Class entityClass, - String collection, MongoOperations mongoOperations) { - super(serializer, entityClass, collection, mongoOperations); - } + super(new SpringDataMongodbSerializer(operations.getConverter()), type, collectionName, operations); } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/SpringDataMongodbSerializer.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/SpringDataMongodbSerializer.java index f0f2316db..8a2e8f4f4 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/SpringDataMongodbSerializer.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/SpringDataMongodbSerializer.java @@ -54,7 +54,7 @@ class SpringDataMongodbSerializer extends MongodbDocumentSerializer { static { - Set pathTypes = new HashSet(); + Set pathTypes = new HashSet<>(); pathTypes.add(PathType.VARIABLE); pathTypes.add(PathType.PROPERTY); @@ -66,9 +66,9 @@ class SpringDataMongodbSerializer extends MongodbDocumentSerializer { private final QueryMapper mapper; /** - * Creates a new {@link SpringDataMongodbSerializer} for the given {@link MappingContext}. + * Creates a new {@link SpringDataMongodbSerializer} for the given {@link MongoConverter}. * - * @param mappingContext must not be {@literal null}. + * @param converter must not be {@literal null}. */ public SpringDataMongodbSerializer(MongoConverter converter) { @@ -113,7 +113,7 @@ class SpringDataMongodbSerializer extends MongodbDocumentSerializer { /* * (non-Javadoc) - * @see com.querydsl.mongodb.MongodbSerializer#asDocument(java.lang.String, java.lang.Object) + * @see org.springframework.data.mongodb.repository.support.MongodbSerializer#asDocument(java.lang.String, java.lang.Object) */ @Override protected Document asDocument(@Nullable String key, @Nullable Object value) { @@ -139,8 +139,7 @@ class SpringDataMongodbSerializer extends MongodbDocumentSerializer { Object convertedId = mapper.convertId(idValue); - return mapper.getMappedObject(super.asDocument(key, convertedId), - Optional.empty()); + return mapper.getMappedObject(super.asDocument(key, convertedId), Optional.empty()); } /* @@ -208,6 +207,7 @@ class SpringDataMongodbSerializer extends MongodbDocumentSerializer { : asReference(constant.getConstant(), path); } + @Nullable private MongoPersistentProperty getPropertyFor(Path path) { Path parent = path.getMetadata().getParent(); @@ -217,7 +217,7 @@ class SpringDataMongodbSerializer extends MongodbDocumentSerializer { } MongoPersistentEntity entity = mappingContext.getPersistentEntity(parent.getType()); - return entity != null ? entity.getRequiredPersistentProperty(path.getMetadata().getName()) : null; + return entity != null ? entity.getPersistentProperty(path.getMetadata().getName()) : null; } /** diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/event/ApplicationContextEventTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/event/ApplicationContextEventTests.java index dcca0927a..dbd64e76f 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/event/ApplicationContextEventTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/event/ApplicationContextEventTests.java @@ -40,6 +40,11 @@ import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.aggregation.Aggregation; import org.springframework.data.mongodb.core.mapping.DBRef; import org.springframework.data.mongodb.core.mapping.PersonPojoStringId; +import org.springframework.data.mongodb.repository.Person; +import org.springframework.data.mongodb.repository.QPerson; +import org.springframework.data.mongodb.repository.query.MongoEntityInformation; +import org.springframework.data.mongodb.repository.support.MongoRepositoryFactory; +import org.springframework.data.mongodb.repository.support.QuerydslMongoPredicateExecutor; import com.mongodb.MongoClient; import com.mongodb.WriteConcern; @@ -386,6 +391,29 @@ public class ApplicationContextEventTests { assertThat(listener.onAfterConvertEvents.get(0).getCollectionName()).isEqualTo(COLLECTION_NAME); } + @Test // DATAMONGO-700, DATAMONGO-1185, DATAMONGO-1848 + public void publishesEventsForQuerydslFindQueries() { + + template.dropCollection(Person.class); + + template.save(new Person("Boba", "Fett", 40)); + + MongoRepositoryFactory factory = new MongoRepositoryFactory(template); + MongoEntityInformation entityInformation = factory.getEntityInformation(Person.class); + QuerydslMongoPredicateExecutor executor = new QuerydslMongoPredicateExecutor<>(entityInformation, template); + + executor.findOne(QPerson.person.lastname.startsWith("Fe")); + + assertThat(listener.onAfterLoadEvents).hasSize(1); + assertThat(listener.onAfterLoadEvents.get(0).getCollectionName()).isEqualTo("person"); + + assertThat(listener.onBeforeConvertEvents).hasSize(1); + assertThat(listener.onBeforeConvertEvents.get(0).getCollectionName()).isEqualTo("person"); + + assertThat(listener.onAfterConvertEvents).hasSize(1); + assertThat(listener.onAfterConvertEvents.get(0).getCollectionName()).isEqualTo("person"); + } + private void comparePersonAndDocument(PersonPojoStringId p, PersonPojoStringId p2, org.bson.Document document) { assertThat(p2.getId()).isEqualTo(p.getId()); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/QuerydslMongoPredicateExecutorIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/QuerydslMongoPredicateExecutorIntegrationTests.java index 6dbede532..36dacb0ed 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/QuerydslMongoPredicateExecutorIntegrationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/QuerydslMongoPredicateExecutorIntegrationTests.java @@ -18,6 +18,7 @@ package org.springframework.data.mongodb.repository.support; import static org.assertj.core.api.Assertions.*; import java.util.Arrays; +import java.util.LinkedHashSet; import java.util.List; import org.junit.Before; @@ -25,15 +26,25 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.IncorrectResultSizeDataAccessException; +import org.springframework.dao.PermissionDeniedDataAccessException; import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort.Direction; +import org.springframework.data.mongodb.MongoDbFactory; import org.springframework.data.mongodb.core.MongoOperations; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.repository.Address; import org.springframework.data.mongodb.repository.Person; +import org.springframework.data.mongodb.repository.QAddress; import org.springframework.data.mongodb.repository.QPerson; +import org.springframework.data.mongodb.repository.QUser; +import org.springframework.data.mongodb.repository.User; import org.springframework.data.mongodb.repository.query.MongoEntityInformation; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import com.mongodb.MongoException; +import com.mongodb.client.MongoDatabase; + /** * Integration test for {@link QuerydslMongoPredicateExecutor}. * @@ -47,6 +58,8 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; public class QuerydslMongoPredicateExecutorIntegrationTests { @Autowired MongoOperations operations; + @Autowired MongoDbFactory dbFactory; + QuerydslMongoPredicateExecutor repository; Person dave, oliver, carter; @@ -57,7 +70,7 @@ public class QuerydslMongoPredicateExecutorIntegrationTests { MongoRepositoryFactory factory = new MongoRepositoryFactory(operations); MongoEntityInformation entityInformation = factory.getEntityInformation(Person.class); - repository = new QuerydslMongoPredicateExecutor(entityInformation, operations); + repository = new QuerydslMongoPredicateExecutor<>(entityInformation, operations); operations.dropCollection(Person.class); @@ -99,4 +112,114 @@ public class QuerydslMongoPredicateExecutorIntegrationTests { public void findOneWithPredicateThrowsExceptionForNonUniqueResults() { repository.findOne(person.firstname.contains("e")); } + + @Test // DATAMONGO-1848 + public void findUsingAndShouldWork() { + + assertThat(repository.findAll( + person.lastname.startsWith(oliver.getLastname()).and(person.firstname.startsWith(dave.getFirstname())))) + .containsExactly(dave); + } + + @Test // DATAMONGO-362, DATAMONGO-1848 + public void springDataMongodbQueryShouldAllowJoinOnDBref() { + + User user1 = new User(); + user1.setUsername("user-1"); + + User user2 = new User(); + user2.setUsername("user-2"); + + User user3 = new User(); + user3.setUsername("user-3"); + + operations.save(user1); + operations.save(user2); + operations.save(user3); + + Person person1 = new Person("Max", "The Mighty"); + person1.setCoworker(user1); + + Person person2 = new Person("Jack", "The Ripper"); + person2.setCoworker(user2); + + Person person3 = new Person("Bob", "The Builder"); + person3.setCoworker(user3); + + operations.save(person1); + operations.save(person2); + operations.save(person3); + + List result = new SpringDataMongodbQuery<>(operations, Person.class).where() + .join(person.coworker, QUser.user).on(QUser.user.username.eq("user-2")).fetch(); + + assertThat(result).containsExactly(person2); + } + + @Test // DATAMONGO-362, DATAMONGO-1848 + public void springDataMongodbQueryShouldReturnEmptyOnJoinWithNoResults() { + + User user1 = new User(); + user1.setUsername("user-1"); + + User user2 = new User(); + user2.setUsername("user-2"); + + operations.save(user1); + operations.save(user2); + + Person person1 = new Person("Max", "The Mighty"); + person1.setCoworker(user1); + + Person person2 = new Person("Jack", "The Ripper"); + person2.setCoworker(user2); + + operations.save(person1); + operations.save(person2); + + List result = new SpringDataMongodbQuery<>(operations, Person.class).where() + .join(person.coworker, QUser.user).on(QUser.user.username.eq("does-not-exist")).fetch(); + + assertThat(result).isEmpty(); + } + + @Test // DATAMONGO-595, DATAMONGO-1848 + public void springDataMongodbQueryShouldAllowElemMatchOnArrays() { + + Address adr1 = new Address("Hauptplatz", "4020", "Linz"); + Address adr2 = new Address("Stephansplatz", "1010", "Wien"); + Address adr3 = new Address("Tower of London", "EC3N 4AB", "London"); + + Person person1 = new Person("Max", "The Mighty"); + person1.setShippingAddresses(new LinkedHashSet<>(Arrays.asList(adr1, adr2))); + + Person person2 = new Person("Jack", "The Ripper"); + person2.setShippingAddresses(new LinkedHashSet<>(Arrays.asList(adr2, adr3))); + + operations.save(person1); + operations.save(person2); + + List result = new SpringDataMongodbQuery<>(operations, Person.class).where() + .anyEmbedded(person.shippingAddresses, QAddress.address).on(QAddress.address.city.eq("London")).fetch(); + + assertThat(result).containsExactly(person2); + } + + @Test(expected = PermissionDeniedDataAccessException.class) // DATAMONGO-1434, DATAMONGO-1848 + public void translatesExceptionsCorrectly() { + + MongoOperations ops = new MongoTemplate(dbFactory) { + + @Override + protected MongoDatabase doGetDatabase() { + throw new MongoException(18, "Authentication Failed"); + } + }; + + MongoRepositoryFactory factory = new MongoRepositoryFactory(ops); + MongoEntityInformation entityInformation = factory.getEntityInformation(Person.class); + repository = new QuerydslMongoPredicateExecutor<>(entityInformation, ops); + + repository.findOne(person.firstname.contains("batman")); + } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/QuerydslRepositorySupportTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/QuerydslRepositorySupportTests.java index acaa18845..e8fd4b7c4 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/QuerydslRepositorySupportTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/QuerydslRepositorySupportTests.java @@ -26,6 +26,7 @@ import org.bson.types.ObjectId; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.springframework.beans.DirectFieldAccessor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.MongoOperations; @@ -139,6 +140,79 @@ public class QuerydslRepositorySupportTests { assertThat(query.fetchOne(), equalTo(outer)); } + @Test // DATAMONGO-1810, DATAMONGO-1848 + public void shouldFetchObjectsViaStringWhenUsingInOnDbRef() { + + User bart = new User(); + DirectFieldAccessor dfa = new DirectFieldAccessor(bart); + dfa.setPropertyValue("id", "bart"); + + bart.setUsername("bart@simpson.com"); + operations.save(bart); + + User lisa = new User(); + dfa = new DirectFieldAccessor(lisa); + dfa.setPropertyValue("id", "lisa"); + + lisa.setUsername("lisa@simposon.com"); + operations.save(lisa); + + person.setCoworker(bart); + operations.save(person); + + QPerson p = QPerson.person; + + SpringDataMongodbQuery queryUsingIdFieldWithinInClause = repoSupport.from(p) + .where(p.coworker.id.in(Arrays.asList(bart.getId(), lisa.getId()))); + + SpringDataMongodbQuery queryUsingRefObject = repoSupport.from(p).where(p.coworker.eq(bart)); + + assertThat(queryUsingIdFieldWithinInClause.fetchOne(), equalTo(person)); + assertThat(queryUsingIdFieldWithinInClause.fetchOne(), equalTo(queryUsingRefObject.fetchOne())); + } + + @Test // DATAMONGO-1810, DATAMONGO-1848 + public void shouldFetchObjectsViaStringStoredAsObjectIdWhenUsingInOnDbRef() { + + User bart = new User(); + bart.setUsername("bart@simpson.com"); + operations.save(bart); + + User lisa = new User(); + lisa.setUsername("lisa@simposon.com"); + operations.save(lisa); + + person.setCoworker(bart); + operations.save(person); + + QPerson p = QPerson.person; + + SpringDataMongodbQuery queryUsingIdFieldWithinInClause = repoSupport.from(p) + .where(p.coworker.id.in(Arrays.asList(bart.getId(), lisa.getId()))); + + SpringDataMongodbQuery queryUsingRefObject = repoSupport.from(p).where(p.coworker.eq(bart)); + + assertThat(queryUsingIdFieldWithinInClause.fetchOne(), equalTo(person)); + assertThat(queryUsingIdFieldWithinInClause.fetchOne(), equalTo(queryUsingRefObject.fetchOne())); + } + + @Test // DATAMONGO-1848, DATAMONGO-2010 + public void shouldConvertStringIdThatIsAValidObjectIdWhenUsedInInPredicateIntoTheSuch() { + + Outer outer = new Outer(); + outer.id = new ObjectId().toHexString(); + outer.inner = new Inner(); + outer.inner.id = new ObjectId().toHexString(); + outer.inner.value = "eat sleep workout repeat"; + + operations.save(outer); + + QQuerydslRepositorySupportTests_Outer o = QQuerydslRepositorySupportTests_Outer.outer; + SpringDataMongodbQuery query = repoSupport.from(o).where(o.inner.id.in(outer.inner.id, outer.inner.id)); + + assertThat(query.fetchOne(), equalTo(outer)); + } + @Data @Document public static class Outer { @@ -152,6 +226,5 @@ public class QuerydslRepositorySupportTests { @Id String id; String value; - } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/SpringDataMongodbSerializerUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/SpringDataMongodbSerializerUnitTests.java index 0af433800..14f51c091 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/SpringDataMongodbSerializerUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/SpringDataMongodbSerializerUnitTests.java @@ -27,7 +27,6 @@ import org.bson.Document; import org.bson.types.ObjectId; import org.hamcrest.collection.IsIterableContainingInOrder; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -182,16 +181,15 @@ public class SpringDataMongodbSerializerUnitTests { assertThat(((Document) mappedPredicate).get("sex"), is("f")); } - @Test // DATAMONGO-1943 - @Ignore("FIXME mp911de") + @Test // DATAMONGO-1848, DATAMONGO-1943 public void shouldRemarshallListsAndDocuments() { - BooleanExpression criteria = QPerson.person.firstname.isNotEmpty() + BooleanExpression criteria = QPerson.person.lastname.isNotEmpty() .and(QPerson.person.firstname.containsIgnoreCase("foo")).not(); assertThat(this.serializer.handle(criteria), - is(equalTo(Document.parse("{ \"$or\" : [ { \"firstname\" : { \"$not\" : { " - + "\"$ne\" : \"\"}}} , { \"firstname\" : { \"$not\" : { \"$regex\" : \".*\\\\Qfoo\\\\E.*\" , \"$options\" : \"i\"}}}]}")))); + is(equalTo(Document.parse("{ \"$or\" : [ { \"lastname\" : { \"$not\" : { " + + "\"$ne\" : \"\"}}} , { \"firstname\" : { \"$not\" : { \"$regex\" : \".*\\\\Qfoo\\\\E.*\" , \"$options\" : \"i\"}}}]}")))); } class Address {