From 8b8eb3cfe526ce7fd131f02eea9eb56276a2f8e9 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 | 13 ++++- .../mongodb/repository/query/BooleanUtil.java | 48 +++++++++++++++++++ .../query/ReactivePartTreeMongoQuery.java | 9 ++++ .../query/ReactiveStringBasedMongoQuery.java | 38 ++++++++++++--- .../query/StringBasedMongoQuery.java | 20 ++------ .../ReactiveMongoRepositoryTests.java | 7 +++ ...eactiveStringBasedMongoQueryUnitTests.java | 11 +++++ 7 files changed, 121 insertions(+), 25 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 f850f3f2f..eee7b4b4a 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 @@ -15,10 +15,10 @@ */ package org.springframework.data.mongodb.repository.query; -import org.bson.Document; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import org.bson.Document; import org.reactivestreams.Publisher; import org.springframework.core.convert.converter.Converter; import org.springframework.data.convert.EntityInstantiators; @@ -36,7 +36,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; /** @@ -152,6 +151,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) -> { @@ -223,6 +224,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 4f929506e..f97c785e0 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 6f565cc38..daaa8c4e1 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 @@ -28,7 +28,6 @@ import org.springframework.data.mongodb.repository.query.ExpressionEvaluatingPar import org.springframework.data.mongodb.repository.query.StringBasedMongoQuery.ParameterBinding; import org.springframework.data.mongodb.repository.query.StringBasedMongoQuery.ParameterBindingParser; import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider; -import org.springframework.data.spel.EvaluationContextProvider; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.util.Assert; @@ -41,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; @@ -93,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); @@ -133,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() @@ -151,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 fb2898f02..b8262188a 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 @@ -170,11 +170,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() @@ -184,18 +179,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 1651bbb3e..d9809ab0e 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 @@ -309,6 +309,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(); + } + @Test // DATAMONGO-1979 public void findAppliesAnnotatedSort() { @@ -353,6 +358,8 @@ public class ReactiveMongoRepositoryTests implements BeanClassLoaderAware, BeanF Flux findPersonByLocationNear(Point point, Distance maxDistance); + Mono existsByLastname(String lastname); + Mono findFirstByLastname(String lastname); @Query(sort = "{ age : -1 }") 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 1f51f9f86..054000923 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); } }