10 changed files with 1119 additions and 10 deletions
@ -0,0 +1,199 @@
@@ -0,0 +1,199 @@
|
||||
/* |
||||
* Copyright 2018 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package org.springframework.data.mongodb.core; |
||||
|
||||
import java.util.List; |
||||
|
||||
import org.springframework.data.mongodb.core.ExecutableFindOperation.ExecutableFind; |
||||
import org.springframework.data.mongodb.core.mapreduce.MapReduceOptions; |
||||
import org.springframework.data.mongodb.core.query.Query; |
||||
|
||||
/** |
||||
* {@link ExecutableMapReduceOperation} allows creation and execution of MongoDB mapReduce operations in a fluent API |
||||
* style. The starting {@literal domainType} is used for mapping an optional {@link Query} provided via {@code matching} |
||||
* into the MongoDB specific representation. By default, the originating {@literal domainType} is also used for mapping |
||||
* back the results 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> |
||||
* mapReduce(Human.class) |
||||
* .map("function() { emit(this.id, this.firstname) }") |
||||
* .reduce("function(id, name) { return sum(id, name); }") |
||||
* .inCollection("star-wars") |
||||
* .as(Jedi.class) |
||||
* .matching(query(where("lastname").is("skywalker"))) |
||||
* .all(); |
||||
* </code> |
||||
* </pre> |
||||
* |
||||
* @author Christoph Strobl |
||||
* @since 2.1 |
||||
*/ |
||||
public interface ExecutableMapReduceOperation { |
||||
|
||||
/** |
||||
* Start creating a mapReduce operation for the given {@literal domainType}. |
||||
* |
||||
* @param domainType must not be {@literal null}. |
||||
* @return new instance of {@link ExecutableFind}. |
||||
* @throws IllegalArgumentException if domainType is {@literal null}. |
||||
*/ |
||||
<T> MapReduceWithMapFunction<T> mapReduce(Class<T> domainType); |
||||
|
||||
/** |
||||
* Trigger mapReduce execution by calling one of the terminating methods. |
||||
* |
||||
* @author Christoph Strobl |
||||
* @since 2.1 |
||||
*/ |
||||
interface TerminatingMapReduce<T> { |
||||
|
||||
/** |
||||
* Get the mapReduce results. |
||||
* |
||||
* @return never {@literal null}. |
||||
*/ |
||||
List<T> all(); |
||||
} |
||||
|
||||
/** |
||||
* Provide the Javascript {@code function()} used to map matching documents. |
||||
* |
||||
* @author Christoph Strobl |
||||
* @since 2.1 |
||||
*/ |
||||
interface MapReduceWithMapFunction<T> { |
||||
|
||||
/** |
||||
* Set the Javascript map {@code function()}. |
||||
* |
||||
* @param mapFunction must not be {@literal null} nor empty. |
||||
* @return new instance of {@link MapReduceWithReduceFunction}. |
||||
* @throws IllegalArgumentException if {@literal mapFunction} is {@literal null} or empty. |
||||
*/ |
||||
MapReduceWithReduceFunction<T> map(String mapFunction); |
||||
|
||||
} |
||||
|
||||
/** |
||||
* Provide the Javascript {@code function()} used to reduce matching documents. |
||||
* |
||||
* @author Christoph Strobl |
||||
* @since 2.1 |
||||
*/ |
||||
interface MapReduceWithReduceFunction<T> { |
||||
|
||||
/** |
||||
* Set the Javascript map {@code function()}. |
||||
* |
||||
* @param reduceFunction must not be {@literal null} nor empty. |
||||
* @return new instance of {@link ExecutableMapReduce}. |
||||
* @throws IllegalArgumentException if {@literal reduceFunction} is {@literal null} or empty. |
||||
*/ |
||||
ExecutableMapReduce<T> reduce(String reduceFunction); |
||||
|
||||
} |
||||
|
||||
/** |
||||
* Collection override (Optional). |
||||
* |
||||
* @author Christoph Strobl |
||||
* @since 2.1 |
||||
*/ |
||||
interface MapReduceWithCollection<T> extends MapReduceWithQuery<T> { |
||||
|
||||
/** |
||||
* Explicitly set the name of the collection to perform the mapReduce operation 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 MapReduceWithProjection}. |
||||
* @throws IllegalArgumentException if collection is {@literal null}. |
||||
*/ |
||||
MapReduceWithProjection<T> inCollection(String collection); |
||||
} |
||||
|
||||
/** |
||||
* Input document filter query (Optional). |
||||
* |
||||
* @author Christoph Strobl |
||||
* @since 2.1 |
||||
*/ |
||||
interface MapReduceWithQuery<T> extends TerminatingMapReduce<T> { |
||||
|
||||
/** |
||||
* Set the filter query to be used. |
||||
* |
||||
* @param query must not be {@literal null}. |
||||
* @return new instance of {@link TerminatingMapReduce}. |
||||
* @throws IllegalArgumentException if query is {@literal null}. |
||||
*/ |
||||
TerminatingMapReduce<T> matching(Query query); |
||||
} |
||||
|
||||
/** |
||||
* Result type override (Optional). |
||||
* |
||||
* @author Christoph Strobl |
||||
* @since 2.1 |
||||
*/ |
||||
interface MapReduceWithProjection<T> extends MapReduceWithQuery<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 TerminatingMapReduce}. |
||||
* @throws IllegalArgumentException if resultType is {@literal null}. |
||||
*/ |
||||
<R> MapReduceWithQuery<R> as(Class<R> resultType); |
||||
} |
||||
|
||||
/** |
||||
* Additional mapReduce options (Optional). |
||||
* |
||||
* @author Christoph Strobl |
||||
* @since 2.1 |
||||
*/ |
||||
interface MapReduceWithOptions<T> { |
||||
|
||||
/** |
||||
* Set additional options to apply to the mapReduce operation. |
||||
* |
||||
* @param options must not be {@literal null}. |
||||
* @return new instance of {@link ExecutableMapReduce}. |
||||
* @throws IllegalArgumentException if options is {@literal null}. |
||||
*/ |
||||
ExecutableMapReduce<T> with(MapReduceOptions options); |
||||
} |
||||
|
||||
/** |
||||
* {@link ExecutableMapReduce} provides methods for constructing mapReduce operations in a fluent way. |
||||
* |
||||
* @author Christoph Strobl |
||||
* @since 2.1 |
||||
*/ |
||||
interface ExecutableMapReduce<T> extends MapReduceWithMapFunction<T>, MapReduceWithReduceFunction<T>, |
||||
MapReduceWithCollection<T>, MapReduceWithProjection<T>, MapReduceWithOptions<T> { |
||||
|
||||
} |
||||
} |
||||
@ -0,0 +1,184 @@
@@ -0,0 +1,184 @@
|
||||
/* |
||||
* Copyright 2018 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package org.springframework.data.mongodb.core; |
||||
|
||||
import java.util.List; |
||||
|
||||
import org.springframework.data.mongodb.core.mapreduce.MapReduceOptions; |
||||
import org.springframework.data.mongodb.core.query.Query; |
||||
import org.springframework.lang.Nullable; |
||||
import org.springframework.util.Assert; |
||||
import org.springframework.util.StringUtils; |
||||
|
||||
/** |
||||
* Implementation of {@link ExecutableMapReduceOperation}. |
||||
* |
||||
* @author Christoph Strobl |
||||
* @since 2.1 |
||||
*/ |
||||
class ExecutableMapReduceOperationSupport implements ExecutableMapReduceOperation { |
||||
|
||||
private static final Query ALL_QUERY = new Query(); |
||||
|
||||
private final MongoTemplate template; |
||||
|
||||
/** |
||||
* Create new {@link ExecutableMapReduceOperationSupport}. |
||||
* |
||||
* @param template must not be {@literal null}. |
||||
* @throws IllegalArgumentException if template is {@literal null}. |
||||
*/ |
||||
ExecutableMapReduceOperationSupport(MongoTemplate template) { |
||||
|
||||
Assert.notNull(template, "Template must not be null!"); |
||||
this.template = template; |
||||
} |
||||
|
||||
/* |
||||
* (non-Javascript) |
||||
* @see in org.springframework.data.mongodb.core.ExecutableMapReduceOperation#mapReduce(java.lang.Class) |
||||
*/ |
||||
@Override |
||||
public <T> ExecutableMapReduceSupport<T> mapReduce(Class<T> domainType) { |
||||
|
||||
Assert.notNull(domainType, "DomainType must not be null!"); |
||||
|
||||
return new ExecutableMapReduceSupport(template, domainType, domainType, null, ALL_QUERY, null, null, null); |
||||
} |
||||
|
||||
/** |
||||
* @author Christoph Strobl |
||||
* @since 2.1 |
||||
*/ |
||||
static class ExecutableMapReduceSupport<T> |
||||
implements ExecutableMapReduce<T>, MapReduceWithOptions<T>, MapReduceWithCollection<T>, |
||||
MapReduceWithProjection<T>, MapReduceWithQuery<T>, MapReduceWithReduceFunction<T>, MapReduceWithMapFunction<T> { |
||||
|
||||
private final MongoTemplate template; |
||||
private final Class<?> domainType; |
||||
private final Class<T> returnType; |
||||
private final @Nullable String collection; |
||||
private final Query query; |
||||
private final @Nullable String mapFunction; |
||||
private final @Nullable String reduceFunction; |
||||
private final @Nullable MapReduceOptions options; |
||||
|
||||
ExecutableMapReduceSupport(MongoTemplate template, Class<?> domainType, Class<T> returnType, String collection, |
||||
Query query, @Nullable String mapFunction, @Nullable String reduceFunction, MapReduceOptions options) { |
||||
|
||||
this.template = template; |
||||
this.domainType = domainType; |
||||
this.returnType = returnType; |
||||
this.collection = collection; |
||||
this.query = query; |
||||
this.mapFunction = mapFunction; |
||||
this.reduceFunction = reduceFunction; |
||||
this.options = options; |
||||
} |
||||
|
||||
/* |
||||
* (non-Javascript) |
||||
* @see in org.springframework.data.mongodb.core.ExecutableMapReduceOperation.TerminatingMapReduce#all() |
||||
*/ |
||||
@Override |
||||
public List<T> all() { |
||||
return template.mapReduce(query, domainType, getCollectionName(), mapFunction, reduceFunction, options, |
||||
returnType); |
||||
} |
||||
|
||||
/* |
||||
* (non-Javascript) |
||||
* @see in org.springframework.data.mongodb.core.ExecutableMapReduceOperation.MapReduceWithCollection#inCollection(java.lang.String) |
||||
*/ |
||||
@Override |
||||
public MapReduceWithProjection<T> inCollection(String collection) { |
||||
|
||||
Assert.hasText(collection, "Collection name must not be null nor empty!"); |
||||
|
||||
return new ExecutableMapReduceSupport<>(template, domainType, returnType, collection, query, mapFunction, |
||||
reduceFunction, options); |
||||
} |
||||
|
||||
/* |
||||
* (non-Javascript) |
||||
* @see in org.springframework.data.mongodb.core.ExecutableMapReduceOperation.MapReduceWithQuery#query(org.springframework.data.mongodb.core.query.Query) |
||||
*/ |
||||
@Override |
||||
public TerminatingMapReduce<T> matching(Query query) { |
||||
|
||||
Assert.notNull(query, "Query must not be null!"); |
||||
|
||||
return new ExecutableMapReduceSupport<>(template, domainType, returnType, collection, query, mapFunction, |
||||
reduceFunction, options); |
||||
} |
||||
|
||||
/* |
||||
* (non-Javascript) |
||||
* @see in org.springframework.data.mongodb.core.ExecutableMapReduceOperation.MapReduceWithProjection#as(java.lang.Class) |
||||
*/ |
||||
@Override |
||||
public <R> MapReduceWithQuery<R> as(Class<R> resultType) { |
||||
|
||||
Assert.notNull(resultType, "ResultType must not be null!"); |
||||
|
||||
return new ExecutableMapReduceSupport<>(template, domainType, resultType, collection, query, mapFunction, |
||||
reduceFunction, options); |
||||
} |
||||
|
||||
/* |
||||
* (non-Javascript) |
||||
* @see in org.springframework.data.mongodb.core.ExecutableMapReduceOperation.MapReduceWithOptions#with(org.springframework.data.mongodb.core.mapreduce.MapReduceOptions) |
||||
*/ |
||||
@Override |
||||
public ExecutableMapReduce<T> with(MapReduceOptions options) { |
||||
|
||||
Assert.notNull(options, "Options must not be null! Please consider empty MapReduceOptions#options() instead."); |
||||
|
||||
return new ExecutableMapReduceSupport<>(template, domainType, returnType, collection, query, mapFunction, |
||||
reduceFunction, options); |
||||
} |
||||
|
||||
/* |
||||
* (non-Javascript) |
||||
* @see in org.springframework.data.mongodb.core.ExecutableMapReduceOperation.MapReduceWithMapFunction#map(java.lang.String) |
||||
*/ |
||||
@Override |
||||
public MapReduceWithReduceFunction<T> map(String mapFunction) { |
||||
|
||||
Assert.hasText(mapFunction, "MapFunction name must not be null nor empty!"); |
||||
|
||||
return new ExecutableMapReduceSupport<>(template, domainType, returnType, collection, query, mapFunction, |
||||
reduceFunction, options); |
||||
} |
||||
|
||||
/* |
||||
* (non-Javascript) |
||||
* @see in org.springframework.data.mongodb.core.ExecutableMapReduceOperation.MapReduceWithReduceFunction#reduce(java.lang.String) |
||||
*/ |
||||
@Override |
||||
public ExecutableMapReduce<T> reduce(String reduceFunction) { |
||||
|
||||
Assert.hasText(reduceFunction, "ReduceFunction name must not be null nor empty!"); |
||||
|
||||
return new ExecutableMapReduceSupport<>(template, domainType, returnType, collection, query, mapFunction, |
||||
reduceFunction, options); |
||||
} |
||||
|
||||
private String getCollectionName() { |
||||
return StringUtils.hasText(collection) ? collection : template.determineCollectionName(domainType); |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,199 @@
@@ -0,0 +1,199 @@
|
||||
/* |
||||
* Copyright 2018 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package org.springframework.data.mongodb.core; |
||||
|
||||
import reactor.core.publisher.Flux; |
||||
|
||||
import org.springframework.data.mongodb.core.ExecutableFindOperation.ExecutableFind; |
||||
import org.springframework.data.mongodb.core.mapreduce.MapReduceOptions; |
||||
import org.springframework.data.mongodb.core.query.Query; |
||||
|
||||
/** |
||||
* {@link ReactiveMapReduceOperation} allows creation and execution of MongoDB mapReduce operations in a fluent API |
||||
* style. The starting {@literal domainType} is used for mapping an optional {@link Query} provided via {@code matching} |
||||
* into the MongoDB specific representation. By default, the originating {@literal domainType} is also used for mapping |
||||
* back the results 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> |
||||
* mapReduce(Human.class) |
||||
* .map("function() { emit(this.id, this.firstname) }") |
||||
* .reduce("function(id, name) { return sum(id, name); }") |
||||
* .inCollection("star-wars") |
||||
* .as(Jedi.class) |
||||
* .matching(query(where("lastname").is("skywalker"))) |
||||
* .all(); |
||||
* </code> |
||||
* </pre> |
||||
* |
||||
* @author Christoph Strobl |
||||
* @since 2.1 |
||||
*/ |
||||
public interface ReactiveMapReduceOperation { |
||||
|
||||
/** |
||||
* Start creating a mapReduce operation for the given {@literal domainType}. |
||||
* |
||||
* @param domainType must not be {@literal null}. |
||||
* @return new instance of {@link ExecutableFind}. |
||||
* @throws IllegalArgumentException if domainType is {@literal null}. |
||||
*/ |
||||
<T> MapReduceWithMapFunction<T> mapReduce(Class<T> domainType); |
||||
|
||||
/** |
||||
* Trigger mapReduce execution by calling one of the terminating methods. |
||||
* |
||||
* @author Christoph Strobl |
||||
* @since 2.1 |
||||
*/ |
||||
interface TerminatingMapReduce<T> { |
||||
|
||||
/** |
||||
* Get the {@link Flux} emitting mapReduce results. |
||||
* |
||||
* @return a {@link Flux} emitting the already mapped operation results. |
||||
*/ |
||||
Flux<T> all(); |
||||
} |
||||
|
||||
/** |
||||
* Provide the Javascript {@code function()} used to map matching documents. |
||||
* |
||||
* @author Christoph Strobl |
||||
* @since 2.1 |
||||
*/ |
||||
interface MapReduceWithMapFunction<T> { |
||||
|
||||
/** |
||||
* Set the Javascript map {@code function()}. |
||||
* |
||||
* @param mapFunction must not be {@literal null} nor empty. |
||||
* @return new instance of {@link MapReduceWithReduceFunction}. |
||||
* @throws IllegalArgumentException if {@literal mapFunction} is {@literal null} or empty. |
||||
*/ |
||||
MapReduceWithReduceFunction<T> map(String mapFunction); |
||||
|
||||
} |
||||
|
||||
/** |
||||
* Provide the Javascript {@code function()} used to reduce matching documents. |
||||
* |
||||
* @author Christoph Strobl |
||||
* @since 2.1 |
||||
*/ |
||||
interface MapReduceWithReduceFunction<T> { |
||||
|
||||
/** |
||||
* Set the Javascript map {@code function()}. |
||||
* |
||||
* @param reduceFunction must not be {@literal null} nor empty. |
||||
* @return new instance of {@link ReactiveMapReduce}. |
||||
* @throws IllegalArgumentException if {@literal reduceFunction} is {@literal null} or empty. |
||||
*/ |
||||
ReactiveMapReduce<T> reduce(String reduceFunction); |
||||
|
||||
} |
||||
|
||||
/** |
||||
* Collection override (Optional). |
||||
* |
||||
* @author Christoph Strobl |
||||
* @since 2.1 |
||||
*/ |
||||
interface MapReduceWithCollection<T> extends MapReduceWithQuery<T> { |
||||
|
||||
/** |
||||
* Explicitly set the name of the collection to perform the mapReduce operation 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 MapReduceWithProjection}. |
||||
* @throws IllegalArgumentException if collection is {@literal null}. |
||||
*/ |
||||
MapReduceWithProjection<T> inCollection(String collection); |
||||
} |
||||
|
||||
/** |
||||
* Input document filter query (Optional). |
||||
* |
||||
* @author Christoph Strobl |
||||
* @since 2.1 |
||||
*/ |
||||
interface MapReduceWithQuery<T> extends TerminatingMapReduce<T> { |
||||
|
||||
/** |
||||
* Set the filter query to be used. |
||||
* |
||||
* @param query must not be {@literal null}. |
||||
* @return new instance of {@link TerminatingMapReduce}. |
||||
* @throws IllegalArgumentException if query is {@literal null}. |
||||
*/ |
||||
TerminatingMapReduce<T> matching(Query query); |
||||
} |
||||
|
||||
/** |
||||
* Result type override (Optional). |
||||
* |
||||
* @author Christoph Strobl |
||||
* @since 2.1 |
||||
*/ |
||||
interface MapReduceWithProjection<T> extends MapReduceWithQuery<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 TerminatingMapReduce}. |
||||
* @throws IllegalArgumentException if resultType is {@literal null}. |
||||
*/ |
||||
<R> MapReduceWithQuery<R> as(Class<R> resultType); |
||||
} |
||||
|
||||
/** |
||||
* Additional mapReduce options (Optional). |
||||
* |
||||
* @author Christoph Strobl |
||||
* @since 2.1 |
||||
*/ |
||||
interface MapReduceWithOptions<T> { |
||||
|
||||
/** |
||||
* Set additional options to apply to the mapReduce operation. |
||||
* |
||||
* @param options must not be {@literal null}. |
||||
* @return new instance of {@link ReactiveMapReduce}. |
||||
* @throws IllegalArgumentException if options is {@literal null}. |
||||
*/ |
||||
ReactiveMapReduce<T> with(MapReduceOptions options); |
||||
} |
||||
|
||||
/** |
||||
* {@link ReactiveMapReduce} provides methods for constructing reactive mapReduce operations in a fluent way. |
||||
* |
||||
* @author Christoph Strobl |
||||
* @since 2.1 |
||||
*/ |
||||
interface ReactiveMapReduce<T> extends MapReduceWithMapFunction<T>, MapReduceWithReduceFunction<T>, |
||||
MapReduceWithCollection<T>, MapReduceWithProjection<T>, MapReduceWithOptions<T> { |
||||
|
||||
} |
||||
} |
||||
@ -0,0 +1,186 @@
@@ -0,0 +1,186 @@
|
||||
/* |
||||
* Copyright 2018 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package org.springframework.data.mongodb.core; |
||||
|
||||
import reactor.core.publisher.Flux; |
||||
|
||||
import org.springframework.data.mongodb.core.mapreduce.MapReduceOptions; |
||||
import org.springframework.data.mongodb.core.query.Query; |
||||
import org.springframework.lang.Nullable; |
||||
import org.springframework.util.Assert; |
||||
import org.springframework.util.StringUtils; |
||||
|
||||
/** |
||||
* Implementation of {@link ReactiveMapReduceOperation}. |
||||
* |
||||
* @author Christoph Strobl |
||||
* @since 2.1 |
||||
*/ |
||||
class ReactiveMapReduceOperationSupport implements ReactiveMapReduceOperation { |
||||
|
||||
private static final Query ALL_QUERY = new Query(); |
||||
|
||||
private final ReactiveMongoTemplate template; |
||||
|
||||
/** |
||||
* Create new {@link ReactiveMapReduceOperationSupport}. |
||||
* |
||||
* @param template must not be {@literal null}. |
||||
* @throws IllegalArgumentException if template is {@literal null}. |
||||
*/ |
||||
ReactiveMapReduceOperationSupport(ReactiveMongoTemplate template) { |
||||
|
||||
Assert.notNull(template, "Template must not be null!"); |
||||
this.template = template; |
||||
} |
||||
|
||||
/* |
||||
* (non-Javascript) |
||||
* @see in org.springframework.data.mongodb.core.ExecutableMapReduceOperation#mapReduce(java.lang.Class) |
||||
*/ |
||||
@Override |
||||
public <T> ReactiveMapReduceSupport<T> mapReduce(Class<T> domainType) { |
||||
|
||||
Assert.notNull(domainType, "DomainType must not be null!"); |
||||
|
||||
return new ReactiveMapReduceSupport(template, domainType, domainType, null, ALL_QUERY, null, null, null); |
||||
} |
||||
|
||||
/** |
||||
* @author Christoph Strobl |
||||
* @since 2.1 |
||||
*/ |
||||
static class ReactiveMapReduceSupport<T> |
||||
implements ReactiveMapReduce<T>, MapReduceWithOptions<T>, MapReduceWithCollection<T>, MapReduceWithProjection<T>, |
||||
MapReduceWithQuery<T>, MapReduceWithReduceFunction<T>, MapReduceWithMapFunction<T> { |
||||
|
||||
private final ReactiveMongoTemplate template; |
||||
private final Class<?> domainType; |
||||
private final Class<T> returnType; |
||||
private final @Nullable String collection; |
||||
private final Query query; |
||||
private final @Nullable String mapFunction; |
||||
private final @Nullable String reduceFunction; |
||||
private final @Nullable MapReduceOptions options; |
||||
|
||||
ReactiveMapReduceSupport(ReactiveMongoTemplate template, Class<?> domainType, Class<T> returnType, |
||||
String collection, Query query, @Nullable String mapFunction, @Nullable String reduceFunction, |
||||
MapReduceOptions options) { |
||||
|
||||
this.template = template; |
||||
this.domainType = domainType; |
||||
this.returnType = returnType; |
||||
this.collection = collection; |
||||
this.query = query; |
||||
this.mapFunction = mapFunction; |
||||
this.reduceFunction = reduceFunction; |
||||
this.options = options; |
||||
} |
||||
|
||||
/* |
||||
* (non-Javascript) |
||||
* @see in org.springframework.data.mongodb.core.ExecutableMapReduceOperation.TerminatingMapReduce#all() |
||||
*/ |
||||
@Override |
||||
public Flux<T> all() { |
||||
|
||||
return template.mapReduce(query, domainType, getCollectionName(), returnType, mapFunction, reduceFunction, |
||||
options); |
||||
} |
||||
|
||||
/* |
||||
* (non-Javascript) |
||||
* @see in org.springframework.data.mongodb.core.ReactiveMapReduceOperation.MapReduceWithCollection#inCollection(java.lang.String) |
||||
*/ |
||||
@Override |
||||
public MapReduceWithProjection<T> inCollection(String collection) { |
||||
|
||||
Assert.hasText(collection, "Collection name must not be null nor empty!"); |
||||
|
||||
return new ReactiveMapReduceSupport<>(template, domainType, returnType, collection, query, mapFunction, |
||||
reduceFunction, options); |
||||
} |
||||
|
||||
/* |
||||
* (non-Javascript) |
||||
* @see in org.springframework.data.mongodb.core.ReactiveMapReduceOperation.MapReduceWithQuery#query(org.springframework.data.mongodb.core.query.Query) |
||||
*/ |
||||
@Override |
||||
public TerminatingMapReduce<T> matching(Query query) { |
||||
|
||||
Assert.notNull(query, "Query must not be null!"); |
||||
|
||||
return new ReactiveMapReduceSupport<>(template, domainType, returnType, collection, query, mapFunction, |
||||
reduceFunction, options); |
||||
} |
||||
|
||||
/* |
||||
* (non-Javascript) |
||||
* @see in org.springframework.data.mongodb.core.ReactiveMapReduceOperation.MapReduceWithProjection#as(java.lang.Class) |
||||
*/ |
||||
@Override |
||||
public <R> MapReduceWithQuery<R> as(Class<R> resultType) { |
||||
|
||||
Assert.notNull(resultType, "ResultType must not be null!"); |
||||
|
||||
return new ReactiveMapReduceSupport<>(template, domainType, resultType, collection, query, mapFunction, |
||||
reduceFunction, options); |
||||
} |
||||
|
||||
/* |
||||
* (non-Javascript) |
||||
* @see in org.springframework.data.mongodb.core.ReactiveMapReduceOperation.MapReduceWithOptions#with(org.springframework.data.mongodb.core.mapreduce.MapReduceOptions) |
||||
*/ |
||||
@Override |
||||
public ReactiveMapReduce<T> with(MapReduceOptions options) { |
||||
|
||||
Assert.notNull(options, "Options must not be null! Please consider empty MapReduceOptions#options() instead."); |
||||
|
||||
return new ReactiveMapReduceSupport<>(template, domainType, returnType, collection, query, mapFunction, |
||||
reduceFunction, options); |
||||
} |
||||
|
||||
/* |
||||
* (non-Javascript) |
||||
* @see in org.springframework.data.mongodb.core.ReactiveMapReduceOperation.MapReduceWithMapFunction#map(java.lang.String) |
||||
*/ |
||||
@Override |
||||
public MapReduceWithReduceFunction<T> map(String mapFunction) { |
||||
|
||||
Assert.hasText(mapFunction, "MapFunction name must not be null nor empty!"); |
||||
|
||||
return new ReactiveMapReduceSupport<>(template, domainType, returnType, collection, query, mapFunction, |
||||
reduceFunction, options); |
||||
} |
||||
|
||||
/* |
||||
* (non-Javascript) |
||||
* @see in org.springframework.data.mongodb.core.ReactiveMapReduceOperation.MapReduceWithReduceFunction#reduce(java.lang.String) |
||||
*/ |
||||
@Override |
||||
public ReactiveMapReduce<T> reduce(String reduceFunction) { |
||||
|
||||
Assert.hasText(reduceFunction, "ReduceFunction name must not be null nor empty!"); |
||||
|
||||
return new ReactiveMapReduceSupport<>(template, domainType, returnType, collection, query, mapFunction, |
||||
reduceFunction, options); |
||||
} |
||||
|
||||
private String getCollectionName() { |
||||
return StringUtils.hasText(collection) ? collection : template.determineCollectionName(domainType); |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,141 @@
@@ -0,0 +1,141 @@
|
||||
/* |
||||
* Copyright 2018 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package org.springframework.data.mongodb.core; |
||||
|
||||
import static org.mockito.ArgumentMatchers.any; |
||||
import static org.mockito.ArgumentMatchers.eq; |
||||
import static org.mockito.ArgumentMatchers.isNull; |
||||
import static org.mockito.Mockito.*; |
||||
|
||||
import lombok.AllArgsConstructor; |
||||
import lombok.Data; |
||||
import lombok.NoArgsConstructor; |
||||
|
||||
import org.junit.Before; |
||||
import org.junit.Test; |
||||
import org.junit.runner.RunWith; |
||||
import org.mockito.Mock; |
||||
import org.mockito.junit.MockitoJUnitRunner; |
||||
import org.springframework.data.annotation.Id; |
||||
import org.springframework.data.mongodb.core.mapping.Field; |
||||
import org.springframework.data.mongodb.core.mapreduce.MapReduceOptions; |
||||
import org.springframework.data.mongodb.core.query.BasicQuery; |
||||
import org.springframework.data.mongodb.core.query.Query; |
||||
|
||||
/** |
||||
* Unit tests for {@link ExecutableMapReduceOperationSupport}. |
||||
* |
||||
* @author Christoph Strobl |
||||
* @currentRead Beyond the Shadows - Brent Weeks |
||||
*/ |
||||
@RunWith(MockitoJUnitRunner.class) |
||||
public class ExecutableMapReduceOperationSupportUnitTests { |
||||
|
||||
private static final String STAR_WARS = "star-wars"; |
||||
private static final String MAP_FUNCTION = "function() { emit(this.id, this.firstname) }"; |
||||
private static final String REDUCE_FUNCTION = "function(id, name) { return sum(id, name); }"; |
||||
|
||||
@Mock MongoTemplate template; |
||||
|
||||
ExecutableMapReduceOperationSupport mapReduceOpsSupport; |
||||
|
||||
@Before |
||||
public void setUp() { |
||||
|
||||
when(template.determineCollectionName(eq(Person.class))).thenReturn(STAR_WARS); |
||||
|
||||
mapReduceOpsSupport = new ExecutableMapReduceOperationSupport(template); |
||||
} |
||||
|
||||
@Test(expected = IllegalArgumentException.class) // DATAMONGO-1929
|
||||
public void throwsExceptionOnNullTemplate() { |
||||
new ExecutableMapReduceOperationSupport(null); |
||||
} |
||||
|
||||
@Test(expected = IllegalArgumentException.class) // DATAMONGO-1929
|
||||
public void throwsExceptionOnNullDomainType() { |
||||
mapReduceOpsSupport.mapReduce(null); |
||||
} |
||||
|
||||
@Test // DATAMONGO-1929
|
||||
public void usesExtractedCollectionName() { |
||||
|
||||
mapReduceOpsSupport.mapReduce(Person.class).map(MAP_FUNCTION).reduce(REDUCE_FUNCTION).all(); |
||||
|
||||
verify(template).mapReduce(any(Query.class), eq(Person.class), eq(STAR_WARS), eq(MAP_FUNCTION), eq(REDUCE_FUNCTION), |
||||
isNull(), eq(Person.class)); |
||||
} |
||||
|
||||
@Test // DATAMONGO-1929
|
||||
public void usesExplicitCollectionName() { |
||||
|
||||
mapReduceOpsSupport.mapReduce(Person.class).map(MAP_FUNCTION).reduce(REDUCE_FUNCTION) |
||||
.inCollection("the-night-angel").all(); |
||||
|
||||
verify(template).mapReduce(any(Query.class), eq(Person.class), eq("the-night-angel"), eq(MAP_FUNCTION), |
||||
eq(REDUCE_FUNCTION), isNull(), eq(Person.class)); |
||||
} |
||||
|
||||
@Test // DATAMONGO-1929
|
||||
public void usesMapReduceOptionsWhenPresent() { |
||||
|
||||
MapReduceOptions options = MapReduceOptions.options(); |
||||
mapReduceOpsSupport.mapReduce(Person.class).map(MAP_FUNCTION).reduce(REDUCE_FUNCTION).with(options).all(); |
||||
|
||||
verify(template).mapReduce(any(Query.class), eq(Person.class), eq(STAR_WARS), eq(MAP_FUNCTION), eq(REDUCE_FUNCTION), |
||||
eq(options), eq(Person.class)); |
||||
} |
||||
|
||||
@Test // DATAMONGO-1929
|
||||
public void usesQueryWhenPresent() { |
||||
|
||||
Query query = new BasicQuery("{ 'lastname' : 'skywalker' }"); |
||||
mapReduceOpsSupport.mapReduce(Person.class).map(MAP_FUNCTION).reduce(REDUCE_FUNCTION).matching(query).all(); |
||||
|
||||
verify(template).mapReduce(eq(query), eq(Person.class), eq(STAR_WARS), eq(MAP_FUNCTION), eq(REDUCE_FUNCTION), |
||||
isNull(), eq(Person.class)); |
||||
} |
||||
|
||||
@Test // DATAMONGO-1929
|
||||
public void usesProjectionWhenPresent() { |
||||
|
||||
mapReduceOpsSupport.mapReduce(Person.class).map(MAP_FUNCTION).reduce(REDUCE_FUNCTION).as(Jedi.class).all(); |
||||
|
||||
verify(template).mapReduce(any(Query.class), eq(Person.class), eq(STAR_WARS), eq(MAP_FUNCTION), eq(REDUCE_FUNCTION), |
||||
isNull(), eq(Jedi.class)); |
||||
} |
||||
|
||||
interface Contact {} |
||||
|
||||
@Data |
||||
@org.springframework.data.mongodb.core.mapping.Document(collection = STAR_WARS) |
||||
static class Person implements Contact { |
||||
|
||||
@Id String id; |
||||
String firstname; |
||||
String lastname; |
||||
Object ability; |
||||
Person father; |
||||
} |
||||
|
||||
@Data |
||||
@AllArgsConstructor |
||||
@NoArgsConstructor |
||||
static class Jedi { |
||||
|
||||
@Field("firstname") String name; |
||||
} |
||||
} |
||||
@ -0,0 +1,141 @@
@@ -0,0 +1,141 @@
|
||||
/* |
||||
* Copyright 2018 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package org.springframework.data.mongodb.core; |
||||
|
||||
import static org.mockito.ArgumentMatchers.any; |
||||
import static org.mockito.ArgumentMatchers.eq; |
||||
import static org.mockito.ArgumentMatchers.isNull; |
||||
import static org.mockito.Mockito.*; |
||||
|
||||
import lombok.AllArgsConstructor; |
||||
import lombok.Data; |
||||
import lombok.NoArgsConstructor; |
||||
|
||||
import org.junit.Before; |
||||
import org.junit.Test; |
||||
import org.junit.runner.RunWith; |
||||
import org.mockito.Mock; |
||||
import org.mockito.junit.MockitoJUnitRunner; |
||||
import org.springframework.data.annotation.Id; |
||||
import org.springframework.data.mongodb.core.mapping.Field; |
||||
import org.springframework.data.mongodb.core.mapreduce.MapReduceOptions; |
||||
import org.springframework.data.mongodb.core.query.BasicQuery; |
||||
import org.springframework.data.mongodb.core.query.Query; |
||||
|
||||
/** |
||||
* Unit tests for {@link ReactiveMapReduceOperationSupport}. |
||||
* |
||||
* @author Christoph Strobl |
||||
* @currentRead Beyond the Shadows - Brent Weeks |
||||
*/ |
||||
@RunWith(MockitoJUnitRunner.class) |
||||
public class ReactiveMapReduceOperationSupportUnitTests { |
||||
|
||||
private static final String STAR_WARS = "star-wars"; |
||||
private static final String MAP_FUNCTION = "function() { emit(this.id, this.firstname) }"; |
||||
private static final String REDUCE_FUNCTION = "function(id, name) { return sum(id, name); }"; |
||||
|
||||
@Mock ReactiveMongoTemplate template; |
||||
|
||||
ReactiveMapReduceOperationSupport mapReduceOpsSupport; |
||||
|
||||
@Before |
||||
public void setUp() { |
||||
|
||||
when(template.determineCollectionName(eq(Person.class))).thenReturn(STAR_WARS); |
||||
|
||||
mapReduceOpsSupport = new ReactiveMapReduceOperationSupport(template); |
||||
} |
||||
|
||||
@Test(expected = IllegalArgumentException.class) // DATAMONGO-1929
|
||||
public void throwsExceptionOnNullTemplate() { |
||||
new ExecutableMapReduceOperationSupport(null); |
||||
} |
||||
|
||||
@Test(expected = IllegalArgumentException.class) // DATAMONGO-1929
|
||||
public void throwsExceptionOnNullDomainType() { |
||||
mapReduceOpsSupport.mapReduce(null); |
||||
} |
||||
|
||||
@Test // DATAMONGO-1929
|
||||
public void usesExtractedCollectionName() { |
||||
|
||||
mapReduceOpsSupport.mapReduce(Person.class).map(MAP_FUNCTION).reduce(REDUCE_FUNCTION).all(); |
||||
|
||||
verify(template).mapReduce(any(Query.class), eq(Person.class), eq(STAR_WARS), eq(Person.class), eq(MAP_FUNCTION), |
||||
eq(REDUCE_FUNCTION), isNull()); |
||||
} |
||||
|
||||
@Test // DATAMONGO-1929
|
||||
public void usesExplicitCollectionName() { |
||||
|
||||
mapReduceOpsSupport.mapReduce(Person.class).map(MAP_FUNCTION).reduce(REDUCE_FUNCTION) |
||||
.inCollection("the-night-angel").all(); |
||||
|
||||
verify(template).mapReduce(any(Query.class), eq(Person.class), eq("the-night-angel"), eq(Person.class), |
||||
eq(MAP_FUNCTION), eq(REDUCE_FUNCTION), isNull()); |
||||
} |
||||
|
||||
@Test // DATAMONGO-1929
|
||||
public void usesMapReduceOptionsWhenPresent() { |
||||
|
||||
MapReduceOptions options = MapReduceOptions.options(); |
||||
mapReduceOpsSupport.mapReduce(Person.class).map(MAP_FUNCTION).reduce(REDUCE_FUNCTION).with(options).all(); |
||||
|
||||
verify(template).mapReduce(any(Query.class), eq(Person.class), eq(STAR_WARS), eq(Person.class), eq(MAP_FUNCTION), |
||||
eq(REDUCE_FUNCTION), eq(options)); |
||||
} |
||||
|
||||
@Test // DATAMONGO-1929
|
||||
public void usesQueryWhenPresent() { |
||||
|
||||
Query query = new BasicQuery("{ 'lastname' : 'skywalker' }"); |
||||
mapReduceOpsSupport.mapReduce(Person.class).map(MAP_FUNCTION).reduce(REDUCE_FUNCTION).matching(query).all(); |
||||
|
||||
verify(template).mapReduce(eq(query), eq(Person.class), eq(STAR_WARS), eq(Person.class), eq(MAP_FUNCTION), |
||||
eq(REDUCE_FUNCTION), isNull()); |
||||
} |
||||
|
||||
@Test // DATAMONGO-1929
|
||||
public void usesProjectionWhenPresent() { |
||||
|
||||
mapReduceOpsSupport.mapReduce(Person.class).map(MAP_FUNCTION).reduce(REDUCE_FUNCTION).as(Jedi.class).all(); |
||||
|
||||
verify(template).mapReduce(any(Query.class), eq(Person.class), eq(STAR_WARS), eq(Jedi.class), eq(MAP_FUNCTION), |
||||
eq(REDUCE_FUNCTION), isNull()); |
||||
} |
||||
|
||||
interface Contact {} |
||||
|
||||
@Data |
||||
@org.springframework.data.mongodb.core.mapping.Document(collection = STAR_WARS) |
||||
static class Person implements Contact { |
||||
|
||||
@Id String id; |
||||
String firstname; |
||||
String lastname; |
||||
Object ability; |
||||
Person father; |
||||
} |
||||
|
||||
@Data |
||||
@AllArgsConstructor |
||||
@NoArgsConstructor |
||||
static class Jedi { |
||||
|
||||
@Field("firstname") String name; |
||||
} |
||||
} |
||||
Loading…
Reference in new issue