diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/Aggregation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/Aggregation.java index e5a587c9b..caf5f2ae1 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/Aggregation.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/Aggregation.java @@ -45,6 +45,7 @@ import com.mongodb.DBObject; * @author Mark Paluch * @author Alessio Fachechi * @author Christoph Strobl + * @author Nikolay Bogdanov * @since 1.3 */ public class Aggregation { @@ -160,6 +161,13 @@ public class Aggregation { Assert.isTrue(!aggregationOperations.isEmpty(), "At least one AggregationOperation has to be provided"); Assert.notNull(options, "AggregationOptions must not be null!"); + //check $out is the last operation if exist + for (int i = 0; i < aggregationOperations.size(); i++) { + if (aggregationOperations.get(i) instanceof OutOperation && i != aggregationOperations.size() - 1) { + throw new IllegalArgumentException("The $out operator must be the last stage in the pipeline."); + } + } + this.operations = aggregationOperations; this.options = options; } @@ -318,6 +326,20 @@ public class Aggregation { return new MatchOperation(criteria); } + /** + * Creates a new {@link OutOperation} using the given collection name. This operation must be the last operation + * in the pipeline. + * + * @param outCollectionName collection name to export aggregation results. The {@link OutOperation} creates a new + * collection in the current database if one does not already exist. The collection is + * not visible until the aggregation completes. If the aggregation fails, MongoDB does + * not create the collection. Must not be {@literal null}. + * @return + */ + public static OutOperation out(String outCollectionName) { + return new OutOperation(outCollectionName); + } + /** * Creates a new {@link LookupOperation}. * diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/OutOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/OutOperation.java new file mode 100644 index 000000000..68b357a62 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/OutOperation.java @@ -0,0 +1,51 @@ +/* + * Copyright 2016 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; +import org.springframework.util.Assert; + +/** + * Encapsulates the {@code $out}-operation. + *

+ * We recommend to use the static factory method {@link Aggregation#out(String)} instead of creating instances of this + * class directly. + * + * @see http://docs.mongodb.org/manual/reference/aggregation/out/ + * @author Nikolay Bogdanov + */ +public class OutOperation implements AggregationOperation { + + private final String collectionName; + + /** + * @param outCollectionName Collection name to export the results. Must not be {@literal null}. + */ + public OutOperation(String outCollectionName) { + Assert.notNull(outCollectionName, "Collection name must not be null!"); + this.collectionName = outCollectionName; + } + + /* + * (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("$out", collectionName); + } +} diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java index 4caf61f07..1de24d41b 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java @@ -55,6 +55,7 @@ import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.Venue; import org.springframework.data.mongodb.core.aggregation.AggregationTests.CarDescriptor.Entry; import org.springframework.data.mongodb.core.index.GeospatialIndex; +import org.springframework.data.mongodb.core.query.Criteria; import org.springframework.data.mongodb.core.query.NearQuery; import org.springframework.data.mongodb.core.query.Query; import org.springframework.data.mongodb.repository.Person; @@ -78,6 +79,7 @@ import com.mongodb.util.JSON; * @author Oliver Gierke * @author Christoph Strobl * @author Mark Paluch + * @author Nikolay Bogdanov */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:infrastructure.xml") @@ -1182,6 +1184,50 @@ public class AggregationTests { assertThat(firstItem, isBsonObject().containing("linkedPerson.[0].firstname", "u1")); } + /** + * @see DATAMONGO-1418 + */ + @Test + public void shouldCreateOutputCollection() { + + assumeTrue(mongoVersion.isGreaterThanOrEqualTo(TWO_DOT_SIX)); + + mongoTemplate.save(new Person("Anna", "Ivanova", 21, Person.Sex.FEMALE)); + mongoTemplate.save(new Person("Pavel", "Sidorov", 36, Person.Sex.MALE)); + mongoTemplate.save(new Person("Anastasia", "Volochkova", 29, Person.Sex.FEMALE)); + mongoTemplate.save(new Person("Igor", "Stepanov", 31, Person.Sex.MALE)); + mongoTemplate.save(new Person("Leoniv", "Yakubov", 55, Person.Sex.MALE)); + + String tempOutCollection = "personQueryTemp"; + TypedAggregation agg = newAggregation(Person.class, // + group("sex").count().as("count"), // + sort(DESC, "count"), // + out(tempOutCollection)); + + AggregationResults results = mongoTemplate.aggregate(agg, DBObject.class); + assertThat(results.getMappedResults(), is(empty())); + + List list = mongoTemplate.findAll(DBObject.class, tempOutCollection); + + assertThat(list, hasSize(2)); + assertThat(list.get(0), isBsonObject().containing("_id", "MALE").containing("count", 3)); + assertThat(list.get(1), isBsonObject().containing("_id", "FEMALE").containing("count", 2)); + + mongoTemplate.dropCollection(tempOutCollection); + } + + /** + * @see DATAMONGO-1418 + */ + @Test(expected = IllegalArgumentException.class) + public void outShouldOutBeTheLastOperation() { + + newAggregation(match(new Criteria()), // + group("field1").count().as("totalCount"), // + out("collection1"), // + skip(100)); + } + private void createUsersWithReferencedPersons() { mongoTemplate.dropCollection(User.class); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/OutOperationUnitTest.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/OutOperationUnitTest.java new file mode 100644 index 000000000..3b51da6cd --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/OutOperationUnitTest.java @@ -0,0 +1,31 @@ +/* + * Copyright 2016 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; + +/** + * Unit tests for {@link OutOperation}. + * + * @author Nikolay Bogdanov + */ +public class OutOperationUnitTest { + + @Test(expected = IllegalArgumentException.class) + public void shouldCheckNPEInCreation() { + new OutOperation(null); + } +}