From a6a0bde6f2294b84a5d2489dc731b5da881e98d1 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Fri, 14 Jul 2017 17:43:57 +0200 Subject: [PATCH] DATAMONGO-1719 - Add fluent reactive operations. We now provide a fluent API for find, insert, update, aggregate and delete operations that can be used as an alternative for their counterparts in ReactiveMongoOperations. Original Pull Request: #487 --- .../mongodb/core/FindPublisherPreparer.java | 5 +- .../core/ReactiveAggregationOperation.java | 100 +++++++ .../ReactiveAggregationOperationSupport.java | 109 +++++++ .../mongodb/core/ReactiveFindOperation.java | 176 ++++++++++++ .../core/ReactiveFindOperationSupport.java | 177 ++++++++++++ .../core/ReactiveFluentMongoOperations.java | 24 ++ .../mongodb/core/ReactiveInsertOperation.java | 91 ++++++ .../core/ReactiveInsertOperationSupport.java | 85 ++++++ .../mongodb/core/ReactiveMongoOperations.java | 2 +- .../mongodb/core/ReactiveMongoTemplate.java | 77 ++++- .../mongodb/core/ReactiveRemoveOperation.java | 111 +++++++ .../core/ReactiveRemoveOperationSupport.java | 101 +++++++ .../mongodb/core/ReactiveUpdateOperation.java | 160 +++++++++++ .../core/ReactiveUpdateOperationSupport.java | 131 +++++++++ .../ReactiveAggregationOperationExtensions.kt | 36 +++ .../core/ReactiveFindOperationExtensions.kt | 56 ++++ .../core/ReactiveInsertOperationExtensions.kt | 36 +++ .../core/ReactiveRemoveOperationExtensions.kt | 36 +++ .../core/ReactiveUpdateOperationExtensions.kt | 36 +++ ...eAggregationOperationSupportUnitTests.java | 111 +++++++ .../ReactiveFindOperationSupportTests.java | 272 ++++++++++++++++++ ...activeInsertOperationSupportUnitTests.java | 110 +++++++ .../ReactiveRemoveOperationSupportTests.java | 109 +++++++ .../ReactiveUpdateOperationSupportTests.java | 205 +++++++++++++ ...tiveAggregationOperationExtensionsTests.kt | 49 ++++ .../ReactiveFindOperationExtensionsTests.kt | 65 +++++ .../ReactiveInsertOperationExtensionsTests.kt | 48 ++++ .../ReactiveRemoveOperationExtensionsTests.kt | 48 ++++ .../ReactiveUpdateOperationExtensionsTests.kt | 50 ++++ 29 files changed, 2610 insertions(+), 6 deletions(-) create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveAggregationOperation.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveAggregationOperationSupport.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveFindOperation.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveFindOperationSupport.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveFluentMongoOperations.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveInsertOperation.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveInsertOperationSupport.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveRemoveOperation.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveRemoveOperationSupport.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveUpdateOperation.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveUpdateOperationSupport.java create mode 100644 spring-data-mongodb/src/main/kotlin/org/springframework/data/mongodb/core/ReactiveAggregationOperationExtensions.kt create mode 100644 spring-data-mongodb/src/main/kotlin/org/springframework/data/mongodb/core/ReactiveFindOperationExtensions.kt create mode 100644 spring-data-mongodb/src/main/kotlin/org/springframework/data/mongodb/core/ReactiveInsertOperationExtensions.kt create mode 100644 spring-data-mongodb/src/main/kotlin/org/springframework/data/mongodb/core/ReactiveRemoveOperationExtensions.kt create mode 100644 spring-data-mongodb/src/main/kotlin/org/springframework/data/mongodb/core/ReactiveUpdateOperationExtensions.kt create mode 100644 spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveAggregationOperationSupportUnitTests.java create mode 100644 spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveFindOperationSupportTests.java create mode 100644 spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveInsertOperationSupportUnitTests.java create mode 100644 spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveRemoveOperationSupportTests.java create mode 100644 spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveUpdateOperationSupportTests.java create mode 100644 spring-data-mongodb/src/test/kotlin/org/springframework/data/mongodb/core/ReactiveAggregationOperationExtensionsTests.kt create mode 100644 spring-data-mongodb/src/test/kotlin/org/springframework/data/mongodb/core/ReactiveFindOperationExtensionsTests.kt create mode 100644 spring-data-mongodb/src/test/kotlin/org/springframework/data/mongodb/core/ReactiveInsertOperationExtensionsTests.kt create mode 100644 spring-data-mongodb/src/test/kotlin/org/springframework/data/mongodb/core/ReactiveRemoveOperationExtensionsTests.kt create mode 100644 spring-data-mongodb/src/test/kotlin/org/springframework/data/mongodb/core/ReactiveUpdateOperationExtensionsTests.kt diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/FindPublisherPreparer.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/FindPublisherPreparer.java index a41203d38..9806ee5a6 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/FindPublisherPreparer.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/FindPublisherPreparer.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 the original author or authors. + * Copyright 2016-2017 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. @@ -15,7 +15,6 @@ */ package org.springframework.data.mongodb.core; -import com.mongodb.DBCursor; import com.mongodb.reactivestreams.client.FindPublisher; /** @@ -28,7 +27,7 @@ interface FindPublisherPreparer { /** * Prepare the given cursor (apply limits, skips and so on). Returns the prepared cursor. * - * @param cursor + * @param findPublisher must not be {@literal null}. */ FindPublisher prepare(FindPublisher findPublisher); } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveAggregationOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveAggregationOperation.java new file mode 100644 index 000000000..ffcbf63a8 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveAggregationOperation.java @@ -0,0 +1,100 @@ +/* + * Copyright 2017 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.core; + +import reactor.core.publisher.Flux; + +import org.springframework.data.mongodb.core.aggregation.Aggregation; + +/** + * {@link ReactiveAggregationOperation} allows creation and execution of reactive MongoDB aggregation operations in a + * fluent API style.
+ * The starting {@literal domainType} is used for mapping the {@link Aggregation} provided via {@code by} into the + * MongoDB specific representation, as well as mapping back the resulting {@link org.bson.Document}. An alternative + * input type for mapping the {@link Aggregation} can be provided by using + * {@link org.springframework.data.mongodb.core.aggregation.TypedAggregation}. + * + *
+ *     
+ *         aggregateAndReturn(Jedi.class)
+ *             .by(newAggregation(Human.class, project("These are not the droids you are looking for")))
+ *             .all();
+ *     
+ * 
+ * + * @author Mark Paluch + * @since 2.0 + */ +public interface ReactiveAggregationOperation { + + /** + * Start creating an aggregation operation that returns results mapped to the given domain type.
+ * Use {@link org.springframework.data.mongodb.core.aggregation.TypedAggregation} to specify a potentially different + * input type for he aggregation. + * + * @param domainType must not be {@literal null}. + * @return new instance of {@link ReactiveAggregation}. + * @throws IllegalArgumentException if domainType is {@literal null}. + */ + ReactiveAggregation aggregateAndReturn(Class domainType); + + /** + * Collection override (optional). + */ + interface AggregationOperationWithCollection { + + /** + * Explicitly set the name of the collection to perform the query on.
+ * Skip this step to use the default collection derived from the domain type. + * + * @param collection must not be {@literal null} nor {@literal empty}. + * @return new instance of {@link AggregationOperationWithAggregation}. + * @throws IllegalArgumentException if collection is {@literal null}. + */ + AggregationOperationWithAggregation inCollection(String collection); + } + + /** + * Trigger execution by calling one of the terminating methods. + */ + interface TerminatingAggregationOperation { + + /** + * Apply pipeline operations as specified and stream all matching elements.
+ * + * @return a {@link Flux} streaming all matching elements. Never {@literal null}. + */ + Flux all(); + } + + /** + * Define the aggregation with pipeline stages. + */ + interface AggregationOperationWithAggregation { + + /** + * Set the aggregation to be used. + * + * @param aggregation must not be {@literal null}. + * @return new instance of {@link TerminatingAggregationOperation}. + * @throws IllegalArgumentException if aggregation is {@literal null}. + */ + TerminatingAggregationOperation by(Aggregation aggregation); + } + + interface ReactiveAggregation + extends AggregationOperationWithCollection, AggregationOperationWithAggregation {} +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveAggregationOperationSupport.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveAggregationOperationSupport.java new file mode 100644 index 000000000..72d107dff --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveAggregationOperationSupport.java @@ -0,0 +1,109 @@ +/* + * Copyright 2017 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.core; + +import lombok.AccessLevel; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import lombok.experimental.FieldDefaults; +import reactor.core.publisher.Flux; + +import org.springframework.data.mongodb.core.aggregation.Aggregation; +import org.springframework.data.mongodb.core.aggregation.TypedAggregation; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +/** + * Implementation of {@link ExecutableAggregationOperation} operating directly on {@link ReactiveMongoTemplate}. + * + * @author Mark Paluch + * @since 2.0 + */ +class ReactiveAggregationOperationSupport implements ReactiveAggregationOperation { + + private final ReactiveMongoTemplate template; + + /** + * Create new instance of {@link ReactiveAggregationOperationSupport}. + * + * @param template must not be {@literal null}. + * @throws IllegalArgumentException if template is {@literal null}. + */ + ReactiveAggregationOperationSupport(ReactiveMongoTemplate template) { + + Assert.notNull(template, "Template must not be null!"); + + this.template = template; + } + + @Override + public ReactiveAggregation aggregateAndReturn(Class domainType) { + + Assert.notNull(domainType, "DomainType must not be null!"); + + return new ReactiveAggregationSupport<>(template, domainType, null, null); + } + + @RequiredArgsConstructor + @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) + static class ReactiveAggregationSupport + implements AggregationOperationWithAggregation, ReactiveAggregation, TerminatingAggregationOperation { + + @NonNull ReactiveMongoTemplate template; + @NonNull Class domainType; + Aggregation aggregation; + String collection; + + @Override + public AggregationOperationWithAggregation inCollection(String collection) { + + Assert.hasText(collection, "Collection must not be null nor empty!"); + + return new ReactiveAggregationSupport<>(template, domainType, aggregation, collection); + } + + @Override + public TerminatingAggregationOperation by(Aggregation aggregation) { + + Assert.notNull(aggregation, "Aggregation must not be null!"); + + return new ReactiveAggregationSupport<>(template, domainType, aggregation, collection); + } + + @Override + public Flux all() { + return template.aggregate(aggregation, getCollectionName(aggregation), domainType); + } + + private String getCollectionName(Aggregation aggregation) { + + if (StringUtils.hasText(collection)) { + return collection; + } + + if (aggregation instanceof TypedAggregation) { + + TypedAggregation typedAggregation = (TypedAggregation) aggregation; + + if (typedAggregation.getInputType() != null) { + return template.determineCollectionName(typedAggregation.getInputType()); + } + } + + return template.determineCollectionName(domainType); + } + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveFindOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveFindOperation.java new file mode 100644 index 000000000..a3bc71280 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveFindOperation.java @@ -0,0 +1,176 @@ +/* + * Copyright 2017 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.core; + +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import org.springframework.data.geo.GeoResult; +import org.springframework.data.mongodb.core.query.NearQuery; +import org.springframework.data.mongodb.core.query.Query; + +/** + * {@link ReactiveFindOperation} allows creation and execution of reactive MongoDB find operations in a fluent API + * style.
+ * The starting {@literal domainType} is used for mapping the {@link Query} provided via {@code matching} into the + * MongoDB specific representation. By default, the originating {@literal domainType} is also used for mapping back the + * result from the {@link org.bson.Document}. However, it is possible to define an different {@literal returnType} via + * {@code as} to mapping the result.
+ * The collection to operate on is by default derived from the initial {@literal domainType} and can be defined there + * via {@link org.springframework.data.mongodb.core.mapping.Document}. Using {@code inCollection} allows to override the + * collection name for the execution. + * + *
+ *     
+ *         query(Human.class)
+ *             .inCollection("star-wars")
+ *             .as(Jedi.class)
+ *             .matching(query(where("firstname").is("luke")))
+ *             .all();
+ *     
+ * 
+ * + * @author Mark Paluch + * @since 2.0 + */ +public interface ReactiveFindOperation { + + /** + * Start creating a find operation for the given {@literal domainType}. + * + * @param domainType must not be {@literal null}. + * @return new instance of {@link ReactiveFind}. + * @throws IllegalArgumentException if domainType is {@literal null}. + */ + ReactiveFind query(Class domainType); + + /** + * Compose find execution by calling one of the terminating methods. + */ + interface TerminatingFind { + + /** + * Get exactly zero or one result. + * + * @return {@link Mono#empty()} if no match found. + * @throws org.springframework.dao.IncorrectResultSizeDataAccessException if more than one match found. + */ + Mono one(); + + /** + * Get the first or no result. + * + * @return {@link Mono#empty()} if no match found. + */ + Mono first(); + + /** + * Get all matching elements. + * + * @return never {@literal null}. + */ + Flux all(); + + /** + * Get the number of matching elements. + * + * @return total number of matching elements. + */ + Mono count(); + + /** + * Check for the presence of matching elements. + * + * @return {@literal true} if at least one matching element exists. + */ + Mono exists(); + } + + /** + * Compose geonear execution by calling one of the terminating methods. + */ + interface TerminatingFindNear { + + /** + * Find all matching elements and return them as {@link org.springframework.data.geo.GeoResult}. + * + * @return never {@literal null}. + */ + Flux> all(); + } + + /** + * Provide a {@link Query} override (optional). + */ + interface FindWithQuery extends TerminatingFind { + + /** + * Set the filter query to be used. + * + * @param query must not be {@literal null}. + * @return new instance of {@link TerminatingFind}. + * @throws IllegalArgumentException if query is {@literal null}. + */ + TerminatingFind matching(Query query); + + /** + * Set the filter query for the geoNear execution. + * + * @param nearQuery must not be {@literal null}. + * @return new instance of {@link TerminatingFindNear}. + * @throws IllegalArgumentException if nearQuery is {@literal null}. + */ + TerminatingFindNear near(NearQuery nearQuery); + } + + /** + * Collection override (optional). + */ + interface FindWithCollection extends FindWithQuery { + + /** + * Explicitly set the name of the collection to perform the query on.
+ * Skip this step to use the default collection derived from the domain type. + * + * @param collection must not be {@literal null} nor {@literal empty}. + * @return new instance of {@link FindWithProjection}. + * @throws IllegalArgumentException if collection is {@literal null}. + */ + FindWithProjection inCollection(String collection); + } + + /** + * Result type override (optional). + */ + interface FindWithProjection extends FindWithQuery { + + /** + * Define the target type fields should be mapped to.
+ * Skip this step if you are anyway only interested in the original domain type. + * + * @param resultType must not be {@literal null}. + * @param result type. + * @return new instance of {@link FindWithProjection}. + * @throws IllegalArgumentException if resultType is {@literal null}. + */ + FindWithQuery as(Class resultType); + } + + /** + * {@link ReactiveFind} provides methods for constructing lookup operations in a fluent way. + */ + interface ReactiveFind extends FindWithCollection, FindWithProjection {} +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveFindOperationSupport.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveFindOperationSupport.java new file mode 100644 index 000000000..e65d3afc6 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveFindOperationSupport.java @@ -0,0 +1,177 @@ +/* + * Copyright 2017 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.core; + +import lombok.AccessLevel; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import lombok.experimental.FieldDefaults; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import org.bson.Document; +import org.springframework.dao.IncorrectResultSizeDataAccessException; +import org.springframework.data.mongodb.core.query.NearQuery; +import org.springframework.data.mongodb.core.query.Query; +import org.springframework.data.mongodb.core.query.SerializationUtils; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +import com.mongodb.reactivestreams.client.FindPublisher; + +/** + * Implementation of {@link ReactiveFindOperation}. + * + * @author Mark Paluch + * @since 2.0 + */ +@RequiredArgsConstructor +class ReactiveFindOperationSupport implements ReactiveFindOperation { + + private static final Query ALL_QUERY = new Query(); + + private final @NonNull ReactiveMongoTemplate template; + + @Override + public ReactiveFind query(Class domainType) { + + Assert.notNull(domainType, "DomainType must not be null!"); + + return new ReactiveFindSupport<>(template, domainType, domainType, null, ALL_QUERY); + } + + /** + * @param + * @author Christoph Strobl + * @since 2.0 + */ + @RequiredArgsConstructor + @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) + static class ReactiveFindSupport + implements ReactiveFind, FindWithCollection, FindWithProjection, FindWithQuery { + + @NonNull ReactiveMongoTemplate template; + @NonNull Class domainType; + Class returnType; + String collection; + Query query; + + @Override + public FindWithProjection inCollection(String collection) { + + Assert.hasText(collection, "Collection name must not be null nor empty!"); + + return new ReactiveFindSupport<>(template, domainType, returnType, collection, query); + } + + @Override + public FindWithQuery as(Class returnType) { + + Assert.notNull(returnType, "ReturnType must not be null!"); + + return new ReactiveFindSupport<>(template, domainType, returnType, collection, query); + } + + @Override + public TerminatingFind matching(Query query) { + + Assert.notNull(query, "Query must not be null!"); + + return new ReactiveFindSupport<>(template, domainType, returnType, collection, query); + } + + @Override + public Mono first() { + + FindPublisherPreparer preparer = getCursorPreparer(query); + Flux result = doFind(new FindPublisherPreparer() { + @Override + public FindPublisher prepare(FindPublisher publisher) { + return preparer.prepare(publisher).limit(1); + } + }); + + return result.next(); + } + + @Override + public Mono one() { + + FindPublisherPreparer preparer = getCursorPreparer(query); + Flux result = doFind(new FindPublisherPreparer() { + @Override + public FindPublisher prepare(FindPublisher publisher) { + return preparer.prepare(publisher).limit(2); + } + }); + + return result.collectList().flatMap(it -> { + + if (it.isEmpty()) { + return Mono.empty(); + } + + if (it.size() > 1) { + return Mono.error( + new IncorrectResultSizeDataAccessException("Query " + asString() + " returned non unique result.", 1)); + } + + return Mono.just(it.get(0)); + }); + } + + @Override + public Flux all() { + return doFind(null); + } + + @Override + public TerminatingFindNear near(NearQuery nearQuery) { + return () -> template.geoNear(nearQuery, domainType, getCollectionName(), returnType); + } + + @Override + public Mono count() { + return template.count(query, domainType, getCollectionName()); + } + + @Override + public Mono exists() { + return template.exists(query, domainType, getCollectionName()); + } + + private Flux doFind(FindPublisherPreparer preparer) { + + Document queryObject = query.getQueryObject(); + Document fieldsObject = query.getFieldsObject(); + + return template.doFind(getCollectionName(), queryObject, fieldsObject, domainType, returnType, + preparer != null ? preparer : getCursorPreparer(query)); + } + + private FindPublisherPreparer getCursorPreparer(Query query) { + return template.new QueryFindPublisherPreparer(query, domainType); + } + + private String getCollectionName() { + return StringUtils.hasText(collection) ? collection : template.determineCollectionName(domainType); + } + + private String asString() { + return SerializationUtils.serializeToJsonSafely(query); + } + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveFluentMongoOperations.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveFluentMongoOperations.java new file mode 100644 index 000000000..95d688dab --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveFluentMongoOperations.java @@ -0,0 +1,24 @@ +/* + * Copyright 2017 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.core; + +/** + * Stripped down interface providing access to a fluent API that specifies a basic set of reactive MongoDB operations. + * + * @since 2.0 + */ +public interface ReactiveFluentMongoOperations extends ReactiveFindOperation, ReactiveInsertOperation, + ReactiveUpdateOperation, ReactiveRemoveOperation, ReactiveAggregationOperation {} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveInsertOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveInsertOperation.java new file mode 100644 index 000000000..fa1892299 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveInsertOperation.java @@ -0,0 +1,91 @@ +/* + * Copyright 2017 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.core; + +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.util.Collection; + +/** + * {@link ReactiveInsertOperation} allows creation and execution of reactive MongoDB insert and bulk insert operations + * in a fluent API style.
+ * The collection to operate on is by default derived from the initial {@literal domainType} and can be defined there + * via {@link org.springframework.data.mongodb.core.mapping.Document}. Using {@code inCollection} allows to override the + * collection name for the execution. + * + *
+ *     
+ *         insert(Jedi.class)
+ *             .inCollection("star-wars")
+ *             .one(luke);
+ *     
+ * 
+ * + * @author Mark Paluch + * @since 2.0 + */ +public interface ReactiveInsertOperation { + + /** + * Start creating an insert operation for given {@literal domainType}. + * + * @param domainType must not be {@literal null}. + * @return new instance of {@link ReactiveInsert}. + * @throws IllegalArgumentException if domainType is {@literal null}. + */ + ReactiveInsert insert(Class domainType); + + /** + * Compose insert execution by calling one of the terminating methods. + */ + interface TerminatingInsert { + + /** + * Insert exactly one object. + * + * @param object must not be {@literal null}. + * @throws IllegalArgumentException if object is {@literal null}. + */ + Mono one(T object); + + /** + * Insert a collection of objects. + * + * @param objects must not be {@literal null}. + * @throws IllegalArgumentException if objects is {@literal null}. + */ + Flux all(Collection objects); + } + + interface ReactiveInsert extends TerminatingInsert, InsertWithCollection {} + + /** + * Collection override (optional). + */ + interface InsertWithCollection { + + /** + * Explicitly set the name of the collection.
+ * Skip this step to use the default collection derived from the domain type. + * + * @param collection must not be {@literal null} nor {@literal empty}. + * @return new instance of {@link TerminatingInsert}. + * @throws IllegalArgumentException if collection is {@literal null}. + */ + TerminatingInsert inCollection(String collection); + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveInsertOperationSupport.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveInsertOperationSupport.java new file mode 100644 index 000000000..5cef493b0 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveInsertOperationSupport.java @@ -0,0 +1,85 @@ +/* + * Copyright 2017 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.core; + +import lombok.AccessLevel; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import lombok.experimental.FieldDefaults; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.util.Collection; + +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +/** + * Implementation of {@link ReactiveInsertOperation}. + * + * @author Mark Paluch + * @since 2.0 + */ +@RequiredArgsConstructor +class ReactiveInsertOperationSupport implements ReactiveInsertOperation { + + private final @NonNull ReactiveMongoTemplate template; + + @Override + public ReactiveInsert insert(Class domainType) { + + Assert.notNull(domainType, "DomainType must not be null!"); + + return new ReactiveInsertSupport<>(template, domainType, null); + } + + @RequiredArgsConstructor + @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) + static class ReactiveInsertSupport implements ReactiveInsert { + + @NonNull ReactiveMongoTemplate template; + @NonNull Class domainType; + String collection; + + @Override + public Mono one(T object) { + + Assert.notNull(object, "Object must not be null!"); + + return template.insert(object, getCollectionName()); + } + + @Override + public Flux all(Collection objects) { + + Assert.notNull(objects, "Objects must not be null!"); + + return template.insert(objects, getCollectionName()); + } + + @Override + public ReactiveInsert inCollection(String collection) { + + Assert.hasText(collection, "Collection must not be null nor empty."); + + return new ReactiveInsertSupport<>(template, domainType, collection); + } + + private String getCollectionName() { + return StringUtils.hasText(collection) ? collection : template.determineCollectionName(domainType); + } + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoOperations.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoOperations.java index 6aac97f9b..048379591 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoOperations.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoOperations.java @@ -55,7 +55,7 @@ import com.mongodb.reactivestreams.client.MongoCollection; * @see Mono * @see Project Reactor */ -public interface ReactiveMongoOperations { +public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations { /** * Returns the reactive operations that can be performed on indexes diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoTemplate.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoTemplate.java index 10d591a3b..536390e74 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoTemplate.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoTemplate.java @@ -763,6 +763,11 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati @Override @SuppressWarnings("unchecked") public Flux> geoNear(NearQuery near, Class entityClass, String collectionName) { + return geoNear(near, entityClass, collectionName, entityClass); + } + + protected Flux> geoNear(NearQuery near, Class entityClass, String collectionName, + Class returnType) { if (near == null) { throw new InvalidDataAccessApiUsageException("NearQuery must not be null!"); @@ -791,7 +796,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati } GeoNearResultDbObjectCallback callback = new GeoNearResultDbObjectCallback( - new ReadDocumentCallback(mongoConverter, entityClass, collectionName), near.getMetric()); + new ReadDocumentCallback(mongoConverter, returnType, collectionName), near.getMetric()); return executeCommand(command, this.readPreference).flatMapMany(document -> { @@ -1682,6 +1687,51 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati new TailingQueryFindPublisherPreparer(query, entityClass)); } + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.ReactiveFindOperation#query(java.lang.Class) + */ + @Override + public ReactiveFind query(Class domainType) { + return new ReactiveFindOperationSupport(this).query(domainType); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.ReactiveUpdateOperation#update(java.lang.Class) + */ + @Override + public ReactiveUpdate update(Class domainType) { + return new ReactiveUpdateOperationSupport(this).update(domainType); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.ReactiveRemoveOperation#remove(java.lang.Class) + */ + @Override + public ReactiveRemove remove(Class domainType) { + return new ReactiveRemoveOperationSupport(this).remove(domainType); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.ReactiveInsertOperation#insert(java.lang.Class) + */ + @Override + public ReactiveInsert insert(Class domainType) { + return new ReactiveInsertOperationSupport(this).insert(domainType); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.ReactiveAggregationOperation#aggregateAndReturn(java.lang.Class) + */ + @Override + public ReactiveAggregation aggregateAndReturn(Class domainType) { + return new ReactiveAggregationOperationSupport(this).aggregateAndReturn(domainType); + } + /** * Retrieve and remove all documents matching the given {@code query} by calling {@link #find(Query, Class, String)} * and {@link #remove(Query, Class, String)}, whereas the {@link Query} for {@link #remove(Query, Class, String)} is @@ -1798,6 +1848,29 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati collectionName); } + /** + * Map the results of an ad-hoc query on the default MongoDB collection to a List of the specified targetClass while + * using sourceClass for mapping the query. + * + * @since 2.0 + */ + Flux doFind(String collectionName, Document query, Document fields, Class sourceClass, + Class targetClass, FindPublisherPreparer preparer) { + + MongoPersistentEntity entity = mappingContext.getRequiredPersistentEntity(sourceClass); + + Document mappedFields = queryMapper.getMappedFields(fields, entity); + Document mappedQuery = queryMapper.getMappedObject(query, entity); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("find using query: {} fields: {} for class: {} in collection: {}", + serializeToJsonSafely(mappedQuery), mappedFields, sourceClass, collectionName); + } + + return executeFindMultiInternal(new FindCallback(mappedQuery, mappedFields), preparer, + new ReadDocumentCallback(mongoConverter, targetClass, collectionName), collectionName); + } + protected CreateCollectionOptions convertToCreateCollectionOptions(CollectionOptions collectionOptions) { CreateCollectionOptions result = new CreateCollectionOptions(); @@ -2381,7 +2454,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati * * @author Mark Paluch */ - private class ReadDocumentCallback implements DocumentCallback { + class ReadDocumentCallback implements DocumentCallback { private final EntityReader reader; private final Class type; diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveRemoveOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveRemoveOperation.java new file mode 100644 index 000000000..ce818078c --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveRemoveOperation.java @@ -0,0 +1,111 @@ +/* + * Copyright 2017 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.core; + +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import org.springframework.data.mongodb.core.query.Query; + +import com.mongodb.client.result.DeleteResult; + +/** + * {@link ReactiveRemoveOperation} allows creation and execution of reactive MongoDB remove / findAndRemove operations + * in a fluent API style.
+ * The starting {@literal domainType} is used for mapping the {@link Query} provided via {@code matching} into the + * MongoDB specific representation. The collection to operate on is by default derived from the initial + * {@literal domainType} and can be defined there via {@link org.springframework.data.mongodb.core.mapping.Document}. + * Using {@code inCollection} allows to override the collection name for the execution. + * + *
+ *     
+ *         remove(Jedi.class)
+ *             .inCollection("star-wars")
+ *             .matching(query(where("firstname").is("luke")))
+ *             .all();
+ *     
+ * 
+ * + * @author Mark Paluch + * @since 2.0 + */ +public interface ReactiveRemoveOperation { + + /** + * Start creating a remove operation for the given {@literal domainType}. + * + * @param domainType must not be {@literal null}. + * @return new instance of {@link ReactiveRemove}. + * @throws IllegalArgumentException if domainType is {@literal null}. + */ + ReactiveRemove remove(Class domainType); + + /** + * Compose remove execution by calling one of the terminating methods. + */ + interface TerminatingRemove { + + /** + * Remove all documents matching. + * + * @return the {@link DeleteResult}. Never {@literal null}. + */ + Mono all(); + + /** + * Remove and return all matching documents.
+ * NOTE The entire list of documents will be fetched before sending the actual delete commands. + * Also, {@link org.springframework.context.ApplicationEvent}s will be published for each and every delete + * operation. + * + * @return empty {@link Flux} if no match found. Never {@literal null}. + */ + Flux findAndRemove(); + } + + /** + * Collection override (optional). + */ + interface RemoveWithCollection extends RemoveWithQuery { + + /** + * Explicitly set the name of the collection to perform the query on.
+ * Skip this step to use the default collection derived from the domain type. + * + * @param collection must not be {@literal null} nor {@literal empty}. + * @return new instance of {@link RemoveWithCollection}. + * @throws IllegalArgumentException if collection is {@literal null}. + */ + RemoveWithQuery inCollection(String collection); + } + + /** + * Provide a {@link Query} override (optional). + */ + interface RemoveWithQuery extends TerminatingRemove { + + /** + * Define the query filtering elements. + * + * @param query must not be {@literal null}. + * @return new instance of {@link TerminatingRemove}. + * @throws IllegalArgumentException if query is {@literal null}. + */ + TerminatingRemove matching(Query query); + } + + interface ReactiveRemove extends RemoveWithCollection {} +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveRemoveOperationSupport.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveRemoveOperationSupport.java new file mode 100644 index 000000000..0427bb233 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveRemoveOperationSupport.java @@ -0,0 +1,101 @@ +/* + * Copyright 2017 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.core; + +import lombok.AccessLevel; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import lombok.experimental.FieldDefaults; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import org.springframework.data.mongodb.core.query.Query; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +import com.mongodb.client.result.DeleteResult; + +/** + * Implementation of {@link ReactiveRemoveOperation}. + * + * @author Mark Paluch + * @since 2.0 + */ +@RequiredArgsConstructor +class ReactiveRemoveOperationSupport implements ReactiveRemoveOperation { + + private static final Query ALL_QUERY = new Query(); + + private final @NonNull ReactiveMongoTemplate tempate; + + @Override + public ReactiveRemove remove(Class domainType) { + + Assert.notNull(domainType, "DomainType must not be null!"); + + return new ReactiveRemoveSupport<>(tempate, domainType, null, null); + } + + @RequiredArgsConstructor + @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) + static class ReactiveRemoveSupport implements ReactiveRemove, RemoveWithCollection { + + @NonNull ReactiveMongoTemplate template; + @NonNull Class domainType; + Query query; + String collection; + + @Override + public RemoveWithQuery inCollection(String collection) { + + Assert.hasText(collection, "Collection must not be null nor empty!"); + + return new ReactiveRemoveSupport<>(template, domainType, query, collection); + } + + @Override + public TerminatingRemove matching(Query query) { + + Assert.notNull(query, "Query must not be null!"); + + return new ReactiveRemoveSupport<>(template, domainType, query, collection); + } + + @Override + public Mono all() { + + String collectionName = getCollectionName(); + + return template.doRemove(collectionName, getQuery(), domainType); + } + + @Override + public Flux findAndRemove() { + + String collectionName = getCollectionName(); + + return template.doFindAndDelete(collectionName, getQuery(), domainType); + } + + private String getCollectionName() { + return StringUtils.hasText(collection) ? collection : template.determineCollectionName(domainType); + } + + private Query getQuery() { + return query != null ? query : ALL_QUERY; + } + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveUpdateOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveUpdateOperation.java new file mode 100644 index 000000000..0ca32b544 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveUpdateOperation.java @@ -0,0 +1,160 @@ +/* + * Copyright 2017 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.core; + +import reactor.core.publisher.Mono; + +import org.springframework.data.mongodb.core.query.Query; + +import com.mongodb.client.result.UpdateResult; + +/** + * {@link ReactiveUpdateOperation} allows creation and execution of reactive MongoDB update / findAndModify operations + * in a fluent API style.
+ * The starting {@literal domainType} is used for mapping the {@link Query} provided via {@code matching}, as well as + * the {@link org.springframework.data.mongodb.core.query.Update} via {@code apply} into the MongoDB specific + * representations. The collection to operate on is by default derived from the initial {@literal domainType} and can be + * defined there via {@link org.springframework.data.mongodb.core.mapping.Document}. Using {@code inCollection} allows + * to override the collection name for the execution. + * + *
+ *     
+ *         update(Jedi.class)
+ *             .inCollection("star-wars")
+ *             .matching(query(where("firstname").is("luke")))
+ *             .apply(new Update().set("lastname", "skywalker"))
+ *             .upsert();
+ *     
+ * 
+ * + * @author Mark Paluch + * @since 2.0 + */ +public interface ReactiveUpdateOperation { + + /** + * Start creating an update operation for the given {@literal domainType}. + * + * @param domainType must not be {@literal null}. + * @return new instance of {@link ReactiveUpdate}. + * @throws IllegalArgumentException if domainType is {@literal null}. + */ + ReactiveUpdate update(Class domainType); + + /** + * Compose findAndModify execution by calling one of the terminating methods. + */ + interface TerminatingFindAndModify { + + /** + * Find, modify and return the first matching document. + * + * @return {@link Mono#empty()} if nothing found. + */ + Mono findAndModify(); + } + + /** + * Compose update execution by calling one of the terminating methods. + */ + interface TerminatingUpdate extends TerminatingFindAndModify, FindAndModifyWithOptions { + + /** + * Update all matching documents in the collection. + * + * @return never {@literal null}. + */ + Mono all(); + + /** + * Update the first document in the collection. + * + * @return never {@literal null}. + */ + Mono first(); + + /** + * Creates a new document if no documents match the filter query or updates the matching ones. + * + * @return never {@literal null}. + */ + Mono upsert(); + } + + interface ReactiveUpdate extends UpdateWithCollection, UpdateWithQuery, UpdateWithUpdate {} + + /** + * Declare the {@link org.springframework.data.mongodb.core.query.Update} to apply. + */ + interface UpdateWithUpdate { + + /** + * Set the {@link org.springframework.data.mongodb.core.query.Update} to be applied. + * + * @param update must not be {@literal null}. + * @return new instance of {@link TerminatingUpdate}. + * @throws IllegalArgumentException if update is {@literal null}. + */ + TerminatingUpdate apply(org.springframework.data.mongodb.core.query.Update update); + } + + /** + * Explicitly define the name of the collection to perform operation in (optional). + */ + interface UpdateWithCollection { + + /** + * Explicitly set the name of the collection to perform the query on.
+ * Skip this step to use the default collection derived from the domain type. + * + * @param collection must not be {@literal null} nor {@literal empty}. + * @return new instance of {@link UpdateWithCollection}. + * @throws IllegalArgumentException if collection is {@literal null}. + */ + UpdateWithQuery inCollection(String collection); + } + + /** + * Define a filter query for the {@link org.springframework.data.mongodb.core.query.Update} (optional). + */ + interface UpdateWithQuery extends UpdateWithUpdate { + + /** + * Filter documents by given {@literal query}. + * + * @param query must not be {@literal null}. + * @return new instance of {@link UpdateWithQuery}. + * @throws IllegalArgumentException if query is {@literal null}. + */ + UpdateWithUpdate matching(Query query); + } + + /** + * Define {@link FindAndModifyOptions} (optional). + */ + interface FindAndModifyWithOptions { + + /** + * Explicitly define {@link FindAndModifyOptions} for the + * {@link org.springframework.data.mongodb.core.query.Update}. + * + * @param options must not be {@literal null}. + * @return new instance of {@link FindAndModifyWithOptions}. + * @throws IllegalArgumentException if options is {@literal null}. + */ + TerminatingFindAndModify withOptions(FindAndModifyOptions options); + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveUpdateOperationSupport.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveUpdateOperationSupport.java new file mode 100644 index 000000000..d0c2fcb5f --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveUpdateOperationSupport.java @@ -0,0 +1,131 @@ +/* + * Copyright 2017 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.core; + +import lombok.AccessLevel; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import lombok.experimental.FieldDefaults; +import reactor.core.publisher.Mono; + +import org.springframework.data.mongodb.core.query.Query; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +import com.mongodb.client.result.UpdateResult; + +/** + * Implementation of {@link ReactiveUpdateOperation}. + * + * @author Mark Paluch + * @since 2.0 + */ +@RequiredArgsConstructor +class ReactiveUpdateOperationSupport implements ReactiveUpdateOperation { + + private static final Query ALL_QUERY = new Query(); + + private final @NonNull ReactiveMongoTemplate template; + + @Override + public ReactiveUpdate update(Class domainType) { + + Assert.notNull(domainType, "DomainType must not be null!"); + + return new ReactiveUpdateSupport<>(template, domainType, null, null, null, null); + } + + @RequiredArgsConstructor + @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) + static class ReactiveUpdateSupport + implements ReactiveUpdate, UpdateWithCollection, UpdateWithQuery, TerminatingUpdate { + + @NonNull ReactiveMongoTemplate template; + @NonNull Class domainType; + Query query; + org.springframework.data.mongodb.core.query.Update update; + String collection; + FindAndModifyOptions options; + + @Override + public TerminatingUpdate apply(org.springframework.data.mongodb.core.query.Update update) { + + Assert.notNull(update, "Update must not be null!"); + + return new ReactiveUpdateSupport<>(template, domainType, query, update, collection, options); + } + + @Override + public UpdateWithQuery inCollection(String collection) { + + Assert.hasText(collection, "Collection must not be null nor empty!"); + + return new ReactiveUpdateSupport<>(template, domainType, query, update, collection, options); + } + + @Override + public Mono first() { + return doUpdate(false, false); + } + + @Override + public Mono upsert() { + return doUpdate(true, true); + } + + @Override + public Mono findAndModify() { + + String collectionName = getCollectionName(); + + return template.findAndModify(query != null ? query : ALL_QUERY, update, options, domainType, collectionName); + } + + @Override + public UpdateWithUpdate matching(Query query) { + + Assert.notNull(query, "Query must not be null!"); + + return new ReactiveUpdateSupport<>(template, domainType, query, update, collection, options); + } + + @Override + public Mono all() { + return doUpdate(true, false); + } + + @Override + public TerminatingFindAndModify withOptions(FindAndModifyOptions options) { + + Assert.notNull(options, "Options must not be null!"); + + return new ReactiveUpdateSupport<>(template, domainType, query, update, collection, options); + } + + private Mono doUpdate(boolean multi, boolean upsert) { + + String collectionName = getCollectionName(); + + Query query = this.query != null ? this.query : ALL_QUERY; + + return template.doUpdate(collectionName, query, update, domainType, upsert, multi); + } + + private String getCollectionName() { + return StringUtils.hasText(collection) ? collection : template.determineCollectionName(domainType); + } + } +} diff --git a/spring-data-mongodb/src/main/kotlin/org/springframework/data/mongodb/core/ReactiveAggregationOperationExtensions.kt b/spring-data-mongodb/src/main/kotlin/org/springframework/data/mongodb/core/ReactiveAggregationOperationExtensions.kt new file mode 100644 index 000000000..cedf4775f --- /dev/null +++ b/spring-data-mongodb/src/main/kotlin/org/springframework/data/mongodb/core/ReactiveAggregationOperationExtensions.kt @@ -0,0 +1,36 @@ +/* + * Copyright 2017 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.core + +import kotlin.reflect.KClass + +/** + * Extension for [ExecutableAggregationOperation.aggregateAndReturn] providing a [KClass] based variant. + * + * @author Mark Paluch + * @since 2.0 + */ +fun ReactiveAggregationOperation.aggregateAndReturn(entityClass: KClass): ReactiveAggregationOperation.ReactiveAggregation = + aggregateAndReturn(entityClass.java) + +/** + * Extension for [ExecutableAggregationOperation.aggregateAndReturn] leveraging reified type parameters. + * + * @author Mark Paluch + * @since 2.0 + */ +inline fun ReactiveAggregationOperation.aggregateAndReturn(): ReactiveAggregationOperation.ReactiveAggregation = + aggregateAndReturn(T::class.java) \ No newline at end of file diff --git a/spring-data-mongodb/src/main/kotlin/org/springframework/data/mongodb/core/ReactiveFindOperationExtensions.kt b/spring-data-mongodb/src/main/kotlin/org/springframework/data/mongodb/core/ReactiveFindOperationExtensions.kt new file mode 100644 index 000000000..354bae8e5 --- /dev/null +++ b/spring-data-mongodb/src/main/kotlin/org/springframework/data/mongodb/core/ReactiveFindOperationExtensions.kt @@ -0,0 +1,56 @@ +/* + * Copyright 2017 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.core + +import kotlin.reflect.KClass + +/** + * Extension for [ReactiveFindOperation.query] providing a [KClass] based variant. + * + * @author Mark Paluch + * @since 2.0 + */ +fun ReactiveFindOperation.query(entityClass: KClass): ReactiveFindOperation.ReactiveFind = + query(entityClass.java) + +/** + * Extension for [ReactiveFindOperation.query] leveraging reified type parameters. + * + * @author Mark Paluch + * @since 2.0 + */ +inline fun ReactiveFindOperation.query(): ReactiveFindOperation.ReactiveFind = + query(T::class.java) + +/** + * Extension for [ReactiveFindOperation.FindWithProjection.as] providing a [KClass] based variant. + * + * @author Mark Paluch + * @since 2.0 + */ +fun ReactiveFindOperation.FindWithProjection.asType(resultType: KClass): ReactiveFindOperation.FindWithQuery = + `as`(resultType.java) + +/** + * Extension for [ReactiveFindOperation.FindWithProjection.as] leveraging reified type parameters. + * + * @author Mark Paluch + * @since 2.0 + */ +inline fun ReactiveFindOperation.FindWithProjection.asType(): ReactiveFindOperation.FindWithQuery = + `as`(T::class.java) + + diff --git a/spring-data-mongodb/src/main/kotlin/org/springframework/data/mongodb/core/ReactiveInsertOperationExtensions.kt b/spring-data-mongodb/src/main/kotlin/org/springframework/data/mongodb/core/ReactiveInsertOperationExtensions.kt new file mode 100644 index 000000000..ce37488eb --- /dev/null +++ b/spring-data-mongodb/src/main/kotlin/org/springframework/data/mongodb/core/ReactiveInsertOperationExtensions.kt @@ -0,0 +1,36 @@ +/* + * Copyright 2017 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.core + +import kotlin.reflect.KClass + +/** + * Extension for [ReactiveInsertOperation.insert] providing a [KClass] based variant. + * + * @author Mark Paluch + * @since 2.0 + */ +fun ReactiveInsertOperation.insert(entityClass: KClass): ReactiveInsertOperation.ReactiveInsert = + insert(entityClass.java) + +/** + * Extension for [ReactiveInsertOperation.insert] leveraging reified type parameters. + * + * @author Mark Paluch + * @since 2.0 + */ +inline fun ReactiveInsertOperation.insert(): ReactiveInsertOperation.ReactiveInsert = + insert(T::class.java) \ No newline at end of file diff --git a/spring-data-mongodb/src/main/kotlin/org/springframework/data/mongodb/core/ReactiveRemoveOperationExtensions.kt b/spring-data-mongodb/src/main/kotlin/org/springframework/data/mongodb/core/ReactiveRemoveOperationExtensions.kt new file mode 100644 index 000000000..0398a26d0 --- /dev/null +++ b/spring-data-mongodb/src/main/kotlin/org/springframework/data/mongodb/core/ReactiveRemoveOperationExtensions.kt @@ -0,0 +1,36 @@ +/* + * Copyright 2017 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.core + +import kotlin.reflect.KClass + +/** + * Extension for [ReactiveRemoveOperation.remove] providing a [KClass] based variant. + * + * @author Mark Paluch + * @since 2.0 + */ +fun ReactiveRemoveOperation.remove(entityClass: KClass): ReactiveRemoveOperation.ReactiveRemove = + remove(entityClass.java) + +/** + * Extension for [ReactiveRemoveOperation.remove] leveraging reified type parameters. + * + * @author Mark Paluch + * @since 2.0 + */ +inline fun ReactiveRemoveOperation.remove(): ReactiveRemoveOperation.ReactiveRemove = + remove(T::class.java) \ No newline at end of file diff --git a/spring-data-mongodb/src/main/kotlin/org/springframework/data/mongodb/core/ReactiveUpdateOperationExtensions.kt b/spring-data-mongodb/src/main/kotlin/org/springframework/data/mongodb/core/ReactiveUpdateOperationExtensions.kt new file mode 100644 index 000000000..c9e557bce --- /dev/null +++ b/spring-data-mongodb/src/main/kotlin/org/springframework/data/mongodb/core/ReactiveUpdateOperationExtensions.kt @@ -0,0 +1,36 @@ +/* + * Copyright 2017 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.core + +import kotlin.reflect.KClass + +/** + * Extension for [ReactiveUpdateOperation.update] providing a [KClass] based variant. + * + * @author Mark Paluch + * @since 2.0 + */ +fun ReactiveUpdateOperation.update(entityClass: KClass): ReactiveUpdateOperation.ReactiveUpdate = + update(entityClass.java) + +/** + * Extension for [ReactiveUpdateOperation.insert] leveraging reified type parameters. + * + * @author Mark Paluch + * @since 2.0 + */ +inline fun ReactiveUpdateOperation.update(): ReactiveUpdateOperation.ReactiveUpdate = + update(T::class.java) \ No newline at end of file diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveAggregationOperationSupportUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveAggregationOperationSupportUnitTests.java new file mode 100644 index 000000000..9df635364 --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveAggregationOperationSupportUnitTests.java @@ -0,0 +1,111 @@ +/* + * Copyright 2017 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.core; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.*; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.*; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.data.mongodb.core.aggregation.Aggregation; + +/** + * Unit tests for {@link ReactiveAggregationOperationSupport}. + * + * @author Mark Paluch + */ +@RunWith(MockitoJUnitRunner.class) +public class ReactiveAggregationOperationSupportUnitTests { + + @Mock ReactiveMongoTemplate template; + ReactiveAggregationOperationSupport opSupport; + + @Before + public void setUp() { + opSupport = new ReactiveAggregationOperationSupport(template); + } + + @Test(expected = IllegalArgumentException.class) // DATAMONGO-1719 + public void throwsExceptionOnNullDomainType() { + opSupport.aggregateAndReturn(null); + } + + @Test(expected = IllegalArgumentException.class) // DATAMONGO-1719 + public void throwsExceptionOnNullCollectionWhenUsed() { + opSupport.aggregateAndReturn(Person.class).inCollection(null); + } + + @Test(expected = IllegalArgumentException.class) // DATAMONGO-1719 + public void throwsExceptionOnEmptyCollectionWhenUsed() { + opSupport.aggregateAndReturn(Person.class).inCollection(""); + } + + @Test(expected = IllegalArgumentException.class) // DATAMONGO-1719 + public void throwsExceptionOnNullAggregation() { + opSupport.aggregateAndReturn(Person.class).by(null); + } + + @Test // DATAMONGO-1719 + public void aggregateWithUntypedAggregationAndExplicitCollection() { + + opSupport.aggregateAndReturn(Person.class).inCollection("star-wars").by(newAggregation(project("foo"))).all(); + + ArgumentCaptor captor = ArgumentCaptor.forClass(Class.class); + verify(template).aggregate(any(Aggregation.class), eq("star-wars"), captor.capture()); + assertThat(captor.getValue()).isEqualTo(Person.class); + } + + @Test // DATAMONGO-1719 + public void aggregateWithUntypedAggregation() { + + when(template.determineCollectionName(any(Class.class))).thenReturn("person"); + + opSupport.aggregateAndReturn(Person.class).by(newAggregation(project("foo"))).all(); + + ArgumentCaptor captor = ArgumentCaptor.forClass(Class.class); + + verify(template).determineCollectionName(captor.capture()); + verify(template).aggregate(any(Aggregation.class), eq("person"), captor.capture()); + + assertThat(captor.getAllValues()).containsExactly(Person.class, Person.class); + } + + @Test // DATAMONGO-1719 + public void aggregateWithTypeAggregation() { + + when(template.determineCollectionName(any(Class.class))).thenReturn("person"); + + opSupport.aggregateAndReturn(Jedi.class).by(newAggregation(Person.class, project("foo"))).all(); + + ArgumentCaptor captor = ArgumentCaptor.forClass(Class.class); + + verify(template).determineCollectionName(captor.capture()); + verify(template).aggregate(any(Aggregation.class), eq("person"), captor.capture()); + + assertThat(captor.getAllValues()).containsExactly(Person.class, Jedi.class); + } + + static class Person {} + + static class Jedi {} +} diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveFindOperationSupportTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveFindOperationSupportTests.java new file mode 100644 index 000000000..ad7d47fae --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveFindOperationSupportTests.java @@ -0,0 +1,272 @@ +/* + * Copyright 2017 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.core; + +import static org.assertj.core.api.Assertions.*; +import static org.springframework.data.mongodb.core.query.Criteria.*; +import static org.springframework.data.mongodb.core.query.Query.*; + +import lombok.AllArgsConstructor; +import lombok.Data; +import reactor.test.StepVerifier; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.dao.IncorrectResultSizeDataAccessException; +import org.springframework.data.annotation.Id; +import org.springframework.data.geo.Point; +import org.springframework.data.mongodb.core.index.GeoSpatialIndexType; +import org.springframework.data.mongodb.core.index.GeospatialIndex; +import org.springframework.data.mongodb.core.mapping.Field; +import org.springframework.data.mongodb.core.query.BasicQuery; +import org.springframework.data.mongodb.core.query.NearQuery; + +import com.mongodb.MongoClient; +import com.mongodb.reactivestreams.client.MongoClients; + +/** + * Integration tests for {@link ReactiveFindOperationSupport}. + * + * @author Mark Paluch + */ +public class ReactiveFindOperationSupportTests { + + private static final String STAR_WARS = "star-wars"; + MongoTemplate blocking; + ReactiveMongoTemplate template; + + Person han; + Person luke; + + @Before + public void setUp() { + + blocking = new MongoTemplate(new SimpleMongoDbFactory(new MongoClient(), "ExecutableFindOperationSupportTests")); + blocking.dropCollection(STAR_WARS); + + han = new Person(); + han.firstname = "han"; + han.id = "id-1"; + + luke = new Person(); + luke.firstname = "luke"; + luke.id = "id-2"; + + blocking.save(han); + blocking.save(luke); + + template = new ReactiveMongoTemplate(MongoClients.create(), "ExecutableFindOperationSupportTests"); + } + + @Test(expected = IllegalArgumentException.class) // DATAMONGO-1719 + public void domainTypeIsRequired() { + template.query(null); + } + + @Test(expected = IllegalArgumentException.class) // DATAMONGO-1719 + public void returnTypeIsRequiredOnSet() { + template.query(Person.class).as(null); + } + + @Test(expected = IllegalArgumentException.class) // DATAMONGO-1719 + public void collectionIsRequiredOnSet() { + template.query(Person.class).inCollection(null); + } + + @Test // DATAMONGO-1719 + public void findAll() { + + StepVerifier.create(template.query(Person.class).all().collectList()).consumeNextWith(actual -> { + assertThat(actual).containsExactlyInAnyOrder(han, luke); + }).verifyComplete(); + } + + @Test // DATAMONGO-1719 + public void findAllWithCollection() { + StepVerifier.create(template.query(Human.class).inCollection(STAR_WARS).all()).expectNextCount(2).verifyComplete(); + } + + @Test // DATAMONGO-1719 + public void findAllWithProjection() { + + StepVerifier.create(template.query(Person.class).as(Jedi.class).all().map(it -> it.getClass().getName())) + .expectNext(Jedi.class.getName(), Jedi.class.getName()).verifyComplete(); + } + + @Test // DATAMONGO-1719 + public void findAllBy() { + + StepVerifier.create(template.query(Person.class).matching(query(where("firstname").is("luke"))).all()) + .expectNext(luke).verifyComplete(); + } + + @Test // DATAMONGO-1719 + public void findAllByWithCollectionUsingMappingInformation() { + + StepVerifier + .create(template.query(Jedi.class).inCollection(STAR_WARS).matching(query(where("name").is("luke"))).all()) + .consumeNextWith(it -> assertThat(it).isInstanceOf(Jedi.class)).verifyComplete(); + } + + @Test // DATAMONGO-1719 + public void findAllByWithCollection() { + + StepVerifier + .create( + template.query(Human.class).inCollection(STAR_WARS).matching(query(where("firstname").is("luke"))).all()) + .expectNextCount(1).verifyComplete(); + } + + @Test // DATAMONGO-1719 + public void findAllByWithProjection() { + + StepVerifier + .create(template.query(Person.class).as(Jedi.class).matching(query(where("firstname").is("luke"))).all()) + .consumeNextWith(it -> assertThat(it).isInstanceOf(Jedi.class)).verifyComplete(); + } + + @Test // DATAMONGO-1719 + public void findBy() { + + StepVerifier.create(template.query(Person.class).matching(query(where("firstname").is("luke"))).one()) + .expectNext(luke).verifyComplete(); + } + + @Test // DATAMONGO-1719 + public void findByNoMatch() { + + StepVerifier.create(template.query(Person.class).matching(query(where("firstname").is("spock"))).one()) + .verifyComplete(); + } + + @Test // DATAMONGO-1719 + public void findByTooManyResults() { + + StepVerifier.create(template.query(Person.class).matching(query(where("firstname").in("han", "luke"))).one()) + .expectError(IncorrectResultSizeDataAccessException.class).verify(); + } + + @Test // DATAMONGO-1719 + public void findAllNearBy() { + + blocking.indexOps(Planet.class).ensureIndex( + new GeospatialIndex("coordinates").typed(GeoSpatialIndexType.GEO_2DSPHERE).named("planet-coordinate-idx")); + + Planet alderan = new Planet("alderan", new Point(-73.9836, 40.7538)); + Planet dantooine = new Planet("dantooine", new Point(-73.9928, 40.7193)); + + blocking.save(alderan); + blocking.save(dantooine); + + StepVerifier.create(template.query(Planet.class).near(NearQuery.near(-73.9667, 40.78).spherical(true)).all()) + .consumeNextWith(actual -> { + assertThat(actual.getDistance()).isNotNull(); + }).expectNextCount(1).verifyComplete(); + } + + @Test // DATAMONGO-1719 + public void findAllNearByWithCollectionAndProjection() { + + blocking.indexOps(Planet.class).ensureIndex( + new GeospatialIndex("coordinates").typed(GeoSpatialIndexType.GEO_2DSPHERE).named("planet-coordinate-idx")); + + Planet alderan = new Planet("alderan", new Point(-73.9836, 40.7538)); + Planet dantooine = new Planet("dantooine", new Point(-73.9928, 40.7193)); + + blocking.save(alderan); + blocking.save(dantooine); + + StepVerifier.create(template.query(Object.class).inCollection(STAR_WARS).as(Human.class) + .near(NearQuery.near(-73.9667, 40.78).spherical(true)).all()).consumeNextWith(actual -> { + assertThat(actual.getDistance()).isNotNull(); + assertThat(actual.getContent()).isInstanceOf(Human.class); + assertThat(actual.getContent().getId()).isEqualTo("alderan"); + }).expectNextCount(1).verifyComplete(); + } + + @Test // DATAMONGO-1719 + public void firstShouldReturnFirstEntryInCollection() { + StepVerifier.create(template.query(Person.class).first()).expectNextCount(1).verifyComplete(); + } + + @Test // DATAMONGO-1719 + public void countShouldReturnNrOfElementsInCollectionWhenNoQueryPresent() { + StepVerifier.create(template.query(Person.class).count()).expectNext(2L).verifyComplete(); + } + + @Test // DATAMONGO-1719 + public void countShouldReturnNrOfElementsMatchingQuery() { + + StepVerifier + .create(template.query(Person.class).matching(query(where("firstname").is(luke.getFirstname()))).count()) + .expectNext(1L).verifyComplete(); + } + + @Test // DATAMONGO-1719 + public void existsShouldReturnTrueIfAtLeastOneElementExistsInCollection() { + StepVerifier.create(template.query(Person.class).exists()).expectNext(true).verifyComplete(); + } + + @Test // DATAMONGO-1719 + public void existsShouldReturnFalseIfNoElementExistsInCollection() { + + blocking.remove(new BasicQuery("{}"), STAR_WARS); + + StepVerifier.create(template.query(Person.class).exists()).expectNext(false).verifyComplete(); + } + + @Test // DATAMONGO-1719 + public void existsShouldReturnTrueIfAtLeastOneElementMatchesQuery() { + + StepVerifier + .create(template.query(Person.class).matching(query(where("firstname").is(luke.getFirstname()))).exists()) + .expectNext(true).verifyComplete(); + } + + @Test // DATAMONGO-1719 + public void existsShouldReturnFalseWhenNoElementMatchesQuery() { + + StepVerifier.create(template.query(Person.class).matching(query(where("firstname").is("spock"))).exists()) + .expectNext(false).verifyComplete(); + } + + @Data + @org.springframework.data.mongodb.core.mapping.Document(collection = STAR_WARS) + static class Person { + @Id String id; + String firstname; + } + + @Data + static class Human { + @Id String id; + } + + @Data + static class Jedi { + + @Field("firstname") String name; + } + + @Data + @AllArgsConstructor + @org.springframework.data.mongodb.core.mapping.Document(collection = STAR_WARS) + static class Planet { + + @Id String name; + Point coordinates; + } +} diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveInsertOperationSupportUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveInsertOperationSupportUnitTests.java new file mode 100644 index 000000000..7ad297a3b --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveInsertOperationSupportUnitTests.java @@ -0,0 +1,110 @@ +/* + * Copyright 2017 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.core; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.*; +import static org.mockito.Mockito.anyList; + +import lombok.Data; + +import java.util.Arrays; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.data.annotation.Id; + +/** + * Unit tests for {@link ExecutableInsertOperationSupport}. + * + * @author Mark Paluch + */ +@RunWith(MockitoJUnitRunner.class) +public class ReactiveInsertOperationSupportUnitTests { + + private static final String STAR_WARS = "star-wars"; + + @Mock ReactiveMongoTemplate template; + + ReactiveInsertOperationSupport ops; + + Person luke, han; + + @Before + public void setUp() { + + when(template.determineCollectionName(any(Class.class))).thenReturn(STAR_WARS); + + ops = new ReactiveInsertOperationSupport(template); + + luke = new Person(); + luke.id = "id-1"; + luke.firstname = "luke"; + + han = new Person(); + han.firstname = "han"; + han.id = "id-2"; + } + + @Test(expected = IllegalArgumentException.class) // DATAMONGO-1719 + public void nullCollectionShouldThrowException() { + ops.insert(Person.class).inCollection(null); + } + + @Test // DATAMONGO-1719 + public void insertShouldUseDerivedCollectionName() { + + ops.insert(Person.class).one(luke); + + ArgumentCaptor captor = ArgumentCaptor.forClass(Class.class); + + verify(template).determineCollectionName(captor.capture()); + verify(template).insert(eq(luke), eq(STAR_WARS)); + + assertThat(captor.getAllValues()).containsExactly(Person.class); + } + + @Test // DATAMONGO-1719 + public void insertShouldUseExplicitCollectionName() { + + ops.insert(Person.class).inCollection(STAR_WARS).one(luke); + + verify(template, never()).determineCollectionName(any(Class.class)); + verify(template).insert(eq(luke), eq(STAR_WARS)); + } + + @Test // DATAMONGO-1719 + public void insertCollectionShouldDelegateCorrectly() { + + ops.insert(Person.class).all(Arrays.asList(luke, han)); + + verify(template).determineCollectionName(any(Class.class)); + verify(template).insert(anyList(), eq(STAR_WARS)); + } + + @Data + @org.springframework.data.mongodb.core.mapping.Document(collection = STAR_WARS) + static class Person { + @Id String id; + String firstname; + } +} diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveRemoveOperationSupportTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveRemoveOperationSupportTests.java new file mode 100644 index 000000000..53cee518e --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveRemoveOperationSupportTests.java @@ -0,0 +1,109 @@ +/* + * Copyright 2017 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.core; + +import static org.assertj.core.api.Assertions.*; +import static org.springframework.data.mongodb.core.query.Criteria.*; +import static org.springframework.data.mongodb.core.query.Query.*; + +import lombok.Data; +import reactor.test.StepVerifier; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Field; + +import com.mongodb.MongoClient; +import com.mongodb.reactivestreams.client.MongoClients; + +/** + * Integration tests for {@link ReactiveRemoveOperationSupport}. + * + * @author Mark Paluch + */ +public class ReactiveRemoveOperationSupportTests { + + private static final String STAR_WARS = "star-wars"; + MongoTemplate blocking; + ReactiveMongoTemplate template; + + Person han; + Person luke; + + @Before + public void setUp() { + + blocking = new MongoTemplate(new SimpleMongoDbFactory(new MongoClient(), "ExecutableRemoveOperationSupportTests")); + blocking.dropCollection(STAR_WARS); + + han = new Person(); + han.firstname = "han"; + han.id = "id-1"; + + luke = new Person(); + luke.firstname = "luke"; + luke.id = "id-2"; + + blocking.save(han); + blocking.save(luke); + + template = new ReactiveMongoTemplate(MongoClients.create(), "ExecutableRemoveOperationSupportTests"); + } + + @Test // DATAMONGO-1719 + public void removeAll() { + + StepVerifier.create(template.remove(Person.class).all()).consumeNextWith(actual -> { + assertThat(actual.getDeletedCount()).isEqualTo(2L); + }).verifyComplete(); + } + + @Test // DATAMONGO-1719 + public void removeAllMatching() { + + StepVerifier.create(template.remove(Person.class).matching(query(where("firstname").is("han"))).all()) + .consumeNextWith(actual -> assertThat(actual.getDeletedCount()).isEqualTo(1L)).verifyComplete(); + } + + @Test // DATAMONGO-1719 + public void removeAllMatchingWithAlternateDomainTypeAndCollection() { + + StepVerifier + .create(template.remove(Jedi.class).inCollection(STAR_WARS).matching(query(where("name").is("luke"))).all()) + .consumeNextWith(actual -> assertThat(actual.getDeletedCount()).isEqualTo(1L)).verifyComplete(); + } + + @Test // DATAMONGO-1719 + public void removeAndReturnAllMatching() { + + StepVerifier.create(template.remove(Person.class).matching(query(where("firstname").is("han"))).findAndRemove()) + .expectNext(han).verifyComplete(); + } + + @Data + @org.springframework.data.mongodb.core.mapping.Document(collection = STAR_WARS) + static class Person { + @Id String id; + String firstname; + } + + @Data + static class Jedi { + + @Field("firstname") String name; + } +} diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveUpdateOperationSupportTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveUpdateOperationSupportTests.java new file mode 100644 index 000000000..3c6691b25 --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveUpdateOperationSupportTests.java @@ -0,0 +1,205 @@ +/* + * Copyright 2017 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.core; + +import static org.assertj.core.api.Assertions.*; +import static org.springframework.data.mongodb.core.query.Criteria.*; +import static org.springframework.data.mongodb.core.query.Query.*; + +import lombok.Data; +import reactor.test.StepVerifier; + +import org.bson.BsonString; +import org.junit.Before; +import org.junit.Test; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Field; +import org.springframework.data.mongodb.core.query.Query; +import org.springframework.data.mongodb.core.query.Update; + +import com.mongodb.MongoClient; +import com.mongodb.reactivestreams.client.MongoClients; + +/** + * Integration tests for {@link ReactiveUpdateOperationSupport}. + * + * @author Mark Paluch + */ +public class ReactiveUpdateOperationSupportTests { + + private static final String STAR_WARS = "star-wars"; + MongoTemplate blocking; + ReactiveMongoTemplate template; + + Person han; + Person luke; + + @Before + public void setUp() { + + blocking = new MongoTemplate(new SimpleMongoDbFactory(new MongoClient(), "ExecutableUpdateOperationSupportTests")); + blocking.dropCollection(STAR_WARS); + + han = new Person(); + han.firstname = "han"; + han.id = "id-1"; + + luke = new Person(); + luke.firstname = "luke"; + luke.id = "id-2"; + + blocking.save(han); + blocking.save(luke); + + template = new ReactiveMongoTemplate(MongoClients.create(), "ExecutableUpdateOperationSupportTests"); + } + + @Test(expected = IllegalArgumentException.class) // DATAMONGO-1719 + public void domainTypeIsRequired() { + template.update(null); + } + + @Test(expected = IllegalArgumentException.class) // DATAMONGO-1719 + public void updateIsRequired() { + template.update(Person.class).apply(null); + } + + @Test(expected = IllegalArgumentException.class) // DATAMONGO-1719 + public void collectionIsRequiredOnSet() { + template.update(Person.class).inCollection(null); + } + + @Test(expected = IllegalArgumentException.class) // DATAMONGO-1719 + public void findAndModifyOptionsAreRequiredOnSet() { + template.update(Person.class).apply(new Update()).withOptions(null); + } + + @Test // DATAMONGO-1719 + public void updateFirst() { + + StepVerifier.create(template.update(Person.class).apply(new Update().set("firstname", "Han")).first()) + .consumeNextWith(actual -> { + + assertThat(actual.getModifiedCount()).isEqualTo(1L); + assertThat(actual.getUpsertedId()).isNull(); + }).verifyComplete(); + + } + + @Test // DATAMONGO-1719 + public void updateAll() { + + StepVerifier.create(template.update(Person.class).apply(new Update().set("firstname", "Han")).all()) + .consumeNextWith(actual -> { + + assertThat(actual.getModifiedCount()).isEqualTo(2L); + assertThat(actual.getUpsertedId()).isNull(); + }).verifyComplete(); + } + + @Test // DATAMONGO-1719 + public void updateAllMatching() { + + StepVerifier + .create(template.update(Person.class).matching(queryHan()).apply(new Update().set("firstname", "Han")).all()) + .consumeNextWith(actual -> { + + assertThat(actual.getModifiedCount()).isEqualTo(1L); + assertThat(actual.getUpsertedId()).isNull(); + }).verifyComplete(); + } + + @Test // DATAMONGO-1719 + public void updateWithDifferentDomainClassAndCollection() { + + StepVerifier.create(template.update(Jedi.class).inCollection(STAR_WARS) + .matching(query(where("_id").is(han.getId()))).apply(new Update().set("name", "Han")).all()) + .consumeNextWith(actual -> { + + assertThat(actual.getModifiedCount()).isEqualTo(1L); + assertThat(actual.getUpsertedId()).isNull(); + }).verifyComplete(); + + assertThat(blocking.findOne(queryHan(), Person.class)).isNotEqualTo(han).hasFieldOrPropertyWithValue("firstname", + "Han"); + } + + @Test // DATAMONGO-1719 + public void findAndModify() { + + StepVerifier.create( + template.update(Person.class).matching(queryHan()).apply(new Update().set("firstname", "Han")).findAndModify()) + .expectNext(han).verifyComplete(); + + assertThat(blocking.findOne(queryHan(), Person.class)).isNotEqualTo(han).hasFieldOrPropertyWithValue("firstname", + "Han"); + } + + @Test // DATAMONGO-1719 + public void findAndModifyWithDifferentDomainTypeAndCollection() { + + StepVerifier + .create(template.update(Jedi.class).inCollection(STAR_WARS).matching(query(where("_id").is(han.getId()))) + .apply(new Update().set("name", "Han")).findAndModify()) + .consumeNextWith(actual -> assertThat(actual.getName()).isEqualTo("han")).verifyComplete(); + + assertThat(blocking.findOne(queryHan(), Person.class)).isNotEqualTo(han).hasFieldOrPropertyWithValue("firstname", + "Han"); + } + + @Test // DATAMONGO-1719 + public void findAndModifyWithOptions() { + + StepVerifier.create(template.update(Person.class).matching(queryHan()).apply(new Update().set("firstname", "Han")) + .withOptions(FindAndModifyOptions.options().returnNew(true)).findAndModify()).consumeNextWith(actual -> { + + assertThat(actual).isNotEqualTo(han).hasFieldOrPropertyWithValue("firstname", "Han"); + }).verifyComplete(); + } + + @Test // DATAMONGO-1719 + public void upsert() { + + StepVerifier.create(template.update(Person.class).matching(query(where("id").is("id-3"))) + .apply(new Update().set("firstname", "Chewbacca")).upsert()).consumeNextWith(actual -> { + + assertThat(actual.getModifiedCount()).isEqualTo(0L); + assertThat(actual.getUpsertedId()).isEqualTo(new BsonString("id-3")); + }).verifyComplete(); + } + + private Query queryHan() { + return query(where("id").is(han.getId())); + } + + @Data + @org.springframework.data.mongodb.core.mapping.Document(collection = STAR_WARS) + static class Person { + @Id String id; + String firstname; + } + + @Data + static class Human { + @Id String id; + } + + @Data + static class Jedi { + + @Field("firstname") String name; + } +} diff --git a/spring-data-mongodb/src/test/kotlin/org/springframework/data/mongodb/core/ReactiveAggregationOperationExtensionsTests.kt b/spring-data-mongodb/src/test/kotlin/org/springframework/data/mongodb/core/ReactiveAggregationOperationExtensionsTests.kt new file mode 100644 index 000000000..5eee26ab9 --- /dev/null +++ b/spring-data-mongodb/src/test/kotlin/org/springframework/data/mongodb/core/ReactiveAggregationOperationExtensionsTests.kt @@ -0,0 +1,49 @@ +/* + * Copyright 2017 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.core + +import com.nhaarman.mockito_kotlin.verify +import example.first.First +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Answers +import org.mockito.Mock +import org.mockito.junit.MockitoJUnitRunner + +/** + * @author Mark Paluch + */ +@RunWith(MockitoJUnitRunner::class) +class ReactiveAggregationOperationExtensionsTests { + + @Mock(answer = Answers.RETURNS_MOCKS) + lateinit var operation: ReactiveAggregationOperation + + @Test // DATAMONGO-1719 + fun `aggregateAndReturn(KClass) extension should call its Java counterpart`() { + + operation.aggregateAndReturn(First::class) + verify(operation).aggregateAndReturn(First::class.java) + } + + @Test // DATAMONGO-1719 + fun `aggregateAndReturn() with reified type parameter extension should call its Java counterpart`() { + + operation.aggregateAndReturn() + verify(operation).aggregateAndReturn(First::class.java) + } + +} diff --git a/spring-data-mongodb/src/test/kotlin/org/springframework/data/mongodb/core/ReactiveFindOperationExtensionsTests.kt b/spring-data-mongodb/src/test/kotlin/org/springframework/data/mongodb/core/ReactiveFindOperationExtensionsTests.kt new file mode 100644 index 000000000..fda680cb7 --- /dev/null +++ b/spring-data-mongodb/src/test/kotlin/org/springframework/data/mongodb/core/ReactiveFindOperationExtensionsTests.kt @@ -0,0 +1,65 @@ +/* + * Copyright 2017 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.core + +import com.nhaarman.mockito_kotlin.verify +import example.first.First +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Answers +import org.mockito.Mock +import org.mockito.junit.MockitoJUnitRunner + +/** + * @author Mark Paluch + */ +@RunWith(MockitoJUnitRunner::class) +class ReactiveFindOperationExtensionsTests { + + @Mock(answer = Answers.RETURNS_MOCKS) + lateinit var operation: ReactiveFindOperation + + @Mock(answer = Answers.RETURNS_MOCKS) + lateinit var operationWithProjection: ReactiveFindOperation.FindWithProjection + + @Test // DATAMONGO-1719 + fun `ReactiveFind#query(KClass) extension should call its Java counterpart`() { + + operation.query(First::class) + verify(operation).query(First::class.java) + } + + @Test // DATAMONGO-1719 + fun `ReactiveFind#query() with reified type parameter extension should call its Java counterpart`() { + + operation.query() + verify(operation).query(First::class.java) + } + + @Test // DATAMONGO-1719 + fun `ReactiveFind#FindOperatorWithProjection#asType(KClass) extension should call its Java counterpart`() { + + operationWithProjection.asType(First::class) + verify(operationWithProjection).`as`(First::class.java) + } + + @Test // DATAMONGO-1719 + fun `ReactiveFind#FindOperatorWithProjection#asType() with reified type parameter extension should call its Java counterpart`() { + + operationWithProjection.asType() + verify(operationWithProjection).`as`(First::class.java) + } +} diff --git a/spring-data-mongodb/src/test/kotlin/org/springframework/data/mongodb/core/ReactiveInsertOperationExtensionsTests.kt b/spring-data-mongodb/src/test/kotlin/org/springframework/data/mongodb/core/ReactiveInsertOperationExtensionsTests.kt new file mode 100644 index 000000000..236dd06d5 --- /dev/null +++ b/spring-data-mongodb/src/test/kotlin/org/springframework/data/mongodb/core/ReactiveInsertOperationExtensionsTests.kt @@ -0,0 +1,48 @@ +/* + * Copyright 2017 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.core + +import com.nhaarman.mockito_kotlin.verify +import example.first.First +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Answers +import org.mockito.Mock +import org.mockito.junit.MockitoJUnitRunner + +/** + * @author Mark Paluch + */ +@RunWith(MockitoJUnitRunner::class) +class ReactiveInsertOperationExtensionsTests { + + @Mock(answer = Answers.RETURNS_MOCKS) + lateinit var operation: ReactiveInsertOperation + + @Test // DATAMONGO-1719 + fun `insert(KClass) extension should call its Java counterpart`() { + + operation.insert(First::class) + verify(operation).insert(First::class.java) + } + + @Test // DATAMONGO-1719 + fun `insert() with reified type parameter extension should call its Java counterpart`() { + + operation.insert() + verify(operation).insert(First::class.java) + } +} diff --git a/spring-data-mongodb/src/test/kotlin/org/springframework/data/mongodb/core/ReactiveRemoveOperationExtensionsTests.kt b/spring-data-mongodb/src/test/kotlin/org/springframework/data/mongodb/core/ReactiveRemoveOperationExtensionsTests.kt new file mode 100644 index 000000000..a8650ce5c --- /dev/null +++ b/spring-data-mongodb/src/test/kotlin/org/springframework/data/mongodb/core/ReactiveRemoveOperationExtensionsTests.kt @@ -0,0 +1,48 @@ +/* + * Copyright 2017 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.core + +import com.nhaarman.mockito_kotlin.verify +import example.first.First +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Answers +import org.mockito.Mock +import org.mockito.junit.MockitoJUnitRunner + +/** + * @author Mark Paluch + */ +@RunWith(MockitoJUnitRunner::class) +class ReactiveRemoveOperationExtensionsTests { + + @Mock(answer = Answers.RETURNS_MOCKS) + lateinit var operation: ReactiveRemoveOperation + + @Test // DATAMONGO-1719 + fun `remove(KClass) extension should call its Java counterpart`() { + + operation.remove(First::class) + verify(operation).remove(First::class.java) + } + + @Test // DATAMONGO-1719 + fun `remove() with reified type parameter extension should call its Java counterpart`() { + + operation.remove() + verify(operation).remove(First::class.java) + } +} diff --git a/spring-data-mongodb/src/test/kotlin/org/springframework/data/mongodb/core/ReactiveUpdateOperationExtensionsTests.kt b/spring-data-mongodb/src/test/kotlin/org/springframework/data/mongodb/core/ReactiveUpdateOperationExtensionsTests.kt new file mode 100644 index 000000000..725c48ea4 --- /dev/null +++ b/spring-data-mongodb/src/test/kotlin/org/springframework/data/mongodb/core/ReactiveUpdateOperationExtensionsTests.kt @@ -0,0 +1,50 @@ +/* + * Copyright 2017 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.core + +import com.nhaarman.mockito_kotlin.verify +import example.first.First +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Answers +import org.mockito.Mock +import org.mockito.junit.MockitoJUnitRunner + +/** + * Unit tests for [ReactiveExecutableUpdateOperationExtensions]. + * + * @author Mark Paluch + */ +@RunWith(MockitoJUnitRunner::class) +class ReactiveUpdateOperationExtensionsTests { + + @Mock(answer = Answers.RETURNS_MOCKS) + lateinit var operation: ReactiveUpdateOperation + + @Test // DATAMONGO-1719 + fun `update(KClass) extension should call its Java counterpart`() { + + operation.update(First::class) + verify(operation).update(First::class.java) + } + + @Test // DATAMONGO-1719 + fun `update() with reified type parameter extension should call its Java counterpart`() { + + operation.update() + verify(operation).update(First::class.java) + } +}