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