From 0539641f075e54142310db4270fecd89b6141105 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Fri, 12 May 2017 10:36:24 +0200 Subject: [PATCH 1/5] DATAMONGO-1563 - Prepare issue branch. --- pom.xml | 2 +- spring-data-mongodb-cross-store/pom.xml | 4 ++-- spring-data-mongodb-distribution/pom.xml | 2 +- spring-data-mongodb-log4j/pom.xml | 2 +- spring-data-mongodb/pom.xml | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pom.xml b/pom.xml index cb4e66d37e..5656cb3f41 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-mongodb-parent - 2.0.0.BUILD-SNAPSHOT + 2.0.0.DATAMONGO-1563-SNAPSHOT pom Spring Data MongoDB diff --git a/spring-data-mongodb-cross-store/pom.xml b/spring-data-mongodb-cross-store/pom.xml index 4a49168713..c2ff64f291 100644 --- a/spring-data-mongodb-cross-store/pom.xml +++ b/spring-data-mongodb-cross-store/pom.xml @@ -6,7 +6,7 @@ org.springframework.data spring-data-mongodb-parent - 2.0.0.BUILD-SNAPSHOT + 2.0.0.DATAMONGO-1563-SNAPSHOT ../pom.xml @@ -48,7 +48,7 @@ org.springframework.data spring-data-mongodb - 2.0.0.BUILD-SNAPSHOT + 2.0.0.DATAMONGO-1563-SNAPSHOT diff --git a/spring-data-mongodb-distribution/pom.xml b/spring-data-mongodb-distribution/pom.xml index 750ed23aa8..569160a582 100644 --- a/spring-data-mongodb-distribution/pom.xml +++ b/spring-data-mongodb-distribution/pom.xml @@ -13,7 +13,7 @@ org.springframework.data spring-data-mongodb-parent - 2.0.0.BUILD-SNAPSHOT + 2.0.0.DATAMONGO-1563-SNAPSHOT ../pom.xml diff --git a/spring-data-mongodb-log4j/pom.xml b/spring-data-mongodb-log4j/pom.xml index 50d0a6454a..526e5bb318 100644 --- a/spring-data-mongodb-log4j/pom.xml +++ b/spring-data-mongodb-log4j/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-mongodb-parent - 2.0.0.BUILD-SNAPSHOT + 2.0.0.DATAMONGO-1563-SNAPSHOT ../pom.xml diff --git a/spring-data-mongodb/pom.xml b/spring-data-mongodb/pom.xml index 99ef83a54b..cb73baabd8 100644 --- a/spring-data-mongodb/pom.xml +++ b/spring-data-mongodb/pom.xml @@ -11,7 +11,7 @@ org.springframework.data spring-data-mongodb-parent - 2.0.0.BUILD-SNAPSHOT + 2.0.0.DATAMONGO-1563-SNAPSHOT ../pom.xml From 057d281d96127738439776e4885d0b76cf8d5b0d Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Fri, 12 May 2017 13:53:21 +0200 Subject: [PATCH 2/5] DATAMONGO-1563 - Add fluent alternative for MongoOperations. We now provide an alternative API for MongoOperations that allows defining operations in a more fluent way. This reduces the number of methods and strips down the interface to a minimum while offering a more readable API. // find all with filter query and projecting return type template.query(Person.class) .matching(query(where("firstname").is("luke"))) .as(Jedi.class) .all(); // update with filter & upsert template.update(Person.class) .apply(new Update().set("firstname", "Han")) .matching(query(where("id").is("han-solo"))) .upsert(); // remove all matching template.remove(Jedi.class) .inCollection(STAR_WARS) .matching(query(where("name").is("luke"))) .all(); // aggregate template.aggregateAndReturn(Jedi.class) .inCollection("star-wars) .by(newAggregation(project("name"))) .get(); --- ...ExecutableAggregationOperationBuilder.java | 110 ++++++++ ...ExecutableAggregationOperationSupport.java | 115 +++++++++ .../core/ExecutableFindOperationBuilder.java | 176 +++++++++++++ .../core/ExecutableFindOperationSupport.java | 209 +++++++++++++++ .../ExecutableRemoveOperationBuilder.java | 110 ++++++++ .../ExecutableRemoveOperationSupport.java | 110 ++++++++ .../ExecutableUpdateOperationBuilder.java | 185 +++++++++++++ .../ExecutableUpdateOperationSupport.java | 144 +++++++++++ .../mongodb/core/FluentMongoOperations.java | 75 ++++++ .../data/mongodb/core/MongoOperations.java | 2 +- .../data/mongodb/core/MongoTemplate.java | 81 +++++- ...eAggregationOperationSupportUnitTests.java | 152 +++++++++++ .../ExecutableFindOperationSupportTests.java | 242 ++++++++++++++++++ ...ExecutableRemoveOperationSupportTests.java | 108 ++++++++ ...ExecutableUpdateOperationSupportTests.java | 187 ++++++++++++++ .../data/mongodb/core/MongoTemplateTests.java | 2 +- 16 files changed, 1999 insertions(+), 9 deletions(-) create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableAggregationOperationBuilder.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableAggregationOperationSupport.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableFindOperationBuilder.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableFindOperationSupport.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableRemoveOperationBuilder.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableRemoveOperationSupport.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableUpdateOperationBuilder.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableUpdateOperationSupport.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/FluentMongoOperations.java create mode 100644 spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ExecutableAggregationOperationSupportUnitTests.java create mode 100644 spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ExecutableFindOperationSupportTests.java create mode 100644 spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ExecutableRemoveOperationSupportTests.java create mode 100644 spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ExecutableUpdateOperationSupportTests.java diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableAggregationOperationBuilder.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableAggregationOperationBuilder.java new file mode 100644 index 0000000000..dbad4a12a4 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableAggregationOperationBuilder.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 org.springframework.data.mongodb.core.aggregation.Aggregation; +import org.springframework.data.mongodb.core.aggregation.AggregationResults; +import org.springframework.data.util.CloseableIterator; + +/** + * @author Christoph Strobl + * @since 2.0 + */ +public interface ExecutableAggregationOperationBuilder { + + /** + * 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}. + * @param + * @return + * @throws IllegalArgumentException if domainType is {@literal null}. + */ + public AggregationOperationBuilder aggregateAndReturn(Class domainType); + + /** + * Collection override (Optional). + * + * @param + * @author Christoph Strobl + * @since 2.0 + */ + interface WithCollectionBuilder { + + /** + * [optional] Explicitly set the name of the collection to perform the query on.
+ * Just skip this step to use the default collection derived from the domain type. + * + * @param collection must not be {@literal null} nor {@literal empty}. + * @return + * @throws IllegalArgumentException if collection is {@literal null}. + */ + WithAggregationBuilder inCollection(String collection); + } + + /** + * Trigger execution by calling one of the terminating methods. + * + * @param + */ + interface AggregateOperationBuilderTerminatingOperations { + + /** + * Apply pipeline operations as specified. + * + * @return + */ + AggregationResults get(); + + /** + * Apply pipeline operations as specified.
+ * Returns a {@link CloseableIterator} that wraps the a Mongo DB {@link com.mongodb.Cursor} + * + * @return + */ + CloseableIterator stream(); + } + + /** + * Define the aggregation with pipeline stages. + * + * @param + * @author Christoph Strobl + * @since 2.0 + */ + interface WithAggregationBuilder { + + /** + * [required] Set the aggregation to be used. + * + * @param aggregation must not be {@literal null}. + * @return + * @throws IllegalArgumentException if aggregation is {@literal null}. + */ + AggregateOperationBuilderTerminatingOperations by(Aggregation aggregation); + } + + /** + * @param + * @author Christoph Strobl + * @since 2.0 + */ + interface AggregationOperationBuilder extends WithCollectionBuilder, WithAggregationBuilder { + + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableAggregationOperationSupport.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableAggregationOperationSupport.java new file mode 100644 index 0000000000..ab38d53ab6 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableAggregationOperationSupport.java @@ -0,0 +1,115 @@ +/* + * 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 org.springframework.data.mongodb.core.aggregation.Aggregation; +import org.springframework.data.mongodb.core.aggregation.AggregationResults; +import org.springframework.data.mongodb.core.aggregation.TypedAggregation; +import org.springframework.data.util.CloseableIterator; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +/** + * Implementation of {@link ExecutableAggregationOperationBuilder} operating directly on {@link MongoTemplate}. + * + * @author Christoph Strobl + * @since 2.0 + */ +class ExecutableAggregationOperationSupport implements ExecutableAggregationOperationBuilder { + + private final MongoTemplate template; + + /** + * Create new instance of ExecutableAggregationOperationSupport. + * + * @param template must not be {@literal null}. + * @throws IllegalArgumentException if template is {@literal null}. + */ + public ExecutableAggregationOperationSupport(MongoTemplate template) { + + Assert.notNull(template, "Template must not be null!"); + this.template = template; + } + + @Override + public AggregationOperationBuilder aggregateAndReturn(Class domainType) { + + Assert.notNull(domainType, "DomainType must not be null!"); + return new AggreationBuilder(template, null, domainType, null); + } + + /** + * @param + * @author Christoph Strobl + * @since 2.0 + */ + static class AggreationBuilder implements WithAggregationBuilder, AggregationOperationBuilder, + AggregateOperationBuilderTerminatingOperations { + + private final MongoTemplate template; + private final Aggregation aggregation; + private final Class domainType; + private final String collection; + + public AggreationBuilder(MongoTemplate template, Aggregation aggregation, Class domainType, String collection) { + + this.template = template; + this.aggregation = aggregation; + this.domainType = domainType; + this.collection = collection; + } + + @Override + public WithAggregationBuilder inCollection(String collection) { + + Assert.hasText(collection, "Collection must not be null nor empty!"); + return new AggreationBuilder(template, aggregation, domainType, collection); + } + + @Override + public AggregateOperationBuilderTerminatingOperations by(Aggregation aggregation) { + + Assert.notNull(aggregation, "Aggregation must not be null!"); + return new AggreationBuilder(template, aggregation, domainType, collection); + } + + @Override + public AggregationResults get() { + return template.aggregate(aggregation, getCollectionName(aggregation), domainType); + } + + @Override + public CloseableIterator stream() { + return template.aggregateStream(aggregation, getCollectionName(aggregation), domainType); + } + + private String getCollectionName(Aggregation aggregation) { + + if (StringUtils.hasText(collection)) { + return collection; + } + + if (aggregation instanceof TypedAggregation) { + + if (((TypedAggregation) aggregation).getInputType() != null) { + return template.determineCollectionName(((TypedAggregation) aggregation).getInputType()); + } + } + + return template.determineCollectionName(domainType); + } + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableFindOperationBuilder.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableFindOperationBuilder.java new file mode 100644 index 0000000000..e3cdf8c450 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableFindOperationBuilder.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 java.util.List; + +import org.springframework.data.geo.GeoResults; +import org.springframework.data.mongodb.core.query.NearQuery; +import org.springframework.data.mongodb.core.query.Query; +import org.springframework.data.util.CloseableIterator; + +/** + * @author Christoph Strobl + * @since 2.0 + */ +public interface ExecutableFindOperationBuilder { + + /** + * Start creating a find operation for the given {@literal domainType}.
+ * + * @param domainType must not be {@literal null}. + * @param + * @return + * @throws IllegalArgumentException if domainType is {@literal null}. + */ + public FindOperationBuilder find(Class domainType); + + /** + * Trigger find execution by calling one of the terminating methods. + * + * @param + * @author Christoph Strobl + * @since 2.0 + */ + interface FindOperationBuilderTerminatingOperations { + + /** + * Get exactly zero or one result. + * + * @return {@literal null} if no match found. + * @throws org.springframework.dao.IncorrectResultSizeDataAccessException if more than one match found. + */ + T one(); + + /** + * Get the first or no result. + * + * @return {@literal null} if no match found. + */ + T first(); + + /** + * Get all matching elements. + * + * @return never {@literal}. + */ + List all(); + + /** + * Stream all matching elements. + * + * @return a {@link CloseableIterator} that wraps the a Mongo DB {@link com.mongodb.Cursor} that needs to be closed. + * Never {@literal null}. + */ + CloseableIterator stream(); + } + + /** + * Trigger geonear execution by calling one of the terminating methods. + * + * @param + * @author Christoph Strobl + * @since 2.0 + */ + interface FindOperationBuilderTerminatingNearOperations { + + /** + * Find all matching elements and return them as {@link org.springframework.data.geo.GeoResult}. + * + * @return never {@literal null}. + */ + GeoResults all(); + } + + /** + * Terminating operations invoking the actual query execution. + * + * @param + * @author Christoph Strobl + * @since 2.0 + */ + interface WithQueryBuilder extends FindOperationBuilderTerminatingOperations { + + /** + * [optional] Set the filter query to be used. + * + * @param query must not be {@literal null}. + * @return + * @throws IllegalArgumentException if query is {@literal null}. + */ + FindOperationBuilderTerminatingOperations matching(Query query); + + /** + * [optional] Set the filter query for the geoNear execution. + * + * @param nearQuery must not be {@literal null}. + * @return + * @throws IllegalArgumentException if nearQuery is {@literal null}. + */ + FindOperationBuilderTerminatingNearOperations near(NearQuery nearQuery); + + } + + /** + * Collection override (Optional). + * + * @param + * @author Christoph Strobl + * @since 2.0 + */ + interface WithCollectionBuilder extends WithQueryBuilder { + + /** + * [optional] Explicitly set the name of the collection to perform the query on.
+ * Just skip this step to use the default collection derived from the domain type. + * + * @param collection must not be {@literal null} nor {@literal empty}. + * @return + * @throws IllegalArgumentException if collection is {@literal null}. + */ + WithProjectionBuilder inCollection(String collection); + } + + /** + * Result type override (Optional). + * + * @param + * @author Christoph Strobl + * @since 2.0 + */ + interface WithProjectionBuilder extends WithQueryBuilder { + + /** + * [optional] Define the target type fields should be mapped to.
+ * Just skip this step if you are anyway only interested in the original domain type. + * + * @param resultType must not be {@literal null}. + * @param + * @return + * @throws IllegalArgumentException if resultType is {@literal null}. + */ + WithQueryBuilder as(Class resultType); + } + + /** + * @param + * @author Christoph Strobl + * @since 2.0 + */ + interface FindOperationBuilder extends WithCollectionBuilder, WithProjectionBuilder { + + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableFindOperationSupport.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableFindOperationSupport.java new file mode 100644 index 0000000000..f56bb453f6 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableFindOperationSupport.java @@ -0,0 +1,209 @@ +/* + * 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 java.util.List; +import java.util.Optional; + +import org.bson.Document; +import org.springframework.dao.IncorrectResultSizeDataAccessException; +import org.springframework.data.mongodb.core.query.BasicQuery; +import org.springframework.data.mongodb.core.query.NearQuery; +import org.springframework.data.mongodb.core.query.Query; +import org.springframework.data.util.CloseableIterator; +import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; + +import com.mongodb.client.FindIterable; + +/** + * Implementation of {@link ExecutableFindOperationBuilder}. + * + * @author Christoph Strobl + * @since 2.0 + */ +class ExecutableFindOperationSupport implements ExecutableFindOperationBuilder { + + private final MongoTemplate template; + + /** + * Create new {@link ExecutableFindOperationSupport}. + * + * @param template must not be {@literal null}. + * @throws IllegalArgumentException if template is {@literal null}. + */ + ExecutableFindOperationSupport(MongoTemplate template) { + + Assert.notNull(template, "Template must not be null!"); + this.template = template; + } + + @Override + public FindOperationBuilder find(Class domainType) { + + Assert.notNull(domainType, "DomainType must not be null!"); + + return new FindBuilder<>(template, null, domainType, null, domainType); + } + + /** + * @param + * @author Christoph Strobl + * @since 2.0 + */ + static class FindBuilder + implements FindOperationBuilder, WithCollectionBuilder, WithProjectionBuilder, WithQueryBuilder { + + private final MongoTemplate template; + private final Class domainType; + private final Class returnType; + private final String collection; + private final Query query; + + private FindBuilder(MongoTemplate template, Query query, Class domainType, String collection, + Class returnType) { + + this.template = template; + this.query = query; + this.returnType = returnType; + this.domainType = domainType; + this.collection = collection; + } + + @Override + public WithProjectionBuilder inCollection(String collection) { + + Assert.hasText(collection, "Collection name must not be null nor empty!"); + + return new FindBuilder<>(template, query, domainType, collection, returnType); + } + + @Override + public WithQueryBuilder as(Class returnType) { + + Assert.notNull(returnType, "ReturnType must not be null!"); + + return new FindBuilder<>(template, query, domainType, collection, returnType); + } + + public String asString() { + return ""; + } + + @Override + public T one() { + + List result = doFind(new DelegatingQueryCursorPreparer(getCursorPreparer(query, null)).limit(2)); + + if (ObjectUtils.isEmpty(result)) { + return null; + } + + if (result.size() > 1) { + throw new IncorrectResultSizeDataAccessException("Query " + asString() + " returned non unique result.", 1); + } + + return result.iterator().next(); + } + + @Override + public T first() { + + List result = doFind(new DelegatingQueryCursorPreparer(getCursorPreparer(query, null)).limit(1)); + return ObjectUtils.isEmpty(result) ? null : result.iterator().next(); + } + + @Override + public List all() { + return doFind(null); + } + + @Override + public CloseableIterator stream() { + return doStream(); + } + + @Override + public FindOperationBuilderTerminatingOperations matching(Query query) { + + Assert.notNull(query, "Query must not be null!"); + + return new FindBuilder<>(template, query, domainType, collection, returnType); + } + + @Override + public FindOperationBuilderTerminatingNearOperations near(NearQuery nearQuery) { + return () -> template.geoNear(nearQuery, domainType, getCollectionName(), returnType); + } + + private List doFind(CursorPreparer preparer) { + + Document queryObject = query != null ? query.getQueryObject() : new Document(); + Document fieldsObject = query != null ? query.getFieldsObject() : new Document(); + + return template.doFind(getCollectionName(), queryObject, fieldsObject, domainType, returnType, + getCursorPreparer(query, preparer)); + } + + private CloseableIterator doStream() { + + return template.doStream(query != null ? query : new BasicQuery(new Document()), domainType, getCollectionName(), + returnType); + } + + private CursorPreparer getCursorPreparer(Query query, CursorPreparer preparer) { + return query == null || preparer != null ? preparer : template.new QueryCursorPreparer(query, domainType); + } + + private String getCollectionName() { + return StringUtils.hasText(collection) ? collection : template.determineCollectionName(domainType); + } + } + + /** + * @param + * @author Christoph Strobl + * @since 2.0 + */ + static class DelegatingQueryCursorPreparer implements CursorPreparer { + + private final CursorPreparer delegate; + private Optional limit = Optional.empty(); + + public DelegatingQueryCursorPreparer(CursorPreparer delegate) { + this.delegate = delegate; + } + + @Override + public FindIterable prepare(FindIterable cursor) { + + FindIterable target = delegate.prepare(cursor); + + if (limit.isPresent()) { + target = target.limit(limit.get()); + } + + return target; + } + + CursorPreparer limit(int limit) { + + this.limit = Optional.of(limit); + return this; + } + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableRemoveOperationBuilder.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableRemoveOperationBuilder.java new file mode 100644 index 0000000000..791542adcc --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableRemoveOperationBuilder.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 java.util.List; + +import org.springframework.data.mongodb.core.query.Query; + +import com.mongodb.client.result.DeleteResult; + +/** + * @author Christoph Strobl + * @since 2.0 + */ +public interface ExecutableRemoveOperationBuilder { + + /** + * Start creating a rempve operation for the given {@literal domainType}. + * + * @param domainType must not be {@literal null}. + * @param + * @return + * @throws IllegalArgumentException if domainType is {@literal null}. + */ + RemoveOperationBuilder remove(Class domainType); + + /** + * Collection override (Optional). + * + * @param + * @author Christoph Strobl + * @since 2.0 + */ + interface WithCollectionBuilder extends WithQueryBuilder { + + /** + * [optional] Explicitly set the name of the collection to perform the query on.
+ * Just skip this step to use the default collection derived from the domain type. + * + * @param collection must not be {@literal null} nor {@literal empty}. + * @return + * @throws IllegalArgumentException if domainType is {@literal null}. + */ + WithQueryBuilder inCollection(String collection); + } + + /** + * @param + * @author Christoph Strobl + * @since 2.0 + */ + interface RemoveOperationBuilderTerminatingOperations { + + /** + * Remove all documents matching. + * + * @return + */ + DeleteResult all(); + + /** + * Remove and return all documents matching.
+ * 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. + * + * @param filter must not be {@literal null}. + * @return empty {@link List} if no match found. Never {@literal null}. + */ + List allAndReturn(); + } + + /** + * @param + * @author Christoph Strobl + * @since 2.0 + */ + interface WithQueryBuilder extends RemoveOperationBuilderTerminatingOperations { + + /** + * [optional] Define the query filtering elements. + * + * @param query must not be {@literal null}. + * @return + * @throws IllegalArgumentException if domainType is {@literal null}. + */ + RemoveOperationBuilderTerminatingOperations matching(Query query); + } + + /** + * @param + * @author Christoph Strobl + * @since 2.0 + */ + interface RemoveOperationBuilder extends WithCollectionBuilder { + + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableRemoveOperationSupport.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableRemoveOperationSupport.java new file mode 100644 index 0000000000..993ae7a388 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableRemoveOperationSupport.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 java.util.List; + +import org.bson.Document; +import org.springframework.data.mongodb.core.query.BasicQuery; +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 ExecutableRemoveOperationBuilder}. + * + * @author Christoph Strobl + * @since 2.0 + */ +class ExecutableRemoveOperationSupport implements ExecutableRemoveOperationBuilder { + + private final MongoTemplate tempate; + + /** + * Create new {@link ExecutableRemoveOperationSupport}. + * + * @param template must not be {@literal null}. + * @throws IllegalArgumentException if template is {@literal null}. + */ + ExecutableRemoveOperationSupport(MongoTemplate template) { + + Assert.notNull(template, "Template must not be null!"); + this.tempate = template; + } + + @Override + public RemoveOperationBuilder remove(Class domainType) { + + Assert.notNull(domainType, "DomainType must not be null!"); + return new RemoveBuilder(tempate, null, domainType, null); + } + + /** + * @param + * @author Christoph Strobl + * @since 2.0 + */ + static class RemoveBuilder implements RemoveOperationBuilder, WithCollectionBuilder { + + private final MongoTemplate template; + private final Query query; + private final Class domainType; + private final String collection; + + public RemoveBuilder(MongoTemplate template, Query query, Class domainType, String collection) { + + this.template = template; + this.query = query; + this.domainType = domainType; + this.collection = collection; + } + + @Override + public WithQueryBuilder inCollection(String collection) { + + Assert.hasText(collection, "Collection must not be null nor empty!"); + return new RemoveBuilder(template, query, domainType, collection); + } + + @Override + public RemoveOperationBuilderTerminatingOperations matching(Query query) { + + Assert.notNull(query, "Query must not be null!"); + return new RemoveBuilder(template, query, domainType, collection); + } + + @Override + public DeleteResult all() { + + String collectionName = StringUtils.hasText(collection) ? collection + : template.determineCollectionName(domainType); + + return template.doRemove(collectionName, query != null ? query : new BasicQuery(new Document()), domainType); + } + + @Override + public List allAndReturn() { + + String collectionName = StringUtils.hasText(collection) ? collection + : template.determineCollectionName(domainType); + + return template.doFindAndDelete(collectionName, query != null ? query : new BasicQuery(new Document()), + domainType); + } + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableUpdateOperationBuilder.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableUpdateOperationBuilder.java new file mode 100644 index 0000000000..15599ff918 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableUpdateOperationBuilder.java @@ -0,0 +1,185 @@ +/* + * 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 org.springframework.data.mongodb.core.query.Query; +import org.springframework.data.mongodb.core.query.Update; + +import com.mongodb.client.result.UpdateResult; + +/** + * @author Christoph Strobl + * @since 2.0 + */ +public interface ExecutableUpdateOperationBuilder { + + /** + * Start creating an update operation for the given {@literal domainType}. + * + * @param domainType must not be {@literal null}. + * @param + * @return + * @throws IllegalArgumentException if domainType is {@literal null}. + */ + public UpdateOperationBuilder update(Class domainType); + + /** + * Trigger update execution by calling one of the terminating methods. + * + * @param + * @author Christoph Strobl + * @since 2.0 + */ + interface UpdateOperationBuilderTerminatingOperations + extends UpdateOperationBuilderTerminatingFindAndModifyOperations { + + /** + * Update the first document in the collection. + * + * @return + */ + UpdateResult first(); + + /** + * Creates a new document if no documents match the filter query or updates the matching ones. + * + * @return + */ + UpdateResult upsert(); + + /** + * Update all matching documents in the collection. + * + * @return + */ + UpdateResult all(); + } + + /** + * Trigger findAndModify execution by calling one of the terminating methods. + * + * @param + */ + interface UpdateOperationBuilderTerminatingFindAndModifyOperations { + + /** + * Find, modify and return the first matching document. + * + * @return + */ + T findAndModify(); + } + + /** + * Define a filter query for the {@link Update}. + * + * @param + * @author Christoph Strobl + * @since 2.0 + */ + interface WithQueryBuilder extends WithFindAndModifyBuilder, UpdateOperationBuilderTerminatingOperations { + + /** + * [optional] Filter documents by given {@literal query}. + * + * @param filter must not be {@literal null}. + * @return + * @throws IllegalArgumentException if filter is {@literal null}. + */ + UpdateOperationBuilderTerminatingOperations matching(Query filter); + + } + + /** + * Define a filter query for the {@link Update} used for {@literal findAndModify}. + * + * @param + * @author Christoph Strobl + * @since 2.0 + */ + interface WithFindAndModifyBuilder { + + /** + * [optional] Find, modify and return the first matching document. + * + * @param filter must not be {@literal null}. + * @return + * @throws IllegalArgumentException if filter is {@literal null}. + */ + UpdateOperationBuilderTerminatingFindAndModifyOperations matching(Query filter); + } + + /** + * @param + * @author Christoph Strobl + * @since 2.0 + */ + interface UpdateOperationBuilder { + + /** + * [required] Set the {@link Update} to be applied. + * + * @param update must not be {@literal null}. + * @return + * @throws IllegalArgumentException if update is {@literal null}. + */ + WithOptionsBuilder apply(Update update); + } + + /** + * @param + * @author Christoph Strobl + * @since 2.0 + */ + interface WithCollectionBuilder extends WithFindAndModifyOptionsBuilder { + + /** + * [optional] Explicitly set the name of the collection to perform the query on.
+ * Just skip this step to use the default collection derived from the domain type. + * + * @param collection must not be {@literal null} nor {@literal empty}. + * @return + * @throws IllegalArgumentException if collection is {@literal null}. + */ + WithQueryBuilder inCollection(String collection); + } + + /** + * @param + * @author Christoph Strobl + * @since 2.0 + */ + interface WithOptionsBuilder extends WithCollectionBuilder, WithQueryBuilder { + + } + + /** + * @param + * @author Christoph Strobl + * @since 2.0 + */ + interface WithFindAndModifyOptionsBuilder { + + /** + * [optional] Explicitly define {@link FindAndModifyOptions} for the {@link Update}. + * + * @param options must not be {@literal null}. + * @return + * @throws IllegalArgumentException if options is {@literal null}. + */ + WithFindAndModifyBuilder withOptions(FindAndModifyOptions options); + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableUpdateOperationSupport.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableUpdateOperationSupport.java new file mode 100644 index 0000000000..e52b9167ac --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableUpdateOperationSupport.java @@ -0,0 +1,144 @@ +/* + * 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 org.bson.Document; +import org.springframework.data.mongodb.core.query.BasicQuery; +import org.springframework.data.mongodb.core.query.Query; +import org.springframework.data.mongodb.core.query.Update; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +import com.mongodb.client.result.UpdateResult; + +/** + * Implementation of {@link ExecutableUpdateOperationBuilder}. + * + * @author Christoph Strobl + * @since 2.0 + */ +class ExecutableUpdateOperationSupport implements ExecutableUpdateOperationBuilder { + + private final MongoTemplate template; + + /** + * Creates new {@link ExecutableUpdateOperationSupport}. + * + * @param template must not be {@literal null}. + */ + ExecutableUpdateOperationSupport(MongoTemplate template) { + + Assert.notNull(template, "Template must not be null!"); + this.template = template; + } + + @Override + public UpdateOperationBuilder update(Class domainType) { + + Assert.notNull(domainType, "DomainType must not be null!"); + return new UpdateBuilder(template, null, domainType, null, null, null); + } + + /** + * @param + * @author Christoph Strobl + * @since 2.0 + */ + static class UpdateBuilder + implements WithOptionsBuilder, UpdateOperationBuilder, WithCollectionBuilder, WithQueryBuilder { + + private final MongoTemplate template; + private final Query query; + private final Class domainType; + private final Update update; + private final String collection; + private final FindAndModifyOptions options; + + private UpdateBuilder(MongoTemplate template, Query query, Class domainType, Update update, String collection, + FindAndModifyOptions options) { + + this.template = template; + this.query = query; + this.domainType = domainType; + this.update = update; + this.collection = collection; + this.options = options; + } + + @Override + public WithOptionsBuilder apply(Update update) { + + Assert.notNull(update, "Update must not be null!"); + return new UpdateBuilder(template, query, domainType, update, collection, options); + } + + @Override + public WithQueryBuilder inCollection(String collection) { + + Assert.hasText(collection, "Collection must not be null nor empty!"); + return new UpdateBuilder(template, query, domainType, update, collection, options); + } + + @Override + public UpdateResult first() { + return doUpdate(false, false); + } + + @Override + public UpdateResult upsert() { + return doUpdate(true, true); + } + + @Override + public T findAndModify() { + + String collectionName = StringUtils.hasText(collection) ? collection + : template.determineCollectionName(domainType); + + return template.findAndModify(query != null ? query : new BasicQuery(new Document()), update, options, domainType, + collectionName); + } + + @Override + public UpdateOperationBuilderTerminatingOperations matching(Query query) { + + Assert.notNull(query, "Query must not be null!"); + return new UpdateBuilder(template, query, domainType, update, collection, options); + } + + @Override + public UpdateResult all() { + return doUpdate(true, false); + } + + @Override + public WithFindAndModifyBuilder withOptions(FindAndModifyOptions options) { + + Assert.notNull(options, "Options must not be null!"); + return new UpdateBuilder(template, query, domainType, update, collection, options); + } + + private UpdateResult doUpdate(boolean multi, boolean upsert) { + + String collectionName = StringUtils.hasText(collection) ? collection + : template.determineCollectionName(domainType); + + Query query = this.query != null ? this.query : new BasicQuery(new Document()); + + return template.doUpdate(collectionName, query, update, domainType, upsert, multi); + } + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/FluentMongoOperations.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/FluentMongoOperations.java new file mode 100644 index 0000000000..369a253e0b --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/FluentMongoOperations.java @@ -0,0 +1,75 @@ +/* + * 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 org.springframework.data.mongodb.core.ExecutableAggregationOperationBuilder.AggregationOperationBuilder; +import org.springframework.data.mongodb.core.ExecutableFindOperationBuilder.FindOperationBuilder; +import org.springframework.data.mongodb.core.ExecutableRemoveOperationBuilder.RemoveOperationBuilder; +import org.springframework.data.mongodb.core.ExecutableUpdateOperationBuilder.UpdateOperationBuilder; + +/** + * Stripped down interface providing access to a {@literal builder} based fluent API that specifies a basic set of + * MongoDB operations. + * + * @author Christoph Strobl + * @since 2.0 + */ +public interface FluentMongoOperations { + + /** + * Entry point for constructing and executing queries for a given domain type. + * + * @param domainType must not be {@literal null}. + * @param + * @return new instance of {@link FindExecutionBuilder}. + * @throws IllegalArgumentException if domainType is {@literal null}. + * @since 2.0 + */ + FindOperationBuilder query(Class domainType); + + /** + * Entry point for constructing and executing updates for a given domain type. + * + * @param domainType must not be {@literal null}. + * @param + * @return new instance of {@link ExecutableUpdateOperationBuilder}. + * @throws IllegalArgumentException if domainType is {@literal null}. + * @since 2.0 + */ + UpdateOperationBuilder update(Class domainType); + + /** + * Entry point for constructing and executing deletes for a given domain type. + * + * @param domainType must not be {@literal null}. + * @param + * @return new instance of {@link RemoveOperationBuilder}. + * @throws IllegalArgumentException if domainType is {@literal null}. + * @since 2.0 + */ + RemoveOperationBuilder remove(Class domainType); + + /** + * Entry point for constructing and executing aggregation operations. + * + * @param domainType must not be {@literal null}. + * @param + * @return new instance of {@link AggregationOperation}. + * @throws IllegalArgumentException if domainType is {@literal null}. + * @since 2.0 + */ + AggregationOperationBuilder aggregateAndReturn(Class domainType); +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoOperations.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoOperations.java index cf0c737d14..2935e167de 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoOperations.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoOperations.java @@ -58,7 +58,7 @@ * @author Thomas Darimont * @author Maninder Singh */ -public interface MongoOperations { +public interface MongoOperations extends FluentMongoOperations { /** * The collection name used for the specified class by this template. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java index ade633bf5f..2165ea50cf 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java @@ -67,6 +67,9 @@ import org.springframework.data.mapping.model.MappingException; import org.springframework.data.mongodb.MongoDbFactory; import org.springframework.data.mongodb.core.BulkOperations.BulkMode; +import org.springframework.data.mongodb.core.ExecutableFindOperationBuilder.FindOperationBuilder; +import org.springframework.data.mongodb.core.ExecutableRemoveOperationBuilder.RemoveOperationBuilder; +import org.springframework.data.mongodb.core.ExecutableUpdateOperationBuilder.UpdateOperationBuilder; import org.springframework.data.mongodb.core.aggregation.Aggregation; import org.springframework.data.mongodb.core.aggregation.AggregationOperationContext; import org.springframework.data.mongodb.core.aggregation.AggregationOptions; @@ -344,10 +347,16 @@ public CloseableIterator stream(final Query query, final Class entityT */ @Override public CloseableIterator stream(final Query query, final Class entityType, final String collectionName) { + return doStream(query, entityType, collectionName, entityType); + } + + protected CloseableIterator doStream(final Query query, final Class entityType, final String collectionName, + Class returnType) { Assert.notNull(query, "Query must not be null!"); Assert.notNull(entityType, "Entity type must not be null!"); Assert.hasText(collectionName, "Collection name must not be null or empty!"); + Assert.notNull(returnType, "ReturnType must not be null!"); return execute(collectionName, new CollectionCallback>() { @@ -364,7 +373,7 @@ public CloseableIterator doInCollection(MongoCollection collection) .prepare(collection.find(mappedQuery).projection(mappedFields)); return new CloseableIterableCursorAdapter(cursor, exceptionTranslator, - new ReadDocumentCallback(mongoConverter, entityType, collectionName)); + new ReadDocumentCallback(mongoConverter, returnType, collectionName)); } }); } @@ -644,17 +653,21 @@ public GeoResults geoNear(NearQuery near, Class entityClass) { } @SuppressWarnings("unchecked") - public GeoResults geoNear(NearQuery near, Class entityClass, String collectionName) { + public GeoResults geoNear(NearQuery near, Class domainType, String collectionName) { + return geoNear(near, domainType, collectionName, domainType); + } + + public GeoResults geoNear(NearQuery near, Class domainType, String collectionName, Class returnType) { if (near == null) { throw new InvalidDataAccessApiUsageException("NearQuery must not be null!"); } - if (entityClass == null) { + if (domainType == null) { throw new InvalidDataAccessApiUsageException("Entity class must not be null!"); } - String collection = StringUtils.hasText(collectionName) ? collectionName : determineCollectionName(entityClass); + String collection = StringUtils.hasText(collectionName) ? collectionName : determineCollectionName(domainType); Document nearDocument = near.toDocument(); Document command = new Document("geoNear", collection); @@ -662,12 +675,12 @@ public GeoResults geoNear(NearQuery near, Class entityClass, String co if (nearDocument.containsKey("query")) { Document query = (Document) nearDocument.get("query"); - command.put("query", queryMapper.getMappedObject(query, getPersistentEntity(entityClass))); + command.put("query", queryMapper.getMappedObject(query, getPersistentEntity(domainType))); } if (LOGGER.isDebugEnabled()) { LOGGER.debug("Executing geoNear using: {} for class: {} in collection: {}", serializeToJsonSafely(command), - entityClass, collectionName); + domainType, collectionName); } Document commandResult = executeCommand(command, this.readPreference); @@ -675,7 +688,7 @@ public GeoResults geoNear(NearQuery near, Class entityClass, String co results = results == null ? Collections.emptyList() : results; DocumentCallback> callback = new GeoNearResultDocumentCallback( - new ReadDocumentCallback(mongoConverter, entityClass, collectionName), near.getMetric()); + new ReadDocumentCallback(mongoConverter, returnType, collectionName), near.getMetric()); List> result = new ArrayList>(results.size()); int index = 0; @@ -1776,6 +1789,27 @@ public CloseableIterator doInCollection(MongoCollection collection) }); } + @Override + public FindOperationBuilder query(Class domainType) { + return new ExecutableFindOperationSupport(this).find(domainType); + } + + @Override + public UpdateOperationBuilder update(Class domainType) { + return new ExecutableUpdateOperationSupport(this).update(domainType); + } + + @Override + public RemoveOperationBuilder remove(Class domainType) { + return new ExecutableRemoveOperationSupport(this).remove(domainType); + } + + @Override + public ExecutableAggregationOperationBuilder.AggregationOperationBuilder aggregateAndReturn( + Class domainType) { + return new ExecutableAggregationOperationSupport(this).aggregateAndReturn(domainType); + } + /** * Assert that the {@link Document} does not enable Aggregation explain mode. * @@ -1958,6 +1992,38 @@ protected List doFind(String collectionName, Document query, Document 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. + * + * @param collectionName + * @param query + * @param fields + * @param sourceClass + * @param targetClass + * @param objectCallback + * @param + * @param + * @return + * @since 2.0 + */ + List doFind(String collectionName, Document query, Document fields, Class sourceClass, + Class targetClass, CursorPreparer 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 Document convertToDocument(CollectionOptions collectionOptions) { Document document = new Document(); @@ -2674,4 +2740,5 @@ public void close() { public MongoDbFactory getMongoDbFactory() { return mongoDbFactory; } + } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ExecutableAggregationOperationSupportUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ExecutableAggregationOperationSupportUnitTests.java new file mode 100644 index 0000000000..1a01e44523 --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ExecutableAggregationOperationSupportUnitTests.java @@ -0,0 +1,152 @@ +/* + * 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; + +/** + * @author Christoph Strobl + */ +@RunWith(MockitoJUnitRunner.class) +public class ExecutableAggregationOperationSupportUnitTests { + + @Mock MongoTemplate template; + ExecutableAggregationOperationSupport opSupport; + + @Before + public void setUp() { + opSupport = new ExecutableAggregationOperationSupport(template); + } + + @Test(expected = IllegalArgumentException.class) // DATAMONGO-1563 + public void throwsExceptionOnNullDomainType() { + opSupport.aggregateAndReturn(null); + } + + @Test(expected = IllegalArgumentException.class) // DATAMONGO-1563 + public void throwsExceptionOnNullCollectionWhenUsed() { + opSupport.aggregateAndReturn(Person.class).inCollection(null); + } + + @Test(expected = IllegalArgumentException.class) // DATAMONGO-1563 + public void throwsExceptionOnEmptyCollectionWhenUsed() { + opSupport.aggregateAndReturn(Person.class).inCollection(""); + } + + @Test(expected = IllegalArgumentException.class) // DATAMONGO-1563 + public void throwsExceptionOnNullAggregation() { + opSupport.aggregateAndReturn(Person.class).by(null); + } + + @Test // DATAMONGO-1563 + public void aggregateWithUntypedAggregationAndExplicitCollection() { + + opSupport.aggregateAndReturn(Person.class).inCollection("star-wars").by(newAggregation(project("foo"))).get(); + + 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-1563 + public void aggregateWithUntypedAggregation() { + + when(template.determineCollectionName(any(Class.class))).thenReturn("person"); + + opSupport.aggregateAndReturn(Person.class).by(newAggregation(project("foo"))).get(); + + 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-1563 + public void aggregateWithTypeAggregation() { + + when(template.determineCollectionName(any(Class.class))).thenReturn("person"); + + opSupport.aggregateAndReturn(Jedi.class).by(newAggregation(Person.class, project("foo"))).get(); + + 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); + } + + @Test // DATAMONGO-1563 + public void aggregateStreamWithUntypedAggregationAndExplicitCollection() { + + opSupport.aggregateAndReturn(Person.class).inCollection("star-wars").by(newAggregation(project("foo"))).stream(); + + ArgumentCaptor captor = ArgumentCaptor.forClass(Class.class); + verify(template).aggregateStream(any(Aggregation.class), eq("star-wars"), captor.capture()); + assertThat(captor.getValue()).isEqualTo(Person.class); + } + + @Test // DATAMONGO-1563 + public void aggregateStreamWithUntypedAggregation() { + + when(template.determineCollectionName(any(Class.class))).thenReturn("person"); + + opSupport.aggregateAndReturn(Person.class).by(newAggregation(project("foo"))).stream(); + + ArgumentCaptor captor = ArgumentCaptor.forClass(Class.class); + + verify(template).determineCollectionName(captor.capture()); + verify(template).aggregateStream(any(Aggregation.class), eq("person"), captor.capture()); + + assertThat(captor.getAllValues()).containsExactly(Person.class, Person.class); + } + + @Test // DATAMONGO-1563 + public void aggregateStreamWithTypeAggregation() { + + when(template.determineCollectionName(any(Class.class))).thenReturn("person"); + + opSupport.aggregateAndReturn(Jedi.class).by(newAggregation(Person.class, project("foo"))).stream(); + + ArgumentCaptor captor = ArgumentCaptor.forClass(Class.class); + + verify(template).determineCollectionName(captor.capture()); + verify(template).aggregateStream(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/ExecutableFindOperationSupportTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ExecutableFindOperationSupportTests.java new file mode 100644 index 0000000000..151a829a88 --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ExecutableFindOperationSupportTests.java @@ -0,0 +1,242 @@ +/* + * 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 org.junit.Before; +import org.junit.Test; +import org.springframework.dao.IncorrectResultSizeDataAccessException; +import org.springframework.data.annotation.Id; +import org.springframework.data.geo.GeoResults; +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.NearQuery; +import org.springframework.data.util.CloseableIterator; + +import com.mongodb.MongoClient; + +/** + * @author Christoph Strobl + */ +public class ExecutableFindOperationSupportTests { + + private static final String STAR_WARS = "star-wars"; + MongoTemplate template; + + Person han; + Person luke; + + @Before + public void setUp() { + + template = new MongoTemplate(new SimpleMongoDbFactory(new MongoClient(), "ExecutableFindOperationSupportTests")); + template.dropCollection(STAR_WARS); + + han = new Person(); + han.firstname = "han"; + han.id = "id-1"; + + luke = new Person(); + luke.firstname = "luke"; + luke.id = "id-2"; + + template.save(han); + template.save(luke); + } + + @Test(expected = IllegalArgumentException.class) // DATAMONGO-1563 + public void domainTypeIsRequired() { + template.query(null); + } + + @Test(expected = IllegalArgumentException.class) // DATAMONGO-1563 + public void returnTypeIsRequiredOnSet() { + template.query(Person.class).as(null); + } + + @Test(expected = IllegalArgumentException.class) // DATAMONGO-1563 + public void collectionIsRequiredOnSet() { + template.query(Person.class).inCollection(null); + } + + @Test // DATAMONGO-1563 + public void findAll() { + assertThat(template.query(Person.class).all()).containsExactlyInAnyOrder(han, luke); + } + + @Test // DATAMONGO-1563 + public void findAllWithCollection() { + assertThat(template.query(Human.class).inCollection(STAR_WARS).all()).hasSize(2); + } + + @Test // DATAMONGO-1563 + public void findAllWithProjection() { + + assertThat(template.query(Person.class).as(Jedi.class).all()).hasOnlyElementsOfType(Jedi.class) + .hasSize(2); + } + + @Test // DATAMONGO-1563 + public void findAllBy() { + + assertThat(template.query(Person.class).matching(query(where("firstname").is("luke"))).all()) + .containsExactlyInAnyOrder(luke); + } + + @Test // DATAMONGO-1563 + public void findAllByWithCollectionUsingMappingInformation() { + + assertThat(template.query(Jedi.class).inCollection(STAR_WARS).matching(query(where("name").is("luke"))).all()).hasSize(1) + .hasOnlyElementsOfType(Jedi.class); + } + + @Test // DATAMONGO-1563 + public void findAllByWithCollection() { + assertThat(template.query(Human.class).inCollection(STAR_WARS).matching(query(where("firstname").is("luke"))).all()) + .hasSize(1); + } + + @Test // DATAMONGO-1563 + public void findAllByWithProjection() { + + assertThat(template.query(Person.class).as(Jedi.class).matching(query(where("firstname").is("luke"))).all()) + .hasOnlyElementsOfType(Jedi.class).hasSize(1); + } + + @Test // DATAMONGO-1563 + public void findBy() { + assertThat(template.query(Person.class).matching(query(where("firstname").is("luke"))).one()).isEqualTo(luke); + } + + @Test // DATAMONGO-1563 + public void findByNoMatch() { + assertThat(template.query(Person.class).matching(query(where("firstname").is("spock"))).one()).isNull(); + } + + @Test(expected = IncorrectResultSizeDataAccessException.class) // DATAMONGO-1563 + public void findByTooManyResults() { + template.query(Person.class).matching(query(where("firstname").in("han", "luke"))).one(); + } + + @Test // DATAMONGO-1563 + public void streamAll() { + + try (CloseableIterator stream = template.query(Person.class).stream()) { + assertThat(stream).containsExactlyInAnyOrder(han, luke); + } + } + + @Test // DATAMONGO-1563 + public void streamAllWithCollection() { + + try (CloseableIterator stream = template.query(Human.class).inCollection(STAR_WARS).stream()) { + assertThat(stream).hasSize(2); + } + } + + @Test // DATAMONGO-1563 + public void streamAllWithProjection() { + + try (CloseableIterator stream = template.query(Person.class).as(Jedi.class).stream()) { + assertThat(stream).hasOnlyElementsOfType(Jedi.class).hasSize(2); + } + } + + @Test // DATAMONGO-1563 + public void streamAllBy() { + + try (CloseableIterator stream = template.query(Person.class) + .matching(query(where("firstname").is("luke"))).stream()) { + + assertThat(stream).containsExactlyInAnyOrder(luke); + } + } + + @Test // DATAMONGO-1563 + public void findAllNearBy() { + + template.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)); + + template.save(alderan); + template.save(dantooine); + + GeoResults results = template.query(Planet.class) + .near(NearQuery.near(-73.9667, 40.78).spherical(true)).all(); + assertThat(results.getContent()).hasSize(2); + assertThat(results.getContent().get(0).getDistance()).isNotNull(); + } + + @Test // DATAMONGO-1563 + public void findAllNearByWithCollectionAndProjection() { + + template.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)); + + template.save(alderan); + template.save(dantooine); + + GeoResults results = template.query(Object.class).inCollection(STAR_WARS).as(Human.class) + .near(NearQuery.near(-73.9667, 40.78).spherical(true)).all(); + + assertThat(results.getContent()).hasSize(2); + assertThat(results.getContent().get(0).getDistance()).isNotNull(); + assertThat(results.getContent().get(0).getContent()).isInstanceOf(Human.class); + assertThat(results.getContent().get(0).getContent().getId()).isEqualTo("alderan"); + } + + @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/ExecutableRemoveOperationSupportTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ExecutableRemoveOperationSupportTests.java new file mode 100644 index 0000000000..f1a486f8d3 --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ExecutableRemoveOperationSupportTests.java @@ -0,0 +1,108 @@ +/* + * 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 java.util.List; + +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.client.result.DeleteResult; + +/** + * @author Christoph Strobl + */ +public class ExecutableRemoveOperationSupportTests { + + private static final String STAR_WARS = "star-wars"; + MongoTemplate template; + + Person han; + Person luke; + + @Before + public void setUp() { + + template = new MongoTemplate(new SimpleMongoDbFactory(new MongoClient(), "ExecutableRemoveOperationSupportTests")); + template.dropCollection(STAR_WARS); + + han = new Person(); + han.firstname = "han"; + han.id = "id-1"; + + luke = new Person(); + luke.firstname = "luke"; + luke.id = "id-2"; + + template.save(han); + template.save(luke); + } + + @Test // DATAMONGO-1563 + public void removeAll() { + + DeleteResult result = template.remove(Person.class).all(); + + assertThat(result.getDeletedCount()).isEqualTo(2L); + } + + @Test // DATAMONGO-1563 + public void removeAllMatching() { + + DeleteResult result = template.remove(Person.class).matching(query(where("firstname").is("han"))).all(); + + assertThat(result.getDeletedCount()).isEqualTo(1L); + } + + @Test // DATAMONGO-1563 + public void removeAllMatchingWithAlternateDomainTypeAndCollection() { + + DeleteResult result = template.remove(Jedi.class).inCollection(STAR_WARS).matching(query(where("name").is("luke"))) + .all(); + + assertThat(result.getDeletedCount()).isEqualTo(1L); + } + + @Test // DATAMONGO-1563 + public void removeAndReturnAllMatching() { + + List result = template.remove(Person.class).matching(query(where("firstname").is("han"))).allAndReturn(); + + assertThat(result).containsExactly(han); + } + + @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/ExecutableUpdateOperationSupportTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ExecutableUpdateOperationSupportTests.java new file mode 100644 index 0000000000..bef093456c --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ExecutableUpdateOperationSupportTests.java @@ -0,0 +1,187 @@ +/* + * 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 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.client.result.UpdateResult; + +/** + * @author Christoph Strobl + */ +public class ExecutableUpdateOperationSupportTests { + + private static final String STAR_WARS = "star-wars"; + MongoTemplate template; + + Person han; + Person luke; + + @Before + public void setUp() { + + template = new MongoTemplate(new SimpleMongoDbFactory(new MongoClient(), "ExecutableUpdateOperationSupportTests")); + template.dropCollection(STAR_WARS); + + han = new Person(); + han.firstname = "han"; + han.id = "id-1"; + + luke = new Person(); + luke.firstname = "luke"; + luke.id = "id-2"; + + template.save(han); + template.save(luke); + } + + @Test(expected = IllegalArgumentException.class) // DATAMONGO-1563 + public void domainTypeIsRequired() { + template.update(null); + } + + @Test(expected = IllegalArgumentException.class) // DATAMONGO-1563 + public void updateIsRequired() { + template.update(Person.class).apply(null); + } + + @Test(expected = IllegalArgumentException.class) // DATAMONGO-1563 + public void collectionIsRequiredOnSet() { + template.update(Person.class).apply(new Update()).inCollection(null); + } + + @Test(expected = IllegalArgumentException.class) // DATAMONGO-1563 + public void findAndModifyOptionsAreRequiredOnSet() { + template.update(Person.class).apply(new Update()).withOptions(null); + } + + @Test // DATAMONGO-1563 + public void updateFirst() { + + UpdateResult result = template.update(Person.class).apply(new Update().set("firstname", "Han")).first(); + + assertThat(result.getModifiedCount()).isEqualTo(1L); + assertThat(result.getUpsertedId()).isNull(); + } + + @Test // DATAMONGO-1563 + public void updateAll() { + + UpdateResult result = template.update(Person.class).apply(new Update().set("firstname", "Han")).all(); + + assertThat(result.getModifiedCount()).isEqualTo(2L); + assertThat(result.getUpsertedId()).isNull(); + } + + @Test // DATAMONGO-1563 + public void updateAllMatching() { + + UpdateResult result = template.update(Person.class).apply(new Update().set("firstname", "Han")) + .matching(queryHan()).all(); + + assertThat(result.getModifiedCount()).isEqualTo(1L); + assertThat(result.getUpsertedId()).isNull(); + } + + @Test // DATAMONGO-1563 + public void updateWithDifferentDomainClassAndCollection() { + + UpdateResult result = template.update(Jedi.class).apply(new Update().set("name", "Han")).inCollection(STAR_WARS) + .matching(query(where("_id").is(han.getId()))).all(); + + assertThat(result.getModifiedCount()).isEqualTo(1L); + assertThat(result.getUpsertedId()).isNull(); + assertThat(template.findOne(queryHan(), Person.class)).isNotEqualTo(han).hasFieldOrPropertyWithValue("firstname", + "Han"); + } + + @Test // DATAMONGO-1563 + public void findAndModify() { + + Person result = template.update(Person.class).apply(new Update().set("firstname", "Han")) + .matching(queryHan()).findAndModify(); + + assertThat(result).isEqualTo(han); + assertThat(template.findOne(queryHan(), Person.class)).isNotEqualTo(han).hasFieldOrPropertyWithValue("firstname", + "Han"); + } + + @Test // DATAMONGO-1563 + public void findAndModifyWithDifferentDomainTypeAndCollection() { + + Jedi result = template.update(Jedi.class).apply(new Update().set("name", "Han")).inCollection(STAR_WARS) + .matching(query(where("_id").is(han.getId()))).findAndModify(); + + assertThat(result).hasFieldOrPropertyWithValue("name", "han"); + assertThat(template.findOne(queryHan(), Person.class)).isNotEqualTo(han).hasFieldOrPropertyWithValue("firstname", + "Han"); + } + + @Test // DATAMONGO-1563 + public void findAndModifyWithOptions() { + + Person result = template.update(Person.class).apply(new Update().set("firstname", "Han")) + .withOptions(FindAndModifyOptions.options().returnNew(true)).matching(queryHan()).findAndModify(); + + assertThat(result).isNotEqualTo(han).hasFieldOrPropertyWithValue("firstname", "Han"); + } + + @Test // DATAMONGO-1563 + public void upsert() { + + UpdateResult result = template.update(Person.class).apply(new Update().set("firstname", "Chewbacca")) + .matching(query(where("id").is("id-3"))).upsert(); + + assertThat(result.getModifiedCount()).isEqualTo(0L); + assertThat(result.getUpsertedId()).isEqualTo(new BsonString("id-3")); + } + + 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/java/org/springframework/data/mongodb/core/MongoTemplateTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateTests.java index 5cb6c43df2..bbe47bfe6b 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateTests.java @@ -1159,7 +1159,7 @@ public Object doInCollection(MongoCollection collection) @Test // DATADOC-166 public void removingNullIsANoOp() { - template.remove(null); + template.remove((Object) null); } @Test // DATADOC-240, DATADOC-212 From 787095f3a9e6e4541d09191c0c2461b7f4423525 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Thu, 8 Jun 2017 14:23:33 +0200 Subject: [PATCH 3/5] DATAMONGO-1563 - First round of polishing. Javadoc, rename RemoveOperationBuilderTerminatingOperations methods to DeleteResult remove() and List findAndRemove (was: all/allAndReturn). Add missing diamonds where necessary. Use lombok where appropriate. --- ...ExecutableAggregationOperationBuilder.java | 11 +++----- ...ExecutableAggregationOperationSupport.java | 20 +++++--------- .../core/ExecutableFindOperationBuilder.java | 25 ++++++++--------- .../core/ExecutableFindOperationSupport.java | 23 ++++++---------- .../ExecutableRemoveOperationBuilder.java | 27 +++++++++---------- .../ExecutableRemoveOperationSupport.java | 25 +++++++---------- .../ExecutableUpdateOperationBuilder.java | 19 ++++++------- .../ExecutableUpdateOperationSupport.java | 18 ++++--------- .../mongodb/core/FluentMongoOperations.java | 8 ++---- ...ExecutableRemoveOperationSupportTests.java | 8 +++--- 10 files changed, 71 insertions(+), 113 deletions(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableAggregationOperationBuilder.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableAggregationOperationBuilder.java index dbad4a12a4..6f2868f86b 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableAggregationOperationBuilder.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableAggregationOperationBuilder.java @@ -31,11 +31,10 @@ public interface ExecutableAggregationOperationBuilder { * input type for he aggregation. * * @param domainType must not be {@literal null}. - * @param * @return * @throws IllegalArgumentException if domainType is {@literal null}. */ - public AggregationOperationBuilder aggregateAndReturn(Class domainType); + AggregationOperationBuilder aggregateAndReturn(Class domainType); /** * Collection override (Optional). @@ -47,8 +46,8 @@ public interface ExecutableAggregationOperationBuilder { interface WithCollectionBuilder { /** - * [optional] Explicitly set the name of the collection to perform the query on.
- * Just skip this step to use the default collection derived from the domain type. + * 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 @@ -104,7 +103,5 @@ interface WithAggregationBuilder { * @author Christoph Strobl * @since 2.0 */ - interface AggregationOperationBuilder extends WithCollectionBuilder, WithAggregationBuilder { - - } + interface AggregationOperationBuilder extends WithCollectionBuilder, WithAggregationBuilder {} } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableAggregationOperationSupport.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableAggregationOperationSupport.java index ab38d53ab6..f6d8485bf3 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableAggregationOperationSupport.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableAggregationOperationSupport.java @@ -15,6 +15,7 @@ */ package org.springframework.data.mongodb.core; +import lombok.RequiredArgsConstructor; import org.springframework.data.mongodb.core.aggregation.Aggregation; import org.springframework.data.mongodb.core.aggregation.AggregationResults; import org.springframework.data.mongodb.core.aggregation.TypedAggregation; @@ -38,7 +39,7 @@ class ExecutableAggregationOperationSupport implements ExecutableAggregationOper * @param template must not be {@literal null}. * @throws IllegalArgumentException if template is {@literal null}. */ - public ExecutableAggregationOperationSupport(MongoTemplate template) { + ExecutableAggregationOperationSupport(MongoTemplate template) { Assert.notNull(template, "Template must not be null!"); this.template = template; @@ -48,7 +49,7 @@ public ExecutableAggregationOperationSupport(MongoTemplate template) { public AggregationOperationBuilder aggregateAndReturn(Class domainType) { Assert.notNull(domainType, "DomainType must not be null!"); - return new AggreationBuilder(template, null, domainType, null); + return new AggregationBuilder(template, null, domainType, null); } /** @@ -56,7 +57,8 @@ public AggregationOperationBuilder aggregateAndReturn(Class domainType * @author Christoph Strobl * @since 2.0 */ - static class AggreationBuilder implements WithAggregationBuilder, AggregationOperationBuilder, + @RequiredArgsConstructor + static class AggregationBuilder implements WithAggregationBuilder, AggregationOperationBuilder, AggregateOperationBuilderTerminatingOperations { private final MongoTemplate template; @@ -64,26 +66,18 @@ static class AggreationBuilder implements WithAggregationBuilder, Aggregat private final Class domainType; private final String collection; - public AggreationBuilder(MongoTemplate template, Aggregation aggregation, Class domainType, String collection) { - - this.template = template; - this.aggregation = aggregation; - this.domainType = domainType; - this.collection = collection; - } - @Override public WithAggregationBuilder inCollection(String collection) { Assert.hasText(collection, "Collection must not be null nor empty!"); - return new AggreationBuilder(template, aggregation, domainType, collection); + return new AggregationBuilder(template, aggregation, domainType, collection); } @Override public AggregateOperationBuilderTerminatingOperations by(Aggregation aggregation) { Assert.notNull(aggregation, "Aggregation must not be null!"); - return new AggreationBuilder(template, aggregation, domainType, collection); + return new AggregationBuilder(template, aggregation, domainType, collection); } @Override diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableFindOperationBuilder.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableFindOperationBuilder.java index e3cdf8c450..41fa0de344 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableFindOperationBuilder.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableFindOperationBuilder.java @@ -29,14 +29,13 @@ public interface ExecutableFindOperationBuilder { /** - * Start creating a find operation for the given {@literal domainType}.
+ * Start creating a find operation for the given {@literal domainType}. * * @param domainType must not be {@literal null}. - * @param * @return * @throws IllegalArgumentException if domainType is {@literal null}. */ - public FindOperationBuilder find(Class domainType); + FindOperationBuilder find(Class domainType); /** * Trigger find execution by calling one of the terminating methods. @@ -105,7 +104,7 @@ interface FindOperationBuilderTerminatingNearOperations { interface WithQueryBuilder extends FindOperationBuilderTerminatingOperations { /** - * [optional] Set the filter query to be used. + * Set the filter query to be used. * * @param query must not be {@literal null}. * @return @@ -114,7 +113,7 @@ interface WithQueryBuilder extends FindOperationBuilderTerminatingOperations< FindOperationBuilderTerminatingOperations matching(Query query); /** - * [optional] Set the filter query for the geoNear execution. + * Set the filter query for the geoNear execution. * * @param nearQuery must not be {@literal null}. * @return @@ -134,8 +133,8 @@ interface WithQueryBuilder extends FindOperationBuilderTerminatingOperations< interface WithCollectionBuilder extends WithQueryBuilder { /** - * [optional] Explicitly set the name of the collection to perform the query on.
- * Just skip this step to use the default collection derived from the domain type. + * 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 @@ -154,15 +153,15 @@ interface WithCollectionBuilder extends WithQueryBuilder { interface WithProjectionBuilder extends WithQueryBuilder { /** - * [optional] Define the target type fields should be mapped to.
- * Just skip this step if you are anyway only interested in the original domain type. + * 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 + * @param result type. * @return * @throws IllegalArgumentException if resultType is {@literal null}. */ - WithQueryBuilder as(Class resultType); + WithQueryBuilder as(Class resultType); } /** @@ -170,7 +169,5 @@ interface WithProjectionBuilder extends WithQueryBuilder { * @author Christoph Strobl * @since 2.0 */ - interface FindOperationBuilder extends WithCollectionBuilder, WithProjectionBuilder { - - } + interface FindOperationBuilder extends WithCollectionBuilder, WithProjectionBuilder {} } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableFindOperationSupport.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableFindOperationSupport.java index f56bb453f6..ecc45b7dd8 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableFindOperationSupport.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableFindOperationSupport.java @@ -15,6 +15,8 @@ */ package org.springframework.data.mongodb.core; +import lombok.RequiredArgsConstructor; + import java.util.List; import java.util.Optional; @@ -57,7 +59,7 @@ public FindOperationBuilder find(Class domainType) { Assert.notNull(domainType, "DomainType must not be null!"); - return new FindBuilder<>(template, null, domainType, null, domainType); + return new FindBuilder<>(template, domainType, domainType, null, null); } /** @@ -65,6 +67,7 @@ public FindOperationBuilder find(Class domainType) { * @author Christoph Strobl * @since 2.0 */ + @RequiredArgsConstructor static class FindBuilder implements FindOperationBuilder, WithCollectionBuilder, WithProjectionBuilder, WithQueryBuilder { @@ -74,22 +77,12 @@ static class FindBuilder private final String collection; private final Query query; - private FindBuilder(MongoTemplate template, Query query, Class domainType, String collection, - Class returnType) { - - this.template = template; - this.query = query; - this.returnType = returnType; - this.domainType = domainType; - this.collection = collection; - } - @Override public WithProjectionBuilder inCollection(String collection) { Assert.hasText(collection, "Collection name must not be null nor empty!"); - return new FindBuilder<>(template, query, domainType, collection, returnType); + return new FindBuilder<>(template, domainType, returnType, collection, query); } @Override @@ -97,7 +90,7 @@ public WithQueryBuilder as(Class returnType) { Assert.notNull(returnType, "ReturnType must not be null!"); - return new FindBuilder<>(template, query, domainType, collection, returnType); + return new FindBuilder<>(template, domainType, returnType, collection, query); } public String asString() { @@ -142,11 +135,11 @@ public FindOperationBuilderTerminatingOperations matching(Query query) { Assert.notNull(query, "Query must not be null!"); - return new FindBuilder<>(template, query, domainType, collection, returnType); + return new FindBuilder<>(template, domainType, returnType, collection, query); } @Override - public FindOperationBuilderTerminatingNearOperations near(NearQuery nearQuery) { + public FindOperationBuilderTerminatingNearOperations near(NearQuery nearQuery) { return () -> template.geoNear(nearQuery, domainType, getCollectionName(), returnType); } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableRemoveOperationBuilder.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableRemoveOperationBuilder.java index 791542adcc..9e3db5524a 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableRemoveOperationBuilder.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableRemoveOperationBuilder.java @@ -28,10 +28,9 @@ public interface ExecutableRemoveOperationBuilder { /** - * Start creating a rempve operation for the given {@literal domainType}. + * Start creating a remove operation for the given {@literal domainType}. * * @param domainType must not be {@literal null}. - * @param * @return * @throws IllegalArgumentException if domainType is {@literal null}. */ @@ -44,17 +43,17 @@ public interface ExecutableRemoveOperationBuilder { * @author Christoph Strobl * @since 2.0 */ - interface WithCollectionBuilder extends WithQueryBuilder { + interface WithCollectionBuilder extends WithQueryBuilder { /** - * [optional] Explicitly set the name of the collection to perform the query on.
- * Just skip this step to use the default collection derived from the domain type. + * 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 * @throws IllegalArgumentException if domainType is {@literal null}. */ - WithQueryBuilder inCollection(String collection); + WithQueryBuilder inCollection(String collection); } /** @@ -69,17 +68,17 @@ interface RemoveOperationBuilderTerminatingOperations { * * @return */ - DeleteResult all(); + DeleteResult remove(); /** - * Remove and return all documents matching.
+ * 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. + * Also, {@link org.springframework.context.ApplicationEvent}s will be published for each and every delete + * operation. * - * @param filter must not be {@literal null}. * @return empty {@link List} if no match found. Never {@literal null}. */ - List allAndReturn(); + List findAndRemove(); } /** @@ -90,7 +89,7 @@ interface RemoveOperationBuilderTerminatingOperations { interface WithQueryBuilder extends RemoveOperationBuilderTerminatingOperations { /** - * [optional] Define the query filtering elements. + * Define the query filtering elements. * * @param query must not be {@literal null}. * @return @@ -104,7 +103,5 @@ interface WithQueryBuilder extends RemoveOperationBuilderTerminatingOperation * @author Christoph Strobl * @since 2.0 */ - interface RemoveOperationBuilder extends WithCollectionBuilder { - - } + interface RemoveOperationBuilder extends WithCollectionBuilder {} } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableRemoveOperationSupport.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableRemoveOperationSupport.java index 993ae7a388..6dc51026a6 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableRemoveOperationSupport.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableRemoveOperationSupport.java @@ -15,6 +15,8 @@ */ package org.springframework.data.mongodb.core; +import lombok.RequiredArgsConstructor; + import java.util.List; import org.bson.Document; @@ -51,7 +53,7 @@ class ExecutableRemoveOperationSupport implements ExecutableRemoveOperationBuild public RemoveOperationBuilder remove(Class domainType) { Assert.notNull(domainType, "DomainType must not be null!"); - return new RemoveBuilder(tempate, null, domainType, null); + return new RemoveBuilder<>(tempate, null, domainType, null); } /** @@ -59,6 +61,7 @@ public RemoveOperationBuilder remove(Class domainType) { * @author Christoph Strobl * @since 2.0 */ + @RequiredArgsConstructor static class RemoveBuilder implements RemoveOperationBuilder, WithCollectionBuilder { private final MongoTemplate template; @@ -66,30 +69,22 @@ static class RemoveBuilder implements RemoveOperationBuilder, WithCollecti private final Class domainType; private final String collection; - public RemoveBuilder(MongoTemplate template, Query query, Class domainType, String collection) { - - this.template = template; - this.query = query; - this.domainType = domainType; - this.collection = collection; - } - @Override - public WithQueryBuilder inCollection(String collection) { + public WithQueryBuilder inCollection(String collection) { Assert.hasText(collection, "Collection must not be null nor empty!"); - return new RemoveBuilder(template, query, domainType, collection); + return new RemoveBuilder<>(template, query, domainType, collection); } @Override - public RemoveOperationBuilderTerminatingOperations matching(Query query) { + public RemoveOperationBuilderTerminatingOperations matching(Query query) { Assert.notNull(query, "Query must not be null!"); - return new RemoveBuilder(template, query, domainType, collection); + return new RemoveBuilder<>(template, query, domainType, collection); } @Override - public DeleteResult all() { + public DeleteResult remove() { String collectionName = StringUtils.hasText(collection) ? collection : template.determineCollectionName(domainType); @@ -98,7 +93,7 @@ public DeleteResult all() { } @Override - public List allAndReturn() { + public List findAndRemove() { String collectionName = StringUtils.hasText(collection) ? collection : template.determineCollectionName(domainType); diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableUpdateOperationBuilder.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableUpdateOperationBuilder.java index 15599ff918..f3ebd19ba9 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableUpdateOperationBuilder.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableUpdateOperationBuilder.java @@ -30,11 +30,10 @@ public interface ExecutableUpdateOperationBuilder { * Start creating an update operation for the given {@literal domainType}. * * @param domainType must not be {@literal null}. - * @param * @return * @throws IllegalArgumentException if domainType is {@literal null}. */ - public UpdateOperationBuilder update(Class domainType); + UpdateOperationBuilder update(Class domainType); /** * Trigger update execution by calling one of the terminating methods. @@ -93,7 +92,7 @@ interface UpdateOperationBuilderTerminatingFindAndModifyOperations { interface WithQueryBuilder extends WithFindAndModifyBuilder, UpdateOperationBuilderTerminatingOperations { /** - * [optional] Filter documents by given {@literal query}. + * Filter documents by given {@literal query}. * * @param filter must not be {@literal null}. * @return @@ -113,7 +112,7 @@ interface WithQueryBuilder extends WithFindAndModifyBuilder, UpdateOperati interface WithFindAndModifyBuilder { /** - * [optional] Find, modify and return the first matching document. + * Find, modify and return the first matching document. * * @param filter must not be {@literal null}. * @return @@ -130,7 +129,7 @@ interface WithFindAndModifyBuilder { interface UpdateOperationBuilder { /** - * [required] Set the {@link Update} to be applied. + * Set the {@link Update} to be applied. * * @param update must not be {@literal null}. * @return @@ -147,8 +146,8 @@ interface UpdateOperationBuilder { interface WithCollectionBuilder extends WithFindAndModifyOptionsBuilder { /** - * [optional] Explicitly set the name of the collection to perform the query on.
- * Just skip this step to use the default collection derived from the domain type. + * 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 @@ -162,9 +161,7 @@ interface WithCollectionBuilder extends WithFindAndModifyOptionsBuilder { * @author Christoph Strobl * @since 2.0 */ - interface WithOptionsBuilder extends WithCollectionBuilder, WithQueryBuilder { - - } + interface WithOptionsBuilder extends WithCollectionBuilder, WithQueryBuilder {} /** * @param @@ -174,7 +171,7 @@ interface WithOptionsBuilder extends WithCollectionBuilder, WithQueryBuild interface WithFindAndModifyOptionsBuilder { /** - * [optional] Explicitly define {@link FindAndModifyOptions} for the {@link Update}. + * Explicitly define {@link FindAndModifyOptions} for the {@link Update}. * * @param options must not be {@literal null}. * @return diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableUpdateOperationSupport.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableUpdateOperationSupport.java index e52b9167ac..7ccee320ba 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableUpdateOperationSupport.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableUpdateOperationSupport.java @@ -15,6 +15,8 @@ */ package org.springframework.data.mongodb.core; +import lombok.RequiredArgsConstructor; + import org.bson.Document; import org.springframework.data.mongodb.core.query.BasicQuery; import org.springframework.data.mongodb.core.query.Query; @@ -57,6 +59,7 @@ public UpdateOperationBuilder update(Class domainType) { * @author Christoph Strobl * @since 2.0 */ + @RequiredArgsConstructor static class UpdateBuilder implements WithOptionsBuilder, UpdateOperationBuilder, WithCollectionBuilder, WithQueryBuilder { @@ -67,19 +70,8 @@ static class UpdateBuilder private final String collection; private final FindAndModifyOptions options; - private UpdateBuilder(MongoTemplate template, Query query, Class domainType, Update update, String collection, - FindAndModifyOptions options) { - - this.template = template; - this.query = query; - this.domainType = domainType; - this.update = update; - this.collection = collection; - this.options = options; - } - @Override - public WithOptionsBuilder apply(Update update) { + public WithOptionsBuilder apply(Update update) { Assert.notNull(update, "Update must not be null!"); return new UpdateBuilder(template, query, domainType, update, collection, options); @@ -125,7 +117,7 @@ public UpdateResult all() { } @Override - public WithFindAndModifyBuilder withOptions(FindAndModifyOptions options) { + public WithFindAndModifyBuilder withOptions(FindAndModifyOptions options) { Assert.notNull(options, "Options must not be null!"); return new UpdateBuilder(template, query, domainType, update, collection, options); diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/FluentMongoOperations.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/FluentMongoOperations.java index 369a253e0b..c4c8d27075 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/FluentMongoOperations.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/FluentMongoOperations.java @@ -33,8 +33,7 @@ public interface FluentMongoOperations { * Entry point for constructing and executing queries for a given domain type. * * @param domainType must not be {@literal null}. - * @param - * @return new instance of {@link FindExecutionBuilder}. + * @return new instance of {@link FindOperationBuilder}. * @throws IllegalArgumentException if domainType is {@literal null}. * @since 2.0 */ @@ -44,7 +43,6 @@ public interface FluentMongoOperations { * Entry point for constructing and executing updates for a given domain type. * * @param domainType must not be {@literal null}. - * @param * @return new instance of {@link ExecutableUpdateOperationBuilder}. * @throws IllegalArgumentException if domainType is {@literal null}. * @since 2.0 @@ -55,7 +53,6 @@ public interface FluentMongoOperations { * Entry point for constructing and executing deletes for a given domain type. * * @param domainType must not be {@literal null}. - * @param * @return new instance of {@link RemoveOperationBuilder}. * @throws IllegalArgumentException if domainType is {@literal null}. * @since 2.0 @@ -66,8 +63,7 @@ public interface FluentMongoOperations { * Entry point for constructing and executing aggregation operations. * * @param domainType must not be {@literal null}. - * @param - * @return new instance of {@link AggregationOperation}. + * @return new instance of {@link AggregationOperationBuilder}. * @throws IllegalArgumentException if domainType is {@literal null}. * @since 2.0 */ diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ExecutableRemoveOperationSupportTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ExecutableRemoveOperationSupportTests.java index f1a486f8d3..85943cdec2 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ExecutableRemoveOperationSupportTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ExecutableRemoveOperationSupportTests.java @@ -63,7 +63,7 @@ public void setUp() { @Test // DATAMONGO-1563 public void removeAll() { - DeleteResult result = template.remove(Person.class).all(); + DeleteResult result = template.remove(Person.class).remove(); assertThat(result.getDeletedCount()).isEqualTo(2L); } @@ -71,7 +71,7 @@ public void removeAll() { @Test // DATAMONGO-1563 public void removeAllMatching() { - DeleteResult result = template.remove(Person.class).matching(query(where("firstname").is("han"))).all(); + DeleteResult result = template.remove(Person.class).matching(query(where("firstname").is("han"))).remove(); assertThat(result.getDeletedCount()).isEqualTo(1L); } @@ -80,7 +80,7 @@ public void removeAllMatching() { public void removeAllMatchingWithAlternateDomainTypeAndCollection() { DeleteResult result = template.remove(Jedi.class).inCollection(STAR_WARS).matching(query(where("name").is("luke"))) - .all(); + .remove(); assertThat(result.getDeletedCount()).isEqualTo(1L); } @@ -88,7 +88,7 @@ public void removeAllMatchingWithAlternateDomainTypeAndCollection() { @Test // DATAMONGO-1563 public void removeAndReturnAllMatching() { - List result = template.remove(Person.class).matching(query(where("firstname").is("han"))).allAndReturn(); + List result = template.remove(Person.class).matching(query(where("firstname").is("han"))).findAndRemove(); assertThat(result).containsExactly(han); } From d96206e7dece7c7ccf1c2301dac482988c21a924 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Fri, 9 Jun 2017 08:01:10 +0200 Subject: [PATCH 4/5] DATAMONGO-1563 - Rename fluent operation interfaces and update documentation. Get rid of 'Builder' within the fluent API interface names. Roll back method rename all() -> remove() -> all() in TerminatingRemoveOperation and fix position of collection name override in UpdateOperation which lead to a restructured operation flow. --- ...va => ExecutableAggregationOperation.java} | 45 ++++-- ...ExecutableAggregationOperationSupport.java | 21 +-- ...lder.java => ExecutableFindOperation.java} | 68 +++++--- .../core/ExecutableFindOperationSupport.java | 61 ++++--- ...er.java => ExecutableRemoveOperation.java} | 46 ++++-- .../ExecutableRemoveOperationSupport.java | 20 +-- ...er.java => ExecutableUpdateOperation.java} | 151 +++++++++--------- .../ExecutableUpdateOperationSupport.java | 36 +++-- .../mongodb/core/FluentMongoOperations.java | 50 +----- .../data/mongodb/core/MongoTemplate.java | 17 +- .../ExecutableFindOperationSupportTests.java | 4 +- ...ExecutableRemoveOperationSupportTests.java | 6 +- ...ExecutableUpdateOperationSupportTests.java | 35 ++-- 13 files changed, 291 insertions(+), 269 deletions(-) rename spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/{ExecutableAggregationOperationBuilder.java => ExecutableAggregationOperation.java} (59%) rename spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/{ExecutableFindOperationBuilder.java => ExecutableFindOperation.java} (60%) rename spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/{ExecutableRemoveOperationBuilder.java => ExecutableRemoveOperation.java} (57%) rename spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/{ExecutableUpdateOperationBuilder.java => ExecutableUpdateOperation.java} (53%) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableAggregationOperationBuilder.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableAggregationOperation.java similarity index 59% rename from spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableAggregationOperationBuilder.java rename to spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableAggregationOperation.java index 6f2868f86b..2fb319c347 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableAggregationOperationBuilder.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableAggregationOperation.java @@ -20,10 +20,25 @@ import org.springframework.data.util.CloseableIterator; /** + * {@link ExecutableAggregationOperation} allows creation and execution of 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")))
+ *             .get();
+ *     
+ * 
+ * * @author Christoph Strobl * @since 2.0 */ -public interface ExecutableAggregationOperationBuilder { +public interface ExecutableAggregationOperation { /** * Start creating an aggregation operation that returns results mapped to the given domain type.
@@ -31,10 +46,10 @@ public interface ExecutableAggregationOperationBuilder { * input type for he aggregation. * * @param domainType must not be {@literal null}. - * @return + * @return new instance of {@link AggregationOperation}. * @throws IllegalArgumentException if domainType is {@literal null}. */ - AggregationOperationBuilder aggregateAndReturn(Class domainType); + AggregationOperation aggregateAndReturn(Class domainType); /** * Collection override (Optional). @@ -43,17 +58,17 @@ public interface ExecutableAggregationOperationBuilder { * @author Christoph Strobl * @since 2.0 */ - interface WithCollectionBuilder { + 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 + * @return new instance of {@link AggregationOperationWithAggregation}. * @throws IllegalArgumentException if collection is {@literal null}. */ - WithAggregationBuilder inCollection(String collection); + AggregationOperationWithAggregation inCollection(String collection); } /** @@ -61,12 +76,12 @@ interface WithCollectionBuilder { * * @param */ - interface AggregateOperationBuilderTerminatingOperations { + interface TerminatingAggregationOperation { /** * Apply pipeline operations as specified. * - * @return + * @return never {@literal null}. */ AggregationResults get(); @@ -74,7 +89,8 @@ interface AggregateOperationBuilderTerminatingOperations { * Apply pipeline operations as specified.
* Returns a {@link CloseableIterator} that wraps the a Mongo DB {@link com.mongodb.Cursor} * - * @return + * @return a {@link CloseableIterator} that wraps the a Mongo DB {@link com.mongodb.Cursor} that needs to be closed. + * Never {@literal null}. */ CloseableIterator stream(); } @@ -86,16 +102,16 @@ interface AggregateOperationBuilderTerminatingOperations { * @author Christoph Strobl * @since 2.0 */ - interface WithAggregationBuilder { + interface AggregationOperationWithAggregation { /** - * [required] Set the aggregation to be used. + * Set the aggregation to be used. * * @param aggregation must not be {@literal null}. - * @return + * @return new instance of {@link TerminatingAggregationOperation}. * @throws IllegalArgumentException if aggregation is {@literal null}. */ - AggregateOperationBuilderTerminatingOperations by(Aggregation aggregation); + TerminatingAggregationOperation by(Aggregation aggregation); } /** @@ -103,5 +119,6 @@ interface WithAggregationBuilder { * @author Christoph Strobl * @since 2.0 */ - interface AggregationOperationBuilder extends WithCollectionBuilder, WithAggregationBuilder {} + interface AggregationOperation + extends AggregationOperationWithCollection, AggregationOperationWithAggregation {} } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableAggregationOperationSupport.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableAggregationOperationSupport.java index f6d8485bf3..c4fb33e335 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableAggregationOperationSupport.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableAggregationOperationSupport.java @@ -16,6 +16,7 @@ package org.springframework.data.mongodb.core; import lombok.RequiredArgsConstructor; + import org.springframework.data.mongodb.core.aggregation.Aggregation; import org.springframework.data.mongodb.core.aggregation.AggregationResults; import org.springframework.data.mongodb.core.aggregation.TypedAggregation; @@ -24,12 +25,12 @@ import org.springframework.util.StringUtils; /** - * Implementation of {@link ExecutableAggregationOperationBuilder} operating directly on {@link MongoTemplate}. + * Implementation of {@link ExecutableAggregationOperation} operating directly on {@link MongoTemplate}. * * @author Christoph Strobl * @since 2.0 */ -class ExecutableAggregationOperationSupport implements ExecutableAggregationOperationBuilder { +class ExecutableAggregationOperationSupport implements ExecutableAggregationOperation { private final MongoTemplate template; @@ -46,10 +47,10 @@ class ExecutableAggregationOperationSupport implements ExecutableAggregationOper } @Override - public AggregationOperationBuilder aggregateAndReturn(Class domainType) { + public AggregationOperation aggregateAndReturn(Class domainType) { Assert.notNull(domainType, "DomainType must not be null!"); - return new AggregationBuilder(template, null, domainType, null); + return new AggregationOperationSupport(template, null, domainType, null); } /** @@ -58,8 +59,8 @@ public AggregationOperationBuilder aggregateAndReturn(Class domainType * @since 2.0 */ @RequiredArgsConstructor - static class AggregationBuilder implements WithAggregationBuilder, AggregationOperationBuilder, - AggregateOperationBuilderTerminatingOperations { + static class AggregationOperationSupport + implements AggregationOperationWithAggregation, AggregationOperation, TerminatingAggregationOperation { private final MongoTemplate template; private final Aggregation aggregation; @@ -67,17 +68,17 @@ static class AggregationBuilder implements WithAggregationBuilder, Aggrega private final String collection; @Override - public WithAggregationBuilder inCollection(String collection) { + public AggregationOperationWithAggregation inCollection(String collection) { Assert.hasText(collection, "Collection must not be null nor empty!"); - return new AggregationBuilder(template, aggregation, domainType, collection); + return new AggregationOperationSupport(template, aggregation, domainType, collection); } @Override - public AggregateOperationBuilderTerminatingOperations by(Aggregation aggregation) { + public TerminatingAggregationOperation by(Aggregation aggregation) { Assert.notNull(aggregation, "Aggregation must not be null!"); - return new AggregationBuilder(template, aggregation, domainType, collection); + return new AggregationOperationSupport(template, aggregation, domainType, collection); } @Override diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableFindOperationBuilder.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableFindOperation.java similarity index 60% rename from spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableFindOperationBuilder.java rename to spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableFindOperation.java index 41fa0de344..f218c4abb6 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableFindOperationBuilder.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableFindOperation.java @@ -16,6 +16,7 @@ package org.springframework.data.mongodb.core; import java.util.List; +import java.util.Optional; import org.springframework.data.geo.GeoResults; import org.springframework.data.mongodb.core.query.NearQuery; @@ -23,19 +24,39 @@ import org.springframework.data.util.CloseableIterator; /** + * {@link ExecutableFindOperation} allows creation and execution of 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 this 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} that is then used for mapping the result mapping.
+ * 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 Christoph Strobl * @since 2.0 */ -public interface ExecutableFindOperationBuilder { +public interface ExecutableFindOperation { /** * Start creating a find operation for the given {@literal domainType}. * * @param domainType must not be {@literal null}. - * @return + * @return new instance of {@link FindOperation}. * @throws IllegalArgumentException if domainType is {@literal null}. */ - FindOperationBuilder find(Class domainType); + FindOperation query(Class domainType); /** * Trigger find execution by calling one of the terminating methods. @@ -44,22 +65,22 @@ public interface ExecutableFindOperationBuilder { * @author Christoph Strobl * @since 2.0 */ - interface FindOperationBuilderTerminatingOperations { + interface TerminatingFindOperation { /** * Get exactly zero or one result. * - * @return {@literal null} if no match found. + * @return {@link Optional#empty()} if no match found. * @throws org.springframework.dao.IncorrectResultSizeDataAccessException if more than one match found. */ - T one(); + Optional one(); /** * Get the first or no result. * - * @return {@literal null} if no match found. + * @return {@link Optional#empty()} if no match found. */ - T first(); + Optional first(); /** * Get all matching elements. @@ -84,7 +105,7 @@ interface FindOperationBuilderTerminatingOperations { * @author Christoph Strobl * @since 2.0 */ - interface FindOperationBuilderTerminatingNearOperations { + interface TerminatingFindNearOperation { /** * Find all matching elements and return them as {@link org.springframework.data.geo.GeoResult}. @@ -101,26 +122,25 @@ interface FindOperationBuilderTerminatingNearOperations { * @author Christoph Strobl * @since 2.0 */ - interface WithQueryBuilder extends FindOperationBuilderTerminatingOperations { + interface FindOperationWithQuery extends TerminatingFindOperation { /** * Set the filter query to be used. * * @param query must not be {@literal null}. - * @return + * @return new instance of {@link TerminatingFindOperation}. * @throws IllegalArgumentException if query is {@literal null}. */ - FindOperationBuilderTerminatingOperations matching(Query query); + TerminatingFindOperation matching(Query query); /** * Set the filter query for the geoNear execution. * * @param nearQuery must not be {@literal null}. - * @return + * @return new instance of {@link TerminatingFindNearOperation}. * @throws IllegalArgumentException if nearQuery is {@literal null}. */ - FindOperationBuilderTerminatingNearOperations near(NearQuery nearQuery); - + TerminatingFindNearOperation near(NearQuery nearQuery); } /** @@ -130,17 +150,17 @@ interface WithQueryBuilder extends FindOperationBuilderTerminatingOperations< * @author Christoph Strobl * @since 2.0 */ - interface WithCollectionBuilder extends WithQueryBuilder { + interface FindOperationWithCollection extends FindOperationWithQuery { /** * 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 + * @return new instance of {@link FindOperationWithProjection}. * @throws IllegalArgumentException if collection is {@literal null}. */ - WithProjectionBuilder inCollection(String collection); + FindOperationWithProjection inCollection(String collection); } /** @@ -150,7 +170,7 @@ interface WithCollectionBuilder extends WithQueryBuilder { * @author Christoph Strobl * @since 2.0 */ - interface WithProjectionBuilder extends WithQueryBuilder { + interface FindOperationWithProjection extends FindOperationWithQuery { /** * Define the target type fields should be mapped to.
@@ -158,16 +178,20 @@ interface WithProjectionBuilder extends WithQueryBuilder { * * @param resultType must not be {@literal null}. * @param result type. - * @return + * @return new instance of {@link FindOperationWithProjection}. * @throws IllegalArgumentException if resultType is {@literal null}. */ - WithQueryBuilder as(Class resultType); + FindOperationWithQuery as(Class resultType); } /** + * {@link FindOperation} provides methods for constructing lookup operations in a fluent way. + * * @param * @author Christoph Strobl * @since 2.0 */ - interface FindOperationBuilder extends WithCollectionBuilder, WithProjectionBuilder {} + interface FindOperation extends FindOperationWithCollection, FindOperationWithProjection { + + } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableFindOperationSupport.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableFindOperationSupport.java index ecc45b7dd8..4b1943a4b7 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableFindOperationSupport.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableFindOperationSupport.java @@ -25,6 +25,7 @@ import org.springframework.data.mongodb.core.query.BasicQuery; 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.data.util.CloseableIterator; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; @@ -33,12 +34,12 @@ import com.mongodb.client.FindIterable; /** - * Implementation of {@link ExecutableFindOperationBuilder}. + * Implementation of {@link ExecutableFindOperation}. * * @author Christoph Strobl * @since 2.0 */ -class ExecutableFindOperationSupport implements ExecutableFindOperationBuilder { +class ExecutableFindOperationSupport implements ExecutableFindOperation { private final MongoTemplate template; @@ -51,15 +52,16 @@ class ExecutableFindOperationSupport implements ExecutableFindOperationBuilder { ExecutableFindOperationSupport(MongoTemplate template) { Assert.notNull(template, "Template must not be null!"); + this.template = template; } @Override - public FindOperationBuilder find(Class domainType) { + public FindOperation query(Class domainType) { Assert.notNull(domainType, "DomainType must not be null!"); - return new FindBuilder<>(template, domainType, domainType, null, null); + return new FindOperationSupport<>(template, domainType, domainType, null, null); } /** @@ -68,8 +70,8 @@ public FindOperationBuilder find(Class domainType) { * @since 2.0 */ @RequiredArgsConstructor - static class FindBuilder - implements FindOperationBuilder, WithCollectionBuilder, WithProjectionBuilder, WithQueryBuilder { + static class FindOperationSupport implements FindOperation, FindOperationWithCollection, + FindOperationWithProjection, FindOperationWithQuery { private final MongoTemplate template; private final Class domainType; @@ -78,46 +80,50 @@ static class FindBuilder private final Query query; @Override - public WithProjectionBuilder inCollection(String collection) { + public FindOperationWithProjection inCollection(String collection) { Assert.hasText(collection, "Collection name must not be null nor empty!"); - return new FindBuilder<>(template, domainType, returnType, collection, query); + return new FindOperationSupport<>(template, domainType, returnType, collection, query); } @Override - public WithQueryBuilder as(Class returnType) { + public FindOperationWithQuery as(Class returnType) { Assert.notNull(returnType, "ReturnType must not be null!"); - return new FindBuilder<>(template, domainType, returnType, collection, query); + return new FindOperationSupport<>(template, domainType, returnType, collection, query); } - public String asString() { - return ""; + @Override + public TerminatingFindOperation matching(Query query) { + + Assert.notNull(query, "Query must not be null!"); + + return new FindOperationSupport<>(template, domainType, returnType, collection, query); } @Override - public T one() { + public Optional one() { List result = doFind(new DelegatingQueryCursorPreparer(getCursorPreparer(query, null)).limit(2)); if (ObjectUtils.isEmpty(result)) { - return null; + return Optional.empty(); } if (result.size() > 1) { throw new IncorrectResultSizeDataAccessException("Query " + asString() + " returned non unique result.", 1); } - return result.iterator().next(); + return Optional.of(result.iterator().next()); } @Override - public T first() { + public Optional first() { List result = doFind(new DelegatingQueryCursorPreparer(getCursorPreparer(query, null)).limit(1)); - return ObjectUtils.isEmpty(result) ? null : result.iterator().next(); + return ObjectUtils.isEmpty(result) ? Optional.empty() : Optional.of(result.iterator().next()); } @Override @@ -131,15 +137,7 @@ public CloseableIterator stream() { } @Override - public FindOperationBuilderTerminatingOperations matching(Query query) { - - Assert.notNull(query, "Query must not be null!"); - - return new FindBuilder<>(template, domainType, returnType, collection, query); - } - - @Override - public FindOperationBuilderTerminatingNearOperations near(NearQuery nearQuery) { + public TerminatingFindNearOperation near(NearQuery nearQuery) { return () -> template.geoNear(nearQuery, domainType, getCollectionName(), returnType); } @@ -165,6 +163,10 @@ private CursorPreparer getCursorPreparer(Query query, CursorPreparer preparer) { private String getCollectionName() { return StringUtils.hasText(collection) ? collection : template.determineCollectionName(domainType); } + + private String asString() { + return SerializationUtils.serializeToJsonSafely(query); + } } /** @@ -185,12 +187,7 @@ public DelegatingQueryCursorPreparer(CursorPreparer delegate) { public FindIterable prepare(FindIterable cursor) { FindIterable target = delegate.prepare(cursor); - - if (limit.isPresent()) { - target = target.limit(limit.get()); - } - - return target; + return limit.map(it -> target.limit(it)).orElse(target); } CursorPreparer limit(int limit) { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableRemoveOperationBuilder.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableRemoveOperation.java similarity index 57% rename from spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableRemoveOperationBuilder.java rename to spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableRemoveOperation.java index 9e3db5524a..87be5e0f5f 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableRemoveOperationBuilder.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableRemoveOperation.java @@ -22,19 +22,35 @@ import com.mongodb.client.result.DeleteResult; /** + * {@link ExecutableRemoveOperation} allows creation and execution of 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 Christoph Strobl * @since 2.0 */ -public interface ExecutableRemoveOperationBuilder { +public interface ExecutableRemoveOperation { /** * Start creating a remove operation for the given {@literal domainType}. * * @param domainType must not be {@literal null}. - * @return + * @return new instance of {@link RemoveOperation}. * @throws IllegalArgumentException if domainType is {@literal null}. */ - RemoveOperationBuilder remove(Class domainType); + RemoveOperation remove(Class domainType); /** * Collection override (Optional). @@ -43,17 +59,17 @@ public interface ExecutableRemoveOperationBuilder { * @author Christoph Strobl * @since 2.0 */ - interface WithCollectionBuilder extends WithQueryBuilder { + interface RemoveOperationWithCollection extends RemoveOperationWithQuery { /** * 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 - * @throws IllegalArgumentException if domainType is {@literal null}. + * @return new instance of {@link RemoveOperationWithCollection}. + * @throws IllegalArgumentException if collection is {@literal null}. */ - WithQueryBuilder inCollection(String collection); + RemoveOperationWithQuery inCollection(String collection); } /** @@ -61,14 +77,14 @@ interface WithCollectionBuilder extends WithQueryBuilder { * @author Christoph Strobl * @since 2.0 */ - interface RemoveOperationBuilderTerminatingOperations { + interface TerminatingRemoveOperation { /** * Remove all documents matching. * * @return */ - DeleteResult remove(); + DeleteResult all(); /** * Remove and return all matching documents.
@@ -86,16 +102,16 @@ interface RemoveOperationBuilderTerminatingOperations { * @author Christoph Strobl * @since 2.0 */ - interface WithQueryBuilder extends RemoveOperationBuilderTerminatingOperations { + interface RemoveOperationWithQuery extends TerminatingRemoveOperation { /** * Define the query filtering elements. * * @param query must not be {@literal null}. - * @return - * @throws IllegalArgumentException if domainType is {@literal null}. + * @return new instance of {@link TerminatingRemoveOperation}. + * @throws IllegalArgumentException if query is {@literal null}. */ - RemoveOperationBuilderTerminatingOperations matching(Query query); + TerminatingRemoveOperation matching(Query query); } /** @@ -103,5 +119,7 @@ interface WithQueryBuilder extends RemoveOperationBuilderTerminatingOperation * @author Christoph Strobl * @since 2.0 */ - interface RemoveOperationBuilder extends WithCollectionBuilder {} + interface RemoveOperation extends RemoveOperationWithCollection { + + } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableRemoveOperationSupport.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableRemoveOperationSupport.java index 6dc51026a6..bc38852794 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableRemoveOperationSupport.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableRemoveOperationSupport.java @@ -28,12 +28,12 @@ import com.mongodb.client.result.DeleteResult; /** - * Implementation of {@link ExecutableRemoveOperationBuilder}. + * Implementation of {@link ExecutableRemoveOperation}. * * @author Christoph Strobl * @since 2.0 */ -class ExecutableRemoveOperationSupport implements ExecutableRemoveOperationBuilder { +class ExecutableRemoveOperationSupport implements ExecutableRemoveOperation { private final MongoTemplate tempate; @@ -50,10 +50,10 @@ class ExecutableRemoveOperationSupport implements ExecutableRemoveOperationBuild } @Override - public RemoveOperationBuilder remove(Class domainType) { + public RemoveOperation remove(Class domainType) { Assert.notNull(domainType, "DomainType must not be null!"); - return new RemoveBuilder<>(tempate, null, domainType, null); + return new RemoveOperationSupport<>(tempate, null, domainType, null); } /** @@ -62,7 +62,7 @@ public RemoveOperationBuilder remove(Class domainType) { * @since 2.0 */ @RequiredArgsConstructor - static class RemoveBuilder implements RemoveOperationBuilder, WithCollectionBuilder { + static class RemoveOperationSupport implements RemoveOperation, RemoveOperationWithCollection { private final MongoTemplate template; private final Query query; @@ -70,21 +70,21 @@ static class RemoveBuilder implements RemoveOperationBuilder, WithCollecti private final String collection; @Override - public WithQueryBuilder inCollection(String collection) { + public RemoveOperationWithQuery inCollection(String collection) { Assert.hasText(collection, "Collection must not be null nor empty!"); - return new RemoveBuilder<>(template, query, domainType, collection); + return new RemoveOperationSupport<>(template, query, domainType, collection); } @Override - public RemoveOperationBuilderTerminatingOperations matching(Query query) { + public TerminatingRemoveOperation matching(Query query) { Assert.notNull(query, "Query must not be null!"); - return new RemoveBuilder<>(template, query, domainType, collection); + return new RemoveOperationSupport<>(template, query, domainType, collection); } @Override - public DeleteResult remove() { + public DeleteResult all() { String collectionName = StringUtils.hasText(collection) ? collection : template.determineCollectionName(domainType); diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableUpdateOperationBuilder.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableUpdateOperation.java similarity index 53% rename from spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableUpdateOperationBuilder.java rename to spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableUpdateOperation.java index f3ebd19ba9..9d57b02f58 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableUpdateOperationBuilder.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableUpdateOperation.java @@ -15,16 +15,36 @@ */ package org.springframework.data.mongodb.core; +import java.util.Optional; + import org.springframework.data.mongodb.core.query.Query; import org.springframework.data.mongodb.core.query.Update; import com.mongodb.client.result.UpdateResult; /** + * {@link ExecutableUpdateOperation} allows creation and execution of 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 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 Christoph Strobl * @since 2.0 */ -public interface ExecutableUpdateOperationBuilder { +public interface ExecutableUpdateOperation { /** * Start creating an update operation for the given {@literal domainType}. @@ -33,53 +53,50 @@ public interface ExecutableUpdateOperationBuilder { * @return * @throws IllegalArgumentException if domainType is {@literal null}. */ - UpdateOperationBuilder update(Class domainType); + UpdateOperation update(Class domainType); + + interface UpdateOperation + extends UpdateOperationWithCollection, UpdateOperationWithQuery, UpdateOperationWithUpdate { + + } /** - * Trigger update execution by calling one of the terminating methods. + * Declare the {@link Update} to apply. * * @param * @author Christoph Strobl * @since 2.0 */ - interface UpdateOperationBuilderTerminatingOperations - extends UpdateOperationBuilderTerminatingFindAndModifyOperations { - - /** - * Update the first document in the collection. - * - * @return - */ - UpdateResult first(); - - /** - * Creates a new document if no documents match the filter query or updates the matching ones. - * - * @return - */ - UpdateResult upsert(); + interface UpdateOperationWithUpdate { /** - * Update all matching documents in the collection. + * Set the {@link Update} to be applied. * - * @return + * @param update must not be {@literal null}. + * @return new instance of {@link TerminatingUpdateOperation}. + * @throws IllegalArgumentException if update is {@literal null}. */ - UpdateResult all(); + TerminatingUpdateOperation apply(Update update); } /** - * Trigger findAndModify execution by calling one of the terminating methods. + * Explicitly define the name of the collection to perform operation in. * * @param + * @author Christoph Strobl + * @since 2.0 */ - interface UpdateOperationBuilderTerminatingFindAndModifyOperations { + interface UpdateOperationWithCollection { /** - * Find, modify and return the first matching document. + * 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. * - * @return + * @param collection must not be {@literal null} nor {@literal empty}. + * @return new instance of {@link UpdateOperationWithCollection}. + * @throws IllegalArgumentException if collection is {@literal null}. */ - T findAndModify(); + UpdateOperationWithQuery inCollection(String collection); } /** @@ -89,94 +106,80 @@ interface UpdateOperationBuilderTerminatingFindAndModifyOperations { * @author Christoph Strobl * @since 2.0 */ - interface WithQueryBuilder extends WithFindAndModifyBuilder, UpdateOperationBuilderTerminatingOperations { + interface UpdateOperationWithQuery extends UpdateOperationWithUpdate { /** * Filter documents by given {@literal query}. * - * @param filter must not be {@literal null}. - * @return - * @throws IllegalArgumentException if filter is {@literal null}. + * @param query must not be {@literal null}. + * @return new instance of {@link UpdateOperationWithQuery}. + * @throws IllegalArgumentException if query is {@literal null}. */ - UpdateOperationBuilderTerminatingOperations matching(Query filter); - + UpdateOperationWithUpdate matching(Query query); } /** - * Define a filter query for the {@link Update} used for {@literal findAndModify}. + * Define {@link FindAndModifyOptions}. * * @param * @author Christoph Strobl * @since 2.0 */ - interface WithFindAndModifyBuilder { + interface FindAndModifyWithOptions { /** - * Find, modify and return the first matching document. + * Explicitly define {@link FindAndModifyOptions} for the {@link Update}. * - * @param filter must not be {@literal null}. - * @return - * @throws IllegalArgumentException if filter is {@literal null}. + * @param options must not be {@literal null}. + * @return new instance of {@link FindAndModifyWithOptions}. + * @throws IllegalArgumentException if options is {@literal null}. */ - UpdateOperationBuilderTerminatingFindAndModifyOperations matching(Query filter); + TerminatingFindAndModifyOperation withOptions(FindAndModifyOptions options); } /** + * Trigger findAndModify execution by calling one of the terminating methods. + * * @param - * @author Christoph Strobl - * @since 2.0 */ - interface UpdateOperationBuilder { + interface TerminatingFindAndModifyOperation { /** - * Set the {@link Update} to be applied. + * Find, modify and return the first matching document. * - * @param update must not be {@literal null}. - * @return - * @throws IllegalArgumentException if update is {@literal null}. + * @return {@link Optional#empty()} if nothing found. */ - WithOptionsBuilder apply(Update update); + Optional findAndModify(); } /** + * Trigger update execution by calling one of the terminating methods. + * * @param * @author Christoph Strobl * @since 2.0 */ - interface WithCollectionBuilder extends WithFindAndModifyOptionsBuilder { + interface TerminatingUpdateOperation extends TerminatingFindAndModifyOperation, FindAndModifyWithOptions { /** - * 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. + * Update all matching documents in the collection. * - * @param collection must not be {@literal null} nor {@literal empty}. - * @return - * @throws IllegalArgumentException if collection is {@literal null}. + * @return never {@literal null}. */ - WithQueryBuilder inCollection(String collection); - } - - /** - * @param - * @author Christoph Strobl - * @since 2.0 - */ - interface WithOptionsBuilder extends WithCollectionBuilder, WithQueryBuilder {} + UpdateResult all(); - /** - * @param - * @author Christoph Strobl - * @since 2.0 - */ - interface WithFindAndModifyOptionsBuilder { + /** + * Update the first document in the collection. + * + * @return never {@literal null}. + */ + UpdateResult first(); /** - * Explicitly define {@link FindAndModifyOptions} for the {@link Update}. + * Creates a new document if no documents match the filter query or updates the matching ones. * - * @param options must not be {@literal null}. - * @return - * @throws IllegalArgumentException if options is {@literal null}. + * @return never {@literal null}. */ - WithFindAndModifyBuilder withOptions(FindAndModifyOptions options); + UpdateResult upsert(); } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableUpdateOperationSupport.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableUpdateOperationSupport.java index 7ccee320ba..8856776827 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableUpdateOperationSupport.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableUpdateOperationSupport.java @@ -17,6 +17,8 @@ import lombok.RequiredArgsConstructor; +import java.util.Optional; + import org.bson.Document; import org.springframework.data.mongodb.core.query.BasicQuery; import org.springframework.data.mongodb.core.query.Query; @@ -27,12 +29,12 @@ import com.mongodb.client.result.UpdateResult; /** - * Implementation of {@link ExecutableUpdateOperationBuilder}. + * Implementation of {@link ExecutableUpdateOperation}. * * @author Christoph Strobl * @since 2.0 */ -class ExecutableUpdateOperationSupport implements ExecutableUpdateOperationBuilder { +class ExecutableUpdateOperationSupport implements ExecutableUpdateOperation { private final MongoTemplate template; @@ -48,10 +50,10 @@ class ExecutableUpdateOperationSupport implements ExecutableUpdateOperationBuild } @Override - public UpdateOperationBuilder update(Class domainType) { + public UpdateOperation update(Class domainType) { Assert.notNull(domainType, "DomainType must not be null!"); - return new UpdateBuilder(template, null, domainType, null, null, null); + return new UpdateOperationSupport(template, null, domainType, null, null, null); } /** @@ -60,8 +62,8 @@ public UpdateOperationBuilder update(Class domainType) { * @since 2.0 */ @RequiredArgsConstructor - static class UpdateBuilder - implements WithOptionsBuilder, UpdateOperationBuilder, WithCollectionBuilder, WithQueryBuilder { + static class UpdateOperationSupport implements UpdateOperation, UpdateOperationWithCollection, + UpdateOperationWithQuery, TerminatingUpdateOperation { private final MongoTemplate template; private final Query query; @@ -71,17 +73,17 @@ static class UpdateBuilder private final FindAndModifyOptions options; @Override - public WithOptionsBuilder apply(Update update) { + public TerminatingUpdateOperation apply(Update update) { Assert.notNull(update, "Update must not be null!"); - return new UpdateBuilder(template, query, domainType, update, collection, options); + return new UpdateOperationSupport(template, query, domainType, update, collection, options); } @Override - public WithQueryBuilder inCollection(String collection) { + public UpdateOperationWithQuery inCollection(String collection) { Assert.hasText(collection, "Collection must not be null nor empty!"); - return new UpdateBuilder(template, query, domainType, update, collection, options); + return new UpdateOperationSupport(template, query, domainType, update, collection, options); } @Override @@ -95,20 +97,20 @@ public UpdateResult upsert() { } @Override - public T findAndModify() { + public Optional findAndModify() { String collectionName = StringUtils.hasText(collection) ? collection : template.determineCollectionName(domainType); - return template.findAndModify(query != null ? query : new BasicQuery(new Document()), update, options, domainType, - collectionName); + return Optional.ofNullable(template.findAndModify(query != null ? query : new BasicQuery(new Document()), update, + options, domainType, collectionName)); } @Override - public UpdateOperationBuilderTerminatingOperations matching(Query query) { + public UpdateOperationWithUpdate matching(Query query) { Assert.notNull(query, "Query must not be null!"); - return new UpdateBuilder(template, query, domainType, update, collection, options); + return new UpdateOperationSupport(template, query, domainType, update, collection, options); } @Override @@ -117,10 +119,10 @@ public UpdateResult all() { } @Override - public WithFindAndModifyBuilder withOptions(FindAndModifyOptions options) { + public TerminatingFindAndModifyOperation withOptions(FindAndModifyOptions options) { Assert.notNull(options, "Options must not be null!"); - return new UpdateBuilder(template, query, domainType, update, collection, options); + return new UpdateOperationSupport(template, query, domainType, update, collection, options); } private UpdateResult doUpdate(boolean multi, boolean upsert) { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/FluentMongoOperations.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/FluentMongoOperations.java index c4c8d27075..ff61dafc56 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/FluentMongoOperations.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/FluentMongoOperations.java @@ -15,57 +15,13 @@ */ package org.springframework.data.mongodb.core; -import org.springframework.data.mongodb.core.ExecutableAggregationOperationBuilder.AggregationOperationBuilder; -import org.springframework.data.mongodb.core.ExecutableFindOperationBuilder.FindOperationBuilder; -import org.springframework.data.mongodb.core.ExecutableRemoveOperationBuilder.RemoveOperationBuilder; -import org.springframework.data.mongodb.core.ExecutableUpdateOperationBuilder.UpdateOperationBuilder; - /** - * Stripped down interface providing access to a {@literal builder} based fluent API that specifies a basic set of - * MongoDB operations. + * Stripped down interface providing access to a fluent API that specifies a basic set of MongoDB operations. * * @author Christoph Strobl * @since 2.0 */ -public interface FluentMongoOperations { - - /** - * Entry point for constructing and executing queries for a given domain type. - * - * @param domainType must not be {@literal null}. - * @return new instance of {@link FindOperationBuilder}. - * @throws IllegalArgumentException if domainType is {@literal null}. - * @since 2.0 - */ - FindOperationBuilder query(Class domainType); - - /** - * Entry point for constructing and executing updates for a given domain type. - * - * @param domainType must not be {@literal null}. - * @return new instance of {@link ExecutableUpdateOperationBuilder}. - * @throws IllegalArgumentException if domainType is {@literal null}. - * @since 2.0 - */ - UpdateOperationBuilder update(Class domainType); - - /** - * Entry point for constructing and executing deletes for a given domain type. - * - * @param domainType must not be {@literal null}. - * @return new instance of {@link RemoveOperationBuilder}. - * @throws IllegalArgumentException if domainType is {@literal null}. - * @since 2.0 - */ - RemoveOperationBuilder remove(Class domainType); +public interface FluentMongoOperations extends ExecutableFindOperation, ExecutableUpdateOperation, + ExecutableRemoveOperation, ExecutableAggregationOperation { - /** - * Entry point for constructing and executing aggregation operations. - * - * @param domainType must not be {@literal null}. - * @return new instance of {@link AggregationOperationBuilder}. - * @throws IllegalArgumentException if domainType is {@literal null}. - * @since 2.0 - */ - AggregationOperationBuilder aggregateAndReturn(Class domainType); } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java index 2165ea50cf..e126ced50d 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java @@ -67,9 +67,10 @@ import org.springframework.data.mapping.model.MappingException; import org.springframework.data.mongodb.MongoDbFactory; import org.springframework.data.mongodb.core.BulkOperations.BulkMode; -import org.springframework.data.mongodb.core.ExecutableFindOperationBuilder.FindOperationBuilder; -import org.springframework.data.mongodb.core.ExecutableRemoveOperationBuilder.RemoveOperationBuilder; -import org.springframework.data.mongodb.core.ExecutableUpdateOperationBuilder.UpdateOperationBuilder; +import org.springframework.data.mongodb.core.ExecutableAggregationOperation.AggregationOperation; +import org.springframework.data.mongodb.core.ExecutableFindOperation.FindOperation; +import org.springframework.data.mongodb.core.ExecutableRemoveOperation.RemoveOperation; +import org.springframework.data.mongodb.core.ExecutableUpdateOperation.UpdateOperation; import org.springframework.data.mongodb.core.aggregation.Aggregation; import org.springframework.data.mongodb.core.aggregation.AggregationOperationContext; import org.springframework.data.mongodb.core.aggregation.AggregationOptions; @@ -1790,22 +1791,22 @@ public CloseableIterator doInCollection(MongoCollection collection) } @Override - public FindOperationBuilder query(Class domainType) { - return new ExecutableFindOperationSupport(this).find(domainType); + public FindOperation query(Class domainType) { + return new ExecutableFindOperationSupport(this).query(domainType); } @Override - public UpdateOperationBuilder update(Class domainType) { + public UpdateOperation update(Class domainType) { return new ExecutableUpdateOperationSupport(this).update(domainType); } @Override - public RemoveOperationBuilder remove(Class domainType) { + public RemoveOperation remove(Class domainType) { return new ExecutableRemoveOperationSupport(this).remove(domainType); } @Override - public ExecutableAggregationOperationBuilder.AggregationOperationBuilder aggregateAndReturn( + public AggregationOperation aggregateAndReturn( Class domainType) { return new ExecutableAggregationOperationSupport(this).aggregateAndReturn(domainType); } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ExecutableFindOperationSupportTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ExecutableFindOperationSupportTests.java index 151a829a88..0e68082d95 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ExecutableFindOperationSupportTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ExecutableFindOperationSupportTests.java @@ -126,12 +126,12 @@ public void findAllByWithProjection() { @Test // DATAMONGO-1563 public void findBy() { - assertThat(template.query(Person.class).matching(query(where("firstname").is("luke"))).one()).isEqualTo(luke); + assertThat(template.query(Person.class).matching(query(where("firstname").is("luke"))).one()).contains(luke); } @Test // DATAMONGO-1563 public void findByNoMatch() { - assertThat(template.query(Person.class).matching(query(where("firstname").is("spock"))).one()).isNull(); + assertThat(template.query(Person.class).matching(query(where("firstname").is("spock"))).one()).isEmpty(); } @Test(expected = IncorrectResultSizeDataAccessException.class) // DATAMONGO-1563 diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ExecutableRemoveOperationSupportTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ExecutableRemoveOperationSupportTests.java index 85943cdec2..5033c7b84d 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ExecutableRemoveOperationSupportTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ExecutableRemoveOperationSupportTests.java @@ -63,7 +63,7 @@ public void setUp() { @Test // DATAMONGO-1563 public void removeAll() { - DeleteResult result = template.remove(Person.class).remove(); + DeleteResult result = template.remove(Person.class).all(); assertThat(result.getDeletedCount()).isEqualTo(2L); } @@ -71,7 +71,7 @@ public void removeAll() { @Test // DATAMONGO-1563 public void removeAllMatching() { - DeleteResult result = template.remove(Person.class).matching(query(where("firstname").is("han"))).remove(); + DeleteResult result = template.remove(Person.class).matching(query(where("firstname").is("han"))).all(); assertThat(result.getDeletedCount()).isEqualTo(1L); } @@ -80,7 +80,7 @@ public void removeAllMatching() { public void removeAllMatchingWithAlternateDomainTypeAndCollection() { DeleteResult result = template.remove(Jedi.class).inCollection(STAR_WARS).matching(query(where("name").is("luke"))) - .remove(); + .all(); assertThat(result.getDeletedCount()).isEqualTo(1L); } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ExecutableUpdateOperationSupportTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ExecutableUpdateOperationSupportTests.java index bef093456c..a960016f7e 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ExecutableUpdateOperationSupportTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ExecutableUpdateOperationSupportTests.java @@ -21,6 +21,8 @@ import lombok.Data; +import java.util.Optional; + import org.bson.BsonString; import org.junit.Before; import org.junit.Test; @@ -73,7 +75,7 @@ public void updateIsRequired() { @Test(expected = IllegalArgumentException.class) // DATAMONGO-1563 public void collectionIsRequiredOnSet() { - template.update(Person.class).apply(new Update()).inCollection(null); + template.update(Person.class).inCollection(null); } @Test(expected = IllegalArgumentException.class) // DATAMONGO-1563 @@ -102,8 +104,8 @@ public void updateAll() { @Test // DATAMONGO-1563 public void updateAllMatching() { - UpdateResult result = template.update(Person.class).apply(new Update().set("firstname", "Han")) - .matching(queryHan()).all(); + UpdateResult result = template.update(Person.class).matching(queryHan()).apply(new Update().set("firstname", "Han")) + .all(); assertThat(result.getModifiedCount()).isEqualTo(1L); assertThat(result.getUpsertedId()).isNull(); @@ -112,8 +114,8 @@ public void updateAllMatching() { @Test // DATAMONGO-1563 public void updateWithDifferentDomainClassAndCollection() { - UpdateResult result = template.update(Jedi.class).apply(new Update().set("name", "Han")).inCollection(STAR_WARS) - .matching(query(where("_id").is(han.getId()))).all(); + UpdateResult result = template.update(Jedi.class).inCollection(STAR_WARS) + .matching(query(where("_id").is(han.getId()))).apply(new Update().set("name", "Han")).all(); assertThat(result.getModifiedCount()).isEqualTo(1L); assertThat(result.getUpsertedId()).isNull(); @@ -124,10 +126,10 @@ public void updateWithDifferentDomainClassAndCollection() { @Test // DATAMONGO-1563 public void findAndModify() { - Person result = template.update(Person.class).apply(new Update().set("firstname", "Han")) - .matching(queryHan()).findAndModify(); + Optional result = template.update(Person.class).matching(queryHan()) + .apply(new Update().set("firstname", "Han")).findAndModify(); - assertThat(result).isEqualTo(han); + assertThat(result).contains(han); assertThat(template.findOne(queryHan(), Person.class)).isNotEqualTo(han).hasFieldOrPropertyWithValue("firstname", "Han"); } @@ -135,10 +137,10 @@ public void findAndModify() { @Test // DATAMONGO-1563 public void findAndModifyWithDifferentDomainTypeAndCollection() { - Jedi result = template.update(Jedi.class).apply(new Update().set("name", "Han")).inCollection(STAR_WARS) - .matching(query(where("_id").is(han.getId()))).findAndModify(); + Optional result = template.update(Jedi.class).inCollection(STAR_WARS) + .matching(query(where("_id").is(han.getId()))).apply(new Update().set("name", "Han")).findAndModify(); - assertThat(result).hasFieldOrPropertyWithValue("name", "han"); + assertThat(result.get()).hasFieldOrPropertyWithValue("name", "han"); assertThat(template.findOne(queryHan(), Person.class)).isNotEqualTo(han).hasFieldOrPropertyWithValue("firstname", "Han"); } @@ -146,17 +148,18 @@ public void findAndModifyWithDifferentDomainTypeAndCollection() { @Test // DATAMONGO-1563 public void findAndModifyWithOptions() { - Person result = template.update(Person.class).apply(new Update().set("firstname", "Han")) - .withOptions(FindAndModifyOptions.options().returnNew(true)).matching(queryHan()).findAndModify(); + Optional result = template.update(Person.class).matching(queryHan()) + .apply(new Update().set("firstname", "Han")).withOptions(FindAndModifyOptions.options().returnNew(true)) + .findAndModify(); - assertThat(result).isNotEqualTo(han).hasFieldOrPropertyWithValue("firstname", "Han"); + assertThat(result.get()).isNotEqualTo(han).hasFieldOrPropertyWithValue("firstname", "Han"); } @Test // DATAMONGO-1563 public void upsert() { - UpdateResult result = template.update(Person.class).apply(new Update().set("firstname", "Chewbacca")) - .matching(query(where("id").is("id-3"))).upsert(); + UpdateResult result = template.update(Person.class).matching(query(where("id").is("id-3"))) + .apply(new Update().set("firstname", "Chewbacca")).upsert(); assertThat(result.getModifiedCount()).isEqualTo(0L); assertThat(result.getUpsertedId()).isEqualTo(new BsonString("id-3")); From 59637c04685b8eb0216a536c8ac53931aa70ce9d Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Fri, 9 Jun 2017 20:56:35 +0200 Subject: [PATCH 5/5] DATAMONGO-1563 - Add fluent insert operations. --- .../core/ExecutableInsertOperation.java | 144 +++++++++++++++++ .../ExecutableInsertOperationSupport.java | 113 ++++++++++++++ .../mongodb/core/FluentMongoOperations.java | 4 +- .../data/mongodb/core/MongoTemplate.java | 12 +- ...utableInsertOperationSupportUnitTests.java | 145 ++++++++++++++++++ 5 files changed, 410 insertions(+), 8 deletions(-) create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableInsertOperation.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableInsertOperationSupport.java create mode 100644 spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ExecutableInsertOperationSupportUnitTests.java diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableInsertOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableInsertOperation.java new file mode 100644 index 0000000000..083f80ea5f --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableInsertOperation.java @@ -0,0 +1,144 @@ +/* + * 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 java.util.Collection; + +import org.springframework.data.mongodb.core.BulkOperations.BulkMode; + +import com.mongodb.bulk.BulkWriteResult; + +/** + * {@link ExecutableFindOperation} allows creation and execution of 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 Christoph Strobl + * @since 2.0 + */ +public interface ExecutableInsertOperation { + + /** + * Start creating an insert operation for given {@literal domainType}. + * + * @param domainType must not be {@literal null}. + * @return new instance of {@link InsertOperation}. + * @throws IllegalArgumentException if domainType is {@literal null}. + */ + InsertOperation insert(Class domainType); + + /** + * Trigger insert execution by calling one of the terminating methods. + * + * @param + * @author Christoph Strobl + * @since 2.0 + */ + interface TerminatingInsertOperation extends TerminatingBulkInsertOperation { + + /** + * Insert exactly one object. + * + * @param object must not be {@literal null}. + * @throws IllegalArgumentException if object is {@literal null}. + */ + void one(T object); + + /** + * Insert a collection of objects. + * + * @param objects must not be {@literal null}. + * @throws IllegalArgumentException if objects is {@literal null}. + */ + void all(Collection objects); + } + + /** + * Trigger bulk insert execution by calling one of the terminating methods. + * + * @param + * @author Christoph Strobl + * @since 2.0 + */ + interface TerminatingBulkInsertOperation { + + /** + * Bulk write collection of objects. + * + * @param objects must not be {@literal null}. + * @return resulting {@link BulkWriteResult}. + * @throws IllegalArgumentException if objects is {@literal null}. + */ + BulkWriteResult bulk(Collection objects); + } + + /** + * @param + * @author Christoph Strobl + * @since 2.0 + */ + interface InsertOperation + extends TerminatingInsertOperation, InsertOperationWithCollection, InsertOperationWithBulkMode { + + } + + /** + * Collection override (Optional). + * + * @param + * @author Christoph Strobl + * @since 2.0 + */ + interface InsertOperationWithCollection { + + /** + * 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 InsertOperationWithBulkMode}. + * @throws IllegalArgumentException if collection is {@literal null}. + */ + InsertOperationWithBulkMode inCollection(String collection); + } + + /** + * @param + * @author Christoph Strobl + * @since 2.0 + */ + interface InsertOperationWithBulkMode extends TerminatingInsertOperation { + + /** + * Define the {@link BulkMode} to use for bulk insert operation. + * + * @param mode must not be {@literal null}. + * @return new instance of {@link TerminatingBulkInsertOperation}. + * @throws IllegalArgumentException if bulkMode is {@literal null}. + */ + TerminatingBulkInsertOperation withBulkMode(BulkMode bulkMode); + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableInsertOperationSupport.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableInsertOperationSupport.java new file mode 100644 index 0000000000..83ca96994e --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableInsertOperationSupport.java @@ -0,0 +1,113 @@ +/* + * 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.RequiredArgsConstructor; + +import java.util.ArrayList; +import java.util.Collection; + +import org.springframework.data.mongodb.core.BulkOperations.BulkMode; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +import com.mongodb.bulk.BulkWriteResult; + +/** + * Implementation of {@link ExecutableInsertOperation}. + * + * @author Christoph Strobl + * @since 2.0 + */ +class ExecutableInsertOperationSupport implements ExecutableInsertOperation { + + private final MongoTemplate template; + + /** + * Create new {@link ExecutableInsertOperationSupport}. + * + * @param template must not be {@literal null}. + * @throws IllegalArgumentException if template is {@literal null}. + */ + ExecutableInsertOperationSupport(MongoTemplate template) { + + Assert.notNull(template, "Template must not be null!"); + + this.template = template; + } + + @Override + public InsertOperation insert(Class domainType) { + + Assert.notNull(domainType, "DomainType must not be null!"); + return new InsertOperationSupport(template, domainType, null, null); + } + + /** + * @param + * @author Christoph Strobl + * @since 2.0 + */ + @RequiredArgsConstructor + static class InsertOperationSupport implements InsertOperation { + + private final MongoTemplate template; + private final Class domainType; + private final String collection; + private final BulkMode bulkMode; + + @Override + public void one(T object) { + + Assert.notNull(object, "Object must not be null!"); + template.insert(object, getCollectionName()); + } + + @Override + public void all(Collection objects) { + + Assert.notNull(objects, "Objects must not be null!"); + template.insert(objects, getCollectionName()); + } + + @Override + public BulkWriteResult bulk(Collection objects) { + + Assert.notNull(objects, "Objects must not be null!"); + return template.bulkOps(bulkMode != null ? bulkMode : BulkMode.ORDERED, domainType, getCollectionName()) + .insert(new ArrayList<>(objects)).execute(); + } + + @Override + public InsertOperationWithBulkMode inCollection(String collection) { + + Assert.hasText(collection, "Collection must not be null nor empty."); + return new InsertOperationSupport(template, domainType, collection, bulkMode); + } + + @Override + public TerminatingBulkInsertOperation withBulkMode(BulkMode bulkMode) { + + Assert.notNull(bulkMode, "BulkMode must not be null!"); + return new InsertOperationSupport(template, domainType, collection, bulkMode); + } + + 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/FluentMongoOperations.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/FluentMongoOperations.java index ff61dafc56..0daa36e6ce 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/FluentMongoOperations.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/FluentMongoOperations.java @@ -21,7 +21,7 @@ * @author Christoph Strobl * @since 2.0 */ -public interface FluentMongoOperations extends ExecutableFindOperation, ExecutableUpdateOperation, - ExecutableRemoveOperation, ExecutableAggregationOperation { +public interface FluentMongoOperations extends ExecutableFindOperation, ExecutableInsertOperation, + ExecutableUpdateOperation, ExecutableRemoveOperation, ExecutableAggregationOperation { } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java index e126ced50d..01f5120f81 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java @@ -67,10 +67,6 @@ import org.springframework.data.mapping.model.MappingException; import org.springframework.data.mongodb.MongoDbFactory; import org.springframework.data.mongodb.core.BulkOperations.BulkMode; -import org.springframework.data.mongodb.core.ExecutableAggregationOperation.AggregationOperation; -import org.springframework.data.mongodb.core.ExecutableFindOperation.FindOperation; -import org.springframework.data.mongodb.core.ExecutableRemoveOperation.RemoveOperation; -import org.springframework.data.mongodb.core.ExecutableUpdateOperation.UpdateOperation; import org.springframework.data.mongodb.core.aggregation.Aggregation; import org.springframework.data.mongodb.core.aggregation.AggregationOperationContext; import org.springframework.data.mongodb.core.aggregation.AggregationOptions; @@ -1806,11 +1802,15 @@ public RemoveOperation remove(Class domainType) { } @Override - public AggregationOperation aggregateAndReturn( - Class domainType) { + public AggregationOperation aggregateAndReturn(Class domainType) { return new ExecutableAggregationOperationSupport(this).aggregateAndReturn(domainType); } + @Override + public InsertOperation insert(Class domainType) { + return new ExecutableInsertOperationSupport(this).insert(domainType); + } + /** * Assert that the {@link Document} does not enable Aggregation explain mode. * diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ExecutableInsertOperationSupportUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ExecutableInsertOperationSupportUnitTests.java new file mode 100644 index 0000000000..326cf271ff --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ExecutableInsertOperationSupportUnitTests.java @@ -0,0 +1,145 @@ +/* + * 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; +import org.springframework.data.mongodb.core.BulkOperations.BulkMode; + +/** + * @author Christoph Strobl + * @since 2017/06 + */ +@RunWith(MockitoJUnitRunner.Silent.class) +public class ExecutableInsertOperationSupportUnitTests { + + private static final String STAR_WARS = "star-wars"; + + @Mock MongoTemplate template; + @Mock BulkOperations bulkOperations; + + ExecutableInsertOperationSupport ops; + + Person luke, han; + + @Before + public void setUp() { + + when(template.bulkOps(any(), any(), any())).thenReturn(bulkOperations); + when(template.determineCollectionName(any(Class.class))).thenReturn(STAR_WARS); + when(bulkOperations.insert(anyList())).thenReturn(bulkOperations); + + ops = new ExecutableInsertOperationSupport(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-1563 + public void nullCollectionShouldThrowException() { + ops.insert(Person.class).inCollection(null); + + } + + @Test(expected = IllegalArgumentException.class) // DATAMONGO-1563 + public void nullBulkModeShouldThrowException() { + ops.insert(Person.class).withBulkMode(null); + } + + @Test // DATAMONGO-1563 + 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-1563 + 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-1563 + 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)); + } + + @Test // DATAMONGO-1563 + public void bulkInsertCollectionShouldDelegateCorrectly() { + + ops.insert(Person.class).bulk(Arrays.asList(luke, han)); + + ArgumentCaptor captor = ArgumentCaptor.forClass(Class.class); + + verify(template).determineCollectionName(any(Class.class)); + verify(template).bulkOps(eq(BulkMode.ORDERED), captor.capture(), eq(STAR_WARS)); + verify(bulkOperations).insert(anyList()); + verify(bulkOperations).execute(); + } + + @Test // DATAMONGO-1563 + public void bulkInsertWithBulkModeShouldDelegateCorrectly() { + + ops.insert(Person.class).withBulkMode(BulkMode.UNORDERED).bulk(Arrays.asList(luke, han)); + + ArgumentCaptor captor = ArgumentCaptor.forClass(Class.class); + + verify(template).determineCollectionName(any(Class.class)); + verify(template).bulkOps(eq(BulkMode.UNORDERED), captor.capture(), eq(STAR_WARS)); + verify(bulkOperations).insert(anyList()); + verify(bulkOperations).execute(); + } + + @Data + @org.springframework.data.mongodb.core.mapping.Document(collection = STAR_WARS) + static class Person { + @Id String id; + String firstname; + } +}