From 82c91cbb7190878bbe6ab95bcc5b7879a2aa75f3 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Mon, 23 Jul 2018 16:28:51 +0200 Subject: [PATCH] DATAMONGO-2030 - Reinstantiate existsBy queries for reactive repositories. We now support existsBy queries for reactive repositories to align with blocking repository support. ExistsBy support got lost during merging and is now back in place. Extract boolean flag counting into BooleanUtil. --- .../query/AbstractReactiveMongoQuery.java | 11 ++++- .../mongodb/repository/query/BooleanUtil.java | 48 +++++++++++++++++++ .../query/ReactivePartTreeMongoQuery.java | 9 ++++ .../query/ReactiveStringBasedMongoQuery.java | 37 ++++++++++++-- .../query/StringBasedMongoQuery.java | 20 ++------ .../ReactiveMongoRepositoryTests.java | 7 +++ ...eactiveStringBasedMongoQueryUnitTests.java | 11 +++++ 7 files changed, 120 insertions(+), 23 deletions(-) create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/BooleanUtil.java diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractReactiveMongoQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractReactiveMongoQuery.java index 89cf6d160..3d633e435 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractReactiveMongoQuery.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractReactiveMongoQuery.java @@ -35,7 +35,6 @@ import org.springframework.data.mongodb.repository.query.ReactiveMongoQueryExecu import org.springframework.data.repository.query.ParameterAccessor; import org.springframework.data.repository.query.RepositoryQuery; import org.springframework.data.repository.query.ResultProcessor; -import org.springframework.data.repository.query.ReturnedType; import org.springframework.util.Assert; /** @@ -150,6 +149,8 @@ public abstract class AbstractReactiveMongoQuery implements RepositoryQuery { return (q, t, c) -> operation.matching(q.with(accessor.getPageable())).all(); } else if (isCountQuery()) { return (q, t, c) -> operation.matching(q).count(); + } else if (isExistsQuery()) { + return (q, t, c) -> operation.matching(q).exists(); } else { return (q, t, c) -> { @@ -204,6 +205,14 @@ public abstract class AbstractReactiveMongoQuery implements RepositoryQuery { */ protected abstract boolean isCountQuery(); + /** + * Returns whether the query should get an exists projection applied. + * + * @return + * @since 2.0.9 + */ + protected abstract boolean isExistsQuery(); + /** * Return weather the query should delete matching documents. * diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/BooleanUtil.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/BooleanUtil.java new file mode 100644 index 000000000..201de07dc --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/BooleanUtil.java @@ -0,0 +1,48 @@ +/* + * 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.query; + +import lombok.experimental.UtilityClass; + +/** + * Utility class containing methods to interact with boolean values. + * + * @author Mark Paluch + * @since 2.0.9 + */ +@UtilityClass +class BooleanUtil { + + /** + * Count the number of {@literal true} values. + * + * @param values + * @return the number of values that are {@literal true}. + */ + static int countBooleanTrueValues(boolean... values) { + + int count = 0; + + for (boolean value : values) { + + if (value) { + count++; + } + } + + return count; + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ReactivePartTreeMongoQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ReactivePartTreeMongoQuery.java index 20823d577..368c9a655 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ReactivePartTreeMongoQuery.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ReactivePartTreeMongoQuery.java @@ -134,6 +134,15 @@ public class ReactivePartTreeMongoQuery extends AbstractReactiveMongoQuery { return tree.isCountProjection(); } + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.repository.query.AbstractReactiveMongoQuery#isExistsQuery() + */ + @Override + protected boolean isExistsQuery() { + return tree.isExistsProjection(); + } + /* * (non-Javadoc) * @see org.springframework.data.mongodb.repository.query.AbstractReactiveMongoQuery#isDeleteQuery() diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ReactiveStringBasedMongoQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ReactiveStringBasedMongoQuery.java index 8bdffe30b..703794cc5 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ReactiveStringBasedMongoQuery.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ReactiveStringBasedMongoQuery.java @@ -40,13 +40,14 @@ import org.springframework.util.Assert; */ public class ReactiveStringBasedMongoQuery extends AbstractReactiveMongoQuery { - private static final String COUND_AND_DELETE = "Manually defined query for %s cannot be both a count and delete query at the same time!"; + private static final String COUNT_EXISTS_AND_DELETE = "Manually defined query for %s cannot be a count and exists or delete query at the same time!"; private static final Logger LOG = LoggerFactory.getLogger(ReactiveStringBasedMongoQuery.class); private static final ParameterBindingParser BINDING_PARSER = ParameterBindingParser.INSTANCE; private final String query; private final String fieldSpec; private final boolean isCountQuery; + private final boolean isExistsQuery; private final boolean isDeleteQuery; private final List queryParameterBindings; private final List fieldSpecParameterBindings; @@ -92,11 +93,23 @@ public class ReactiveStringBasedMongoQuery extends AbstractReactiveMongoQuery { this.fieldSpec = BINDING_PARSER.parseAndCollectParameterBindingsFromQueryIntoBindings( method.getFieldSpecification(), this.fieldSpecParameterBindings); - this.isCountQuery = method.hasAnnotatedQuery() ? method.getQueryAnnotation().count() : false; - this.isDeleteQuery = method.hasAnnotatedQuery() ? method.getQueryAnnotation().delete() : false; + if (method.hasAnnotatedQuery()) { - if (isCountQuery && isDeleteQuery) { - throw new IllegalArgumentException(String.format(COUND_AND_DELETE, method)); + org.springframework.data.mongodb.repository.Query queryAnnotation = method.getQueryAnnotation(); + + this.isCountQuery = queryAnnotation.count(); + this.isExistsQuery = queryAnnotation.exists(); + this.isDeleteQuery = queryAnnotation.delete(); + + if (hasAmbiguousProjectionFlags(this.isCountQuery, this.isExistsQuery, this.isDeleteQuery)) { + throw new IllegalArgumentException(String.format(COUNT_EXISTS_AND_DELETE, method)); + } + + } else { + + this.isCountQuery = false; + this.isExistsQuery = false; + this.isDeleteQuery = false; } this.parameterBinder = new ExpressionEvaluatingParameterBinder(expressionParser, evaluationContextProvider); @@ -132,6 +145,15 @@ public class ReactiveStringBasedMongoQuery extends AbstractReactiveMongoQuery { return isCountQuery; } + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.repository.query.AbstractReactiveMongoQuery#isExistsQuery() + */ + @Override + protected boolean isExistsQuery() { + return isExistsQuery; + } + /* * (non-Javadoc) * @see org.springframework.data.mongodb.repository.query.AbstractReactiveMongoQuery#isDeleteQuery() @@ -150,4 +172,9 @@ public class ReactiveStringBasedMongoQuery extends AbstractReactiveMongoQuery { return false; } + private static boolean hasAmbiguousProjectionFlags(boolean isCountQuery, boolean isExistsQuery, + boolean isDeleteQuery) { + return BooleanUtil.countBooleanTrueValues(isCountQuery, isExistsQuery, isDeleteQuery) > 1; + } + } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQuery.java index b994261a3..56153776c 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQuery.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQuery.java @@ -169,11 +169,6 @@ public class StringBasedMongoQuery extends AbstractMongoQuery { return this.isDeleteQuery; } - private static boolean hasAmbiguousProjectionFlags(boolean isCountQuery, boolean isExistsQuery, - boolean isDeleteQuery) { - return countBooleanValues(isCountQuery, isExistsQuery, isDeleteQuery) > 1; - } - /* * (non-Javadoc) * @see org.springframework.data.mongodb.repository.query.AbstractMongoQuery#isLimiting() @@ -183,18 +178,9 @@ public class StringBasedMongoQuery extends AbstractMongoQuery { return false; } - private static int countBooleanValues(boolean... values) { - - int count = 0; - - for (boolean value : values) { - - if (value) { - count++; - } - } - - return count; + private static boolean hasAmbiguousProjectionFlags(boolean isCountQuery, boolean isExistsQuery, + boolean isDeleteQuery) { + return BooleanUtil.countBooleanTrueValues(isCountQuery, isExistsQuery, isDeleteQuery) > 1; } /** diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/ReactiveMongoRepositoryTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/ReactiveMongoRepositoryTests.java index 6609ba693..ae7a76c65 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/ReactiveMongoRepositoryTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/ReactiveMongoRepositoryTests.java @@ -313,6 +313,11 @@ public class ReactiveMongoRepositoryTests implements BeanClassLoaderAware, BeanF StepVerifier.create(repository.findFirstByLastname(dave.getLastname())).expectNextCount(1).verifyComplete(); } + @Test // DATAMONGO-2030 + public void shouldReturnExistsBy() { + StepVerifier.create(repository.existsByLastname(dave.getLastname())).expectNext(true).verifyComplete(); + } + interface ReactivePersonRepository extends ReactiveMongoRepository { Flux findByLastname(String lastname); @@ -340,6 +345,8 @@ public class ReactiveMongoRepositoryTests implements BeanClassLoaderAware, BeanF Flux findPersonByLocationNear(Point point, Distance maxDistance); + Mono existsByLastname(String lastname); + Mono findFirstByLastname(String lastname); } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/ReactiveStringBasedMongoQueryUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/ReactiveStringBasedMongoQueryUnitTests.java index acdf4aced..dae19c11a 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/ReactiveStringBasedMongoQueryUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/ReactiveStringBasedMongoQueryUnitTests.java @@ -122,6 +122,14 @@ public class ReactiveStringBasedMongoQueryUnitTests { createQueryForMethod("invalidMethod", String.class); } + @Test // DATAMONGO-2030 + public void shouldSupportExistsProjection() throws Exception { + + ReactiveStringBasedMongoQuery mongoQuery = createQueryForMethod("existsByLastname", String.class); + + assertThat(mongoQuery.isExistsQuery(), is(true)); + } + @Test // DATAMONGO-1444 public void shouldSupportFindByParameterizedCriteriaAndFields() throws Exception { @@ -260,5 +268,8 @@ public class ReactiveStringBasedMongoQueryUnitTests { @Query("{'id':?#{ [0] ? { $exists :true} : [1] }, 'foo':42, 'bar': ?#{ [0] ? { $exists :false} : [1] }}") Flux findByQueryWithExpressionAndMultipleNestedObjects(boolean param0, String param1, String param2); + + @Query(value = "{ 'lastname' : ?0 }", exists = true) + Mono existsByLastname(String lastname); } }