DATAMONGO-586 - Next round of polishing.
Refined the way the aggregation pipeline gets rendered into a DBObject. More tests. Added $avg and shortcut methods to GroupOperations. Fixed ProjectionOperation to use 1 for implicit references. Made ProjectionOperation publicly visible. Added automatic result unwrapping. API consistency, tests, JavaDoc polish.
This commit is contained in:
@@ -311,13 +311,12 @@ public interface MongoOperations {
|
||||
*
|
||||
* @param aggregation The {@link TypedAggregation} specification holding the aggregation operations, must not be
|
||||
* {@literal null}.
|
||||
* @param inputCollectionName The name of the input collection to use for the aggreation.
|
||||
* @param collectionName The name of the input collection to use for the aggreation.
|
||||
* @param outputType The parameterized type of the returned list, must not be {@literal null}.
|
||||
* @return The results of the aggregation operation.
|
||||
* @since 1.3
|
||||
*/
|
||||
<I, O> AggregationResults<O> aggregate(TypedAggregation<I, O> aggregation, String inputCollectionName,
|
||||
Class<O> outputType);
|
||||
<O> AggregationResults<O> aggregate(TypedAggregation<?> aggregation, String collectionName, Class<O> outputType);
|
||||
|
||||
/**
|
||||
* Execute an aggregation operation. The raw results will be mapped to the given entity class. The name of the
|
||||
@@ -329,33 +328,33 @@ public interface MongoOperations {
|
||||
* @return The results of the aggregation operation.
|
||||
* @since 1.3
|
||||
*/
|
||||
<I, O> AggregationResults<O> aggregate(TypedAggregation<I, O> aggregation, Class<O> outputType);
|
||||
<O> AggregationResults<O> aggregate(TypedAggregation<?> aggregation, Class<O> outputType);
|
||||
|
||||
/**
|
||||
* Execute an aggregation operation. The raw results will be mapped to the given entity class.
|
||||
*
|
||||
* @param aggregation The {@link Aggregation} specification holding the aggregation operations, must not be
|
||||
* {@literal null}.
|
||||
* @param inputType the inputType where the aggregation operation will read from, must not be {@literal null} or
|
||||
* empty.
|
||||
* @param aggregation The {@link Aggregation} specification holding the aggregation operations, must not be
|
||||
* {@literal null}.
|
||||
* @param outputType The parameterized type of the returned list, must not be {@literal null}.
|
||||
* @return The results of the aggregation operation.
|
||||
* @since 1.3
|
||||
*/
|
||||
<I, O> AggregationResults<O> aggregate(Class<I> inputType, Aggregation<I, O> aggregation, Class<O> outputType);
|
||||
<O> AggregationResults<O> aggregate(Aggregation aggregation, Class<?> inputType, Class<O> outputType);
|
||||
|
||||
/**
|
||||
* Execute an aggregation operation. The raw results will be mapped to the given entity class.
|
||||
*
|
||||
* @param inputCollectionName the collection where the aggregation operation will read from, must not be
|
||||
* {@literal null} or empty.
|
||||
* @param aggregation The {@link Aggregation} specification holding the aggregation operations, must not be
|
||||
* {@literal null}.
|
||||
* @param collectionName the collection where the aggregation operation will read from, must not be {@literal null} or
|
||||
* empty.
|
||||
* @param outputType The parameterized type of the returned list, must not be {@literal null}.
|
||||
* @return The results of the aggregation operation.
|
||||
* @since 1.3
|
||||
*/
|
||||
<O> AggregationResults<O> aggregate(String inputCollectionName, Aggregation<?, O> aggregation, Class<O> outputType);
|
||||
<O> AggregationResults<O> aggregate(Aggregation aggregation, String collectionName, Class<O> outputType);
|
||||
|
||||
/**
|
||||
* Execute a map-reduce operation. The map-reduce operation will be formed with an output type of INLINE
|
||||
|
||||
@@ -54,7 +54,10 @@ import org.springframework.data.mapping.model.BeanWrapper;
|
||||
import org.springframework.data.mapping.model.MappingException;
|
||||
import org.springframework.data.mongodb.MongoDbFactory;
|
||||
import org.springframework.data.mongodb.core.aggregation.Aggregation;
|
||||
import org.springframework.data.mongodb.core.aggregation.AggregationOperationContext;
|
||||
import org.springframework.data.mongodb.core.aggregation.AggregationResults;
|
||||
import org.springframework.data.mongodb.core.aggregation.Fields;
|
||||
import org.springframework.data.mongodb.core.aggregation.TypeBasedAggregationOperationContext;
|
||||
import org.springframework.data.mongodb.core.aggregation.TypedAggregation;
|
||||
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
|
||||
import org.springframework.data.mongodb.core.convert.MongoConverter;
|
||||
@@ -1215,31 +1218,47 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
|
||||
}
|
||||
|
||||
@Override
|
||||
public <I, O> AggregationResults<O> aggregate(TypedAggregation<I, O> aggregation, Class<O> outputType) {
|
||||
public <O> AggregationResults<O> aggregate(TypedAggregation<?> aggregation, Class<O> outputType) {
|
||||
return aggregate(aggregation, determineCollectionName(aggregation.getInputType()), outputType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <I, O> AggregationResults<O> aggregate(TypedAggregation<I, O> aggregation, String inputCollectionName,
|
||||
Class<O> outputType) {
|
||||
return aggregate(inputCollectionName, aggregation, outputType);
|
||||
}
|
||||
|
||||
public <I, O> AggregationResults<O> aggregate(Class<I> inputType, Aggregation<I, O> aggregation, Class<O> outputType) {
|
||||
return aggregate(determineCollectionName(inputType), aggregation, outputType);
|
||||
}
|
||||
|
||||
public <O> AggregationResults<O> aggregate(String inputCollectionName, Aggregation<? extends Object, O> aggregation,
|
||||
public <O> AggregationResults<O> aggregate(TypedAggregation<?> aggregation, String inputCollectionName,
|
||||
Class<O> outputType) {
|
||||
|
||||
Assert.notNull(inputCollectionName, "Collection name is missing");
|
||||
Assert.notNull(aggregation, "Aggregation pipeline is missing");
|
||||
Assert.notNull(outputType, "Entity class is missing");
|
||||
Assert.notNull(aggregation, "Aggregation pipeline must not be null!");
|
||||
|
||||
// prepare command
|
||||
DBObject command = aggregation.toDbObject(inputCollectionName);
|
||||
AggregationOperationContext context = new TypeBasedAggregationOperationContext(aggregation.getInputType(),
|
||||
mappingContext, queryMapper);
|
||||
return aggregate(aggregation, inputCollectionName, outputType, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <O> AggregationResults<O> aggregate(Aggregation aggregation, Class<?> inputType, Class<O> outputType) {
|
||||
|
||||
return aggregate(aggregation, determineCollectionName(inputType), outputType,
|
||||
new TypeBasedAggregationOperationContext(inputType, mappingContext, queryMapper));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <O> AggregationResults<O> aggregate(Aggregation aggregation, String collectionName, Class<O> outputType) {
|
||||
return aggregate(aggregation, collectionName, outputType, null);
|
||||
}
|
||||
|
||||
protected <O> AggregationResults<O> aggregate(Aggregation aggregation, String collectionName, Class<O> outputType,
|
||||
AggregationOperationContext context) {
|
||||
|
||||
Assert.hasText(collectionName, "Collection name must not be null or empty!");
|
||||
Assert.notNull(aggregation, "Aggregation pipeline must not be null!");
|
||||
Assert.notNull(outputType, "Output type must not be null!");
|
||||
|
||||
AggregationOperationContext rootContext = context == null ? Aggregation.DEFAULT_CONTEXT : context;
|
||||
DBObject command = aggregation.toDbObject(collectionName, rootContext);
|
||||
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("Executing aggregation: {}", serializeToJsonSafely(command));
|
||||
}
|
||||
|
||||
// execute command
|
||||
CommandResult commandResult = executeCommand(command);
|
||||
handleCommandError(commandResult, command);
|
||||
|
||||
@@ -1247,7 +1266,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
|
||||
@SuppressWarnings("unchecked")
|
||||
Iterable<DBObject> resultSet = (Iterable<DBObject>) commandResult.get("result");
|
||||
List<O> mappedResults = new ArrayList<O>();
|
||||
DbObjectCallback<O> callback = new ReadDbObjectCallback<O>(mongoConverter, outputType);
|
||||
DbObjectCallback<O> callback = new UnwrapAndReadDbObjectCallback<O>(mongoConverter, outputType);
|
||||
|
||||
for (DBObject dbObject : resultSet) {
|
||||
mappedResults.add(callback.doWith(dbObject));
|
||||
}
|
||||
@@ -1920,6 +1940,35 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
|
||||
}
|
||||
}
|
||||
|
||||
class UnwrapAndReadDbObjectCallback<T> extends ReadDbObjectCallback<T> {
|
||||
|
||||
public UnwrapAndReadDbObjectCallback(EntityReader<? super T, DBObject> reader, Class<T> type) {
|
||||
super(reader, type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T doWith(DBObject object) {
|
||||
|
||||
Object idField = object.get(Fields.UNDERSCORE_ID);
|
||||
|
||||
if (!(idField instanceof DBObject)) {
|
||||
return super.doWith(object);
|
||||
}
|
||||
|
||||
DBObject toMap = new BasicDBObject();
|
||||
DBObject nested = (DBObject) idField;
|
||||
toMap.putAll(nested);
|
||||
|
||||
for (String key : object.keySet()) {
|
||||
if (!Fields.UNDERSCORE_ID.equals(key)) {
|
||||
toMap.put(key, object.get(key));
|
||||
}
|
||||
}
|
||||
|
||||
return super.doWith(toMap);
|
||||
}
|
||||
}
|
||||
|
||||
private enum DefaultWriteConcernResolver implements WriteConcernResolver {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 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.aggregation;
|
||||
|
||||
import com.mongodb.BasicDBObject;
|
||||
import com.mongodb.DBObject;
|
||||
|
||||
/**
|
||||
* @author Thomas Darimont
|
||||
*/
|
||||
abstract class AbstractAggregateOperation implements AggregationOperation {
|
||||
private final String operationName;
|
||||
|
||||
/**
|
||||
* @param operationName
|
||||
*/
|
||||
public AbstractAggregateOperation(String operationName) {
|
||||
this.operationName = operationName;
|
||||
}
|
||||
|
||||
public String getOperationName() {
|
||||
return operationName;
|
||||
}
|
||||
|
||||
public String getOperationCommand() {
|
||||
return OPERATOR_PREFIX + getOperationName();
|
||||
}
|
||||
|
||||
public Object getOperationArgument() {
|
||||
return new BasicDBObject();
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.aggregation.AggregationOperation#toDbObject()
|
||||
*/
|
||||
@Override
|
||||
public DBObject toDbObject() {
|
||||
return new BasicDBObject(getOperationCommand(), getOperationArgument());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.valueOf(toDbObject());
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 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.aggregation;
|
||||
|
||||
import com.mongodb.BasicDBObject;
|
||||
import com.mongodb.DBObject;
|
||||
|
||||
/**
|
||||
* @author Thomas Darimont
|
||||
*/
|
||||
abstract class AbstractContextAwareAggregateOperation extends AbstractAggregateOperation implements
|
||||
ContextConsumingAggregateOperation {
|
||||
|
||||
public AbstractContextAwareAggregateOperation(String operationName) {
|
||||
super(operationName);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.aggregation.AbstractAggregateOperation#getOperationArgument()
|
||||
*/
|
||||
@Override
|
||||
public Object getOperationArgument() {
|
||||
throw new UnsupportedOperationException(String.format("This is not supported on an instance of %s",
|
||||
ContextConsumingAggregateOperation.class.getName()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the argument for the aggregation operation from the given {@code inputAggregateOperationContext}
|
||||
*
|
||||
* @param inputAggregateOperationContext
|
||||
* @return the argument for the operation
|
||||
*/
|
||||
public abstract Object getOperationArgument(AggregateOperationContext inputAggregateOperationContext);
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.aggregation.AbstractAggregateOperation#toDbObject()
|
||||
*/
|
||||
@Override
|
||||
public DBObject toDbObject() {
|
||||
throw new UnsupportedOperationException(String.format("This is not supported on an instance of %s",
|
||||
ContextConsumingAggregateOperation.class.getName()));
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.aggregation.ContextAwareAggregateOperation#toDbObject(org.springframework.data.mongodb.core.aggregation.AggregateOperationContext)
|
||||
*/
|
||||
public DBObject toDbObject(AggregateOperationContext inputAggregateOperationContext) {
|
||||
return new BasicDBObject(getOperationCommand(), getOperationArgument(inputAggregateOperationContext));
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 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.aggregation;
|
||||
|
||||
/**
|
||||
* @author Thomas Darimont
|
||||
*/
|
||||
abstract class AbstractContextProducingAggregateOperation extends AbstractContextAwareAggregateOperation implements
|
||||
ContextProducingAggregateOperation {
|
||||
|
||||
private final AggregateOperationContext outputAggregateOperationContext;
|
||||
|
||||
public AbstractContextProducingAggregateOperation(String operationName) {
|
||||
super(operationName);
|
||||
this.outputAggregateOperationContext = createAggregateContext();
|
||||
}
|
||||
|
||||
private AggregateOperationContext createAggregateContext() {
|
||||
return new BasicAggregateOperationContext();
|
||||
}
|
||||
|
||||
public AggregateOperationContext getOutputAggregateOperationContext() {
|
||||
return this.outputAggregateOperationContext;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 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.aggregation;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A {@code AggregateOperationContext} holds information about available fields for the aggregation steps.
|
||||
*
|
||||
* @author Thomas Darimont
|
||||
*/
|
||||
interface AggregateOperationContext {
|
||||
|
||||
Map<String, String> getAvailableFields();
|
||||
|
||||
/**
|
||||
* @param fieldName
|
||||
* @return the alias for the given fieldName if present in available fields. If the given field is not available the
|
||||
* given fieldName is return instead.
|
||||
*/
|
||||
String returnFieldNameAliasIfAvailableOr(String fieldName);
|
||||
|
||||
/**
|
||||
* @param fieldName
|
||||
* @return true if the a field with the given field name is available.
|
||||
*/
|
||||
boolean isFieldAvailable(String fieldName);
|
||||
|
||||
/**
|
||||
* Registers a field with the given {@code fieldName} as available field.
|
||||
*
|
||||
* @param fieldName
|
||||
*/
|
||||
void registerAvailableField(String fieldName);
|
||||
|
||||
/**
|
||||
* Registers a field with the given {@code fieldName} as field available with the given {@code availableFieldName} as
|
||||
* an alias.
|
||||
*
|
||||
* @param fieldName
|
||||
*/
|
||||
void registerAvailableField(String fieldName, String availableFieldName);
|
||||
|
||||
/**
|
||||
* Removes the field with the given fieldName from the available fields.
|
||||
*
|
||||
* @param fieldName
|
||||
*/
|
||||
void unregisterAvailableField(String fieldName);
|
||||
}
|
||||
@@ -15,13 +15,19 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core.aggregation;
|
||||
|
||||
import static org.springframework.data.mongodb.core.aggregation.Fields.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.domain.Sort.Direction;
|
||||
import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExposedField;
|
||||
import org.springframework.data.mongodb.core.aggregation.ExposedFields.FieldReference;
|
||||
import org.springframework.data.mongodb.core.aggregation.Fields.AggregationField;
|
||||
import org.springframework.data.mongodb.core.query.Criteria;
|
||||
import org.springframework.data.mongodb.core.query.NearQuery;
|
||||
import org.springframework.data.mongodb.core.query.SerializationUtils;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.mongodb.BasicDBObject;
|
||||
@@ -31,30 +37,179 @@ import com.mongodb.DBObject;
|
||||
* An {@code Aggregation} is a representation of a list of aggregation steps to be performed by the MongoDB Aggregation
|
||||
* Framework.
|
||||
*
|
||||
* @author Tobias Trelle - Original API and Implementation
|
||||
* @author Thomas Darimont - Refactoring, embedded DSL
|
||||
* @author Tobias Trelle
|
||||
* @author Thomas Darimont
|
||||
* @author Oliver Gierke
|
||||
* @since 1.3
|
||||
*/
|
||||
public class Aggregation<I, O> {
|
||||
public class Aggregation {
|
||||
|
||||
private final List<AggregationOperation> operations = new ArrayList<AggregationOperation>();
|
||||
public static final AggregationOperationContext DEFAULT_CONTEXT = new NoOpAggregationOperationContext();
|
||||
|
||||
private final List<AggregationOperation> operations;
|
||||
|
||||
/**
|
||||
* Creates a new {@link Aggregation} from the given {@link AggregationOperation}s.
|
||||
*
|
||||
* @param operations must not be {@literal null} or empty.
|
||||
*/
|
||||
public Aggregation(AggregationOperation... operations) {
|
||||
registerOperations(operations);
|
||||
public static Aggregation newAggregation(AggregationOperation... operations) {
|
||||
return new Aggregation(operations);
|
||||
}
|
||||
|
||||
private void registerOperations(AggregationOperation... operations) {
|
||||
/**
|
||||
* Creates a new {@link TypedAggregation} for the given type and {@link AggregationOperation}s.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
* @param operations must not be {@literal null} or empty.
|
||||
*/
|
||||
public static <T> TypedAggregation<T> newAggregation(Class<T> type, AggregationOperation... operations) {
|
||||
return new TypedAggregation<T>(type, operations);
|
||||
}
|
||||
|
||||
Assert.notNull(operations, "Operations must not be null!");
|
||||
Assert.isTrue(operations.length > 0, "operations must not be empty!");
|
||||
for (AggregationOperation operation : operations) {
|
||||
Assert.notNull(operation, "Operation is not allowed to be null");
|
||||
this.operations.add(operation);
|
||||
}
|
||||
/**
|
||||
* Creates a new {@link Aggregation} from the given {@link AggregationOperation}s.
|
||||
*
|
||||
* @param aggregationOperations must not be {@literal null} or empty.
|
||||
*/
|
||||
protected Aggregation(AggregationOperation... aggregationOperations) {
|
||||
|
||||
Assert.notNull(aggregationOperations, "AggregationOperations must not be null!");
|
||||
Assert.isTrue(aggregationOperations.length > 0, "At least one AggregationOperation has to be provided");
|
||||
|
||||
this.operations = Arrays.asList(aggregationOperations);
|
||||
}
|
||||
|
||||
/**
|
||||
* A pointer to the previous {@link AggregationOperation}.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static String previousOperation() {
|
||||
return "_id";
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link ProjectionOperation} including the given fields.
|
||||
*
|
||||
* @param fields must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public static ProjectionOperation project(String... fields) {
|
||||
return project(fields(fields));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link ProjectionOperation} includeing the given {@link Fields}.
|
||||
*
|
||||
* @param fields must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public static ProjectionOperation project(Fields fields) {
|
||||
return new ProjectionOperation(fields);
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method to create a new {@link UnwindOperation} for the field with the given name.
|
||||
*
|
||||
* @param fieldName must not be {@literal null} or empty.
|
||||
* @return
|
||||
*/
|
||||
public static UnwindOperation unwind(String field) {
|
||||
return new UnwindOperation(field(field));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link GroupOperation} for the given fields.
|
||||
*
|
||||
* @param fields must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public static GroupOperation group(String... fields) {
|
||||
return group(fields(fields));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link GroupOperation} for the given {@link Fields}.
|
||||
*
|
||||
* @param fields must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public static GroupOperation group(Fields fields) {
|
||||
return new GroupOperation(fields);
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method to create a new {@link SortOperation} for the given {@link Sort}.
|
||||
*
|
||||
* @param sort must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public static SortOperation sort(Sort sort) {
|
||||
return new SortOperation(sort);
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method to create a new {@link SortOperation} for the given sort {@link Direction} and {@code fields}.
|
||||
*
|
||||
* @param direction must not be {@literal null}.
|
||||
* @param fields must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public static SortOperation sort(Direction direction, String... fields) {
|
||||
return new SortOperation(new Sort(direction, fields));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link SkipOperation} skipping the given number of elements.
|
||||
*
|
||||
* @param elementsToSkip must not be less than zero.
|
||||
* @return
|
||||
*/
|
||||
public static SkipOperation skip(int elementsToSkip) {
|
||||
return new SkipOperation(elementsToSkip);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link LimitOperation} limiting the result to the given number of elements.
|
||||
*
|
||||
* @param maxElements must not be less than zero.
|
||||
* @return
|
||||
*/
|
||||
public static LimitOperation limit(long maxElements) {
|
||||
return new LimitOperation(maxElements);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link MatchOperation} using the given {@link Criteria}.
|
||||
*
|
||||
* @param criteria must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public static MatchOperation match(Criteria criteria) {
|
||||
return new MatchOperation(criteria);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link Fields} instance for the given field names.
|
||||
*
|
||||
* @see Fields#fields(String...)
|
||||
* @param fields must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public static Fields fields(String... fields) {
|
||||
return Fields.fields(fields);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link Fields} instance from the given field name and target reference.
|
||||
*
|
||||
* @param name must not be {@literal null} or empty.
|
||||
* @param target must not be {@literal null} or empty.
|
||||
* @return
|
||||
*/
|
||||
public static Fields bind(String name, String target) {
|
||||
return Fields.from(field(name, target));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -63,273 +218,68 @@ public class Aggregation<I, O> {
|
||||
* @param inputCollectionName the name of the input collection
|
||||
* @return the {@code DBObject} representing this aggregation
|
||||
*/
|
||||
public DBObject toDbObject(String inputCollectionName) {
|
||||
public DBObject toDbObject(String inputCollectionName, AggregationOperationContext rootContext) {
|
||||
|
||||
AggregationOperationContext context = rootContext;
|
||||
List<DBObject> operationDocuments = new ArrayList<DBObject>(operations.size());
|
||||
|
||||
for (AggregationOperation operation : operations) {
|
||||
|
||||
operationDocuments.add(operation.toDBObject(context));
|
||||
|
||||
if (operation instanceof AggregationOperationContext) {
|
||||
context = (AggregationOperationContext) operation;
|
||||
}
|
||||
}
|
||||
|
||||
DBObject command = new BasicDBObject("aggregate", inputCollectionName);
|
||||
command.put("pipeline", getOperationObjects());
|
||||
command.put("pipeline", operationDocuments);
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
private List<DBObject> getOperationObjects() {
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return SerializationUtils
|
||||
.serializeToJsonSafely(toDbObject("__collection__", new NoOpAggregationOperationContext()));
|
||||
}
|
||||
|
||||
AggregateOperationContext aggregateOperationContext = createInitialAggregateOperationContext();
|
||||
List<DBObject> operationObjects = new ArrayList<DBObject>();
|
||||
for (AggregationOperation operation : operations) {
|
||||
/**
|
||||
* Simple {@link AggregationOperationContext} that just returns {@link FieldReference}s as is.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
private static class NoOpAggregationOperationContext implements AggregationOperationContext {
|
||||
|
||||
if (operation instanceof NoopAggreationOperation) {
|
||||
continue;
|
||||
}
|
||||
|
||||
operationObjects.add(toOperationObject(operation, aggregateOperationContext));
|
||||
if (operation instanceof ContextProducingAggregateOperation) {
|
||||
aggregateOperationContext = ((ContextProducingAggregateOperation) operation)
|
||||
.getOutputAggregateOperationContext();
|
||||
}
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.aggregation.AggregationOperationContext#getMappedObject(com.mongodb.DBObject)
|
||||
*/
|
||||
@Override
|
||||
public DBObject getMappedObject(DBObject dbObject) {
|
||||
return dbObject;
|
||||
}
|
||||
return operationObjects;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param aggregateOperationContext
|
||||
* @param operation
|
||||
* @return the {@link DBObject} representation of the given {@link AggregationOperation}
|
||||
*/
|
||||
protected DBObject toOperationObject(AggregationOperation operation,
|
||||
AggregateOperationContext aggregateOperationContext) {
|
||||
|
||||
DBObject operationObject;
|
||||
if (operation instanceof ContextConsumingAggregateOperation) {
|
||||
operationObject = ((ContextConsumingAggregateOperation) operation).toDbObject(aggregateOperationContext);
|
||||
} else {
|
||||
operationObject = operation.toDbObject();
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.aggregation.AggregationOperationContext#getReference(org.springframework.data.mongodb.core.aggregation.ExposedFields.AvailableField)
|
||||
*/
|
||||
@Override
|
||||
public FieldReference getReference(Field field) {
|
||||
return new FieldReference(new ExposedField(field, true));
|
||||
}
|
||||
return operationObject;
|
||||
}
|
||||
|
||||
protected AggregateOperationContext createInitialAggregateOperationContext() {
|
||||
return new BasicAggregateOperationContext();
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.aggregation.AggregationOperationContext#getReference(java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public FieldReference getReference(String name) {
|
||||
return new FieldReference(new ExposedField(new AggregationField(name), true));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method to create a new {@link GroupOperation} for the given {@code idFields}.
|
||||
*
|
||||
* @param idField the first idField to use, must not be {@literal null}.
|
||||
* @param additionalIdFields more id fields to use, can be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public static GroupOperation group(String idField, String... additionalIdFields) {
|
||||
return new GroupOperation(fields(idField, additionalIdFields));
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method to create a new {@link GroupOperation} for the given {@code idFields}.
|
||||
*
|
||||
* @param idFields
|
||||
* @return
|
||||
*/
|
||||
public static GroupOperation group(Fields idFields) {
|
||||
return new GroupOperation(idFields);
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method to create a new {@link ProjectionOperation} for the given {@code fields}. The {@code _id} field is
|
||||
* implicitly excluded.
|
||||
*
|
||||
* @param fields a list of fields to include in the projection.
|
||||
* @return The {@link ProjectionOperation}.
|
||||
*/
|
||||
public static ProjectionOperation project(String... fields) {
|
||||
return new ProjectionOperation(fields);
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method to create a new {@link ProjectionOperation}.
|
||||
*
|
||||
* @return The {@link ProjectionOperation}.
|
||||
*/
|
||||
public static ProjectionOperation project() {
|
||||
return new ProjectionOperation();
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method to create a new {@link ProjectionOperation} for the given {@code targetClass}.
|
||||
*
|
||||
* @param targetClass
|
||||
* @return
|
||||
*/
|
||||
public static ProjectionOperation project(Class<?> targetClass) {
|
||||
return new ProjectionOperation(targetClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method to create a new {@link MatchOperation} for the given {@link Criteria}.
|
||||
*
|
||||
* @param criteria must not be {@literal null}
|
||||
* @return
|
||||
*/
|
||||
public static MatchOperation match(Criteria criteria) {
|
||||
return new MatchOperation(criteria);
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method to create a new {@link UnwindOperation} for the given {@literal fieldName}.
|
||||
*
|
||||
* @param fieldName {@link UnwindOperation}.
|
||||
* @return
|
||||
*/
|
||||
public static UnwindOperation unwind(String fieldName) {
|
||||
return new UnwindOperation(fieldName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method to create a new {@link SkipOperation} for the given {@code skipCount}.
|
||||
*
|
||||
* @param skipCount the number of documents to skip.
|
||||
* @return
|
||||
*/
|
||||
public static SkipOperation skip(int skipCount) {
|
||||
return new SkipOperation(skipCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method to create a new {@link LimitOperation} for the given {@code maxElements}.
|
||||
*
|
||||
* @param maxElements, the max number of documents to return.
|
||||
* @return
|
||||
*/
|
||||
public static LimitOperation limit(int maxElements) {
|
||||
return new LimitOperation(maxElements);
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method to create a new {@link GeoNearOperation} for the given {@code nearQuery}.
|
||||
*
|
||||
* @param nearQuery, must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public static GeoNearOperation geoNear(NearQuery nearQuery) {
|
||||
return new GeoNearOperation(nearQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method to create a new {@link SortOperation} for the given sort {@link Direction} {@code direction} and
|
||||
* {@code fields}.
|
||||
*
|
||||
* @param direction, the sort direction, must not be {@literal null}.
|
||||
* @param fields must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public static SortOperation sort(Sort.Direction direction, String... fields) {
|
||||
return sort(new Sort(direction, fields));
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method to create a new {@link SortOperation} for the given {@link Sort}.
|
||||
*
|
||||
* @param sort
|
||||
* @return
|
||||
*/
|
||||
public static SortOperation sort(Sort sort) {
|
||||
return new SortOperation(sort);
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method to create a new {@link Fields} container for key-value pairs from the given {@code fieldNames}.
|
||||
* <p>
|
||||
* A call to fields("a","b","c") generates:
|
||||
* <p>
|
||||
*
|
||||
* <pre>
|
||||
* {
|
||||
* a: $a,
|
||||
* b: $b,
|
||||
* c: $c
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static Fields fields(String fieldName, String... additionalFieldNames) {
|
||||
return new BackendFields(additionalFieldNames).and(fieldName);
|
||||
}
|
||||
|
||||
public static Fields fields() {
|
||||
return new BackendFields();
|
||||
}
|
||||
|
||||
public static Fields pick(String fieldName, Object fieldNameOrValue) {
|
||||
return fields().and(fieldName, fieldNameOrValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* A convenience shortcut to {@link ReferenceUtil#$id()}
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static String $id() {
|
||||
return ReferenceUtil.$id();
|
||||
}
|
||||
|
||||
/**
|
||||
* A convenience shortcut to {@link ReferenceUtil#$(String)}
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static String $(String fieldName) {
|
||||
return ReferenceUtil.$(fieldName);
|
||||
}
|
||||
|
||||
/**
|
||||
* A convenience shortcut to {@link ReferenceUtil#$id(String)}
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static String $id(String fieldName) {
|
||||
return ReferenceUtil.$id(fieldName);
|
||||
}
|
||||
|
||||
/**
|
||||
* A convenience shortcut to {@link ReferenceUtil#ID_KEY}
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static String id() {
|
||||
return ReferenceUtil.ID_KEY;
|
||||
}
|
||||
|
||||
/**
|
||||
* A convenience shortcut to {@link ReferenceUtil#id(String)}
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static String id(String fieldName) {
|
||||
return ReferenceUtil.id(fieldName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link Aggregation}.
|
||||
*
|
||||
* @param <I> the input type of the {@link Aggregation}.
|
||||
* @param <O> the output type of the {@link Aggregation}.
|
||||
* @param inputType
|
||||
* @param operations
|
||||
* @return
|
||||
*/
|
||||
public static <I, O> TypedAggregation<I, O> newAggregation(Class<I> inputType, AggregationOperation... operations) {
|
||||
return new TypedAggregation<I, O>(inputType, operations);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link Aggregation}.
|
||||
*
|
||||
* @param <I> the input type of the {@link Aggregation}.
|
||||
* @param <O> the output type of the {@link Aggregation}.
|
||||
* @param inputType
|
||||
* @param operations
|
||||
* @return
|
||||
*/
|
||||
public static <I, O> Aggregation<I, O> newAggregation(AggregationOperation... operations) {
|
||||
return new Aggregation<I, O>(operations);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -22,16 +22,16 @@ import com.mongodb.DBObject;
|
||||
*
|
||||
* @author Sebastian Herold
|
||||
* @author Thomas Darimont
|
||||
* @author Oliver Gierke
|
||||
* @since 1.3
|
||||
*/
|
||||
public interface AggregationOperation {
|
||||
|
||||
String OPERATOR_PREFIX = "$";
|
||||
|
||||
/**
|
||||
* Creates a {@link DBObject} representation backing this object.
|
||||
* Turns the {@link AggregationOperation} into a {@link DBObject} by using the given
|
||||
* {@link AggregationOperationContext}.
|
||||
*
|
||||
* @return the DBObject
|
||||
*/
|
||||
DBObject toDbObject();
|
||||
DBObject toDBObject(AggregationOperationContext context);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 2013 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.aggregation;
|
||||
|
||||
import org.springframework.data.mongodb.core.aggregation.ExposedFields.FieldReference;
|
||||
|
||||
import com.mongodb.DBObject;
|
||||
|
||||
/**
|
||||
* The context for an {@link AggregationOperation}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @since 1.3
|
||||
*/
|
||||
public interface AggregationOperationContext {
|
||||
|
||||
/**
|
||||
* Returns the mapped {@link DBObject}, potentially converting the source considering mapping metadata etc.
|
||||
*
|
||||
* @param dbObject will never be {@literal null}.
|
||||
* @return must not be {@literal null}.
|
||||
*/
|
||||
DBObject getMappedObject(DBObject dbObject);
|
||||
|
||||
/**
|
||||
* Returns a {@link FieldReference} for the given field or {@literal null} if the context does not expose the given
|
||||
* field.
|
||||
*
|
||||
* @param field must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
FieldReference getReference(Field field);
|
||||
|
||||
/**
|
||||
* Returns the {@link FieldReference} for the field with the given name or {@literal null} if the context does not
|
||||
* expose a field with the given name.
|
||||
*
|
||||
* @param name must not be {@literal null} or empty.
|
||||
* @return
|
||||
*/
|
||||
FieldReference getReference(String name);
|
||||
}
|
||||
@@ -58,10 +58,21 @@ public class AggregationResults<T> implements Iterable<T> {
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public List<T> getAggregationResult() {
|
||||
public List<T> getMappedResults() {
|
||||
return mappedResults;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the unique mapped result. Assumes no result or exactly one.
|
||||
*
|
||||
* @return
|
||||
* @throws IllegalArgumentException in case more than one result is available.
|
||||
*/
|
||||
public T getUniqueMappedResult() {
|
||||
Assert.isTrue(mappedResults.size() < 2, "Expected unique result or null, but got more than one!");
|
||||
return mappedResults.size() == 1 ? mappedResults.get(0) : null;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Iterable#iterator()
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
package org.springframework.data.mongodb.core.aggregation;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Implementation of {@link Fields}
|
||||
*
|
||||
* @author Thomas Darimont
|
||||
*/
|
||||
class BackendFields implements Fields {
|
||||
|
||||
private Map<String, Object> values = new HashMap<String, Object>();
|
||||
|
||||
/**
|
||||
* @param names
|
||||
*/
|
||||
public BackendFields(String... names) {
|
||||
for (String name : names) {
|
||||
and(name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param name
|
||||
* @return
|
||||
*/
|
||||
public Fields and(String name) {
|
||||
return and(name, name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param name
|
||||
* @param value
|
||||
* @return
|
||||
*/
|
||||
public Fields and(String name, Object value) {
|
||||
this.values.put(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return
|
||||
*/
|
||||
public Map<String, Object> getValues() {
|
||||
return new HashMap<String, Object>(this.values);
|
||||
}
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 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.aggregation;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Map based implementation of {@link AggregateOperationContext}.
|
||||
*
|
||||
* @author Thomas Darimont
|
||||
*/
|
||||
public class BasicAggregateOperationContext implements AggregateOperationContext {
|
||||
|
||||
private Map<String, String> availableFields = new LinkedHashMap<String, String>();
|
||||
|
||||
@Override
|
||||
public Map<String, String> getAvailableFields() {
|
||||
return new HashMap<String, String>(getAvailableFieldsInternal());
|
||||
}
|
||||
|
||||
protected Map<String, String> getAvailableFieldsInternal() {
|
||||
return this.availableFields;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerAvailableField(String fieldName) {
|
||||
registerAvailableField(fieldName, fieldName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerAvailableField(String fieldName, String availableFieldName) {
|
||||
getAvailableFieldsInternal().put(fieldName, availableFieldName);
|
||||
}
|
||||
|
||||
public String returnFieldNameAliasIfAvailableOr(String fieldName) {
|
||||
return isFieldAvailable(fieldName) ? getAvailableFieldsInternal().get(fieldName) : fieldName;
|
||||
}
|
||||
|
||||
public boolean isFieldAvailable(String fieldName) {
|
||||
return getAvailableFieldsInternal().containsKey(ReferenceUtil.safeNonReference(fieldName));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterAvailableField(String fieldName) {
|
||||
getAvailableFieldsInternal().remove(fieldName);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,286 @@
|
||||
/*
|
||||
* Copyright 2013 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.aggregation;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExposedField;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CompositeIterator;
|
||||
|
||||
/**
|
||||
* Value object to capture the fields exposed by an {@link AggregationOperation}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @since 1.3
|
||||
*/
|
||||
public class ExposedFields implements Iterable<ExposedField> {
|
||||
|
||||
private static final List<ExposedField> NO_FIELDS = Collections.emptyList();
|
||||
private static final ExposedFields EMPTY = new ExposedFields(NO_FIELDS, NO_FIELDS);
|
||||
|
||||
private final List<ExposedField> originalFields;
|
||||
private final List<ExposedField> syntheticFields;
|
||||
|
||||
/**
|
||||
* Creates a new {@link ExposedFields} instance from the given {@link ExposedField}s.
|
||||
*
|
||||
* @param fields must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public static ExposedFields from(ExposedField... fields) {
|
||||
return from(Arrays.asList(fields));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link ExposedFields} instance from the given {@link ExposedField}s.
|
||||
*
|
||||
* @param fields must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
private static ExposedFields from(List<ExposedField> fields) {
|
||||
|
||||
ExposedFields result = EMPTY;
|
||||
|
||||
for (ExposedField field : fields) {
|
||||
result = result.and(field);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates synthetic {@link ExposedFields} from the given {@link Fields}.
|
||||
*
|
||||
* @param fields must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public static ExposedFields synthetic(Fields fields) {
|
||||
return createFields(fields, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates non-synthetic {@link ExposedFields} from the given {@link Fields}.
|
||||
*
|
||||
* @param fields must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public static ExposedFields nonSynthetic(Fields fields) {
|
||||
return createFields(fields, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link ExposedFields} instance for the given fields in either sythetic or non-synthetic way.
|
||||
*
|
||||
* @param fields must not be {@literal null}.
|
||||
* @param synthetic
|
||||
* @return
|
||||
*/
|
||||
private static ExposedFields createFields(Fields fields, boolean synthetic) {
|
||||
|
||||
Assert.notNull(fields, "Fields must not be null!");
|
||||
List<ExposedField> result = new ArrayList<ExposedField>();
|
||||
|
||||
for (Field field : fields) {
|
||||
result.add(new ExposedField(field, synthetic));
|
||||
}
|
||||
|
||||
return ExposedFields.from(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link ExposedFields} with the given orignals and synthetics.
|
||||
*
|
||||
* @param originals must not be {@literal null}.
|
||||
* @param synthetic must not be {@literal null}.
|
||||
*/
|
||||
private ExposedFields(List<ExposedField> originals, List<ExposedField> synthetic) {
|
||||
|
||||
this.originalFields = originals;
|
||||
this.syntheticFields = synthetic;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link ExposedFields} adding the given {@link ExposedField}.
|
||||
*
|
||||
* @param field must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public ExposedFields and(ExposedField field) {
|
||||
|
||||
Assert.notNull(field, "Exposed field must not be null!");
|
||||
|
||||
ArrayList<ExposedField> result = new ArrayList<ExposedField>();
|
||||
result.addAll(field.synthetic ? syntheticFields : originalFields);
|
||||
result.add(field);
|
||||
|
||||
return new ExposedFields(field.synthetic ? originalFields : result, field.synthetic ? result : syntheticFields);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the field with the given name or {@literal null} if no field with the given name is available.
|
||||
*
|
||||
* @param name
|
||||
* @return
|
||||
*/
|
||||
public ExposedField getField(String name) {
|
||||
|
||||
for (ExposedField field : this) {
|
||||
if (field.canBeReferredToBy(name)) {
|
||||
return field;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the {@link ExposedFields} exposes a single field only.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean exposesSingleFieldOnly() {
|
||||
return originalFields.size() + syntheticFields.size() == 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Iterable#iterator()
|
||||
*/
|
||||
@Override
|
||||
public Iterator<ExposedField> iterator() {
|
||||
|
||||
CompositeIterator<ExposedField> iterator = new CompositeIterator<ExposedField>();
|
||||
iterator.add(syntheticFields.iterator());
|
||||
iterator.add(originalFields.iterator());
|
||||
|
||||
return iterator;
|
||||
}
|
||||
|
||||
/**
|
||||
* A single exposed field.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
static class ExposedField implements Field {
|
||||
|
||||
private final boolean synthetic;
|
||||
private final Field field;
|
||||
|
||||
/**
|
||||
* Creates a new {@link ExposedField} with the given key.
|
||||
*
|
||||
* @param key must not be {@literal null} or empty.
|
||||
* @param synthetic whether the exposed field is synthetic.
|
||||
*/
|
||||
public ExposedField(String key, boolean synthetic) {
|
||||
this(Fields.field(key), synthetic);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link ExposedField} for the given {@link Field}.
|
||||
*
|
||||
* @param delegate must not be {@literal null}.
|
||||
* @param synthetic whether the exposed field is synthetic.
|
||||
*/
|
||||
public ExposedField(Field delegate, boolean synthetic) {
|
||||
|
||||
this.field = delegate;
|
||||
this.synthetic = synthetic;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.aggregation.Field#getKey()
|
||||
*/
|
||||
@Override
|
||||
public String getName() {
|
||||
return field.getName();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.aggregation.Field#getTarget()
|
||||
*/
|
||||
@Override
|
||||
public String getTarget() {
|
||||
return field.getTarget();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the field can be referred to using the given name.
|
||||
*
|
||||
* @param input
|
||||
* @return
|
||||
*/
|
||||
public boolean canBeReferredToBy(String input) {
|
||||
return getTarget().equals(input);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("AggregationField: %s, synthetic: %s", field, synthetic);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A reference to an {@link ExposedField}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
static class FieldReference {
|
||||
|
||||
private final ExposedField field;
|
||||
|
||||
/**
|
||||
* Creates a new {@link FieldReference} for the given {@link ExposedField}.
|
||||
*
|
||||
* @param field must not be {@literal null}.
|
||||
*/
|
||||
public FieldReference(ExposedField field) {
|
||||
|
||||
Assert.notNull(field, "ExposedField must not be null!");
|
||||
this.field = field;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the raw, unqualified reference, i.e. the field reference without a {@literal $} prefix.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String getRaw() {
|
||||
String target = field.getTarget();
|
||||
return field.synthetic ? target : String.format("%s.%s", Fields.UNDERSCORE_ID, target);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("$%s", getRaw());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright 2013 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.aggregation;
|
||||
|
||||
import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExposedField;
|
||||
import org.springframework.data.mongodb.core.aggregation.ExposedFields.FieldReference;
|
||||
|
||||
import com.mongodb.DBObject;
|
||||
|
||||
/**
|
||||
* Support class to implement {@link AggregationOperation}s that will become an {@link AggregationOperationContext} as
|
||||
* well defining {@link ExposedFields}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @since 1.3
|
||||
*/
|
||||
public abstract class ExposedFieldsAggregationOperationContext implements AggregationOperationContext {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.aggregation.AggregationOperationContext#getMappedObject(com.mongodb.DBObject)
|
||||
*/
|
||||
@Override
|
||||
public DBObject getMappedObject(DBObject dbObject) {
|
||||
return dbObject;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.aggregation.AggregationOperationContext#getReference(org.springframework.data.mongodb.core.aggregation.ExposedFields.AvailableField)
|
||||
*/
|
||||
@Override
|
||||
public FieldReference getReference(Field field) {
|
||||
return getReference(field.getName());
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.aggregation.AggregationOperationContext#getReference(java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public FieldReference getReference(String name) {
|
||||
|
||||
ExposedField field = getFields().getField(name);
|
||||
|
||||
if (field != null) {
|
||||
return new FieldReference(field);
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException(String.format("Invalid reference '%s'!", name));
|
||||
}
|
||||
|
||||
protected abstract ExposedFields getFields();
|
||||
}
|
||||
@@ -15,17 +15,25 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core.aggregation;
|
||||
|
||||
import com.mongodb.BasicDBObject;
|
||||
import com.mongodb.DBObject;
|
||||
|
||||
/**
|
||||
* Represents a skippable AggregationOperation that is not considered for execution.
|
||||
* Abstraction for a field.
|
||||
*
|
||||
* @author Thomas Darimont
|
||||
* @author Oliver Gierke
|
||||
* @since 1.3
|
||||
*/
|
||||
public class NoopAggreationOperation implements AggregationOperation {
|
||||
public interface Field {
|
||||
|
||||
public DBObject toDbObject() {
|
||||
return new BasicDBObject();
|
||||
}
|
||||
/**
|
||||
* Returns the name of the field.
|
||||
*
|
||||
* @return must not be {@literal null}.
|
||||
*/
|
||||
String getName();
|
||||
|
||||
/**
|
||||
* Returns the target of the field. In case no explicit target is available {@link #getName()} should be returned.
|
||||
*
|
||||
* @return must not be {@literal null}.
|
||||
*/
|
||||
String getTarget();
|
||||
}
|
||||
@@ -15,17 +15,221 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core.aggregation;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Fields is a collection of key-value pairs.
|
||||
* Value object to capture a list of {@link Field} instances.
|
||||
*
|
||||
* @author Thomas Darimont
|
||||
* @author Oliver Gierke
|
||||
* @since 1.3
|
||||
*/
|
||||
public interface Fields {
|
||||
Map<String, Object> getValues();
|
||||
public class Fields implements Iterable<Field> {
|
||||
|
||||
Fields and(String name);
|
||||
private static final String AMBIGUOUS_EXCEPTION = "Found two fields both using '%s' as name: %s and %s! Please "
|
||||
+ "customize your field definitions to get to unique field names!";
|
||||
|
||||
Fields and(String name, Object value);
|
||||
public static String UNDERSCORE_ID = "_id";
|
||||
public static String UNDERSCORE_ID_REF = "$_id";
|
||||
|
||||
private final List<Field> fields;
|
||||
|
||||
/**
|
||||
* Creates a new {@link Fields} instance from the given {@link Fields}.
|
||||
*
|
||||
* @param fields must not be {@literal null} or empty.
|
||||
* @return
|
||||
*/
|
||||
public static Fields from(Field... fields) {
|
||||
|
||||
Assert.notNull(fields, "Fields must not be null!");
|
||||
return new Fields(Arrays.asList(fields));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link Fields} instance for {@link Field}s with the given names.
|
||||
*
|
||||
* @param names must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public static Fields fields(String... names) {
|
||||
|
||||
Assert.notNull(names, "Field names must not be null!");
|
||||
|
||||
List<Field> fields = new ArrayList<Field>();
|
||||
|
||||
for (String name : names) {
|
||||
fields.add(field(name));
|
||||
}
|
||||
|
||||
return new Fields(fields);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link Field} with the given name.
|
||||
*
|
||||
* @param name must not be {@literal null} or empty.
|
||||
* @return
|
||||
*/
|
||||
public static Field field(String name) {
|
||||
return new AggregationField(name);
|
||||
}
|
||||
|
||||
public static Field field(String name, String target) {
|
||||
Assert.hasText(target, "Target must not be null or empty!");
|
||||
return new AggregationField(name, target);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link Fields} instance using the given {@link Field}s.
|
||||
*
|
||||
* @param fields must not be {@literal null}.
|
||||
*/
|
||||
private Fields(List<Field> fields) {
|
||||
|
||||
Assert.notNull(fields, "Fields must not be null!");
|
||||
|
||||
this.fields = verify(fields);
|
||||
}
|
||||
|
||||
private static final List<Field> verify(List<Field> fields) {
|
||||
|
||||
Map<String, Field> reference = new HashMap<String, Field>();
|
||||
|
||||
for (Field field : fields) {
|
||||
|
||||
String name = field.getName();
|
||||
Field found = reference.get(name);
|
||||
|
||||
if (found != null) {
|
||||
throw new IllegalArgumentException(String.format(AMBIGUOUS_EXCEPTION, name, found, field));
|
||||
}
|
||||
|
||||
reference.put(name, field);
|
||||
}
|
||||
|
||||
return fields;
|
||||
}
|
||||
|
||||
private Fields(Fields existing, Field tail) {
|
||||
|
||||
this.fields = new ArrayList<Field>(existing.fields.size() + 1);
|
||||
this.fields.addAll(existing.fields);
|
||||
this.fields.add(tail);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link Fields} instance with a new {@link Field} of the given name added.
|
||||
*
|
||||
* @param name must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public Fields and(String name) {
|
||||
return and(new AggregationField(name));
|
||||
}
|
||||
|
||||
public Fields and(String name, String target) {
|
||||
return and(new AggregationField(name, target));
|
||||
}
|
||||
|
||||
public Fields and(Field field) {
|
||||
return new Fields(this, field);
|
||||
}
|
||||
|
||||
public Fields and(Fields fields) {
|
||||
|
||||
Fields result = this;
|
||||
|
||||
for (Field field : fields) {
|
||||
result = result.and(field);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public Field getField(String name) {
|
||||
|
||||
for (Field field : fields) {
|
||||
if (field.getName().equals(name)) {
|
||||
return field;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Iterable#iterator()
|
||||
*/
|
||||
@Override
|
||||
public Iterator<Field> iterator() {
|
||||
return fields.iterator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Value object to encapsulate a field in an aggregation operation.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
static class AggregationField implements Field {
|
||||
|
||||
private final String name;
|
||||
private final String target;
|
||||
|
||||
/**
|
||||
* Creates an aggregation fieldwith the given name. As no target is set explicitly, the name will be used as target
|
||||
* as well.
|
||||
*
|
||||
* @param key
|
||||
*/
|
||||
public AggregationField(String key) {
|
||||
this(key, null);
|
||||
}
|
||||
|
||||
public AggregationField(String name, String target) {
|
||||
|
||||
Assert.hasText(name, "AggregationField name must not be null or empty!");
|
||||
|
||||
if (target == null && name.contains(".")) {
|
||||
this.name = name.substring(name.indexOf(".") + 1);
|
||||
this.target = name;
|
||||
} else {
|
||||
this.name = name;
|
||||
this.target = target;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.aggregation.Field#getKey()
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.aggregation.Field#getAlias()
|
||||
*/
|
||||
public String getTarget() {
|
||||
return StringUtils.hasText(this.target) ? this.target : this.name;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("AggregationField - name: %s, target: %s", name, target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,21 +18,29 @@ package org.springframework.data.mongodb.core.aggregation;
|
||||
import org.springframework.data.mongodb.core.query.NearQuery;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.mongodb.BasicDBObject;
|
||||
import com.mongodb.DBObject;
|
||||
|
||||
/**
|
||||
* @author Thomas Darimont
|
||||
* @since 1.3
|
||||
*/
|
||||
public class GeoNearOperation extends AbstractAggregateOperation {
|
||||
public class GeoNearOperation implements AggregationOperation {
|
||||
|
||||
private final NearQuery nearQuery;
|
||||
|
||||
public GeoNearOperation(NearQuery nearQuery) {
|
||||
super("geoNear");
|
||||
|
||||
Assert.notNull(nearQuery);
|
||||
this.nearQuery = nearQuery;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.aggregation.AggregationOperation#toDBObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext)
|
||||
*/
|
||||
@Override
|
||||
public Object getOperationArgument() {
|
||||
return nearQuery.toDBObject();
|
||||
public DBObject toDBObject(AggregationOperationContext context) {
|
||||
return new BasicDBObject("$geoNear", context.getMappedObject(nearQuery.toDBObject()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,9 +17,12 @@ package org.springframework.data.mongodb.core.aggregation;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExposedField;
|
||||
import org.springframework.data.mongodb.core.aggregation.ExposedFields.FieldReference;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.mongodb.BasicDBObject;
|
||||
import com.mongodb.DBObject;
|
||||
@@ -30,392 +33,232 @@ import com.mongodb.DBObject;
|
||||
* @see http://docs.mongodb.org/manual/reference/aggregation/group/#stage._S_group
|
||||
* @author Sebastian Herold
|
||||
* @author Thomas Darimont
|
||||
* @author Oliver Gierke
|
||||
* @since 1.3
|
||||
*/
|
||||
public class GroupOperation extends AbstractContextProducingAggregateOperation {
|
||||
public class GroupOperation extends ExposedFieldsAggregationOperationContext implements AggregationOperation {
|
||||
|
||||
final Object id;
|
||||
final List<GroupingOperation> ops = new ArrayList<GroupOperation.GroupingOperation>();
|
||||
private final ExposedFields nonSynthecticFields;
|
||||
private final List<Operation> operations;
|
||||
|
||||
/**
|
||||
* Creates a <code>$group</code> operation with <code>_id</code> referencing to a field of the document. The returned
|
||||
* db object equals to
|
||||
* Creates a new {@link GroupOperation} including the given {@link Fields}.
|
||||
*
|
||||
* <pre>
|
||||
* {_id: "$field"}
|
||||
* </pre>
|
||||
*
|
||||
* @param id
|
||||
* @param moreIdFields
|
||||
* @param fields must not be {@literal null}.
|
||||
*/
|
||||
public GroupOperation(Fields fields) {
|
||||
super("group");
|
||||
this.id = createGroupIdFrom(fields);
|
||||
|
||||
this.nonSynthecticFields = ExposedFields.nonSynthetic(fields);
|
||||
this.operations = new ArrayList<Operation>();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param fields
|
||||
* @return
|
||||
* Creates a new {@link GroupOperation} from the given {@link GroupOperation} and the given {@link Operation}.
|
||||
*
|
||||
* @param current must not be {@literal null}.
|
||||
* @param operation must not be {@literal null}.
|
||||
*/
|
||||
private Object createGroupIdFrom(Fields fields) {
|
||||
protected GroupOperation(GroupOperation current, Operation operation) {
|
||||
|
||||
Assert.notNull(fields, "fields must not be null!");
|
||||
Map<String, Object> values = fields.getValues();
|
||||
Assert.notEmpty(values, "fields.values must not be empty!");
|
||||
Assert.notNull(current, "GroupOperation must not be null!");
|
||||
Assert.notNull(operation, "Operation must not be null!");
|
||||
|
||||
DBObject idReferences = new BasicDBObject(values.size());
|
||||
for (Map.Entry<String, Object> entry : values.entrySet()) {
|
||||
String idFieldName = ReferenceUtil.safeNonReference(entry.getKey());
|
||||
Object idFieldValue = entry.getValue() instanceof String ? ReferenceUtil.safeReference(entry.getValue()
|
||||
.toString()) : entry.getValue();
|
||||
idReferences.put(idFieldName, idFieldValue);
|
||||
}
|
||||
return idReferences;
|
||||
this.nonSynthecticFields = current.nonSynthecticFields;
|
||||
this.operations = new ArrayList<Operation>(current.operations.size() + 1);
|
||||
this.operations.addAll(current.operations);
|
||||
this.operations.add(operation);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.aggregation.AbstractAggregateOperation#getOperationArgument()
|
||||
/**
|
||||
* Creates a new {@link GroupOperation} from the current one adding the given {@link Operation}.
|
||||
*
|
||||
* @param operation must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
protected GroupOperation and(Operation operation) {
|
||||
return new GroupOperation(this, operation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link GroupOperationBuilder} to build a grouping operation for the field with the given name
|
||||
*
|
||||
* @param field must not be {@literal null} or empty.
|
||||
* @return
|
||||
*/
|
||||
public GroupOperationBuilder and(String field) {
|
||||
return new GroupOperationBuilder(field, this);
|
||||
}
|
||||
|
||||
public class GroupOperationBuilder {
|
||||
|
||||
private final String name;
|
||||
private final GroupOperation current;
|
||||
|
||||
public GroupOperationBuilder(String name, GroupOperation current) {
|
||||
|
||||
Assert.hasText(name, "Field name must not be null or empty!");
|
||||
Assert.notNull(current, "GroupOperation must not be null!");
|
||||
|
||||
this.name = name;
|
||||
this.current = current;
|
||||
}
|
||||
|
||||
public GroupOperation count() {
|
||||
return sum(1);
|
||||
}
|
||||
|
||||
public GroupOperation count(String reference) {
|
||||
return sum(reference, 1);
|
||||
}
|
||||
|
||||
public GroupOperation sum() {
|
||||
return sum(name);
|
||||
}
|
||||
|
||||
public GroupOperation sum(String reference) {
|
||||
return sum(reference, null);
|
||||
}
|
||||
|
||||
public GroupOperation sum(Object value) {
|
||||
return sum(null, value);
|
||||
}
|
||||
|
||||
public GroupOperation sum(String reference, Object value) {
|
||||
return current.and(new Operation(GroupOps.SUM, name, reference, value));
|
||||
}
|
||||
|
||||
public GroupOperation addToSet() {
|
||||
return addToSet(null);
|
||||
}
|
||||
|
||||
public GroupOperation addToSet(String reference) {
|
||||
return current.and(new Operation(GroupOps.ADD_TO_SET, name, reference, null));
|
||||
}
|
||||
|
||||
public GroupOperation last() {
|
||||
return last(null);
|
||||
}
|
||||
|
||||
public GroupOperation last(String reference) {
|
||||
return current.and(new Operation(GroupOps.LAST, name, reference, null));
|
||||
}
|
||||
|
||||
public GroupOperation first() {
|
||||
return first(null);
|
||||
}
|
||||
|
||||
public GroupOperation first(String reference) {
|
||||
return current.and(new Operation(GroupOps.FIRST, name, reference, null));
|
||||
}
|
||||
|
||||
public GroupOperation avg() {
|
||||
return avg(null);
|
||||
}
|
||||
|
||||
public GroupOperation avg(String reference) {
|
||||
return current.and(new Operation(GroupOps.AVG, name, reference, null));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.aggregation.AggregationOperationContext#getFields()
|
||||
*/
|
||||
@Override
|
||||
public Object getOperationArgument(AggregateOperationContext inputAggregateOperationContext) {
|
||||
public ExposedFields getFields() {
|
||||
|
||||
DBObject projection = new BasicDBObject();
|
||||
ExposedFields fields = this.nonSynthecticFields.and(new ExposedField(Fields.UNDERSCORE_ID, true));
|
||||
|
||||
Object idToUse = id;
|
||||
if (idToUse instanceof DBObject) {
|
||||
idToUse = createGroupIdObject((DBObject) idToUse, inputAggregateOperationContext);
|
||||
}
|
||||
projection.put(ReferenceUtil.ID_KEY, idToUse);
|
||||
|
||||
for (GroupingOperation op : ops) {
|
||||
projection.put(op.alias, op.toDbObject(inputAggregateOperationContext));
|
||||
for (Operation operation : operations) {
|
||||
fields = fields.and(operation.asField());
|
||||
}
|
||||
|
||||
return projection;
|
||||
return fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param idCandidate
|
||||
* @param inputAggregateOperationContext
|
||||
* @return
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.aggregation.AggregationOperation#toDBObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext)
|
||||
*/
|
||||
private Object createGroupIdObject(DBObject groupIdObject, AggregateOperationContext inputAggregateOperationContext) {
|
||||
@Override
|
||||
public com.mongodb.DBObject toDBObject(AggregationOperationContext context) {
|
||||
|
||||
Object simpleIdOrNull = returnIfGroupIdIsSingleFieldReference(inputAggregateOperationContext, groupIdObject);
|
||||
if (simpleIdOrNull != null) {
|
||||
return simpleIdOrNull;
|
||||
}
|
||||
BasicDBObject operationObject = new BasicDBObject();
|
||||
|
||||
DBObject idObject = new BasicDBObject();
|
||||
for (String idFieldName : groupIdObject.keySet()) {
|
||||
if (nonSynthecticFields.exposesSingleFieldOnly()) {
|
||||
|
||||
Object idFieldValue = groupIdObject.get(idFieldName);
|
||||
Object idFieldValueOrNull = returnIfFieldValueReferencesAvailableField(inputAggregateOperationContext,
|
||||
idFieldName, idFieldValue);
|
||||
if (idFieldValueOrNull != null) {
|
||||
idFieldValue = idFieldValueOrNull;
|
||||
FieldReference reference = context.getReference(nonSynthecticFields.iterator().next());
|
||||
operationObject.put(Fields.UNDERSCORE_ID, reference.toString());
|
||||
|
||||
} else {
|
||||
|
||||
BasicDBObject inner = new BasicDBObject();
|
||||
|
||||
for (ExposedField field : nonSynthecticFields) {
|
||||
FieldReference reference = context.getReference(field);
|
||||
inner.put(field.getName(), reference.toString());
|
||||
}
|
||||
|
||||
getOutputAggregateOperationContext().registerAvailableField(idFieldName, ReferenceUtil.id(idFieldName));
|
||||
idObject.put(idFieldName, idFieldValue);
|
||||
operationObject.put(Fields.UNDERSCORE_ID, inner);
|
||||
}
|
||||
|
||||
return idObject;
|
||||
}
|
||||
|
||||
private Object returnIfGroupIdIsSingleFieldReference(AggregateOperationContext inputAggregateOperationContext,
|
||||
DBObject idObject) {
|
||||
|
||||
if (idObject.keySet().size() != 1) {
|
||||
return null;
|
||||
for (Operation operation : operations) {
|
||||
operationObject.putAll(operation.toDBObject(context));
|
||||
}
|
||||
|
||||
return returnIfFieldNameIsSimpleReference(inputAggregateOperationContext, idObject, idObject.keySet().iterator()
|
||||
.next());
|
||||
return new BasicDBObject("$group", operationObject);
|
||||
}
|
||||
|
||||
private Object returnIfFieldValueReferencesAvailableField(AggregateOperationContext inputAggregateOperationContext,
|
||||
String idFieldName, Object idFieldValue) {
|
||||
interface Keyword {
|
||||
|
||||
Assert.notNull(inputAggregateOperationContext, "inputAggregateOperationContext must not be null");
|
||||
|
||||
if (!ReferenceUtil.isValueFieldReference(idFieldName, idFieldValue)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!inputAggregateOperationContext.isFieldAvailable(idFieldName)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String idFieldNameToUse = inputAggregateOperationContext.returnFieldNameAliasIfAvailableOr(idFieldName);
|
||||
return ReferenceUtil.safeReference(inputAggregateOperationContext instanceof GroupOperation ? ReferenceUtil
|
||||
.id(idFieldNameToUse) : idFieldNameToUse);
|
||||
String toString();
|
||||
}
|
||||
|
||||
private Object returnIfFieldNameIsSimpleReference(AggregateOperationContext inputAggregateOperationContext,
|
||||
DBObject idObject, String idFieldName) {
|
||||
private static enum GroupOps implements Keyword {
|
||||
|
||||
Object idFieldValue = idObject.get(idFieldName);
|
||||
SUM, LAST, FIRST, PUSH, AVG, MIN, MAX, ADD_TO_SET, COUNT;
|
||||
|
||||
if (!idFieldValueIsSimpleIdFieldExpression(idFieldName, idFieldValue)) {
|
||||
return null;
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
|
||||
getOutputAggregateOperationContext().registerAvailableField(idFieldName, ReferenceUtil.id(idFieldName));
|
||||
idFieldValue = ReferenceUtil.safeReference(inputAggregateOperationContext
|
||||
.returnFieldNameAliasIfAvailableOr(idFieldName));
|
||||
String[] parts = name().split("_");
|
||||
|
||||
return idFieldValue;
|
||||
}
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
private static boolean idFieldValueIsSimpleIdFieldExpression(String idFieldName, Object idFieldValue) {
|
||||
return idFieldValue instanceof String
|
||||
&& idFieldName.equals(ReferenceUtil.safeNonReference(idFieldValue.toString()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a field with the <a
|
||||
* href="http://docs.mongodb.org/manual/reference/aggregation/addToSet/#grp._S_addToSet">$addToSet operation</a>.
|
||||
*
|
||||
* <pre>
|
||||
* { $group : {
|
||||
* _id : "$id_field",
|
||||
* name : { $addToSet : "$field" }
|
||||
* }}
|
||||
* </pre>
|
||||
*
|
||||
* @see http://docs.mongodb.org/manual/reference/aggregation/addToSet/#grp._S_addToSet
|
||||
* @param name key of the field.
|
||||
* @param field reference to a field of the document.
|
||||
* @return
|
||||
*/
|
||||
public GroupOperation addToSet(String name, String field) {
|
||||
return addOperation("$addToSet", name, field);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a field with the {@code $first} operation.
|
||||
*
|
||||
* <pre>
|
||||
* { $group : {
|
||||
* _id : "$id_field",
|
||||
* name : { $first : "$field" }
|
||||
* }}
|
||||
* </pre>
|
||||
*
|
||||
* @see http://docs.mongodb.org/manual/reference/aggregation/addToSet/#grp._S_first
|
||||
* @param name key of the field
|
||||
* @param field reference to a field of the document
|
||||
* @return
|
||||
*/
|
||||
public GroupOperation first(String name, String field) {
|
||||
return addOperation("$first", name, field);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a field with the <a href="http://docs.mongodb.org/manual/reference/aggregation/addToSet/#grp._S_last">$last
|
||||
* operation</a>.
|
||||
*
|
||||
* <pre>
|
||||
* {$group: {
|
||||
* _id: "$id_field",
|
||||
* name: {$last: "$field"}
|
||||
* }}
|
||||
* </pre>
|
||||
*
|
||||
* @param name key of the field
|
||||
* @param field reference to a field of the document
|
||||
* @return
|
||||
*/
|
||||
public GroupOperation last(String name, String field) {
|
||||
return addOperation("$last", name, field);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a field with the <a href="http://docs.mongodb.org/manual/reference/aggregation/addToSet/#grp._S_max">$max
|
||||
* operation</a>.
|
||||
*
|
||||
* <pre>
|
||||
* {$group: {
|
||||
* _id: "$id_field",
|
||||
* name: {$max: "$field"}
|
||||
* }}
|
||||
* </pre>
|
||||
*
|
||||
* @param name key of the field
|
||||
* @param field reference to a field of the document
|
||||
* @return
|
||||
*/
|
||||
public GroupOperation max(String name, String field) {
|
||||
return addOperation("$max", name, field);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a field with the <a href="http://docs.mongodb.org/manual/reference/aggregation/addToSet/#grp._S_min">$min
|
||||
* operation</a>.
|
||||
*
|
||||
* <pre>
|
||||
* {$group: {
|
||||
* _id: "$id_field",
|
||||
* name: {$min: "$field"}
|
||||
* }}
|
||||
* </pre>
|
||||
*
|
||||
* @param name key of the field
|
||||
* @param field reference to a field of the document
|
||||
* @return
|
||||
*/
|
||||
public GroupOperation min(String name, String field) {
|
||||
return addOperation("$min", name, field);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a field with the <a href="http://docs.mongodb.org/manual/reference/aggregation/addToSet/#grp._S_avg">$avg
|
||||
* operation</a>.
|
||||
*
|
||||
* <pre>
|
||||
* {$group: {
|
||||
* _id: "$id_field",
|
||||
* name: {$avg: "$field"}
|
||||
* }}
|
||||
* </pre>
|
||||
*
|
||||
* @param name key of the field
|
||||
* @param field reference to a field of the document
|
||||
* @return
|
||||
*/
|
||||
public GroupOperation avg(String name, String field) {
|
||||
return addOperation("$avg", name, field);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a field with the <a href="http://docs.mongodb.org/manual/reference/aggregation/addToSet/#grp._S_push">$push
|
||||
* operation</a>.
|
||||
*
|
||||
* <pre>
|
||||
* {$group: {
|
||||
* _id: "$id_field",
|
||||
* name: {$push: "$field"}
|
||||
* }}
|
||||
* </pre>
|
||||
*
|
||||
* @param name key of the field
|
||||
* @param field reference to a field of the document
|
||||
* @return
|
||||
*/
|
||||
public GroupOperation push(String name, String field) {
|
||||
return addOperation("$push", name, field);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a field with the <a href="http://docs.mongodb.org/manual/reference/aggregation/addToSet/#grp._S_sum">$sum
|
||||
* operation</a> with a constant value.
|
||||
*
|
||||
* <pre>
|
||||
* {$group: {
|
||||
* _id: "$id_field",
|
||||
* name: {$sum: increment}
|
||||
* }}
|
||||
* </pre>
|
||||
*
|
||||
* @param name key of the field
|
||||
* @param increment increment for each item
|
||||
* @return
|
||||
*/
|
||||
public GroupOperation count(String name, double increment) {
|
||||
return sum(name, increment);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a field with the <a href="http://docs.mongodb.org/manual/reference/aggregation/addToSet/#grp._S_sum">$sum
|
||||
* operation</a> count every item.
|
||||
*
|
||||
* <pre>
|
||||
* {$group: {
|
||||
* _id: "$id_field",
|
||||
* name: {$sum: 1}
|
||||
* }}
|
||||
* </pre>
|
||||
*
|
||||
* @param name key of the field
|
||||
* @return
|
||||
*/
|
||||
public GroupOperation count(String name) {
|
||||
return count(name, 1);
|
||||
}
|
||||
|
||||
private GroupOperation sum(String name, Object field) {
|
||||
return addOperation("$sum", name, field);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a field with the <a href="http://docs.mongodb.org/manual/reference/aggregation/addToSet/#grp._S_sum">$sum
|
||||
* operation</a>.
|
||||
*
|
||||
* <pre>
|
||||
* {$group: {
|
||||
* _id: "$id_field",
|
||||
* name: {$sum: "$field"}
|
||||
* }}
|
||||
* </pre>
|
||||
*
|
||||
* @param name key of the field
|
||||
* @param field reference to a field of the document
|
||||
* @return
|
||||
*/
|
||||
public GroupOperation sum(String name, String field) {
|
||||
return sum(name, (Object) field);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a field with the <a href="http://docs.mongodb.org/manual/reference/aggregation/addToSet/#grp._S_sum">$sum
|
||||
* operation</a>.
|
||||
*
|
||||
* <pre>
|
||||
* {$group: {
|
||||
* _id: "$id_field",
|
||||
* field: {$sum: "$field"}
|
||||
* }}
|
||||
* </pre>
|
||||
*
|
||||
* @param field reference to a field of the document
|
||||
* @return
|
||||
*/
|
||||
public GroupOperation sum(String field) {
|
||||
return sum(field, field);
|
||||
}
|
||||
|
||||
protected GroupOperation addOperation(String operation, String name, Object field) {
|
||||
|
||||
getOutputAggregateOperationContext().registerAvailableField(name);
|
||||
this.ops.add(new GroupingOperation(operation, name, field));
|
||||
return this;
|
||||
}
|
||||
|
||||
static class GroupingOperation {
|
||||
final String operation;
|
||||
final String alias;
|
||||
final Object fieldNameOrValue;
|
||||
|
||||
public GroupingOperation(String operation, String alias, Object fieldNameOrValue) {
|
||||
this.operation = operation;
|
||||
this.alias = alias;
|
||||
this.fieldNameOrValue = fieldNameOrValue;
|
||||
}
|
||||
|
||||
public DBObject toDbObject(AggregateOperationContext inputAggregateOperationContext) {
|
||||
|
||||
Object fieldNameOrValueToUse = fieldNameOrValue;
|
||||
|
||||
if (fieldNameOrValue instanceof String) {
|
||||
if (inputAggregateOperationContext != null) {
|
||||
fieldNameOrValueToUse = inputAggregateOperationContext
|
||||
.returnFieldNameAliasIfAvailableOr((String) fieldNameOrValueToUse);
|
||||
}
|
||||
fieldNameOrValueToUse = ReferenceUtil.safeReference((String) fieldNameOrValueToUse);
|
||||
for (String part : parts) {
|
||||
String lowerCase = part.toLowerCase(Locale.US);
|
||||
builder.append(builder.length() == 0 ? lowerCase : StringUtils.capitalize(lowerCase));
|
||||
}
|
||||
|
||||
return new BasicDBObject(operation, fieldNameOrValueToUse);
|
||||
return "$" + builder.toString();
|
||||
}
|
||||
}
|
||||
|
||||
static class Operation implements AggregationOperation {
|
||||
|
||||
private final Keyword op;
|
||||
private final String key;
|
||||
private final String reference;
|
||||
private final Object value;
|
||||
|
||||
public Operation(Keyword op, String key, String reference, Object value) {
|
||||
|
||||
this.op = op;
|
||||
this.key = key;
|
||||
this.reference = reference;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public ExposedField asField() {
|
||||
return new ExposedField(key, true);
|
||||
}
|
||||
|
||||
public DBObject toDBObject(AggregationOperationContext context) {
|
||||
return new BasicDBObject(key, new BasicDBObject(op.toString(), getValue(context)));
|
||||
}
|
||||
|
||||
public Object getValue(AggregationOperationContext context) {
|
||||
return reference == null ? value : context.getReference(reference).toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,13 +15,20 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core.aggregation;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.mongodb.BasicDBObject;
|
||||
import com.mongodb.DBObject;
|
||||
|
||||
/**
|
||||
* Encapsulates the {@code $limit}-operation
|
||||
*
|
||||
* @see http://docs.mongodb.org/manual/reference/aggregation/limit/
|
||||
* @author Thomas Darimont
|
||||
* @author Oliver Gierke
|
||||
* @since 1.3
|
||||
*/
|
||||
class LimitOperation extends AbstractAggregateOperation {
|
||||
class LimitOperation implements AggregationOperation {
|
||||
|
||||
private final long maxElements;
|
||||
|
||||
@@ -29,12 +36,17 @@ class LimitOperation extends AbstractAggregateOperation {
|
||||
* @param maxElements Number of documents to consider.
|
||||
*/
|
||||
public LimitOperation(long maxElements) {
|
||||
super("limit");
|
||||
|
||||
Assert.isTrue(maxElements >= 0, "Maximum number of elements must be greater or equal to zero!");
|
||||
this.maxElements = maxElements;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.aggregation.AggregationOperation#toDBObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext)
|
||||
*/
|
||||
@Override
|
||||
public Object getOperationArgument() {
|
||||
return maxElements;
|
||||
public DBObject toDBObject(AggregationOperationContext context) {
|
||||
return new BasicDBObject("$limit", maxElements);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ package org.springframework.data.mongodb.core.aggregation;
|
||||
import org.springframework.data.mongodb.core.query.Criteria;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.mongodb.BasicDBObject;
|
||||
import com.mongodb.DBObject;
|
||||
|
||||
/**
|
||||
@@ -26,10 +27,12 @@ import com.mongodb.DBObject;
|
||||
* @see http://docs.mongodb.org/manual/reference/aggregation/match/
|
||||
* @author Sebastian Herold
|
||||
* @author Thomas Darimont
|
||||
* @author Oliver Gierke
|
||||
* @since 1.3
|
||||
*/
|
||||
public class MatchOperation extends AbstractAggregateOperation {
|
||||
private final DBObject criteria;
|
||||
public class MatchOperation implements AggregationOperation {
|
||||
|
||||
private final Criteria criteria;
|
||||
|
||||
/**
|
||||
* Creates a new {@link MatchOperation} for the given {@link Criteria}.
|
||||
@@ -38,16 +41,16 @@ public class MatchOperation extends AbstractAggregateOperation {
|
||||
*/
|
||||
public MatchOperation(Criteria criteria) {
|
||||
|
||||
super("match");
|
||||
Assert.notNull(criteria, "Criteria must not be null!");
|
||||
this.criteria = criteria.getCriteriaObject();
|
||||
this.criteria = criteria;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.aggregation.AbstractAggregateOperation#getOperationArgument()
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.aggregation.AggregationOperation#toDBObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext)
|
||||
*/
|
||||
@Override
|
||||
public Object getOperationArgument() {
|
||||
return criteria;
|
||||
public DBObject toDBObject(AggregationOperationContext context) {
|
||||
return new BasicDBObject("$match", context.getMappedObject(criteria.getCriteriaObject()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,268 +16,386 @@
|
||||
package org.springframework.data.mongodb.core.aggregation;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.EmptyStackException;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Stack;
|
||||
|
||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
import org.springframework.data.mongodb.core.query.Field;
|
||||
import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExposedField;
|
||||
import org.springframework.data.mongodb.core.aggregation.ExposedFields.FieldReference;
|
||||
import org.springframework.data.mongodb.core.aggregation.ProjectionOperation.ProjectionOperationBuilder.FieldProjection;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.mongodb.BasicDBObject;
|
||||
import com.mongodb.DBObject;
|
||||
|
||||
/**
|
||||
* Encapsulates the aggregation framework {@code $project}-operation.
|
||||
* <p>
|
||||
* Projection of field to be used in an {@link Aggregation}. A projection is similar to a {@link Field}
|
||||
* inclusion/exclusion but more powerful. It can generate new fields, change values of given field etc.
|
||||
* Encapsulates the aggregation framework {@code $project}-operation. Projection of field to be used in an
|
||||
* {@link Aggregation}. A projection is similar to a {@link Field} inclusion/exclusion but more powerful. It can
|
||||
* generate new fields, change values of given field etc.
|
||||
* <p>
|
||||
*
|
||||
* @see http://docs.mongodb.org/manual/reference/aggregation/project/
|
||||
* @author Tobias Trelle
|
||||
* @author Thomas Darimont
|
||||
* @author Oliver Gierke
|
||||
* @since 1.3
|
||||
*/
|
||||
public class ProjectionOperation extends AbstractContextProducingAggregateOperation {
|
||||
public class ProjectionOperation extends ExposedFieldsAggregationOperationContext implements AggregationOperation {
|
||||
|
||||
/** Stack of key names. Size is 0 or 1. */
|
||||
private final Stack<String> reference = new Stack<String>();
|
||||
private final Map<String, Object> projection = new HashMap<String, Object>();
|
||||
private static final List<Projection> NONE = Collections.emptyList();
|
||||
|
||||
private DBObject rightHandExpression;
|
||||
private final List<Projection> projections;
|
||||
|
||||
/**
|
||||
* This convenience constructor excludes the field {@code _id} and includes the given fields.
|
||||
*
|
||||
* @param includes Keys of field to include, must not be {@literal null} or empty.
|
||||
*/
|
||||
public ProjectionOperation(String... includes) {
|
||||
public ProjectionOperation() {
|
||||
this(NONE, NONE);
|
||||
}
|
||||
|
||||
super("project");
|
||||
public ProjectionOperation(Fields fields) {
|
||||
this(NONE, ProjectionOperationBuilder.FieldProjection.from(fields, true));
|
||||
}
|
||||
|
||||
Assert.notNull(includes, "includes must not be null");
|
||||
exclude("_id");
|
||||
private ProjectionOperation(List<? extends Projection> current, List<? extends Projection> projections) {
|
||||
|
||||
for (String key : includes) {
|
||||
include(key);
|
||||
}
|
||||
this.projections = new ArrayList<ProjectionOperation.Projection>(current.size() + projections.size());
|
||||
this.projections.addAll(current);
|
||||
this.projections.addAll(projections);
|
||||
}
|
||||
|
||||
protected ProjectionOperation and(Projection projection) {
|
||||
return new ProjectionOperation(this.projections, Arrays.asList(projection));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an empty projection.
|
||||
* Creates a new {@link ProjectionOperationBuilder} to define a projection for the field with the given name.
|
||||
*
|
||||
* @param targetClass
|
||||
*/
|
||||
public ProjectionOperation(Class<?> targetClass) {
|
||||
this(extractFieldsFrom(targetClass));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param targetClass
|
||||
* @param name must not be {@literal null} or empty.
|
||||
* @return
|
||||
*/
|
||||
private static String[] extractFieldsFrom(Class<?> targetClass) {
|
||||
return new String[0];
|
||||
public ProjectionOperationBuilder and(String name) {
|
||||
return new ProjectionOperationBuilder(name, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Excludes a given field.
|
||||
* Excludes the given fields from the projection.
|
||||
*
|
||||
* @param key The key of the field.
|
||||
*/
|
||||
public final ProjectionOperation exclude(String key) {
|
||||
|
||||
Assert.hasText(key, "Missing key");
|
||||
getOutputAggregateOperationContext().unregisterAvailableField(ReferenceUtil.safeNonReference(key));
|
||||
|
||||
projection.put(key, 0);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Includes a given field.
|
||||
*
|
||||
* @param key The key of the field, must not be {@literal null} or empty.
|
||||
*/
|
||||
public final ProjectionOperation include(String key) {
|
||||
|
||||
Assert.hasText(key, "Missing key");
|
||||
|
||||
safePop();
|
||||
reference.push(key);
|
||||
getOutputAggregateOperationContext().registerAvailableField(key);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the key for a computed field.
|
||||
*
|
||||
* @param key must not be {@literal null} or empty.
|
||||
*/
|
||||
public final ProjectionOperation as(String key) {
|
||||
|
||||
Assert.hasText(key, "Missing key");
|
||||
|
||||
try {
|
||||
String rhsFieldName = reference.pop();
|
||||
getOutputAggregateOperationContext().unregisterAvailableField(ReferenceUtil.safeNonReference(rhsFieldName));
|
||||
getOutputAggregateOperationContext().registerAvailableField(ReferenceUtil.safeNonReference(key));
|
||||
projection.put(key, rightHandSide(ReferenceUtil.safeReference(rhsFieldName)));
|
||||
} catch (EmptyStackException e) {
|
||||
throw new InvalidDataAccessApiUsageException("Invalid use of as()", e);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the key for a computed field.
|
||||
*
|
||||
* @param key must not be {@literal null} or empty.
|
||||
*/
|
||||
public final ProjectionOperation asSelf() {
|
||||
|
||||
try {
|
||||
String selfRef = reference.pop();
|
||||
projection.put(selfRef, rightHandSide(ReferenceUtil.safeReference(selfRef)));
|
||||
} catch (EmptyStackException e) {
|
||||
throw new InvalidDataAccessApiUsageException("Invalid use of as()", e);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public final ProjectionOperation plus(Number n) {
|
||||
return arithmeticOperation("add", n);
|
||||
}
|
||||
|
||||
public final ProjectionOperation minus(Number n) {
|
||||
return arithmeticOperation("substract", n);
|
||||
}
|
||||
|
||||
private ProjectionOperation arithmeticOperation(String op, Number n) {
|
||||
|
||||
Assert.notNull(n, "Missing number");
|
||||
rightHandExpression = createArrayObject(op, ReferenceUtil.safeReference(reference.peek()), n);
|
||||
return this;
|
||||
}
|
||||
|
||||
private DBObject createArrayObject(String op, Object... items) {
|
||||
|
||||
List<Object> list = new ArrayList<Object>();
|
||||
Collections.addAll(list, items);
|
||||
|
||||
return new BasicDBObject(ReferenceUtil.safeReference(op), list);
|
||||
}
|
||||
|
||||
private void safePop() {
|
||||
|
||||
if (!reference.empty()) {
|
||||
projection.put(reference.pop(), rightHandSide(1));
|
||||
}
|
||||
}
|
||||
|
||||
private Object rightHandSide(Object defaultValue) {
|
||||
Object value = rightHandExpression != null ? rightHandExpression : defaultValue;
|
||||
rightHandExpression = null;
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string
|
||||
* @param projection
|
||||
* @param fields must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public ProjectionOperation addField(String key, Object value) {
|
||||
|
||||
Assert.notNull(key, "Missing Key");
|
||||
Assert.notNull(value);
|
||||
|
||||
getOutputAggregateOperationContext().registerAvailableField(key);
|
||||
registerAvailableFieldsRecursive(key, value);
|
||||
this.projection.put(key, value);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
private void registerAvailableFieldsRecursive(String outerKey, Object value) {
|
||||
|
||||
if (value instanceof Fields) {
|
||||
Map<String, Object> values = ((Fields) value).getValues();
|
||||
for (String key : values.keySet()) {
|
||||
String innerKey = outerKey + "." + key;
|
||||
getOutputAggregateOperationContext().registerAvailableField(innerKey);
|
||||
registerAvailableFieldsRecursive(innerKey, values.get(key));
|
||||
}
|
||||
}
|
||||
public ProjectionOperation andExclude(String... fields) {
|
||||
List<FieldProjection> excludeProjections = FieldProjection.from(Fields.fields(fields), false);
|
||||
return new ProjectionOperation(this.projections, excludeProjections);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param name
|
||||
* @param value
|
||||
* Includes the given fields into the projection.
|
||||
*
|
||||
* @param fields must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public ProjectionOperation field(String name, Object value) {
|
||||
addField(name, value);
|
||||
return this;
|
||||
public ProjectionOperation andInclude(String... fields) {
|
||||
|
||||
List<FieldProjection> projections = FieldProjection.from(Fields.fields(fields), true);
|
||||
return new ProjectionOperation(this.projections, projections);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.aggregation.AbstractAggregateOperation#getOperationArgument()
|
||||
/**
|
||||
* Includes the given fields into the projection.
|
||||
*
|
||||
* @param fields must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public ProjectionOperation andInclude(Fields fields) {
|
||||
return new ProjectionOperation(this.projections, FieldProjection.from(fields, true));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.aggregation.ExposedFieldsAggregationOperationContext#getFields()
|
||||
*/
|
||||
@Override
|
||||
public Object getOperationArgument(AggregateOperationContext inputAggregateOperationContext) {
|
||||
protected ExposedFields getFields() {
|
||||
|
||||
Assert.notNull(inputAggregateOperationContext, "inputAggregateOperationContext must not be null");
|
||||
safePop();
|
||||
ExposedFields fields = null;
|
||||
|
||||
DBObject projectionObject = new BasicDBObject();
|
||||
for (Map.Entry<String, Object> entry : projection.entrySet()) {
|
||||
Object fieldNameOrValueToUse = entry.getValue();
|
||||
|
||||
DBObject fieldsObject = returnIfValueIsIdFields(inputAggregateOperationContext, fieldNameOrValueToUse);
|
||||
if (fieldsObject != null) {
|
||||
projectionObject.put(entry.getKey(), fieldsObject != null ? fieldsObject : fieldNameOrValueToUse);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (fieldNameOrValueToUse instanceof String) {
|
||||
String fieldName = inputAggregateOperationContext
|
||||
.returnFieldNameAliasIfAvailableOr((String) fieldNameOrValueToUse);
|
||||
fieldNameOrValueToUse = ReferenceUtil.safeReference(fieldName);
|
||||
}
|
||||
|
||||
projectionObject.put(entry.getKey(), fieldNameOrValueToUse);
|
||||
for (Projection projection : projections) {
|
||||
ExposedField field = projection.getField();
|
||||
fields = fields == null ? ExposedFields.from(field) : fields.and(field);
|
||||
}
|
||||
|
||||
return projectionObject;
|
||||
return fields;
|
||||
}
|
||||
|
||||
private DBObject returnIfValueIsIdFields(AggregateOperationContext inputAggregateOperationContext,
|
||||
Object fieldNameOrValueToUse) {
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.aggregation.AggregationOperation#toDBObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext)
|
||||
*/
|
||||
@Override
|
||||
public DBObject toDBObject(AggregationOperationContext context) {
|
||||
|
||||
Assert.notNull(inputAggregateOperationContext, "inputAggregateOperationContext must not be null");
|
||||
BasicDBObject fieldObject = new BasicDBObject();
|
||||
|
||||
if (!(fieldNameOrValueToUse instanceof Fields)) {
|
||||
return null;
|
||||
for (Projection projection : projections) {
|
||||
fieldObject.putAll(projection.toDBObject(context));
|
||||
}
|
||||
|
||||
DBObject fieldsObject = new BasicDBObject();
|
||||
for (Map.Entry<String, Object> fieldsEntry : ((Fields) fieldNameOrValueToUse).getValues().entrySet()) {
|
||||
return new BasicDBObject("$project", fieldObject);
|
||||
}
|
||||
|
||||
Object fieldsEntryFieldNameOrValueToUse = fieldsEntry.getValue();
|
||||
if (fieldsEntryFieldNameOrValueToUse instanceof String && inputAggregateOperationContext != null) {
|
||||
String fieldName = inputAggregateOperationContext
|
||||
.returnFieldNameAliasIfAvailableOr((String) fieldsEntryFieldNameOrValueToUse);
|
||||
fieldsEntryFieldNameOrValueToUse = ReferenceUtil.safeReference(fieldName);
|
||||
/**
|
||||
* Builder for {@link ProjectionOperation}s on a field.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public static class ProjectionOperationBuilder {
|
||||
|
||||
private final String name;
|
||||
private final ProjectionOperation operation;
|
||||
|
||||
/**
|
||||
* Creates a new {@link ProjectionOperationBuilder} for the field with the given name on top of the given
|
||||
* {@link ProjectionOperation}.
|
||||
*
|
||||
* @param name must not be {@literal null} or empty.
|
||||
* @param operation must not be {@literal null}.
|
||||
*/
|
||||
public ProjectionOperationBuilder(String name, ProjectionOperation operation) {
|
||||
|
||||
Assert.hasText(name, "Field name must not be null or empty!");
|
||||
Assert.notNull(operation, "ProjectionOperation must not be null!");
|
||||
|
||||
this.name = name;
|
||||
this.operation = operation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Projects the result of the previous operation onto the current field. Will automatically add an exclusion for
|
||||
* {@code _id} as what would be held in it by default will now go into the field just projected into.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public ProjectionOperation previousOperation() {
|
||||
|
||||
return this.operation.andExclude(Fields.UNDERSCORE_ID) //
|
||||
.and(new PreviousOperationProjection(name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines a nested field binding for the current field.
|
||||
*
|
||||
* @param fields must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public ProjectionOperation nested(Fields fields) {
|
||||
return this.operation.and(new NestedFieldProjection(name, fields));
|
||||
}
|
||||
|
||||
public ProjectionOperation plus(Number number) {
|
||||
Assert.notNull(number, "Number must not be null!");
|
||||
return project("add", number);
|
||||
}
|
||||
|
||||
public ProjectionOperation minus(Number number) {
|
||||
Assert.notNull(number, "Number must not be null!");
|
||||
return project("substract", number);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a generic projection for the current field.
|
||||
*
|
||||
* @param operation the operation key, e.g. {@code $add}.
|
||||
* @param values the values to be set for the projection operation.
|
||||
* @return
|
||||
*/
|
||||
public ProjectionOperation project(String operation, Object... values) {
|
||||
return this.operation.and(new OperationProjection(name, operation, values));
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link Projection} to pull in the result of the previous operation.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
static class PreviousOperationProjection extends Projection {
|
||||
|
||||
private final String name;
|
||||
|
||||
/**
|
||||
* Creates a new {@link PreviousOperationProjection} for the field with the given name.
|
||||
*
|
||||
* @param name must not be {@literal null} or empty.
|
||||
*/
|
||||
public PreviousOperationProjection(String name) {
|
||||
super(Fields.field(name));
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.aggregation.ProjectionOperation.Projection#toDBObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext)
|
||||
*/
|
||||
@Override
|
||||
public DBObject toDBObject(AggregationOperationContext context) {
|
||||
return new BasicDBObject(name, Fields.UNDERSCORE_ID_REF);
|
||||
}
|
||||
fieldsObject.put(fieldsEntry.getKey(), fieldsEntryFieldNameOrValueToUse);
|
||||
}
|
||||
return fieldsObject;
|
||||
|
||||
/**
|
||||
* A {@link FieldProjection} to map a result of a previous {@link AggregationOperation} to a new field.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
static class FieldProjection extends Projection {
|
||||
|
||||
private final Field field;
|
||||
private final Object value;
|
||||
|
||||
/**
|
||||
* Creates a new {@link FieldProjection} for the field of the given name, assigning the given value.
|
||||
*
|
||||
* @param name must not be {@literal null} or empty.
|
||||
* @param value
|
||||
*/
|
||||
public FieldProjection(String name, Object value) {
|
||||
this(Fields.field(name), value);
|
||||
}
|
||||
|
||||
private FieldProjection(Field field, Object value) {
|
||||
|
||||
super(field);
|
||||
|
||||
this.field = field;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method to easily create {@link FieldProjection}s for the given {@link Fields}.
|
||||
*
|
||||
* @param fields the {@link Fields} to in- or exclude, must not be {@literal null}.
|
||||
* @param include whether to include or exclude the fields.
|
||||
* @return
|
||||
*/
|
||||
public static List<FieldProjection> from(Fields fields, boolean include) {
|
||||
|
||||
Assert.notNull(fields, "Fields must not be null!");
|
||||
List<FieldProjection> projections = new ArrayList<FieldProjection>();
|
||||
|
||||
for (Field field : fields) {
|
||||
projections.add(new FieldProjection(field, include ? null : 0));
|
||||
}
|
||||
|
||||
return projections;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.aggregation.ProjectionOperation.Projection#toDBObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext)
|
||||
*/
|
||||
@Override
|
||||
public DBObject toDBObject(AggregationOperationContext context) {
|
||||
|
||||
if (value != null) {
|
||||
return new BasicDBObject(field.getName(), value);
|
||||
}
|
||||
|
||||
FieldReference reference = context.getReference(field.getTarget());
|
||||
return new BasicDBObject(field.getName(), reference.toString());
|
||||
}
|
||||
}
|
||||
|
||||
static class OperationProjection extends Projection {
|
||||
|
||||
private final String name;
|
||||
private final String operation;
|
||||
private final List<Object> values;
|
||||
|
||||
/**
|
||||
* Creates a new {@link OperationProjection} for the given field.
|
||||
*
|
||||
* @param name the name of the field to add the operation projection for, must not be {@literal null} or empty.
|
||||
* @param operation the actual operation key, must not be {@literal null} or empty.
|
||||
* @param values the values to pass into the operation, must not be {@literal null}.
|
||||
*/
|
||||
public OperationProjection(String name, String operation, Object... values) {
|
||||
|
||||
super(Fields.field(name));
|
||||
|
||||
Assert.hasText(operation, "Operation must not be null or empty!");
|
||||
Assert.notNull(values, "Values must not be null!");
|
||||
|
||||
this.name = name;
|
||||
this.operation = operation;
|
||||
this.values = Arrays.asList(values);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.aggregation.ProjectionOperation.Projection#toDBObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext)
|
||||
*/
|
||||
@Override
|
||||
public DBObject toDBObject(AggregationOperationContext context) {
|
||||
|
||||
List<Object> values = buildReferences(context);
|
||||
DBObject inner = new BasicDBObject(operation, values.size() == 1 ? values.get(0) : values.toArray());
|
||||
|
||||
return new BasicDBObject(name, inner);
|
||||
}
|
||||
|
||||
private List<Object> buildReferences(AggregationOperationContext context) {
|
||||
|
||||
List<Object> result = new ArrayList<Object>(values.size());
|
||||
|
||||
for (Object element : values) {
|
||||
result.add(element instanceof Field ? context.getReference((Field) element).toString() : element);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
static class NestedFieldProjection extends Projection {
|
||||
|
||||
private final String name;
|
||||
private final Fields fields;
|
||||
|
||||
public NestedFieldProjection(String name, Fields fields) {
|
||||
|
||||
super(Fields.field(name));
|
||||
this.name = name;
|
||||
this.fields = fields;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.aggregation.ProjectionOperation.Projection#toDBObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext)
|
||||
*/
|
||||
@Override
|
||||
public DBObject toDBObject(AggregationOperationContext context) {
|
||||
|
||||
DBObject nestedObject = new BasicDBObject();
|
||||
|
||||
for (Field field : fields) {
|
||||
nestedObject.put(field.getName(), context.getReference(field.getTarget()).toString());
|
||||
}
|
||||
|
||||
return new BasicDBObject(name, nestedObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static abstract class Projection {
|
||||
|
||||
private final ExposedField field;
|
||||
|
||||
public Projection(Field name) {
|
||||
|
||||
Assert.notNull(name, "Field must not be null!");
|
||||
this.field = new ExposedField(name, true);
|
||||
}
|
||||
|
||||
public ExposedField getField() {
|
||||
return field;
|
||||
}
|
||||
|
||||
public abstract DBObject toDBObject(AggregationOperationContext context);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,121 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 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.aggregation;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Utility class for mongo db reference operator <code>$</code>
|
||||
*
|
||||
* @author Sebastian Herold
|
||||
* @author Thomas Darimont
|
||||
* @since 1.3
|
||||
*/
|
||||
class ReferenceUtil {
|
||||
|
||||
public static final String ID_KEY = "_id";
|
||||
|
||||
private static final String REFERENCE_PREFIX = "$";
|
||||
|
||||
/**
|
||||
* Ensures that the returned string begins with {@link #REFERENCE_PREFIX}.
|
||||
*
|
||||
* @param key reference key with or without {@link #REFERENCE_PREFIX} at the beginning.
|
||||
* @return key that definitely begins with {@link #REFERENCE_PREFIX}.
|
||||
*/
|
||||
public static String safeReference(String key) {
|
||||
|
||||
Assert.hasText(key);
|
||||
|
||||
if (!key.startsWith(REFERENCE_PREFIX)) {
|
||||
return REFERENCE_PREFIX + key;
|
||||
} else {
|
||||
return key;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that the returned string does not start with {@link #REFERENCE_PREFIX}.
|
||||
*
|
||||
* @param field reference key with or without {@link #REFERENCE_PREFIX} at the beginning.
|
||||
* @return key that definitely does not begin with {@link #REFERENCE_PREFIX}.
|
||||
*/
|
||||
public static String safeNonReference(String field) {
|
||||
|
||||
Assert.hasText(field);
|
||||
|
||||
if (field.startsWith(REFERENCE_PREFIX)) {
|
||||
return field.substring(REFERENCE_PREFIX.length());
|
||||
}
|
||||
|
||||
return field;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $_id
|
||||
*/
|
||||
public static String $id() {
|
||||
return $(ID_KEY);
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* $("a") -> $a
|
||||
* </pre>
|
||||
*
|
||||
* @param fieldName
|
||||
* @return the field name prefixed with {@literal $}
|
||||
* @see #safeReference(String)
|
||||
*/
|
||||
public static String $(String fieldName) {
|
||||
return safeReference(fieldName);
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* $id("a") -> $_id.a
|
||||
* </pre>
|
||||
*
|
||||
* @param fieldName
|
||||
* @return
|
||||
* @see #safeNonReference(String)
|
||||
*/
|
||||
public static String $id(String fieldName) {
|
||||
return $id() + "." + safeNonReference(fieldName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param fieldName
|
||||
* @return
|
||||
*/
|
||||
public static String id(String fieldName) {
|
||||
return ID_KEY + "." + fieldName;
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* a: $a -> true
|
||||
* </pre>
|
||||
*
|
||||
* @param idFieldName
|
||||
* @param idFieldValue
|
||||
* @return true if {@code idFieldValue} corresponds to the given {@code idFieldName} e.g.
|
||||
*/
|
||||
public static boolean isValueFieldReference(String idFieldName, Object idFieldValue) {
|
||||
return idFieldValue instanceof String && idFieldName.equals(safeNonReference((String) idFieldValue));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -15,26 +15,40 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core.aggregation;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.mongodb.BasicDBObject;
|
||||
import com.mongodb.DBObject;
|
||||
|
||||
/**
|
||||
* Encapsulates the aggregation framework {@code $skip}-operation.
|
||||
*
|
||||
* @see http://docs.mongodb.org/manual/reference/aggregation/skip/
|
||||
* @author Thomas Darimont
|
||||
* @author Oliver Gierke
|
||||
* @since 1.3
|
||||
*/
|
||||
public class SkipOperation extends AbstractAggregateOperation {
|
||||
public class SkipOperation implements AggregationOperation {
|
||||
|
||||
private final long skipCount;
|
||||
|
||||
/**
|
||||
* Creates a new {@link SkipOperation} skipping the given number of elements.
|
||||
*
|
||||
* @param skipCount number of documents to skip.
|
||||
*/
|
||||
public SkipOperation(long skipCount) {
|
||||
super("skip");
|
||||
|
||||
Assert.isTrue(skipCount >= 0, "Skip count must not be negative!");
|
||||
this.skipCount = skipCount;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.aggregation.AggregationOperation#toDBObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext)
|
||||
*/
|
||||
@Override
|
||||
public Object getOperationArgument() {
|
||||
return skipCount;
|
||||
public DBObject toDBObject(AggregationOperationContext context) {
|
||||
return new BasicDBObject("$skip", skipCount);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,9 @@
|
||||
package org.springframework.data.mongodb.core.aggregation;
|
||||
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.domain.Sort.Direction;
|
||||
import org.springframework.data.domain.Sort.Order;
|
||||
import org.springframework.data.mongodb.core.aggregation.ExposedFields.FieldReference;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.mongodb.BasicDBObject;
|
||||
@@ -26,42 +29,48 @@ import com.mongodb.DBObject;
|
||||
*
|
||||
* @see http://docs.mongodb.org/manual/reference/aggregation/sort/#pipe._S_sort
|
||||
* @author Thomas Darimont
|
||||
* @author Oliver Gierke
|
||||
* @since 1.3
|
||||
*/
|
||||
public class SortOperation extends AbstractContextAwareAggregateOperation implements ContextConsumingAggregateOperation {
|
||||
public class SortOperation implements AggregationOperation {
|
||||
|
||||
private Sort sort;
|
||||
private final Sort sort;
|
||||
|
||||
/**
|
||||
* @param sort
|
||||
* Creates a new {@link SortOperation} for the given {@link Sort} instance.
|
||||
*
|
||||
* @param sort must not be {@literal null}.
|
||||
*/
|
||||
public SortOperation(Sort sort) {
|
||||
super("sort");
|
||||
|
||||
Assert.notNull(sort);
|
||||
Assert.notNull(sort, "Sort must not be null!");
|
||||
this.sort = sort;
|
||||
}
|
||||
|
||||
public SortOperation and(Direction direction, String... fields) {
|
||||
return and(new Sort(direction, fields));
|
||||
}
|
||||
|
||||
public SortOperation and(Sort sort) {
|
||||
return new SortOperation(this.sort.and(sort));
|
||||
}
|
||||
|
||||
public SortOperation and(Sort.Direction direction, String... fields) {
|
||||
return and(new Sort(direction, fields));
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.aggregation.AbstractAggregateOperation#getOperationArgument()
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.aggregation.AggregationOperation#toDBObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext)
|
||||
*/
|
||||
@Override
|
||||
public Object getOperationArgument(AggregateOperationContext inputAggregateOperationContext) {
|
||||
public DBObject toDBObject(AggregationOperationContext context) {
|
||||
|
||||
Assert.notNull(inputAggregateOperationContext, "inputAggregateOperationContext must not be null!");
|
||||
BasicDBObject object = new BasicDBObject();
|
||||
|
||||
DBObject sortProperties = new BasicDBObject();
|
||||
for (org.springframework.data.domain.Sort.Order order : sort) {
|
||||
String fieldName = inputAggregateOperationContext.returnFieldNameAliasIfAvailableOr(order.getProperty());
|
||||
sortProperties.put(fieldName, order.isAscending() ? 1 : -1);
|
||||
for (Order order : sort) {
|
||||
|
||||
// Check reference
|
||||
FieldReference reference = context.getReference(order.getProperty());
|
||||
object.put(reference.getRaw(), order.isAscending() ? 1 : -1);
|
||||
}
|
||||
return sortProperties;
|
||||
|
||||
return new BasicDBObject("$sort", object);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Copyright 2013 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.aggregation;
|
||||
|
||||
import static org.springframework.data.mongodb.core.aggregation.Fields.*;
|
||||
|
||||
import org.springframework.data.mapping.PropertyPath;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.mapping.context.PersistentPropertyPath;
|
||||
import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExposedField;
|
||||
import org.springframework.data.mongodb.core.aggregation.ExposedFields.FieldReference;
|
||||
import org.springframework.data.mongodb.core.convert.QueryMapper;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.mongodb.DBObject;
|
||||
|
||||
/**
|
||||
* {@link AggregationOperationContext} aware of a particular type and a {@link MappingContext} to potentially translate
|
||||
* property references into document field names.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @since 1.3
|
||||
*/
|
||||
public class TypeBasedAggregationOperationContext implements AggregationOperationContext {
|
||||
|
||||
private final Class<?> type;
|
||||
private final MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext;
|
||||
private final QueryMapper mapper;
|
||||
|
||||
/**
|
||||
* Creates a new {@link TypeBasedAggregationOperationContext} for the given type, {@link MappingContext} and
|
||||
* {@link QueryMapper}.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
* @param mappingContext must not be {@literal null}.
|
||||
* @param mapper must not be {@literal null}.
|
||||
*/
|
||||
public TypeBasedAggregationOperationContext(Class<?> type,
|
||||
MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext, QueryMapper mapper) {
|
||||
|
||||
Assert.notNull(type, "Type must not be null!");
|
||||
Assert.notNull(mappingContext, "MappingContext must not be null!");
|
||||
Assert.notNull(mapper, "QueryMapper must not be null!");
|
||||
|
||||
this.type = type;
|
||||
this.mappingContext = mappingContext;
|
||||
this.mapper = mapper;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.aggregation.AggregationOperationContext#getMappedObject(com.mongodb.DBObject)
|
||||
*/
|
||||
@Override
|
||||
public DBObject getMappedObject(DBObject dbObject) {
|
||||
return mapper.getMappedObject(dbObject, mappingContext.getPersistentEntity(type));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.aggregation.AggregationOperationContext#getReference(org.springframework.data.mongodb.core.aggregation.ExposedFields.AvailableField)
|
||||
*/
|
||||
@Override
|
||||
public FieldReference getReference(Field field) {
|
||||
|
||||
PropertyPath.from(field.getName(), type);
|
||||
return getReferenceFor(field);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.aggregation.AggregationOperationContext#getReference(java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public FieldReference getReference(String name) {
|
||||
PropertyPath path = PropertyPath.from(name, type);
|
||||
|
||||
PersistentPropertyPath<MongoPersistentProperty> propertyPath = mappingContext.getPersistentPropertyPath(path);
|
||||
|
||||
return getReferenceFor(field(path.getLeafProperty().getSegment(),
|
||||
propertyPath.toDotPath(MongoPersistentProperty.PropertyToFieldNameConverter.INSTANCE)));
|
||||
}
|
||||
|
||||
private FieldReference getReferenceFor(Field field) {
|
||||
return new FieldReference(new ExposedField(field, true));
|
||||
}
|
||||
}
|
||||
@@ -15,14 +15,17 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core.aggregation;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A {@code TypedAggregation} is a special {@link Aggregation} that holds information of the input aggregation type.
|
||||
*
|
||||
* @author Thomas Darimont
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class TypedAggregation<I, O> extends Aggregation<I, O> {
|
||||
public class TypedAggregation<I> extends Aggregation {
|
||||
|
||||
private Class<I> inputType;
|
||||
private final Class<I> inputType;
|
||||
|
||||
/**
|
||||
* Creates a new {@link TypedAggregation} from the given {@link AggregationOperation}s.
|
||||
@@ -30,20 +33,19 @@ public class TypedAggregation<I, O> extends Aggregation<I, O> {
|
||||
* @param operations must not be {@literal null} or empty.
|
||||
*/
|
||||
public TypedAggregation(Class<I> inputType, AggregationOperation... operations) {
|
||||
|
||||
super(operations);
|
||||
|
||||
Assert.notNull(inputType, "Input type must not be null!");
|
||||
this.inputType = inputType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the inputType
|
||||
* Returns the input type for the {@link Aggregation}.
|
||||
*
|
||||
* @return the inputType will never be {@literal null}.
|
||||
*/
|
||||
public Class<?> getInputType() {
|
||||
public Class<I> getInputType() {
|
||||
return inputType;
|
||||
}
|
||||
|
||||
protected AggregateOperationContext createInitialAggregateOperationContext() {
|
||||
|
||||
// TODO construct initial aggregate operation context from input type.
|
||||
return super.createInitialAggregateOperationContext();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,30 +15,50 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core.aggregation;
|
||||
|
||||
import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExposedField;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.mongodb.BasicDBObject;
|
||||
import com.mongodb.DBObject;
|
||||
|
||||
/**
|
||||
* Encapsulates the aggregation framework {@code $unwind}-operation.
|
||||
*
|
||||
* @see http://docs.mongodb.org/manual/reference/aggregation/unwind/#pipe._S_unwind
|
||||
* @author Thomas Darimont
|
||||
* @author Oliver Gierke
|
||||
* @since 1.3
|
||||
*/
|
||||
public class UnwindOperation extends AbstractContextProducingAggregateOperation implements
|
||||
ContextConsumingAggregateOperation {
|
||||
public class UnwindOperation extends ExposedFieldsAggregationOperationContext implements AggregationOperation {
|
||||
|
||||
private final String fieldName;
|
||||
private final ExposedField field;
|
||||
|
||||
public UnwindOperation(String fieldName) {
|
||||
/**
|
||||
* Creates a new {@link UnwindOperation} for the given {@link Field}.
|
||||
*
|
||||
* @param field must not be {@literal null}.
|
||||
*/
|
||||
public UnwindOperation(Field field) {
|
||||
|
||||
super("unwind");
|
||||
Assert.notNull(fieldName);
|
||||
this.fieldName = fieldName;
|
||||
|
||||
getOutputAggregateOperationContext().registerAvailableField(fieldName, fieldName);
|
||||
Assert.notNull(field);
|
||||
this.field = new ExposedField(field, true);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.aggregation.ExposedFieldsAggregationOperationContext#getFields()
|
||||
*/
|
||||
@Override
|
||||
public Object getOperationArgument(AggregateOperationContext inputAggregateOperationContext) {
|
||||
return ReferenceUtil.safeReference(fieldName);
|
||||
protected ExposedFields getFields() {
|
||||
return ExposedFields.from(field);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.aggregation.AggregationOperation#toDBObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext)
|
||||
*/
|
||||
@Override
|
||||
public DBObject toDBObject(AggregationOperationContext context) {
|
||||
return new BasicDBObject("$unwind", context.getReference(field).toString());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013 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.
|
||||
*/
|
||||
/**
|
||||
* @author Thomas Darimont
|
||||
* Support for the MongoDB aggregation framework.
|
||||
* @since 1.3
|
||||
*/
|
||||
package org.springframework.data.mongodb.core.aggregation;
|
||||
@@ -356,7 +356,7 @@ public class QueryMapper {
|
||||
protected final String name;
|
||||
|
||||
/**
|
||||
* Creates a new {@link Field} without meta-information but the given name.
|
||||
* Creates a new {@link DocumentField} without meta-information but the given name.
|
||||
*
|
||||
* @param name must not be {@literal null} or empty.
|
||||
*/
|
||||
@@ -367,7 +367,7 @@ public class QueryMapper {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new {@link Field} with the given name.
|
||||
* Returns a new {@link DocumentField} with the given name.
|
||||
*
|
||||
* @param name must not be {@literal null} or empty.
|
||||
* @return
|
||||
@@ -423,7 +423,7 @@ public class QueryMapper {
|
||||
}
|
||||
|
||||
/**
|
||||
* Extension of {@link Field} to be backed with mapping metadata.
|
||||
* Extension of {@link DocumentField} to be backed with mapping metadata.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
|
||||
@@ -51,7 +51,7 @@ import org.springframework.util.StringUtils;
|
||||
public class BasicMongoPersistentEntity<T> extends BasicPersistentEntity<T, MongoPersistentProperty> implements
|
||||
MongoPersistentEntity<T>, ApplicationContextAware {
|
||||
|
||||
private static final String AMBIGUOUS_FIELD_MAPPING = "Ambiguous field mapping detected! Both %s and %s map to the same field name %s! Disambiguate using @Field annotation!";
|
||||
private static final String AMBIGUOUS_FIELD_MAPPING = "Ambiguous field mapping detected! Both %s and %s map to the same field name %s! Disambiguate using @DocumentField annotation!";
|
||||
private final String collection;
|
||||
private final SpelExpressionParser parser;
|
||||
private final StandardEvaluationContext context;
|
||||
|
||||
@@ -18,7 +18,7 @@ package org.springframework.data.mongodb.core.mapping;
|
||||
/**
|
||||
* SPI interface to determine how to name document fields in cases the field name is not manually defined.
|
||||
*
|
||||
* @see Field
|
||||
* @see DocumentField
|
||||
* @see PropertyNameFieldNamingStrategy
|
||||
* @see CamelCaseAbbreviatingFieldNamingStrategy
|
||||
* @since 1.3
|
||||
|
||||
@@ -73,7 +73,7 @@ public class Field {
|
||||
*/
|
||||
public Field position(String field, int value) {
|
||||
|
||||
Assert.hasText(field, "Field must not be null or empty!");
|
||||
Assert.hasText(field, "DocumentField must not be null or empty!");
|
||||
|
||||
postionKey = field;
|
||||
positionValue = value;
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright 2013 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.hamcrest.CoreMatchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
import org.springframework.data.mongodb.MongoDbFactory;
|
||||
import org.springframework.data.mongodb.core.MongoTemplate.UnwrapAndReadDbObjectCallback;
|
||||
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
|
||||
|
||||
import com.mongodb.BasicDBObject;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link UnwrapAndReadDbObjectCallback}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class UnwrapAndReadDbObjectCallbackUnitTests {
|
||||
|
||||
@Mock MongoDbFactory factory;
|
||||
|
||||
UnwrapAndReadDbObjectCallback<Target> callback;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
|
||||
MongoTemplate template = new MongoTemplate(factory);
|
||||
MappingMongoConverter converter = new MappingMongoConverter(factory, new MongoMappingContext());
|
||||
|
||||
this.callback = template.new UnwrapAndReadDbObjectCallback<Target>(converter, Target.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void usesFirstLevelValues() {
|
||||
|
||||
Target target = callback.doWith(new BasicDBObject("foo", "bar"));
|
||||
|
||||
assertThat(target.id, is(nullValue()));
|
||||
assertThat(target.foo, is("bar"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void unwrapsUnderscoreIdIfBasicDBObject() {
|
||||
|
||||
Target target = callback.doWith(new BasicDBObject("_id", new BasicDBObject("foo", "bar")));
|
||||
|
||||
assertThat(target.id, is(nullValue()));
|
||||
assertThat(target.foo, is("bar"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void firstLevelPropertiesTrumpNestedOnes() {
|
||||
|
||||
Target target = callback.doWith(new BasicDBObject("_id", new BasicDBObject("foo", "bar")).append("foo", "foobar"));
|
||||
|
||||
assertThat(target.id, is(nullValue()));
|
||||
assertThat(target.foo, is("foobar"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void keepsUnderscoreIdIfScalarValue() {
|
||||
|
||||
Target target = callback.doWith(new BasicDBObject("_id", "bar").append("foo", "foo"));
|
||||
|
||||
assertThat(target.id, is("bar"));
|
||||
assertThat(target.foo, is("foo"));
|
||||
}
|
||||
|
||||
static class Target {
|
||||
|
||||
String id;
|
||||
String foo;
|
||||
}
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 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.aggregation;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.springframework.data.domain.Sort.Direction.*;
|
||||
import static org.springframework.data.mongodb.core.DBObjectUtils.*;
|
||||
import static org.springframework.data.mongodb.core.aggregation.Aggregation.*;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.data.mongodb.core.query.Criteria;
|
||||
|
||||
import com.mongodb.DBObject;
|
||||
|
||||
/**
|
||||
* Tests of the {@link AggregationPipeline}.
|
||||
*
|
||||
* @see DATAMONGO-586
|
||||
* @author Tobias Trelle
|
||||
* @author Thomas Darimont
|
||||
*/
|
||||
public class AggregationPipelineTests {
|
||||
|
||||
@Test
|
||||
public void limitOperation() {
|
||||
|
||||
assertSingleDBObject("$limit", 42L, limit(42).toDbObject());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void skipOperation() {
|
||||
|
||||
assertSingleDBObject("$skip", 5L, skip(5).toDbObject());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void unwindOperation() {
|
||||
|
||||
assertSingleDBObject("$unwind", "$field", unwind("$field").toDbObject(new BasicAggregateOperationContext()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void unwindOperationWithAddedPrefix() {
|
||||
|
||||
assertSingleDBObject("$unwind", "$field", unwind("field").toDbObject(new BasicAggregateOperationContext()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchOperation() {
|
||||
|
||||
DBObject match = match(new Criteria("title").is("Doc 1")).toDbObject();
|
||||
DBObject criteriaDoc = getAsDBObject(match, "$match");
|
||||
assertThat(criteriaDoc, is(notNullValue()));
|
||||
assertSingleDBObject("title", "Doc 1", criteriaDoc);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sortOperation() {
|
||||
|
||||
DBObject sortDoc = sort(ASC, "n").toDbObject(new BasicAggregateOperationContext());
|
||||
DBObject orderDoc = getAsDBObject(sortDoc, "$sort");
|
||||
assertThat(orderDoc, is(notNullValue()));
|
||||
assertSingleDBObject("n", 1, orderDoc);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void projectOperation() {
|
||||
|
||||
DBObject projectionDoc = project("a").toDbObject(new BasicAggregateOperationContext());
|
||||
DBObject fields = getAsDBObject(projectionDoc, "$project");
|
||||
assertThat(fields, is(notNullValue()));
|
||||
assertSingleDBObject("a", 1, fields);
|
||||
}
|
||||
|
||||
private static void assertSingleDBObject(String key, Object value, DBObject doc) {
|
||||
assertThat(doc.get(key), is(value));
|
||||
}
|
||||
}
|
||||
@@ -55,26 +55,24 @@ import com.mongodb.util.JSON;
|
||||
* @see DATAMONGO-586
|
||||
* @author Tobias Trelle
|
||||
* @author Thomas Darimont
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration("classpath:infrastructure.xml")
|
||||
public class AggregationTests {
|
||||
|
||||
private static final String INPUT_COLLECTION = "aggregation_test_collection";
|
||||
private static boolean initialized = false;
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(AggregationTests.class);
|
||||
|
||||
private static boolean initialized = false;
|
||||
|
||||
@Autowired MongoTemplate mongoTemplate;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
|
||||
cleanDb();
|
||||
initSampleDataIfNecessary();
|
||||
|
||||
CommandResult result = mongoTemplate.executeCommand("{ buildInfo: 1 }");
|
||||
Object version = result.get("version");
|
||||
|
||||
LOGGER.debug("Server uses MongoDB Version: {}", version);
|
||||
}
|
||||
|
||||
@After
|
||||
@@ -97,8 +95,9 @@ public class AggregationTests {
|
||||
|
||||
if (!initialized) {
|
||||
|
||||
CommandResult result = mongoTemplate.executeCommand(new BasicDBObject("buildInfo", 1));
|
||||
LOGGER.error(result.toString());
|
||||
CommandResult result = mongoTemplate.executeCommand("{ buildInfo: 1 }");
|
||||
Object version = result.get("version");
|
||||
LOGGER.debug("Server uses MongoDB Version: {}", version);
|
||||
|
||||
mongoTemplate.dropCollection(ZipInfo.class);
|
||||
mongoTemplate.execute(ZipInfo.class, new CollectionCallback<Void>() {
|
||||
@@ -133,17 +132,17 @@ public class AggregationTests {
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void shouldHandleMissingInputCollection() {
|
||||
mongoTemplate.aggregate((String) null, new Aggregation<Object, TagCount>(), TagCount.class);
|
||||
mongoTemplate.aggregate(newAggregation(), (String) null, TagCount.class);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void shouldHandleMissingAggregationPipeline() {
|
||||
mongoTemplate.aggregate(INPUT_COLLECTION, null, TagCount.class);
|
||||
mongoTemplate.aggregate(null, INPUT_COLLECTION, TagCount.class);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void shouldHandleMissingEntityClass() {
|
||||
mongoTemplate.aggregate(INPUT_COLLECTION, new Aggregation<Object, TagCount>(), null);
|
||||
mongoTemplate.aggregate(newAggregation(), INPUT_COLLECTION, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -151,20 +150,22 @@ public class AggregationTests {
|
||||
|
||||
createTagDocuments();
|
||||
|
||||
Aggregation<Object, TagCount> agg = newAggregation( //
|
||||
Aggregation agg = newAggregation( //
|
||||
project("tags"), //
|
||||
unwind("tags"), //
|
||||
group("tags").count("n"), //
|
||||
project().field("tag", $id()).field("n", 1), //
|
||||
group("tags") //
|
||||
.and("n").count(), //
|
||||
project("n") //
|
||||
.and("tag").previousOperation(), //
|
||||
sort(DESC, "n") //
|
||||
);
|
||||
|
||||
AggregationResults<TagCount> results = mongoTemplate.aggregate(INPUT_COLLECTION, agg, TagCount.class);
|
||||
AggregationResults<TagCount> results = mongoTemplate.aggregate(agg, INPUT_COLLECTION, TagCount.class);
|
||||
|
||||
assertThat(results, is(notNullValue()));
|
||||
assertThat(results.getServerUsed(), is("/127.0.0.1:27017"));
|
||||
|
||||
List<TagCount> tagCount = results.getAggregationResult();
|
||||
List<TagCount> tagCount = results.getMappedResults();
|
||||
|
||||
assertThat(tagCount, is(notNullValue()));
|
||||
assertThat(tagCount.size(), is(3));
|
||||
@@ -177,20 +178,22 @@ public class AggregationTests {
|
||||
@Test
|
||||
public void shouldAggregateEmptyCollection() {
|
||||
|
||||
Aggregation<Object, TagCount> agg = newAggregation(//
|
||||
Aggregation aggregation = newAggregation(//
|
||||
project("tags"), //
|
||||
unwind("tags"), //
|
||||
group("tags").count("n"), //
|
||||
project().field("tag", $id()).field("n", 1), //
|
||||
group("tags") //
|
||||
.and("n").count(), //
|
||||
project("n") //
|
||||
.and("tag").previousOperation(), //
|
||||
sort(DESC, "n") //
|
||||
);
|
||||
|
||||
AggregationResults<TagCount> results = mongoTemplate.aggregate(INPUT_COLLECTION, agg, TagCount.class);
|
||||
AggregationResults<TagCount> results = mongoTemplate.aggregate(aggregation, INPUT_COLLECTION, TagCount.class);
|
||||
|
||||
assertThat(results, is(notNullValue()));
|
||||
assertThat(results.getServerUsed(), is("/127.0.0.1:27017"));
|
||||
|
||||
List<TagCount> tagCount = results.getAggregationResult();
|
||||
List<TagCount> tagCount = results.getMappedResults();
|
||||
|
||||
assertThat(tagCount, is(notNullValue()));
|
||||
assertThat(tagCount.size(), is(0));
|
||||
@@ -200,19 +203,21 @@ public class AggregationTests {
|
||||
public void shouldDetectResultMismatch() {
|
||||
|
||||
createTagDocuments();
|
||||
Aggregation<Object, TagCount> agg = newAggregation( //
|
||||
|
||||
Aggregation aggregation = newAggregation( //
|
||||
project("tags"), //
|
||||
unwind("tags"), //
|
||||
group("tags").count("count"), //
|
||||
group("tags") //
|
||||
.and("count").count(), //
|
||||
limit(2) //
|
||||
);
|
||||
|
||||
AggregationResults<TagCount> results = mongoTemplate.aggregate(INPUT_COLLECTION, agg, TagCount.class);
|
||||
AggregationResults<TagCount> results = mongoTemplate.aggregate(aggregation, INPUT_COLLECTION, TagCount.class);
|
||||
|
||||
assertThat(results, is(notNullValue()));
|
||||
assertThat(results.getServerUsed(), is("/127.0.0.1:27017"));
|
||||
|
||||
List<TagCount> tagCount = results.getAggregationResult();
|
||||
List<TagCount> tagCount = results.getMappedResults();
|
||||
|
||||
assertThat(tagCount, is(notNullValue()));
|
||||
assertThat(tagCount.size(), is(2));
|
||||
@@ -220,61 +225,6 @@ public class AggregationTests {
|
||||
assertTagCount(null, 0, tagCount.get(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fieldsFactoryMethod() {
|
||||
|
||||
Fields fields = fields("a", "b").and("c").and("d", 42);
|
||||
assertThat(fields, is(notNullValue()));
|
||||
assertThat(fields.getValues(), is(notNullValue()));
|
||||
assertThat(fields.getValues().size(), is(4));
|
||||
assertThat(fields.getValues().get("a"), is((Object) "a"));
|
||||
assertThat(fields.getValues().get("b"), is((Object) "b"));
|
||||
assertThat(fields.getValues().get("c"), is((Object) "c"));
|
||||
assertThat(fields.getValues().get("d"), is((Object) 42));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldCreateSimpleIdForGroupOperationWithSingleSimpleIdField() {
|
||||
|
||||
Fields fields = fields("a");
|
||||
GroupOperation groupOperation = new GroupOperation(fields);
|
||||
|
||||
DBObject dbObject = groupOperation.toDbObject(new BasicAggregateOperationContext());
|
||||
assertThat(dbObject, is(notNullValue()));
|
||||
assertThat(dbObject.get("$group"), is(notNullValue()));
|
||||
assertThat(((DBObject) dbObject.get("$group")).get(id()), is(notNullValue()));
|
||||
assertThat(((DBObject) dbObject.get("$group")).get(id()), is((Object) "$a"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldCreateComplexIdForGroupOperationWithSingleComplexIdField() {
|
||||
|
||||
Fields fields = fields().and("a", 42);
|
||||
GroupOperation groupOperation = new GroupOperation(fields);
|
||||
|
||||
assertThat(groupOperation.toDbObject(new BasicAggregateOperationContext()), is(notNullValue()));
|
||||
assertThat(groupOperation.id, is(notNullValue()));
|
||||
assertThat(groupOperation.id, is((Object) new BasicDBObject("a", 42)));
|
||||
}
|
||||
|
||||
// @Test
|
||||
// public void groupFactoryMethodWithMultipleFieldsAndSumOperation() {
|
||||
//
|
||||
// Fields fields = fields("a", "b").pick("c").pick("d", 42);
|
||||
// GroupOperation groupOperation = group(fields).sum("e");
|
||||
//
|
||||
// assertThat(groupOperation, is(notNullValue()));
|
||||
// assertThat(groupOperation.toDbObject(null), is(notNullValue()));
|
||||
// assertThat(groupOperation.id, is(notNullValue()));
|
||||
// assertThat(groupOperation.id, is((Object) new BasicDBObject(fields.getValues())));
|
||||
// assertThat(groupOperation.fields, is(notNullValue()));
|
||||
// assertThat(groupOperation.fields.size(), is(1));
|
||||
// assertThat(groupOperation.fields.containsKey("e"), is(true));
|
||||
// assertThat(groupOperation.fields.get("e"), is(notNullValue()));
|
||||
// assertThat(groupOperation.fields.get("e").get("$sum"), is(notNullValue()));
|
||||
// assertThat(groupOperation.fields.get("e").get("$sum"), is((Object) "$e"));
|
||||
// }
|
||||
|
||||
@Test
|
||||
public void complexAggregationFrameworkUsageLargestAndSmallestCitiesByState() {
|
||||
/*
|
||||
@@ -337,31 +287,31 @@ public class AggregationTests {
|
||||
)
|
||||
*/
|
||||
|
||||
TypedAggregation<ZipInfo, ZipInfoStats> agg = newAggregation(ZipInfo.class, //
|
||||
group("state", "city").sum("pop"), //
|
||||
TypedAggregation<ZipInfo> aggregation = newAggregation(ZipInfo.class, //
|
||||
group("state", "city").and("pop").sum("population"), //
|
||||
sort(ASC, "pop", "state", "city"), //
|
||||
group("state") //
|
||||
.last("biggestCity", "city") //
|
||||
.last("biggestPop", "pop") //
|
||||
.first("smallestCity", "city") //
|
||||
.first("smallestPop", "pop"), //
|
||||
project(ZipInfoStats.class) //
|
||||
.field("_id", 0) //
|
||||
.field("state", id()) //
|
||||
.field("biggestCity", pick("name", "biggestCity").and("population", "biggestPop")) //
|
||||
.field("smallestCity", pick("name", "smallestCity").and("population", "smallestPop")), //
|
||||
.and("biggestCity").last("city") //
|
||||
.and("biggestPop").last("pop") //
|
||||
.and("smallestCity").first("city") //
|
||||
.and("smallestPop").first("pop"), //
|
||||
project() //
|
||||
// .and(previousOperation()).exclude() //
|
||||
.and("state").previousOperation() //
|
||||
.and("biggestCity").nested(bind("name", "biggestCity").and("population", "biggestPop")) //
|
||||
.and("smallestCity").nested(bind("name", "smallestCity").and("population", "smallestPop")), //
|
||||
sort(ASC, "state") //
|
||||
);
|
||||
|
||||
assertThat(agg, is(notNullValue()));
|
||||
assertThat(agg.toString(), is(notNullValue()));
|
||||
assertThat(aggregation, is(notNullValue()));
|
||||
assertThat(aggregation.toString(), is(notNullValue()));
|
||||
|
||||
AggregationResults<ZipInfoStats> result = mongoTemplate.aggregate(agg, ZipInfoStats.class);
|
||||
AggregationResults<ZipInfoStats> result = mongoTemplate.aggregate(aggregation, ZipInfoStats.class);
|
||||
assertThat(result, is(notNullValue()));
|
||||
assertThat(result.getAggregationResult(), is(notNullValue()));
|
||||
assertThat(result.getAggregationResult().size(), is(51));
|
||||
assertThat(result.getMappedResults(), is(notNullValue()));
|
||||
assertThat(result.getMappedResults().size(), is(51));
|
||||
|
||||
ZipInfoStats firstZipInfoStats = result.getAggregationResult().get(0);
|
||||
ZipInfoStats firstZipInfoStats = result.getMappedResults().get(0);
|
||||
assertThat(firstZipInfoStats, is(notNullValue()));
|
||||
assertThat(firstZipInfoStats.id, is(nullValue()));
|
||||
assertThat(firstZipInfoStats.state, is("AK"));
|
||||
@@ -372,7 +322,7 @@ public class AggregationTests {
|
||||
assertThat(firstZipInfoStats.biggestCity.name, is("ANCHORAGE"));
|
||||
assertThat(firstZipInfoStats.biggestCity.population, is(183987));
|
||||
|
||||
ZipInfoStats lastZipInfoStats = result.getAggregationResult().get(50);
|
||||
ZipInfoStats lastZipInfoStats = result.getMappedResults().get(50);
|
||||
assertThat(lastZipInfoStats, is(notNullValue()));
|
||||
assertThat(lastZipInfoStats.id, is(nullValue()));
|
||||
assertThat(lastZipInfoStats.state, is("WY"));
|
||||
@@ -408,9 +358,10 @@ public class AggregationTests {
|
||||
)
|
||||
*/
|
||||
|
||||
TypedAggregation<ZipInfo, StateStats> agg = newAggregation(ZipInfo.class, //
|
||||
group("state").sum("totalPop", "pop"), //
|
||||
sort(ASC, id(), "totalPop"), //
|
||||
TypedAggregation<ZipInfo> agg = newAggregation(ZipInfo.class, //
|
||||
group("state") //
|
||||
.and("totalPop").sum("population"), //
|
||||
sort(ASC, previousOperation(), "totalPop"), //
|
||||
match(where("totalPop").gte(10 * 1000 * 1000)) //
|
||||
);
|
||||
|
||||
@@ -419,10 +370,10 @@ public class AggregationTests {
|
||||
|
||||
AggregationResults<StateStats> result = mongoTemplate.aggregate(agg, StateStats.class);
|
||||
assertThat(result, is(notNullValue()));
|
||||
assertThat(result.getAggregationResult(), is(notNullValue()));
|
||||
assertThat(result.getAggregationResult().size(), is(7));
|
||||
assertThat(result.getMappedResults(), is(notNullValue()));
|
||||
assertThat(result.getMappedResults().size(), is(7));
|
||||
|
||||
StateStats stateStats = result.getAggregationResult().get(0);
|
||||
StateStats stateStats = result.getMappedResults().get(0);
|
||||
assertThat(stateStats, is(notNullValue()));
|
||||
assertThat(stateStats.id, is("CA"));
|
||||
assertThat(stateStats.state, is(nullValue()));
|
||||
@@ -447,12 +398,12 @@ public class AggregationTests {
|
||||
|
||||
*/
|
||||
|
||||
TypedAggregation<UserWithLikes, LikeStats> agg = newAggregation(UserWithLikes.class, //
|
||||
TypedAggregation<UserWithLikes> agg = newAggregation(UserWithLikes.class, //
|
||||
unwind("likes"), //
|
||||
group("likes").count("number"), //
|
||||
group("likes").and("number").count(), //
|
||||
sort(DESC, "number"), //
|
||||
limit(5), //
|
||||
sort(ASC, id()) //
|
||||
sort(ASC, previousOperation()) //
|
||||
);
|
||||
|
||||
assertThat(agg, is(notNullValue()));
|
||||
@@ -460,14 +411,14 @@ public class AggregationTests {
|
||||
|
||||
AggregationResults<LikeStats> result = mongoTemplate.aggregate(agg, LikeStats.class);
|
||||
assertThat(result, is(notNullValue()));
|
||||
assertThat(result.getAggregationResult(), is(notNullValue()));
|
||||
assertThat(result.getAggregationResult().size(), is(5));
|
||||
assertThat(result.getMappedResults(), is(notNullValue()));
|
||||
assertThat(result.getMappedResults().size(), is(5));
|
||||
|
||||
assertLikeStats(result.getAggregationResult().get(0), "a", 4);
|
||||
assertLikeStats(result.getAggregationResult().get(1), "b", 2);
|
||||
assertLikeStats(result.getAggregationResult().get(2), "c", 4);
|
||||
assertLikeStats(result.getAggregationResult().get(3), "d", 2);
|
||||
assertLikeStats(result.getAggregationResult().get(4), "e", 3);
|
||||
assertLikeStats(result.getMappedResults().get(0), "a", 4);
|
||||
assertLikeStats(result.getMappedResults().get(1), "b", 2);
|
||||
assertLikeStats(result.getMappedResults().get(2), "c", 4);
|
||||
assertLikeStats(result.getMappedResults().get(3), "d", 2);
|
||||
assertLikeStats(result.getMappedResults().get(4), "e", 3);
|
||||
}
|
||||
|
||||
private void assertLikeStats(LikeStats like, String id, long count) {
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2013 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.aggregation;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.data.mongodb.core.aggregation.Aggregation;
|
||||
import org.springframework.data.mongodb.core.aggregation.AggregationOperation;
|
||||
|
||||
/**
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class AggregationUnitTests {
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void rejectsNullAggregationOperation() {
|
||||
Aggregation.newAggregation((AggregationOperation[]) null);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void rejectsNullTypedAggregationOperation() {
|
||||
Aggregation.newAggregation(String.class, (AggregationOperation[]) null);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void rejectsNoAggregationOperation() {
|
||||
Aggregation.newAggregation(new AggregationOperation[0]);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void rejectsNoTypedAggregationOperation() {
|
||||
Aggregation.newAggregation(String.class, new AggregationOperation[0]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 2013 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.aggregation;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExposedField;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link ExposedFields}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class ExposedFieldsUnitTests {
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void rejectsNullFields() {
|
||||
ExposedFields.from((ExposedField) null);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void rejectsNullFieldsForSynthetics() {
|
||||
ExposedFields.synthetic(null);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void rejectsNullFieldsForNonSynthetics() {
|
||||
ExposedFields.nonSynthetic(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void exposesSingleField() {
|
||||
|
||||
ExposedFields fields = ExposedFields.synthetic(Fields.fields("foo"));
|
||||
assertThat(fields.exposesSingleFieldOnly(), is(true));
|
||||
|
||||
fields = fields.and(new ExposedField("bar", true));
|
||||
assertThat(fields.exposesSingleFieldOnly(), is(false));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* Copyright 2013 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.aggregation;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.springframework.data.mongodb.core.aggregation.Fields.*;
|
||||
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.springframework.data.mongodb.core.aggregation.Fields.AggregationField;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link Fields}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Thomas Darimont
|
||||
*/
|
||||
public class FieldsUnitTests {
|
||||
|
||||
@Rule public ExpectedException exception = ExpectedException.none();
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void rejectsNullFieldVarArgs() {
|
||||
Fields.from((Field[]) null);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void rejectsNullFieldNameVarArgs() {
|
||||
Fields.fields((String[]) null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createsFieldFromNameOnly() {
|
||||
verify(Fields.field("foo"), "foo", null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createsFieldFromNameAndTarget() {
|
||||
verify(Fields.field("foo", "bar"), "foo", "bar");
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void rejectsNullFieldName() {
|
||||
Fields.field(null);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void rejectsNullFieldNameIfTargetGiven() {
|
||||
Fields.field(null, "foo");
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void rejectsEmptyFieldName() {
|
||||
Fields.field("");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createsFieldsFromFieldInstances() {
|
||||
|
||||
AggregationField reference = new AggregationField("foo");
|
||||
Fields fields = Fields.from(reference);
|
||||
|
||||
assertThat(fields, is(Matchers.<Field> iterableWithSize(1)));
|
||||
assertThat(fields, hasItem(reference));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void aliasesPathExpressionsIntoLeafForImplicits() {
|
||||
verify(Fields.field("foo.bar"), "bar", "foo.bar");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fieldsFactoryMethod() {
|
||||
|
||||
Fields fields = fields("a", "b").and("c").and("d", "e");
|
||||
|
||||
assertThat(fields, is(Matchers.<Field> iterableWithSize(4)));
|
||||
|
||||
verify(fields.getField("a"), "a", null);
|
||||
verify(fields.getField("b"), "b", null);
|
||||
verify(fields.getField("c"), "c", null);
|
||||
verify(fields.getField("d"), "d", "e");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rejectsAmbiguousFieldNames() {
|
||||
|
||||
exception.expect(IllegalArgumentException.class);
|
||||
|
||||
fields("b", "a.b");
|
||||
}
|
||||
|
||||
private static void verify(Field field, String name, String target) {
|
||||
|
||||
assertThat(field, is(notNullValue()));
|
||||
assertThat(field.getName(), is(name));
|
||||
assertThat(field.getTarget(), is(target != null ? target : name));
|
||||
}
|
||||
}
|
||||
@@ -15,22 +15,30 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core.aggregation;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.data.mongodb.core.DBObjectUtils;
|
||||
import org.springframework.data.mongodb.core.query.NearQuery;
|
||||
|
||||
import com.mongodb.DBObject;
|
||||
|
||||
/**
|
||||
* Represents one single operation in an aggregation pipeline that is aware of an {@link AggregateOperationContext}. The
|
||||
* {@code AggregateOperationContext} can be used to resolve the correct field reference expression for field references.
|
||||
* Unit tests for {@link GeoNearOperation}.
|
||||
*
|
||||
* @author Thomas Darimont
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public interface ContextConsumingAggregateOperation extends AggregationOperation {
|
||||
public class GeoNearOperationUnitTests {
|
||||
|
||||
/**
|
||||
* Creates a {@link DBObject} representation backing this object and considers the field references from the given
|
||||
* {@link AggregateOperationContext}.
|
||||
*
|
||||
* @param inputAggregateOperationContext
|
||||
* @return
|
||||
*/
|
||||
DBObject toDbObject(AggregateOperationContext inputAggregateOperationContext);
|
||||
@Test
|
||||
public void rendersNearQueryAsAggregationOperation() {
|
||||
|
||||
NearQuery query = NearQuery.near(10.0, 10.0);
|
||||
GeoNearOperation operation = new GeoNearOperation(query);
|
||||
DBObject dbObject = operation.toDBObject(Aggregation.DEFAULT_CONTEXT);
|
||||
|
||||
DBObject nearClause = DBObjectUtils.getAsDBObject(dbObject, "$geoNear");
|
||||
assertThat(nearClause, is(query.toDBObject()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright 2013 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.aggregation;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.springframework.data.mongodb.core.aggregation.Fields.*;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.data.mongodb.core.DBObjectUtils;
|
||||
|
||||
import com.mongodb.BasicDBObject;
|
||||
import com.mongodb.DBObject;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link GroupOperation}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class GroupOperationUnitTests {
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void rejectsNullFields() {
|
||||
new GroupOperation(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createsGroupOperationWithSingleField() {
|
||||
|
||||
GroupOperation operation = new GroupOperation(fields("a"));
|
||||
|
||||
DBObject dbObject = operation.toDBObject(Aggregation.DEFAULT_CONTEXT);
|
||||
DBObject groupClause = DBObjectUtils.getAsDBObject(dbObject, "$group");
|
||||
|
||||
assertThat(groupClause.get(UNDERSCORE_ID), is((Object) "$a"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createsGroupOperationWithMultipleFields() {
|
||||
|
||||
GroupOperation operation = new GroupOperation(fields("a").and("b", "c"));
|
||||
|
||||
DBObject dbObject = operation.toDBObject(Aggregation.DEFAULT_CONTEXT);
|
||||
DBObject groupClause = DBObjectUtils.getAsDBObject(dbObject, "$group");
|
||||
DBObject idClause = DBObjectUtils.getAsDBObject(groupClause, UNDERSCORE_ID);
|
||||
|
||||
assertThat(idClause.get("a"), is((Object) "$a"));
|
||||
assertThat(idClause.get("b"), is((Object) "$c"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldCreateComplexIdForGroupOperationWithSingleComplexIdField() {
|
||||
|
||||
// Fields fields = fields().and("a", 42);
|
||||
// GroupOperation groupOperation = new GroupOperation(fields());
|
||||
//
|
||||
// assertThat(groupOperation.toDBObject(Aggregation.DEFAULT_CONTEXT), is(notNullValue()));
|
||||
// assertThat(groupOperation.id, is(notNullValue()));
|
||||
// assertThat(groupOperation.id, is((Object) new BasicDBObject("a", 42)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void groupFactoryMethodWithMultipleFieldsAndSumOperation() {
|
||||
|
||||
Fields fields = fields("a", "b").and("c"); // .and("d", 42);
|
||||
GroupOperation groupOperation = new GroupOperation(fields).and("e").sum();
|
||||
|
||||
DBObject dbObject = groupOperation.toDBObject(Aggregation.DEFAULT_CONTEXT);
|
||||
|
||||
DBObject groupClause = DBObjectUtils.getAsDBObject(dbObject, "$group");
|
||||
DBObject eOp = DBObjectUtils.getAsDBObject(groupClause, "e");
|
||||
assertThat(eOp, is((DBObject) new BasicDBObject("$sum", "$e")));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright 2013 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.aggregation;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.data.mongodb.core.DBObjectUtils;
|
||||
|
||||
import com.mongodb.DBObject;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link ProjectionOperation}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class ProjectionOperationUnitTests {
|
||||
|
||||
static final String PROJECT = "$project";
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void rejectsNullFields() {
|
||||
new ProjectionOperation(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void declaresBackReferenceCorrectly() {
|
||||
|
||||
ProjectionOperation operation = new ProjectionOperation();
|
||||
operation = operation.and("prop").previousOperation();
|
||||
|
||||
DBObject dbObject = operation.toDBObject(Aggregation.DEFAULT_CONTEXT);
|
||||
DBObject projectClause = DBObjectUtils.getAsDBObject(dbObject, PROJECT);
|
||||
assertThat(projectClause.get("prop"), is((Object) Fields.UNDERSCORE_ID_REF));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void alwaysUsesExplicitReference() {
|
||||
|
||||
ProjectionOperation operation = new ProjectionOperation(Fields.fields("foo").and("bar", "foobar"));
|
||||
|
||||
DBObject dbObject = operation.toDBObject(Aggregation.DEFAULT_CONTEXT);
|
||||
DBObject projectClause = DBObjectUtils.getAsDBObject(dbObject, PROJECT);
|
||||
|
||||
assertThat(projectClause.get("foo"), is((Object) "$foo"));
|
||||
assertThat(projectClause.get("bar"), is((Object) "$foobar"));
|
||||
}
|
||||
}
|
||||
@@ -1,159 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 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.aggregation;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.springframework.data.mongodb.core.aggregation.Aggregation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
|
||||
import com.mongodb.DBObject;
|
||||
|
||||
/**
|
||||
* Tests of {@link ProjectionOperation}.
|
||||
*
|
||||
* @see DATAMONGO-586
|
||||
* @author Tobias Trelle
|
||||
*/
|
||||
public class ProjectionTests {
|
||||
|
||||
@Test
|
||||
public void emptyProjection() {
|
||||
|
||||
DBObject raw = safeExtractDbObjectFromProjection(project());
|
||||
assertThat(raw.toMap().size(), is(1));
|
||||
assertThat((Integer) raw.get("_id"), is(0));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void shouldDetectNullIncludesInConstructor() {
|
||||
new ProjectionOperation((String[]) null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void includesWithConstructor() {
|
||||
|
||||
DBObject raw = safeExtractDbObjectFromProjection(project("a", "b"));
|
||||
assertThat(raw, is(notNullValue()));
|
||||
assertThat(raw.toMap().size(), is(3));
|
||||
assertThat((Integer) raw.get("_id"), is(0));
|
||||
assertThat((Integer) raw.get("a"), is(1));
|
||||
assertThat((Integer) raw.get("b"), is(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void include() {
|
||||
|
||||
DBObject raw = safeExtractDbObjectFromProjection(project().include("a"));
|
||||
assertSingleDBObject("a", 1, raw);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void exclude() {
|
||||
|
||||
DBObject raw = safeExtractDbObjectFromProjection(project().exclude("a"));
|
||||
assertThat(raw.toMap().size(), is(2));
|
||||
assertThat((Integer) raw.get("_id"), is(0));
|
||||
assertThat((Integer) raw.get("a"), is(0));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void includeAlias() {
|
||||
|
||||
DBObject raw = safeExtractDbObjectFromProjection(project().include("a").as("b"));
|
||||
assertThat(raw.toMap().size(), is(2));
|
||||
assertThat((Integer) raw.get("_id"), is(0));
|
||||
assertThat((String) raw.get("b"), is("$a"));
|
||||
}
|
||||
|
||||
@Test(expected = InvalidDataAccessApiUsageException.class)
|
||||
public void shouldDetectAliasWithoutInclude() {
|
||||
project().as("b");
|
||||
}
|
||||
|
||||
@Test(expected = InvalidDataAccessApiUsageException.class)
|
||||
public void shouldDetectDuplicateAlias() {
|
||||
project().include("a").as("b").as("c");
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void plus() {
|
||||
|
||||
DBObject raw = safeExtractDbObjectFromProjection(project().include("a").plus(10));
|
||||
assertThat(raw, is(notNullValue()));
|
||||
|
||||
DBObject addition = (DBObject) raw.get("a");
|
||||
assertThat(addition, is(notNullValue()));
|
||||
|
||||
List<Object> summands = (List<Object>) addition.get("$add");
|
||||
assertThat(summands, is(notNullValue()));
|
||||
assertThat(summands.size(), is(2));
|
||||
assertThat((String) summands.get(0), is("$a"));
|
||||
assertThat((Integer) summands.get(1), is(10));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void plusWithAlias() {
|
||||
|
||||
DBObject raw = safeExtractDbObjectFromProjection(project().include("a").plus(10).as("b"));
|
||||
assertThat(raw, is(notNullValue()));
|
||||
|
||||
DBObject addition = (DBObject) raw.get("b");
|
||||
assertThat(addition, is(notNullValue()));
|
||||
|
||||
List<Object> summands = (List<Object>) addition.get("$add");
|
||||
assertThat(summands, is(notNullValue()));
|
||||
assertThat(summands.size(), is(2));
|
||||
assertThat((String) summands.get(0), is("$a"));
|
||||
assertThat((Integer) summands.get(1), is(10));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void projectionWithFields() {
|
||||
ProjectionOperation projectionOperation = project(ZipInfoStats.class) //
|
||||
.field("_id", 0) //
|
||||
.field("state", $id()) // $id() -> $_id
|
||||
.field("biggestCity", fields().and("name", $("biggestCity")).and("population", $("biggestPop"))) //
|
||||
.field("smallestCity", fields().and("name", $("smallestCity")).and("population", $("smallestPop")));
|
||||
|
||||
assertThat(projectionOperation, is(notNullValue()));
|
||||
}
|
||||
|
||||
private static DBObject safeExtractDbObjectFromProjection(ProjectionOperation projectionOperation) {
|
||||
|
||||
assertThat(projectionOperation, is(notNullValue()));
|
||||
DBObject dbObject = projectionOperation.toDbObject(new BasicAggregateOperationContext());
|
||||
assertThat(dbObject, is(notNullValue()));
|
||||
Object projection = dbObject.get("$project");
|
||||
assertThat("Expected non null value for key $project ", projection, is(notNullValue()));
|
||||
assertTrue("projection contents should be a " + DBObject.class.getSimpleName(), projection instanceof DBObject);
|
||||
|
||||
return DBObject.class.cast(projection);
|
||||
}
|
||||
|
||||
private static void assertSingleDBObject(String key, Object value, DBObject doc) {
|
||||
|
||||
assertThat(doc, is(notNullValue()));
|
||||
assertThat(doc.get(key), is(value));
|
||||
}
|
||||
}
|
||||
@@ -15,12 +15,33 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core.aggregation;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import com.mongodb.DBObject;
|
||||
|
||||
/**
|
||||
* Represents one single operation in an aggregation pipeline that is aware of an {@link AggregateOperationContext} that
|
||||
* produces an {@link AggregateOperationContext} as output.
|
||||
* Unit tests for {@link SkipOperation}.
|
||||
*
|
||||
* @author Thomas Darimont
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public interface ContextProducingAggregateOperation extends AggregationOperation {
|
||||
AggregateOperationContext getOutputAggregateOperationContext();
|
||||
public class SkipOperationUnitTests {
|
||||
|
||||
static final String OP = "$skip";
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void rejectsNegativeSkip() {
|
||||
new SkipOperation(-1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rendersSkipOperation() {
|
||||
|
||||
SkipOperation operation = new SkipOperation(10L);
|
||||
DBObject dbObject = operation.toDBObject(Aggregation.DEFAULT_CONTEXT);
|
||||
|
||||
assertThat(dbObject.get(OP), is((Object) 10L));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright 2013 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.aggregation;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.springframework.data.mongodb.core.DBObjectUtils.*;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.domain.Sort.Direction;
|
||||
|
||||
import com.mongodb.DBObject;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link SortOperation}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class SortOperationUnitTests {
|
||||
|
||||
@Test
|
||||
public void createsDBObjectForAscendingSortCorrectly() {
|
||||
|
||||
SortOperation operation = new SortOperation(new Sort(Direction.ASC, "foobar"));
|
||||
DBObject result = operation.toDBObject(Aggregation.DEFAULT_CONTEXT);
|
||||
|
||||
DBObject sortValue = getAsDBObject(result, "$sort");
|
||||
assertThat(sortValue, is(notNullValue()));
|
||||
assertThat(sortValue.get("foobar"), is((Object) 1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createsDBObjectForDescendingSortCorrectly() {
|
||||
|
||||
SortOperation operation = new SortOperation(new Sort(Direction.DESC, "foobar"));
|
||||
DBObject result = operation.toDBObject(Aggregation.DEFAULT_CONTEXT);
|
||||
|
||||
DBObject sortValue = getAsDBObject(result, "$sort");
|
||||
assertThat(sortValue, is(notNullValue()));
|
||||
assertThat(sortValue.get("foobar"), is((Object) (0 - 1)));
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,7 @@ import static org.junit.Assert.*;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link Field}.
|
||||
* Unit tests for {@link DocumentField}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user