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:
Mark Paluch
2017-07-14 17:43:57 +02:00
committed by Christoph Strobl
parent 30a8608135
commit a6a0bde6f2
29 changed files with 2610 additions and 6 deletions

View File

@@ -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);
}

View File

@@ -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> {}
}

View File

@@ -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);
}
}
}

View File

@@ -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> {}
}

View File

@@ -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);
}
}
}

View File

@@ -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 {}

View File

@@ -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);
}
}

View File

@@ -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);
}
}
}

View File

@@ -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

View File

@@ -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;

View File

@@ -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> {}
}

View File

@@ -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;
}
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}
}

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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 {}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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)
}
}

View File

@@ -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)
}
}

View File

@@ -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)
}
}

View File

@@ -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)
}
}

View File

@@ -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)
}
}