diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 75a4b5815e..6c5d559a8a 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,9 +1,12 @@ + + - [ ] You have read the [Spring Data contribution guidelines](https://github.com/spring-projects/spring-data-build/blob/master/CONTRIBUTING.adoc). - [ ] There is a ticket in the bug tracker for the project in our [JIRA](https://jira.spring.io/browse/DATAMONGO). - [ ] You use the code formatters provided [here](https://github.com/spring-projects/spring-data-build/tree/master/etc/ide) and have them applied to your changes. Don’t submit any formatting related changes. - [ ] You submit test cases (unit or integration tests) that back your changes. - [ ] You added yourself as author in the headers of the classes you touched. Amend the date range in the Apache license header if needed. For new types, add the license header (copy from another file and set the current year only). -- [ ] You provide your full name and an email address registered with your GitHub account. If you’re a first-time submitter, make sure you have completed the [Contributor’s License Agreement form](https://support.springsource.com/spring_committer_signup). \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index ed1d391663..d078db8f05 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,6 +15,7 @@ env: - PROFILE=mongo31 - PROFILE=mongo32 - PROFILE=mongo33 + - PROFILE=mongo34 - PROFILE=mongo34-next # Current MongoDB version is 2.4.2 as of 2016-04, see https://github.com/travis-ci/travis-ci/issues/3694 @@ -22,10 +23,11 @@ env: addons: apt: sources: - - mongodb-3.2-precise + - mongodb-3.4-precise packages: - mongodb-org-server - mongodb-org-shell + - oracle-java8-installer sudo: false diff --git a/README.md b/README.md index ea77d6aa90..33a0115958 100644 --- a/README.md +++ b/README.md @@ -143,8 +143,8 @@ public class MyService { Here are some ways for you to get involved in the community: * Get involved with the Spring community on Stackoverflow and help out on the [spring-data-mongodb](http://stackoverflow.com/questions/tagged/spring-data-mongodb) tag by responding to questions and joining the debate. -* Create [JIRA](https://jira.springframework.org/browse/DATADOC) tickets for bugs and new features and comment and vote on the ones that you are interested in. +* Create [JIRA](https://jira.spring.io/browse/DATAMONGO) tickets for bugs and new features and comment and vote on the ones that you are interested in. * Github is for social coding: if you want to write code, we encourage contributions through pull requests from [forks of this repository](http://help.github.com/forking/). If you want to contribute code this way, please reference a JIRA ticket as well covering the specific issue you are addressing. * Watch for upcoming articles on Spring by [subscribing](http://spring.io/blog) to spring.io. -Before we accept a non-trivial patch or pull request we will need you to sign the [contributor's agreement](https://support.springsource.com/spring_committer_signup). Signing the contributor's agreement does not grant anyone commit rights to the main repository, but it does mean that we can accept your contributions, and you will get an author credit if we do. Active contributors might be asked to join the core team, and given the ability to merge pull requests. +Before we accept a non-trivial patch or pull request we will need you to [sign the Contributor License Agreement](https://cla.pivotal.io/sign/spring). Signing the contributor’s agreement does not grant anyone commit rights to the main repository, but it does mean that we can accept your contributions, and you will get an author credit if we do. If you forget to do so, you'll be reminded when you submit a pull request. Active contributors might be asked to join the core team, and given the ability to merge pull requests. diff --git a/pom.xml b/pom.xml index ea80a3cb74..75fa63795e 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-mongodb-parent - 1.10.0.BUILD-SNAPSHOT + 1.11.0.BUILD-SNAPSHOT pom Spring Data MongoDB @@ -15,7 +15,7 @@ org.springframework.data.build spring-data-parent - 1.9.0.BUILD-SNAPSHOT + 1.10.0.BUILD-SNAPSHOT @@ -28,7 +28,7 @@ multi spring-data-mongodb - 1.13.0.BUILD-SNAPSHOT + 1.14.0.BUILD-SNAPSHOT 2.14.3 2.13.0 @@ -178,11 +178,20 @@ + + + mongo34 + + 3.4.0 + + + + mongo34-next - 3.4.0-SNAPSHOT + 3.4.1-SNAPSHOT diff --git a/spring-data-mongodb-cross-store/pom.xml b/spring-data-mongodb-cross-store/pom.xml index ae0a5d6c8f..dc5a04a7ad 100644 --- a/spring-data-mongodb-cross-store/pom.xml +++ b/spring-data-mongodb-cross-store/pom.xml @@ -6,7 +6,7 @@ org.springframework.data spring-data-mongodb-parent - 1.10.0.BUILD-SNAPSHOT + 1.11.0.BUILD-SNAPSHOT ../pom.xml @@ -48,7 +48,7 @@ org.springframework.data spring-data-mongodb - 1.10.0.BUILD-SNAPSHOT + 1.11.0.BUILD-SNAPSHOT diff --git a/spring-data-mongodb-distribution/pom.xml b/spring-data-mongodb-distribution/pom.xml index 2d02722262..9876e11ee3 100644 --- a/spring-data-mongodb-distribution/pom.xml +++ b/spring-data-mongodb-distribution/pom.xml @@ -13,7 +13,7 @@ org.springframework.data spring-data-mongodb-parent - 1.10.0.BUILD-SNAPSHOT + 1.11.0.BUILD-SNAPSHOT ../pom.xml diff --git a/spring-data-mongodb-log4j/pom.xml b/spring-data-mongodb-log4j/pom.xml index ee5e3336db..c0436bac90 100644 --- a/spring-data-mongodb-log4j/pom.xml +++ b/spring-data-mongodb-log4j/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-mongodb-parent - 1.10.0.BUILD-SNAPSHOT + 1.11.0.BUILD-SNAPSHOT ../pom.xml diff --git a/spring-data-mongodb-log4j/src/main/java/org/springframework/data/mongodb/log4j/MongoLog4jAppender.java b/spring-data-mongodb-log4j/src/main/java/org/springframework/data/mongodb/log4j/MongoLog4jAppender.java index 165061e6bd..0474c56dd0 100644 --- a/spring-data-mongodb-log4j/src/main/java/org/springframework/data/mongodb/log4j/MongoLog4jAppender.java +++ b/spring-data-mongodb-log4j/src/main/java/org/springframework/data/mongodb/log4j/MongoLog4jAppender.java @@ -18,6 +18,8 @@ import java.net.UnknownHostException; import java.util.Arrays; import java.util.Calendar; +import java.util.Collections; +import java.util.List; import java.util.Map; import org.apache.log4j.AppenderSkeleton; @@ -31,14 +33,17 @@ import com.mongodb.DB; import com.mongodb.Mongo; import com.mongodb.MongoClient; +import com.mongodb.MongoCredential; +import com.mongodb.ServerAddress; import com.mongodb.WriteConcern; /** * Log4j appender writing log entries into a MongoDB instance. - * + * * @author Jon Brisbin * @author Oliver Gierke - * @auhtor Christoph Strobl + * @author Christoph Strobl + * @author Ricardo Espirito Santo */ public class MongoLog4jAppender extends AppenderSkeleton { @@ -56,6 +61,9 @@ public class MongoLog4jAppender extends AppenderSkeleton { protected String host = "localhost"; protected int port = 27017; + protected String username; + protected String password; + protected String authenticationDatabase; protected String database = "logs"; protected String collectionPattern = "%c"; protected PatternLayout collectionLayout = new PatternLayout(collectionPattern); @@ -65,8 +73,7 @@ public class MongoLog4jAppender extends AppenderSkeleton { protected Mongo mongo; protected DB db; - public MongoLog4jAppender() { - } + public MongoLog4jAppender() {} public MongoLog4jAppender(boolean isActive) { super(isActive); @@ -88,6 +95,53 @@ public void setPort(int port) { this.port = port; } + /** + * @return + * @since 1.10 + */ + public String getUsername() { + return username; + } + + /** + * @param username may be {@literal null} for unauthenticated access. + * @since 1.10 + */ + public void setUsername(String username) { + this.username = username; + } + + /** + * @return + * @since 1.10 + */ + public String getPassword() { + return password; + } + + /** + * @param password may be {@literal null} for unauthenticated access. + * @since 1.10 + */ + public void setPassword(String password) { + this.password = password; + } + + /** + * @return + */ + public String getAuthenticationDatabase() { + return authenticationDatabase; + } + + /** + * @param authenticationDatabase may be {@literal null} to use {@link #getDatabase()} as authentication database. + * @since 1.10 + */ + public void setAuthenticationDatabase(String authenticationDatabase) { + this.authenticationDatabase = authenticationDatabase; + } + public String getDatabase() { return database; } @@ -113,14 +167,14 @@ public void setApplicationId(String applicationId) { this.applicationId = applicationId; } - public void setWarnOrHigherWriteConcern(String wc) { - this.warnOrHigherWriteConcern = WriteConcern.valueOf(wc); - } - public String getWarnOrHigherWriteConcern() { return warnOrHigherWriteConcern.toString(); } + public void setWarnOrHigherWriteConcern(String wc) { + this.warnOrHigherWriteConcern = WriteConcern.valueOf(wc); + } + public String getInfoOrLowerWriteConcern() { return infoOrLowerWriteConcern.toString(); } @@ -130,10 +184,26 @@ public void setInfoOrLowerWriteConcern(String wc) { } protected void connectToMongo() throws UnknownHostException { - this.mongo = new MongoClient(host, port); + + this.mongo = createMongoClient(); this.db = mongo.getDB(database); } + private MongoClient createMongoClient() throws UnknownHostException { + + ServerAddress serverAddress = new ServerAddress(host, port); + + if (null == password || null == username) { + return new MongoClient(serverAddress); + } + + String authenticationDatabaseToUse = authenticationDatabase == null ? this.database : authenticationDatabase; + MongoCredential mongoCredential = MongoCredential.createCredential(username, + authenticationDatabaseToUse, password.toCharArray()); + List credentials = Collections.singletonList(mongoCredential); + return new MongoClient(serverAddress, credentials); + } + /* * (non-Javadoc) * @see org.apache.log4j.AppenderSkeleton#append(org.apache.log4j.spi.LoggingEvent) diff --git a/spring-data-mongodb-log4j/src/test/java/org/springframework/data/mongodb/log4j/MongoLog4jAppenderAuthenticationIntegrationTests.java b/spring-data-mongodb-log4j/src/test/java/org/springframework/data/mongodb/log4j/MongoLog4jAppenderAuthenticationIntegrationTests.java new file mode 100644 index 0000000000..6cf2818dea --- /dev/null +++ b/spring-data-mongodb-log4j/src/test/java/org/springframework/data/mongodb/log4j/MongoLog4jAppenderAuthenticationIntegrationTests.java @@ -0,0 +1,114 @@ +/* + * 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.log4j; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; + +import java.util.Calendar; +import java.util.Collections; + +import org.apache.log4j.LogManager; +import org.apache.log4j.Logger; +import org.apache.log4j.MDC; +import org.apache.log4j.PropertyConfigurator; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import com.mongodb.BasicDBList; +import com.mongodb.BasicDBObject; +import com.mongodb.BasicDBObjectBuilder; +import com.mongodb.DB; +import com.mongodb.DBCursor; +import com.mongodb.MongoClient; +import com.mongodb.MongoCredential; +import com.mongodb.ServerAddress; + +/** + * Integration tests for {@link MongoLog4jAppender} using authentication. + * + * @author Mark Paluch + */ +public class MongoLog4jAppenderAuthenticationIntegrationTests { + + private final static String username = "admin"; + private final static String password = "test"; + private final static String authenticationDatabase = "logs"; + + MongoClient mongo; + DB db; + String collection; + ServerAddress serverLocation; + + Logger log; + + @Before + public void setUp() throws Exception { + + serverLocation = new ServerAddress("localhost", 27017); + + mongo = new MongoClient(serverLocation); + db = mongo.getDB("logs"); + + BasicDBList roles = new BasicDBList(); + roles.add("dbOwner"); + db.command(new BasicDBObjectBuilder().add("createUser", username).add("pwd", password).add("roles", roles).get()); + mongo.close(); + + mongo = new MongoClient(serverLocation, Collections + .singletonList(MongoCredential.createCredential(username, authenticationDatabase, password.toCharArray()))); + db = mongo.getDB("logs"); + + Calendar now = Calendar.getInstance(); + collection = String.valueOf(now.get(Calendar.YEAR)) + String.format("%1$02d", now.get(Calendar.MONTH) + 1); + + LogManager.resetConfiguration(); + PropertyConfigurator.configure(getClass().getResource("/log4j-with-authentication.properties")); + + log = Logger.getLogger(MongoLog4jAppenderIntegrationTests.class.getName()); + } + + @After + public void tearDown() { + + if (db != null) { + db.getCollection(collection).remove(new BasicDBObject()); + db.command(new BasicDBObject("dropUser", username)); + } + + LogManager.resetConfiguration(); + PropertyConfigurator.configure(getClass().getResource("/log4j.properties")); + } + + @Test + public void testLogging() { + + log.debug("DEBUG message"); + log.info("INFO message"); + log.warn("WARN message"); + log.error("ERROR message"); + + DBCursor msgs = db.getCollection(collection).find(); + assertThat(msgs.count(), is(4)); + } + + @Test + public void testProperties() { + MDC.put("property", "one"); + log.debug("DEBUG message"); + } +} diff --git a/spring-data-mongodb-log4j/src/test/java/org/springframework/data/mongodb/log4j/MongoLog4jAppenderIntegrationTests.java b/spring-data-mongodb-log4j/src/test/java/org/springframework/data/mongodb/log4j/MongoLog4jAppenderIntegrationTests.java index 6862b054dd..0271d47cdc 100644 --- a/spring-data-mongodb-log4j/src/test/java/org/springframework/data/mongodb/log4j/MongoLog4jAppenderIntegrationTests.java +++ b/spring-data-mongodb-log4j/src/test/java/org/springframework/data/mongodb/log4j/MongoLog4jAppenderIntegrationTests.java @@ -20,8 +20,10 @@ import java.util.Calendar; +import org.apache.log4j.LogManager; import org.apache.log4j.Logger; import org.apache.log4j.MDC; +import org.apache.log4j.PropertyConfigurator; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -30,31 +32,34 @@ import com.mongodb.DB; import com.mongodb.DBCursor; import com.mongodb.MongoClient; +import com.mongodb.ServerAddress; /** * Integration tests for {@link MongoLog4jAppender}. - * + * * @author Jon Brisbin * @author Oliver Gierke * @author Christoph Strobl */ public class MongoLog4jAppenderIntegrationTests { - static final String NAME = MongoLog4jAppenderIntegrationTests.class.getName(); - - private static final Logger log = Logger.getLogger(NAME); MongoClient mongo; DB db; String collection; + ServerAddress serverLocation; + Logger log; @Before public void setUp() throws Exception { + serverLocation = new ServerAddress("localhost", 27017); - mongo = new MongoClient("localhost", 27017); + mongo = new MongoClient(serverLocation); db = mongo.getDB("logs"); Calendar now = Calendar.getInstance(); collection = String.valueOf(now.get(Calendar.YEAR)) + String.format("%1$02d", now.get(Calendar.MONTH) + 1); + + log = Logger.getLogger(MongoLog4jAppenderIntegrationTests.class.getName()); } @After @@ -76,7 +81,6 @@ public void testLogging() { @Test public void testProperties() { - MDC.put("property", "one"); log.debug("DEBUG message"); } diff --git a/spring-data-mongodb-log4j/src/test/java/org/springframework/data/mongodb/log4j/MongoLog4jAppenderUnitTests.java b/spring-data-mongodb-log4j/src/test/java/org/springframework/data/mongodb/log4j/MongoLog4jAppenderUnitTests.java index 2be86e958d..e192fffc37 100644 --- a/spring-data-mongodb-log4j/src/test/java/org/springframework/data/mongodb/log4j/MongoLog4jAppenderUnitTests.java +++ b/spring-data-mongodb-log4j/src/test/java/org/springframework/data/mongodb/log4j/MongoLog4jAppenderUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,10 +24,7 @@ */ public class MongoLog4jAppenderUnitTests { - /** - * @see DATAMONGO-641 - */ - @Test + @Test // DATAMONGO-641 public void closesWithoutMongoInstancePresent() { new MongoLog4jAppender().close(); } diff --git a/spring-data-mongodb-log4j/src/test/resources/log4j-with-authentication.properties b/spring-data-mongodb-log4j/src/test/resources/log4j-with-authentication.properties new file mode 100644 index 0000000000..d83684463e --- /dev/null +++ b/spring-data-mongodb-log4j/src/test/resources/log4j-with-authentication.properties @@ -0,0 +1,16 @@ +log4j.rootCategory=INFO, mongo + +log4j.appender.mongo=org.springframework.data.mongodb.log4j.MongoLog4jAppender +log4j.appender.mongo.layout=org.apache.log4j.PatternLayout +log4j.appender.mongo.layout.ConversionPattern=%d %p [%c] - <%m>%n +log4j.appender.mongo.host = localhost +log4j.appender.mongo.port = 27017 +log4j.appender.mongo.database = logs +log4j.appender.mongo.username = admin +log4j.appender.mongo.password = test +log4j.appender.mongo.authenticationDatabase = logs +log4j.appender.mongo.collectionPattern = %X{year}%X{month} +log4j.appender.mongo.applicationId = my.application +log4j.appender.mongo.warnOrHigherWriteConcern = FSYNC_SAFE + +log4j.category.org.springframework.data.mongodb=DEBUG diff --git a/spring-data-mongodb-log4j/src/test/resources/log4j.properties b/spring-data-mongodb-log4j/src/test/resources/log4j.properties index 88459b3ffa..75136af0a8 100644 --- a/spring-data-mongodb-log4j/src/test/resources/log4j.properties +++ b/spring-data-mongodb-log4j/src/test/resources/log4j.properties @@ -1,13 +1,13 @@ -log4j.rootCategory=INFO, stdout +log4j.rootCategory=INFO, mongo -log4j.appender.stdout=org.springframework.data.mongodb.log4j.MongoLog4jAppender -log4j.appender.stdout.layout=org.apache.log4j.PatternLayout -log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - <%m>%n -log4j.appender.stdout.host = localhost -log4j.appender.stdout.port = 27017 -log4j.appender.stdout.database = logs -log4j.appender.stdout.collectionPattern = %X{year}%X{month} -log4j.appender.stdout.applicationId = my.application -log4j.appender.stdout.warnOrHigherWriteConcern = FSYNC_SAFE +log4j.appender.mongo=org.springframework.data.mongodb.log4j.MongoLog4jAppender +log4j.appender.mongo.layout=org.apache.log4j.PatternLayout +log4j.appender.mongo.layout.ConversionPattern=%d %p [%c] - <%m>%n +log4j.appender.mongo.host = localhost +log4j.appender.mongo.port = 27017 +log4j.appender.mongo.database = logs +log4j.appender.mongo.collectionPattern = %X{year}%X{month} +log4j.appender.mongo.applicationId = my.application +log4j.appender.mongo.warnOrHigherWriteConcern = FSYNC_SAFE log4j.category.org.springframework.data.mongodb=DEBUG diff --git a/spring-data-mongodb/pom.xml b/spring-data-mongodb/pom.xml index 8072d3f665..ea1b594b99 100644 --- a/spring-data-mongodb/pom.xml +++ b/spring-data-mongodb/pom.xml @@ -11,7 +11,7 @@ org.springframework.data spring-data-mongodb-parent - 1.10.0.BUILD-SNAPSHOT + 1.11.0.BUILD-SNAPSHOT ../pom.xml diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/MappingMongoConverterParser.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/MappingMongoConverterParser.java index 3aae756891..fa4ac4946e 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/MappingMongoConverterParser.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/MappingMongoConverterParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2014 the original author or authors. + * Copyright 2011-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -361,7 +361,9 @@ private static class NegatingFilter implements TypeFilter { * @param filters */ public NegatingFilter(TypeFilter... filters) { - Assert.notNull(filters); + + Assert.notNull(filters, "TypeFilters must not be null"); + this.delegates = new HashSet(Arrays.asList(filters)); } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultBulkOperations.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultBulkOperations.java index 40f3bf77c9..440301857a 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultBulkOperations.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultBulkOperations.java @@ -25,6 +25,7 @@ import org.springframework.data.util.Pair; import org.springframework.util.Assert; +import com.mongodb.BasicDBObject; import com.mongodb.BulkWriteException; import com.mongodb.BulkWriteOperation; import com.mongodb.BulkWriteRequestBuilder; @@ -38,6 +39,7 @@ * * @author Tobias Trelle * @author Oliver Gierke + * @author Christoph Strobl * @since 1.9 */ class DefaultBulkOperations implements BulkOperations { @@ -117,7 +119,15 @@ public BulkOperations insert(Object document) { Assert.notNull(document, "Document must not be null!"); - bulk.insert((DBObject) mongoOperations.getConverter().convertToMongoType(document)); + if (document instanceof DBObject) { + + bulk.insert((DBObject) document); + return this; + } + + DBObject sink = new BasicDBObject(); + mongoOperations.getConverter().write(document, sink); + bulk.insert(sink); return this; } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultIndexOperations.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultIndexOperations.java index a1f2c96725..ac4c24998d 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultIndexOperations.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultIndexOperations.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2015 the original author or authors. + * Copyright 2011-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,17 +15,15 @@ */ package org.springframework.data.mongodb.core; -import static org.springframework.data.domain.Sort.Direction.*; - import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.List; import org.springframework.dao.DataAccessException; +import org.springframework.data.mongodb.core.convert.QueryMapper; import org.springframework.data.mongodb.core.index.IndexDefinition; -import org.springframework.data.mongodb.core.index.IndexField; import org.springframework.data.mongodb.core.index.IndexInfo; +import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity; import org.springframework.util.Assert; import com.mongodb.DBCollection; @@ -42,12 +40,12 @@ */ public class DefaultIndexOperations implements IndexOperations { - private static final Double ONE = Double.valueOf(1); - private static final Double MINUS_ONE = Double.valueOf(-1); - private static final Collection TWO_D_IDENTIFIERS = Arrays.asList("2d", "2dsphere"); + private static final String PARTIAL_FILTER_EXPRESSION_KEY = "partialFilterExpression"; private final MongoOperations mongoOperations; private final String collectionName; + private final QueryMapper mapper; + private final Class type; /** * Creates a new {@link DefaultIndexOperations}. @@ -56,12 +54,26 @@ public class DefaultIndexOperations implements IndexOperations { * @param collectionName must not be {@literal null}. */ public DefaultIndexOperations(MongoOperations mongoOperations, String collectionName) { + this(mongoOperations, collectionName, null); + } + + /** + * Creates a new {@link DefaultIndexOperations}. + * + * @param mongoOperations must not be {@literal null}. + * @param collectionName must not be {@literal null}. + * @param type Type used for mapping potential partial index filter expression. Can be {@literal null}. + * @since 1.10 + */ + public DefaultIndexOperations(MongoOperations mongoOperations, String collectionName, Class type) { Assert.notNull(mongoOperations, "MongoOperations must not be null!"); Assert.notNull(collectionName, "Collection name can not be null!"); this.mongoOperations = mongoOperations; this.collectionName = collectionName; + this.mapper = new QueryMapper(mongoOperations.getConverter()); + this.type = type; } /* @@ -69,9 +81,20 @@ public DefaultIndexOperations(MongoOperations mongoOperations, String collection * @see org.springframework.data.mongodb.core.IndexOperations#ensureIndex(org.springframework.data.mongodb.core.index.IndexDefinition) */ public void ensureIndex(final IndexDefinition indexDefinition) { + mongoOperations.execute(collectionName, new CollectionCallback() { public Object doInCollection(DBCollection collection) throws MongoException, DataAccessException { DBObject indexOptions = indexDefinition.getIndexOptions(); + + if (indexOptions != null && indexOptions.containsField(PARTIAL_FILTER_EXPRESSION_KEY)) { + + Assert.isInstanceOf(DBObject.class, indexOptions.get(PARTIAL_FILTER_EXPRESSION_KEY)); + + indexOptions.put(PARTIAL_FILTER_EXPRESSION_KEY, + mapper.getMappedObject((DBObject) indexOptions.get(PARTIAL_FILTER_EXPRESSION_KEY), + lookupPersistentEntity(type, collectionName))); + } + if (indexOptions != null) { collection.createIndex(indexDefinition.getIndexKeys(), indexOptions); } else { @@ -79,6 +102,24 @@ public Object doInCollection(DBCollection collection) throws MongoException, Dat } return null; } + + private MongoPersistentEntity lookupPersistentEntity(Class entityType, String collection) { + + if (entityType != null) { + return mongoOperations.getConverter().getMappingContext().getPersistentEntity(entityType); + } + + Collection> entities = mongoOperations.getConverter().getMappingContext() + .getPersistentEntities(); + + for (MongoPersistentEntity entity : entities) { + if (entity.getCollection().equals(collection)) { + return entity; + } + } + + return null; + } }); } @@ -126,7 +167,9 @@ public Void doInCollection(DBCollection collection) throws MongoException, DataA public List getIndexInfo() { return mongoOperations.execute(collectionName, new CollectionCallback>() { + public List doInCollection(DBCollection collection) throws MongoException, DataAccessException { + List dbObjectList = collection.getIndexInfo(); return getIndexData(dbObjectList); } @@ -136,44 +179,7 @@ private List getIndexData(List dbObjectList) { List indexInfoList = new ArrayList(); for (DBObject ix : dbObjectList) { - - DBObject keyDbObject = (DBObject) ix.get("key"); - int numberOfElements = keyDbObject.keySet().size(); - - List indexFields = new ArrayList(numberOfElements); - - for (String key : keyDbObject.keySet()) { - - Object value = keyDbObject.get(key); - - if (TWO_D_IDENTIFIERS.contains(value)) { - indexFields.add(IndexField.geo(key)); - } else if ("text".equals(value)) { - - DBObject weights = (DBObject) ix.get("weights"); - for (String fieldName : weights.keySet()) { - indexFields.add(IndexField.text(fieldName, Float.valueOf(weights.get(fieldName).toString()))); - } - - } else { - - Double keyValue = new Double(value.toString()); - - if (ONE.equals(keyValue)) { - indexFields.add(IndexField.create(key, ASC)); - } else if (MINUS_ONE.equals(keyValue)) { - indexFields.add(IndexField.create(key, DESC)); - } - } - } - - String name = ix.get("name").toString(); - - boolean unique = ix.containsField("unique") ? (Boolean) ix.get("unique") : false; - boolean dropDuplicates = ix.containsField("dropDups") ? (Boolean) ix.get("dropDups") : false; - boolean sparse = ix.containsField("sparse") ? (Boolean) ix.get("sparse") : false; - String language = ix.containsField("default_language") ? (String) ix.get("default_language") : ""; - indexInfoList.add(new IndexInfo(indexFields, name, unique, dropDuplicates, sparse, language)); + indexInfoList.add(IndexInfo.indexInfoOf(ix)); } return indexInfoList; diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/GeoCommandStatistics.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/GeoCommandStatistics.java index 93cd6db533..c0aa38d2ec 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/GeoCommandStatistics.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/GeoCommandStatistics.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 the original author or authors. + * Copyright 2016-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -62,7 +62,7 @@ public static GeoCommandStatistics from(DBObject commandResult) { * didn't return any result introduced in MongoDB 3.2 RC1. * * @return - * @see https://jira.mongodb.org/browse/SERVER-21024 + * @see MongoDB Jira SERVER-21024 */ public double getAverageDistance() { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/GeoJsonConfiguration.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/GeoJsonConfiguration.java index cb29dca57d..ccc81cd799 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/GeoJsonConfiguration.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/GeoJsonConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 the original author or authors. + * Copyright 2015-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. @@ -17,15 +17,14 @@ import org.springframework.context.annotation.Bean; import org.springframework.data.mongodb.core.geo.GeoJsonModule; -import org.springframework.data.web.config.SpringDataWebConfigurationMixin; +import org.springframework.data.web.config.SpringDataJacksonModules; /** * Configuration class to expose {@link GeoJsonModule} as a Spring bean. * * @author Oliver Gierke */ -@SpringDataWebConfigurationMixin -public class GeoJsonConfiguration { +public class GeoJsonConfiguration implements SpringDataJacksonModules { @Bean public GeoJsonModule geoJsonModule() { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoAdmin.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoAdmin.java index 804ad5e593..aaa64ff274 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoAdmin.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoAdmin.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2013 the original author or authors. + * Copyright 2011-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,7 +27,8 @@ * Mongo server administration exposed via JMX annotations * * @author Mark Pollack - * @author Thomas Darimont + * @author Thomas Darimont + * @author Mark Paluch */ @ManagedResource(description = "Mongo Admin Operations") public class MongoAdmin implements MongoAdminOperations { @@ -35,10 +36,11 @@ public class MongoAdmin implements MongoAdminOperations { private final Mongo mongo; private String username; private String password; - private String authenticationDatabaseName; + private String authenticationDatabaseName; public MongoAdmin(Mongo mongo) { - Assert.notNull(mongo); + + Assert.notNull(mongo, "Mongo must not be null!"); this.mongo = mongo; } @@ -84,16 +86,16 @@ public void setPassword(String password) { this.password = password; } - /** - * Sets the authenticationDatabaseName to use to authenticate with the Mongo database. - * - * @param authenticationDatabaseName The authenticationDatabaseName to use. - */ - public void setAuthenticationDatabaseName(String authenticationDatabaseName) { - this.authenticationDatabaseName = authenticationDatabaseName; - } - + /** + * Sets the authenticationDatabaseName to use to authenticate with the Mongo database. + * + * @param authenticationDatabaseName The authenticationDatabaseName to use. + */ + public void setAuthenticationDatabaseName(String authenticationDatabaseName) { + this.authenticationDatabaseName = authenticationDatabaseName; + } + DB getDB(String databaseName) { - return MongoDbUtils.getDB(mongo, databaseName, new UserCredentials(username, password), authenticationDatabaseName); + return MongoDbUtils.getDB(mongo, databaseName, new UserCredentials(username, password), authenticationDatabaseName); } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java index a126edb269..f8708cb388 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java @@ -1,5 +1,5 @@ /* - * Copyright 2010-2016 the original author or authors. + * Copyright 2010-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -126,7 +126,7 @@ /** * Primary implementation of {@link MongoOperations}. - * + * * @author Thomas Risberg * @author Graeme Rocher * @author Mark Pollack @@ -177,7 +177,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware { /** * Constructor used for a basic template configuration - * + * * @param mongo must not be {@literal null}. * @param databaseName must not be {@literal null} or empty. */ @@ -188,7 +188,7 @@ public MongoTemplate(Mongo mongo, String databaseName) { /** * Constructor used for a template configuration with user credentials in the form of * {@link org.springframework.data.authentication.UserCredentials} - * + * * @param mongo must not be {@literal null}. * @param databaseName must not be {@literal null} or empty. * @param userCredentials @@ -199,7 +199,7 @@ public MongoTemplate(Mongo mongo, String databaseName, UserCredentials userCrede /** * Constructor used for a basic template configuration. - * + * * @param mongoDbFactory must not be {@literal null}. */ public MongoTemplate(MongoDbFactory mongoDbFactory) { @@ -208,13 +208,13 @@ public MongoTemplate(MongoDbFactory mongoDbFactory) { /** * Constructor used for a basic template configuration. - * + * * @param mongoDbFactory must not be {@literal null}. * @param mongoConverter */ public MongoTemplate(MongoDbFactory mongoDbFactory, MongoConverter mongoConverter) { - Assert.notNull(mongoDbFactory); + Assert.notNull(mongoDbFactory, "MongoDbFactory must not be null!"); this.mongoDbFactory = mongoDbFactory; this.exceptionTranslator = mongoDbFactory.getExceptionTranslator(); @@ -237,7 +237,7 @@ public MongoTemplate(MongoDbFactory mongoDbFactory, MongoConverter mongoConverte /** * Configures the {@link WriteResultChecking} to be used with the template. Setting {@literal null} will reset the * default of {@value #DEFAULT_WRITE_RESULT_CHECKING}. - * + * * @param resultChecking */ public void setWriteResultChecking(WriteResultChecking resultChecking) { @@ -248,7 +248,7 @@ public void setWriteResultChecking(WriteResultChecking resultChecking) { * Configures the {@link WriteConcern} to be used with the template. If none is configured the {@link WriteConcern} * configured on the {@link MongoDbFactory} will apply. If you configured a {@link Mongo} instance no * {@link WriteConcern} will be used. - * + * * @param writeConcern */ public void setWriteConcern(WriteConcern writeConcern) { @@ -257,7 +257,7 @@ public void setWriteConcern(WriteConcern writeConcern) { /** * Configures the {@link WriteConcernResolver} to be used with the template. - * + * * @param writeConcernResolver */ public void setWriteConcernResolver(WriteConcernResolver writeConcernResolver) { @@ -267,7 +267,7 @@ public void setWriteConcernResolver(WriteConcernResolver writeConcernResolver) { /** * Used by @{link {@link #prepareCollection(DBCollection)} to set the {@link ReadPreference} before any operations are * performed. - * + * * @param readPreference */ public void setReadPreference(ReadPreference readPreference) { @@ -294,7 +294,7 @@ public void setApplicationContext(ApplicationContext applicationContext) throws * they were registered for the current {@link MappingContext}. If no creator for the current {@link MappingContext} * can be found we manually add the internally created one as {@link ApplicationListener} to make sure indexes get * created appropriately for entity types persisted through this {@link MongoTemplate} instance. - * + * * @param context must not be {@literal null}. */ private void prepareIndexCreator(ApplicationContext context) { @@ -314,15 +314,15 @@ private void prepareIndexCreator(ApplicationContext context) { } /** - * Returns the default {@link org.springframework.data.mongodb.core.core.convert.MongoConverter}. - * + * Returns the default {@link org.springframework.data.mongodb.core.convert.MongoConverter}. + * * @return */ public MongoConverter getConverter() { return this.mongoConverter; } - /* + /* * (non-Javadoc) * @see org.springframework.data.mongodb.core.MongoOperations#executeAsStream(org.springframework.data.mongodb.core.query.Query, java.lang.Class) */ @@ -332,7 +332,7 @@ public CloseableIterator stream(final Query query, final Class entityT return stream(query, entityType, determineCollectionName(entityType)); } - /* + /* * (non-Javadoc) * @see org.springframework.data.mongodb.core.MongoOperations#stream(org.springframework.data.mongodb.core.query.Query, java.lang.Class, java.lang.String) */ @@ -428,7 +428,7 @@ public void executeQuery(Query query, String collectionName, DocumentCallbackHan /** * Execute a MongoDB query and iterate over the query results on a per-document basis with a * {@link DocumentCallbackHandler} using the provided CursorPreparer. - * + * * @param query the query class that specifies the criteria used to find a record and also an optional fields * specification, must not be {@literal null}. * @param collectionName name of the collection to retrieve the objects from @@ -439,7 +439,7 @@ public void executeQuery(Query query, String collectionName, DocumentCallbackHan protected void executeQuery(Query query, String collectionName, DocumentCallbackHandler dch, CursorPreparer preparer) { - Assert.notNull(query); + Assert.notNull(query, "Query must not be null!"); DBObject queryObject = queryMapper.getMappedObject(query.getQueryObject(), null); DBObject sortObject = query.getSortObject(); @@ -455,7 +455,7 @@ protected void executeQuery(Query query, String collectionName, DocumentCallback public T execute(DbCallback action) { - Assert.notNull(action); + Assert.notNull(action, "DbCallbackmust not be null!"); try { DB db = this.getDb(); @@ -471,7 +471,7 @@ public T execute(Class entityClass, CollectionCallback callback) { public T execute(String collectionName, CollectionCallback callback) { - Assert.notNull(callback); + Assert.notNull(callback, "CollectionCallback must not be null!"); try { DBCollection collection = getAndPrepareCollection(getDb(), collectionName); @@ -557,7 +557,7 @@ public IndexOperations indexOps(String collectionName) { } public IndexOperations indexOps(Class entityClass) { - return new DefaultIndexOperations(this, determineCollectionName(entityClass)); + return new DefaultIndexOperations(this, determineCollectionName(entityClass), entityClass); } public BulkOperations bulkOps(BulkMode bulkMode, String collectionName) { @@ -699,8 +699,8 @@ public GeoResults geoNear(NearQuery near, Class entityClass, String co /* * As MongoDB currently (2.4.4) doesn't support the skipping of elements in near queries * we skip the elements ourselves to avoid at least the document 2 object mapping overhead. - * - * @see https://jira.mongodb.org/browse/SERVER-3925 + * + * @see MongoDB Jira: SERVER-3925 */ if (index >= elementsToSkip) { result.add(callback.doWith((DBObject) element)); @@ -749,7 +749,8 @@ public T findAndRemove(Query query, Class entityClass, String collectionN } public long count(Query query, Class entityClass) { - Assert.notNull(entityClass); + + Assert.notNull(entityClass, "Entity class must not be null!"); return count(query, entityClass, determineCollectionName(entityClass)); } @@ -763,7 +764,8 @@ public long count(final Query query, String collectionName) { */ public long count(Query query, Class entityClass, String collectionName) { - Assert.hasText(collectionName); + Assert.hasText(collectionName, "Collection name must not be null or empty!"); + final DBObject dbObject = query == null ? null : queryMapper.getMappedObject(query.getQueryObject(), entityClass == null ? null : mappingContext.getPersistentEntity(entityClass)); @@ -804,7 +806,7 @@ protected void ensureNotIterable(Object o) { /** * Prepare the collection before any processing is done using it. This allows a convenient way to apply settings like * slaveOk() etc. Can be overridden in sub-classes. - * + * * @param collection */ protected void prepareCollection(DBCollection collection) { @@ -818,7 +820,7 @@ protected void prepareCollection(DBCollection collection) { * settings in sub-classes.
* In case of using MongoDB Java driver version 3 the returned {@link WriteConcern} will be defaulted to * {@link WriteConcern#ACKNOWLEDGED} when {@link WriteResultChecking} is set to {@link WriteResultChecking#EXCEPTION}. - * + * * @param writeConcern any WriteConcern already configured or null * @return The prepared WriteConcern or null */ @@ -934,7 +936,7 @@ protected void doInsertAll(Collection listToSave, MongoWriter void doInsertBatch(String collectionName, Collection batchToSave, MongoWriter writer) { - Assert.notNull(writer); + Assert.notNull(writer, "MongoWriter must not be null!"); List dbObjectList = new ArrayList(); for (T o : batchToSave) { @@ -963,14 +965,14 @@ protected void doInsertBatch(String collectionName, Collection public void save(Object objectToSave) { - Assert.notNull(objectToSave); + Assert.notNull(objectToSave, "Object to save must not be null!"); save(objectToSave, determineEntityCollectionName(objectToSave)); } public void save(Object objectToSave, String collectionName) { - Assert.notNull(objectToSave); - Assert.hasText(collectionName); + Assert.notNull(objectToSave, "Object to save must not be null!"); + Assert.hasText(collectionName, "Collection name must not be null or empty!"); MongoPersistentEntity mongoPersistentEntity = getPersistentEntity(objectToSave.getClass()); @@ -1216,7 +1218,7 @@ public WriteResult remove(Object object) { public WriteResult remove(Object object, String collection) { - Assert.hasText(collection); + Assert.hasText(collection, "Collection name must not be null or empty!"); if (object == null) { return null; @@ -1228,7 +1230,7 @@ public WriteResult remove(Object object, String collection) { /** * Returns {@link Entry} containing the field name of the id property as {@link Entry#getKey()} and the {@link Id}s * property value as its {@link Entry#getValue()}. - * + * * @param object * @return */ @@ -1255,7 +1257,7 @@ private Entry extractIdPropertyAndValue(Object object) { /** * Returns a {@link Query} for the given entity by its id. - * + * * @param object must not be {@literal null}. * @return */ @@ -1267,7 +1269,7 @@ private Query getIdQueryFor(Object object) { /** * Returns a {@link Query} for the given entities by their ids. - * + * * @param objects must not be {@literal null} or {@literal empty}. * @return */ @@ -1538,7 +1540,7 @@ public List findAllAndRemove(Query query, Class entityClass, String co * Retrieve and remove all documents matching the given {@code query} by calling {@link #find(Query, Class, String)} * and {@link #remove(Query, Class, String)}, whereas the {@link Query} for {@link #remove(Query, Class, String)} is * constructed out of the find result. - * + * * @param collectionName * @param query * @param entityClass @@ -1578,7 +1580,7 @@ protected AggregationResults aggregate(Aggregation aggregation, String co /** * Returns the potentially mapped results of the given {@commandResult} contained some. - * + * * @param outputType * @param commandResult * @return @@ -1690,7 +1692,7 @@ protected void maybeEmitEvent(MongoMappingEvent event) { /** * Create the specified collection using the provided options - * + * * @param collectionName * @param collectionOptions * @return the collection that was created @@ -1711,7 +1713,7 @@ public DBCollection doInDB(DB db) throws MongoException, DataAccessException { /** * Map the results of an ad-hoc query on the default MongoDB collection to an object using the template's converter. * The query document is specified as a standard {@link DBObject} and so is the fields specification. - * + * * @param collectionName name of the collection to retrieve the objects from. * @param query the query document that specifies the criteria used to find a record. * @param fields the document that specifies the fields to be returned. @@ -1736,7 +1738,7 @@ protected T doFindOne(String collectionName, DBObject query, DBObject fields /** * Map the results of an ad-hoc query on the default MongoDB collection to a List using the template's converter. The * query document is specified as a standard DBObject and so is the fields specification. - * + * * @param collectionName name of the collection to retrieve the objects from * @param query the query document that specifies the criteria used to find a record * @param fields the document that specifies the fields to be returned @@ -1752,7 +1754,7 @@ protected List doFind(String collectionName, DBObject query, DBObject fie * Map the results of an ad-hoc query on the default MongoDB collection to a List of the specified type. The object is * converted from the MongoDB native representation using an instance of {@see MongoConverter}. The query document is * specified as a standard DBObject and so is the fields specification. - * + * * @param collectionName name of the collection to retrieve the objects from. * @param query the query document that specifies the criteria used to find a record. * @param fields the document that specifies the fields to be returned. @@ -1805,7 +1807,7 @@ protected DBObject convertToDbObject(CollectionOptions collectionOptions) { * The first document that matches the query is returned and also removed from the collection in the database. *

* The query document is specified as a standard DBObject and so is the fields specification. - * + * * @param collectionName name of the collection to retrieve the objects from * @param query the query document that specifies the criteria used to find a record * @param entityClass the parameterized type of the returned list. @@ -1856,7 +1858,7 @@ protected T doFindAndModify(String collectionName, DBObject query, DBObject /** * Populates the id property of the saved object, if it's not set already. - * + * * @param savedObject * @param id */ @@ -1906,7 +1908,7 @@ private DBCollection getAndPrepareCollection(DB db, String collectionName) { *

  • Execute the given {@link ConnectionCallback} for a {@link DBObject}.
  • *
  • Apply the given {@link DbObjectCallback} to each of the {@link DBObject}s to obtain the result.
  • *
      - * + * * @param * @param collectionCallback the callback to retrieve the {@link DBObject} with * @param objectCallback the {@link DbObjectCallback} to transform {@link DBObject}s into the actual domain type @@ -1935,7 +1937,7 @@ private T executeFindOneInternal(CollectionCallback collectionCall *
    1. Iterate over the {@link DBCursor} and applies the given {@link DbObjectCallback} to each of the * {@link DBObject}s collecting the actual result {@link List}.
    2. *
        - * + * * @param * @param collectionCallback the callback to retrieve the {@link DBCursor} with * @param preparer the {@link CursorPreparer} to potentially modify the {@link DBCursor} before ireating over it @@ -2042,7 +2044,7 @@ String determineCollectionName(Class entityClass) { /** * Handles {@link WriteResult} errors based on the configured {@link WriteResultChecking}. - * + * * @param writeResult * @param query * @param operation @@ -2086,7 +2088,7 @@ protected void handleAnyWriteResultErrors(WriteResult writeResult, DBObject quer /** * Inspects the given {@link CommandResult} for erros and potentially throws an * {@link InvalidDataAccessApiUsageException} for that error. - * + * * @param result must not be {@literal null}. * @param source must not be {@literal null}. */ @@ -2124,7 +2126,7 @@ private DBObject getMappedSortObject(Query query, Class type) { /** * Tries to convert the given {@link RuntimeException} into a {@link DataAccessException} but returns the original * exception if the conversation failed. Thus allows safe re-throwing of the return value. - * + * * @param ex the exception to translate * @param exceptionTranslator the {@link PersistenceExceptionTranslator} to be used for translation * @return @@ -2162,7 +2164,7 @@ private static List consolidateIdentifiers(List ids, List { @@ -2272,7 +2274,7 @@ public DBObject doInCollection(DBCollection collection) throws MongoException, D /** * Simple internal callback to allow operations on a {@link DBObject}. - * + * * @author Oliver Gierke * @author Thomas Darimont */ @@ -2285,7 +2287,7 @@ interface DbObjectCallback { /** * Simple {@link DbObjectCallback} that will transform {@link DBObject} into the given target type using the given * {@link MongoReader}. - * + * * @author Oliver Gierke * @author Christoph Strobl */ @@ -2297,8 +2299,9 @@ private class ReadDbObjectCallback implements DbObjectCallback { public ReadDbObjectCallback(EntityReader reader, Class type, String collectionName) { - Assert.notNull(reader); - Assert.notNull(type); + Assert.notNull(reader, "EntityReader must not be null!"); + Assert.notNull(type, "Entity type must not be null!"); + this.reader = reader; this.type = type; this.collectionName = collectionName; @@ -2431,7 +2434,7 @@ public DBCursor prepare(DBCursor cursor) { /** * {@link DbObjectCallback} that assumes a {@link GeoResult} to be created, delegates actual content unmarshalling to * a delegate and creates a {@link GeoResult} from the result. - * + * * @author Oliver Gierke */ static class GeoNearResultDbObjectCallback implements DbObjectCallback> { @@ -2442,11 +2445,13 @@ static class GeoNearResultDbObjectCallback implements DbObjectCallback delegate, Metric metric) { - Assert.notNull(delegate); + + Assert.notNull(delegate, "DocumentCallback must not be null!"); + this.delegate = delegate; this.metric = metric; } @@ -2464,7 +2469,7 @@ public GeoResult doWith(DBObject object) { /** * A {@link CloseableIterator} that is backed by a MongoDB {@link Cursor}. - * + * * @since 1.7 * @author Thomas Darimont */ @@ -2476,7 +2481,7 @@ static class CloseableIterableCursorAdapter implements CloseableIterator { /** * Creates a new {@link CloseableIterableCursorAdapter} backed by the given {@link Cursor}. - * + * * @param cursor * @param exceptionTranslator * @param objectReadCallback diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AbstractAggregationExpression.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AbstractAggregationExpression.java new file mode 100644 index 0000000000..57abd72014 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AbstractAggregationExpression.java @@ -0,0 +1,153 @@ +/* + * 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 java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; + +import org.springframework.util.ObjectUtils; + +import com.mongodb.BasicDBObject; +import com.mongodb.DBObject; + +/** + * @author Christoph Strobl + * @since 1.10 + */ +abstract class AbstractAggregationExpression implements AggregationExpression { + + private final Object value; + + protected AbstractAggregationExpression(Object value) { + this.value = value; + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.AggregationExpression#toDbObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) + */ + @Override + public DBObject toDbObject(AggregationOperationContext context) { + return toDbObject(this.value, context); + } + + @SuppressWarnings("unchecked") + public DBObject toDbObject(Object value, AggregationOperationContext context) { + + Object valueToUse; + if (value instanceof List) { + + List arguments = (List) value; + List args = new ArrayList(arguments.size()); + + for (Object val : arguments) { + args.add(unpack(val, context)); + } + valueToUse = args; + } else if (value instanceof java.util.Map) { + + DBObject dbo = new BasicDBObject(); + for (java.util.Map.Entry entry : ((java.util.Map) value).entrySet()) { + dbo.put(entry.getKey(), unpack(entry.getValue(), context)); + } + valueToUse = dbo; + } else { + valueToUse = unpack(value, context); + } + + return new BasicDBObject(getMongoMethod(), valueToUse); + } + + protected static List asFields(String... fieldRefs) { + + if (ObjectUtils.isEmpty(fieldRefs)) { + return Collections.emptyList(); + } + + return Fields.fields(fieldRefs).asList(); + } + + @SuppressWarnings("unchecked") + private Object unpack(Object value, AggregationOperationContext context) { + + if (value instanceof AggregationExpression) { + return ((AggregationExpression) value).toDbObject(context); + } + + if (value instanceof Field) { + return context.getReference((Field) value).toString(); + } + + if (value instanceof List) { + + List sourceList = (List) value; + List mappedList = new ArrayList(sourceList.size()); + + for (Object item : sourceList) { + mappedList.add(unpack(item, context)); + } + return mappedList; + } + + return value; + } + + protected List append(Object value) { + + if (this.value instanceof List) { + + List clone = new ArrayList((List) this.value); + + if (value instanceof List) { + for (Object val : (List) value) { + clone.add(val); + } + } else { + clone.add(value); + } + return clone; + } + + return Arrays.asList(this.value, value); + } + + @SuppressWarnings("unchecked") + protected java.util.Map append(String key, Object value) { + + if (!(this.value instanceof java.util.Map)) { + throw new IllegalArgumentException("o_O"); + } + java.util.Map clone = new LinkedHashMap((java.util.Map) this.value); + clone.put(key, value); + return clone; + + } + + protected List values() { + + if (value instanceof List) { + return new ArrayList((List) value); + } + if (value instanceof java.util.Map) { + return new ArrayList(((java.util.Map) value).values()); + } + return new ArrayList(Collections.singletonList(value)); + } + + protected abstract String getMongoMethod(); +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AccumulatorOperators.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AccumulatorOperators.java new file mode 100644 index 0000000000..945c211b98 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AccumulatorOperators.java @@ -0,0 +1,648 @@ +/* + * 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 java.util.Collections; +import java.util.List; + +import org.springframework.util.Assert; + +import com.mongodb.DBObject; + +/** + * Gateway to {@literal accumulator} aggregation operations. + * + * @author Christoph Strobl + * @since 1.10 + * @soundtrack Rage Against The Machine - Killing In The Name + */ +public class AccumulatorOperators { + + /** + * Take the numeric value referenced by given {@literal fieldReference}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static AccumulatorOperatorFactory valueOf(String fieldReference) { + return new AccumulatorOperatorFactory(fieldReference); + } + + /** + * Take the numeric value referenced resulting from given {@link AggregationExpression}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static AccumulatorOperatorFactory valueOf(AggregationExpression expression) { + return new AccumulatorOperatorFactory(expression); + } + + /** + * @author Christoph Strobl + */ + public static class AccumulatorOperatorFactory { + + private final String fieldReference; + private final AggregationExpression expression; + + /** + * Creates new {@link AccumulatorOperatorFactory} for given {@literal fieldReference}. + * + * @param fieldReference must not be {@literal null}. + */ + public AccumulatorOperatorFactory(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + this.fieldReference = fieldReference; + this.expression = null; + } + + /** + * Creates new {@link AccumulatorOperatorFactory} for given {@link AggregationExpression}. + * + * @param expression must not be {@literal null}. + */ + public AccumulatorOperatorFactory(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + this.fieldReference = null; + this.expression = expression; + } + + /** + * Creates new {@link AggregationExpression} that takes the associated numeric value expression and calculates and + * returns the sum. + * + * @return + */ + public Sum sum() { + return usesFieldRef() ? Sum.sumOf(fieldReference) : Sum.sumOf(expression); + } + + /** + * Creates new {@link AggregationExpression} that takes the associated numeric value expression and returns the + * average value. + * + * @return + */ + public Avg avg() { + return usesFieldRef() ? Avg.avgOf(fieldReference) : Avg.avgOf(expression); + } + + /** + * Creates new {@link AggregationExpression} that takes the associated numeric value expression and returns the + * maximum value. + * + * @return + */ + public Max max() { + return usesFieldRef() ? Max.maxOf(fieldReference) : Max.maxOf(expression); + } + + /** + * Creates new {@link AggregationExpression} that takes the associated numeric value expression and returns the + * minimum value. + * + * @return + */ + public Min min() { + return usesFieldRef() ? Min.minOf(fieldReference) : Min.minOf(expression); + } + + /** + * Creates new {@link AggregationExpression} that takes the associated numeric value expression and calculates the + * population standard deviation of the input values. + * + * @return + */ + public StdDevPop stdDevPop() { + return usesFieldRef() ? StdDevPop.stdDevPopOf(fieldReference) : StdDevPop.stdDevPopOf(expression); + } + + /** + * Creates new {@link AggregationExpression} that takes the associated numeric value expression and calculates the + * sample standard deviation of the input values. + * + * @return + */ + public StdDevSamp stdDevSamp() { + return usesFieldRef() ? StdDevSamp.stdDevSampOf(fieldReference) : StdDevSamp.stdDevSampOf(expression); + } + + private boolean usesFieldRef() { + return fieldReference != null; + } + } + + /** + * {@link AggregationExpression} for {@code $sum}. + * + * @author Christoph Strobl + */ + public static class Sum extends AbstractAggregationExpression { + + private Sum(Object value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$sum"; + } + + /** + * Creates new {@link Sum}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Sum sumOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Sum(asFields(fieldReference)); + } + + /** + * Creates new {@link Sum}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Sum sumOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Sum(Collections.singletonList(expression)); + } + + /** + * Creates new {@link Sum} with all previously added arguments appending the given one.
        + * NOTE: Only possible in {@code $project} stage. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public Sum and(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Sum(append(Fields.field(fieldReference))); + } + + /** + * Creates new {@link Sum} with all previously added arguments appending the given one.
        + * NOTE: Only possible in {@code $project} stage. + * + * @param expression must not be {@literal null}. + * @return + */ + public Sum and(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Sum(append(expression)); + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.AccumulatorOperators.AbstractAggregationExpression#toDbObject(java.lang.Object, org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) + */ + @Override + @SuppressWarnings("unchecked") + public DBObject toDbObject(Object value, AggregationOperationContext context) { + + if (value instanceof List) { + if (((List) value).size() == 1) { + return super.toDbObject(((List) value).iterator().next(), context); + } + } + + return super.toDbObject(value, context); + } + } + + /** + * {@link AggregationExpression} for {@code $avg}. + * + * @author Christoph Strobl + */ + public static class Avg extends AbstractAggregationExpression { + + private Avg(Object value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$avg"; + } + + /** + * Creates new {@link Avg}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Avg avgOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Avg(asFields(fieldReference)); + } + + /** + * Creates new {@link Avg}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Avg avgOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Avg(Collections.singletonList(expression)); + } + + /** + * Creates new {@link Avg} with all previously added arguments appending the given one.
        + * NOTE: Only possible in {@code $project} stage. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public Avg and(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Avg(append(Fields.field(fieldReference))); + } + + /** + * Creates new {@link Avg} with all previously added arguments appending the given one.
        + * NOTE: Only possible in {@code $project} stage. + * + * @param expression must not be {@literal null}. + * @return + */ + public Avg and(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Avg(append(expression)); + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.AccumulatorOperators.AbstractAggregationExpression#toDbObject(java.lang.Object, org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) + */ + @Override + @SuppressWarnings("unchecked") + public DBObject toDbObject(Object value, AggregationOperationContext context) { + + if (value instanceof List) { + if (((List) value).size() == 1) { + return super.toDbObject(((List) value).iterator().next(), context); + } + } + + return super.toDbObject(value, context); + } + } + + /** + * {@link AggregationExpression} for {@code $max}. + * + * @author Christoph Strobl + */ + public static class Max extends AbstractAggregationExpression { + + private Max(Object value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$max"; + } + + /** + * Creates new {@link Max}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Max maxOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Max(asFields(fieldReference)); + } + + /** + * Creates new {@link Max}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Max maxOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Max(Collections.singletonList(expression)); + } + + /** + * Creates new {@link Max} with all previously added arguments appending the given one.
        + * NOTE: Only possible in {@code $project} stage. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public Max and(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Max(append(Fields.field(fieldReference))); + } + + /** + * Creates new {@link Max} with all previously added arguments appending the given one.
        + * NOTE: Only possible in {@code $project} stage. + * + * @param expression must not be {@literal null}. + * @return + */ + public Max and(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Max(append(expression)); + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.AccumulatorOperators.AbstractAggregationExpression#toDbObject(java.lang.Object, org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) + */ + @Override + @SuppressWarnings("unchecked") + public DBObject toDbObject(Object value, AggregationOperationContext context) { + + if (value instanceof List) { + if (((List) value).size() == 1) { + return super.toDbObject(((List) value).iterator().next(), context); + } + } + + return super.toDbObject(value, context); + } + } + + /** + * {@link AggregationExpression} for {@code $min}. + * + * @author Christoph Strobl + */ + public static class Min extends AbstractAggregationExpression { + + private Min(Object value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$min"; + } + + /** + * Creates new {@link Min}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Min minOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Min(asFields(fieldReference)); + } + + /** + * Creates new {@link Min}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Min minOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Min(Collections.singletonList(expression)); + } + + /** + * Creates new {@link Min} with all previously added arguments appending the given one.
        + * NOTE: Only possible in {@code $project} stage. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public Min and(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Min(append(Fields.field(fieldReference))); + } + + /** + * Creates new {@link Min} with all previously added arguments appending the given one.
        + * NOTE: Only possible in {@code $project} stage. + * + * @param expression must not be {@literal null}. + * @return + */ + public Min and(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Min(append(expression)); + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.AccumulatorOperators.AbstractAggregationExpression#toDbObject(java.lang.Object, org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) + */ + @Override + @SuppressWarnings("unchecked") + public DBObject toDbObject(Object value, AggregationOperationContext context) { + + if (value instanceof List) { + if (((List) value).size() == 1) { + return super.toDbObject(((List) value).iterator().next(), context); + } + } + + return super.toDbObject(value, context); + } + } + + /** + * {@link AggregationExpression} for {@code $stdDevPop}. + * + * @author Christoph Strobl + */ + public static class StdDevPop extends AbstractAggregationExpression { + + private StdDevPop(Object value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$stdDevPop"; + } + + /** + * Creates new {@link StdDevPop}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static StdDevPop stdDevPopOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new StdDevPop(asFields(fieldReference)); + } + + /** + * Creates new {@link StdDevPop} with all previously added arguments appending the given one. + * + * @param expression must not be {@literal null}. + * @return + */ + public static StdDevPop stdDevPopOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new StdDevPop(Collections.singletonList(expression)); + } + + /** + * Creates new {@link StdDevPop} with all previously added arguments appending the given one.
        + * NOTE: Only possible in {@code $project} stage. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public StdDevPop and(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new StdDevPop(append(Fields.field(fieldReference))); + } + + /** + * Creates new {@link StdDevSamp} with all previously added arguments appending the given one.
        + * NOTE: Only possible in {@code $project} stage. + * + * @param expression must not be {@literal null}. + * @return + */ + public StdDevPop and(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new StdDevPop(append(expression)); + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.AccumulatorOperators.AbstractAggregationExpression#toDbObject(java.lang.Object, org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) + */ + @Override + @SuppressWarnings("unchecked") + public DBObject toDbObject(Object value, AggregationOperationContext context) { + + if (value instanceof List) { + if (((List) value).size() == 1) { + return super.toDbObject(((List) value).iterator().next(), context); + } + } + + return super.toDbObject(value, context); + } + } + + /** + * {@link AggregationExpression} for {@code $stdDevSamp}. + * + * @author Christoph Strobl + */ + public static class StdDevSamp extends AbstractAggregationExpression { + + private StdDevSamp(Object value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$stdDevSamp"; + } + + /** + * Creates new {@link StdDevSamp}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static StdDevSamp stdDevSampOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new StdDevSamp(asFields(fieldReference)); + } + + /** + * Creates new {@link StdDevSamp}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static StdDevSamp stdDevSampOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new StdDevSamp(Collections.singletonList(expression)); + } + + /** + * Creates new {@link StdDevSamp} with all previously added arguments appending the given one.
        + * NOTE: Only possible in {@code $project} stage. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public StdDevSamp and(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new StdDevSamp(append(Fields.field(fieldReference))); + } + + /** + * Creates new {@link StdDevSamp} with all previously added arguments appending the given one.
        + * NOTE: Only possible in {@code $project} stage. + * + * @param expression must not be {@literal null}. + * @return + */ + public StdDevSamp and(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new StdDevSamp(append(expression)); + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.AccumulatorOperators.AbstractAggregationExpression#toDbObject(java.lang.Object, org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) + */ + @Override + @SuppressWarnings("unchecked") + public DBObject toDbObject(Object value, AggregationOperationContext context) { + + if (value instanceof List) { + if (((List) value).size() == 1) { + return super.toDbObject(((List) value).iterator().next(), context); + } + } + + return super.toDbObject(value, context); + } + } +} 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 2b0a45b469..4798d1e1fc 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 @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,11 +23,18 @@ import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort.Direction; +import org.springframework.data.mongodb.core.aggregation.CountOperation.CountOperationBuilder; +import org.springframework.data.mongodb.core.aggregation.ExposedFields.DirectFieldReference; 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.aggregation.FacetOperation.FacetOperationBuilder; import org.springframework.data.mongodb.core.aggregation.FieldsExposingAggregationOperation.InheritsFieldsAggregationOperation; +import org.springframework.data.mongodb.core.aggregation.GraphLookupOperation.StartWithBuilder; +import org.springframework.data.mongodb.core.aggregation.ReplaceRootOperation.ReplaceRootDocumentOperationBuilder; +import org.springframework.data.mongodb.core.aggregation.ReplaceRootOperation.ReplaceRootOperationBuilder; +import org.springframework.data.mongodb.core.aggregation.Fields.*; import org.springframework.data.mongodb.core.query.Criteria; +import org.springframework.data.mongodb.core.query.CriteriaDefinition; import org.springframework.data.mongodb.core.query.NearQuery; import org.springframework.data.mongodb.core.query.SerializationUtils; import org.springframework.util.Assert; @@ -62,7 +69,7 @@ public class Aggregation { */ public static final String CURRENT = SystemVariable.CURRENT.toString(); - public static final AggregationOperationContext DEFAULT_CONTEXT = new NoOpAggregationOperationContext(); + public static final AggregationOperationContext DEFAULT_CONTEXT = AggregationOperationRenderer.DEFAULT_CONTEXT; public static final AggregationOptions DEFAULT_OPTIONS = newAggregationOptions().build(); protected final List operations; @@ -196,7 +203,7 @@ public static ProjectionOperation project(String... fields) { } /** - * Creates a new {@link ProjectionOperation} includeing the given {@link Fields}. + * Creates a new {@link ProjectionOperation} including the given {@link Fields}. * * @param fields must not be {@literal null}. * @return @@ -215,6 +222,40 @@ public static UnwindOperation unwind(String field) { return new UnwindOperation(field(field)); } + /** + * Factory method to create a new {@link ReplaceRootOperation} for the field with the given name. + * + * @param fieldName must not be {@literal null} or empty. + * @return + * @since 1.10 + */ + public static ReplaceRootOperation replaceRoot(String fieldName) { + return ReplaceRootOperation.builder().withValueOf(fieldName); + } + + /** + * Factory method to create a new {@link ReplaceRootOperation} for the field with the given + * {@link AggregationExpression}. + * + * @param aggregationExpression must not be {@literal null}. + * @return + * @since 1.10 + */ + public static ReplaceRootOperation replaceRoot(AggregationExpression aggregationExpression) { + return ReplaceRootOperation.builder().withValueOf(aggregationExpression); + } + + /** + * Factory method to create a new {@link ReplaceRootDocumentOperationBuilder} to configure a + * {@link ReplaceRootOperation}. + * + * @return the {@literal ReplaceRootDocumentOperationBuilder}. + * @since 1.10 + */ + public static ReplaceRootOperationBuilder replaceRoot() { + return ReplaceRootOperation.builder(); + } + /** * Factory method to create a new {@link UnwindOperation} for the field with the given name and * {@code preserveNullAndEmptyArrays}. Note that extended unwind is supported in MongoDB version 3.2+. @@ -279,6 +320,18 @@ public static GroupOperation group(Fields fields) { return new GroupOperation(fields); } + /** + * Creates a new {@link GraphLookupOperation.GraphLookupOperationFromBuilder} to construct a + * {@link GraphLookupOperation} given {@literal fromCollection}. + * + * @param fromCollection must not be {@literal null} or empty. + * @return + * @since 1.10 + */ + public static StartWithBuilder graphLookup(String fromCollection) { + return GraphLookupOperation.builder().from(fromCollection); + } + /** * Factory method to create a new {@link SortOperation} for the given {@link Sort}. * @@ -341,6 +394,17 @@ public static MatchOperation match(Criteria criteria) { return new MatchOperation(criteria); } + /** + * Creates a new {@link MatchOperation} using the given {@link CriteriaDefinition}. + * + * @param criteria must not be {@literal null}. + * @return + * @since 1.10 + */ + public static MatchOperation match(CriteriaDefinition criteria) { + return new MatchOperation(criteria); + } + /** * Creates a new {@link OutOperation} using the given collection name. This operation must be the last operation in * the pipeline. @@ -356,93 +420,108 @@ public static OutOperation out(String outCollectionName) { } /** - * Creates a new {@link LookupOperation}. + * Creates a new {@link BucketOperation} given {@literal groupByField}. * - * @param from must not be {@literal null}. - * @param localField must not be {@literal null}. - * @param foreignField must not be {@literal null}. - * @param as must not be {@literal null}. - * @return never {@literal null}. - * @since 1.9 + * @param groupByField must not be {@literal null} or empty. + * @return + * @since 1.10 */ - public static LookupOperation lookup(String from, String localField, String foreignField, String as) { - return lookup(field(from), field(localField), field(foreignField), field(as)); + public static BucketOperation bucket(String groupByField) { + return new BucketOperation(field(groupByField)); } /** - * Creates a new {@link LookupOperation} for the given {@link Fields}. + * Creates a new {@link BucketOperation} given {@link AggregationExpression group-by expression}. * - * @param from must not be {@literal null}. - * @param localField must not be {@literal null}. - * @param foreignField must not be {@literal null}. - * @param as must not be {@literal null}. - * @return never {@literal null}. - * @since 1.9 + * @param groupByExpression must not be {@literal null}. + * @return + * @since 1.10 */ - public static LookupOperation lookup(Field from, Field localField, Field foreignField, Field as) { - return new LookupOperation(from, localField, foreignField, as); + public static BucketOperation bucket(AggregationExpression groupByExpression) { + return new BucketOperation(groupByExpression); } /** - * Creates a new {@link IfNullOperator} for the given {@code field} and {@code replacement} value. + * Creates a new {@link BucketAutoOperation} given {@literal groupByField}. * - * @param field must not be {@literal null}. - * @param replacement must not be {@literal null}. - * @return never {@literal null}. + * @param groupByField must not be {@literal null} or empty. + * @param buckets number of buckets, must be a positive integer. + * @return * @since 1.10 */ - public static IfNullOperator ifNull(String field, Object replacement) { - return IfNullOperator.newBuilder().ifNull(field).thenReplaceWith(replacement); + public static BucketAutoOperation bucketAuto(String groupByField, int buckets) { + return new BucketAutoOperation(field(groupByField), buckets); } /** - * Creates a new {@link IfNullOperator} for the given {@link Field} and {@link Field} to obtain a value from. + * Creates a new {@link BucketAutoOperation} given {@link AggregationExpression group-by expression}. * - * @param field must not be {@literal null}. - * @param replacement must not be {@literal null}. - * @return never {@literal null}. + * @param groupByExpression must not be {@literal null}. + * @param buckets number of buckets, must be a positive integer. + * @return * @since 1.10 */ - public static IfNullOperator ifNull(Field field, Field replacement) { - return IfNullOperator.newBuilder().ifNull(field).thenReplaceWith(replacement); + public static BucketAutoOperation bucketAuto(AggregationExpression groupByExpression, int buckets) { + return new BucketAutoOperation(groupByExpression, buckets); } /** - * Creates a new {@link IfNullOperator} for the given {@link Field} and {@code replacement} value. + * Creates a new {@link FacetOperation}. * - * @param field must not be {@literal null}. - * @param replacement must not be {@literal null}. - * @return never {@literal null}. + * @return * @since 1.10 */ - public static IfNullOperator ifNull(Field field, Object replacement) { - return IfNullOperator.newBuilder().ifNull(field).thenReplaceWith(replacement); + public static FacetOperation facet() { + return FacetOperation.EMPTY; } /** - * Creates a new {@link ConditionalOperator} for the given {@link Field} that holds a {@literal boolean} value. + * Creates a new {@link FacetOperationBuilder} given {@link Aggregation}. * - * @param booleanField must not be {@literal null}. - * @param then must not be {@literal null}. - * @param otherwise must not be {@literal null}. - * @return never {@literal null}. + * @param aggregationOperations the sub-pipeline, must not be {@literal null}. + * @return * @since 1.10 */ - public static ConditionalOperator conditional(Field booleanField, Object then, Object otherwise) { - return ConditionalOperator.newBuilder().when(booleanField).then(then).otherwise(otherwise); + public static FacetOperationBuilder facet(AggregationOperation... aggregationOperations) { + return facet().and(aggregationOperations); } /** - * Creates a new {@link ConditionalOperator} for the given {@link Criteria}. + * Creates a new {@link LookupOperation}. + * + * @param from must not be {@literal null}. + * @param localField must not be {@literal null}. + * @param foreignField must not be {@literal null}. + * @param as must not be {@literal null}. + * @return never {@literal null}. + * @since 1.9 + */ + public static LookupOperation lookup(String from, String localField, String foreignField, String as) { + return lookup(field(from), field(localField), field(foreignField), field(as)); + } + + /** + * Creates a new {@link LookupOperation} for the given {@link Fields}. + * + * @param from must not be {@literal null}. + * @param localField must not be {@literal null}. + * @param foreignField must not be {@literal null}. + * @param as must not be {@literal null}. + * @return never {@literal null}. + * @since 1.9 + */ + public static LookupOperation lookup(Field from, Field localField, Field foreignField, Field as) { + return new LookupOperation(from, localField, foreignField, as); + } + + /** + * Creates a new {@link CountOperationBuilder}. * - * @param criteria must not be {@literal null}. - * @param then must not be {@literal null}. - * @param otherwise must not be {@literal null}. * @return never {@literal null}. * @since 1.10 */ - public static ConditionalOperator conditional(Criteria criteria, Object then, Object otherwise) { - return ConditionalOperator.newBuilder().when(criteria).then(then).otherwise(otherwise); + public static CountOperationBuilder count() { + return new CountOperationBuilder(); } /** @@ -498,24 +577,7 @@ public static AggregationOptions.Builder newAggregationOptions() { */ public DBObject toDbObject(String inputCollectionName, AggregationOperationContext rootContext) { - AggregationOperationContext context = rootContext; - List operationDocuments = new ArrayList(operations.size()); - - for (AggregationOperation operation : operations) { - - operationDocuments.add(operation.toDBObject(context)); - - if (operation instanceof FieldsExposingAggregationOperation) { - - FieldsExposingAggregationOperation exposedFieldsOperation = (FieldsExposingAggregationOperation) operation; - - if (operation instanceof InheritsFieldsAggregationOperation) { - context = new InheritingExposedFieldsAggregationOperationContext(exposedFieldsOperation.getFields(), context); - } else { - context = new ExposedFieldsAggregationOperationContext(exposedFieldsOperation.getFields(), context); - } - } - } + List operationDocuments = AggregationOperationRenderer.toDBObject(operations, rootContext); DBObject command = new BasicDBObject("aggregate", inputCollectionName); command.put("pipeline", operationDocuments); @@ -531,50 +593,14 @@ public DBObject toDbObject(String inputCollectionName, AggregationOperationConte */ @Override public String toString() { - return SerializationUtils - .serializeToJsonSafely(toDbObject("__collection__", new NoOpAggregationOperationContext())); - } - - /** - * Simple {@link AggregationOperationContext} that just returns {@link FieldReference}s as is. - * - * @author Oliver Gierke - */ - private static class NoOpAggregationOperationContext 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 new FieldReference(new ExposedField(field, true)); - } - - /* - * (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)); - } + return SerializationUtils.serializeToJsonSafely(toDbObject("__collection__", DEFAULT_CONTEXT)); } /** * Describes the system variables available in MongoDB aggregation framework pipeline expressions. * * @author Thomas Darimont - * @see http://docs.mongodb.org/manual/reference/aggregation-variables + * @see Aggregation Variables */ enum SystemVariable { @@ -607,7 +633,7 @@ public static boolean isReferingToSystemVariable(String fieldRef) { return false; } - /* + /* * (non-Javadoc) * @see java.lang.Enum#toString() */ diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationFunctionExpressions.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationFunctionExpressions.java index 0b88c039ce..ff2769a491 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationFunctionExpressions.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationFunctionExpressions.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 the original author or authors. + * Copyright 2015-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. @@ -29,11 +29,14 @@ * * @author Thomas Darimont * @author Oliver Gierke - * @since 1.10 + * @author Christoph Strobl + * @since 1.7 + * @deprecated since 1.10. Please use {@link ArithmeticOperators} and {@link ComparisonOperators} instead. */ +@Deprecated public enum AggregationFunctionExpressions { - SIZE; + SIZE, CMP, EQ, GT, GTE, LT, LTE, NE, SUBTRACT, ADD, MULTIPLY; /** * Returns an {@link AggregationExpression} build from the current {@link Enum} name and the given parameters. @@ -52,7 +55,7 @@ public AggregationExpression of(Object... parameters) { * * @author Thomas Darimont * @author Oliver Gierke - * @since 1.10 + * @since 1.7 */ static class FunctionExpression implements AggregationExpression { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationOperationRenderer.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationOperationRenderer.java new file mode 100644 index 0000000000..0cb930df1d --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationOperationRenderer.java @@ -0,0 +1,109 @@ +/* + * 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 java.util.ArrayList; +import java.util.List; + +import org.springframework.data.mongodb.core.aggregation.ExposedFields.DirectFieldReference; +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.aggregation.FieldsExposingAggregationOperation.InheritsFieldsAggregationOperation; + +import com.mongodb.DBObject; + +/** + * Rendering support for {@link AggregationOperation} into a {@link List} of {@link com.mongodb.DBObject}. + * + * @author Mark Paluch + * @author Christoph Strobl + * @since 1.10 + */ +class AggregationOperationRenderer { + + static final AggregationOperationContext DEFAULT_CONTEXT = new NoOpAggregationOperationContext(); + + /** + * Render a {@link List} of {@link AggregationOperation} given {@link AggregationOperationContext} into their + * {@link DBObject} representation. + * + * @param operations must not be {@literal null}. + * @param context must not be {@literal null}. + * @return the {@link List} of {@link DBObject}. + */ + static List toDBObject(List operations, AggregationOperationContext rootContext) { + + List operationDocuments = new ArrayList(operations.size()); + + AggregationOperationContext contextToUse = rootContext; + + for (AggregationOperation operation : operations) { + + operationDocuments.add(operation.toDBObject(contextToUse)); + + if (operation instanceof FieldsExposingAggregationOperation) { + + FieldsExposingAggregationOperation exposedFieldsOperation = (FieldsExposingAggregationOperation) operation; + ExposedFields fields = exposedFieldsOperation.getFields(); + + if (operation instanceof InheritsFieldsAggregationOperation) { + contextToUse = new InheritingExposedFieldsAggregationOperationContext(fields, contextToUse); + } else { + contextToUse = fields.exposesNoFields() ? DEFAULT_CONTEXT + : new ExposedFieldsAggregationOperationContext(exposedFieldsOperation.getFields(), contextToUse); + } + } + } + + return operationDocuments; + } + + /** + * Simple {@link AggregationOperationContext} that just returns {@link FieldReference}s as is. + * + * @author Oliver Gierke + */ + private static class NoOpAggregationOperationContext 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 new DirectFieldReference(new ExposedField(field, true)); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.AggregationOperationContext#getReference(java.lang.String) + */ + @Override + public FieldReference getReference(String name) { + return new DirectFieldReference(new ExposedField(new AggregationField(name), true)); + } + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationOptions.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationOptions.java index c7c17b8355..05e0791f98 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationOptions.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationOptions.java @@ -21,7 +21,7 @@ /** * Holds a set of configurable aggregation options that can be used within an aggregation pipeline. A list of support * aggregation options can be found in the MongoDB reference documentation - * http://docs.mongodb.org/manual/reference/command/aggregate/#aggregate + * https://docs.mongodb.org/manual/reference/command/aggregate/#aggregate * * @author Thomas Darimont * @author Oliver Gierke diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationResults.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationResults.java index 2fbf96c4cb..8223358873 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationResults.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationResults.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2014 the original author or authors. + * Copyright 2013-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,6 +29,7 @@ * @author Tobias Trelle * @author Oliver Gierke * @author Thomas Darimont + * @author Mark Paluch * @param The class in which the results are mapped onto. * @since 1.3 */ @@ -46,8 +47,8 @@ public class AggregationResults implements Iterable { */ public AggregationResults(List mappedResults, DBObject rawResults) { - Assert.notNull(mappedResults); - Assert.notNull(rawResults); + Assert.notNull(mappedResults, "List of mapped results must not be null!"); + Assert.notNull(rawResults, "Raw results must not be null!"); this.mappedResults = Collections.unmodifiableList(mappedResults); this.rawResults = rawResults; diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationSpELExpression.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationSpELExpression.java new file mode 100644 index 0000000000..2773ee16f5 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationSpELExpression.java @@ -0,0 +1,74 @@ +/* + * 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.springframework.util.Assert; + +import com.mongodb.DBObject; + +/** + * An {@link AggregationExpression} that renders a MongoDB Aggregation Framework expression from the AST of a + * SpEL + * expression.
        + *
        + * Samples:
        + * + *
        + * // { $and: [ { $gt: [ "$qty", 100 ] }, { $lt: [ "$qty", 250 ] } ] }
        + * expressionOf("qty > 100 && qty < 250);
        + *
        + * // { $cond : { if : { $gte : [ "$a", 42 ]}, then : "answer", else : "no-answer" } }
        + * expressionOf("cond(a >= 42, 'answer', 'no-answer')");
        + * 
        + *
        + * + * @author Christoph Strobl + * @see SpelExpressionTransformer + * @since 1.10 + */ +public class AggregationSpELExpression implements AggregationExpression { + + private static final SpelExpressionTransformer TRANSFORMER = new SpelExpressionTransformer(); + private final String rawExpression; + private final Object[] parameters; + + private AggregationSpELExpression(String rawExpression, Object[] parameters) { + + this.rawExpression = rawExpression; + this.parameters = parameters; + } + + /** + * Creates new {@link AggregationSpELExpression} for the given {@literal expressionString} and {@literal parameters}. + * + * @param expressionString must not be {@literal null}. + * @param parameters can be empty. + * @return + */ + public static AggregationSpELExpression expressionOf(String expressionString, Object... parameters) { + + Assert.notNull(expressionString, "ExpressionString must not be null!"); + return new AggregationSpELExpression(expressionString, parameters); + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.AggregationExpression#toDbObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) + */ + @Override + public DBObject toDbObject(AggregationOperationContext context) { + return (DBObject) TRANSFORMER.transform(rawExpression, context, parameters); + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ArithmeticOperators.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ArithmeticOperators.java new file mode 100644 index 0000000000..43f0c3d1a2 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ArithmeticOperators.java @@ -0,0 +1,1421 @@ +/* + * 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 java.util.Collections; +import java.util.List; + +import org.springframework.data.mongodb.core.aggregation.AccumulatorOperators.Avg; +import org.springframework.data.mongodb.core.aggregation.AccumulatorOperators.Max; +import org.springframework.data.mongodb.core.aggregation.AccumulatorOperators.Min; +import org.springframework.data.mongodb.core.aggregation.AccumulatorOperators.StdDevPop; +import org.springframework.data.mongodb.core.aggregation.AccumulatorOperators.StdDevSamp; +import org.springframework.data.mongodb.core.aggregation.AccumulatorOperators.Sum; +import org.springframework.util.Assert; + +/** + * Gateway to {@literal Arithmetic} aggregation operations that perform math operations on numbers. + * + * @author Christoph Strobl + * @since 1.10 + */ +public class ArithmeticOperators { + + /** + * Take the field referenced by given {@literal fieldReference}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static ArithmeticOperatorFactory valueOf(String fieldReference) { + return new ArithmeticOperatorFactory(fieldReference); + } + + /** + * Take the value resulting from the given {@link AggregationExpression}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static ArithmeticOperatorFactory valueOf(AggregationExpression expression) { + return new ArithmeticOperatorFactory(expression); + } + + /** + * @author Christoph Strobl + */ + public static class ArithmeticOperatorFactory { + + private final String fieldReference; + private final AggregationExpression expression; + + /** + * Creates new {@link ArithmeticOperatorFactory} for given {@literal fieldReference}. + * + * @param fieldReference must not be {@literal null}. + */ + public ArithmeticOperatorFactory(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + this.fieldReference = fieldReference; + this.expression = null; + } + + /** + * Creates new {@link ArithmeticOperatorFactory} for given {@link AggregationExpression}. + * + * @param expression must not be {@literal null}. + */ + public ArithmeticOperatorFactory(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + this.fieldReference = null; + this.expression = expression; + } + + /** + * Creates new {@link AggregationExpression} that returns the absolute value of the associated number. + * + * @return + */ + public Abs abs() { + return fieldReference != null ? Abs.absoluteValueOf(fieldReference) : Abs.absoluteValueOf(expression); + } + + /** + * Creates new {@link AggregationExpression} that adds the value of {@literal fieldReference} to the associated + * number. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public Add add(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return createAdd().add(fieldReference); + } + + /** + * Creates new {@link AggregationExpression} that adds the resulting value of the given + * {@link AggregationExpression} to the associated number. + * + * @param expression must not be {@literal null}. + * @return + */ + public Add add(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return createAdd().add(expression); + } + + /** + * Creates new {@link AggregationExpression} that adds the given {@literal value} to the associated number. + * + * @param value must not be {@literal null}. + * @return + */ + public Add add(Number value) { + + Assert.notNull(value, "Value must not be null!"); + return createAdd().add(value); + } + + private Add createAdd() { + return fieldReference != null ? Add.valueOf(fieldReference) : Add.valueOf(expression); + } + + /** + * Creates new {@link AggregationExpression} that returns the smallest integer greater than or equal to the + * assoicated number. + * + * @return + */ + public Ceil ceil() { + return fieldReference != null ? Ceil.ceilValueOf(fieldReference) : Ceil.ceilValueOf(expression); + } + + /** + * Creates new {@link AggregationExpression} that ivides the associated number by number referenced via + * {@literal fieldReference}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public Divide divideBy(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return createDivide().divideBy(fieldReference); + } + + /** + * Creates new {@link AggregationExpression} that divides the associated number by number extracted via + * {@literal expression}. + * + * @param expression must not be {@literal null}. + * @return + */ + public Divide divideBy(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return createDivide().divideBy(expression); + } + + /** + * Creates new {@link AggregationExpression} that divides the associated number by given {@literal value}. + * + * @param value + * @return + */ + public Divide divideBy(Number value) { + + Assert.notNull(value, "Value must not be null!"); + return createDivide().divideBy(value); + } + + private Divide createDivide() { + return fieldReference != null ? Divide.valueOf(fieldReference) : Divide.valueOf(expression); + } + + /** + * Creates new {@link AggregationExpression} that raises Euler’s number (i.e. e ) on the associated number. + * + * @return + */ + public Exp exp() { + return fieldReference != null ? Exp.expValueOf(fieldReference) : Exp.expValueOf(expression); + } + + /** + * Creates new {@link AggregationExpression} that returns the largest integer less than or equal to the associated + * number. + * + * @return + */ + public Floor floor() { + return fieldReference != null ? Floor.floorValueOf(fieldReference) : Floor.floorValueOf(expression); + } + + /** + * Creates new {@link AggregationExpression} that calculates the natural logarithm ln (i.e loge) of the assoicated + * number. + * + * @return + */ + public Ln ln() { + return fieldReference != null ? Ln.lnValueOf(fieldReference) : Ln.lnValueOf(expression); + } + + /** + * Creates new {@link AggregationExpression} that calculates the log of the associated number in the specified base + * referenced via {@literal fieldReference}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public Log log(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return createLog().log(fieldReference); + } + + /** + * Creates new {@link AggregationExpression} that calculates the log of the associated number in the specified base + * extracted by given {@link AggregationExpression}. + * + * @param expression must not be {@literal null}. + * @return + */ + public Log log(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return createLog().log(fieldReference); + } + + /** + * Creates new {@link AggregationExpression} that calculates the log of a the associated number in the specified + * {@literal base}. + * + * @param base must not be {@literal null}. + * @return + */ + public Log log(Number base) { + + Assert.notNull(base, "Base must not be null!"); + return createLog().log(base); + } + + private Log createLog() { + return fieldReference != null ? Log.valueOf(fieldReference) : Log.valueOf(expression); + } + + /** + * Creates new {@link AggregationExpression} that calculates the log base 10 for the associated number. + * + * @return + */ + public Log10 log10() { + return fieldReference != null ? Log10.log10ValueOf(fieldReference) : Log10.log10ValueOf(expression); + } + + /** + * Creates new {@link AggregationExpression} that divides the associated number by another and returns the + * remainder. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public Mod mod(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return createMod().mod(fieldReference); + } + + /** + * Creates new {@link AggregationExpression} that divides the associated number by another and returns the + * remainder. + * + * @param expression must not be {@literal null}. + * @return + */ + public Mod mod(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return createMod().mod(expression); + } + + /** + * Creates new {@link AggregationExpression} that divides the associated number by another and returns the + * remainder. + * + * @param value must not be {@literal null}. + * @return + */ + public Mod mod(Number value) { + + Assert.notNull(value, "Base must not be null!"); + return createMod().mod(value); + } + + private Mod createMod() { + return fieldReference != null ? Mod.valueOf(fieldReference) : Mod.valueOf(expression); + } + + /** + * Creates new {@link AggregationExpression} that multiplies the associated number with another. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public Multiply multiplyBy(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return createMultiply().multiplyBy(fieldReference); + } + + /** + * Creates new {@link AggregationExpression} that multiplies the associated number with another. + * + * @param expression must not be {@literal null}. + * @return + */ + public Multiply multiplyBy(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return createMultiply().multiplyBy(expression); + } + + /** + * Creates new {@link AggregationExpression} that multiplies the associated number with another. + * + * @param value must not be {@literal null}. + * @return + */ + public Multiply multiplyBy(Number value) { + + Assert.notNull(value, "Value must not be null!"); + return createMultiply().multiplyBy(value); + } + + private Multiply createMultiply() { + return fieldReference != null ? Multiply.valueOf(fieldReference) : Multiply.valueOf(expression); + } + + /** + * Creates new {@link AggregationExpression} that raises the associated number to the specified exponent. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public Pow pow(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return createPow().pow(fieldReference); + } + + /** + * Creates new {@link AggregationExpression} that raises the associated number to the specified exponent. + * + * @param expression must not be {@literal null}. + * @return + */ + public Pow pow(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return createPow().pow(expression); + } + + /** + * Creates new {@link AggregationExpression} that raises the associated number to the specified exponent. + * + * @param value must not be {@literal null}. + * @return + */ + public Pow pow(Number value) { + + Assert.notNull(value, "Value must not be null!"); + return createPow().pow(value); + } + + private Pow createPow() { + return fieldReference != null ? Pow.valueOf(fieldReference) : Pow.valueOf(expression); + } + + /** + * Creates new {@link AggregationExpression} that calculates the square root of the associated number. + * + * @return + */ + public Sqrt sqrt() { + return fieldReference != null ? Sqrt.sqrtOf(fieldReference) : Sqrt.sqrtOf(expression); + } + + /** + * Creates new {@link AggregationExpression} that subtracts value of given from the associated number. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public Subtract subtract(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return createSubtract().subtract(fieldReference); + } + + /** + * Creates new {@link AggregationExpression} that subtracts value of given from the associated number. + * + * @param expression must not be {@literal null}. + * @return + */ + public Subtract subtract(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return createSubtract().subtract(expression); + } + + /** + * Creates new {@link AggregationExpression} that subtracts value from the associated number. + * + * @param value + * @return + */ + public Subtract subtract(Number value) { + + Assert.notNull(value, "Value must not be null!"); + return createSubtract().subtract(value); + } + + private Subtract createSubtract() { + return fieldReference != null ? Subtract.valueOf(fieldReference) : Subtract.valueOf(expression); + } + + /** + * Creates new {@link AggregationExpression} that truncates a number to its integer. + * + * @return + */ + public Trunc trunc() { + return fieldReference != null ? Trunc.truncValueOf(fieldReference) : Trunc.truncValueOf(expression); + } + + /** + * Creates new {@link AggregationExpression} that calculates and returns the sum of numeric values. + * + * @return + */ + public Sum sum() { + return fieldReference != null ? AccumulatorOperators.Sum.sumOf(fieldReference) + : AccumulatorOperators.Sum.sumOf(expression); + } + + /** + * Creates new {@link AggregationExpression} that returns the average value of the numeric values. + * + * @return + */ + public Avg avg() { + return fieldReference != null ? AccumulatorOperators.Avg.avgOf(fieldReference) + : AccumulatorOperators.Avg.avgOf(expression); + } + + /** + * Creates new {@link AggregationExpression} that returns the maximum value. + * + * @return + */ + public Max max() { + return fieldReference != null ? AccumulatorOperators.Max.maxOf(fieldReference) + : AccumulatorOperators.Max.maxOf(expression); + } + + /** + * Creates new {@link AggregationExpression} that returns the minimum value. + * + * @return + */ + public Min min() { + return fieldReference != null ? AccumulatorOperators.Min.minOf(fieldReference) + : AccumulatorOperators.Min.minOf(expression); + } + + /** + * Creates new {@link AggregationExpression} that calculates the population standard deviation of the input values. + * + * @return + */ + public StdDevPop stdDevPop() { + return fieldReference != null ? AccumulatorOperators.StdDevPop.stdDevPopOf(fieldReference) + : AccumulatorOperators.StdDevPop.stdDevPopOf(expression); + } + + /** + * Creates new {@link AggregationExpression} that calculates the sample standard deviation of the input values. + * + * @return + */ + public StdDevSamp stdDevSamp() { + return fieldReference != null ? AccumulatorOperators.StdDevSamp.stdDevSampOf(fieldReference) + : AccumulatorOperators.StdDevSamp.stdDevSampOf(expression); + } + } + + /** + * {@link AggregationExpression} for {@code $abs}. + * + * @author Christoph Strobl + */ + public static class Abs extends AbstractAggregationExpression { + + private Abs(Object value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$abs"; + } + + /** + * Creates new {@link Abs}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Abs absoluteValueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Abs(Fields.field(fieldReference)); + } + + /** + * Creates new {@link Abs}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Abs absoluteValueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Abs(expression); + } + + /** + * Creates new {@link Abs}. + * + * @param value must not be {@literal null}. + * @return + */ + public static Abs absoluteValueOf(Number value) { + + Assert.notNull(value, "Value must not be null!"); + return new Abs(value); + } + } + + /** + * {@link AggregationExpression} for {@code $add}. + * + * @author Christoph Strobl + */ + public static class Add extends AbstractAggregationExpression { + + protected Add(List value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$add"; + } + + /** + * Creates new {@link Add}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Add valueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Add(asFields(fieldReference)); + } + + /** + * Creates new {@link Add}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Add valueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Add(Collections.singletonList(expression)); + } + + /** + * Creates new {@link Add}. + * + * @param value must not be {@literal null}. + * @return + */ + public static Add valueOf(Number value) { + + Assert.notNull(value, "Value must not be null!"); + return new Add(Collections.singletonList(value)); + } + + public Add add(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Add(append(Fields.field(fieldReference))); + } + + public Add add(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Add(append(expression)); + } + + public Add add(Number value) { + return new Add(append(value)); + } + } + + /** + * {@link AggregationExpression} for {@code $ceil}. + * + * @author Christoph Strobl + */ + public static class Ceil extends AbstractAggregationExpression { + + private Ceil(Object value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$ceil"; + } + + /** + * Creates new {@link Ceil}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Ceil ceilValueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Ceil(Fields.field(fieldReference)); + } + + /** + * Creates new {@link Ceil}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Ceil ceilValueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Ceil(expression); + } + + /** + * Creates new {@link Ceil}. + * + * @param value must not be {@literal null}. + * @return + */ + public static Ceil ceilValueOf(Number value) { + + Assert.notNull(value, "Value must not be null!"); + return new Ceil(value); + } + } + + /** + * {@link AggregationExpression} for {@code $divide}. + * + * @author Christoph Strobl + */ + public static class Divide extends AbstractAggregationExpression { + + private Divide(List value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$divide"; + } + + /** + * Creates new {@link Divide}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Divide valueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Divide(asFields(fieldReference)); + } + + /** + * Creates new {@link Divide}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Divide valueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Divide(Collections.singletonList(expression)); + } + + /** + * Creates new {@link Divide}. + * + * @param value must not be {@literal null}. + * @return + */ + public static Divide valueOf(Number value) { + + Assert.notNull(value, "Value must not be null!"); + return new Divide(Collections.singletonList(value)); + } + + public Divide divideBy(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Divide(append(Fields.field(fieldReference))); + } + + public Divide divideBy(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Divide(append(expression)); + } + + public Divide divideBy(Number value) { + return new Divide(append(value)); + } + } + + /** + * {@link AggregationExpression} for {@code $exp}. + * + * @author Christoph Strobl + */ + public static class Exp extends AbstractAggregationExpression { + + private Exp(Object value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$exp"; + } + + /** + * Creates new {@link Exp}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Exp expValueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Exp(Fields.field(fieldReference)); + } + + /** + * Creates new {@link Exp}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Exp expValueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Exp(expression); + } + + /** + * Creates new {@link Exp}. + * + * @param value must not be {@literal null}. + * @return + */ + public static Exp expValueOf(Number value) { + + Assert.notNull(value, "Value must not be null!"); + return new Exp(value); + } + } + + /** + * {@link AggregationExpression} for {@code $floor}. + * + * @author Christoph Strobl + */ + public static class Floor extends AbstractAggregationExpression { + + private Floor(Object value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$floor"; + } + + /** + * Creates new {@link Floor}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Floor floorValueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Floor(Fields.field(fieldReference)); + } + + /** + * Creates new {@link Floor}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Floor floorValueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Floor(expression); + } + + /** + * Creates new {@link Floor}. + * + * @param value must not be {@literal null}. + * @return + */ + public static Floor floorValueOf(Number value) { + + Assert.notNull(value, "Value must not be null!"); + return new Floor(value); + } + } + + /** + * {@link AggregationExpression} for {@code $ln}. + * + * @author Christoph Strobl + */ + public static class Ln extends AbstractAggregationExpression { + + private Ln(Object value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$ln"; + } + + /** + * Creates new {@link Ln}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Ln lnValueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Ln(Fields.field(fieldReference)); + } + + /** + * Creates new {@link Ln}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Ln lnValueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Ln(expression); + } + + /** + * Creates new {@link Ln}. + * + * @param value must not be {@literal null}. + * @return + */ + public static Ln lnValueOf(Number value) { + + Assert.notNull(value, "Value must not be null!"); + return new Ln(value); + } + } + + /** + * {@link AggregationExpression} for {@code $log}. + * + * @author Christoph Strobl + */ + public static class Log extends AbstractAggregationExpression { + + private Log(List values) { + super(values); + } + + @Override + protected String getMongoMethod() { + return "$log"; + } + + /** + * Creates new {@link Min}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Log valueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Log(asFields(fieldReference)); + } + + /** + * Creates new {@link Log}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Log valueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Log(Collections.singletonList(expression)); + } + + /** + * Creates new {@link Log}. + * + * @param value must not be {@literal null}. + * @return + */ + public static Log valueOf(Number value) { + + Assert.notNull(value, "Value must not be null!"); + return new Log(Collections.singletonList(value)); + } + + public Log log(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Log(append(Fields.field(fieldReference))); + } + + public Log log(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Log(append(expression)); + } + + public Log log(Number base) { + return new Log(append(base)); + } + } + + /** + * {@link AggregationExpression} for {@code $log10}. + * + * @author Christoph Strobl + */ + public static class Log10 extends AbstractAggregationExpression { + + private Log10(Object value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$log10"; + } + + /** + * Creates new {@link Log10}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Log10 log10ValueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Log10(Fields.field(fieldReference)); + } + + /** + * Creates new {@link Log10}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Log10 log10ValueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Log10(expression); + } + + /** + * Creates new {@link Log10}. + * + * @param value must not be {@literal null}. + * @return + */ + public static Log10 log10ValueOf(Number value) { + + Assert.notNull(value, "Value must not be null!"); + return new Log10(value); + } + } + + /** + * {@link AggregationExpression} for {@code $mod}. + * + * @author Christoph Strobl + */ + public static class Mod extends AbstractAggregationExpression { + + private Mod(Object value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$mod"; + } + + /** + * Creates new {@link Mod}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Mod valueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Mod(asFields(fieldReference)); + } + + /** + * Creates new {@link Mod}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Mod valueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Mod(Collections.singletonList(expression)); + } + + /** + * Creates new {@link Mod}. + * + * @param value must not be {@literal null}. + * @return + */ + public static Mod valueOf(Number value) { + + Assert.notNull(value, "Value must not be null!"); + return new Mod(Collections.singletonList(value)); + } + + public Mod mod(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Mod(append(Fields.field(fieldReference))); + } + + public Mod mod(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Mod(append(expression)); + } + + public Mod mod(Number base) { + return new Mod(append(base)); + } + } + + /** + * {@link AggregationExpression} for {@code $multiply}. + * + * @author Christoph Strobl + */ + public static class Multiply extends AbstractAggregationExpression { + + private Multiply(List value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$multiply"; + } + + /** + * Creates new {@link Multiply}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Multiply valueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Multiply(asFields(fieldReference)); + } + + /** + * Creates new {@link Multiply}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Multiply valueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Multiply(Collections.singletonList(expression)); + } + + /** + * Creates new {@link Multiply}. + * + * @param value must not be {@literal null}. + * @return + */ + public static Multiply valueOf(Number value) { + + Assert.notNull(value, "Value must not be null!"); + return new Multiply(Collections.singletonList(value)); + } + + public Multiply multiplyBy(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Multiply(append(Fields.field(fieldReference))); + } + + public Multiply multiplyBy(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Multiply(append(expression)); + } + + public Multiply multiplyBy(Number value) { + return new Multiply(append(value)); + } + } + + /** + * {@link AggregationExpression} for {@code $pow}. + * + * @author Christoph Strobl + */ + public static class Pow extends AbstractAggregationExpression { + + private Pow(List value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$pow"; + } + + /** + * Creates new {@link Pow}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Pow valueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Pow(asFields(fieldReference)); + } + + /** + * Creates new {@link Pow}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Pow valueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Pow(Collections.singletonList(expression)); + } + + /** + * Creates new {@link Pow}. + * + * @param value must not be {@literal null}. + * @return + */ + public static Pow valueOf(Number value) { + + Assert.notNull(value, "Value must not be null!"); + return new Pow(Collections.singletonList(value)); + } + + public Pow pow(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Pow(append(Fields.field(fieldReference))); + } + + public Pow pow(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Pow(append(expression)); + } + + public Pow pow(Number value) { + return new Pow(append(value)); + } + } + + /** + * {@link AggregationExpression} for {@code $sqrt}. + * + * @author Christoph Strobl + */ + public static class Sqrt extends AbstractAggregationExpression { + + private Sqrt(Object value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$sqrt"; + } + + /** + * Creates new {@link Sqrt}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Sqrt sqrtOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Sqrt(Fields.field(fieldReference)); + } + + /** + * Creates new {@link Sqrt}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Sqrt sqrtOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Sqrt(expression); + } + + /** + * Creates new {@link Sqrt}. + * + * @param value must not be {@literal null}. + * @return + */ + public static Sqrt sqrtOf(Number value) { + + Assert.notNull(value, "Value must not be null!"); + return new Sqrt(value); + } + } + + /** + * {@link AggregationExpression} for {@code $subtract}. + * + * @author Christoph Strobl + */ + public static class Subtract extends AbstractAggregationExpression { + + private Subtract(List value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$subtract"; + } + + /** + * Creates new {@link Subtract}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Subtract valueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Subtract(asFields(fieldReference)); + } + + /** + * Creates new {@link Subtract}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Subtract valueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Subtract(Collections.singletonList(expression)); + } + + /** + * Creates new {@link Subtract}. + * + * @param value must not be {@literal null}. + * @return + */ + public static Subtract valueOf(Number value) { + + Assert.notNull(value, "Value must not be null!"); + return new Subtract(Collections.singletonList(value)); + } + + public Subtract subtract(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Subtract(append(Fields.field(fieldReference))); + } + + public Subtract subtract(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Subtract(append(expression)); + } + + public Subtract subtract(Number value) { + return new Subtract(append(value)); + } + } + + /** + * {@link AggregationExpression} for {@code $trunc}. + * + * @author Christoph Strobl + */ + public static class Trunc extends AbstractAggregationExpression { + + private Trunc(Object value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$trunc"; + } + + /** + * Creates new {@link Trunc}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Trunc truncValueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Trunc(Fields.field(fieldReference)); + } + + /** + * Creates new {@link Trunc}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Trunc truncValueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Trunc(expression); + } + + /** + * Creates new {@link Trunc}. + * + * @param value must not be {@literal null}. + * @return + */ + public static Trunc truncValueOf(Number value) { + + Assert.notNull(value, "Value must not be null!"); + return new Trunc(value); + } + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ArrayOperators.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ArrayOperators.java new file mode 100644 index 0000000000..d6346baaa3 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ArrayOperators.java @@ -0,0 +1,1522 @@ +/* + * 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 java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.springframework.data.domain.Range; +import org.springframework.data.mongodb.core.aggregation.ArrayOperators.Filter.AsBuilder; +import org.springframework.data.mongodb.core.aggregation.ArrayOperators.Reduce.PropertyExpression; +import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExposedField; +import org.springframework.util.Assert; + +import com.mongodb.BasicDBObject; +import com.mongodb.DBObject; + +/** + * Gateway to {@literal array} aggregation operations. + * + * @author Christoph Strobl + * @since 1.0 + */ +public class ArrayOperators { + + /** + * Take the array referenced by given {@literal fieldReference}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static ArrayOperatorFactory arrayOf(String fieldReference) { + return new ArrayOperatorFactory(fieldReference); + } + + /** + * Take the array referenced resulting from the given {@link AggregationExpression}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static ArrayOperatorFactory arrayOf(AggregationExpression expression) { + return new ArrayOperatorFactory(expression); + } + + /** + * @author Christoph Strobl + */ + public static class ArrayOperatorFactory { + + private final String fieldReference; + private final AggregationExpression expression; + + /** + * Creates new {@link ArrayOperatorFactory} for given {@literal fieldReference}. + * + * @param fieldReference must not be {@literal null}. + */ + public ArrayOperatorFactory(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + this.fieldReference = fieldReference; + this.expression = null; + } + + /** + * Creates new {@link ArrayOperatorFactory} for given {@link AggregationExpression}. + * + * @param expression must not be {@literal null}. + */ + public ArrayOperatorFactory(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + this.fieldReference = null; + this.expression = expression; + } + + /** + * Creates new {@link AggregationExpression} that takes the associated array and returns the element at the + * specified array {@literal position}. + * + * @param position + * @return + */ + public ArrayElemAt elementAt(int position) { + return createArrayElemAt().elementAt(position); + } + + /** + * Creates new {@link AggregationExpression} that takes the associated array and returns the element at the position + * resulting form the given {@literal expression}. + * + * @param expression must not be {@literal null}. + * @return + */ + public ArrayElemAt elementAt(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return createArrayElemAt().elementAt(expression); + } + + /** + * Creates new {@link AggregationExpression} that takes the associated array and returns the element at the position + * defined by the referenced {@literal field}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public ArrayElemAt elementAt(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return createArrayElemAt().elementAt(fieldReference); + } + + private ArrayElemAt createArrayElemAt() { + return usesFieldRef() ? ArrayElemAt.arrayOf(fieldReference) : ArrayElemAt.arrayOf(expression); + } + + /** + * Creates new {@link AggregationExpression} that takes the associated array and concats the given + * {@literal arrayFieldReference} to it. + * + * @param arrayFieldReference must not be {@literal null}. + * @return + */ + public ConcatArrays concat(String arrayFieldReference) { + + Assert.notNull(arrayFieldReference, "ArrayFieldReference must not be null!"); + return createConcatArrays().concat(arrayFieldReference); + } + + /** + * Creates new {@link AggregationExpression} that takes the associated array and concats the array resulting form + * the given {@literal expression} to it. + * + * @param expression must not be {@literal null}. + * @return + */ + public ConcatArrays concat(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return createConcatArrays().concat(expression); + } + + private ConcatArrays createConcatArrays() { + return usesFieldRef() ? ConcatArrays.arrayOf(fieldReference) : ConcatArrays.arrayOf(expression); + } + + /** + * Creates new {@link AggregationExpression} that takes the associated array and selects a subset of the array to + * return based on the specified condition. + * + * @return + */ + public AsBuilder filter() { + return Filter.filter(fieldReference); + } + + /** + * Creates new {@link AggregationExpression} that takes the associated array and an check if its an array. + * + * @return + */ + public IsArray isArray() { + return usesFieldRef() ? IsArray.isArray(fieldReference) : IsArray.isArray(expression); + } + + /** + * Creates new {@link AggregationExpression} that takes the associated array and retrieves its length. + * + * @return + */ + public Size length() { + return usesFieldRef() ? Size.lengthOfArray(fieldReference) : Size.lengthOfArray(expression); + } + + /** + * Creates new {@link AggregationExpression} that takes the associated array and selects a subset from it. + * + * @return + */ + public Slice slice() { + return usesFieldRef() ? Slice.sliceArrayOf(fieldReference) : Slice.sliceArrayOf(expression); + } + + /** + * Creates new {@link AggregationExpression} that searches the associated array for an occurrence of a specified + * value and returns the array index (zero-based) of the first occurrence. + * + * @param value must not be {@literal null}. + * @return + */ + public IndexOfArray indexOf(Object value) { + return usesFieldRef() ? IndexOfArray.arrayOf(fieldReference).indexOf(value) + : IndexOfArray.arrayOf(expression).indexOf(value); + } + + /** + * Creates new {@link AggregationExpression} that returns an array with the elements in reverse order. + * + * @return + */ + public ReverseArray reverse() { + return usesFieldRef() ? ReverseArray.reverseArrayOf(fieldReference) : ReverseArray.reverseArrayOf(expression); + } + + /** + * Start creating new {@link AggregationExpression} that applies an {@link AggregationExpression} to each element + * in an array and combines them into a single value. + * + * @param expression must not be {@literal null}. + * @return + */ + public ArrayOperatorFactory.ReduceInitialValueBuilder reduce(final AggregationExpression expression) { + return new ArrayOperatorFactory.ReduceInitialValueBuilder() { + + @Override + public Reduce startingWith(Object initialValue) { + return (usesFieldRef() ? Reduce.arrayOf(fieldReference) : Reduce.arrayOf(expression)) + .withInitialValue(initialValue).reduce(expression); + } + }; + } + + /** + * Start creating new {@link AggregationExpression} that applies an {@link AggregationExpression} to each element + * in an array and combines them into a single value. + * + * @param expressions + * @return + */ + public ArrayOperatorFactory.ReduceInitialValueBuilder reduce(final PropertyExpression... expressions) { + + return new ArrayOperatorFactory.ReduceInitialValueBuilder() { + + @Override + public Reduce startingWith(Object initialValue) { + return (usesFieldRef() ? Reduce.arrayOf(fieldReference) : Reduce.arrayOf(expression)) + .withInitialValue(initialValue).reduce(expressions); + } + }; + } + + /** + * Creates new {@link AggregationExpression} that transposes an array of input arrays so that the first element of + * the output array would be an array containing, the first element of the first input array, the first element of + * the second input array, etc. + * + * @param arrays must not be {@literal null}. + * @return + */ + public Zip zipWith(Object... arrays) { + return (usesFieldRef() ? Zip.arrayOf(fieldReference) : Zip.arrayOf(expression)).zip(arrays); + } + + /** + * Creates new {@link AggregationExpression} that returns a boolean indicating whether a specified value is in the + * associated array. + * + * @param value must not be {@literal null}. + * @return + */ + public In containsValue(Object value) { + return (usesFieldRef() ? In.arrayOf(fieldReference) : In.arrayOf(expression)).containsValue(value); + } + + /** + * @author Christoph Strobl + */ + public interface ReduceInitialValueBuilder { + + /** + * Define the initial cumulative value set before in is applied to the first element of the input array. + * + * @param initialValue must not be {@literal null}. + * @return + */ + Reduce startingWith(Object initialValue); + } + + private boolean usesFieldRef() { + return fieldReference != null; + } + } + + /** + * {@link AggregationExpression} for {@code $arrayElementAt}. + * + * @author Christoph Strobl + */ + public static class ArrayElemAt extends AbstractAggregationExpression { + + private ArrayElemAt(List value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$arrayElemAt"; + } + + /** + * Creates new {@link ArrayElemAt}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static ArrayElemAt arrayOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new ArrayElemAt(asFields(fieldReference)); + } + + /** + * Creates new {@link ArrayElemAt}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static ArrayElemAt arrayOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new ArrayElemAt(Collections.singletonList(expression)); + } + + public ArrayElemAt elementAt(int index) { + return new ArrayElemAt(append(index)); + } + + public ArrayElemAt elementAt(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new ArrayElemAt(append(expression)); + } + + public ArrayElemAt elementAt(String arrayFieldReference) { + + Assert.notNull(arrayFieldReference, "ArrayReference must not be null!"); + return new ArrayElemAt(append(Fields.field(arrayFieldReference))); + } + } + + /** + * {@link AggregationExpression} for {@code $concatArrays}. + * + * @author Christoph Strobl + */ + public static class ConcatArrays extends AbstractAggregationExpression { + + private ConcatArrays(List value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$concatArrays"; + } + + /** + * Creates new {@link ConcatArrays}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static ConcatArrays arrayOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new ConcatArrays(asFields(fieldReference)); + } + + /** + * Creates new {@link ConcatArrays}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static ConcatArrays arrayOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new ConcatArrays(Collections.singletonList(expression)); + } + + public ConcatArrays concat(String arrayFieldReference) { + + Assert.notNull(arrayFieldReference, "ArrayFieldReference must not be null!"); + return new ConcatArrays(append(Fields.field(arrayFieldReference))); + } + + public ConcatArrays concat(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new ConcatArrays(append(expression)); + } + } + + /** + * {@code $filter} {@link AggregationExpression} allows to select a subset of the array to return based on the + * specified condition. + * + * @author Christoph Strobl + * @since 1.10 + */ + public static class Filter implements AggregationExpression { + + private Object input; + private ExposedField as; + private Object condition; + + private Filter() { + // used by builder + } + + /** + * Set the {@literal field} to apply the {@code $filter} to. + * + * @param field must not be {@literal null}. + * @return never {@literal null}. + */ + public static AsBuilder filter(String field) { + + Assert.notNull(field, "Field must not be null!"); + return filter(Fields.field(field)); + } + + /** + * Set the {@literal field} to apply the {@code $filter} to. + * + * @param field must not be {@literal null}. + * @return never {@literal null}. + */ + public static AsBuilder filter(Field field) { + + Assert.notNull(field, "Field must not be null!"); + return new FilterExpressionBuilder().filter(field); + } + + /** + * Set the {@literal values} to apply the {@code $filter} to. + * + * @param values must not be {@literal null}. + * @return + */ + public static AsBuilder filter(List values) { + + Assert.notNull(values, "Values must not be null!"); + return new FilterExpressionBuilder().filter(values); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.AggregationExpression#toDbObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) + */ + @Override + public DBObject toDbObject(final AggregationOperationContext context) { + return toFilter(ExposedFields.from(as), context); + } + + private DBObject toFilter(ExposedFields exposedFields, AggregationOperationContext context) { + + DBObject filterExpression = new BasicDBObject(); + InheritingExposedFieldsAggregationOperationContext operationContext = new InheritingExposedFieldsAggregationOperationContext( + exposedFields, context); + + filterExpression.putAll(context.getMappedObject(new BasicDBObject("input", getMappedInput(context)))); + filterExpression.put("as", as.getTarget()); + + filterExpression.putAll(context.getMappedObject(new BasicDBObject("cond", getMappedCondition(operationContext)))); + + return new BasicDBObject("$filter", filterExpression); + } + + private Object getMappedInput(AggregationOperationContext context) { + return input instanceof Field ? context.getReference((Field) input).toString() : input; + } + + private Object getMappedCondition(AggregationOperationContext context) { + + if (!(condition instanceof AggregationExpression)) { + return condition; + } + + NestedDelegatingExpressionAggregationOperationContext nea = new NestedDelegatingExpressionAggregationOperationContext( + context); + return ((AggregationExpression) condition).toDbObject(nea); + } + + /** + * @author Christoph Strobl + */ + public interface InputBuilder { + + /** + * Set the {@literal values} to apply the {@code $filter} to. + * + * @param array must not be {@literal null}. + * @return + */ + AsBuilder filter(List array); + + /** + * Set the {@literal field} holding an array to apply the {@code $filter} to. + * + * @param field must not be {@literal null}. + * @return + */ + AsBuilder filter(Field field); + } + + /** + * @author Christoph Strobl + */ + public interface AsBuilder { + + /** + * Set the {@literal variableName} for the elements in the input array. + * + * @param variableName must not be {@literal null}. + * @return + */ + ConditionBuilder as(String variableName); + } + + /** + * @author Christoph Strobl + */ + public interface ConditionBuilder { + + /** + * Set the {@link AggregationExpression} that determines whether to include the element in the resulting array. + * + * @param expression must not be {@literal null}. + * @return + */ + Filter by(AggregationExpression expression); + + /** + * Set the {@literal expression} that determines whether to include the element in the resulting array. + * + * @param expression must not be {@literal null}. + * @return + */ + Filter by(String expression); + + /** + * Set the {@literal expression} that determines whether to include the element in the resulting array. + * + * @param expression must not be {@literal null}. + * @return + */ + Filter by(DBObject expression); + } + + /** + * @author Christoph Strobl + */ + static final class FilterExpressionBuilder implements InputBuilder, AsBuilder, ConditionBuilder { + + private final Filter filter; + + FilterExpressionBuilder() { + this.filter = new Filter(); + } + + /** + * Creates new {@link InputBuilder}. + * + * @return + */ + public static InputBuilder newBuilder() { + return new FilterExpressionBuilder(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.ArrayOperators.Filter.InputBuilder#filter(java.util.List) + */ + @Override + public AsBuilder filter(List array) { + + Assert.notNull(array, "Array must not be null!"); + filter.input = new ArrayList(array); + return this; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.ArrayOperators.Filter.InputBuilder#filter(org.springframework.data.mongodb.core.aggregation.Field) + */ + @Override + public AsBuilder filter(Field field) { + + Assert.notNull(field, "Field must not be null!"); + filter.input = field; + return this; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.ArrayOperators.Filter.AsBuilder#as(java.lang.String) + */ + @Override + public ConditionBuilder as(String variableName) { + + Assert.notNull(variableName, "Variable name must not be null!"); + filter.as = new ExposedField(variableName, true); + return this; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.ArrayOperators.Filter.ConditionBuilder#by(org.springframework.data.mongodb.core.aggregation.AggregationExpression) + */ + @Override + public Filter by(AggregationExpression condition) { + + Assert.notNull(condition, "Condition must not be null!"); + filter.condition = condition; + return filter; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.ArrayOperators.Filter.ConditionBuilder#by(java.lang.String) + */ + @Override + public Filter by(String expression) { + + Assert.notNull(expression, "Expression must not be null!"); + filter.condition = expression; + return filter; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.ArrayOperators.Filter.ConditionBuilder#by(com.mongodb.DBObject) + */ + @Override + public Filter by(DBObject expression) { + + Assert.notNull(expression, "Expression must not be null!"); + filter.condition = expression; + return filter; + } + } + } + + /** + * {@link AggregationExpression} for {@code $isArray}. + * + * @author Christoph Strobl + */ + public static class IsArray extends AbstractAggregationExpression { + + private IsArray(Object value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$isArray"; + } + + /** + * Creates new {@link IsArray}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static IsArray isArray(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new IsArray(Fields.field(fieldReference)); + } + + /** + * Creates new {@link IsArray}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static IsArray isArray(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new IsArray(expression); + } + } + + /** + * {@link AggregationExpression} for {@code $size}. + * + * @author Christoph Strobl + */ + public static class Size extends AbstractAggregationExpression { + + private Size(Object value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$size"; + } + + /** + * Creates new {@link Size}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Size lengthOfArray(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Size(Fields.field(fieldReference)); + } + + /** + * Creates new {@link Size}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Size lengthOfArray(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Size(expression); + } + } + + /** + * {@link AggregationExpression} for {@code $slice}. + * + * @author Christoph Strobl + */ + public static class Slice extends AbstractAggregationExpression { + + private Slice(List value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$slice"; + } + + /** + * Creates new {@link Slice}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Slice sliceArrayOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Slice(asFields(fieldReference)); + } + + /** + * Creates new {@link Slice}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Slice sliceArrayOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Slice(Collections.singletonList(expression)); + } + + public Slice itemCount(int nrElements) { + return new Slice(append(nrElements)); + } + + public SliceElementsBuilder offset(final int position) { + + return new SliceElementsBuilder() { + + @Override + public Slice itemCount(int nrElements) { + return new Slice(append(position)).itemCount(nrElements); + } + }; + } + + /** + * @author Christoph Strobl + */ + public interface SliceElementsBuilder { + + /** + * Set the number of elements given {@literal nrElements}. + * + * @param nrElements + * @return + */ + Slice itemCount(int nrElements); + } + } + + /** + * {@link AggregationExpression} for {@code $indexOfArray}. + * + * @author Christoph Strobl + */ + public static class IndexOfArray extends AbstractAggregationExpression { + + private IndexOfArray(List value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$indexOfArray"; + } + + /** + * Start creating new {@link IndexOfArray}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static IndexOfArrayBuilder arrayOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new IndexOfArrayBuilder(Fields.field(fieldReference)); + } + + /** + * Start creating new {@link IndexOfArray}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static IndexOfArrayBuilder arrayOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new IndexOfArrayBuilder(expression); + } + + public IndexOfArray within(Range range) { + + Assert.notNull(range, "Range must not be null!"); + + List rangeValues = new ArrayList(2); + rangeValues.add(range.getLowerBound()); + if (range.getUpperBound() != null) { + rangeValues.add(range.getUpperBound()); + } + + return new IndexOfArray(append(rangeValues)); + } + + /** + * @author Christoph Strobl + */ + public static class IndexOfArrayBuilder { + + private final Object targetArray; + + private IndexOfArrayBuilder(Object targetArray) { + this.targetArray = targetArray; + } + + /** + * Set the {@literal value} to check for its index in the array. + * + * @param value must not be {@literal null}. + * @return + */ + public IndexOfArray indexOf(Object value) { + + Assert.notNull(value, "Value must not be null!"); + return new IndexOfArray(Arrays.asList(targetArray, value)); + } + } + } + + /** + * {@link AggregationExpression} for {@code $range}. + * + * @author Christoph Strobl + */ + public static class RangeOperator extends AbstractAggregationExpression { + + private RangeOperator(List values) { + super(values); + } + + @Override + protected String getMongoMethod() { + return "$range"; + } + + /** + * Start creating new {@link RangeOperator}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static RangeOperatorBuilder rangeStartingAt(String fieldReference) { + return new RangeOperatorBuilder(Fields.field(fieldReference)); + } + + /** + * Start creating new {@link RangeOperator}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static RangeOperatorBuilder rangeStartingAt(AggregationExpression expression) { + return new RangeOperatorBuilder(expression); + } + + /** + * Start creating new {@link RangeOperator}. + * + * @param value + * @return + */ + public static RangeOperatorBuilder rangeStartingAt(long value) { + return new RangeOperatorBuilder(value); + } + + public RangeOperator withStepSize(long stepSize) { + return new RangeOperator(append(stepSize)); + } + + public static class RangeOperatorBuilder { + + private final Object startPoint; + + private RangeOperatorBuilder(Object startPoint) { + this.startPoint = startPoint; + } + + /** + * Creates new {@link RangeOperator}. + * + * @param index + * @return + */ + public RangeOperator to(long index) { + return new RangeOperator(Arrays.asList(startPoint, index)); + } + + /** + * Creates new {@link RangeOperator}. + * + * @param expression must not be {@literal null}. + * @return + */ + public RangeOperator to(AggregationExpression expression) { + return new RangeOperator(Arrays.asList(startPoint, expression)); + } + + /** + * Creates new {@link RangeOperator}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public RangeOperator to(String fieldReference) { + return new RangeOperator(Arrays.asList(startPoint, Fields.field(fieldReference))); + } + } + } + + /** + * {@link AggregationExpression} for {@code $reverseArray}. + * + * @author Christoph Strobl + */ + public static class ReverseArray extends AbstractAggregationExpression { + + private ReverseArray(Object value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$reverseArray"; + } + + /** + * Creates new {@link ReverseArray} given {@literal fieldReference}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static ReverseArray reverseArrayOf(String fieldReference) { + return new ReverseArray(Fields.field(fieldReference)); + } + + /** + * Creates new {@link ReverseArray} given {@link AggregationExpression}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static ReverseArray reverseArrayOf(AggregationExpression expression) { + return new ReverseArray(expression); + } + } + + /** + * {@link AggregationExpression} for {@code $reduce}. + * + * @author Christoph Strobl + */ + public static class Reduce implements AggregationExpression { + + private final Object input; + private final Object initialValue; + private final List reduceExpressions; + + private Reduce(Object input, Object initialValue, List reduceExpressions) { + + this.input = input; + this.initialValue = initialValue; + this.reduceExpressions = reduceExpressions; + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.AggregationExpression#toDbObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) + */ + @Override + public DBObject toDbObject(AggregationOperationContext context) { + + DBObject dbo = new BasicDBObject(); + + dbo.put("input", getMappedValue(input, context)); + dbo.put("initialValue", getMappedValue(initialValue, context)); + + if (reduceExpressions.iterator().next() instanceof PropertyExpression) { + + DBObject properties = new BasicDBObject(); + for (AggregationExpression e : reduceExpressions) { + properties.putAll(e.toDbObject(context)); + } + dbo.put("in", properties); + } else { + dbo.put("in", (reduceExpressions.iterator().next()).toDbObject(context)); + } + + return new BasicDBObject("$reduce", dbo); + } + + private Object getMappedValue(Object value, AggregationOperationContext context) { + + if (value instanceof DBObject) { + return value; + } + if (value instanceof AggregationExpression) { + return ((AggregationExpression) value).toDbObject(context); + } else if (value instanceof Field) { + return context.getReference(((Field) value)).toString(); + } else { + return context.getMappedObject(new BasicDBObject("###val###", value)).get("###val###"); + } + } + + /** + * Start creating new {@link Reduce}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static InitialValueBuilder arrayOf(final String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null"); + + return new InitialValueBuilder() { + + @Override + public ReduceBuilder withInitialValue(final Object initialValue) { + + Assert.notNull(initialValue, "Initial value must not be null"); + + return new ReduceBuilder() { + + @Override + public Reduce reduce(AggregationExpression expression) { + + Assert.notNull(expression, "AggregationExpression must not be null"); + return new Reduce(Fields.field(fieldReference), initialValue, Collections.singletonList(expression)); + } + + @Override + public Reduce reduce(PropertyExpression... expressions) { + + Assert.notNull(expressions, "PropertyExpressions must not be null"); + + return new Reduce(Fields.field(fieldReference), initialValue, + Arrays. asList(expressions)); + } + }; + } + }; + } + + /** + * Start creating new {@link Reduce}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static InitialValueBuilder arrayOf(final AggregationExpression expression) { + + Assert.notNull(expression, "AggregationExpression must not be null"); + + return new InitialValueBuilder() { + + @Override + public ReduceBuilder withInitialValue(final Object initialValue) { + + Assert.notNull(initialValue, "Initial value must not be null"); + + return new ReduceBuilder() { + + @Override + public Reduce reduce(AggregationExpression expression) { + + Assert.notNull(expression, "AggregationExpression must not be null"); + return new Reduce(expression, initialValue, Collections.singletonList(expression)); + } + + @Override + public Reduce reduce(PropertyExpression... expressions) { + + Assert.notNull(expressions, "PropertyExpressions must not be null"); + return new Reduce(expression, initialValue, Arrays. asList(expressions)); + } + }; + } + }; + } + + /** + * @author Christoph Strobl + */ + public interface InitialValueBuilder { + + /** + * Define the initial cumulative value set before in is applied to the first element of the input array. + * + * @param initialValue must not be {@literal null}. + * @return + */ + ReduceBuilder withInitialValue(Object initialValue); + } + + /** + * @author Christoph Strobl + */ + public interface ReduceBuilder { + + /** + * Define the {@link AggregationExpression} to apply to each element in the input array in left-to-right order. + *
        + * NOTE: During evaluation of the in expression the variable references {@link Variable#THIS} and + * {@link Variable#VALUE} are available. + * + * @param expression must not be {@literal null}. + * @return + */ + Reduce reduce(AggregationExpression expression); + + /** + * Define the {@link PropertyExpression}s to apply to each element in the input array in left-to-right order. + *
        + * NOTE: During evaluation of the in expression the variable references {@link Variable#THIS} and + * {@link Variable#VALUE} are available. + * + * @param expressions must not be {@literal null}. + * @return + */ + Reduce reduce(PropertyExpression... expressions); + } + + /** + * @author Christoph Strobl + */ + public static class PropertyExpression implements AggregationExpression { + + private final String propertyName; + private final AggregationExpression aggregationExpression; + + protected PropertyExpression(String propertyName, AggregationExpression aggregationExpression) { + + Assert.notNull(propertyName, "Property name must not be null!"); + Assert.notNull(aggregationExpression, "AggregationExpression must not be null!"); + + this.propertyName = propertyName; + this.aggregationExpression = aggregationExpression; + } + + /** + * Define a result property for an {@link AggregationExpression} used in {@link Reduce}. + * + * @param name must not be {@literal null}. + * @return + */ + public static AsBuilder property(final String name) { + + return new AsBuilder() { + + @Override + public PropertyExpression definedAs(AggregationExpression expression) { + return new PropertyExpression(name, expression); + } + }; + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.AggregationExpression#toDbObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) + */ + @Override + public DBObject toDbObject(AggregationOperationContext context) { + return new BasicDBObject(propertyName, aggregationExpression.toDbObject(context)); + } + + /** + * @author Christoph Strobl + */ + public interface AsBuilder { + + /** + * Set the {@link AggregationExpression} resulting in the properties value. + * + * @param expression must not be {@literal null}. + * @return + */ + PropertyExpression definedAs(AggregationExpression expression); + } + } + + public enum Variable implements Field { + + THIS { + @Override + public String getName() { + return "$$this"; + } + + @Override + public String getTarget() { + return "$$this"; + } + + @Override + public boolean isAliased() { + return false; + } + + @Override + public String toString() { + return getName(); + } + }, + + VALUE { + @Override + public String getName() { + return "$$value"; + } + + @Override + public String getTarget() { + return "$$value"; + } + + @Override + public boolean isAliased() { + return false; + } + + @Override + public String toString() { + return getName(); + } + }; + + /** + * Create a {@link Field} reference to a given {@literal property} prefixed with the {@link Variable} identifier. + * eg. {@code $$value.product} + * + * @param property must not be {@literal null}. + * @return + */ + public Field referringTo(final String property) { + + return new Field() { + @Override + public String getName() { + return Variable.this.getName() + "." + property; + } + + @Override + public String getTarget() { + return Variable.this.getTarget() + "." + property; + } + + @Override + public boolean isAliased() { + return false; + } + + @Override + public String toString() { + return getName(); + } + }; + } + } + } + + /** + * {@link AggregationExpression} for {@code $zip}. + * + * @author Christoph Strobl + */ + public static class Zip extends AbstractAggregationExpression { + + protected Zip(java.util.Map value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$zip"; + } + + /** + * Start creating new {@link Zip}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static ZipBuilder arrayOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new ZipBuilder(Fields.field(fieldReference)); + } + + /** + * Start creating new {@link Zip}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static ZipBuilder arrayOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new ZipBuilder(expression); + } + + /** + * Create new {@link Zip} and set the {@code useLongestLength} property to {@literal true}. + * + * @return + */ + public Zip useLongestLength() { + return new Zip(append("useLongestLength", true)); + } + + /** + * Optionally provide a default value. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public Zip defaultTo(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Zip(append("defaults", Fields.field(fieldReference))); + } + + /** + * Optionally provide a default value. + * + * @param expression must not be {@literal null}. + * @return + */ + public Zip defaultTo(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Zip(append("defaults", expression)); + } + + /** + * Optionally provide a default value. + * + * @param array must not be {@literal null}. + * @return + */ + public Zip defaultTo(Object[] array) { + + Assert.notNull(array, "Array must not be null!"); + return new Zip(append("defaults", array)); + } + + public static class ZipBuilder { + + private final List sourceArrays; + + private ZipBuilder(Object sourceArray) { + + this.sourceArrays = new ArrayList(); + this.sourceArrays.add(sourceArray); + } + + /** + * Creates new {@link Zip} that transposes an array of input arrays so that the first element of the output array + * would be an array containing, the first element of the first input array, the first element of the second input + * array, etc. + * + * @param arrays arrays to zip the referenced one with. must not be {@literal null}. + * @return + */ + public Zip zip(Object... arrays) { + + Assert.notNull(arrays, "Arrays must not be null!"); + for (Object value : arrays) { + + if (value instanceof String) { + sourceArrays.add(Fields.field((String) value)); + } else { + sourceArrays.add(value); + } + } + + return new Zip(Collections. singletonMap("inputs", sourceArrays)); + } + } + } + + /** + * {@link AggregationExpression} for {@code $in}. + * + * @author Christoph Strobl + */ + public static class In extends AbstractAggregationExpression { + + private In(List values) { + super(values); + } + + @Override + protected String getMongoMethod() { + return "$in"; + } + + /** + * Start creating {@link In}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static InBuilder arrayOf(final String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + + return new InBuilder() { + + @Override + public In containsValue(Object value) { + + Assert.notNull(value, "Value must not be null!"); + return new In(Arrays.asList(value, Fields.field(fieldReference))); + } + }; + } + + /** + * Start creating {@link In}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static InBuilder arrayOf(final AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + + return new InBuilder() { + + @Override + public In containsValue(Object value) { + + Assert.notNull(value, "Value must not be null!"); + return new In(Arrays.asList(value, expression)); + } + }; + } + + /** + * @author Christoph Strobl + */ + public interface InBuilder { + + /** + * Set the {@literal value} to check for existence in the array. + * + * @param value must not be {@literal value}. + * @return + */ + In containsValue(Object value); + } + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/BooleanOperators.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/BooleanOperators.java new file mode 100644 index 0000000000..df93fd8919 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/BooleanOperators.java @@ -0,0 +1,353 @@ +/* + * 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 java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.springframework.util.Assert; + +/** + * Gateway to {@literal boolean expressions} that evaluate their argument expressions as booleans and return a boolean + * as the result. + * + * @author Christoph Strobl + * @since 1.10 + */ +public class BooleanOperators { + + /** + * Take the array referenced by given {@literal fieldReference}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static BooleanOperatorFactory valueOf(String fieldReference) { + return new BooleanOperatorFactory(fieldReference); + } + + /** + * Take the value resulting of the given {@link AggregationExpression}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static BooleanOperatorFactory valueOf(AggregationExpression fieldReference) { + return new BooleanOperatorFactory(fieldReference); + } + + /** + * Creates new {@link AggregationExpression} that evaluates the boolean value of the referenced field and returns the + * opposite boolean value. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Not not(String fieldReference) { + return Not.not(fieldReference); + } + + /** + * Creates new {@link AggregationExpression} that evaluates the boolean value of {@link AggregationExpression} result + * and returns the opposite boolean value. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Not not(AggregationExpression expression) { + return Not.not(expression); + } + + /** + * @author Christoph Strobl + */ + public static class BooleanOperatorFactory { + + private final String fieldReference; + private final AggregationExpression expression; + + /** + * Creates new {@link BooleanOperatorFactory} for given {@literal fieldReference}. + * + * @param fieldReference must not be {@literal null}. + */ + public BooleanOperatorFactory(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + this.fieldReference = fieldReference; + this.expression = null; + } + + /** + * Creates new {@link BooleanOperatorFactory} for given {@link AggregationExpression}. + * + * @param expression must not be {@literal null}. + */ + public BooleanOperatorFactory(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + this.fieldReference = null; + this.expression = expression; + } + + /** + * Creates new {@link AggregationExpression} that evaluates one or more expressions and returns {@literal true} if + * all of the expressions are {@literal true}. + * + * @param expression must not be {@literal null}. + * @return + */ + public And and(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return createAnd().andExpression(expression); + } + + /** + * Creates new {@link AggregationExpression} that evaluates one or more expressions and returns {@literal true} if + * all of the expressions are {@literal true}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public And and(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return createAnd().andField(fieldReference); + } + + private And createAnd() { + return usesFieldRef() ? And.and(Fields.field(fieldReference)) : And.and(expression); + } + + /** + * Creates new {@link AggregationExpression} that evaluates one or more expressions and returns {@literal true} if + * any of the expressions are {@literal true}. + * + * @param expression must not be {@literal null}. + * @return + */ + public Or or(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return createOr().orExpression(expression); + } + + /** + * Creates new {@link AggregationExpression} that evaluates one or more expressions and returns {@literal true} if + * any of the expressions are {@literal true}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public Or or(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return createOr().orField(fieldReference); + } + + private Or createOr() { + return usesFieldRef() ? Or.or(Fields.field(fieldReference)) : Or.or(expression); + } + + /** + * Creates new {@link AggregationExpression} that evaluates a boolean and returns the opposite boolean value. + * + * @return + */ + public Not not() { + return usesFieldRef() ? Not.not(fieldReference) : Not.not(expression); + } + + private boolean usesFieldRef() { + return this.fieldReference != null; + } + } + + /** + * {@link AggregationExpression} for {@code $and}. + * + * @author Christoph Strobl + */ + public static class And extends AbstractAggregationExpression { + + private And(List values) { + super(values); + } + + @Override + protected String getMongoMethod() { + return "$and"; + } + + /** + * Creates new {@link And} that evaluates one or more expressions and returns {@literal true} if all of the + * expressions are {@literal true}. + * + * @param expressions + * @return + */ + public static And and(Object... expressions) { + return new And(Arrays.asList(expressions)); + } + + /** + * Creates new {@link And} with all previously added arguments appending the given one. + * + * @param expression must not be {@literal null}. + * @return + */ + public And andExpression(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new And(append(expression)); + } + + /** + * Creates new {@link And} with all previously added arguments appending the given one. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public And andField(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new And(append(Fields.field(fieldReference))); + } + + /** + * Creates new {@link And} with all previously added arguments appending the given one. + * + * @param value must not be {@literal null}. + * @return + */ + public And andValue(Object value) { + + Assert.notNull(value, "Value must not be null!"); + return new And(append(value)); + } + } + + /** + * {@link AggregationExpression} for {@code $or}. + * + * @author Christoph Strobl + */ + public static class Or extends AbstractAggregationExpression { + + private Or(List values) { + super(values); + } + + @Override + protected String getMongoMethod() { + return "$or"; + } + + /** + * Creates new {@link Or} that evaluates one or more expressions and returns {@literal true} if any of the + * expressions are {@literal true}. + * + * @param expressions must not be {@literal null}. + * @return + */ + public static Or or(Object... expressions) { + + Assert.notNull(expressions, "Expressions must not be null!"); + return new Or(Arrays.asList(expressions)); + } + + /** + * Creates new {@link Or} with all previously added arguments appending the given one. + * + * @param expression must not be {@literal null}. + * @return + */ + public Or orExpression(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Or(append(expression)); + } + + /** + * Creates new {@link Or} with all previously added arguments appending the given one. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public Or orField(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Or(append(Fields.field(fieldReference))); + } + + /** + * Creates new {@link Or} with all previously added arguments appending the given one. + * + * @param value must not be {@literal null}. + * @return + */ + public Or orValue(Object value) { + + Assert.notNull(value, "Value must not be null!"); + return new Or(append(value)); + } + } + + /** + * {@link AggregationExpression} for {@code $not}. + * + * @author Christoph Strobl + */ + public static class Not extends AbstractAggregationExpression { + + private Not(Object value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$not"; + } + + /** + * Creates new {@link Not} that evaluates the boolean value of the referenced field and returns the opposite boolean + * value. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Not not(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Not(asFields(fieldReference)); + } + + /** + * Creates new {@link Not} that evaluates the resulting boolean value of the given {@link AggregationExpression} and + * returns the opposite boolean value. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Not not(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Not(Collections.singletonList(expression)); + } + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/BucketAutoOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/BucketAutoOperation.java new file mode 100644 index 0000000000..08d1b621f1 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/BucketAutoOperation.java @@ -0,0 +1,275 @@ +/* + * 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.springframework.data.mongodb.core.aggregation.BucketAutoOperation.BucketAutoOperationOutputBuilder; +import org.springframework.data.mongodb.core.aggregation.BucketOperationSupport.OutputBuilder; +import org.springframework.util.Assert; + +import com.mongodb.BasicDBObject; +import com.mongodb.DBObject; + +/** + * Encapsulates the aggregation framework {@code $bucketAuto}-operation.
        + * Bucket stage is typically used with {@link Aggregation} and {@code $facet}. Categorizes incoming documents into a + * specific number of groups, called buckets, based on a specified expression. Bucket boundaries are automatically + * determined in an attempt to evenly distribute the documents into the specified number of buckets.
        + * We recommend to use the static factory method {@link Aggregation#bucketAuto(String, int)} instead of creating + * instances of this class directly. + * + * @see https://docs.mongodb.org/manual/reference/aggregation/bucketAuto/ + * @see BucketOperationSupport + * @author Mark Paluch + * @author Christoph Strobl + * @since 1.10 + */ +public class BucketAutoOperation extends BucketOperationSupport + implements FieldsExposingAggregationOperation { + + private final int buckets; + private final String granularity; + + /** + * Creates a new {@link BucketAutoOperation} given a {@link Field group-by field}. + * + * @param groupByField must not be {@literal null}. + * @param buckets number of buckets, must be a positive integer. + */ + public BucketAutoOperation(Field groupByField, int buckets) { + + super(groupByField); + + Assert.isTrue(buckets > 0, "Number of buckets must be greater 0!"); + + this.buckets = buckets; + this.granularity = null; + } + + /** + * Creates a new {@link BucketAutoOperation} given a {@link AggregationExpression group-by expression}. + * + * @param groupByExpression must not be {@literal null}. + * @param buckets number of buckets, must be a positive integer. + */ + public BucketAutoOperation(AggregationExpression groupByExpression, int buckets) { + + super(groupByExpression); + + Assert.isTrue(buckets > 0, "Number of buckets must be greater 0!"); + + this.buckets = buckets; + this.granularity = null; + } + + private BucketAutoOperation(BucketAutoOperation bucketOperation, Outputs outputs) { + + super(bucketOperation, outputs); + + this.buckets = bucketOperation.buckets; + this.granularity = bucketOperation.granularity; + } + + private BucketAutoOperation(BucketAutoOperation bucketOperation, int buckets, String granularity) { + + super(bucketOperation); + + this.buckets = buckets; + this.granularity = granularity; + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.BucketOperationSupport#toDBObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) + */ + @Override + public DBObject toDBObject(AggregationOperationContext context) { + + DBObject options = new BasicDBObject(); + + options.put("buckets", buckets); + options.putAll(super.toDBObject(context)); + + if (granularity != null) { + options.put("granularity", granularity); + } + + return new BasicDBObject("$bucketAuto", options); + } + + /** + * Configures a number of bucket {@literal buckets} and return a new {@link BucketAutoOperation}. + * + * @param buckets must be a positive number. + * @return + */ + public BucketAutoOperation withBuckets(int buckets) { + + Assert.isTrue(buckets > 0, "Number of buckets must be greater 0!"); + return new BucketAutoOperation(this, buckets, granularity); + } + + /** + * Configures {@link Granularity granularity} that specifies the preferred number series to use to ensure that the + * calculated boundary edges end on preferred round numbers or their powers of 10 and return a new + * {@link BucketAutoOperation}.
        + * Use either predefined {@link Granularities} or provide a own one. + * + * @param granularity must not be {@literal null}. + * @return + */ + public BucketAutoOperation withGranularity(Granularity granularity) { + + Assert.notNull(granularity, "Granularity must not be null!"); + + return new BucketAutoOperation(this, buckets, granularity.getMongoRepresentation()); + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.BucketOperationSupport#newBucketOperation(org.springframework.data.mongodb.core.aggregation.BucketOperationSupport.Outputs) + */ + @Override + protected BucketAutoOperation newBucketOperation(Outputs outputs) { + return new BucketAutoOperation(this, outputs); + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.BucketOperationSupport#andOutputExpression(java.lang.String, java.lang.Object[]) + */ + @Override + public ExpressionBucketAutoOperationBuilder andOutputExpression(String expression, Object... params) { + return new ExpressionBucketAutoOperationBuilder(expression, this, params); + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.BucketOperationSupport#andOutput(org.springframework.data.mongodb.core.aggregation.AggregationExpression) + */ + @Override + public BucketAutoOperationOutputBuilder andOutput(AggregationExpression expression) { + return new BucketAutoOperationOutputBuilder(expression, this); + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.BucketOperationSupport#andOutput(java.lang.String) + */ + @Override + public BucketAutoOperationOutputBuilder andOutput(String fieldName) { + return new BucketAutoOperationOutputBuilder(Fields.field(fieldName), this); + } + + /** + * {@link OutputBuilder} implementation for {@link BucketAutoOperation}. + */ + public static class BucketAutoOperationOutputBuilder + extends OutputBuilder { + + /** + * Creates a new {@link BucketAutoOperationOutputBuilder} fot the given value and {@link BucketAutoOperation}. + * + * @param value must not be {@literal null}. + * @param operation must not be {@literal null}. + */ + protected BucketAutoOperationOutputBuilder(Object value, BucketAutoOperation operation) { + super(value, operation); + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.BucketOperationSupport.OutputBuilder#apply(org.springframework.data.mongodb.core.aggregation.BucketOperationSupport.OperationOutput) + */ + @Override + protected BucketAutoOperationOutputBuilder apply(OperationOutput operationOutput) { + return new BucketAutoOperationOutputBuilder(operationOutput, this.operation); + } + } + + /** + * {@link ExpressionBucketOperationBuilderSupport} implementation for {@link BucketAutoOperation} using SpEL + * expression based {@link Output}. + * + * @author Mark Paluch + */ + public static class ExpressionBucketAutoOperationBuilder + extends ExpressionBucketOperationBuilderSupport { + + /** + * Creates a new {@link ExpressionBucketAutoOperationBuilder} for the given value, {@link BucketAutoOperation} and + * parameters. + * + * @param expression must not be {@literal null}. + * @param operation must not be {@literal null}. + * @param parameters + */ + protected ExpressionBucketAutoOperationBuilder(String expression, BucketAutoOperation operation, + Object[] parameters) { + super(expression, operation, parameters); + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.BucketOperationSupport.OutputBuilder#apply(org.springframework.data.mongodb.core.aggregation.BucketOperationSupport.OperationOutput) + */ + @Override + protected BucketAutoOperationOutputBuilder apply(OperationOutput operationOutput) { + return new BucketAutoOperationOutputBuilder(operationOutput, this.operation); + } + } + + /** + * @author Mark Paluch + */ + public interface Granularity { + + /** + * @return a String that represents a MongoDB granularity to be used with {@link BucketAutoOperation}. Never + * {@literal null}. + */ + String getMongoRepresentation(); + } + + /** + * Supported MongoDB granularities. + * + * @see + * + * Bucket stage is typically used with {@link Aggregation} and {@code $facet}. Categorizes incoming documents into + * groups, called buckets, based on a specified expression and bucket boundaries.
        + * + * We recommend to use the static factory method {@link Aggregation#bucket(String)} instead of creating instances of + * this class directly. + * + * @see
        https://docs.mongodb.org/manual/reference/aggregation/bucket/ + * @see BucketOperationSupport + * @author Mark Paluch + * @since 1.10 + */ +public class BucketOperation extends BucketOperationSupport + implements FieldsExposingAggregationOperation { + + private final List boundaries; + private final Object defaultBucket; + + /** + * Creates a new {@link BucketOperation} given a {@link Field group-by field}. + * + * @param groupByField must not be {@literal null}. + */ + public BucketOperation(Field groupByField) { + + super(groupByField); + + this.boundaries = Collections.emptyList(); + this.defaultBucket = null; + } + + /** + * Creates a new {@link BucketOperation} given a {@link AggregationExpression group-by expression}. + * + * @param groupByExpression must not be {@literal null}. + */ + public BucketOperation(AggregationExpression groupByExpression) { + + super(groupByExpression); + + this.boundaries = Collections.emptyList(); + this.defaultBucket = null; + } + + private BucketOperation(BucketOperation bucketOperation, Outputs outputs) { + + super(bucketOperation, outputs); + + this.boundaries = bucketOperation.boundaries; + this.defaultBucket = bucketOperation.defaultBucket; + } + + private BucketOperation(BucketOperation bucketOperation, List boundaries, Object defaultBucket) { + + super(bucketOperation); + + this.boundaries = new ArrayList(boundaries); + this.defaultBucket = defaultBucket; + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.BucketOperationSupport#toDBObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) + */ + @Override + public DBObject toDBObject(AggregationOperationContext context) { + + DBObject options = new BasicDBObject(); + + options.put("boundaries", context.getMappedObject(new BasicDBObject("$set", boundaries)).get("$set")); + + if (defaultBucket != null) { + options.put("default", context.getMappedObject(new BasicDBObject("$set", defaultBucket)).get("$set")); + } + + options.putAll(super.toDBObject(context)); + + return new BasicDBObject("$bucket", options); + } + + /** + * Configures a default bucket {@literal literal} and return a new {@link BucketOperation}. + * + * @param literal must not be {@literal null}. + * @return + */ + public BucketOperation withDefaultBucket(Object literal) { + + Assert.notNull(literal, "Default bucket literal must not be null!"); + return new BucketOperation(this, boundaries, literal); + } + + /** + * Configures {@literal boundaries} and return a new {@link BucketOperation}. Existing {@literal boundaries} are + * preserved and the new {@literal boundaries} are appended. + * + * @param boundaries must not be {@literal null}. + * @return + */ + public BucketOperation withBoundaries(Object... boundaries) { + + Assert.notNull(boundaries, "Boundaries must not be null!"); + Assert.noNullElements(boundaries, "Boundaries must not contain null values!"); + + List newBoundaries = new ArrayList(this.boundaries.size() + boundaries.length); + newBoundaries.addAll(this.boundaries); + newBoundaries.addAll(Arrays.asList(boundaries)); + + return new BucketOperation(this, newBoundaries, defaultBucket); + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.BucketOperationSupport#newBucketOperation(org.springframework.data.mongodb.core.aggregation.BucketOperationSupport.Outputs) + */ + @Override + protected BucketOperation newBucketOperation(Outputs outputs) { + return new BucketOperation(this, outputs); + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.BucketOperationSupport#andOutputExpression(java.lang.String, java.lang.Object[]) + */ + @Override + public ExpressionBucketOperationBuilder andOutputExpression(String expression, Object... params) { + return new ExpressionBucketOperationBuilder(expression, this, params); + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.BucketOperationSupport#andOutput(org.springframework.data.mongodb.core.aggregation.AggregationExpression) + */ + @Override + public BucketOperationOutputBuilder andOutput(AggregationExpression expression) { + return new BucketOperationOutputBuilder(expression, this); + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.BucketOperationSupport#andOutput(java.lang.String) + */ + @Override + public BucketOperationOutputBuilder andOutput(String fieldName) { + return new BucketOperationOutputBuilder(Fields.field(fieldName), this); + } + + /** + * {@link OutputBuilder} implementation for {@link BucketOperation}. + */ + public static class BucketOperationOutputBuilder + extends BucketOperationSupport.OutputBuilder { + + /** + * Creates a new {@link BucketOperationOutputBuilder} fot the given value and {@link BucketOperation}. + * + * @param value must not be {@literal null}. + * @param operation must not be {@literal null}. + */ + protected BucketOperationOutputBuilder(Object value, BucketOperation operation) { + super(value, operation); + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.BucketOperationSupport.OutputBuilder#apply(org.springframework.data.mongodb.core.aggregation.BucketOperationSupport.OperationOutput) + */ + @Override + protected BucketOperationOutputBuilder apply(OperationOutput operationOutput) { + return new BucketOperationOutputBuilder(operationOutput, this.operation); + } + } + + /** + * {@link ExpressionBucketOperationBuilderSupport} implementation for {@link BucketOperation} using SpEL expression + * based {@link Output}. + * + * @author Mark Paluch + */ + public static class ExpressionBucketOperationBuilder + extends ExpressionBucketOperationBuilderSupport { + + /** + * Creates a new {@link ExpressionBucketOperationBuilderSupport} for the given value, {@link BucketOperation} + * and parameters. + * + * @param expression must not be {@literal null}. + * @param operation must not be {@literal null}. + * @param parameters + */ + protected ExpressionBucketOperationBuilder(String expression, BucketOperation operation, Object[] parameters) { + super(expression, operation, parameters); + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.BucketOperationSupport.OutputBuilder#apply(org.springframework.data.mongodb.core.aggregation.BucketOperationSupport.OperationOutput) + */ + @Override + protected BucketOperationOutputBuilder apply(OperationOutput operationOutput) { + return new BucketOperationOutputBuilder(operationOutput, this.operation); + } + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/BucketOperationSupport.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/BucketOperationSupport.java new file mode 100644 index 0000000000..c78eddddd0 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/BucketOperationSupport.java @@ -0,0 +1,680 @@ +/* + * 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 java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import org.springframework.data.mongodb.core.aggregation.BucketOperationSupport.OutputBuilder; +import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExposedField; +import org.springframework.data.mongodb.core.aggregation.ProjectionOperation.ProjectionOperationBuilder; +import org.springframework.expression.spel.ast.Projection; +import org.springframework.util.Assert; + +import com.mongodb.BasicDBObject; +import com.mongodb.DBObject; + +/** + * Base class for bucket operations that support output expressions the aggregation framework.
        + * Bucket stages collect documents into buckets and can contribute output fields.
        + * Implementing classes are required to provide an {@link OutputBuilder}. + * + * @author Mark Paluch + * @author Christoph Strobl + * @since 1.10 + */ +public abstract class BucketOperationSupport, B extends OutputBuilder> + implements FieldsExposingAggregationOperation { + + private final Field groupByField; + private final AggregationExpression groupByExpression; + private final Outputs outputs; + + /** + * Creates a new {@link BucketOperationSupport} given a {@link Field group-by field}. + * + * @param groupByField must not be {@literal null}. + */ + protected BucketOperationSupport(Field groupByField) { + + Assert.notNull(groupByField, "Group by field must not be null!"); + + this.groupByField = groupByField; + this.groupByExpression = null; + this.outputs = Outputs.EMPTY; + } + + /** + * Creates a new {@link BucketOperationSupport} given a {@link AggregationExpression group-by expression}. + * + * @param groupByExpression must not be {@literal null}. + */ + protected BucketOperationSupport(AggregationExpression groupByExpression) { + + Assert.notNull(groupByExpression, "Group by AggregationExpression must not be null!"); + + this.groupByExpression = groupByExpression; + this.groupByField = null; + this.outputs = Outputs.EMPTY; + } + + /** + * Creates a copy of {@link BucketOperationSupport}. + * + * @param operationSupport must not be {@literal null}. + */ + protected BucketOperationSupport(BucketOperationSupport operationSupport) { + this(operationSupport, operationSupport.outputs); + } + + /** + * Creates a copy of {@link BucketOperationSupport} and applies the new {@link Outputs}. + * + * @param operationSupport must not be {@literal null}. + * @param outputs must not be {@literal null}. + */ + protected BucketOperationSupport(BucketOperationSupport operationSupport, Outputs outputs) { + + Assert.notNull(operationSupport, "BucketOperationSupport must not be null!"); + Assert.notNull(outputs, "Outputs must not be null!"); + + this.groupByField = operationSupport.groupByField; + this.groupByExpression = operationSupport.groupByExpression; + this.outputs = outputs; + } + + /** + * Creates a new {@link ExpressionBucketOperationBuilderSupport} given a SpEL {@literal expression} and optional + * {@literal params} to add an output field to the resulting bucket documents. + * + * @param expression the SpEL expression, must not be {@literal null} or empty. + * @param params must not be {@literal null} + * @return + */ + public abstract ExpressionBucketOperationBuilderSupport andOutputExpression(String expression, + Object... params); + + /** + * Creates a new {@link BucketOperationSupport} given an {@link AggregationExpression} to add an output field to the + * resulting bucket documents. + * + * @param expression the SpEL expression, must not be {@literal null} or empty. + * @return + */ + public abstract B andOutput(AggregationExpression expression); + + /** + * Creates a new {@link BucketOperationSupport} given {@literal fieldName} to add an output field to the resulting + * bucket documents. {@link BucketOperationSupport} exposes accumulation operations that can be applied to + * {@literal fieldName}. + * + * @param fieldName must not be {@literal null} or empty. + * @return + */ + public abstract B andOutput(String fieldName); + + /** + * Creates a new {@link BucketOperationSupport} given to add a count field to the resulting bucket documents. + * + * @return + */ + public B andOutputCount() { + return andOutput(new AggregationExpression() { + @Override + public DBObject toDbObject(AggregationOperationContext context) { + return new BasicDBObject("$sum", 1); + } + }); + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.AggregationOperation#toDBObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) + */ + @Override + public DBObject toDBObject(AggregationOperationContext context) { + + DBObject dbObject = new BasicDBObject(); + + dbObject.put("groupBy", groupByExpression == null ? context.getReference(groupByField).toString() + : groupByExpression.toDbObject(context)); + + if (!outputs.isEmpty()) { + dbObject.put("output", outputs.toDbObject(context)); + } + + return dbObject; + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.FieldsExposingAggregationOperation#getFields() + */ + @Override + public ExposedFields getFields() { + return outputs.asExposedFields(); + } + + /** + * Implementation hook to create a new bucket operation. + * + * @param outputs the outputs + * @return the new bucket operation. + */ + protected abstract T newBucketOperation(Outputs outputs); + + protected T andOutput(Output output) { + return newBucketOperation(outputs.and(output)); + } + + /** + * Builder for SpEL expression-based {@link Output}. + * + * @author Mark Paluch + */ + public abstract static class ExpressionBucketOperationBuilderSupport, T extends BucketOperationSupport> + extends OutputBuilder { + + /** + * Creates a new {@link ExpressionBucketOperationBuilderSupport} for the given value, {@link BucketOperationSupport} + * and parameters. + * + * @param expression must not be {@literal null}. + * @param operation must not be {@literal null}. + * @param parameters + */ + protected ExpressionBucketOperationBuilderSupport(String expression, T operation, Object[] parameters) { + super(new SpelExpressionOutput(expression, parameters), operation); + } + } + + /** + * Base class for {@link Output} builders that result in a {@link BucketOperationSupport} providing the built + * {@link Output}. + * + * @author Mark Paluch + */ + public abstract static class OutputBuilder, T extends BucketOperationSupport> { + + protected final Object value; + protected final T operation; + + /** + * Creates a new {@link OutputBuilder} for the given value and {@link BucketOperationSupport}. + * + * @param value must not be {@literal null}. + * @param operation must not be {@literal null}. + */ + protected OutputBuilder(Object value, T operation) { + + Assert.notNull(value, "Value must not be null or empty!"); + Assert.notNull(operation, "ProjectionOperation must not be null!"); + + this.value = value; + this.operation = operation; + } + + /** + * Generates a builder for a {@code $sum}-expression.
        + * Count expressions are emulated via {@code $sum: 1}. + * + * @return + */ + public B count() { + return sum(1); + } + + /** + * Generates a builder for a {@code $sum}-expression for the current value. + * + * @return + */ + public B sum() { + return apply(Accumulators.SUM); + } + + /** + * Generates a builder for a {@code $sum}-expression for the given {@literal value}. + * + * @param value + * @return + */ + public B sum(Number value) { + return apply(new OperationOutput(Accumulators.SUM.getMongoOperator(), Collections.singleton(value))); + } + + /** + * Generates a builder for an {@code $last}-expression for the current value.. + * + * @return + */ + public B last() { + return apply(Accumulators.LAST); + } + + /** + * Generates a builder for a {@code $first}-expression the current value. + * + * @return + */ + public B first() { + return apply(Accumulators.FIRST); + } + + /** + * Generates a builder for an {@code $avg}-expression for the current value. + * + * @param reference + * @return + */ + public B avg() { + return apply(Accumulators.AVG); + } + + /** + * Generates a builder for an {@code $min}-expression for the current value. + * + * @return + */ + public B min() { + return apply(Accumulators.MIN); + } + + /** + * Generates a builder for an {@code $max}-expression for the current value. + * + * @return + */ + public B max() { + return apply(Accumulators.MAX); + } + + /** + * Generates a builder for an {@code $push}-expression for the current value. + * + * @return + */ + public B push() { + return apply(Accumulators.PUSH); + } + + /** + * Generates a builder for an {@code $addToSet}-expression for the current value. + * + * @return + */ + public B addToSet() { + return apply(Accumulators.ADDTOSET); + } + + /** + * Apply an operator to the current value. + * + * @param operation the operation name, must not be {@literal null} or empty. + * @param values must not be {@literal null}. + * @return + */ + public B apply(String operation, Object... values) { + + Assert.hasText(operation, "Operation must not be empty or null!"); + Assert.notNull(value, "Values must not be null!"); + + List objects = new ArrayList(values.length + 1); + objects.add(value); + objects.addAll(Arrays.asList(values)); + return apply(new OperationOutput(operation, objects)); + } + + /** + * Apply an {@link OperationOutput} to this output. + * + * @param operationOutput must not be {@literal null}. + * @return + */ + protected abstract B apply(OperationOutput operationOutput); + + private B apply(Accumulators operation) { + return this.apply(operation.getMongoOperator()); + } + + /** + * Returns the finally to be applied {@link BucketOperation} with the given alias. + * + * @param alias will never be {@literal null} or empty. + * @return + */ + public T as(String alias) { + + if (value instanceof OperationOutput) { + return this.operation.andOutput(((OperationOutput) this.value).withAlias(alias)); + } + + if (value instanceof Field) { + throw new IllegalStateException("Cannot add a field as top-level output. Use accumulator expressions."); + } + + return this.operation + .andOutput(new AggregationExpressionOutput(Fields.field(alias), (AggregationExpression) value)); + } + } + + private enum Accumulators { + + SUM("$sum"), AVG("$avg"), FIRST("$first"), LAST("$last"), MAX("$max"), MIN("$min"), PUSH("$push"), ADDTOSET( + "$addToSet"); + + private String mongoOperator; + + Accumulators(String mongoOperator) { + this.mongoOperator = mongoOperator; + } + + public String getMongoOperator() { + return mongoOperator; + } + } + + /** + * Encapsulates {@link Output}s. + * + * @author Mark Paluch + */ + protected static class Outputs implements AggregationExpression { + + protected static final Outputs EMPTY = new Outputs(); + + private List outputs; + + /** + * Creates a new, empty {@link Outputs}. + */ + private Outputs() { + this.outputs = new ArrayList(); + } + + /** + * Creates new {@link Outputs} containing all given {@link Output}s. + * + * @param current + * @param output + */ + private Outputs(Collection current, Output output) { + + this.outputs = new ArrayList(current.size() + 1); + this.outputs.addAll(current); + this.outputs.add(output); + } + + /** + * @return the {@link ExposedFields} derived from {@link Output}. + */ + protected ExposedFields asExposedFields() { + + // The count field is included by default when the output is not specified. + if (isEmpty()) { + return ExposedFields.from(new ExposedField("count", true)); + } + + ExposedFields fields = ExposedFields.from(); + + for (Output output : outputs) { + fields = fields.and(output.getExposedField()); + } + + return fields; + } + + /** + * Create a new {@link Outputs} that contains the new {@link Output}. + * + * @param output must not be {@literal null}. + * @return the new {@link Outputs} that contains the new {@link Output} + */ + protected Outputs and(Output output) { + + Assert.notNull(output, "BucketOutput must not be null!"); + return new Outputs(this.outputs, output); + } + + /** + * @return {@literal true} if {@link Outputs} contains no {@link Output}. + */ + protected boolean isEmpty() { + return outputs.isEmpty(); + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.AggregationExpression#toDbObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) + */ + @Override + public DBObject toDbObject(AggregationOperationContext context) { + + DBObject dbObject = new BasicDBObject(); + + for (Output output : outputs) { + dbObject.put(output.getExposedField().getName(), output.toDbObject(context)); + } + + return dbObject; + } + + } + + /** + * Encapsulates an output field in a bucket aggregation stage.
        + * Output fields can be either top-level fields that define a valid field name or nested output fields using + * operators. + * + * @author Mark Paluch + */ + protected abstract static class Output implements AggregationExpression { + + private final ExposedField field; + + /** + * Creates new {@link Projection} for the given {@link Field}. + * + * @param field must not be {@literal null}. + */ + protected Output(Field field) { + + Assert.notNull(field, "Field must not be null!"); + this.field = new ExposedField(field, true); + } + + /** + * Returns the field exposed by the {@link Output}. + * + * @return will never be {@literal null}. + */ + protected ExposedField getExposedField() { + return field; + } + } + + /** + * Output field that uses a Mongo operation (expression object) to generate an output field value.
        + * {@link OperationOutput} is used either with a regular field name or an operation keyword (e.g. + * {@literal $sum, $count}). + * + * @author Mark Paluch + */ + protected static class OperationOutput extends Output { + + private final String operation; + private final List values; + + /** + * Creates a new {@link Output} for the given field. + * + * @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 OperationOutput(String operation, Collection values) { + + super(Fields.field(operation)); + + Assert.hasText(operation, "Operation must not be null or empty!"); + Assert.notNull(values, "Values must not be null!"); + + this.operation = operation; + this.values = new ArrayList(values); + } + + private OperationOutput(Field field, OperationOutput operationOutput) { + + super(field); + + this.operation = operationOutput.operation; + this.values = operationOutput.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 operationArguments = getOperationArguments(context); + return new BasicDBObject(operation, + operationArguments.size() == 1 ? operationArguments.get(0) : operationArguments); + } + + protected List getOperationArguments(AggregationOperationContext context) { + + List result = new ArrayList(values != null ? values.size() : 1); + + for (Object element : values) { + + if (element instanceof Field) { + result.add(context.getReference((Field) element).toString()); + } else if (element instanceof Fields) { + for (Field field : (Fields) element) { + result.add(context.getReference(field).toString()); + } + } else if (element instanceof AggregationExpression) { + result.add(((AggregationExpression) element).toDbObject(context)); + } else { + result.add(element); + } + } + + return result; + } + + /** + * Returns the field that holds the {@link ProjectionOperationBuilder.OperationProjection}. + * + * @return + */ + protected Field getField() { + return getExposedField(); + } + + /** + * Creates a new instance of this {@link OperationOutput} with the given alias. + * + * @param alias the alias to set + * @return + */ + public OperationOutput withAlias(String alias) { + + final Field aliasedField = Fields.field(alias); + return new OperationOutput(aliasedField, this) { + + @Override + protected Field getField() { + return aliasedField; + } + + @Override + protected List getOperationArguments(AggregationOperationContext context) { + + // We have to make sure that we use the arguments from the "previous" OperationOutput that we replace + // with this new instance. + return OperationOutput.this.getOperationArguments(context); + } + }; + } + } + + /** + * A {@link Output} based on a SpEL expression. + */ + private static class SpelExpressionOutput extends Output { + + private static final SpelExpressionTransformer TRANSFORMER = new SpelExpressionTransformer(); + + private final String expression; + private final Object[] params; + + /** + * Creates a new {@link SpelExpressionOutput} for the given field, SpEL expression and parameters. + * + * @param expression must not be {@literal null} or empty. + * @param parameters must not be {@literal null}. + */ + public SpelExpressionOutput(String expression, Object[] parameters) { + + super(Fields.field(expression)); + + Assert.hasText(expression, "Expression must not be null!"); + Assert.notNull(parameters, "Parameters must not be null!"); + + this.expression = expression; + this.params = parameters.clone(); + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.BucketOperationSupport.Output#toDbObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) + */ + @Override + public DBObject toDbObject(AggregationOperationContext context) { + return (DBObject) TRANSFORMER.transform(expression, context, params); + } + } + + /** + * @author Mark Paluch + */ + private static class AggregationExpressionOutput extends Output { + + private final AggregationExpression expression; + + /** + * Creates a new {@link AggregationExpressionOutput}. + * + * @param field + * @param expression + */ + protected AggregationExpressionOutput(Field field, AggregationExpression expression) { + + super(field); + + this.expression = expression; + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.BucketOperationSupport.Output#toDbObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) + */ + @Override + public DBObject toDbObject(AggregationOperationContext context) { + return expression.toDbObject(context); + } + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ComparisonOperators.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ComparisonOperators.java new file mode 100644 index 0000000000..2824634237 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ComparisonOperators.java @@ -0,0 +1,879 @@ +/* + * 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 java.util.Collections; +import java.util.List; + +import org.springframework.util.Assert; + +/** + * Gateway to {@literal comparison expressions}. + * + * @author Christoph Strobl + * @since 1.10 + */ +public class ComparisonOperators { + + /** + * Take the field referenced by given {@literal fieldReference}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static ComparisonOperatorFactory valueOf(String fieldReference) { + return new ComparisonOperatorFactory(fieldReference); + } + + /** + * Take the value resulting from the given {@link AggregationExpression}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static ComparisonOperatorFactory valueOf(AggregationExpression expression) { + return new ComparisonOperatorFactory(expression); + } + + public static class ComparisonOperatorFactory { + + private final String fieldReference; + private final AggregationExpression expression; + + /** + * Creates new {@link ComparisonOperatorFactory} for given {@literal fieldReference}. + * + * @param fieldReference must not be {@literal null}. + */ + public ComparisonOperatorFactory(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + this.fieldReference = fieldReference; + this.expression = null; + } + + /** + * Creates new {@link ComparisonOperatorFactory} for given {@link AggregationExpression}. + * + * @param expression must not be {@literal null}. + */ + public ComparisonOperatorFactory(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + this.fieldReference = null; + this.expression = expression; + } + + /** + * Creates new {@link AggregationExpression} that compares two values. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public Cmp compareTo(String fieldReference) { + return createCmp().compareTo(fieldReference); + } + + /** + * Creates new {@link AggregationExpression} that compares two values. + * + * @param expression must not be {@literal null}. + * @return + */ + public Cmp compareTo(AggregationExpression expression) { + return createCmp().compareTo(expression); + } + + /** + * Creates new {@link AggregationExpression} that compares two values. + * + * @param value must not be {@literal null}. + * @return + */ + public Cmp compareToValue(Object value) { + return createCmp().compareToValue(value); + } + + private Cmp createCmp() { + return usesFieldRef() ? Cmp.valueOf(fieldReference) : Cmp.valueOf(expression); + } + + /** + * Creates new {@link AggregationExpression} that compares two values and returns {@literal true} when the first + * value is equal to the value of the referenced field. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public Eq equalTo(String fieldReference) { + return createEq().equalTo(fieldReference); + } + + /** + * Creates new {@link AggregationExpression} that compares two values and returns {@literal true} when the first + * value is equal to the expression result. + * + * @param expression must not be {@literal null}. + * @return + */ + public Eq equalTo(AggregationExpression expression) { + return createEq().equalTo(expression); + } + + /** + * Creates new {@link AggregationExpression} that compares two values and returns {@literal true} when the first + * value is equal to the given value. + * + * @param value must not be {@literal null}. + * @return + */ + public Eq equalToValue(Object value) { + return createEq().equalToValue(value); + } + + private Eq createEq() { + return usesFieldRef() ? Eq.valueOf(fieldReference) : Eq.valueOf(expression); + } + + /** + * Creates new {@link AggregationExpression} that compares two values and returns {@literal true} when the first + * value is greater than the value of the referenced field. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public Gt greaterThan(String fieldReference) { + return createGt().greaterThan(fieldReference); + } + + /** + * Creates new {@link AggregationExpression} that compares two values and returns {@literal true} when the first + * value is greater than the expression result. + * + * @param expression must not be {@literal null}. + * @return + */ + public Gt greaterThan(AggregationExpression expression) { + return createGt().greaterThan(expression); + } + + /** + * Creates new {@link AggregationExpression} that compares two values and returns {@literal true} when the first + * value is greater than the given value. + * + * @param value must not be {@literal null}. + * @return + */ + public Gt greaterThanValue(Object value) { + return createGt().greaterThanValue(value); + } + + private Gt createGt() { + return usesFieldRef() ? Gt.valueOf(fieldReference) : Gt.valueOf(expression); + } + + /** + * Creates new {@link AggregationExpression} that compares two values and returns {@literal true} when the first + * value is greater than or equivalent to the value of the referenced field. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public Gte greaterThanEqualTo(String fieldReference) { + return createGte().greaterThanEqualTo(fieldReference); + } + + /** + * Creates new {@link AggregationExpression} that compares two values and returns {@literal true} when the first + * value is greater than or equivalent to the expression result. + * + * @param expression must not be {@literal null}. + * @return + */ + public Gte greaterThanEqualTo(AggregationExpression expression) { + return createGte().greaterThanEqualTo(expression); + } + + /** + * Creates new {@link AggregationExpression} that compares two values and returns {@literal true} when the first + * value is greater than or equivalent to the given value. + * + * @param value must not be {@literal null}. + * @return + */ + public Gte greaterThanEqualToValue(Object value) { + return createGte().greaterThanEqualToValue(value); + } + + private Gte createGte() { + return usesFieldRef() ? Gte.valueOf(fieldReference) : Gte.valueOf(expression); + } + + /** + * Creates new {@link AggregationExpression} that compares two values and returns {@literal true} when the first + * value is less than the value of the referenced field. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public Lt lessThan(String fieldReference) { + return createLt().lessThan(fieldReference); + } + + /** + * Creates new {@link AggregationExpression} that compares two values and returns {@literal true} when the first + * value is less than the expression result. + * + * @param expression must not be {@literal null}. + * @return + */ + public Lt lessThan(AggregationExpression expression) { + return createLt().lessThan(expression); + } + + /** + * Creates new {@link AggregationExpression} that compares two values and returns {@literal true} when the first + * value is less than to the given value. + * + * @param value must not be {@literal null}. + * @return + */ + public Lt lessThanValue(Object value) { + return createLt().lessThanValue(value); + } + + private Lt createLt() { + return usesFieldRef() ? Lt.valueOf(fieldReference) : Lt.valueOf(expression); + } + + /** + * Creates new {@link AggregationExpression} that compares two values and returns {@literal true} when the first + * value is less than or equivalent to the value of the referenced field. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public Lte lessThanEqualTo(String fieldReference) { + return createLte().lessThanEqualTo(fieldReference); + } + + /** + * Creates new {@link AggregationExpression} that compares two values and returns {@literal true} when the first + * value is less than or equivalent to the expression result. + * + * @param expression must not be {@literal null}. + * @return + */ + public Lte lessThanEqualTo(AggregationExpression expression) { + return createLte().lessThanEqualTo(expression); + } + + /** + * Creates new {@link AggregationExpression} that compares two values and returns {@literal true} when the first + * value is less than or equivalent to the given value. + * + * @param value + * @return + */ + public Lte lessThanEqualToValue(Object value) { + return createLte().lessThanEqualToValue(value); + } + + private Lte createLte() { + return usesFieldRef() ? Lte.valueOf(fieldReference) : Lte.valueOf(expression); + } + + /** + * Creates new {@link AggregationExpression} that compares two values and returns {@literal true} when the values + * are not equivalent. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public Ne notEqualTo(String fieldReference) { + return createNe().notEqualTo(fieldReference); + } + + /** + * Creates new {@link AggregationExpression} that compares two values and returns {@literal true} when the values + * are not equivalent. + * + * @param expression must not be {@literal null}. + * @return + */ + public Ne notEqualTo(AggregationExpression expression) { + return createNe().notEqualTo(expression); + } + + /** + * Creates new {@link AggregationExpression} that compares two values and returns {@literal true} when the values + * are not equivalent. + * + * @param value must not be {@literal null}. + * @return + */ + public Ne notEqualToValue(Object value) { + return createNe().notEqualToValue(value); + } + + private Ne createNe() { + return usesFieldRef() ? Ne.valueOf(fieldReference) : Ne.valueOf(expression); + } + + private boolean usesFieldRef() { + return fieldReference != null; + } + } + + /** + * {@link AggregationExpression} for {@code $cmp}. + * + * @author Christoph Strobl + */ + public static class Cmp extends AbstractAggregationExpression { + + private Cmp(List value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$cmp"; + } + + /** + * Creates new {@link Cmp}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Cmp valueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Cmp(asFields(fieldReference)); + } + + /** + * Creates new {@link Cmp}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Cmp valueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Cmp(Collections.singletonList(expression)); + } + + /** + * Creates new {@link Cmp} with all previously added arguments appending the given one. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public Cmp compareTo(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Cmp(append(Fields.field(fieldReference))); + } + + /** + * Creates new {@link Cmp} with all previously added arguments appending the given one. + * + * @param expression must not be {@literal null}. + * @return + */ + public Cmp compareTo(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Cmp(append(expression)); + } + + /** + * Creates new {@link Cmp} with all previously added arguments appending the given one. + * + * @param value must not be {@literal null}. + * @return + */ + public Cmp compareToValue(Object value) { + + Assert.notNull(value, "Value must not be null!"); + return new Cmp(append(value)); + } + } + + /** + * {@link AggregationExpression} for {@code $eq}. + * + * @author Christoph Strobl + */ + public static class Eq extends AbstractAggregationExpression { + + private Eq(List value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$eq"; + } + + /** + * Creates new {@link Eq}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Eq valueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Eq(asFields(fieldReference)); + } + + /** + * Creates new {@link Eq}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Eq valueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Eq(Collections.singletonList(expression)); + } + + /** + * Creates new {@link Eq} with all previously added arguments appending the given one. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public Eq equalTo(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Eq(append(Fields.field(fieldReference))); + } + + /** + * Creates new {@link Eq} with all previously added arguments appending the given one. + * + * @param expression must not be {@literal null}. + * @return + */ + public Eq equalTo(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Eq(append(expression)); + } + + /** + * Creates new {@link Eq} with all previously added arguments appending the given one. + * + * @param value must not be {@literal null}. + * @return + */ + public Eq equalToValue(Object value) { + + Assert.notNull(value, "Value must not be null!"); + return new Eq(append(value)); + } + } + + /** + * {@link AggregationExpression} for {@code $gt}. + * + * @author Christoph Strobl + */ + public static class Gt extends AbstractAggregationExpression { + + private Gt(List value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$gt"; + } + + /** + * Creates new {@link Gt}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Gt valueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Gt(asFields(fieldReference)); + } + + /** + * Creates new {@link Gt}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Gt valueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Gt(Collections.singletonList(expression)); + } + + /** + * Creates new {@link Gt} with all previously added arguments appending the given one. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public Gt greaterThan(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Gt(append(Fields.field(fieldReference))); + } + + /** + * Creates new {@link Gt} with all previously added arguments appending the given one. + * + * @param expression must not be {@literal null}. + * @return + */ + public Gt greaterThan(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Gt(append(expression)); + } + + /** + * Creates new {@link Gt} with all previously added arguments appending the given one. + * + * @param value must not be {@literal null}. + * @return + */ + public Gt greaterThanValue(Object value) { + + Assert.notNull(value, "Value must not be null!"); + return new Gt(append(value)); + } + } + + /** + * {@link AggregationExpression} for {@code $lt}. + * + * @author Christoph Strobl + */ + public static class Lt extends AbstractAggregationExpression { + + private Lt(List value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$lt"; + } + + /** + * Creates new {@link Lt}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Lt valueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Lt(asFields(fieldReference)); + } + + /** + * Creates new {@link Lt}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Lt valueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Lt(Collections.singletonList(expression)); + } + + /** + * Creates new {@link Lt} with all previously added arguments appending the given one. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public Lt lessThan(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Lt(append(Fields.field(fieldReference))); + } + + /** + * Creates new {@link Lt} with all previously added arguments appending the given one. + * + * @param expression must not be {@literal null}. + * @return + */ + public Lt lessThan(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Lt(append(expression)); + } + + /** + * Creates new {@link Lt} with all previously added arguments appending the given one. + * + * @param value must not be {@literal null}. + * @return + */ + public Lt lessThanValue(Object value) { + + Assert.notNull(value, "Value must not be null!"); + return new Lt(append(value)); + } + } + + /** + * {@link AggregationExpression} for {@code $gte}. + * + * @author Christoph Strobl + */ + public static class Gte extends AbstractAggregationExpression { + + private Gte(List value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$gte"; + } + + /** + * Creates new {@link Gte}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Gte valueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Gte(asFields(fieldReference)); + } + + /** + * Creates new {@link Gte}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Gte valueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Gte(Collections.singletonList(expression)); + } + + /** + * Creates new {@link Gte} with all previously added arguments appending the given one. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public Gte greaterThanEqualTo(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Gte(append(Fields.field(fieldReference))); + } + + /** + * Creates new {@link Gte} with all previously added arguments appending the given one. + * + * @param expression must not be {@literal null}. + * @return + */ + public Gte greaterThanEqualTo(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Gte(append(expression)); + } + + /** + * Creates new {@link Gte} with all previously added arguments appending the given one. + * + * @param value must not be {@literal null}. + * @return + */ + public Gte greaterThanEqualToValue(Object value) { + + Assert.notNull(value, "Value must not be null!"); + return new Gte(append(value)); + } + } + + /** + * {@link AggregationExpression} for {@code $lte}. + * + * @author Christoph Strobl + */ + public static class Lte extends AbstractAggregationExpression { + + private Lte(List value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$lte"; + } + + /** + * Creates new {@link Lte}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Lte valueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Lte(asFields(fieldReference)); + } + + /** + * Creates new {@link Lte}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Lte valueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Lte(Collections.singletonList(expression)); + } + + /** + * Creates new {@link Lte} with all previously added arguments appending the given one. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public Lte lessThanEqualTo(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Lte(append(Fields.field(fieldReference))); + } + + /** + * Creates new {@link Lte} with all previously added arguments appending the given one. + * + * @param expression must not be {@literal null}. + * @return + */ + public Lte lessThanEqualTo(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Lte(append(expression)); + } + + /** + * Creates new {@link Lte} with all previously added arguments appending the given one. + * + * @param value must not be {@literal null}. + * @return + */ + public Lte lessThanEqualToValue(Object value) { + + Assert.notNull(value, "Value must not be null!"); + return new Lte(append(value)); + } + } + + /** + * {@link AggregationExpression} for {@code $ne}. + * + * @author Christoph Strobl + */ + public static class Ne extends AbstractAggregationExpression { + + private Ne(List value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$ne"; + } + + /** + * Creates new {@link Ne}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Ne valueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Ne(asFields(fieldReference)); + } + + /** + * Creates new {@link Ne}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Ne valueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Ne(Collections.singletonList(expression)); + } + + /** + * Creates new {@link Ne} with all previously added arguments appending the given one. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public Ne notEqualTo(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Ne(append(Fields.field(fieldReference))); + } + + /** + * Creates new {@link Ne} with all previously added arguments appending the given one. + * + * @param expression must not be {@literal null}. + * @return + */ + public Ne notEqualTo(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Ne(append(expression)); + } + + /** + * Creates new {@link Eq} with all previously added arguments appending the given one. + * + * @param value must not be {@literal null}. + * @return + */ + public Ne notEqualToValue(Object value) { + + Assert.notNull(value, "Value must not be null!"); + return new Ne(append(value)); + } + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ConditionalOperator.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ConditionalOperator.java deleted file mode 100644 index 881894a14f..0000000000 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ConditionalOperator.java +++ /dev/null @@ -1,394 +0,0 @@ -/* - * 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 java.util.ArrayList; -import java.util.List; - -import org.springframework.dao.InvalidDataAccessApiUsageException; -import org.springframework.data.mongodb.core.query.CriteriaDefinition; -import org.springframework.util.Assert; -import org.springframework.util.ClassUtils; - -import com.mongodb.BasicDBObject; -import com.mongodb.DBObject; - -/** - * Encapsulates the aggregation framework {@code $cond} operator. A {@link ConditionalOperator} allows nested conditions - * {@code if-then[if-then-else]-else} using {@link Field}, {@link CriteriaDefinition} or a {@link DBObject custom} - * condition. Replacement values can be either {@link Field field references}, values of simple MongoDB types or values - * that can be converted to a simple MongoDB type. - * - * @see http://docs.mongodb.com/manual/reference/operator/aggregation/cond/ - * @author Mark Paluch - * @author Christoph Strobl - * @since 1.10 - */ -public class ConditionalOperator implements AggregationExpression { - - private final Object condition; - private final Object thenValue; - private final Object otherwiseValue; - - /** - * Creates a new {@link ConditionalOperator} for a given {@link Field} and {@code then}/{@code otherwise} values. - * - * @param condition must not be {@literal null}. - * @param thenValue must not be {@literal null}. - * @param otherwiseValue must not be {@literal null}. - */ - public ConditionalOperator(Field condition, Object thenValue, Object otherwiseValue) { - this((Object) condition, thenValue, otherwiseValue); - } - - /** - * Creates a new {@link ConditionalOperator} for a given {@link CriteriaDefinition} and {@code then}/{@code otherwise} - * values. - * - * @param condition must not be {@literal null}. - * @param thenValue must not be {@literal null}. - * @param otherwiseValue must not be {@literal null}. - */ - public ConditionalOperator(CriteriaDefinition condition, Object thenValue, Object otherwiseValue) { - this((Object) condition, thenValue, otherwiseValue); - } - - /** - * Creates a new {@link ConditionalOperator} for a given {@link DBObject criteria} and {@code then}/{@code otherwise} - * values. - * - * @param condition must not be {@literal null}. - * @param thenValue must not be {@literal null}. - * @param otherwiseValue must not be {@literal null}. - */ - public ConditionalOperator(DBObject condition, Object thenValue, Object otherwiseValue) { - this((Object) condition, thenValue, otherwiseValue); - } - - private ConditionalOperator(Object condition, Object thenValue, Object otherwiseValue) { - - Assert.notNull(condition, "Condition must not be null!"); - Assert.notNull(thenValue, "'Then value' must not be null!"); - Assert.notNull(otherwiseValue, "'Otherwise value' must not be null!"); - - assertNotBuilder(condition, "Condition"); - assertNotBuilder(thenValue, "'Then value'"); - assertNotBuilder(otherwiseValue, "'Otherwise value'"); - - this.condition = condition; - this.thenValue = thenValue; - this.otherwiseValue = otherwiseValue; - } - - /* - * (non-Javadoc) - * @see org.springframework.data.mongodb.core.aggregation.AggregationExpression#toDbObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) - */ - @Override - public DBObject toDbObject(AggregationOperationContext context) { - - BasicDBObject condObject = new BasicDBObject(); - - condObject.append("if", resolveCriteria(context, condition)); - condObject.append("then", resolveValue(context, thenValue)); - condObject.append("else", resolveValue(context, otherwiseValue)); - - return new BasicDBObject("$cond", condObject); - } - - private Object resolveValue(AggregationOperationContext context, Object value) { - - if (value instanceof DBObject || value instanceof Field) { - return resolve(context, value); - } - - if (value instanceof ConditionalOperator) { - return ((ConditionalOperator) value).toDbObject(context); - } - - return context.getMappedObject(new BasicDBObject("$set", value)).get("$set"); - } - - private Object resolveCriteria(AggregationOperationContext context, Object value) { - - if (value instanceof DBObject || value instanceof Field) { - return resolve(context, value); - } - - if (value instanceof CriteriaDefinition) { - - DBObject mappedObject = context.getMappedObject(((CriteriaDefinition) value).getCriteriaObject()); - List clauses = new ArrayList(); - - clauses.addAll(getClauses(context, mappedObject)); - - if (clauses.size() == 1) { - return clauses.get(0); - } - - return clauses; - } - - throw new InvalidDataAccessApiUsageException( - String.format("Invalid value in condition. Supported: DBObject, Field references, Criteria, got: %s", value)); - } - - private List getClauses(AggregationOperationContext context, DBObject mappedObject) { - - List clauses = new ArrayList(); - - for (String key : mappedObject.keySet()) { - - Object predicate = mappedObject.get(key); - clauses.addAll(getClauses(context, key, predicate)); - } - - return clauses; - } - - private List getClauses(AggregationOperationContext context, String key, Object predicate) { - - List clauses = new ArrayList(); - - if (predicate instanceof List) { - - List args = new ArrayList(); - for (Object clause : (List) predicate) { - if (clause instanceof DBObject) { - args.addAll(getClauses(context, (DBObject) clause)); - } - } - - clauses.add(new BasicDBObject(key, args)); - - } else if (predicate instanceof DBObject) { - - DBObject nested = (DBObject) predicate; - - for (String s : nested.keySet()) { - - if (!isKeyword(s)) { - continue; - } - - List args = new ArrayList(); - args.add("$" + key); - args.add(nested.get(s)); - clauses.add(new BasicDBObject(s, args)); - } - - } else if (!isKeyword(key)) { - - List args = new ArrayList(); - args.add("$" + key); - args.add(predicate); - clauses.add(new BasicDBObject("$eq", args)); - } - - return clauses; - } - - /** - * Returns whether the given {@link String} is a MongoDB keyword. - * - * @param candidate - * @return - */ - private boolean isKeyword(String candidate) { - return candidate.startsWith("$"); - } - - private Object resolve(AggregationOperationContext context, Object value) { - - if (value instanceof DBObject) { - return context.getMappedObject((DBObject) value); - } - - return context.getReference((Field) value).toString(); - } - - private void assertNotBuilder(Object toCheck, String name) { - Assert.isTrue(!ClassUtils.isAssignableValue(ConditionalExpressionBuilder.class, toCheck), - String.format("%s must not be of type %s", name, ConditionalExpressionBuilder.class.getSimpleName())); - } - - /** - * Get a builder that allows fluent creation of {@link ConditionalOperator}. - * - * @return a new {@link ConditionalExpressionBuilder}. - */ - public static ConditionalExpressionBuilder newBuilder() { - return ConditionalExpressionBuilder.newBuilder(); - } - - /** - * @since 1.10 - */ - public static interface WhenBuilder { - - /** - * @param booleanExpression expression that yields in a boolean result, must not be {@literal null}. - * @return the {@link ThenBuilder} - */ - ThenBuilder when(DBObject booleanExpression); - - /** - * @param booleanField reference to a field holding a boolean value, must not be {@literal null}. - * @return the {@link ThenBuilder} - */ - ThenBuilder when(Field booleanField); - - /** - * @param booleanField name of a field holding a boolean value, must not be {@literal null}. - * @return the {@link ThenBuilder} - */ - ThenBuilder when(String booleanField); - - /** - * @param criteria criteria to evaluate, must not be {@literal null}. - * @return the {@link ThenBuilder} - */ - ThenBuilder when(CriteriaDefinition criteria); - } - - /** - * @since 1.10 - */ - public static interface ThenBuilder { - - /** - * @param value the value to be used if the condition evaluates {@literal true}. Can be a {@link DBObject}, a value - * that is supported by MongoDB or a value that can be converted to a MongoDB representation but must not - * be {@literal null}. - * @return the {@link OtherwiseBuilder} - */ - OtherwiseBuilder then(Object value); - } - - /** - * @since 1.10 - */ - public static interface OtherwiseBuilder { - - /** - * @param value the value to be used if the condition evaluates {@literal false}. Can be a {@link DBObject}, a value - * that is supported by MongoDB or a value that can be converted to a MongoDB representation but must not - * be {@literal null}. - * @return the {@link ConditionalOperator} - */ - ConditionalOperator otherwise(Object value); - } - - /** - * Builder for fluent {@link ConditionalOperator} creation. - * - * @author Mark Paluch - * @since 1.10 - */ - public static final class ConditionalExpressionBuilder implements WhenBuilder, ThenBuilder, OtherwiseBuilder { - - private Object condition; - private Object thenValue; - - private ConditionalExpressionBuilder() {} - - /** - * Creates a new builder for {@link ConditionalOperator}. - * - * @return never {@literal null}. - */ - public static ConditionalExpressionBuilder newBuilder() { - return new ConditionalExpressionBuilder(); - } - - /* - * (non-Javadoc) - * @see org.springframework.data.mongodb.core.aggregation.ConditionalOperator.WhenBuilder#when(com.mongodb.DBObject) - */ - @Override - public ConditionalExpressionBuilder when(DBObject booleanExpression) { - - Assert.notNull(booleanExpression, "'Boolean expression' must not be null!"); - - this.condition = booleanExpression; - return this; - } - - /* - * (non-Javadoc) - * @see org.springframework.data.mongodb.core.aggregation.ConditionalOperator.WhenBuilder#when(org.springframework.data.mongodb.core.query.CriteriaDefinition) - */ - @Override - public ThenBuilder when(CriteriaDefinition criteria) { - - Assert.notNull(criteria, "Criteria must not be null!"); - - this.condition = criteria; - return this; - } - - /* - * (non-Javadoc) - * @see org.springframework.data.mongodb.core.aggregation.ConditionalOperator.WhenBuilder#when(org.springframework.data.mongodb.core.aggregation.Field) - */ - @Override - public ThenBuilder when(Field booleanField) { - - Assert.notNull(booleanField, "Boolean field must not be null!"); - - this.condition = booleanField; - return this; - } - - /* - * (non-Javadoc) - * @see org.springframework.data.mongodb.core.aggregation.ConditionalOperator.WhenBuilder#when(java.lang.String) - */ - @Override - public ThenBuilder when(String booleanField) { - - Assert.hasText(booleanField, "Boolean field name must not be null or empty!"); - - this.condition = Fields.field(booleanField); - return this; - } - - /* - * (non-Javadoc) - * @see org.springframework.data.mongodb.core.aggregation.ConditionalOperator.ThenBuilder#then(java.lang.Object) - */ - @Override - public OtherwiseBuilder then(Object thenValue) { - - Assert.notNull(thenValue, "'Then-value' must not be null!"); - - this.thenValue = thenValue; - return this; - } - - /* - * (non-Javadoc) - * @see org.springframework.data.mongodb.core.aggregation.ConditionalOperator.OtherwiseBuilder#otherwise(java.lang.Object) - */ - @Override - public ConditionalOperator otherwise(Object otherwiseValue) { - - Assert.notNull(otherwiseValue, "'Otherwise-value' must not be null!"); - - return new ConditionalOperator(condition, thenValue, otherwiseValue); - } - } -} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ConditionalOperators.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ConditionalOperators.java new file mode 100644 index 0000000000..8208059b18 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ConditionalOperators.java @@ -0,0 +1,978 @@ +/* + * 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 java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.springframework.dao.InvalidDataAccessApiUsageException; +import org.springframework.data.mongodb.core.aggregation.ConditionalOperators.Cond.OtherwiseBuilder; +import org.springframework.data.mongodb.core.aggregation.ConditionalOperators.Cond.ThenBuilder; +import org.springframework.data.mongodb.core.aggregation.ConditionalOperators.Switch.CaseOperator; +import org.springframework.data.mongodb.core.query.CriteriaDefinition; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; + +import com.mongodb.BasicDBObject; +import com.mongodb.DBObject; + +/** + * Gateway to {@literal conditional expressions} that evaluate their argument expressions as booleans to a value. + * + * @author Mark Paluch + * @since 1.10 + */ +public class ConditionalOperators { + + /** + * Take the field referenced by given {@literal fieldReference}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static ConditionalOperatorFactory when(String fieldReference) { + return new ConditionalOperatorFactory(fieldReference); + } + + /** + * Take the value resulting from the given {@literal expression}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static ConditionalOperatorFactory when(AggregationExpression expression) { + return new ConditionalOperatorFactory(expression); + } + + /** + * Take the value resulting from the given {@literal criteriaDefinition}. + * + * @param criteriaDefinition must not be {@literal null}. + * @return + */ + public static ConditionalOperatorFactory when(CriteriaDefinition criteriaDefinition) { + return new ConditionalOperatorFactory(criteriaDefinition); + } + + /** + * Creates new {@link AggregationExpression} that evaluates an expression and returns the value of the expression if + * the expression evaluates to a non-null value. If the expression evaluates to a {@literal null} value, including + * instances of undefined values or missing fields, returns the value of the replacement expression. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static IfNull.ThenBuilder ifNull(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return IfNull.ifNull(fieldReference); + } + + /** + * Creates new {@link AggregationExpression} that evaluates an expression and returns the value of the expression if + * the expression evaluates to a non-null value. If the expression evaluates to a {@literal null} value, including + * instances of undefined values or missing fields, returns the value of the replacement expression. + * + * @param expression must not be {@literal null}. + * @return + */ + public static IfNull.ThenBuilder ifNull(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return IfNull.ifNull(expression); + } + + /** + * Creates new {@link AggregationExpression} that evaluates a series of {@link CaseOperator} expressions. When it + * finds an expression which evaluates to {@literal true}, {@code $switch} executes a specified expression and breaks + * out of the control flow. + * + * @param conditions must not be {@literal null}. + * @return + */ + public static Switch switchCases(CaseOperator... conditions) { + return Switch.switchCases(conditions); + } + + /** + * Creates new {@link AggregationExpression} that evaluates a series of {@link CaseOperator} expressions. When it + * finds an expression which evaluates to {@literal true}, {@code $switch} executes a specified expression and breaks + * out of the control flow. + * + * @param conditions must not be {@literal null}. + * @return + */ + public static Switch switchCases(List conditions) { + return Switch.switchCases(conditions); + } + + public static class ConditionalOperatorFactory { + + private final String fieldReference; + private final AggregationExpression expression; + private final CriteriaDefinition criteriaDefinition; + + /** + * Creates new {@link ConditionalOperatorFactory} for given {@literal fieldReference}. + * + * @param fieldReference must not be {@literal null}. + */ + public ConditionalOperatorFactory(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + + this.fieldReference = fieldReference; + this.expression = null; + this.criteriaDefinition = null; + } + + /** + * Creates new {@link ConditionalOperatorFactory} for given {@link AggregationExpression}. + * + * @param expression must not be {@literal null}. + */ + public ConditionalOperatorFactory(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + + this.fieldReference = null; + this.expression = expression; + this.criteriaDefinition = null; + } + + /** + * Creates new {@link ConditionalOperatorFactory} for given {@link CriteriaDefinition}. + * + * @param criteriaDefinition must not be {@literal null}. + */ + public ConditionalOperatorFactory(CriteriaDefinition criteriaDefinition) { + + Assert.notNull(criteriaDefinition, "CriteriaDefinition must not be null!"); + + this.fieldReference = null; + this.expression = null; + this.criteriaDefinition = criteriaDefinition; + } + + /** + * Creates new {@link AggregationExpression} that evaluates a boolean expression to return one of the two specified + * return expressions. + * + * @param value must not be {@literal null}. + * @return + */ + public OtherwiseBuilder then(Object value) { + + Assert.notNull(value, "Value must not be null!"); + return createThenBuilder().then(value); + } + + /** + * Creates new {@link AggregationExpression} that evaluates a boolean expression to return one of the two specified + * return expressions. + * + * @param expression must not be {@literal null}. + * @return + */ + public OtherwiseBuilder thenValueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return createThenBuilder().then(expression); + } + + /** + * Creates new {@link AggregationExpression} that evaluates a boolean expression to return one of the two specified + * return expressions. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public OtherwiseBuilder thenValueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return createThenBuilder().then(fieldReference); + } + + private ThenBuilder createThenBuilder() { + + if (usesFieldRef()) { + return Cond.newBuilder().when(fieldReference); + } + + return usesCriteriaDefinition() ? Cond.newBuilder().when(criteriaDefinition) : Cond.newBuilder().when(expression); + } + + private boolean usesFieldRef() { + return this.fieldReference != null; + } + + private boolean usesCriteriaDefinition() { + return this.criteriaDefinition != null; + } + } + + /** + * Encapsulates the aggregation framework {@code $ifNull} operator. Replacement values can be either {@link Field + * field references}, {@link AggregationExpression expressions}, values of simple MongoDB types or values that can be + * converted to a simple MongoDB type. + * + * @see https://docs.mongodb.com/manual/reference/operator/aggregation/ifNull/ + * @author Mark Paluch + */ + public static class IfNull implements AggregationExpression { + + private final Object condition; + private final Object value; + + private IfNull(Object condition, Object value) { + + this.condition = condition; + this.value = value; + } + + /** + * Creates new {@link IfNull}. + * + * @param fieldReference the field to check for a {@literal null} value, field reference must not be {@literal null} + * . + * @return + */ + public static ThenBuilder ifNull(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new IfNullOperatorBuilder().ifNull(fieldReference); + } + + /** + * Creates new {@link IfNull}. + * + * @param expression the expression to check for a {@literal null} value, field reference must not be + * {@literal null}. + * @return + */ + public static ThenBuilder ifNull(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new IfNullOperatorBuilder().ifNull(expression); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.AggregationExpression#toDbObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) + */ + @Override + public DBObject toDbObject(AggregationOperationContext context) { + + List list = new ArrayList(); + + if (condition instanceof Field) { + list.add(context.getReference((Field) condition).toString()); + } else if (condition instanceof AggregationExpression) { + list.add(((AggregationExpression) condition).toDbObject(context)); + } else { + list.add(condition); + } + + list.add(resolve(value, context)); + + return new BasicDBObject("$ifNull", list); + } + + private Object resolve(Object value, AggregationOperationContext context) { + + if (value instanceof Field) { + return context.getReference((Field) value).toString(); + } else if (value instanceof AggregationExpression) { + return ((AggregationExpression) value).toDbObject(context); + } else if (value instanceof DBObject) { + return value; + } + + return context.getMappedObject(new BasicDBObject("$set", value)).get("$set"); + } + + /** + * @author Mark Paluch + */ + public interface IfNullBuilder { + + /** + * @param fieldReference the field to check for a {@literal null} value, field reference must not be + * {@literal null}. + * @return the {@link ThenBuilder} + */ + ThenBuilder ifNull(String fieldReference); + + /** + * @param expression the expression to check for a {@literal null} value, field name must not be {@literal null} + * or empty. + * @return the {@link ThenBuilder} + */ + ThenBuilder ifNull(AggregationExpression expression); + } + + /** + * @author Mark Paluch + */ + public interface ThenBuilder { + + /** + * @param value the value to be used if the {@code $ifNull} condition evaluates {@literal true}. Can be a + * {@link DBObject}, a value that is supported by MongoDB or a value that can be converted to a MongoDB + * representation but must not be {@literal null}. + * @return + */ + IfNull then(Object value); + + /** + * @param fieldReference the field holding the replacement value, must not be {@literal null}. + * @return + */ + IfNull thenValueOf(String fieldReference); + + /** + * @param expression the expression yielding to the replacement value, must not be {@literal null}. + * @return + */ + IfNull thenValueOf(AggregationExpression expression); + } + + /** + * Builder for fluent {@link IfNull} creation. + * + * @author Mark Paluch + */ + static final class IfNullOperatorBuilder implements IfNullBuilder, ThenBuilder { + + private Object condition; + + private IfNullOperatorBuilder() {} + + /** + * Creates a new builder for {@link IfNull}. + * + * @return never {@literal null}. + */ + public static IfNullOperatorBuilder newBuilder() { + return new IfNullOperatorBuilder(); + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.ConditionalOperators.IfNull.IfNullBuilder#ifNull(java.lang.String) + */ + public ThenBuilder ifNull(String fieldReference) { + + Assert.hasText(fieldReference, "FieldReference name must not be null or empty!"); + this.condition = Fields.field(fieldReference); + return this; + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.ConditionalOperators.IfNull.IfNullBuilder#ifNull(org.springframework.data.mongodb.core.aggregation.AggregationExpression) + */ + @Override + public ThenBuilder ifNull(AggregationExpression expression) { + + Assert.notNull(expression, "AggregationExpression name must not be null or empty!"); + this.condition = expression; + return this; + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.ConditionalOperators.IfNull.ThenBuilder#then(java.lang.Object) + */ + public IfNull then(Object value) { + return new IfNull(condition, value); + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.ConditionalOperators.IfNull.ThenBuilder#thenValueOf(java.lang.String) + */ + public IfNull thenValueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new IfNull(condition, Fields.field(fieldReference)); + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.ConditionalOperators.IfNull.ThenBuilder#thenValueOf(org.springframework.data.mongodb.core.aggregation.AggregationExpression) + */ + public IfNull thenValueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new IfNull(condition, expression); + } + } + } + + /** + * {@link AggregationExpression} for {@code $switch}. + * + * @author Christoph Strobl + */ + public static class Switch extends AbstractAggregationExpression { + + private Switch(java.util.Map values) { + super(values); + } + + @Override + protected String getMongoMethod() { + return "$switch"; + } + + /** + * Creates new {@link Switch}. + * + * @param conditions must not be {@literal null}. + */ + public static Switch switchCases(CaseOperator... conditions) { + + Assert.notNull(conditions, "Conditions must not be null!"); + return switchCases(Arrays.asList(conditions)); + } + + /** + * Creates new {@link Switch}. + * + * @param conditions must not be {@literal null}. + */ + public static Switch switchCases(List conditions) { + + Assert.notNull(conditions, "Conditions must not be null!"); + return new Switch(Collections. singletonMap("branches", new ArrayList(conditions))); + } + + public Switch defaultTo(Object value) { + return new Switch(append("default", value)); + } + + /** + * Encapsulates the aggregation framework case document inside a {@code $switch}-operation. + */ + public static class CaseOperator implements AggregationExpression { + + private final AggregationExpression when; + private final Object then; + + private CaseOperator(AggregationExpression when, Object then) { + + this.when = when; + this.then = then; + } + + public static ThenBuilder when(final AggregationExpression condition) { + + Assert.notNull(condition, "Condition must not be null!"); + + return new ThenBuilder() { + + @Override + public CaseOperator then(Object value) { + + Assert.notNull(value, "Value must not be null!"); + return new CaseOperator(condition, value); + } + }; + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.AggregationExpression#toDbObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) + */ + @Override + public DBObject toDbObject(AggregationOperationContext context) { + + DBObject dbo = new BasicDBObject("case", when.toDbObject(context)); + + if (then instanceof AggregationExpression) { + dbo.put("then", ((AggregationExpression) then).toDbObject(context)); + } else if (then instanceof Field) { + dbo.put("then", context.getReference((Field) then).toString()); + } else { + dbo.put("then", then); + } + + return dbo; + } + + /** + * @author Christoph Strobl + */ + public interface ThenBuilder { + + /** + * Set the then {@literal value}. + * + * @param value must not be {@literal null}. + * @return + */ + CaseOperator then(Object value); + } + } + } + + /** + * Encapsulates the aggregation framework {@code $cond} operator. A {@link Cond} allows nested conditions + * {@code if-then[if-then-else]-else} using {@link Field}, {@link CriteriaDefinition}, {@link AggregationExpression} + * or a {@link DBObject custom} condition. Replacement values can be either {@link Field field references}, + * {@link AggregationExpression expressions}, values of simple MongoDB types or values that can be converted to a + * simple MongoDB type. + * + * @see https://docs.mongodb.com/manual/reference/operator/aggregation/cond/ + * @author Mark Paluch + * @author Christoph Strobl + */ + public static class Cond implements AggregationExpression { + + private final Object condition; + private final Object thenValue; + private final Object otherwiseValue; + + /** + * Creates a new {@link Cond} for a given {@link Field} and {@code then}/{@code otherwise} values. + * + * @param condition must not be {@literal null}. + * @param thenValue must not be {@literal null}. + * @param otherwiseValue must not be {@literal null}. + */ + private Cond(Field condition, Object thenValue, Object otherwiseValue) { + this((Object) condition, thenValue, otherwiseValue); + } + + /** + * Creates a new {@link Cond} for a given {@link CriteriaDefinition} and {@code then}/{@code otherwise} values. + * + * @param condition must not be {@literal null}. + * @param thenValue must not be {@literal null}. + * @param otherwiseValue must not be {@literal null}. + */ + private Cond(CriteriaDefinition condition, Object thenValue, Object otherwiseValue) { + this((Object) condition, thenValue, otherwiseValue); + } + + private Cond(Object condition, Object thenValue, Object otherwiseValue) { + + Assert.notNull(condition, "Condition must not be null!"); + Assert.notNull(thenValue, "Then value must not be null!"); + Assert.notNull(otherwiseValue, "Otherwise value must not be null!"); + + assertNotBuilder(condition, "Condition"); + assertNotBuilder(thenValue, "Then value"); + assertNotBuilder(otherwiseValue, "Otherwise value"); + + this.condition = condition; + this.thenValue = thenValue; + this.otherwiseValue = otherwiseValue; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.AggregationExpression#toDbObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) + */ + @Override + public DBObject toDbObject(AggregationOperationContext context) { + + BasicDBObject condObject = new BasicDBObject(); + + condObject.append("if", resolveCriteria(context, condition)); + condObject.append("then", resolveValue(context, thenValue)); + condObject.append("else", resolveValue(context, otherwiseValue)); + + return new BasicDBObject("$cond", condObject); + } + + private Object resolveValue(AggregationOperationContext context, Object value) { + + if (value instanceof DBObject || value instanceof Field) { + return resolve(context, value); + } + + if (value instanceof AggregationExpression) { + return ((AggregationExpression) value).toDbObject(context); + } + + return context.getMappedObject(new BasicDBObject("$set", value)).get("$set"); + } + + private Object resolveCriteria(AggregationOperationContext context, Object value) { + + if (value instanceof DBObject || value instanceof Field) { + return resolve(context, value); + } + + if (value instanceof AggregationExpression) { + return ((AggregationExpression) value).toDbObject(context); + } + + if (value instanceof CriteriaDefinition) { + + DBObject mappedObject = context.getMappedObject(((CriteriaDefinition) value).getCriteriaObject()); + List clauses = new ArrayList(); + + clauses.addAll(getClauses(context, mappedObject)); + + return clauses.size() == 1 ? clauses.get(0) : clauses; + } + + throw new InvalidDataAccessApiUsageException( + String.format("Invalid value in condition. Supported: DBObject, Field references, Criteria, got: %s", value)); + } + + private List getClauses(AggregationOperationContext context, DBObject mappedObject) { + + List clauses = new ArrayList(); + + for (String key : mappedObject.keySet()) { + + Object predicate = mappedObject.get(key); + clauses.addAll(getClauses(context, key, predicate)); + } + + return clauses; + } + + private List getClauses(AggregationOperationContext context, String key, Object predicate) { + + List clauses = new ArrayList(); + + if (predicate instanceof List) { + + List args = new ArrayList(); + for (Object clause : (List) predicate) { + if (clause instanceof DBObject) { + args.addAll(getClauses(context, (DBObject) clause)); + } + } + + clauses.add(new BasicDBObject(key, args)); + + } else if (predicate instanceof DBObject) { + + DBObject nested = (DBObject) predicate; + + for (String s : nested.keySet()) { + + if (!isKeyword(s)) { + continue; + } + + List args = new ArrayList(); + args.add("$" + key); + args.add(nested.get(s)); + clauses.add(new BasicDBObject(s, args)); + } + + } else if (!isKeyword(key)) { + + List args = new ArrayList(); + args.add("$" + key); + args.add(predicate); + clauses.add(new BasicDBObject("$eq", args)); + } + + return clauses; + } + + /** + * Returns whether the given {@link String} is a MongoDB keyword. + * + * @param candidate + * @return + */ + private boolean isKeyword(String candidate) { + return candidate.startsWith("$"); + } + + private Object resolve(AggregationOperationContext context, Object value) { + + if (value instanceof DBObject) { + return context.getMappedObject((DBObject) value); + } + + return context.getReference((Field) value).toString(); + } + + private void assertNotBuilder(Object toCheck, String name) { + Assert.isTrue(!ClassUtils.isAssignableValue(ConditionalExpressionBuilder.class, toCheck), + String.format("%s must not be of type %s", name, ConditionalExpressionBuilder.class.getSimpleName())); + } + + /** + * Get a builder that allows fluent creation of {@link Cond}. + * + * @return never {@literal null}. + */ + public static WhenBuilder newBuilder() { + return ConditionalExpressionBuilder.newBuilder(); + } + + /** + * Start creating new {@link Cond} by providing the boolean expression used in {@code if}. + * + * @param booleanExpression must not be {@literal null}. + * @return never {@literal null}. + */ + public static ThenBuilder when(DBObject booleanExpression) { + return ConditionalExpressionBuilder.newBuilder().when(booleanExpression); + } + + /** + * Start creating new {@link Cond} by providing the {@link AggregationExpression} used in {@code if}. + * + * @param expression expression that yields in a boolean result, must not be {@literal null}. + * @return never {@literal null}. + */ + public static ThenBuilder when(AggregationExpression expression) { + return ConditionalExpressionBuilder.newBuilder().when(expression); + } + + /** + * Start creating new {@link Cond} by providing the field reference used in {@code if}. + * + * @param booleanField name of a field holding a boolean value, must not be {@literal null}. + * @return never {@literal null}. + */ + public static ThenBuilder when(String booleanField) { + return ConditionalExpressionBuilder.newBuilder().when(booleanField); + } + + /** + * Start creating new {@link Cond} by providing the {@link CriteriaDefinition} used in {@code if}. + * + * @param criteria criteria to evaluate, must not be {@literal null}. + * @return the {@link ThenBuilder} + */ + public static ThenBuilder when(CriteriaDefinition criteria) { + return ConditionalExpressionBuilder.newBuilder().when(criteria); + } + + /** + * @author Mark Paluch + */ + public interface WhenBuilder { + + /** + * @param booleanExpression expression that yields in a boolean result, must not be {@literal null}. + * @return the {@link ThenBuilder} + */ + ThenBuilder when(DBObject booleanExpression); + + /** + * @param expression expression that yields in a boolean result, must not be {@literal null}. + * @return the {@link ThenBuilder} + */ + ThenBuilder when(AggregationExpression expression); + + /** + * @param booleanField name of a field holding a boolean value, must not be {@literal null}. + * @return the {@link ThenBuilder} + */ + ThenBuilder when(String booleanField); + + /** + * @param criteria criteria to evaluate, must not be {@literal null}. + * @return the {@link ThenBuilder} + */ + ThenBuilder when(CriteriaDefinition criteria); + } + + /** + * @author Mark Paluch + */ + public interface ThenBuilder { + + /** + * @param value the value to be used if the condition evaluates {@literal true}. Can be a {@link DBObject}, a + * value that is supported by MongoDB or a value that can be converted to a MongoDB representation but + * must not be {@literal null}. + * @return the {@link OtherwiseBuilder} + */ + OtherwiseBuilder then(Object value); + + /** + * @param fieldReference must not be {@literal null}. + * @return the {@link OtherwiseBuilder} + */ + OtherwiseBuilder thenValueOf(String fieldReference); + + /** + * @param expression must not be {@literal null}. + * @return the {@link OtherwiseBuilder} + */ + OtherwiseBuilder thenValueOf(AggregationExpression expression); + } + + /** + * @author Mark Paluch + */ + public interface OtherwiseBuilder { + + /** + * @param value the value to be used if the condition evaluates {@literal false}. Can be a {@link DBObject}, a + * value that is supported by MongoDB or a value that can be converted to a MongoDB representation but + * must not be {@literal null}. + * @return the {@link Cond} + */ + Cond otherwise(Object value); + + /** + * @param fieldReference must not be {@literal null}. + * @return the {@link Cond} + */ + Cond otherwiseValueOf(String fieldReference); + + /** + * @param expression must not be {@literal null}. + * @return the {@link Cond} + */ + Cond otherwiseValueOf(AggregationExpression expression); + } + + /** + * Builder for fluent {@link Cond} creation. + * + * @author Mark Paluch + */ + static class ConditionalExpressionBuilder implements WhenBuilder, ThenBuilder, OtherwiseBuilder { + + private Object condition; + private Object thenValue; + + private ConditionalExpressionBuilder() {} + + /** + * Creates a new builder for {@link Cond}. + * + * @return never {@literal null}. + */ + public static ConditionalExpressionBuilder newBuilder() { + return new ConditionalExpressionBuilder(); + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.ConditionalOperators.Cond.WhenBuilder#when(com.mongodb.DBObject) + */ + @Override + public ConditionalExpressionBuilder when(DBObject booleanExpression) { + + Assert.notNull(booleanExpression, "'Boolean expression' must not be null!"); + + this.condition = booleanExpression; + return this; + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.ConditionalOperators.Cond.WhenBuilder#when(org.springframework.data.mongodb.core.query.CriteriaDefinition) + */ + @Override + public ThenBuilder when(CriteriaDefinition criteria) { + + Assert.notNull(criteria, "Criteria must not be null!"); + this.condition = criteria; + return this; + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.ConditionalOperators.Cond.WhenBuilder#when(org.springframework.data.mongodb.core.aggregation.AggregationExpression) + */ + @Override + public ThenBuilder when(AggregationExpression expression) { + + Assert.notNull(expression, "AggregationExpression field must not be null!"); + this.condition = expression; + return this; + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.ConditionalOperators.Cond.WhenBuilder#when(java.lang.String) + */ + @Override + public ThenBuilder when(String booleanField) { + + Assert.hasText(booleanField, "Boolean field name must not be null or empty!"); + this.condition = Fields.field(booleanField); + return this; + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.ConditionalOperators.Cond.ThenBuilder#then(java.lang.Object) + */ + @Override + public OtherwiseBuilder then(Object thenValue) { + + Assert.notNull(thenValue, "Then-value must not be null!"); + this.thenValue = thenValue; + return this; + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.ConditionalOperators.Cond.ThenBuilder#thenValueOf(java.lang.String) + */ + @Override + public OtherwiseBuilder thenValueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + this.thenValue = Fields.field(fieldReference); + return this; + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.ConditionalOperators.Cond.ThenBuilder#thenValueOf(org.springframework.data.mongodb.core.aggregation.AggregationExpression) + */ + @Override + public OtherwiseBuilder thenValueOf(AggregationExpression expression) { + + Assert.notNull(expression, "AggregationExpression must not be null!"); + this.thenValue = expression; + return this; + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.ConditionalOperators.Cond.OtherwiseBuilder#otherwise(java.lang.Object) + */ + @Override + public Cond otherwise(Object otherwiseValue) { + + Assert.notNull(otherwiseValue, "Value must not be null!"); + return new Cond(condition, thenValue, otherwiseValue); + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.ConditionalOperators.Cond.OtherwiseBuilder#otherwiseValueOf(java.lang.String) + */ + @Override + public Cond otherwiseValueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Cond(condition, thenValue, Fields.field(fieldReference)); + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.ConditionalOperators.Cond.OtherwiseBuilder#otherwiseValueOf(org.springframework.data.mongodb.core.aggregation.AggregationExpression) + */ + @Override + public Cond otherwiseValueOf(AggregationExpression expression) { + + Assert.notNull(expression, "AggregationExpression must not be null!"); + return new Cond(condition, thenValue, expression); + } + } + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/CountOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/CountOperation.java new file mode 100644 index 0000000000..c99c1521e0 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/CountOperation.java @@ -0,0 +1,82 @@ +/* + * 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.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 $count}-operation.
        + * We recommend to use the static factory method {@link Aggregation#count()} instead of creating instances of this class + * directly. + * + * @see https://docs.mongodb.com/manual/reference/operator/aggregation/count/ + * @author Mark Paluch + * @since 1.10 + */ +public class CountOperation implements FieldsExposingAggregationOperation { + + private final String fieldName; + + /** + * Creates a new {@link CountOperation} given the {@link fieldName} field name. + * + * @param asFieldName must not be {@literal null} or empty. + */ + public CountOperation(String fieldName) { + + Assert.hasText(fieldName, "Field name must not be null or empty!"); + this.fieldName = fieldName; + } + + /* (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("$count", fieldName); + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.FieldsExposingAggregationOperation#getFields() + */ + @Override + public ExposedFields getFields() { + return ExposedFields.from(new ExposedField(fieldName, true)); + } + + /** + * Builder for {@link CountOperation}. + * + * @author Mark Paluch + */ + public static class CountOperationBuilder { + + /** + * Returns the finally to be applied {@link CountOperation} with the given alias. + * + * @param fieldName must not be {@literal null} or empty. + * @return + */ + public CountOperation as(String fieldName) { + return new CountOperation(fieldName); + } + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/DataTypeOperators.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/DataTypeOperators.java new file mode 100644 index 0000000000..9a83753a17 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/DataTypeOperators.java @@ -0,0 +1,67 @@ +/* + * 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.springframework.util.Assert; + +/** + * Gateway to {@literal data type} expressions. + * + * @author Christoph Strobl + * @since 1.10 + * @soundtrack Clawfinger - Catch Me + */ +public class DataTypeOperators { + + /** + * Return the BSON data type of the given {@literal field}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Type typeOf(String fieldReference) { + return Type.typeOf(fieldReference); + } + + /** + * {@link AggregationExpression} for {@code $type}. + * + * @author Christoph Strobl + */ + public static class Type extends AbstractAggregationExpression { + + private Type(Object value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$type"; + } + + /** + * Creates new {@link Type}. + * + * @param field must not be {@literal null}. + * @return + */ + public static Type typeOf(String field) { + + Assert.notNull(field, "Field must not be null!"); + return new Type(Fields.field(field)); + } + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/DateOperators.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/DateOperators.java new file mode 100644 index 0000000000..4b501d782d --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/DateOperators.java @@ -0,0 +1,837 @@ +/* + * 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 java.util.LinkedHashMap; + +import org.springframework.data.mongodb.core.aggregation.ArithmeticOperators.ArithmeticOperatorFactory; +import org.springframework.util.Assert; + +/** + * Gateway to {@literal Date} aggregation operations. + * + * @author Christoph Strobl + * @since 1.10 + */ +public class DateOperators { + + /** + * Take the date referenced by given {@literal fieldReference}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static DateOperatorFactory dateOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new DateOperatorFactory(fieldReference); + } + + /** + * Take the date resulting from the given {@link AggregationExpression}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static DateOperatorFactory dateOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new DateOperatorFactory(expression); + } + + /** + * @author Christoph Strobl + */ + public static class DateOperatorFactory { + + private final String fieldReference; + private final AggregationExpression expression; + + /** + * Creates new {@link ArithmeticOperatorFactory} for given {@literal fieldReference}. + * + * @param fieldReference must not be {@literal null}. + */ + public DateOperatorFactory(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + this.fieldReference = fieldReference; + this.expression = null; + } + + /** + * Creates new {@link ArithmeticOperatorFactory} for given {@link AggregationExpression}. + * + * @param expression must not be {@literal null}. + */ + public DateOperatorFactory(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + this.fieldReference = null; + this.expression = expression; + } + + /** + * Creates new {@link AggregationExpression} that returns the day of the year for a date as a number between 1 and + * 366. + * + * @return + */ + public DayOfYear dayOfYear() { + return usesFieldRef() ? DayOfYear.dayOfYear(fieldReference) : DayOfYear.dayOfYear(expression); + } + + /** + * Creates new {@link AggregationExpression} that returns the day of the month for a date as a number between 1 and + * 31. + * + * @return + */ + public DayOfMonth dayOfMonth() { + return usesFieldRef() ? DayOfMonth.dayOfMonth(fieldReference) : DayOfMonth.dayOfMonth(expression); + } + + /** + * Creates new {@link AggregationExpression} that returns the day of the week for a date as a number between 1 + * (Sunday) and 7 (Saturday). + * + * @return + */ + public DayOfWeek dayOfWeek() { + return usesFieldRef() ? DayOfWeek.dayOfWeek(fieldReference) : DayOfWeek.dayOfWeek(expression); + } + + /** + * Creates new {@link AggregationExpression} that returns the year portion of a date. + * + * @return + */ + public Year year() { + return usesFieldRef() ? Year.yearOf(fieldReference) : Year.yearOf(expression); + } + + /** + * Creates new {@link AggregationExpression} that returns the month of a date as a number between 1 and 12. + * + * @return + */ + public Month month() { + return usesFieldRef() ? Month.monthOf(fieldReference) : Month.monthOf(expression); + } + + /** + * Creates new {@link AggregationExpression} that returns the week of the year for a date as a number between 0 and + * 53. + * + * @return + */ + public Week week() { + return usesFieldRef() ? Week.weekOf(fieldReference) : Week.weekOf(expression); + } + + /** + * Creates new {@link AggregationExpression} that returns the hour portion of a date as a number between 0 and 23. + * + * @return + */ + public Hour hour() { + return usesFieldRef() ? Hour.hourOf(fieldReference) : Hour.hourOf(expression); + } + + /** + * Creates new {@link AggregationExpression} that returns the minute portion of a date as a number between 0 and 59. + * + * @return + */ + public Minute minute() { + return usesFieldRef() ? Minute.minuteOf(fieldReference) : Minute.minuteOf(expression); + } + + /** + * Creates new {@link AggregationExpression} that returns the second portion of a date as a number between 0 and 59, + * but can be 60 to account for leap seconds. + * + * @return + */ + public Second second() { + return usesFieldRef() ? Second.secondOf(fieldReference) : Second.secondOf(expression); + } + + /** + * Creates new {@link AggregationExpression} that returns the millisecond portion of a date as an integer between 0 + * and 999. + * + * @return + */ + public Millisecond millisecond() { + return usesFieldRef() ? Millisecond.millisecondOf(fieldReference) : Millisecond.millisecondOf(expression); + } + + /** + * Creates new {@link AggregationExpression} that converts a date object to a string according to a user-specified + * {@literal format}. + * + * @param format must not be {@literal null}. + * @return + */ + public DateToString toString(String format) { + return (usesFieldRef() ? DateToString.dateOf(fieldReference) : DateToString.dateOf(expression)).toString(format); + } + + /** + * Creates new {@link AggregationExpression} that returns the weekday number in ISO 8601 format, ranging from 1 (for + * Monday) to 7 (for Sunday). + * + * @return + */ + public IsoDayOfWeek isoDayOfWeek() { + return usesFieldRef() ? IsoDayOfWeek.isoDayOfWeek(fieldReference) : IsoDayOfWeek.isoDayOfWeek(expression); + } + + /** + * Creates new {@link AggregationExpression} that returns the week number in ISO 8601 format, ranging from 1 to 53. + * + * @return + */ + public IsoWeek isoWeek() { + return usesFieldRef() ? IsoWeek.isoWeekOf(fieldReference) : IsoWeek.isoWeekOf(expression); + } + + /** + * Creates new {@link AggregationExpression} that returns the year number in ISO 8601 format. + * + * @return + */ + public IsoWeekYear isoWeekYear() { + return usesFieldRef() ? IsoWeekYear.isoWeekYearOf(fieldReference) : IsoWeekYear.isoWeekYearOf(expression); + } + + private boolean usesFieldRef() { + return fieldReference != null; + } + } + + /** + * {@link AggregationExpression} for {@code $dayOfYear}. + * + * @author Christoph Strobl + */ + public static class DayOfYear extends AbstractAggregationExpression { + + private DayOfYear(Object value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$dayOfYear"; + } + + /** + * Creates new {@link DayOfYear}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static DayOfYear dayOfYear(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new DayOfYear(Fields.field(fieldReference)); + } + + /** + * Creates new {@link DayOfYear}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static DayOfYear dayOfYear(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new DayOfYear(expression); + } + } + + /** + * {@link AggregationExpression} for {@code $dayOfMonth}. + * + * @author Christoph Strobl + */ + public static class DayOfMonth extends AbstractAggregationExpression { + + private DayOfMonth(Object value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$dayOfMonth"; + } + + /** + * Creates new {@link DayOfMonth}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static DayOfMonth dayOfMonth(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new DayOfMonth(Fields.field(fieldReference)); + } + + /** + * Creates new {@link DayOfMonth}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static DayOfMonth dayOfMonth(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new DayOfMonth(expression); + } + } + + /** + * {@link AggregationExpression} for {@code $dayOfWeek}. + * + * @author Christoph Strobl + */ + public static class DayOfWeek extends AbstractAggregationExpression { + + private DayOfWeek(Object value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$dayOfWeek"; + } + + /** + * Creates new {@link DayOfWeek}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static DayOfWeek dayOfWeek(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new DayOfWeek(Fields.field(fieldReference)); + } + + /** + * Creates new {@link DayOfWeek}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static DayOfWeek dayOfWeek(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new DayOfWeek(expression); + } + } + + /** + * {@link AggregationExpression} for {@code $year}. + * + * @author Christoph Strobl + */ + public static class Year extends AbstractAggregationExpression { + + private Year(Object value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$year"; + } + + /** + * Creates new {@link Year}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Year yearOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Year(Fields.field(fieldReference)); + } + + /** + * Creates new {@link Year}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Year yearOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Year(expression); + } + } + + /** + * {@link AggregationExpression} for {@code $month}. + * + * @author Christoph Strobl + */ + public static class Month extends AbstractAggregationExpression { + + private Month(Object value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$month"; + } + + /** + * Creates new {@link Month}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Month monthOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Month(Fields.field(fieldReference)); + } + + /** + * Creates new {@link Month}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Month monthOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Month(expression); + } + } + + /** + * {@link AggregationExpression} for {@code $week}. + * + * @author Christoph Strobl + */ + public static class Week extends AbstractAggregationExpression { + + private Week(Object value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$week"; + } + + /** + * Creates new {@link Week}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Week weekOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Week(Fields.field(fieldReference)); + } + + /** + * Creates new {@link Week}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Week weekOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Week(expression); + } + } + + /** + * {@link AggregationExpression} for {@code $hour}. + * + * @author Christoph Strobl + */ + public static class Hour extends AbstractAggregationExpression { + + private Hour(Object value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$hour"; + } + + /** + * Creates new {@link Hour}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Hour hourOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Hour(Fields.field(fieldReference)); + } + + /** + * Creates new {@link Hour}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Hour hourOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Hour(expression); + } + } + + /** + * {@link AggregationExpression} for {@code $minute}. + * + * @author Christoph Strobl + */ + public static class Minute extends AbstractAggregationExpression { + + private Minute(Object value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$minute"; + } + + /** + * Creates new {@link Minute}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Minute minuteOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Minute(Fields.field(fieldReference)); + } + + /** + * Creates new {@link Minute}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Minute minuteOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Minute(expression); + } + } + + /** + * {@link AggregationExpression} for {@code $second}. + * + * @author Christoph Strobl + */ + public static class Second extends AbstractAggregationExpression { + + private Second(Object value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$second"; + } + + /** + * Creates new {@link Second}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Second secondOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Second(Fields.field(fieldReference)); + } + + /** + * Creates new {@link Second}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Second secondOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Second(expression); + } + } + + /** + * {@link AggregationExpression} for {@code $millisecond}. + * + * @author Christoph Strobl + */ + public static class Millisecond extends AbstractAggregationExpression { + + private Millisecond(Object value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$millisecond"; + } + + /** + * Creates new {@link Millisecond}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Millisecond millisecondOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Millisecond(Fields.field(fieldReference)); + } + + /** + * Creates new {@link Millisecond}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Millisecond millisecondOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Millisecond(expression); + } + } + + /** + * {@link AggregationExpression} for {@code $dateToString}. + * + * @author Christoph Strobl + */ + public static class DateToString extends AbstractAggregationExpression { + + private DateToString(Object value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$dateToString"; + } + + /** + * Creates new {@link FormatBuilder} allowing to define the date format to apply. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static FormatBuilder dateOf(final String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + + return new FormatBuilder() { + + @Override + public DateToString toString(String format) { + + Assert.notNull(format, "Format must not be null!"); + return new DateToString(argumentMap(Fields.field(fieldReference), format)); + } + }; + } + + /** + * Creates new {@link FormatBuilder} allowing to define the date format to apply. + * + * @param expression must not be {@literal null}. + * @return + */ + public static FormatBuilder dateOf(final AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + + return new FormatBuilder() { + + @Override + public DateToString toString(String format) { + + Assert.notNull(format, "Format must not be null!"); + return new DateToString(argumentMap(expression, format)); + } + }; + } + + private static java.util.Map argumentMap(Object date, String format) { + + java.util.Map args = new LinkedHashMap(2); + args.put("format", format); + args.put("date", date); + return args; + } + + public interface FormatBuilder { + + /** + * Creates new {@link DateToString} with all previously added arguments appending the given one. + * + * @param format must not be {@literal null}. + * @return + */ + DateToString toString(String format); + } + } + + /** + * {@link AggregationExpression} for {@code $isoDayOfWeek}. + * + * @author Christoph Strobl + */ + public static class IsoDayOfWeek extends AbstractAggregationExpression { + + private IsoDayOfWeek(Object value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$isoDayOfWeek"; + } + + /** + * Creates new {@link IsoDayOfWeek}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static IsoDayOfWeek isoDayOfWeek(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new IsoDayOfWeek(Fields.field(fieldReference)); + } + + /** + * Creates new {@link IsoDayOfWeek}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static IsoDayOfWeek isoDayOfWeek(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new IsoDayOfWeek(expression); + } + } + + /** + * {@link AggregationExpression} for {@code $isoWeek}. + * + * @author Christoph Strobl + */ + public static class IsoWeek extends AbstractAggregationExpression { + + private IsoWeek(Object value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$isoWeek"; + } + + /** + * Creates new {@link IsoWeek}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static IsoWeek isoWeekOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new IsoWeek(Fields.field(fieldReference)); + } + + /** + * Creates new {@link IsoWeek}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static IsoWeek isoWeekOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new IsoWeek(expression); + } + } + + /** + * {@link AggregationExpression} for {@code $isoWeekYear}. + * + * @author Christoph Strobl + */ + public static class IsoWeekYear extends AbstractAggregationExpression { + + private IsoWeekYear(Object value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$isoWeekYear"; + } + + /** + * Creates new {@link IsoWeekYear}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static IsoWeekYear isoWeekYearOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new IsoWeekYear(Fields.field(fieldReference)); + } + + /** + * Creates new {@link Millisecond}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static IsoWeekYear isoWeekYearOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new IsoWeekYear(expression); + } + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ExposedFields.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ExposedFields.java index 02537fcbd7..ce51f40624 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ExposedFields.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ExposedFields.java @@ -22,8 +22,10 @@ import java.util.List; import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExposedField; +import org.springframework.data.mongodb.core.aggregation.Fields.AggregationField; import org.springframework.util.Assert; import org.springframework.util.CompositeIterator; +import org.springframework.util.ObjectUtils; /** * Value object to capture the fields exposed by an {@link AggregationOperation}. @@ -104,7 +106,7 @@ private static ExposedFields createFields(Fields fields, boolean synthetic) { result.add(new ExposedField(field, synthetic)); } - return ExposedFields.from(result); + return from(result); } /** @@ -336,12 +338,36 @@ public int hashCode() { } } + /** + * A reference to an {@link ExposedField}. + * + * @author Christoph Strobl + * @since 1.10 + */ + interface FieldReference { + + /** + * Returns the raw, unqualified reference, i.e. the field reference without a {@literal $} prefix. + * + * @return + */ + String getRaw(); + + /** + * Returns the reference value for the given field reference. Will return 1 for a synthetic, unaliased field or the + * raw rendering of the reference otherwise. + * + * @return + */ + Object getReferenceValue(); + } + /** * A reference to an {@link ExposedField}. * * @author Oliver Gierke */ - static class FieldReference { + static class DirectFieldReference implements FieldReference { private final ExposedField field; @@ -350,17 +376,16 @@ static class FieldReference { * * @param field must not be {@literal null}. */ - public FieldReference(ExposedField field) { + public DirectFieldReference(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 + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.ExposedFields.FieldReference#getRaw() */ public String getRaw() { @@ -368,11 +393,9 @@ public String getRaw() { return field.synthetic ? target : String.format("%s.%s", Fields.UNDERSCORE_ID, target); } - /** - * Returns the reference value for the given field reference. Will return 1 for a synthetic, unaliased field or the - * raw rendering of the reference otherwise. - * - * @return + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.ExposedFields.FieldReference#getReferenceValue() */ public Object getReferenceValue() { return field.synthetic && !field.isAliased() ? 1 : toString(); @@ -384,6 +407,11 @@ public Object getReferenceValue() { */ @Override public String toString() { + + if(getRaw().startsWith("$")) { + return getRaw(); + } + return String.format("$%s", getRaw()); } @@ -398,11 +426,11 @@ public boolean equals(Object obj) { return true; } - if (!(obj instanceof FieldReference)) { + if (!(obj instanceof DirectFieldReference)) { return false; } - FieldReference that = (FieldReference) obj; + DirectFieldReference that = (DirectFieldReference) obj; return this.field.equals(that.field); } @@ -416,4 +444,78 @@ public int hashCode() { return field.hashCode(); } } + + /** + * A {@link FieldReference} to a {@link Field} used within a nested {@link AggregationExpression}. + * + * @author Christoph Strobl + * @since 1.10 + */ + static class ExpressionFieldReference implements FieldReference { + + private FieldReference delegate; + + /** + * Creates a new {@link FieldReference} for the given {@link ExposedField}. + * + * @param field must not be {@literal null}. + */ + public ExpressionFieldReference(FieldReference field) { + delegate = field; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.ExposedFields.FieldReference#getRaw() + */ + @Override + public String getRaw() { + return delegate.getRaw(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.ExposedFields.FieldReference#getReferenceValue() + */ + @Override + public Object getReferenceValue() { + return delegate.getReferenceValue(); + } + + @Override + public String toString() { + + String fieldRef = delegate.toString(); + + if (fieldRef.startsWith("$$")) { + return fieldRef; + } + + if (fieldRef.startsWith("$")) { + return "$" + fieldRef; + } + + return fieldRef; + } + + @Override + public boolean equals(Object obj) { + + if (this == obj) { + return true; + } + + if (!(obj instanceof ExpressionFieldReference)) { + return false; + } + + ExpressionFieldReference that = (ExpressionFieldReference) obj; + return ObjectUtils.nullSafeEquals(this.delegate, that.delegate); + } + + @Override + public int hashCode() { + return delegate.hashCode(); + } + } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ExposedFieldsAggregationOperationContext.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ExposedFieldsAggregationOperationContext.java index e4c11ae541..9f2f8ba822 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ExposedFieldsAggregationOperationContext.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ExposedFieldsAggregationOperationContext.java @@ -16,6 +16,7 @@ package org.springframework.data.mongodb.core.aggregation; import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExposedField; +import org.springframework.data.mongodb.core.aggregation.ExposedFields.DirectFieldReference; import org.springframework.data.mongodb.core.aggregation.ExposedFields.FieldReference; import org.springframework.util.Assert; @@ -112,10 +113,10 @@ protected FieldReference resolveExposedField(Field field, String name) { if (field != null) { // we return a FieldReference to the given field directly to make sure that we reference the proper alias here. - return new FieldReference(new ExposedField(field, exposedField.isSynthetic())); + return new DirectFieldReference(new ExposedField(field, exposedField.isSynthetic())); } - return new FieldReference(exposedField); + return new DirectFieldReference(exposedField); } if (name.contains(".")) { @@ -126,7 +127,7 @@ protected FieldReference resolveExposedField(Field field, String name) { if (rootField != null) { // We have to synthetic to true, in order to render the field-name as is. - return new FieldReference(new ExposedField(name, true)); + return new DirectFieldReference(new ExposedField(name, true)); } } return null; diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/FacetOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/FacetOperation.java new file mode 100644 index 0000000000..17d9e8b826 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/FacetOperation.java @@ -0,0 +1,228 @@ +/* + * Copyright 2016-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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.List; + +import org.springframework.data.mongodb.core.aggregation.BucketOperationSupport.Output; +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 $facet}-operation.
        + * Facet of {@link AggregationOperation}s to be used in an {@link Aggregation}. Processes multiple + * {@link AggregationOperation} pipelines within a single stage on the same set of input documents. Each sub-pipeline + * has its own field in the output document where its results are stored as an array of documents. + * {@link FacetOperation} enables various aggregations on the same set of input documents, without needing to retrieve + * the input documents multiple times.
        + * As of MongoDB 3.4, {@link FacetOperation} cannot be used with nested pipelines containing {@link GeoNearOperation}, + * {@link OutOperation} and {@link FacetOperation}.
        + * We recommend to use the static factory method {@link Aggregation#facet()} instead of creating instances of this class + * directly. + * + * @author Mark Paluch + * @author Christoph Strobl + * @since 1.10 + * @see MongoDB Aggregation Framework: $facet + */ +public class FacetOperation implements FieldsExposingAggregationOperation { + + /** + * Empty (initial) {@link FacetOperation}. + */ + public static final FacetOperation EMPTY = new FacetOperation(); + + private final Facets facets; + + /** + * Creates a new {@link FacetOperation}. + */ + public FacetOperation() { + this(Facets.EMPTY); + } + + private FacetOperation(Facets facets) { + this.facets = facets; + } + + /** + * Creates a new {@link FacetOperationBuilder} to append a new facet using {@literal operations}.
        + * {@link FacetOperationBuilder} takes a pipeline of {@link AggregationOperation} to categorize documents into a + * single facet. + * + * @param operations must not be {@literal null} or empty. + * @return + */ + public FacetOperationBuilder and(AggregationOperation... operations) { + + Assert.notNull(operations, "AggregationOperations must not be null!"); + Assert.notEmpty(operations, "AggregationOperations must not be empty!"); + + return new FacetOperationBuilder(facets, Arrays.asList(operations)); + } + + /* (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("$facet", facets.toDBObject(context)); + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.FieldsExposingAggregationOperation#getFields() + */ + @Override + public ExposedFields getFields() { + return facets.asExposedFields(); + } + + /** + * Builder for {@link FacetOperation} by adding existing and the new pipeline of {@link AggregationOperation} to the + * new {@link FacetOperation}. + * + * @author Mark Paluch + */ + public static class FacetOperationBuilder { + + private final Facets current; + private final List operations; + + private FacetOperationBuilder(Facets current, List operations) { + this.current = current; + this.operations = operations; + } + + /** + * Creates a new {@link FacetOperation} that contains the configured pipeline of {@link AggregationOperation} + * exposed as {@literal fieldName} in the resulting facet document. + * + * @param fieldName must not be {@literal null} or empty. + * @return + */ + public FacetOperation as(String fieldName) { + + Assert.hasText(fieldName, "FieldName must not be null or empty!"); + + return new FacetOperation(current.and(fieldName, operations)); + } + } + + /** + * Encapsulates multiple {@link Facet}s + * + * @author Mark Paluch + */ + private static class Facets { + + private static final Facets EMPTY = new Facets(Collections. emptyList()); + + private List facets; + + /** + * Creates a new {@link Facets} given {@link List} of {@link Facet}. + * + * @param facets + */ + private Facets(List facets) { + this.facets = facets; + } + + /** + * @return the {@link ExposedFields} derived from {@link Output}. + */ + ExposedFields asExposedFields() { + + ExposedFields fields = ExposedFields.from(); + + for (Facet facet : facets) { + fields = fields.and(facet.getExposedField()); + } + + return fields; + } + + DBObject toDBObject(AggregationOperationContext context) { + + DBObject dbObject = new BasicDBObject(facets.size()); + + for (Facet facet : facets) { + dbObject.put(facet.getExposedField().getName(), facet.toDBObjects(context)); + } + + return dbObject; + } + + /** + * Adds a facet to this {@link Facets}. + * + * @param fieldName must not be {@literal null}. + * @param operations must not be {@literal null}. + * @return the new {@link Facets}. + */ + Facets and(String fieldName, List operations) { + + Assert.hasText(fieldName, "FieldName must not be null or empty!"); + Assert.notNull(operations, "AggregationOperations must not be null!"); + + List facets = new ArrayList(this.facets.size() + 1); + facets.addAll(this.facets); + facets.add(new Facet(new ExposedField(fieldName, true), operations)); + + return new Facets(facets); + } + } + + /** + * A single facet with a {@link ExposedField} and its {@link AggregationOperation} pipeline. + * + * @author Mark Paluch + */ + private static class Facet { + + private final ExposedField exposedField; + private final List operations; + + /** + * Creates a new {@link Facet} given {@link ExposedField} and {@link AggregationOperation} pipeline. + * + * @param exposedField must not be {@literal null}. + * @param operations must not be {@literal null}. + */ + Facet(ExposedField exposedField, List operations) { + + Assert.notNull(exposedField, "ExposedField must not be null!"); + Assert.notNull(operations, "AggregationOperations must not be null!"); + + this.exposedField = exposedField; + this.operations = operations; + } + + ExposedField getExposedField() { + return exposedField; + } + + List toDBObjects(AggregationOperationContext context) { + return AggregationOperationRenderer.toDBObject(operations, context); + } + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/Fields.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/Fields.java index 70cd2070a3..2ba33412a4 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/Fields.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/Fields.java @@ -17,6 +17,7 @@ import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; @@ -185,6 +186,14 @@ public Iterator iterator() { return fields.iterator(); } + /** + * @return + * @since 1.10 + */ + public List asList() { + return Collections.unmodifiableList(fields); + } + /** * Value object to encapsulate a field in an aggregation operation. * @@ -192,6 +201,7 @@ public Iterator iterator() { */ static class AggregationField implements Field { + private final String raw; private final String name; private final String target; @@ -216,6 +226,7 @@ public AggregationField(String name) { */ public AggregationField(String name, String target) { + raw = name; String nameToSet = cleanUp(name); String targetToSet = cleanUp(target); @@ -257,6 +268,11 @@ public String getName() { * @see org.springframework.data.mongodb.core.aggregation.Field#getAlias() */ public String getTarget() { + + if (isLocalVar()) { + return this.getRaw(); + } + return StringUtils.hasText(this.target) ? this.target : this.name; } @@ -269,6 +285,22 @@ public boolean isAliased() { return !getName().equals(getTarget()); } + /** + * @return {@literal true} in case the field name starts with {@code $$}. + * @since 1.10 + */ + public boolean isLocalVar() { + return raw.startsWith("$$") && !raw.startsWith("$$$"); + } + + /** + * @return + * @since 1.10 + */ + public String getRaw() { + return raw; + } + /* * (non-Javadoc) * @see java.lang.Object#toString() diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/GraphLookupOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/GraphLookupOperation.java new file mode 100644 index 0000000000..d46f367638 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/GraphLookupOperation.java @@ -0,0 +1,411 @@ +/* + * 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 java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExposedField; +import org.springframework.data.mongodb.core.aggregation.FieldsExposingAggregationOperation.InheritsFieldsAggregationOperation; +import org.springframework.data.mongodb.core.query.CriteriaDefinition; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; + +import com.mongodb.BasicDBObject; +import com.mongodb.DBObject; + +/** + * Encapsulates the aggregation framework {@code $graphLookup}-operation.
        + * Performs a recursive search on a collection, with options for restricting the search by recursion depth and query + * filter.
        + * We recommend to use the static factory method {@link Aggregation#graphLookup(String)} instead of creating instances + * of this class directly. + * + * @see https://docs.mongodb.org/manual/reference/aggregation/graphLookup/ + * @author Mark Paluch + * @author Christoph Strobl + * @since 1.10 + */ +public class GraphLookupOperation implements InheritsFieldsAggregationOperation { + + private static final Set> ALLOWED_START_TYPES = new HashSet>( + Arrays.> asList(AggregationExpression.class, String.class, Field.class, DBObject.class)); + + private final String from; + private final List startWith; + private final Field connectFrom; + private final Field connectTo; + private final Field as; + private final Long maxDepth; + private final Field depthField; + private final CriteriaDefinition restrictSearchWithMatch; + + private GraphLookupOperation(String from, List startWith, Field connectFrom, Field connectTo, Field as, + Long maxDepth, Field depthField, CriteriaDefinition restrictSearchWithMatch) { + + this.from = from; + this.startWith = startWith; + this.connectFrom = connectFrom; + this.connectTo = connectTo; + this.as = as; + this.maxDepth = maxDepth; + this.depthField = depthField; + this.restrictSearchWithMatch = restrictSearchWithMatch; + } + + /** + * Creates a new {@link FromBuilder} to build {@link GraphLookupOperation}. + * + * @return a new {@link FromBuilder}. + */ + public static FromBuilder builder() { + return new GraphLookupOperationFromBuilder(); + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.AggregationOperation#toDBObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) + */ + @Override + public DBObject toDBObject(AggregationOperationContext context) { + + DBObject graphLookup = new BasicDBObject(); + + graphLookup.put("from", from); + + List mappedStartWith = new ArrayList(startWith.size()); + + for (Object startWithElement : startWith) { + + if (startWithElement instanceof AggregationExpression) { + mappedStartWith.add(((AggregationExpression) startWithElement).toDbObject(context)); + } else if (startWithElement instanceof Field) { + mappedStartWith.add(context.getReference((Field) startWithElement).toString()); + } else { + mappedStartWith.add(startWithElement); + } + } + + graphLookup.put("startWith", mappedStartWith.size() == 1 ? mappedStartWith.iterator().next() : mappedStartWith); + + graphLookup.put("connectFromField", connectFrom.getName()); + graphLookup.put("connectToField", connectTo.getName()); + graphLookup.put("as", as.getName()); + + if (maxDepth != null) { + graphLookup.put("maxDepth", maxDepth); + } + + if (depthField != null) { + graphLookup.put("depthField", depthField.getName()); + } + + if (restrictSearchWithMatch != null) { + graphLookup.put("restrictSearchWithMatch", context.getMappedObject(restrictSearchWithMatch.getCriteriaObject())); + } + + return new BasicDBObject("$graphLookup", graphLookup); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.FieldsExposingAggregationOperation#getFields() + */ + @Override + public ExposedFields getFields() { + return ExposedFields.from(new ExposedField(as, true)); + } + + /** + * @author Mark Paluch + */ + public interface FromBuilder { + + /** + * Set the {@literal collectionName} to apply the {@code $graphLookup} to. + * + * @param collectionName must not be {@literal null} or empty. + * @return + */ + StartWithBuilder from(String collectionName); + } + + /** + * @author Mark Paluch + * @author Christoph Strobl + */ + public interface StartWithBuilder { + + /** + * Set the startWith {@literal fieldReferences} to apply the {@code $graphLookup} to. + * + * @param fieldReferences must not be {@literal null}. + * @return + */ + ConnectFromBuilder startWith(String... fieldReferences); + + /** + * Set the startWith {@literal expressions} to apply the {@code $graphLookup} to. + * + * @param expressions must not be {@literal null}. + * @return + */ + ConnectFromBuilder startWith(AggregationExpression... expressions); + + /** + * Set the startWith as either {@literal fieldReferences}, {@link Fields}, {@link DBObject} or + * {@link AggregationExpression} to apply the {@code $graphLookup} to. + * + * @param expressions must not be {@literal null}. + * @return + * @throws IllegalArgumentException + */ + ConnectFromBuilder startWith(Object... expressions); + } + + /** + * @author Mark Paluch + */ + public interface ConnectFromBuilder { + + /** + * Set the connectFrom {@literal fieldName} to apply the {@code $graphLookup} to. + * + * @param fieldName must not be {@literal null} or empty. + * @return + */ + ConnectToBuilder connectFrom(String fieldName); + } + + /** + * @author Mark Paluch + */ + public interface ConnectToBuilder { + + /** + * Set the connectTo {@literal fieldName} to apply the {@code $graphLookup} to. + * + * @param fieldName must not be {@literal null} or empty. + * @return + */ + GraphLookupOperationBuilder connectTo(String fieldName); + } + + /** + * Builder to build the initial {@link GraphLookupOperationBuilder} that configures the initial mandatory set of + * {@link GraphLookupOperation} properties. + * + * @author Mark Paluch + */ + static final class GraphLookupOperationFromBuilder + implements FromBuilder, StartWithBuilder, ConnectFromBuilder, ConnectToBuilder { + + private String from; + private List startWith; + private String connectFrom; + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.GraphLookupOperation.FromBuilder#from(java.lang.String) + */ + @Override + public StartWithBuilder from(String collectionName) { + + Assert.hasText(collectionName, "CollectionName must not be null or empty!"); + + this.from = collectionName; + return this; + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.GraphLookupOperation.StartWithBuilder#startWith(java.lang.String[]) + */ + @Override + public ConnectFromBuilder startWith(String... fieldReferences) { + + Assert.notNull(fieldReferences, "FieldReferences must not be null!"); + Assert.noNullElements(fieldReferences, "FieldReferences must not contain null elements!"); + + List fields = new ArrayList(fieldReferences.length); + + for (String fieldReference : fieldReferences) { + fields.add(Fields.field(fieldReference)); + } + + this.startWith = fields; + return this; + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.GraphLookupOperation.StartWithBuilder#startWith(org.springframework.data.mongodb.core.aggregation.AggregationExpression[]) + */ + @Override + public ConnectFromBuilder startWith(AggregationExpression... expressions) { + + Assert.notNull(expressions, "AggregationExpressions must not be null!"); + Assert.noNullElements(expressions, "AggregationExpressions must not contain null elements!"); + + this.startWith = Arrays.asList(expressions); + return this; + } + + @Override + public ConnectFromBuilder startWith(Object... expressions) { + + Assert.notNull(expressions, "Expressions must not be null!"); + Assert.noNullElements(expressions, "Expressions must not contain null elements!"); + + this.startWith = verifyAndPotentiallyTransformStartsWithTypes(expressions); + return this; + } + + private List verifyAndPotentiallyTransformStartsWithTypes(Object... expressions) { + + List expressionsToUse = new ArrayList(expressions.length); + + for (Object expression : expressions) { + + assertStartWithType(expression); + + if (expression instanceof String) { + expressionsToUse.add(Fields.field((String) expression)); + } else { + expressionsToUse.add(expression); + } + + } + return expressionsToUse; + } + + private void assertStartWithType(Object expression) { + + for (Class type : ALLOWED_START_TYPES) { + + if (ClassUtils.isAssignable(type, expression.getClass())) { + return; + } + } + + throw new IllegalArgumentException( + String.format("Expression must be any of %s but was %s", ALLOWED_START_TYPES, expression.getClass())); + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.GraphLookupOperation.ConnectFromBuilder#connectFrom(java.lang.String) + */ + @Override + public ConnectToBuilder connectFrom(String fieldName) { + + Assert.hasText(fieldName, "ConnectFrom must not be null or empty!"); + + this.connectFrom = fieldName; + return this; + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.GraphLookupOperation.ConnectToBuilder#connectTo(java.lang.String) + */ + @Override + public GraphLookupOperationBuilder connectTo(String fieldName) { + + Assert.hasText(fieldName, "ConnectTo must not be null or empty!"); + + return new GraphLookupOperationBuilder(from, startWith, connectFrom, fieldName); + } + } + + /** + * @author Mark Paluch + */ + static final class GraphLookupOperationBuilder { + + private final String from; + private final List startWith; + private final Field connectFrom; + private final Field connectTo; + private Long maxDepth; + private Field depthField; + private CriteriaDefinition restrictSearchWithMatch; + + protected GraphLookupOperationBuilder(String from, List startWith, String connectFrom, + String connectTo) { + + this.from = from; + this.startWith = new ArrayList(startWith); + this.connectFrom = Fields.field(connectFrom); + this.connectTo = Fields.field(connectTo); + } + + /** + * Optionally limit the number of recursions. + * + * @param numberOfRecursions must be greater or equal to zero. + * @return + */ + public GraphLookupOperationBuilder maxDepth(long numberOfRecursions) { + + Assert.isTrue(numberOfRecursions >= 0, "Max depth must be >= 0!"); + + this.maxDepth = numberOfRecursions; + return this; + } + + /** + * Optionally add a depth field {@literal fieldName} to each traversed document in the search path. + * + * @param fieldName must not be {@literal null} or empty. + * @return + */ + public GraphLookupOperationBuilder depthField(String fieldName) { + + Assert.hasText(fieldName, "Depth field name must not be null or empty!"); + + this.depthField = Fields.field(fieldName); + return this; + } + + /** + * Optionally add a query specifying conditions to the recursive search. + * + * @param criteriaDefinition must not be {@literal null}. + * @return + */ + public GraphLookupOperationBuilder restrict(CriteriaDefinition criteriaDefinition) { + + Assert.notNull(criteriaDefinition, "CriteriaDefinition must not be null!"); + + this.restrictSearchWithMatch = criteriaDefinition; + return this; + } + + /** + * Set the name of the array field added to each output document and return the final {@link GraphLookupOperation}. + * Contains the documents traversed in the {@literal $graphLookup} stage to reach the document. + * + * @param fieldName must not be {@literal null} or empty. + * @return the final {@link GraphLookupOperation}. + */ + public GraphLookupOperation as(String fieldName) { + + Assert.hasText(fieldName, "As field name must not be null or empty!"); + + return new GraphLookupOperation(from, startWith, connectFrom, connectTo, Fields.field(fieldName), maxDepth, + depthField, restrictSearchWithMatch); + } + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/GroupOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/GroupOperation.java index 4ee8b37ed8..49ef8d24e7 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/GroupOperation.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/GroupOperation.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2015 the original author or authors. + * Copyright 2013-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,11 +35,13 @@ * We recommend to use the static factory method {@link Aggregation#group(Fields)} instead of creating instances of this * class directly. * - * @see http://docs.mongodb.org/manual/reference/aggregation/group/#stage._S_group * @author Sebastian Herold * @author Thomas Darimont * @author Oliver Gierke + * @author Gustavo de Geus + * @author Christoph Strobl * @since 1.3 + * @see MongoDB Aggregation Framework: $group */ public class GroupOperation implements FieldsExposingAggregationOperation { @@ -307,6 +309,51 @@ public GroupOperationBuilder max(AggregationExpression expr) { return newBuilder(GroupOps.MAX, null, expr); } + /** + * Generates an {@link GroupOperationBuilder} for an {@code $stdDevSamp}-expression that for the given + * field-reference. + * + * @param reference must not be {@literal null}. + * @return never {@literal null}. + * @since 1.10 + */ + public GroupOperationBuilder stdDevSamp(String reference) { + return newBuilder(GroupOps.STD_DEV_SAMP, reference, null); + } + + /** + * Generates an {@link GroupOperationBuilder} for an {@code $stdDevSamp}-expression that for the given {@link AggregationExpression}. + * + * @param expr must not be {@literal null}. + * @return never {@literal null}. + * @since 1.10 + */ + public GroupOperationBuilder stdDevSamp(AggregationExpression expr) { + return newBuilder(GroupOps.STD_DEV_SAMP, null, expr); + } + + /** + * Generates an {@link GroupOperationBuilder} for an {@code $stdDevPop}-expression that for the given field-reference. + * + * @param reference must not be {@literal null}. + * @return never {@literal null}. + * @since 1.10 + */ + public GroupOperationBuilder stdDevPop(String reference) { + return newBuilder(GroupOps.STD_DEV_POP, reference, null); + } + + /** + * Generates an {@link GroupOperationBuilder} for an {@code $stdDevPop}-expression that for the given {@link AggregationExpression}. + * + * @param expr must not be {@literal null}. + * @return never {@literal null}. + * @since 1.10 + */ + public GroupOperationBuilder stdDevPop(AggregationExpression expr) { + return newBuilder(GroupOps.STD_DEV_POP, null, expr); + } + private GroupOperationBuilder newBuilder(Keyword keyword, String reference, Object value) { return new GroupOperationBuilder(this, new Operation(keyword, null, reference, value)); } @@ -371,21 +418,18 @@ interface Keyword { private static enum GroupOps implements Keyword { - SUM, LAST, FIRST, PUSH, AVG, MIN, MAX, ADD_TO_SET, COUNT; + SUM("$sum"), LAST("$last"), FIRST("$first"), PUSH("$push"), AVG("$avg"), MIN("$min"), MAX("$max"), ADD_TO_SET("$addToSet"), STD_DEV_POP("$stdDevPop"), STD_DEV_SAMP("$stdDevSamp"); - @Override - public String toString() { + private String mongoOperator; - String[] parts = name().split("_"); - - StringBuilder builder = new StringBuilder(); + GroupOps(String mongoOperator) { + this.mongoOperator = mongoOperator; + } - for (String part : parts) { - String lowerCase = part.toLowerCase(Locale.US); - builder.append(builder.length() == 0 ? lowerCase : StringUtils.capitalize(lowerCase)); - } - return "$" + builder.toString(); + @Override + public String toString() { + return mongoOperator; } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/IfNullOperator.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/IfNullOperator.java deleted file mode 100644 index b51aba01bb..0000000000 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/IfNullOperator.java +++ /dev/null @@ -1,197 +0,0 @@ -/* - * 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 java.util.ArrayList; -import java.util.List; - -import org.springframework.util.Assert; - -import com.mongodb.BasicDBObject; -import com.mongodb.DBObject; - -/** - * Encapsulates the aggregation framework {@code $ifNull} operator. Replacement values can be either {@link Field field - * references}, values of simple MongoDB types or values that can be converted to a simple MongoDB type. - * - * @see http://docs.mongodb.com/manual/reference/operator/aggregation/ifNull/ - * @author Mark Paluch - * @since 1.10 - */ -public class IfNullOperator implements AggregationExpression { - - private final Field field; - private final Object value; - - /** - * Creates a new {@link IfNullOperator} for the given {@link Field} and replacement {@code value}. - * - * @param field must not be {@literal null}. - * @param value must not be {@literal null}. - */ - public IfNullOperator(Field field, Object value) { - - Assert.notNull(field, "Field must not be null!"); - Assert.notNull(value, "'Replacement-value' must not be null!"); - - this.field = field; - this.value = value; - } - - /* - * (non-Javadoc) - * @see org.springframework.data.mongodb.core.aggregation.AggregationExpression#toDbObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) - */ - @Override - public DBObject toDbObject(AggregationOperationContext context) { - - List list = new ArrayList(); - - list.add(context.getReference(field).toString()); - list.add(resolve(value, context)); - - return new BasicDBObject("$ifNull", list); - } - - private Object resolve(Object value, AggregationOperationContext context) { - - if (value instanceof Field) { - return context.getReference((Field) value).toString(); - } else if (value instanceof DBObject) { - return value; - } - - return context.getMappedObject(new BasicDBObject("$set", value)).get("$set"); - } - - /** - * Get a builder that allows fluent creation of {@link IfNullOperator}. - * - * @return a new {@link IfNullBuilder}. - */ - public static IfNullBuilder newBuilder() { - return IfNullOperatorBuilder.newBuilder(); - } - - /** - * @since 1.10 - */ - public static interface IfNullBuilder { - - /** - * @param field the field to check for a {@literal null} value, field reference must not be {@literal null}. - * @return the {@link ThenBuilder} - */ - ThenBuilder ifNull(Field field); - - /** - * @param field the field to check for a {@literal null} value, field name must not be {@literal null} or empty. - * @return the {@link ThenBuilder} - */ - ThenBuilder ifNull(String field); - } - - /** - * @since 1.10 - */ - public static interface ThenBuilder { - - /** - * @param field the field holding the replacement value, must not be {@literal null}. - * @return the {@link IfNullOperator} - */ - IfNullOperator thenReplaceWith(Field field); - - /** - * @param value the value to be used if the {@code $ifNull }condition evaluates {@literal true}. Can be a - * {@link DBObject}, a value that is supported by MongoDB or a value that can be converted to a MongoDB - * representation but must not be {@literal null}. - * @return the {@link IfNullOperator} - */ - IfNullOperator thenReplaceWith(Object value); - } - - /** - * Builder for fluent {@link IfNullOperator} creation. - * - * @author Mark Paluch - * @since 1.10 - */ - public static final class IfNullOperatorBuilder implements IfNullBuilder, ThenBuilder { - - private Field field; - - private IfNullOperatorBuilder() {} - - /** - * Creates a new builder for {@link IfNullOperator}. - * - * @return never {@literal null}. - */ - public static IfNullOperatorBuilder newBuilder() { - return new IfNullOperatorBuilder(); - } - - /* - * (non-Javadoc) - * @see org.springframework.data.mongodb.core.aggregation.IfNullOperator.IfNullBuilder#ifNull(org.springframework.data.mongodb.core.aggregation.Field) - */ - public ThenBuilder ifNull(Field field) { - - Assert.notNull(field, "Field must not be null!"); - - this.field = field; - return this; - } - - /* - * (non-Javadoc) - * @see org.springframework.data.mongodb.core.aggregation.IfNullOperator.IfNullBuilder#ifNull(java.lang.String) - */ - public ThenBuilder ifNull(String name) { - - Assert.hasText(name, "Field name must not be null or empty!"); - - this.field = Fields.field(name); - return this; - } - - /* - * (non-Javadoc) - * @see org.springframework.data.mongodb.core.aggregation.IfNullOperator.ThenReplaceBuilder#thenReplaceWith(org.springframework.data.mongodb.core.aggregation.Field) - */ - @Override - public IfNullOperator thenReplaceWith(Field replacementField) { - - Assert.notNull(replacementField, "Replacement field must not be null!"); - - return new IfNullOperator(this.field, replacementField); - } - - /* - * (non-Javadoc) - * @see org.springframework.data.mongodb.core.aggregation.IfNullOperator.ThenReplaceBuilder#thenReplaceWith(java.lang.Object) - */ - @Override - public IfNullOperator thenReplaceWith(Object value) { - - Assert.notNull(value, "'Replacement-value' must not be null!"); - - return new IfNullOperator(this.field, value); - } - } -} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/InheritingExposedFieldsAggregationOperationContext.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/InheritingExposedFieldsAggregationOperationContext.java index c25b567328..2071dc0b6c 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/InheritingExposedFieldsAggregationOperationContext.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/InheritingExposedFieldsAggregationOperationContext.java @@ -13,17 +13,16 @@ * 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 org.springframework.util.Assert; /** * {@link ExposedFieldsAggregationOperationContext} that inherits fields from its parent * {@link AggregationOperationContext}. * * @author Mark Paluch + * @since 1.9 */ class InheritingExposedFieldsAggregationOperationContext extends ExposedFieldsAggregationOperationContext { @@ -40,7 +39,7 @@ public InheritingExposedFieldsAggregationOperationContext(ExposedFields exposedF AggregationOperationContext previousContext) { super(exposedFields, previousContext); - Assert.notNull(previousContext, "PreviousContext must not be null!"); + this.previousContext = previousContext; } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/LimitOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/LimitOperation.java index b56a59e01d..684390eb62 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/LimitOperation.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/LimitOperation.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2015 the original author or authors. + * Copyright 2013-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,10 +26,10 @@ * We recommend to use the static factory method {@link Aggregation#limit(long)} instead of creating instances of this * class directly. * - * @see http://docs.mongodb.org/manual/reference/aggregation/limit/ * @author Thomas Darimont * @author Oliver Gierke * @since 1.3 + * @see MongoDB Aggregation Framework: $limit */ public class LimitOperation implements AggregationOperation { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/LiteralOperators.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/LiteralOperators.java new file mode 100644 index 0000000000..e54ab14134 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/LiteralOperators.java @@ -0,0 +1,96 @@ +/* + * 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.springframework.util.Assert; + +/** + * Gateway to {@literal literal} aggregation operations. + * + * @author Christoph Strobl + * @since 1.10 + */ +public class LiteralOperators { + + /** + * Take the value referenced by given {@literal value}. + * + * @param value must not be {@literal null}. + * @return + */ + public static LiteralOperatorFactory valueOf(Object value) { + + Assert.notNull(value, "Value must not be null!"); + return new LiteralOperatorFactory(value); + } + + /** + * @author Christoph Strobl + */ + public static class LiteralOperatorFactory { + + private final Object value; + + /** + * Creates new {@link LiteralOperatorFactory} for given {@literal value}. + * + * @param value must not be {@literal null}. + */ + public LiteralOperatorFactory(Object value) { + + Assert.notNull(value, "Value must not be null!"); + this.value = value; + } + + /** + * Creates new {@link Literal} that returns the associated value without parsing. + * + * @return + */ + public Literal asLiteral() { + return Literal.asLiteral(value); + } + } + + /** + * {@link AggregationExpression} for {@code $literal}. + * + * @author Christoph Strobl + */ + public static class Literal extends AbstractAggregationExpression { + + private Literal(Object value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$literal"; + } + + /** + * Creates new {@link Literal}. + * + * @param value must not be {@literal null}. + * @return + */ + public static Literal asLiteral(Object value) { + + Assert.notNull(value, "Value must not be null!"); + return new Literal(value); + } + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/LookupOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/LookupOperation.java index 78860073aa..f539921be7 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/LookupOperation.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/LookupOperation.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 the original author or authors. + * Copyright 2016-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,8 +29,8 @@ * @author Alessio Fachechi * @author Christoph Strobl * @author Mark Paluch - * @see http://docs.mongodb.org/manual/reference/aggregation/lookup/#stage._S_lookup * @since 1.9 + * @see MongoDB Aggregation Framework: $lookup */ public class LookupOperation implements FieldsExposingAggregationOperation, InheritsFieldsAggregationOperation { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/MatchOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/MatchOperation.java index eb86fb1e5c..f925b52e8e 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/MatchOperation.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/MatchOperation.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2015 the original author or authors. + * Copyright 2013-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,11 +28,11 @@ * {@link Aggregation#match(org.springframework.data.mongodb.core.query.Criteria)} instead of creating instances of this * class directly. * - * @see http://docs.mongodb.org/manual/reference/aggregation/match/ * @author Sebastian Herold * @author Thomas Darimont * @author Oliver Gierke * @since 1.3 + * @see MongoDB Aggregation Framework: $match */ public class MatchOperation implements AggregationOperation { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/NestedDelegatingExpressionAggregationOperationContext.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/NestedDelegatingExpressionAggregationOperationContext.java new file mode 100644 index 0000000000..0e9a71dc9b --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/NestedDelegatingExpressionAggregationOperationContext.java @@ -0,0 +1,73 @@ +/* + * 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.springframework.data.mongodb.core.aggregation.ExposedFields.FieldReference; +import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExpressionFieldReference; +import org.springframework.util.Assert; + +import com.mongodb.DBObject; + +/** + * {@link AggregationOperationContext} that delegates {@link FieldReference} resolution and mapping to a parent one, but + * assures {@link FieldReference} get converted into {@link ExpressionFieldReference} using {@code $$} to ref an inner + * variable. + * + * @author Christoph Strobl + * @since 1.10 + */ +class NestedDelegatingExpressionAggregationOperationContext implements AggregationOperationContext { + + private final AggregationOperationContext delegate; + + /** + * Creates new {@link NestedDelegatingExpressionAggregationOperationContext}. + * + * @param referenceContext must not be {@literal null}. + */ + public NestedDelegatingExpressionAggregationOperationContext(AggregationOperationContext referenceContext) { + + Assert.notNull(referenceContext, "Reference context must not be null!"); + this.delegate = referenceContext; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.AggregationOperationContext#getMappedObject(com.mongodb.DBObject) + */ + @Override + public DBObject getMappedObject(DBObject dbObject) { + return delegate.getMappedObject(dbObject); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.AggregationOperationContext#getReference(org.springframework.data.mongodb.core.aggregation.Field) + */ + @Override + public FieldReference getReference(Field field) { + return new ExpressionFieldReference(delegate.getReference(field)); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.AggregationOperationContext#getReference(java.lang.String) + */ + @Override + public FieldReference getReference(String name) { + return new ExpressionFieldReference(delegate.getReference(name)); + } +} 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 index 68b357a62e..7372415281 100644 --- 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 @@ -1,5 +1,5 @@ /* - * Copyright 2016 the original author or authors. + * Copyright 2016-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,8 +25,8 @@ * 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 + * @see MongoDB Aggregation Framework: $out */ public class OutOperation implements AggregationOperation { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ProjectionOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ProjectionOperation.java index 73e11bf4dd..5858e5d2e8 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ProjectionOperation.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ProjectionOperation.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,12 +17,16 @@ import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.List; +import org.springframework.data.mongodb.core.aggregation.ConditionalOperators.Cond; +import org.springframework.data.mongodb.core.aggregation.ConditionalOperators.IfNull; import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExposedField; import org.springframework.data.mongodb.core.aggregation.Fields.AggregationField; import org.springframework.data.mongodb.core.aggregation.ProjectionOperation.ProjectionOperationBuilder.FieldProjection; +import org.springframework.data.mongodb.core.aggregation.VariableOperators.Let.ExpressionVariable; import org.springframework.util.Assert; import com.mongodb.BasicDBObject; @@ -37,13 +41,13 @@ * We recommend to use the static factory method {@link Aggregation#project(Fields)} instead of creating instances of * this class directly. * - * @see http://docs.mongodb.org/manual/reference/aggregation/project/ * @author Tobias Trelle * @author Thomas Darimont * @author Oliver Gierke * @author Christoph Strobl * @author Mark Paluch * @since 1.3 + * @see MongoDB Aggregation Framework: $project */ public class ProjectionOperation implements FieldsExposingAggregationOperation { @@ -243,22 +247,22 @@ public DBObject toDBObject(AggregationOperationContext context) { public abstract ProjectionOperation as(String alias); /** - * Apply a conditional projection using {@link ConditionalOperator}. + * Apply a conditional projection using {@link Cond}. * - * @param conditionalOperator must not be {@literal null}. + * @param cond must not be {@literal null}. * @return never {@literal null}. * @since 1.10 */ - public abstract ProjectionOperation applyCondition(ConditionalOperator conditionalOperator); + public abstract ProjectionOperation applyCondition(Cond cond); /** - * Apply a conditional value replacement for {@literal null} values using {@link IfNullOperator}. + * Apply a conditional value replacement for {@literal null} values using {@link IfNull}. * - * @param ifNullOperator must not be {@literal null}. + * @param ifNull must not be {@literal null}. * @return never {@literal null}. * @since 1.10 */ - public abstract ProjectionOperation applyCondition(IfNullOperator ifNullOperator); + public abstract ProjectionOperation applyCondition(IfNull ifNull); } /** @@ -462,10 +466,10 @@ public ProjectionOperation as(String alias) { * @see org.springframework.data.mongodb.core.aggregation.ProjectionOperation.AbstractProjectionOperationBuilder#transform(org.springframework.data.mongodb.core.aggregation.ConditionalOperator) */ @Override - public ProjectionOperation applyCondition(ConditionalOperator conditionalOperator) { + public ProjectionOperation applyCondition(Cond cond) { - Assert.notNull(conditionalOperator, "ConditionalOperator must not be null!"); - return this.operation.and(new ExpressionProjection(Fields.field(name), conditionalOperator)); + Assert.notNull(cond, "ConditionalOperator must not be null!"); + return this.operation.and(new ExpressionProjection(Fields.field(name), cond)); } /* @@ -473,10 +477,10 @@ public ProjectionOperation applyCondition(ConditionalOperator conditionalOperato * @see org.springframework.data.mongodb.core.aggregation.ProjectionOperation.AbstractProjectionOperationBuilder#transform(org.springframework.data.mongodb.core.aggregation.IfNullOperator) */ @Override - public ProjectionOperation applyCondition(IfNullOperator ifNullOperator) { + public ProjectionOperation applyCondition(IfNull ifNull) { - Assert.notNull(ifNullOperator, "IfNullOperator must not be null!"); - return this.operation.and(new ExpressionProjection(Fields.field(name), ifNullOperator)); + Assert.notNull(ifNull, "IfNullOperator must not be null!"); + return this.operation.and(new ExpressionProjection(Fields.field(name), ifNull)); } /** @@ -528,6 +532,20 @@ public ProjectionOperationBuilder minus(String fieldReference) { return project("subtract", Fields.field(fieldReference)); } + /** + * Generates an {@code $subtract} expression that subtracts the result of the given {@link AggregationExpression} + * from the previously mentioned field. + * + * @param expression must not be {@literal null}. + * @return + * @since 1.10 + */ + public ProjectionOperationBuilder minus(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return project("subtract", expression); + } + /** * Generates an {@code $multiply} expression that multiplies the given number with the previously mentioned field. * @@ -553,6 +571,20 @@ public ProjectionOperationBuilder multiply(String fieldReference) { return project("multiply", Fields.field(fieldReference)); } + /** + * Generates an {@code $multiply} expression that multiplies the previously with the result of the + * {@link AggregationExpression}. mentioned field. + * + * @param expression must not be {@literal null}. + * @return + * @since 1.10 + */ + public ProjectionOperationBuilder multiply(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return project("multiply", expression); + } + /** * Generates an {@code $divide} expression that divides the previously mentioned field by the given number. * @@ -579,6 +611,20 @@ public ProjectionOperationBuilder divide(String fieldReference) { return project("divide", Fields.field(fieldReference)); } + /** + * Generates an {@code $divide} expression that divides the value of the previously mentioned by the result of the + * {@link AggregationExpression}. + * + * @param expression must not be {@literal null}. + * @return + * @since 1.10 + */ + public ProjectionOperationBuilder divide(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return project("divide", expression); + } + /** * Generates an {@code $mod} expression that divides the previously mentioned field by the given number and returns * the remainder. @@ -596,7 +642,7 @@ public ProjectionOperationBuilder mod(Number number) { /** * Generates an {@code $mod} expression that divides the value of the given field by the previously mentioned field * and returns the remainder. - * + * * @param fieldReference * @return */ @@ -606,6 +652,20 @@ public ProjectionOperationBuilder mod(String fieldReference) { return project("mod", Fields.field(fieldReference)); } + /** + * Generates an {@code $mod} expression that divides the value of the previously mentioned field by the result of + * the {@link AggregationExpression}. + * + * @param expression must not be {@literal null}. + * @return + * @since 1.10 + */ + public ProjectionOperationBuilder mod(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return project("mod", expression); + } + /** * Generates a {@code $size} expression that returns the size of the array held by the given field.
        * @@ -616,6 +676,85 @@ public ProjectionOperationBuilder size() { return project("size"); } + /** + * Generates a {@code $cmp} expression (compare to) that compares the value of the field to a given value or field. + * + * @param compareValue compare value or a {@link Field} object. + * @return never {@literal null}. + * @since 1.10 + */ + public ProjectionOperationBuilder cmp(Object compareValue) { + return project("cmp", compareValue); + } + + /** + * Generates a {@code $eq} expression (equal) that compares the value of the field to a given value or field. + * + * @param compareValue compare value or a {@link Field} object. + * @return never {@literal null}. + * @since 1.10 + */ + public ProjectionOperationBuilder eq(Object compareValue) { + return project("eq", compareValue); + } + + /** + * Generates a {@code $gt} expression (greater than) that compares the value of the field to a given value or field. + * + * @param compareValue compare value or a {@link Field} object. + * @return never {@literal null}. + * @since 1.10 + */ + public ProjectionOperationBuilder gt(Object compareValue) { + return project("gt", compareValue); + } + + /** + * Generates a {@code $gte} expression (greater than equal) that compares the value of the field to a given value or + * field. + * + * @param compareValue compare value or a {@link Field} object. + * @return never {@literal null}. + * @since 1.10 + */ + public ProjectionOperationBuilder gte(Object compareValue) { + return project("gte", compareValue); + } + + /** + * Generates a {@code $lt} expression (less than) that compares the value of the field to a given value or field. + * + * @param compareValue compare value or a {@link Field} object. + * @return never {@literal null}. + * @since 1.10 + */ + public ProjectionOperationBuilder lt(Object compareValue) { + return project("lt", compareValue); + } + + /** + * Generates a {@code $lte} expression (less than equal) that compares the value of the field to a given value or + * field. + * + * @param compareValue the compare value or a {@link Field} object. + * @return never {@literal null}. + * @since 1.10 + */ + public ProjectionOperationBuilder lte(Object compareValue) { + return project("lte", compareValue); + } + + /** + * Generates a {@code $ne} expression (not equal) that compares the value of the field to a given value or field. + * + * @param compareValue compare value or a {@link Field} object. + * @return never {@literal null}. + * @since 1.10 + */ + public ProjectionOperationBuilder ne(Object compareValue) { + return project("ne", compareValue); + } + /** * Generates a {@code $slice} expression that returns a subset of the array held by the given field.
        * If {@literal n} is positive, $slice returns up to the first n elements in the array.
        @@ -641,7 +780,453 @@ public ProjectionOperationBuilder slice(int count, int offset) { return project("slice", offset, count); } - /* + /** + * Generates a {@code $filter} expression that returns a subset of the array held by the given field. + * + * @param as The variable name for the element in the input array. Must not be {@literal null}. + * @param condition The {@link AggregationExpression} that determines whether to include the element in the + * resulting array. Must not be {@literal null}. + * @return never {@literal null}. + * @since 1.10 + */ + public ProjectionOperationBuilder filter(String as, AggregationExpression condition) { + return this.operation.and(ArrayOperators.Filter.filter(name).as(as).by(condition)); + } + + // SET OPERATORS + + /** + * Generates a {@code $setEquals} expression that compares the previously mentioned field to one or more arrays and + * returns {@literal true} if they have the same distinct elements and {@literal false} otherwise. + * + * @param arrays must not be {@literal null}. + * @return never {@literal null}. + * @since 1.10 + */ + public ProjectionOperationBuilder equalsArrays(String... arrays) { + + Assert.notEmpty(arrays, "Arrays must not be null or empty!"); + return project("setEquals", Fields.fields(arrays)); + } + + /** + * Generates a {@code $setIntersection} expression that takes array of the previously mentioned field and one or + * more arrays and returns an array that contains the elements that appear in every of those. + * + * @param arrays must not be {@literal null}. + * @return never {@literal null}. + * @since 1.10 + */ + public ProjectionOperationBuilder intersectsArrays(String... arrays) { + + Assert.notEmpty(arrays, "Arrays must not be null or empty!"); + return project("setIntersection", Fields.fields(arrays)); + } + + /** + * Generates a {@code $setUnion} expression that takes array of the previously mentioned field and one or more + * arrays and returns an array that contains the elements that appear in any of those. + * + * @param arrays must not be {@literal null}. + * @return never {@literal null}. + * @since 1.10 + */ + public ProjectionOperationBuilder unionArrays(String... arrays) { + + Assert.notEmpty(arrays, "Arrays must not be null or empty!"); + return project("setUnion", Fields.fields(arrays)); + } + + /** + * Generates a {@code $setDifference} expression that takes array of the previously mentioned field and returns an + * array containing the elements that do not exist in the given {@literal array}. + * + * @param array must not be {@literal null}. + * @return never {@literal null}. + * @since 1.10 + */ + public ProjectionOperationBuilder differenceToArray(String array) { + + Assert.hasText(array, "Array must not be null or empty!"); + return project("setDifference", Fields.fields(array)); + } + + /** + * Generates a {@code $setIsSubset} expression that takes array of the previously mentioned field and returns + * {@literal true} if it is a subset of the given {@literal array}. + * + * @param array must not be {@literal null}. + * @return never {@literal null}. + * @since 1.10 + */ + public ProjectionOperationBuilder subsetOfArray(String array) { + + Assert.hasText(array, "Array must not be null or empty!"); + return project("setIsSubset", Fields.fields(array)); + } + + /** + * Generates an {@code $anyElementTrue} expression that Takes array of the previously mentioned field and returns + * {@literal true} if any of the elements are {@literal true} and {@literal false} otherwise. + * + * @return never {@literal null}. + * @since 1.10 + */ + public ProjectionOperationBuilder anyElementInArrayTrue() { + return project("anyElementTrue"); + } + + /** + * Generates an {@code $allElementsTrue} expression that takes array of the previously mentioned field and returns + * {@literal true} if no elements is {@literal false}. + * + * @return never {@literal null}. + * @since 1.10 + */ + public ProjectionOperationBuilder allElementsInArrayTrue() { + return project("allElementsTrue"); + } + + /** + * Generates a {@code $abs} expression that takes the number of the previously mentioned field and returns the + * absolute value of it. + * + * @return never {@literal null}. + * @since 1.10 + */ + public ProjectionOperationBuilder absoluteValue() { + return this.operation.and(ArithmeticOperators.Abs.absoluteValueOf(name)); + } + + /** + * Generates a {@code $ceil} expression that takes the number of the previously mentioned field and returns the + * smallest integer greater than or equal to the specified number. + * + * @return never {@literal null}. + * @since 1.10 + */ + public ProjectionOperationBuilder ceil() { + return this.operation.and(ArithmeticOperators.Ceil.ceilValueOf(name)); + } + + /** + * Generates a {@code $exp} expression that takes the number of the previously mentioned field and raises Euler’s + * number (i.e. e ) on it. + * + * @return never {@literal null}. + * @since 1.10 + */ + public ProjectionOperationBuilder exp() { + return this.operation.and(ArithmeticOperators.Exp.expValueOf(name)); + } + + /** + * Generates a {@code $floor} expression that takes the number of the previously mentioned field and returns the + * largest integer less than or equal to it. + * + * @return never {@literal null}. + * @since 1.10 + */ + public ProjectionOperationBuilder floor() { + return this.operation.and(ArithmeticOperators.Floor.floorValueOf(name)); + } + + /** + * Generates a {@code $ln} expression that takes the number of the previously mentioned field and calculates the + * natural logarithm ln (i.e loge) of it. + * + * @return never {@literal null}. + * @since 1.10 + */ + public ProjectionOperationBuilder ln() { + return this.operation.and(ArithmeticOperators.Ln.lnValueOf(name)); + } + + /** + * Generates a {@code $log} expression that takes the number of the previously mentioned field and calculates the + * log of the associated number in the specified base. + * + * @param baseFieldRef must not be {@literal null}. + * @return never {@literal null}. + * @since 1.10 + */ + public ProjectionOperationBuilder log(String baseFieldRef) { + return this.operation.and(ArithmeticOperators.Log.valueOf(name).log(baseFieldRef)); + } + + /** + * Generates a {@code $log} expression that takes the number of the previously mentioned field and calculates the + * log of the associated number in the specified base. + * + * @param base must not be {@literal null}. + * @return never {@literal null}. + * @since 1.10 + */ + public ProjectionOperationBuilder log(Number base) { + return this.operation.and(ArithmeticOperators.Log.valueOf(name).log(base)); + } + + /** + * Generates a {@code $log} expression that takes the number of the previously mentioned field and calculates the + * log of the associated number in the specified base. + * + * @param base must not be {@literal null}. + * @return never {@literal null}. + * @since 1.10 + */ + public ProjectionOperationBuilder log(AggregationExpression base) { + return this.operation.and(ArithmeticOperators.Log.valueOf(name).log(base)); + } + + /** + * Generates a {@code $log10} expression that takes the number of the previously mentioned field and calculates the + * log base 10. + * + * @return never {@literal null}. + * @since 1.10 + */ + public ProjectionOperationBuilder log10() { + return this.operation.and(ArithmeticOperators.Log10.log10ValueOf(name)); + } + + /** + * Generates a {@code $pow} expression that takes the number of the previously mentioned field and raises it by the + * specified exponent. + * + * @param exponentFieldRef must not be {@literal null}. + * @return never {@literal null}. + * @since 1.10 + */ + public ProjectionOperationBuilder pow(String exponentFieldRef) { + return this.operation.and(ArithmeticOperators.Pow.valueOf(name).pow(exponentFieldRef)); + } + + /** + * Generates a {@code $pow} expression that takes the number of the previously mentioned field and raises it by the + * specified exponent. + * + * @param exponent must not be {@literal null}. + * @return never {@literal null}. + * @since 1.10 + */ + public ProjectionOperationBuilder pow(Number exponent) { + return this.operation.and(ArithmeticOperators.Pow.valueOf(name).pow(exponent)); + } + + /** + * Generates a {@code $pow} expression that Takes the number of the previously mentioned field and raises it by the + * specified exponent. + * + * @param exponentExpression must not be {@literal null}. + * @return never {@literal null}. + * @since 1.10 + */ + public ProjectionOperationBuilder pow(AggregationExpression exponentExpression) { + return this.operation.and(ArithmeticOperators.Pow.valueOf(name).pow(exponentExpression)); + } + + /** + * Generates a {@code $sqrt} expression that takes the number of the previously mentioned field and calculates the + * square root. + * + * @return never {@literal null}. + * @since 1.10 + */ + public ProjectionOperationBuilder sqrt() { + return this.operation.and(ArithmeticOperators.Sqrt.sqrtOf(name)); + } + + /** + * Takes the number of the previously mentioned field and truncates it to its integer value. + * + * @return never {@literal null}. + * @since 1.10 + */ + public ProjectionOperationBuilder trunc() { + return this.operation.and(ArithmeticOperators.Trunc.truncValueOf(name)); + } + + /** + * Generates a {@code $concat} expression that takes the string representation of the previously mentioned field and + * concats given values to it. + * + * @return never {@literal null}. + * @since 1.10 + */ + public ProjectionOperationBuilder concat(Object... values) { + return project("concat", values); + } + + /** + * Generates a {@code $substr} expression that Takes the string representation of the previously mentioned field and + * returns a substring starting at a specified index position. + * + * @param start + * @return + * @since 1.10 + */ + public ProjectionOperationBuilder substring(int start) { + return substring(start, -1); + } + + /** + * Generates a {@code $substr} expression that takes the string representation of the previously mentioned field and + * returns a substring starting at a specified index position including the specified number of characters. + * + * @param start + * @param nrOfChars + * @return + * @since 1.10 + */ + public ProjectionOperationBuilder substring(int start, int nrOfChars) { + return project("substr", start, nrOfChars); + } + + /** + * Generates a {@code $toLower} expression that takes the string representation of the previously mentioned field + * and lowers it. + * + * @return + * @since 1.10 + */ + public ProjectionOperationBuilder toLower() { + return this.operation.and(StringOperators.ToLower.lowerValueOf(name)); + } + + /** + * Generates a {@code $toUpper} expression that takes the string representation of the previously mentioned field + * and uppers it. + * + * @return + * @since 1.10 + */ + public ProjectionOperationBuilder toUpper() { + return this.operation.and(StringOperators.ToUpper.upperValueOf(name)); + } + + /** + * Generates a {@code $strcasecmp} expression that takes the string representation of the previously mentioned field + * and performs case-insensitive comparison to the given {@literal value}. + * + * @param value must not be {@literal null}. + * @return + * @since 1.10 + */ + public ProjectionOperationBuilder strCaseCmp(String value) { + return project("strcasecmp", value); + } + + /** + * Generates a {@code $strcasecmp} expression that takes the string representation of the previously mentioned field + * and performs case-insensitive comparison to the referenced {@literal fieldRef}. + * + * @param fieldRef must not be {@literal null}. + * @return + * @since 1.10 + */ + public ProjectionOperationBuilder strCaseCmpValueOf(String fieldRef) { + return project("strcasecmp", fieldRef); + } + + /** + * Generates a {@code $strcasecmp} expression that takes the string representation of the previously mentioned field + * and performs case-insensitive comparison to the result of the given {@link AggregationExpression}. + * + * @param expression must not be {@literal null}. + * @return + * @since 1.10 + */ + public ProjectionOperationBuilder strCaseCmp(AggregationExpression expression) { + return project("strcasecmp", expression); + } + + /** + * Generates a {@code $arrayElemAt} expression that takes the string representation of the previously mentioned + * field and returns the element at the specified array {@literal position}. + * + * @param position + * @return + * @since 1.10 + */ + public ProjectionOperationBuilder arrayElementAt(int position) { + return project("arrayElemAt", position); + } + + /** + * Generates a {@code $concatArrays} expression that takes the string representation of the previously mentioned + * field and concats it with the arrays from the referenced {@literal fields}. + * + * @param fields must not be {@literal null}. + * @return + * @since 1.10 + */ + public ProjectionOperationBuilder concatArrays(String... fields) { + return project("concatArrays", Fields.fields(fields)); + } + + /** + * Generates a {@code $isArray} expression that takes the string representation of the previously mentioned field + * and checks if its an array. + * + * @return + * @since 1.10 + */ + public ProjectionOperationBuilder isArray() { + return this.operation.and(ArrayOperators.IsArray.isArray(name)); + } + + /** + * Generates a {@code $literal} expression that Takes the value previously and uses it as literal. + * + * @return + * @since 1.10 + */ + public ProjectionOperationBuilder asLiteral() { + return this.operation.and(LiteralOperators.Literal.asLiteral(name)); + } + + /** + * Generates a {@code $dateToString} expression that takes the date representation of the previously mentioned field + * and applies given {@literal format} to it. + * + * @param format must not be {@literal null}. + * @return + * @since 1.10 + */ + public ProjectionOperationBuilder dateAsFormattedString(String format) { + return this.operation.and(DateOperators.DateToString.dateOf(name).toString(format)); + } + + /** + * Generates a {@code $let} expression that binds variables for use in the specified expression, and returns the + * result of the expression. + * + * @param valueExpression The {@link AggregationExpression} bound to {@literal variableName}. + * @param variableName The variable name to be used in the {@literal in} {@link AggregationExpression}. + * @param in The {@link AggregationExpression} to evaluate. + * @return never {@literal null}. + * @since 1.10 + */ + public ProjectionOperationBuilder let(AggregationExpression valueExpression, String variableName, + AggregationExpression in) { + return this.operation.and(VariableOperators.Let + .define(ExpressionVariable.newVariable(variableName).forExpression(valueExpression)).andApply(in)); + } + + /** + * Generates a {@code $let} expression that binds variables for use in the specified expression, and returns the + * result of the expression. + * + * @param variables The bound {@link ExpressionVariable}s. + * @param in The {@link AggregationExpression} to evaluate. + * @return never {@literal null}. + * @since 1.10 + */ + public ProjectionOperationBuilder let(Collection variables, AggregationExpression in) { + return this.operation.and(VariableOperators.Let.define(variables).andApply(in)); + } + + /* * (non-Javadoc) * @see org.springframework.data.mongodb.core.aggregation.AggregationOperation#toDBObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) */ @@ -697,6 +1282,7 @@ public DBObject toDBObject(AggregationOperationContext context) { * * @author Oliver Gierke * @author Thomas Darimont + * @author Mark Paluch */ static class FieldProjection extends Projection { @@ -715,7 +1301,7 @@ public FieldProjection(String name, Object value) { private FieldProjection(Field field, Object value) { - super(field); + super(new ExposedField(field.getName(), true)); this.field = field; this.value = value; @@ -825,7 +1411,18 @@ protected List getOperationArguments(AggregationOperationContext context result.add(context.getReference(getField().getName()).toString()); for (Object element : values) { - result.add(element instanceof Field ? context.getReference((Field) element).toString() : element); + + if (element instanceof Field) { + result.add(context.getReference((Field) element).toString()); + } else if (element instanceof Fields) { + for (Field field : (Fields) element) { + result.add(context.getReference(field).toString()); + } + } else if (element instanceof AggregationExpression) { + result.add(((AggregationExpression) element).toDbObject(context)); + } else { + result.add(element); + } } return result; diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ReplaceRootOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ReplaceRootOperation.java new file mode 100644 index 0000000000..ce071591a9 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ReplaceRootOperation.java @@ -0,0 +1,573 @@ +/* + * Copyright 2016-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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.Collection; +import java.util.Collections; +import java.util.List; + +import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExposedField; +import org.springframework.expression.spel.ast.Projection; +import org.springframework.util.Assert; + +import com.mongodb.BasicDBObject; +import com.mongodb.DBObject; + +/** + * Encapsulates the aggregation framework {@code $replaceRoot}-operation.
        + * We recommend to use the static factory method {@link Aggregation#replaceRoot(String)} instead of creating instances + * of this class directly. + * + * @author Mark Paluch + * @author Christoph Strobl + * @since 1.10 + * @see MongoDB Aggregation Framework: $replaceRoot + */ +public class ReplaceRootOperation implements FieldsExposingAggregationOperation { + + private final Replacement replacement; + + /** + * Creates a new {@link ReplaceRootOperation} given the {@link Field} field name. + * + * @param field must not be {@literal null} or empty. + */ + public ReplaceRootOperation(Field field) { + this(new FieldReplacement(field)); + } + + /** + * Creates a new {@link ReplaceRootOperation} given the {@link AggregationExpression} pointing to a document. + * + * @param aggregationExpression must not be {@literal null}. + */ + public ReplaceRootOperation(AggregationExpression aggregationExpression) { + this(new AggregationExpressionReplacement(aggregationExpression)); + } + + /** + * Creates a new {@link ReplaceRootOperation} given the {@link Replacement}. + * + * @param replacement must not be {@literal null}. + */ + public ReplaceRootOperation(Replacement replacement) { + + Assert.notNull(replacement, "Replacement must not be null!"); + this.replacement = replacement; + } + + /** + * Creates a new {@link ReplaceRootDocumentOperationBuilder}. + * + * @return a new {@link ReplaceRootDocumentOperationBuilder}. + */ + public static ReplaceRootOperationBuilder builder() { + return new ReplaceRootOperationBuilder(); + } + + /* (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("$replaceRoot", new BasicDBObject("newRoot", replacement.toDocumentExpression(context))); + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.FieldsExposingAggregationOperation#getFields() + */ + @Override + public ExposedFields getFields() { + return ExposedFields.from(); + } + + /** + * Builder for {@link ReplaceRootOperation}. + * + * @author Mark Paluch + */ + public static class ReplaceRootOperationBuilder { + + /** + * Defines a root document replacement based on a {@literal fieldName} that resolves to a document. + * + * @param fieldName must not be {@literal null} or empty. + * @return the final {@link ReplaceRootOperation}. + */ + public ReplaceRootOperation withValueOf(String fieldName) { + return new ReplaceRootOperation(Fields.field(fieldName)); + } + + /** + * Defines a root document replacement based on a {@link AggregationExpression} that resolves to a document. + * + * @param aggregationExpression must not be {@literal null}. + * @return the final {@link ReplaceRootOperation}. + */ + public ReplaceRootOperation withValueOf(AggregationExpression aggregationExpression) { + return new ReplaceRootOperation(aggregationExpression); + } + + /** + * Defines a root document replacement based on a composable document that is empty initially.
        + * {@link ReplaceRootOperation} can be populated with individual entries and derive its values from other, existing + * documents. + * + * @return the {@link ReplaceRootDocumentOperation}. + */ + public ReplaceRootDocumentOperation withDocument() { + return new ReplaceRootDocumentOperation(); + } + + /** + * Defines a root document replacement based on a composable document given {@literal dbObject}.
        + * {@link ReplaceRootOperation} can be populated with individual entries and derive its values from other, existing + * documents. + * + * @param dbObject must not be {@literal null}. + * @return the final {@link ReplaceRootOperation}. + */ + public ReplaceRootOperation withDocument(DBObject dbObject) { + + Assert.notNull(dbObject, "DBObject must not be null!"); + + return new ReplaceRootDocumentOperation().andValuesOf(dbObject); + } + } + + /** + * Encapsulates the aggregation framework {@code $replaceRoot}-operation to result in a composable replacement + * document.
        + * Instances of {@link ReplaceRootDocumentOperation} yield empty upon construction and can be populated with single + * values and documents. + * + * @author Mark Paluch + */ + static class ReplaceRootDocumentOperation extends ReplaceRootOperation { + + private final static ReplacementDocument EMPTY = new ReplacementDocument(); + private final ReplacementDocument current; + + /** + * Creates an empty {@link ReplaceRootDocumentOperation}. + */ + public ReplaceRootDocumentOperation() { + this(EMPTY); + } + + private ReplaceRootDocumentOperation(ReplacementDocument replacementDocument) { + super(replacementDocument); + current = replacementDocument; + } + + /** + * Creates an extended {@link ReplaceRootDocumentOperation} that combines {@link ReplacementDocument}s from the + * {@literal currentOperation} and {@literal extension} operation. + * + * @param currentOperation must not be {@literal null}. + * @param extension must not be {@literal null}. + */ + protected ReplaceRootDocumentOperation(ReplaceRootDocumentOperation currentOperation, + ReplacementDocument extension) { + this(currentOperation.current.extendWith(extension)); + } + + /** + * Creates a new {@link ReplaceRootDocumentOperationBuilder} to define a field for the + * {@link AggregationExpression}. + * + * @param aggregationExpression must not be {@literal null}. + * @return the {@link ReplaceRootDocumentOperationBuilder}. + */ + public ReplaceRootDocumentOperationBuilder and(AggregationExpression aggregationExpression) { + return new ReplaceRootDocumentOperationBuilder(this, aggregationExpression); + } + + /** + * Creates a new {@link ReplaceRootDocumentOperationBuilder} to define a field for the {@literal value}. + * + * @param value must not be {@literal null}. + * @return the {@link ReplaceRootDocumentOperationBuilder}. + */ + public ReplaceRootDocumentOperationBuilder andValue(Object value) { + return new ReplaceRootDocumentOperationBuilder(this, value); + } + + /** + * Creates a new {@link ReplaceRootDocumentOperation} that merges all existing replacement values with values from + * {@literal value}. Existing replacement values are overwritten. + * + * @param value must not be {@literal null}. + * @return the {@link ReplaceRootDocumentOperation}. + */ + public ReplaceRootDocumentOperation andValuesOf(Object value) { + return new ReplaceRootDocumentOperation(this, ReplacementDocument.valueOf(value)); + } + } + + /** + * Builder for {@link ReplaceRootDocumentOperation} to populate {@link ReplacementDocument} + * + * @author Mark Paluch + */ + public static class ReplaceRootDocumentOperationBuilder { + + private final ReplaceRootDocumentOperation currentOperation; + private final Object value; + + protected ReplaceRootDocumentOperationBuilder(ReplaceRootDocumentOperation currentOperation, Object value) { + + Assert.notNull(currentOperation, "Current ReplaceRootDocumentOperation must not be null!"); + Assert.notNull(value, "Value must not be null!"); + + this.currentOperation = currentOperation; + this.value = value; + } + + public ReplaceRootDocumentOperation as(String fieldName) { + + if (value instanceof AggregationExpression) { + return new ReplaceRootDocumentOperation(currentOperation, + ReplacementDocument.forExpression(fieldName, (AggregationExpression) value)); + } + + return new ReplaceRootDocumentOperation(currentOperation, ReplacementDocument.forSingleValue(fieldName, value)); + } + } + + /** + * Replacement object that results in a replacement document or an expression that results in a document. + * + * @author Mark Paluch + * @author Christoph Strobl + */ + public interface Replacement { + + /** + * Renders the current {@link Replacement} into a its MongoDB representation based on the given + * {@link AggregationOperationContext}. + * + * @param context will never be {@literal null}. + * @return a replacement document or an expression that results in a document. + */ + Object toDocumentExpression(AggregationOperationContext context); + } + + /** + * {@link Replacement} that uses a {@link AggregationExpression} that results in a replacement document. + * + * @author Mark Paluch + */ + private static class AggregationExpressionReplacement implements Replacement { + + private final AggregationExpression aggregationExpression; + + protected AggregationExpressionReplacement(AggregationExpression aggregationExpression) { + + Assert.notNull(aggregationExpression, "AggregationExpression must not be null!"); + this.aggregationExpression = aggregationExpression; + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.ReplaceRootOperation.Replacement#toObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) + */ + @Override + public DBObject toDocumentExpression(AggregationOperationContext context) { + return aggregationExpression.toDbObject(context); + } + } + + /** + * {@link Replacement that references a {@link Field} inside the current aggregation pipeline. + * + * @author Mark Paluch + */ + private static class FieldReplacement implements Replacement { + + private final Field field; + + /** + * Creates {@link FieldReplacement} given {@link Field}. + */ + protected FieldReplacement(Field field) { + + Assert.notNull(field, "Field must not be null!"); + this.field = field; + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.ReplaceRootOperation.Replacement#toObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) + */ + @Override + public Object toDocumentExpression(AggregationOperationContext context) { + return context.getReference(field).toString(); + } + } + + /** + * Replacement document consisting of multiple {@link ReplacementContributor}s. + * + * @author Mark Paluch + */ + private static class ReplacementDocument implements Replacement { + + private final Collection replacements; + + /** + * Creates an empty {@link ReplacementDocument}. + */ + protected ReplacementDocument() { + replacements = new ArrayList(); + } + + /** + * Creates a {@link ReplacementDocument} given {@link ReplacementContributor}. + * + * @param contributor + */ + protected ReplacementDocument(ReplacementContributor contributor) { + + Assert.notNull(contributor, "ReplacementContributor must not be null!"); + replacements = Collections.singleton(contributor); + } + + private ReplacementDocument(Collection replacements) { + this.replacements = replacements; + } + + /** + * Creates a {@link ReplacementDocument} given a {@literal value}. + * + * @param value must not be {@literal null}. + * @return + */ + public static ReplacementDocument valueOf(Object value) { + return new ReplacementDocument(new DocumentContributor(value)); + } + + /** + * Creates a {@link ReplacementDocument} given a single {@literal field} and {@link AggregationExpression}. + * + * @param aggregationExpression must not be {@literal null}. + * @return + */ + public static ReplacementDocument forExpression(String field, AggregationExpression aggregationExpression) { + return new ReplacementDocument(new ExpressionFieldContributor(Fields.field(field), aggregationExpression)); + } + + /** + * Creates a {@link ReplacementDocument} given a single {@literal field} and {@literal value}. + * + * @param value must not be {@literal null}. + * @return + */ + public static ReplacementDocument forSingleValue(String field, Object value) { + return new ReplacementDocument(new ValueFieldContributor(Fields.field(field), value)); + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.ReplaceRootOperation.Replacement#toObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) + */ + @Override + public DBObject toDocumentExpression(AggregationOperationContext context) { + + DBObject dbObject = new BasicDBObject(); + + for (ReplacementContributor replacement : replacements) { + dbObject.putAll(replacement.toDbObject(context)); + } + + return dbObject; + } + + /** + * Extend a replacement document that merges {@code this} and {@literal replacement} {@link ReplacementContributor}s + * in a new {@link ReplacementDocument}. + * + * @param extension must not be {@literal null}. + * @return the new, extended {@link ReplacementDocument} + */ + public ReplacementDocument extendWith(ReplacementDocument extension) { + + Assert.notNull(extension, "ReplacementDocument must not be null"); + + ReplacementDocument replacementDocument = new ReplacementDocument(); + + List replacements = new ArrayList( + this.replacements.size() + extension.replacements.size()); + + replacements.addAll(this.replacements); + replacements.addAll(extension.replacements); + + return new ReplacementDocument(replacements); + } + } + + /** + * Partial {@link DBObject} contributor for document replacement. + * + * @author Mark Paluch + */ + private interface ReplacementContributor extends AggregationExpression { + + /** + * Renders the current {@link ReplacementContributor} into a {@link DBObject} based on the given + * {@link AggregationOperationContext}. + * + * @param context will never be {@literal null}. + * @return + */ + DBObject toDbObject(AggregationOperationContext context); + } + + /** + * {@link ReplacementContributor} to contribute multiple fields based on the input {@literal value}.
        + * The value object is mapped into a MongoDB {@link DBObject}. + * + * @author Mark Paluch + * @author Christoph Strobl + */ + private static class DocumentContributor implements ReplacementContributor { + + private final Object value; + + /** + * Creates new {@link Projection} for the given {@link Field}. + * + * @param value must not be {@literal null}. + */ + public DocumentContributor(Object value) { + + Assert.notNull(value, "Value must not be null!"); + this.value = value; + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.ReplaceRootOperation.ReplacementContributor#toDBObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) + */ + @Override + public DBObject toDbObject(AggregationOperationContext context) { + + if (value instanceof DBObject) { + return (DBObject) value; + } + + return (DBObject) context.getMappedObject(new BasicDBObject("$set", value)).get("$set"); + } + } + + /** + * Base class for {@link ReplacementContributor} implementations to contribute a single {@literal field} Typically + * used to construct a composite document that should contain the resulting key-value pair. + * + * @author Mark Paluch + */ + private abstract static class FieldContributorSupport implements ReplacementContributor { + + private final ExposedField field; + + /** + * Creates new {@link FieldContributorSupport} for the given {@link Field}. + * + * @param field must not be {@literal null}. + */ + public FieldContributorSupport(Field field) { + + Assert.notNull(field, "Field must not be null!"); + this.field = new ExposedField(field, true); + } + + /** + * @return the {@link ExposedField}. + */ + public ExposedField getField() { + return field; + } + } + + /** + * {@link ReplacementContributor} to contribute a single {@literal field} and {@literal value}. The {@literal value} + * is mapped to a MongoDB {@link DBObject} and can be a singular value, a list or subdocument. + * + * @author Mark Paluch + */ + private static class ValueFieldContributor extends FieldContributorSupport { + + private final Object value; + + /** + * Creates new {@link Projection} for the given {@link Field}. + * + * @param field must not be {@literal null}. + * @param value must not be {@literal null}. + */ + public ValueFieldContributor(Field field, Object value) { + + super(field); + + Assert.notNull(value, "Value must not be null!"); + + this.value = value; + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.ReplaceRootOperation.ReplacementContributor#toDBObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) + */ + @Override + public DBObject toDbObject(AggregationOperationContext context) { + + Object mappedValue = value instanceof BasicDBObject ? value + : context.getMappedObject(new BasicDBObject("$set", value)).get("$set"); + + return new BasicDBObject(getField().getTarget(), mappedValue); + } + } + + /** + * {@link ReplacementContributor} to contribute a single {@literal field} and value based on a + * {@link AggregationExpression}. + * + * @author Mark Paluch + */ + private static class ExpressionFieldContributor extends FieldContributorSupport { + + private final AggregationExpression aggregationExpression; + + /** + * Creates new {@link Projection} for the given {@link Field}. + * + * @param field must not be {@literal null}. + * @param aggregationExpression must not be {@literal null}. + */ + public ExpressionFieldContributor(Field field, AggregationExpression aggregationExpression) { + + super(field); + + Assert.notNull(aggregationExpression, "AggregationExpression must not be null!"); + + this.aggregationExpression = aggregationExpression; + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.ReplaceRootOperation.ReplacementContributor#toDBObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) + */ + @Override + public DBObject toDbObject(AggregationOperationContext context) { + return new BasicDBObject(getField().getTarget(), aggregationExpression.toDbObject(context)); + } + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/SetOperators.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/SetOperators.java new file mode 100644 index 0000000000..f14ebd4315 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/SetOperators.java @@ -0,0 +1,666 @@ +/* + * 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 java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.springframework.data.mongodb.core.aggregation.AccumulatorOperators.Sum; +import org.springframework.util.Assert; + +/** + * Gateway to {@literal Set expressions} which perform {@literal set} operation on arrays, treating arrays as sets. + * + * @author Christoph Strobl + * @since 1.10 + */ +public class SetOperators { + + /** + * Take the array referenced by given {@literal fieldReference}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static SetOperatorFactory arrayAsSet(String fieldReference) { + return new SetOperatorFactory(fieldReference); + } + + /** + * Take the array resulting from the given {@link AggregationExpression}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static SetOperatorFactory arrayAsSet(AggregationExpression expression) { + return new SetOperatorFactory(expression); + } + + /** + * @author Christoph Strobl + */ + public static class SetOperatorFactory { + + private final String fieldReference; + private final AggregationExpression expression; + + /** + * Creates new {@link SetOperatorFactory} for given {@literal fieldReference}. + * + * @param fieldReference must not be {@literal null}. + */ + public SetOperatorFactory(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + this.fieldReference = fieldReference; + this.expression = null; + } + + /** + * Creates new {@link SetOperatorFactory} for given {@link AggregationExpression}. + * + * @param expression must not be {@literal null}. + */ + public SetOperatorFactory(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + this.fieldReference = null; + this.expression = expression; + } + + /** + * Creates new {@link AggregationExpression} that compares the previously mentioned field to one or more arrays and + * returns {@literal true} if they have the same distinct elements and {@literal false} otherwise. + * + * @param arrayReferences must not be {@literal null}. + * @return + */ + public SetEquals isEqualTo(String... arrayReferences) { + return createSetEquals().isEqualTo(arrayReferences); + } + + /** + * Creates new {@link AggregationExpression} that compares the previously mentioned field to one or more arrays and + * returns {@literal true} if they have the same distinct elements and {@literal false} otherwise. + * + * @param expressions must not be {@literal null}. + * @return + */ + public SetEquals isEqualTo(AggregationExpression... expressions) { + return createSetEquals().isEqualTo(expressions); + } + + private SetEquals createSetEquals() { + return usesFieldRef() ? SetEquals.arrayAsSet(fieldReference) : SetEquals.arrayAsSet(expression); + } + + /** + * Creates new {@link AggregationExpression} that takes array of the previously mentioned field and one or more + * arrays and returns an array that contains the elements that appear in every of those. + * + * @param arrayReferences must not be {@literal null}. + * @return + */ + public SetIntersection intersects(String... arrayReferences) { + return createSetIntersection().intersects(arrayReferences); + } + + /** + * Creates new {@link AggregationExpression} that takes array of the previously mentioned field and one or more + * arrays and returns an array that contains the elements that appear in every of those. + * + * @param expressions must not be {@literal null}. + * @return + */ + public SetIntersection intersects(AggregationExpression... expressions) { + return createSetIntersection().intersects(expressions); + } + + private SetIntersection createSetIntersection() { + return usesFieldRef() ? SetIntersection.arrayAsSet(fieldReference) : SetIntersection.arrayAsSet(expression); + } + + /** + * Creates new {@link AggregationExpression} that takes array of the previously mentioned field and one or more + * arrays and returns an array that contains the elements that appear in any of those. + * + * @param arrayReferences must not be {@literal null}. + * @return + */ + public SetUnion union(String... arrayReferences) { + return createSetUnion().union(arrayReferences); + } + + /** + * Creates new {@link AggregationExpression} that takes array of the previously mentioned field and one or more + * arrays and returns an array that contains the elements that appear in any of those. + * + * @param expressions must not be {@literal null}. + * @return + */ + public SetUnion union(AggregationExpression... expressions) { + return createSetUnion().union(expressions); + } + + private SetUnion createSetUnion() { + return usesFieldRef() ? SetUnion.arrayAsSet(fieldReference) : SetUnion.arrayAsSet(expression); + } + + /** + * Creates new {@link AggregationExpression} that takes array of the previously mentioned field and returns an array + * containing the elements that do not exist in the given {@literal arrayReference}. + * + * @param arrayReference must not be {@literal null}. + * @return + */ + public SetDifference differenceTo(String arrayReference) { + return createSetDifference().differenceTo(arrayReference); + } + + /** + * Creates new {@link AggregationExpression} that takes array of the previously mentioned field and returns an array + * containing the elements that do not exist in the given {@link AggregationExpression}. + * + * @param expression must not be {@literal null}. + * @return + */ + public SetDifference differenceTo(AggregationExpression expression) { + return createSetDifference().differenceTo(expression); + } + + private SetDifference createSetDifference() { + return usesFieldRef() ? SetDifference.arrayAsSet(fieldReference) : SetDifference.arrayAsSet(expression); + } + + /** + * Creates new {@link AggregationExpression} that takes array of the previously mentioned field and returns + * {@literal true} if it is a subset of the given {@literal arrayReference}. + * + * @param arrayReference must not be {@literal null}. + * @return + */ + public SetIsSubset isSubsetOf(String arrayReference) { + return createSetIsSubset().isSubsetOf(arrayReference); + } + + /** + * Creates new {@link AggregationExpression} that takes array of the previously mentioned field and returns + * {@literal true} if it is a subset of the given {@link AggregationExpression}. + * + * @param expression must not be {@literal null}. + * @return + */ + public SetIsSubset isSubsetOf(AggregationExpression expression) { + return createSetIsSubset().isSubsetOf(expression); + } + + private SetIsSubset createSetIsSubset() { + return usesFieldRef() ? SetIsSubset.arrayAsSet(fieldReference) : SetIsSubset.arrayAsSet(expression); + } + + /** + * Creates new {@link AggregationExpression} that takes array of the previously mentioned field and returns + * {@literal true} if any of the elements are {@literal true} and {@literal false} otherwise. + * + * @return + */ + public AnyElementTrue anyElementTrue() { + return usesFieldRef() ? AnyElementTrue.arrayAsSet(fieldReference) : AnyElementTrue.arrayAsSet(expression); + } + + /** + * Creates new {@link AggregationExpression} that tkes array of the previously mentioned field and returns + * {@literal true} if no elements is {@literal false}. + * + * @return + */ + public AllElementsTrue allElementsTrue() { + return usesFieldRef() ? AllElementsTrue.arrayAsSet(fieldReference) : AllElementsTrue.arrayAsSet(expression); + } + + private boolean usesFieldRef() { + return this.fieldReference != null; + } + } + + /** + * {@link AggregationExpression} for {@code $setEquals}. + * + * @author Christoph Strobl + */ + public static class SetEquals extends AbstractAggregationExpression { + + private SetEquals(List arrays) { + super(arrays); + } + + @Override + protected String getMongoMethod() { + return "$setEquals"; + } + + /** + * Create new {@link SetEquals}. + * + * @param arrayReference must not be {@literal null}. + * @return + */ + public static SetEquals arrayAsSet(String arrayReference) { + + Assert.notNull(arrayReference, "ArrayReference must not be null!"); + return new SetEquals(asFields(arrayReference)); + } + + /** + * Create new {@link SetEquals}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static SetEquals arrayAsSet(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new SetEquals(Collections.singletonList(expression)); + } + + /** + * Creates new {@link java.util.Set} with all previously added arguments appending the given one. + * + * @param arrayReferences must not be {@literal null}. + * @return + */ + public SetEquals isEqualTo(String... arrayReferences) { + + Assert.notNull(arrayReferences, "ArrayReferences must not be null!"); + return new SetEquals(append(Fields.fields(arrayReferences).asList())); + } + + /** + * Creates new {@link Sum} with all previously added arguments appending the given one. + * + * @param expressions must not be {@literal null}. + * @return + */ + public SetEquals isEqualTo(AggregationExpression... expressions) { + + Assert.notNull(expressions, "Expressions must not be null!"); + return new SetEquals(append(Arrays.asList(expressions))); + } + + /** + * Creates new {@link Sum} with all previously added arguments appending the given one. + * + * @param array must not be {@literal null}. + * @return + */ + public SetEquals isEqualTo(Object[] array) { + + Assert.notNull(array, "Array must not be null!"); + return new SetEquals(append(array)); + } + } + + /** + * {@link AggregationExpression} for {@code $setIntersection}. + * + * @author Christoph Strobl + */ + public static class SetIntersection extends AbstractAggregationExpression { + + private SetIntersection(List arrays) { + super(arrays); + } + + @Override + protected String getMongoMethod() { + return "$setIntersection"; + } + + /** + * Creates new {@link SetIntersection} + * + * @param arrayReference must not be {@literal null}. + * @return + */ + public static SetIntersection arrayAsSet(String arrayReference) { + + Assert.notNull(arrayReference, "ArrayReference must not be null!"); + return new SetIntersection(asFields(arrayReference)); + } + + /** + * Creates new {@link SetIntersection}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static SetIntersection arrayAsSet(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new SetIntersection(Collections.singletonList(expression)); + } + + /** + * Creates new {@link SetIntersection} with all previously added arguments appending the given one. + * + * @param arrayReferences must not be {@literal null}. + * @return + */ + public SetIntersection intersects(String... arrayReferences) { + + Assert.notNull(arrayReferences, "ArrayReferences must not be null!"); + return new SetIntersection(append(asFields(arrayReferences))); + } + + /** + * Creates new {@link SetIntersection} with all previously added arguments appending the given one. + * + * @param expressions must not be {@literal null}. + * @return + */ + public SetIntersection intersects(AggregationExpression... expressions) { + + Assert.notNull(expressions, "Expressions must not be null!"); + return new SetIntersection(append(Arrays.asList(expressions))); + } + } + + /** + * {@link AggregationExpression} for {@code $setUnion}. + * + * @author Christoph Strobl + */ + public static class SetUnion extends AbstractAggregationExpression { + + private SetUnion(Object value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$setUnion"; + } + + /** + * Creates new {@link SetUnion}. + * + * @param arrayReference must not be {@literal null}. + * @return + */ + public static SetUnion arrayAsSet(String arrayReference) { + + Assert.notNull(arrayReference, "ArrayReference must not be null!"); + return new SetUnion(asFields(arrayReference)); + } + + /** + * Creates new {@link SetUnion}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static SetUnion arrayAsSet(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new SetUnion(Collections.singletonList(expression)); + } + + /** + * Creates new {@link SetUnion} with all previously added arguments appending the given one. + * + * @param arrayReferences must not be {@literal null}. + * @return + */ + public SetUnion union(String... arrayReferences) { + + Assert.notNull(arrayReferences, "ArrayReferences must not be null!"); + return new SetUnion(append(asFields(arrayReferences))); + } + + /** + * Creates new {@link SetUnion} with all previously added arguments appending the given one. + * + * @param expressions must not be {@literal null}. + * @return + */ + public SetUnion union(AggregationExpression... expressions) { + + Assert.notNull(expressions, "Expressions must not be null!"); + return new SetUnion(append(Arrays.asList(expressions))); + } + } + + /** + * {@link AggregationExpression} for {@code $setDifference}. + * + * @author Christoph Strobl + */ + public static class SetDifference extends AbstractAggregationExpression { + + private SetDifference(Object value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$setDifference"; + } + + /** + * Creates new {@link SetDifference}. + * + * @param arrayReference must not be {@literal null}. + * @return + */ + public static SetDifference arrayAsSet(String arrayReference) { + + Assert.notNull(arrayReference, "ArrayReference must not be null!"); + return new SetDifference(asFields(arrayReference)); + } + + /** + * Creates new {@link SetDifference}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static SetDifference arrayAsSet(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new SetDifference(Collections.singletonList(expression)); + } + + /** + * Creates new {@link SetDifference} with all previously added arguments appending the given one. + * + * @param arrayReference must not be {@literal null}. + * @return + */ + public SetDifference differenceTo(String arrayReference) { + + Assert.notNull(arrayReference, "ArrayReference must not be null!"); + return new SetDifference(append(Fields.field(arrayReference))); + } + + /** + * Creates new {@link SetDifference} with all previously added arguments appending the given one. + * + * @param expression must not be {@literal null}. + * @return + */ + public SetDifference differenceTo(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new SetDifference(append(expression)); + } + } + + /** + * {@link AggregationExpression} for {@code $setIsSubset}. + * + * @author Christoph Strobl + */ + public static class SetIsSubset extends AbstractAggregationExpression { + + private SetIsSubset(Object value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$setIsSubset"; + } + + /** + * Creates new {@link SetIsSubset}. + * + * @param arrayReference must not be {@literal null}. + * @return + */ + public static SetIsSubset arrayAsSet(String arrayReference) { + + Assert.notNull(arrayReference, "ArrayReference must not be null!"); + return new SetIsSubset(asFields(arrayReference)); + } + + /** + * Creates new {@link SetIsSubset}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static SetIsSubset arrayAsSet(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new SetIsSubset(Collections.singletonList(expression)); + } + + /** + * Creates new {@link SetIsSubset} with all previously added arguments appending the given one. + * + * @param arrayReference must not be {@literal null}. + * @return + */ + public SetIsSubset isSubsetOf(String arrayReference) { + + Assert.notNull(arrayReference, "ArrayReference must not be null!"); + return new SetIsSubset(append(Fields.field(arrayReference))); + } + + /** + * Creates new {@link SetIsSubset} with all previously added arguments appending the given one. + * + * @param expression must not be {@literal null}. + * @return + */ + public SetIsSubset isSubsetOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new SetIsSubset(append(expression)); + } + } + + /** + * {@link AggregationExpression} for {@code $anyElementTrue}. + * + * @author Christoph Strobl + */ + public static class AnyElementTrue extends AbstractAggregationExpression { + + private AnyElementTrue(Object value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$anyElementTrue"; + } + + /** + * Creates new {@link AnyElementTrue}. + * + * @param arrayReference must not be {@literal null}. + * @return + */ + public static AnyElementTrue arrayAsSet(String arrayReference) { + + Assert.notNull(arrayReference, "ArrayReference must not be null!"); + return new AnyElementTrue(asFields(arrayReference)); + } + + /** + * Creates new {@link AnyElementTrue}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static AnyElementTrue arrayAsSet(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new AnyElementTrue(Collections.singletonList(expression)); + } + + public AnyElementTrue anyElementTrue() { + return this; + } + } + + /** + * {@link AggregationExpression} for {@code $allElementsTrue}. + * + * @author Christoph Strobl + */ + public static class AllElementsTrue extends AbstractAggregationExpression { + + private AllElementsTrue(Object value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$allElementsTrue"; + } + + /** + * Creates new {@link AllElementsTrue}. + * + * @param arrayReference must not be {@literal null}. + * @return + */ + public static AllElementsTrue arrayAsSet(String arrayReference) { + + Assert.notNull(arrayReference, "ArrayReference must not be null!"); + return new AllElementsTrue(asFields(arrayReference)); + } + + /** + * Creates new {@link AllElementsTrue}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static AllElementsTrue arrayAsSet(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new AllElementsTrue(Collections.singletonList(expression)); + } + + public AllElementsTrue allElementsTrue() { + return this; + } + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/SkipOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/SkipOperation.java index 8d87538e8a..d0a62b89ca 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/SkipOperation.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/SkipOperation.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,10 +26,10 @@ * We recommend to use the static factory method {@link Aggregation#skip(int)} instead of creating instances of this * class directly. * - * @see http://docs.mongodb.org/manual/reference/aggregation/skip/ * @author Thomas Darimont * @author Oliver Gierke * @since 1.3 + * @see MongoDB Aggregation Framework: $skip */ public class SkipOperation implements AggregationOperation { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/SortOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/SortOperation.java index 0b6f6dee2e..2089a7f806 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/SortOperation.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/SortOperation.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2015 the original author or authors. + * Copyright 2013-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,10 +30,10 @@ * We recommend to use the static factory method {@link Aggregation#sort(Direction, String...)} instead of creating * instances of this class directly. * - * @see http://docs.mongodb.org/manual/reference/aggregation/sort/#pipe._S_sort * @author Thomas Darimont * @author Oliver Gierke * @since 1.3 + * @see MongoDB Aggregation Framework: $sort */ public class SortOperation implements AggregationOperation { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/SpelExpressionTransformer.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/SpelExpressionTransformer.java index 58dc08b364..587f0b327e 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/SpelExpressionTransformer.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/SpelExpressionTransformer.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2014 the original author or authors. + * Copyright 2013-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. @@ -26,19 +26,26 @@ import org.springframework.data.mongodb.core.spel.ExpressionTransformationContextSupport; import org.springframework.data.mongodb.core.spel.LiteralNode; import org.springframework.data.mongodb.core.spel.MethodReferenceNode; +import org.springframework.data.mongodb.core.spel.MethodReferenceNode.AggregationMethodReference; +import org.springframework.data.mongodb.core.spel.MethodReferenceNode.AggregationMethodReference.ArgumentType; +import org.springframework.data.mongodb.core.spel.NotOperatorNode; import org.springframework.data.mongodb.core.spel.OperatorNode; import org.springframework.expression.spel.ExpressionState; import org.springframework.expression.spel.SpelNode; import org.springframework.expression.spel.SpelParserConfiguration; import org.springframework.expression.spel.ast.CompoundExpression; +import org.springframework.expression.spel.ast.ConstructorReference; import org.springframework.expression.spel.ast.Indexer; import org.springframework.expression.spel.ast.InlineList; +import org.springframework.expression.spel.ast.InlineMap; +import org.springframework.expression.spel.ast.OperatorNot; import org.springframework.expression.spel.ast.PropertyOrFieldReference; import org.springframework.expression.spel.standard.SpelExpression; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.expression.spel.support.StandardEvaluationContext; import org.springframework.util.Assert; import org.springframework.util.NumberUtils; +import org.springframework.util.ObjectUtils; import com.mongodb.BasicDBList; import com.mongodb.BasicDBObject; @@ -48,6 +55,7 @@ * Renders the AST of a SpEL expression as a MongoDB Aggregation Framework projection expression. * * @author Thomas Darimont + * @author Christoph Strobl */ class SpelExpressionTransformer implements AggregationExpressionTransformer { @@ -69,6 +77,8 @@ public SpelExpressionTransformer() { conversions.add(new PropertyOrFieldReferenceNodeConversion(this)); conversions.add(new CompoundExpressionNodeConversion(this)); conversions.add(new MethodReferenceNodeConversion(this)); + conversions.add(new NotOperatorNodeConversion(this)); + conversions.add(new ValueRetrievingNodeConversion(this)); this.conversions = Collections.unmodifiableList(conversions); } @@ -131,8 +141,8 @@ private ExpressionNodeConversion lookupConversionFor(ExpressionN * @author Thomas Darimont * @author Oliver Gierke */ - private static abstract class ExpressionNodeConversion implements - AggregationExpressionTransformer { + private static abstract class ExpressionNodeConversion + implements AggregationExpressionTransformer { private final AggregationExpressionTransformer transformer; private final Class nodeType; @@ -235,8 +245,17 @@ public OperatorNodeConversion(AggregationExpressionTransformer transformer) { protected Object convert(AggregationExpressionTransformationContext context) { OperatorNode currentNode = context.getCurrentNode(); - DBObject operationObject = createOperationObjectAndAddToPreviousArgumentsIfNecessary(context, currentNode); + + if (currentNode.isLogicalOperator()) { + + for (ExpressionNode expressionNode : currentNode) { + transform(expressionNode, currentNode, operationObject, context); + } + + return operationObject; + } + Object leftResult = transform(currentNode.getLeft(), currentNode, operationObject, context); if (currentNode.isUnaryMinus()) { @@ -271,7 +290,8 @@ private DBObject createOperationObjectAndAddToPreviousArgumentsIfNecessary( return nextDbObject; } - private Object convertUnaryMinusOp(ExpressionTransformationContextSupport context, Object leftResult) { + private Object convertUnaryMinusOp(ExpressionTransformationContextSupport context, + Object leftResult) { Object result = leftResult instanceof Number ? leftResult : new BasicDBObject("$multiply", dbList(-1, leftResult)); @@ -289,7 +309,7 @@ private Object convertUnaryMinusOp(ExpressionTransformationContextSupport context) { MethodReferenceNode node = context.getCurrentNode(); - List args = new ArrayList(); + AggregationMethodReference methodReference = node.getMethodReference(); - for (ExpressionNode childNode : node) { - args.add(transform(childNode, context)); + Object args = null; + + if (ObjectUtils.nullSafeEquals(methodReference.getArgumentType(), ArgumentType.SINGLE)) { + args = transform(node.getChild(0), context); + } else if (ObjectUtils.nullSafeEquals(methodReference.getArgumentType(), ArgumentType.MAP)) { + + DBObject dbo = new BasicDBObject(); + + int i = 0; + for(ExpressionNode child : node) { + dbo.put(methodReference.getArgumentMap()[i++], transform(child, context)); + } + args = dbo; + } else { + + List argList = new ArrayList(); + + for (ExpressionNode childNode : node) { + argList.add(transform(childNode, context)); + } + + args = dbList(argList.toArray()); } - return context.addToPreviousOrReturn(new BasicDBObject(node.getMethodName(), dbList(args.toArray()))); + return context.addToPreviousOrReturn(new BasicDBObject(methodReference.getMongoOperator(), args)); } } @@ -510,4 +550,81 @@ protected boolean supports(ExpressionNode node) { return node.isOfType(CompoundExpression.class); } } + + /** + * @author Christoph Strobl + * @since 1.10 + */ + static class NotOperatorNodeConversion extends ExpressionNodeConversion { + + /** + * Creates a new {@link ExpressionNodeConversion}. + * + * @param transformer must not be {@literal null}. + */ + public NotOperatorNodeConversion(AggregationExpressionTransformer transformer) { + super(transformer); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.SpelExpressionTransformer.SpelNodeWrapper#convertSpelNodeToMongoObjectExpression(org.springframework.data.mongodb.core.aggregation.SpelExpressionTransformer.ExpressionConversionContext) + */ + @Override + protected Object convert(AggregationExpressionTransformationContext context) { + + NotOperatorNode node = context.getCurrentNode(); + List args = new ArrayList(); + + for (ExpressionNode childNode : node) { + args.add(transform(childNode, context)); + } + + return context.addToPreviousOrReturn(new BasicDBObject(node.getMongoOperator(), dbList(args.toArray()))); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.SpelExpressionTransformer.NodeConversion#supports(org.springframework.data.mongodb.core.spel.ExpressionNode) + */ + @Override + protected boolean supports(ExpressionNode node) { + return node.isOfType(OperatorNot.class); + } + } + + /** + * @author Christoph Strobl + * @since 1.10 + */ + static class ValueRetrievingNodeConversion extends ExpressionNodeConversion { + + /** + * Creates a new {@link ExpressionNodeConversion}. + * + * @param transformer must not be {@literal null}. + */ + public ValueRetrievingNodeConversion(AggregationExpressionTransformer transformer) { + super(transformer); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.SpelExpressionTransformer.SpelNodeWrapper#convertSpelNodeToMongoObjectExpression(org.springframework.data.mongodb.core.aggregation.SpelExpressionTransformer.ExpressionConversionContext) + */ + @Override + protected Object convert(AggregationExpressionTransformationContext context) { + return context.getCurrentNode().getValue(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.SpelExpressionTransformer.NodeConversion#supports(org.springframework.data.mongodb.core.spel.ExpressionNode) + */ + @Override + protected boolean supports(ExpressionNode node) { + return node.isOfType(InlineMap.class) || node.isOfType(InlineList.class) + || node.isOfType(ConstructorReference.class); + } + } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/StringOperators.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/StringOperators.java new file mode 100644 index 0000000000..eed2ac8d30 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/StringOperators.java @@ -0,0 +1,1089 @@ +/* + * 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 java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.springframework.data.domain.Range; +import org.springframework.util.Assert; + +/** + * Gateway to {@literal String} aggregation operations. + * + * @author Christoph Strobl + * @since 1.10 + */ +public class StringOperators { + + /** + * Take the array referenced by given {@literal fieldReference}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static StringOperatorFactory valueOf(String fieldReference) { + return new StringOperatorFactory(fieldReference); + } + + /** + * Take the array referenced by given {@literal fieldReference}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static StringOperatorFactory valueOf(AggregationExpression fieldReference) { + return new StringOperatorFactory(fieldReference); + } + + /** + * @author Christoph Strobl + */ + public static class StringOperatorFactory { + + private final String fieldReference; + private final AggregationExpression expression; + + /** + * Creates new {@link StringOperatorFactory} for given {@literal fieldReference}. + * + * @param fieldReference must not be {@literal null}. + */ + public StringOperatorFactory(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + this.fieldReference = fieldReference; + this.expression = null; + } + + /** + * Creates new {@link StringOperatorFactory} for given {@link AggregationExpression}. + * + * @param expression must not be {@literal null}. + */ + public StringOperatorFactory(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + this.fieldReference = null; + this.expression = expression; + } + + /** + * Creates new {@link AggregationExpression} that takes the associated string representation and concats the value + * of the referenced field to it. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public Concat concatValueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return createConcat().concatValueOf(fieldReference); + } + + /** + * Creates new {@link AggregationExpression} that takes the associated string representation and concats the result + * of the given {@link AggregationExpression} to it. + * + * @param expression must not be {@literal null}. + * @return + */ + public Concat concatValueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return createConcat().concatValueOf(expression); + } + + /** + * Creates new {@link AggregationExpression} that takes the associated string representation and concats given + * {@literal value} to it. + * + * @param value must not be {@literal null}. + * @return + */ + public Concat concat(String value) { + + Assert.notNull(value, "Value must not be null!"); + return createConcat().concat(value); + } + + private Concat createConcat() { + return fieldReference != null ? Concat.valueOf(fieldReference) : Concat.valueOf(expression); + } + + /** + * Creates new {@link AggregationExpression} that takes the associated string representation and returns a substring + * starting at a specified index position. + * + * @param start + * @return + */ + public Substr substring(int start) { + return substring(start, -1); + } + + /** + * Creates new {@link AggregationExpression} that takes the associated string representation and returns a substring + * starting at a specified index position including the specified number of characters. + * + * @param start + * @param nrOfChars + * @return + */ + public Substr substring(int start, int nrOfChars) { + return createSubstr().substring(start, nrOfChars); + } + + private Substr createSubstr() { + return fieldReference != null ? Substr.valueOf(fieldReference) : Substr.valueOf(expression); + } + + /** + * Creates new {@link AggregationExpression} that takes the associated string representation and lowers it. + * + * @return + */ + public ToLower toLower() { + return fieldReference != null ? ToLower.lowerValueOf(fieldReference) : ToLower.lowerValueOf(expression); + } + + /** + * Creates new {@link AggregationExpression} that takes the associated string representation and uppers it. + * + * @return + */ + public ToUpper toUpper() { + return fieldReference != null ? ToUpper.upperValueOf(fieldReference) : ToUpper.upperValueOf(expression); + } + + /** + * Creates new {@link AggregationExpression} that takes the associated string representation and performs + * case-insensitive comparison to the given {@literal value}. + * + * @param value must not be {@literal null}. + * @return + */ + public StrCaseCmp strCaseCmp(String value) { + + Assert.notNull(value, "Value must not be null!"); + return createStrCaseCmp().strcasecmp(value); + } + + /** + * Creates new {@link AggregationExpression} that takes the associated string representation and performs + * case-insensitive comparison to the referenced {@literal fieldReference}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public StrCaseCmp strCaseCmpValueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return createStrCaseCmp().strcasecmpValueOf(fieldReference); + } + + /** + * Creates new {@link AggregationExpression} that takes the associated string representation and performs + * case-insensitive comparison to the result of the given {@link AggregationExpression}. + * + * @param expression must not be {@literal null}. + * @return + */ + public StrCaseCmp strCaseCmpValueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return createStrCaseCmp().strcasecmpValueOf(expression); + } + + private StrCaseCmp createStrCaseCmp() { + return fieldReference != null ? StrCaseCmp.valueOf(fieldReference) : StrCaseCmp.valueOf(expression); + } + + /** + * Creates new {@link AggregationExpression} that takes the associated string representation and searches a string + * for an occurrence of a given {@literal substring} and returns the UTF-8 byte index (zero-based) of the first + * occurrence. + * + * @param substring must not be {@literal null}. + * @return + */ + public IndexOfBytes indexOf(String substring) { + + Assert.notNull(substring, "Substring must not be null!"); + return createIndexOfBytesSubstringBuilder().indexOf(substring); + } + + /** + * Creates new {@link AggregationExpression} that takes the associated string representation and searches a string + * for an occurrence of a substring contained in the given {@literal field reference} and returns the UTF-8 byte + * index (zero-based) of the first occurrence. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public IndexOfBytes indexOf(Field fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return createIndexOfBytesSubstringBuilder().indexOf(fieldReference); + } + + /** + * Creates new {@link AggregationExpression} that takes the associated string representation and searches a string + * for an occurrence of a substring resulting from the given {@link AggregationExpression} and returns the UTF-8 + * byte index (zero-based) of the first occurrence. + * + * @param expression must not be {@literal null}. + * @return + */ + public IndexOfBytes indexOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return createIndexOfBytesSubstringBuilder().indexOf(expression); + } + + private IndexOfBytes.SubstringBuilder createIndexOfBytesSubstringBuilder() { + return fieldReference != null ? IndexOfBytes.valueOf(fieldReference) : IndexOfBytes.valueOf(expression); + } + + /** + * Creates new {@link AggregationExpression} that takes the associated string representation and searches a string + * for an occurrence of a given {@literal substring} and returns the UTF-8 code point index (zero-based) of the + * first occurrence. + * + * @param substring must not be {@literal null}. + * @return + */ + public IndexOfCP indexOfCP(String substring) { + + Assert.notNull(substring, "Substring must not be null!"); + return createIndexOfCPSubstringBuilder().indexOf(substring); + } + + /** + * Creates new {@link AggregationExpression} that takes the associated string representation and searches a string + * for an occurrence of a substring contained in the given {@literal field reference} and returns the UTF-8 code + * point index (zero-based) of the first occurrence. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public IndexOfCP indexOfCP(Field fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return createIndexOfCPSubstringBuilder().indexOf(fieldReference); + } + + /** + * Creates new {@link AggregationExpression} that takes the associated string representation and searches a string + * for an occurrence of a substring resulting from the given {@link AggregationExpression} and returns the UTF-8 + * code point index (zero-based) of the first occurrence. + * + * @param expression must not be {@literal null}. + * @return + */ + public IndexOfCP indexOfCP(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return createIndexOfCPSubstringBuilder().indexOf(expression); + } + + private IndexOfCP.SubstringBuilder createIndexOfCPSubstringBuilder() { + return fieldReference != null ? IndexOfCP.valueOf(fieldReference) : IndexOfCP.valueOf(expression); + } + + /** + * Creates new {@link AggregationExpression} that divides the associated string representation into an array of + * substrings based on the given delimiter. + * + * @param delimiter must not be {@literal null}. + * @return + */ + public Split split(String delimiter) { + return createSplit().split(delimiter); + } + + /** + * Creates new {@link AggregationExpression} that divides the associated string representation into an array of + * substrings based on the delimiter resulting from the referenced field.. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public Split split(Field fieldReference) { + return createSplit().split(fieldReference); + } + + /** + * Creates new {@link AggregationExpression} that divides the associated string representation into an array of + * substrings based on a delimiter resulting from the given {@link AggregationExpression}. + * + * @param expression must not be {@literal null}. + * @return + */ + public Split split(AggregationExpression expression) { + return createSplit().split(expression); + } + + private Split createSplit() { + return fieldReference != null ? Split.valueOf(fieldReference) : Split.valueOf(expression); + } + + /** + * Creates new {@link AggregationExpression} that returns the number of UTF-8 bytes in the associated string + * representation. + * + * @return + */ + public StrLenBytes length() { + return fieldReference != null ? StrLenBytes.stringLengthOf(fieldReference) + : StrLenBytes.stringLengthOf(expression); + } + + /** + * Creates new {@link AggregationExpression} that returns the number of UTF-8 code points in the associated string + * representation. + * + * @return + */ + public StrLenCP lengthCP() { + return fieldReference != null ? StrLenCP.stringLengthOfCP(fieldReference) : StrLenCP.stringLengthOfCP(expression); + } + + /** + * Creates new {@link AggregationExpression} that takes the associated string representation and returns a substring + * starting at a specified code point index position. + * + * @param codePointStart + * @return + */ + public SubstrCP substringCP(int codePointStart) { + return substringCP(codePointStart, -1); + } + + /** + * Creates new {@link AggregationExpression} that takes the associated string representation and returns a substring + * starting at a specified code point index position including the specified number of code points. + * + * @param codePointStart + * @param nrOfCodePoints + * @return + */ + public SubstrCP substringCP(int codePointStart, int nrOfCodePoints) { + return createSubstrCP().substringCP(codePointStart, nrOfCodePoints); + } + + private SubstrCP createSubstrCP() { + return fieldReference != null ? SubstrCP.valueOf(fieldReference) : SubstrCP.valueOf(expression); + } + } + + /** + * {@link AggregationExpression} for {@code $concat}. + * + * @author Christoph Strobl + */ + public static class Concat extends AbstractAggregationExpression { + + private Concat(List value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$concat"; + } + + /** + * Creates new {@link Concat}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Concat valueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Concat(asFields(fieldReference)); + } + + /** + * Creates new {@link Concat}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Concat valueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Concat(Collections.singletonList(expression)); + } + + /** + * Creates new {@link Concat}. + * + * @param value must not be {@literal null}. + * @return + */ + public static Concat stringValue(String value) { + + Assert.notNull(value, "Value must not be null!"); + return new Concat(Collections.singletonList(value)); + } + + public Concat concatValueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Concat(append(Fields.field(fieldReference))); + } + + public Concat concatValueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Concat(append(expression)); + } + + public Concat concat(String value) { + return new Concat(append(value)); + } + } + + /** + * {@link AggregationExpression} for {@code $substr}. + * + * @author Christoph Strobl + */ + public static class Substr extends AbstractAggregationExpression { + + private Substr(List value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$substr"; + } + + /** + * Creates new {@link Substr}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Substr valueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Substr(asFields(fieldReference)); + } + + /** + * Creates new {@link Substr}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Substr valueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Substr(Collections.singletonList(expression)); + } + + public Substr substring(int start) { + return substring(start, -1); + } + + public Substr substring(int start, int nrOfChars) { + return new Substr(append(Arrays.asList(start, nrOfChars))); + } + } + + /** + * {@link AggregationExpression} for {@code $toLower}. + * + * @author Christoph Strobl + */ + public static class ToLower extends AbstractAggregationExpression { + + private ToLower(Object value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$toLower"; + } + + /** + * Creates new {@link ToLower}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static ToLower lowerValueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new ToLower(Fields.field(fieldReference)); + } + + /** + * Creates new {@link ToLower}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static ToLower lowerValueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new ToLower(Collections.singletonList(expression)); + } + + /** + * Creates new {@link ToLower}. + * + * @param value must not be {@literal null}. + * @return + */ + public static ToLower lower(String value) { + + Assert.notNull(value, "Value must not be null!"); + return new ToLower(value); + } + } + + /** + * {@link AggregationExpression} for {@code $toUpper}. + * + * @author Christoph Strobl + */ + public static class ToUpper extends AbstractAggregationExpression { + + private ToUpper(Object value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$toUpper"; + } + + /** + * Creates new {@link ToUpper}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static ToUpper upperValueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new ToUpper(Fields.field(fieldReference)); + } + + /** + * Creates new {@link ToUpper}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static ToUpper upperValueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new ToUpper(Collections.singletonList(expression)); + } + + /** + * Creates new {@link ToUpper}. + * + * @param value must not be {@literal null}. + * @return + */ + public static ToUpper upper(String value) { + + Assert.notNull(value, "Value must not be null!"); + return new ToUpper(value); + } + } + + /** + * {@link AggregationExpression} for {@code $strcasecmp}. + * + * @author Christoph Strobl + */ + public static class StrCaseCmp extends AbstractAggregationExpression { + + private StrCaseCmp(List value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$strcasecmp"; + } + + /** + * Creates new {@link StrCaseCmp}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static StrCaseCmp valueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new StrCaseCmp(asFields(fieldReference)); + } + + /** + * Creates new {@link StrCaseCmp}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static StrCaseCmp valueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new StrCaseCmp(Collections.singletonList(expression)); + } + + /** + * Creates new {@link StrCaseCmp}. + * + * @param value must not be {@literal null}. + * @return + */ + public static StrCaseCmp stringValue(String value) { + + Assert.notNull(value, "Value must not be null!"); + return new StrCaseCmp(Collections.singletonList(value)); + } + + public StrCaseCmp strcasecmp(String value) { + return new StrCaseCmp(append(value)); + } + + public StrCaseCmp strcasecmpValueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new StrCaseCmp(append(Fields.field(fieldReference))); + } + + public StrCaseCmp strcasecmpValueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new StrCaseCmp(append(expression)); + } + } + + /** + * {@link AggregationExpression} for {@code $indexOfBytes}. + * + * @author Christoph Strobl + */ + public static class IndexOfBytes extends AbstractAggregationExpression { + + private IndexOfBytes(List value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$indexOfBytes"; + } + + /** + * Start creating a new {@link IndexOfBytes}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static SubstringBuilder valueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new SubstringBuilder(Fields.field(fieldReference)); + } + + /** + * Start creating a new {@link IndexOfBytes}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static SubstringBuilder valueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new SubstringBuilder(expression); + } + + /** + * Optionally define the substring search start and end position. + * + * @param range must not be {@literal null}. + * @return + */ + public IndexOfBytes within(Range range) { + + Assert.notNull(range, "Range must not be null!"); + + List rangeValues = new ArrayList(2); + rangeValues.add(range.getLowerBound()); + if (range.getUpperBound() != null) { + rangeValues.add(range.getUpperBound()); + } + + return new IndexOfBytes(append(rangeValues)); + } + + public static class SubstringBuilder { + + private final Object stringExpression; + + private SubstringBuilder(Object stringExpression) { + this.stringExpression = stringExpression; + } + + /** + * Creates a new {@link IndexOfBytes} given {@literal substring}. + * + * @param substring must not be {@literal null}. + * @return + */ + public IndexOfBytes indexOf(String substring) { + return new IndexOfBytes(Arrays.asList(stringExpression, substring)); + } + + /** + * Creates a new {@link IndexOfBytes} given {@link AggregationExpression} that resolves to the substring. + * + * @param expression must not be {@literal null}. + * @return + */ + public IndexOfBytes indexOf(AggregationExpression expression) { + return new IndexOfBytes(Arrays.asList(stringExpression, expression)); + } + + /** + * Creates a new {@link IndexOfBytes} given {@link Field} that resolves to the substring. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public IndexOfBytes indexOf(Field fieldReference) { + return new IndexOfBytes(Arrays.asList(stringExpression, fieldReference)); + } + } + } + + /** + * {@link AggregationExpression} for {@code $indexOfCP}. + * + * @author Christoph Strobl + */ + public static class IndexOfCP extends AbstractAggregationExpression { + + private IndexOfCP(List value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$indexOfCP"; + } + + /** + * Start creating a new {@link IndexOfCP}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static SubstringBuilder valueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new SubstringBuilder(Fields.field(fieldReference)); + } + + /** + * Start creating a new {@link IndexOfCP}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static SubstringBuilder valueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new SubstringBuilder(expression); + } + + /** + * Optionally define the substring search start and end position. + * + * @param range must not be {@literal null}. + * @return + */ + public IndexOfCP within(Range range) { + + Assert.notNull(range, "Range must not be null!"); + + List rangeValues = new ArrayList(2); + rangeValues.add(range.getLowerBound()); + if (range.getUpperBound() != null) { + rangeValues.add(range.getUpperBound()); + } + + return new IndexOfCP(append(rangeValues)); + } + + public static class SubstringBuilder { + + private final Object stringExpression; + + private SubstringBuilder(Object stringExpression) { + this.stringExpression = stringExpression; + } + + /** + * Creates a new {@link IndexOfCP} given {@literal substring}. + * + * @param substring must not be {@literal null}. + * @return + */ + public IndexOfCP indexOf(String substring) { + return new IndexOfCP(Arrays.asList(stringExpression, substring)); + } + + /** + * Creates a new {@link IndexOfCP} given {@link AggregationExpression} that resolves to the substring. + * + * @param expression must not be {@literal null}. + * @return + */ + public IndexOfCP indexOf(AggregationExpression expression) { + return new IndexOfCP(Arrays.asList(stringExpression, expression)); + } + + /** + * Creates a new {@link IndexOfCP} given {@link Field} that resolves to the substring. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public IndexOfCP indexOf(Field fieldReference) { + return new IndexOfCP(Arrays.asList(stringExpression, fieldReference)); + } + } + } + + /** + * {@link AggregationExpression} for {@code $split}. + * + * @author Christoph Strobl + */ + public static class Split extends AbstractAggregationExpression { + + private Split(List values) { + super(values); + } + + @Override + protected String getMongoMethod() { + return "$split"; + } + + /** + * Start creating a new {@link Split}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Split valueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Split(asFields(fieldReference)); + } + + /** + * Start creating a new {@link Split}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Split valueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Split(Collections.singletonList(expression)); + } + + /** + * Use given {@link String} as delimiter. + * + * @param delimiter must not be {@literal null}. + * @return + */ + public Split split(String delimiter) { + + Assert.notNull(delimiter, "Delimiter must not be null!"); + return new Split(append(delimiter)); + } + + /** + * Use value of referenced field as delimiter. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public Split split(Field fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Split(append(fieldReference)); + } + + /** + * Use value resulting from {@link AggregationExpression} as delimiter. + * + * @param expression must not be {@literal null}. + * @return + */ + public Split split(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Split(append(expression)); + } + } + + /** + * {@link AggregationExpression} for {@code $strLenBytes}. + * + * @author Christoph Strobl + */ + public static class StrLenBytes extends AbstractAggregationExpression { + + private StrLenBytes(Object value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$strLenBytes"; + } + + /** + * Creates new {@link StrLenBytes}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static StrLenBytes stringLengthOf(String fieldReference) { + return new StrLenBytes(Fields.field(fieldReference)); + } + + /** + * Creates new {@link StrLenBytes}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static StrLenBytes stringLengthOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new StrLenBytes(expression); + } + } + + /** + * {@link AggregationExpression} for {@code $strLenCP}. + * + * @author Christoph Strobl + */ + public static class StrLenCP extends AbstractAggregationExpression { + + private StrLenCP(Object value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$strLenCP"; + } + + /** + * Creates new {@link StrLenCP}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static StrLenCP stringLengthOfCP(String fieldReference) { + return new StrLenCP(Fields.field(fieldReference)); + } + + /** + * Creates new {@link StrLenCP}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static StrLenCP stringLengthOfCP(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new StrLenCP(expression); + } + } + + /** + * {@link AggregationExpression} for {@code $substrCP}. + * + * @author Christoph Strobl + */ + public static class SubstrCP extends AbstractAggregationExpression { + + private SubstrCP(List value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$substrCP"; + } + + /** + * Creates new {@link SubstrCP}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static SubstrCP valueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new SubstrCP(asFields(fieldReference)); + } + + /** + * Creates new {@link SubstrCP}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static SubstrCP valueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new SubstrCP(Collections.singletonList(expression)); + } + + public SubstrCP substringCP(int start) { + return substringCP(start, -1); + } + + public SubstrCP substringCP(int start, int nrOfChars) { + return new SubstrCP(append(Arrays.asList(start, nrOfChars))); + } + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/TypeBasedAggregationOperationContext.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/TypeBasedAggregationOperationContext.java index c800c419c6..8b22de53c3 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/TypeBasedAggregationOperationContext.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/TypeBasedAggregationOperationContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,6 +22,7 @@ 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.aggregation.ExposedFields.DirectFieldReference; import org.springframework.data.mongodb.core.convert.QueryMapper; import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity; import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty; @@ -34,6 +35,7 @@ * property references into document field names. * * @author Oliver Gierke + * @author Mark Paluch * @since 1.3 */ public class TypeBasedAggregationOperationContext implements AggregationOperationContext { @@ -95,9 +97,9 @@ private FieldReference getReferenceFor(Field field) { PersistentPropertyPath propertyPath = mappingContext.getPersistentPropertyPath( field.getTarget(), type); - Field mappedField = field(propertyPath.getLeafProperty().getName(), + Field mappedField = field(field.getName(), propertyPath.toDotPath(MongoPersistentProperty.PropertyToFieldNameConverter.INSTANCE)); - return new FieldReference(new ExposedField(mappedField, true)); + return new DirectFieldReference(new ExposedField(mappedField, true)); } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/UnwindOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/UnwindOperation.java index 12929ca879..89b6801824 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/UnwindOperation.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/UnwindOperation.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,12 +27,12 @@ * We recommend to use the static factory method {@link Aggregation#unwind(String)} instead of creating instances of * this class directly. * - * @see http://docs.mongodb.org/manual/reference/aggregation/unwind/#pipe._S_unwind * @author Thomas Darimont * @author Oliver Gierke * @author Mark Paluch * @author Christoph Strobl * @since 1.3 + * @see MongoDB Aggregation Framework: $unwind */ public class UnwindOperation implements AggregationOperation, FieldsExposingAggregationOperation.InheritsFieldsAggregationOperation { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/VariableOperators.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/VariableOperators.java new file mode 100644 index 0000000000..101e65cfee --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/VariableOperators.java @@ -0,0 +1,391 @@ +/* + * 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 java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +import org.springframework.data.mongodb.core.aggregation.VariableOperators.Let.ExpressionVariable; +import org.springframework.util.Assert; + +import com.mongodb.BasicDBObject; +import com.mongodb.DBObject; + +/** + * Gateway to {@literal variable} aggregation operations. + * + * @author Christoph Strobl + * @author Mark Paluch + * @since 1.10 + */ +public class VariableOperators { + + /** + * Starts building new {@link Map} that applies an {@link AggregationExpression} to each item of a referenced array + * and returns an array with the applied results. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Map.AsBuilder mapItemsOf(String fieldReference) { + return Map.itemsOf(fieldReference); + } + + /** + * Starts building new {@link Map} that applies an {@link AggregationExpression} to each item of a referenced array + * and returns an array with the applied results. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Map.AsBuilder mapItemsOf(AggregationExpression expression) { + return Map.itemsOf(expression); + } + + /** + * Start creating new {@link Let} that allows definition of {@link ExpressionVariable} that can be used within a + * nested {@link AggregationExpression}. + * + * @param variables must not be {@literal null}. + * @return + */ + public static Let.LetBuilder define(ExpressionVariable... variables) { + return Let.define(variables); + } + + /** + * Start creating new {@link Let} that allows definition of {@link ExpressionVariable} that can be used within a + * nested {@link AggregationExpression}. + * + * @param variables must not be {@literal null}. + * @return + */ + public static Let.LetBuilder define(Collection variables) { + return Let.define(variables); + } + + /** + * {@link AggregationExpression} for {@code $map}. + */ + public static class Map implements AggregationExpression { + + private Object sourceArray; + private String itemVariableName; + private AggregationExpression functionToApply; + + private Map(Object sourceArray, String itemVariableName, AggregationExpression functionToApply) { + + Assert.notNull(sourceArray, "SourceArray must not be null!"); + Assert.notNull(itemVariableName, "ItemVariableName must not be null!"); + Assert.notNull(functionToApply, "FunctionToApply must not be null!"); + + this.sourceArray = sourceArray; + this.itemVariableName = itemVariableName; + this.functionToApply = functionToApply; + } + + /** + * Starts building new {@link Map} that applies an {@link AggregationExpression} to each item of a referenced array + * and returns an array with the applied results. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static AsBuilder itemsOf(final String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + + return new AsBuilder() { + + @Override + public FunctionBuilder as(final String variableName) { + + Assert.notNull(variableName, "VariableName must not be null!"); + + return new FunctionBuilder() { + + @Override + public Map andApply(final AggregationExpression expression) { + + Assert.notNull(expression, "AggregationExpression must not be null!"); + return new Map(Fields.field(fieldReference), variableName, expression); + } + }; + } + + }; + } + + /** + * Starts building new {@link Map} that applies an {@link AggregationExpression} to each item of a referenced array + * and returns an array with the applied results. + * + * @param source must not be {@literal null}. + * @return + */ + public static AsBuilder itemsOf(final AggregationExpression source) { + + Assert.notNull(source, "AggregationExpression must not be null!"); + + return new AsBuilder() { + + @Override + public FunctionBuilder as(final String variableName) { + + Assert.notNull(variableName, "VariableName must not be null!"); + + return new FunctionBuilder() { + + @Override + public Map andApply(final AggregationExpression expression) { + + Assert.notNull(expression, "AggregationExpression must not be null!"); + return new Map(source, variableName, expression); + } + }; + } + }; + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.AggregationExpression#toDbObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) + */ + @Override + public DBObject toDbObject(final AggregationOperationContext context) { + return toMap(ExposedFields.synthetic(Fields.fields(itemVariableName)), context); + } + + private DBObject toMap(ExposedFields exposedFields, AggregationOperationContext context) { + + BasicDBObject map = new BasicDBObject(); + InheritingExposedFieldsAggregationOperationContext operationContext = new InheritingExposedFieldsAggregationOperationContext( + exposedFields, context); + + BasicDBObject input; + if (sourceArray instanceof Field) { + input = new BasicDBObject("input", context.getReference((Field) sourceArray).toString()); + } else { + input = new BasicDBObject("input", ((AggregationExpression) sourceArray).toDbObject(context)); + } + + map.putAll(context.getMappedObject(input)); + map.put("as", itemVariableName); + map.put("in", + functionToApply.toDbObject(new NestedDelegatingExpressionAggregationOperationContext(operationContext))); + + return new BasicDBObject("$map", map); + } + + public interface AsBuilder { + + /** + * Define the {@literal variableName} for addressing items within the array. + * + * @param variableName must not be {@literal null}. + * @return + */ + FunctionBuilder as(String variableName); + } + + public interface FunctionBuilder { + + /** + * Creates new {@link Map} that applies the given {@link AggregationExpression} to each item of the referenced + * array and returns an array with the applied results. + * + * @param expression must not be {@literal null}. + * @return + */ + Map andApply(AggregationExpression expression); + } + } + + /** + * {@link AggregationExpression} for {@code $let} that binds {@link AggregationExpression} to variables for use in the + * specified {@code in} expression, and returns the result of the expression. + * + * @author Christoph Strobl + * @since 1.10 + */ + public static class Let implements AggregationExpression { + + private final List vars; + private final AggregationExpression expression; + + private Let(List vars, AggregationExpression expression) { + + this.vars = vars; + this.expression = expression; + } + + /** + * Start creating new {@link Let} by defining the variables for {@code $vars}. + * + * @param variables must not be {@literal null}. + * @return + */ + public static LetBuilder define(final Collection variables) { + + Assert.notNull(variables, "Variables must not be null!"); + + return new LetBuilder() { + + @Override + public Let andApply(final AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Let(new ArrayList(variables), expression); + } + }; + } + + /** + * Start creating new {@link Let} by defining the variables for {@code $vars}. + * + * @param variables must not be {@literal null}. + * @return + */ + public static LetBuilder define(final ExpressionVariable... variables) { + + Assert.notNull(variables, "Variables must not be null!"); + + return new LetBuilder() { + + @Override + public Let andApply(final AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Let(Arrays.asList(variables), expression); + } + }; + } + + public interface LetBuilder { + + /** + * Define the {@link AggregationExpression} to evaluate. + * + * @param expression must not be {@literal null}. + * @return + */ + Let andApply(AggregationExpression expression); + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.AggregationExpression#toDbObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) + */ + @Override + public DBObject toDbObject(final AggregationOperationContext context) { + return toLet(ExposedFields.synthetic(Fields.fields(getVariableNames())), context); + } + + private String[] getVariableNames() { + + String[] varNames = new String[this.vars.size()]; + for (int i = 0; i < this.vars.size(); i++) { + varNames[i] = this.vars.get(i).variableName; + } + + return varNames; + } + + private DBObject toLet(ExposedFields exposedFields, AggregationOperationContext context) { + + DBObject letExpression = new BasicDBObject(); + DBObject mappedVars = new BasicDBObject(); + InheritingExposedFieldsAggregationOperationContext operationContext = new InheritingExposedFieldsAggregationOperationContext( + exposedFields, context); + + for (ExpressionVariable var : this.vars) { + mappedVars.putAll(getMappedVariable(var, context)); + } + + letExpression.put("vars", mappedVars); + letExpression.put("in", getMappedIn(operationContext)); + + return new BasicDBObject("$let", letExpression); + } + + private DBObject getMappedVariable(ExpressionVariable var, AggregationOperationContext context) { + + return new BasicDBObject(var.variableName, var.expression instanceof AggregationExpression + ? ((AggregationExpression) var.expression).toDbObject(context) : var.expression); + } + + private Object getMappedIn(AggregationOperationContext context) { + return expression.toDbObject(new NestedDelegatingExpressionAggregationOperationContext(context)); + } + + /** + * @author Christoph Strobl + */ + public static class ExpressionVariable { + + private final String variableName; + private final Object expression; + + /** + * Creates new {@link ExpressionVariable}. + * + * @param variableName can be {@literal null}. + * @param expression can be {@literal null}. + */ + private ExpressionVariable(String variableName, Object expression) { + + this.variableName = variableName; + this.expression = expression; + } + + /** + * Create a new {@link ExpressionVariable} with given name. + * + * @param variableName must not be {@literal null}. + * @return never {@literal null}. + */ + public static ExpressionVariable newVariable(String variableName) { + + Assert.notNull(variableName, "VariableName must not be null!"); + return new ExpressionVariable(variableName, null); + } + + /** + * Create a new {@link ExpressionVariable} with current name and given {@literal expression}. + * + * @param expression must not be {@literal null}. + * @return never {@literal null}. + */ + public ExpressionVariable forExpression(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new ExpressionVariable(variableName, expression); + } + + /** + * Create a new {@link ExpressionVariable} with current name and given {@literal expressionObject}. + * + * @param expressionObject must not be {@literal null}. + * @return never {@literal null}. + */ + public ExpressionVariable forExpression(DBObject expressionObject) { + + Assert.notNull(expressionObject, "Expression must not be null!"); + return new ExpressionVariable(variableName, expressionObject); + } + } + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/ConverterRegistration.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/ConverterRegistration.java index 3365c2359f..133d778a5c 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/ConverterRegistration.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/ConverterRegistration.java @@ -1,5 +1,5 @@ /* - * Copyright 2011 the original author or authors. + * Copyright 2011-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,6 +23,7 @@ * Conversion registration information. * * @author Oliver Gierke + * @author Mark Paluch */ class ConverterRegistration { @@ -39,7 +40,7 @@ class ConverterRegistration { */ public ConverterRegistration(ConvertiblePair convertiblePair, boolean isReading, boolean isWriting) { - Assert.notNull(convertiblePair); + Assert.notNull(convertiblePair, "ConvertiblePair must not be null!"); this.convertiblePair = convertiblePair; this.reading = isReading; diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/CustomConversions.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/CustomConversions.java index 14054d84cd..ffea59ed5f 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/CustomConversions.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/CustomConversions.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2016 the original author or authors. + * Copyright 2011-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -56,6 +56,7 @@ * @author Oliver Gierke * @author Thomas Darimont * @author Christoph Strobl + * @author Mark Paluch */ public class CustomConversions { @@ -88,7 +89,7 @@ public class CustomConversions { */ public CustomConversions(List converters) { - Assert.notNull(converters); + Assert.notNull(converters, "List of converters must not be null!"); this.readingPairs = new LinkedHashSet(); this.writingPairs = new LinkedHashSet(); @@ -345,8 +346,8 @@ public Class get() { private static Class getCustomTarget(Class sourceType, Class requestedTargetType, Collection pairs) { - Assert.notNull(sourceType); - Assert.notNull(pairs); + Assert.notNull(sourceType, "Source Class must not be null!"); + Assert.notNull(pairs, "Collection of ConvertiblePair must not be null!"); if (requestedTargetType != null && pairs.contains(new ConvertiblePair(sourceType, requestedTargetType))) { return requestedTargetType; diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/GeoConverters.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/GeoConverters.java index f4d3cdc663..7644912b01 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/GeoConverters.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/GeoConverters.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2016 the original author or authors. + * Copyright 2014-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -41,6 +41,7 @@ import org.springframework.data.mongodb.core.geo.Sphere; import org.springframework.data.mongodb.core.query.GeoCommand; import org.springframework.util.Assert; +import org.springframework.util.NumberUtils; import org.springframework.util.ObjectUtils; import com.mongodb.BasicDBList; @@ -53,6 +54,7 @@ * @author Thomas Darimont * @author Oliver Gierke * @author Christoph Strobl + * @author Thiago Diniz da Silveira * @since 1.5 */ abstract class GeoConverters { @@ -121,7 +123,7 @@ public Point convert(DBObject source) { return DbObjectToGeoJsonPointConverter.INSTANCE.convert(source); } - return new Point((Double) source.get("x"), (Double) source.get("y")); + return new Point(toPrimitiveDoubleValue(source.get("x")), toPrimitiveDoubleValue(source.get("y"))); } } @@ -255,9 +257,12 @@ public Circle convert(DBObject source) { } DBObject center = (DBObject) source.get("center"); - Double radius = (Double) source.get("radius"); + Number radius = (Number) source.get("radius"); - Distance distance = new Distance(radius); + Assert.notNull(center, "Center must not be null!"); + Assert.notNull(radius, "Radius must not be null!"); + + Distance distance = new Distance(toPrimitiveDoubleValue(radius)); if (source.containsField("metric")) { @@ -267,9 +272,6 @@ public Circle convert(DBObject source) { distance = distance.in(Metrics.valueOf(metricString)); } - Assert.notNull(center, "Center must not be null!"); - Assert.notNull(radius, "Radius must not be null!"); - return new Circle(DbObjectToPointConverter.INSTANCE.convert(center), distance); } } @@ -326,9 +328,12 @@ public Sphere convert(DBObject source) { } DBObject center = (DBObject) source.get("center"); - Double radius = (Double) source.get("radius"); + Number radius = (Number) source.get("radius"); - Distance distance = new Distance(radius); + Assert.notNull(center, "Center must not be null!"); + Assert.notNull(radius, "Radius must not be null!"); + + Distance distance = new Distance(toPrimitiveDoubleValue(radius)); if (source.containsField("metric")) { @@ -338,9 +343,6 @@ public Sphere convert(DBObject source) { distance = distance.in(Metrics.valueOf(metricString)); } - Assert.notNull(center, "Center must not be null!"); - Assert.notNull(radius, "Radius must not be null!"); - return new Sphere(DbObjectToPointConverter.INSTANCE.convert(center), distance); } } @@ -600,7 +602,7 @@ public GeoJsonPoint convert(DBObject source) { String.format("Cannot convert type '%s' to Point.", source.get("type"))); List dbl = (List) source.get("coordinates"); - return new GeoJsonPoint(dbl.get(0).doubleValue(), dbl.get(1).doubleValue()); + return new GeoJsonPoint(toPrimitiveDoubleValue(dbl.get(0)), toPrimitiveDoubleValue(dbl.get(1))); } } @@ -834,7 +836,8 @@ static List toListOfPoint(BasicDBList listOfCoordinatePairs) { List coordinatesList = (List) point; - points.add(new GeoJsonPoint(coordinatesList.get(0).doubleValue(), coordinatesList.get(1).doubleValue())); + points.add(new GeoJsonPoint(toPrimitiveDoubleValue(coordinatesList.get(0)), + toPrimitiveDoubleValue(coordinatesList.get(1)))); } return points; } @@ -849,4 +852,10 @@ static List toListOfPoint(BasicDBList listOfCoordinatePairs) { static GeoJsonPolygon toGeoJsonPolygon(BasicDBList dbList) { return new GeoJsonPolygon(toListOfPoint((BasicDBList) dbList.get(0))); } + + private static double toPrimitiveDoubleValue(Object value) { + + Assert.isInstanceOf(Number.class, value, "Argument must be a Number."); + return NumberUtils.convertNumberToTargetClass((Number) value, Double.class).doubleValue(); + } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java index 344170d9ab..4d41b65ebe 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java @@ -1,11 +1,11 @@ /* - * Copyright 2011-2016 by the original author(s). + * Copyright 2011-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -101,7 +101,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App /** * Creates a new {@link MappingMongoConverter} given the new {@link DbRefResolver} and {@link MappingContext}. * - * @param mongoDbFactory must not be {@literal null}. + * @param dbRefResolver must not be {@literal null}. * @param mappingContext must not be {@literal null}. */ public MappingMongoConverter(DbRefResolver dbRefResolver, @@ -315,32 +315,32 @@ public void doWithAssociation(Association association) return result; } - /* + /* * (non-Javadoc) * @see org.springframework.data.mongodb.core.convert.MongoWriter#toDBRef(java.lang.Object, org.springframework.data.mongodb.core.mapping.MongoPersistentProperty) */ - public DBRef toDBRef(Object object, MongoPersistentProperty referingProperty) { + public DBRef toDBRef(Object object, MongoPersistentProperty referringProperty) { org.springframework.data.mongodb.core.mapping.DBRef annotation = null; - if (referingProperty != null) { - annotation = referingProperty.getDBRef(); + if (referringProperty != null) { + annotation = referringProperty.getDBRef(); Assert.isTrue(annotation != null, "The referenced property has to be mapped with @DBRef!"); } - // @see DATAMONGO-913 + // DATAMONGO-913 if (object instanceof LazyLoadingProxy) { return ((LazyLoadingProxy) object).toDBRef(); } - return createDBRef(object, referingProperty); + return createDBRef(object, referringProperty); } /** * Root entry method into write conversion. Adds a type discriminator to the {@link DBObject}. Shouldn't be called for * nested conversions. * - * @see org.springframework.data.mongodb.core.core.convert.MongoWriter#write(java.lang.Object, com.mongodb.DBObject) + * @see org.springframework.data.mongodb.core.convert.MongoWriter#write(java.lang.Object, com.mongodb.DBObject) */ public void write(final Object obj, final DBObject dbo) { @@ -483,7 +483,7 @@ protected void writePropertyInternal(Object obj, DBObject dbo, MongoPersistentPr DBRef dbRefObj = null; /* - * If we already have a LazyLoadingProxy, we use it's cached DBRef value instead of + * If we already have a LazyLoadingProxy, we use it's cached DBRef value instead of * unnecessarily initializing it only to convert it to a DBRef a few instructions later. */ if (obj instanceof LazyLoadingProxy) { @@ -830,7 +830,7 @@ private Object getPotentiallyConvertedSimpleRead(Object value, Class target) protected DBRef createDBRef(Object target, MongoPersistentProperty property) { - Assert.notNull(target); + Assert.notNull(target, "Target object must not be null!"); if (target instanceof DBRef) { return (DBRef) target; @@ -892,10 +892,6 @@ private Object readCollectionOrArray(TypeInformation targetType, BasicDBList Class collectionType = targetType.getType(); - if (sourceValue.isEmpty()) { - return getPotentiallyConvertedSimpleRead(new HashSet(), collectionType); - } - TypeInformation componentType = targetType.getComponentType(); Class rawComponentType = componentType == null ? null : componentType.getType(); @@ -903,6 +899,10 @@ private Object readCollectionOrArray(TypeInformation targetType, BasicDBList Collection items = targetType.getType().isArray() ? new ArrayList() : CollectionFactory.createCollection(collectionType, rawComponentType, sourceValue.size()); + if (sourceValue.isEmpty()) { + return getPotentiallyConvertedSimpleRead(items, collectionType); + } + if (!DBRef.class.equals(rawComponentType) && isCollectionOfDbRefWhereBulkFetchIsPossible(sourceValue)) { return bulkReadAndConvertDBRefs((List) (List) (sourceValue), componentType, path, rawComponentType); } @@ -1070,7 +1070,7 @@ public BasicDBList maybeConvertList(Iterable source, TypeInformation typeI /** * Removes the type information from the entire conversion result. - * + * * @param object * @param recursively whether to apply the removal recursively * @return @@ -1131,22 +1131,22 @@ private class MongoDbPropertyValueProvider implements PropertyValueProvider typeHint = field == null ? ClassTypeInformation.OBJECT : field.getTypeHint(); - Object value = converter.convertToMongoType(modifier.getValue(), typeHint); - return new BasicDBObject(modifier.getKey(), value); + return converter.convertToMongoType(value, typeHint); } private TypeInformation getTypeHintForEntity(Object source, MongoPersistentEntity entity) { @@ -153,6 +168,17 @@ private TypeInformation getTypeHintForEntity(Object source, MongoPersistentEn return NESTED_DOCUMENT; } + public DBObject getSortObject(Sort sort) { + + DBObject dbo = new BasicDBObject(); + + for (Order order : sort) { + dbo.put(order.getProperty(), order.isAscending() ? 1 : -1); + } + + return dbo; + } + /* * (non-Javadoc) * @see org.springframework.data.mongodb.core.convert.QueryMapper#createPropertyField(org.springframework.data.mongodb.core.mapping.MongoPersistentEntity, java.lang.String, org.springframework.data.mapping.context.MappingContext) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/GeoJson.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/GeoJson.java index 91f48672a5..3c2e817887 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/GeoJson.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/GeoJson.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 the original author or authors. + * Copyright 2015-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,7 +27,7 @@ public interface GeoJson> { * String value representing the type of the {@link GeoJson} object. * * @return will never be {@literal null}. - * @see http://geojson.org/geojson-spec.html#geojson-objects + * @see http://geojson.org/geojson-spec.html#geojson-objects */ String getType(); @@ -36,7 +36,7 @@ public interface GeoJson> { * determined by {@link #getType()} of geometry. * * @return will never be {@literal null}. - * @see http://geojson.org/geojson-spec.html#geometry-objects + * @see http://geojson.org/geojson-spec.html#geometry-objects */ T getCoordinates(); } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/GeoJsonGeometryCollection.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/GeoJsonGeometryCollection.java index 96cc28cae3..55ca7c8919 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/GeoJsonGeometryCollection.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/GeoJsonGeometryCollection.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 the original author or authors. + * Copyright 2015-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,7 +27,7 @@ * * @author Christoph Strobl * @since 1.7 - * @see http://geojson.org/geojson-spec.html#geometry-collection + * @see http://geojson.org/geojson-spec.html#geometry-collection */ public class GeoJsonGeometryCollection implements GeoJson>> { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/GeoJsonLineString.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/GeoJsonLineString.java index 921a8dbf87..c6beaed31f 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/GeoJsonLineString.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/GeoJsonLineString.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 the original author or authors. + * Copyright 2015-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,7 +24,7 @@ * * @author Christoph Strobl * @since 1.7 - * @see http://geojson.org/geojson-spec.html#linestring + * @see http://geojson.org/geojson-spec.html#linestring */ public class GeoJsonLineString extends GeoJsonMultiPoint { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/GeoJsonMultiLineString.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/GeoJsonMultiLineString.java index 90b046ccc2..7bd392493b 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/GeoJsonMultiLineString.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/GeoJsonMultiLineString.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 the original author or authors. + * Copyright 2015-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,7 +28,7 @@ * * @author Christoph Strobl * @since 1.7 - * @see http://geojson.org/geojson-spec.html#multilinestring + * @see http://geojson.org/geojson-spec.html#multilinestring */ public class GeoJsonMultiLineString implements GeoJson> { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/GeoJsonMultiPoint.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/GeoJsonMultiPoint.java index 0812533163..9a48fdeb2a 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/GeoJsonMultiPoint.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/GeoJsonMultiPoint.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 the original author or authors. + * Copyright 2015-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,7 +29,7 @@ * * @author Christoph Strobl * @since 1.7 - * @see http://geojson.org/geojson-spec.html#multipoint + * @see http://geojson.org/geojson-spec.html#multipoint */ public class GeoJsonMultiPoint implements GeoJson> { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/GeoJsonPoint.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/GeoJsonPoint.java index a44aa856ce..a48315e3b8 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/GeoJsonPoint.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/GeoJsonPoint.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 the original author or authors. + * Copyright 2015-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,7 +25,7 @@ * * @author Christoph Strobl * @since 1.7 - * @see http://geojson.org/geojson-spec.html#point + * @see http://geojson.org/geojson-spec.html#point */ public class GeoJsonPoint extends Point implements GeoJson> { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/GeoJsonPolygon.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/GeoJsonPolygon.java index a5e8b1066e..879464b858 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/GeoJsonPolygon.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/GeoJsonPolygon.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2016 the original author or authors. + * Copyright 2015-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,7 +31,7 @@ * * @author Christoph Strobl * @since 1.7 - * @see http://geojson.org/geojson-spec.html#polygon + * @see http://geojson.org/geojson-spec.html#polygon */ public class GeoJsonPolygon extends Polygon implements GeoJson> { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/Sphere.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/Sphere.java index 0cb623ef29..ccfb41d3c4 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/Sphere.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/Sphere.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 the original author or authors. + * Copyright 2014-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,6 +29,7 @@ * Represents a geospatial sphere value. * * @author Thomas Darimont + * @author Mark Paluch * @since 1.5 */ public class Sphere implements Shape { @@ -46,8 +47,8 @@ public class Sphere implements Shape { @PersistenceConstructor public Sphere(Point center, Distance radius) { - Assert.notNull(center); - Assert.notNull(radius); + Assert.notNull(center, "Center point must not be null!"); + Assert.notNull(radius, "Radius must not be null!"); Assert.isTrue(radius.getValue() >= 0, "Radius must not be negative!"); this.center = center; diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/CompoundIndex.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/CompoundIndex.java index 9e5c4a088a..118c04c19d 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/CompoundIndex.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/CompoundIndex.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2015 the original author or authors. + * Copyright 2011-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -54,22 +54,22 @@ IndexDirection direction() default IndexDirection.ASCENDING; /** - * @see http://docs.mongodb.org/manual/core/index-unique/ * @return + * @see https://docs.mongodb.org/manual/core/index-unique/ */ boolean unique() default false; /** * If set to true index will skip over any document that is missing the indexed field. * - * @see http://docs.mongodb.org/manual/core/index-sparse/ * @return + * @see https://docs.mongodb.org/manual/core/index-sparse/ */ boolean sparse() default false; /** - * @see http://docs.mongodb.org/manual/core/index-creation/#index-creation-duplicate-dropping * @return + * @see https://docs.mongodb.org/manual/core/index-creation/#index-creation-duplicate-dropping */ boolean dropDups() default false; @@ -139,8 +139,8 @@ /** * If {@literal true} the index will be created in the background. * - * @see http://docs.mongodb.org/manual/core/indexes/#background-construction * @return + * @see https://docs.mongodb.org/manual/core/indexes/#background-construction */ boolean background() default false; diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/GeospatialIndex.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/GeospatialIndex.java index 64d8841a66..e7d585f868 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/GeospatialIndex.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/GeospatialIndex.java @@ -1,5 +1,5 @@ /* - * Copyright 2010-2014 the original author or authors. + * Copyright 2010-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. @@ -39,6 +39,7 @@ public class GeospatialIndex implements IndexDefinition { private GeoSpatialIndexType type = GeoSpatialIndexType.GEO_2D; private Double bucketSize = 1.0; private String additionalField; + private IndexFilter filter; /** * Creates a new {@link GeospatialIndex} for the given field. @@ -119,6 +120,22 @@ public GeospatialIndex withAdditionalField(String fieldName) { return this; } + + /** + * Only index the documents in a collection that meet a specified {@link IndexFilter filter expression}. + * + * @param filter can be {@literal null}. + * @return + * @see https://docs.mongodb.com/manual/core/index-partial/ + * @since 1.10 + */ + public GeospatialIndex partial(IndexFilter filter) { + + this.filter = filter; + return this; + } + public DBObject getIndexKeys() { DBObject dbo = new BasicDBObject(); @@ -186,6 +203,10 @@ public DBObject getIndexOptions() { break; } + if (filter != null) { + dbo.put("partialFilterExpression", filter.getFilterObject()); + } + return dbo; } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/Index.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/Index.java index dbf59f6e2b..dcdc4017f1 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/Index.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/Index.java @@ -1,5 +1,5 @@ /* - * Copyright 2010-2015 the original author or authors. + * Copyright 2010-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -63,6 +63,8 @@ public enum Duplicates { private long expire = -1; + private IndexFilter filter; + public Index() {} public Index(String key, Direction direction) { @@ -107,8 +109,8 @@ public Index named(String name) { /** * Reject all documents that contain a duplicate value for the indexed field. * - * @see http://docs.mongodb.org/manual/core/index-unique/ * @return + * @see https://docs.mongodb.org/manual/core/index-unique/ */ public Index unique() { this.unique = true; @@ -118,8 +120,8 @@ public Index unique() { /** * Skip over any document that is missing the indexed field. * - * @see http://docs.mongodb.org/manual/core/index-sparse/ * @return + * @see https://docs.mongodb.org/manual/core/index-sparse/ */ public Index sparse() { this.sparse = true; @@ -165,9 +167,9 @@ public Index expire(long value, TimeUnit unit) { } /** - * @see http://docs.mongodb.org/manual/core/index-creation/#index-creation-duplicate-dropping * @param duplicates * @return + * @see http://docs.mongodb.org/manual/core/index-creation/#index-creation-duplicate-dropping */ public Index unique(Duplicates duplicates) { if (duplicates == Duplicates.DROP) { @@ -176,6 +178,21 @@ public Index unique(Duplicates duplicates) { return unique(); } + /** + * Only index the documents in a collection that meet a specified {@link IndexFilter filter expression}. + * + * @param filter can be {@literal null}. + * @return + * @see https://docs.mongodb.com/manual/core/index-partial/ + * @since 1.10 + */ + public Index partial(IndexFilter filter) { + + this.filter = filter; + return this; + } + /* * (non-Javadoc) * @see org.springframework.data.mongodb.core.index.IndexDefinition#getIndexKeys() @@ -213,6 +230,9 @@ public DBObject getIndexOptions() { dbo.put("expireAfterSeconds", expire); } + if (filter != null) { + dbo.put("partialFilterExpression", filter.getFilterObject()); + } return dbo; } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/IndexField.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/IndexField.java index 83bf354369..0e433aa21b 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/IndexField.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/IndexField.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2014 the original author or authors. + * Copyright 2012-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -44,8 +44,13 @@ private IndexField(String key, Direction direction, Type type) { private IndexField(String key, Direction direction, Type type, Float weight) { - Assert.hasText(key); - Assert.isTrue(direction != null ^ (Type.GEO.equals(type) || Type.TEXT.equals(type))); + Assert.hasText(key, "Key must not be null or empty"); + + if (Type.GEO.equals(type) || Type.TEXT.equals(type)) { + Assert.isTrue(direction == null, "Geo/Text indexes must not have a direction!"); + } else { + Assert.notNull(direction, "Default indexes require a direction"); + } this.key = key; this.direction = direction; @@ -58,17 +63,21 @@ private IndexField(String key, Direction direction, Type type, Float weight) { * * @deprecated use {@link #create(String, Direction)}. * @param key must not be {@literal null} or emtpy. - * @param direction must not be {@literal null}. + * @param order must not be {@literal null}. * @return */ @Deprecated public static IndexField create(String key, Order order) { - Assert.notNull(order); + + Assert.notNull(order, "Order must not be null!"); + return new IndexField(key, order.toDirection(), Type.DEFAULT); } public static IndexField create(String key, Direction order) { - Assert.notNull(order); + + Assert.notNull(order, "Direction must not be null!"); + return new IndexField(key, order, Type.DEFAULT); } @@ -128,7 +137,7 @@ public boolean isGeo() { } /** - * Returns wheter the {@link IndexField} is a text index field. + * Returns whether the {@link IndexField} is a text index field. * * @return true if type is {@link Type#TEXT} * @since 1.6 @@ -158,7 +167,7 @@ public boolean equals(Object obj) { && this.type == that.type; } - /* + /* * (non-Javadoc) * @see java.lang.Object#hashCode() */ @@ -173,7 +182,7 @@ public int hashCode() { return result; } - /* + /* * (non-Javadoc) * @see java.lang.Object#toString() */ diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/IndexFilter.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/IndexFilter.java new file mode 100644 index 0000000000..1ddf07451d --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/IndexFilter.java @@ -0,0 +1,35 @@ +/* + * 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.index; + +import com.mongodb.DBObject; + +/** + * Use {@link IndexFilter} to create the partial filter expression used when creating + * Partial Indexes. + * + * @author Christoph Strobl + * @since 1.10 + */ +public interface IndexFilter { + + /** + * Get the raw (unmapped) filter expression. + * + * @return + */ + DBObject getFilterObject(); +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/IndexInfo.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/IndexInfo.java index 2073fc1c28..4d9e5a1821 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/IndexInfo.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/IndexInfo.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,7 +15,10 @@ */ package org.springframework.data.mongodb.core.index; +import static org.springframework.data.domain.Sort.Direction.*; + import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; @@ -23,13 +26,20 @@ import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; +import com.mongodb.DBObject; + /** * @author Mark Pollack * @author Oliver Gierke * @author Christoph Strobl + * @author Mark Paluch */ public class IndexInfo { + private static final Double ONE = Double.valueOf(1); + private static final Double MINUS_ONE = Double.valueOf(-1); + private static final Collection TWO_D_IDENTIFIERS = Arrays.asList("2d", "2dsphere"); + private final List indexFields; private final String name; @@ -37,6 +47,7 @@ public class IndexInfo { private final boolean dropDuplicates; private final boolean sparse; private final String language; + private String partialFilterExpression; /** * @deprecated Will be removed in 1.7. Please use {@link #IndexInfo(List, String, boolean, boolean, boolean, String)} @@ -62,6 +73,64 @@ public IndexInfo(List indexFields, String name, boolean unique, bool this.language = language; } + /** + * Creates new {@link IndexInfo} parsing required properties from the given {@literal sourceDocument}. + * + * @param sourceDocument + * @return + * @since 1.10 + */ + public static IndexInfo indexInfoOf(DBObject sourceDocument) { + + DBObject keyDbObject = (DBObject) sourceDocument.get("key"); + int numberOfElements = keyDbObject.keySet().size(); + + List indexFields = new ArrayList(numberOfElements); + + for (String key : keyDbObject.keySet()) { + + Object value = keyDbObject.get(key); + + if (TWO_D_IDENTIFIERS.contains(value)) { + + indexFields.add(IndexField.geo(key)); + + } else if ("text".equals(value)) { + + DBObject weights = (DBObject) sourceDocument.get("weights"); + + for (String fieldName : weights.keySet()) { + indexFields.add(IndexField.text(fieldName, Float.valueOf(weights.get(fieldName).toString()))); + } + + } else { + + Double keyValue = new Double(value.toString()); + + if (ONE.equals(keyValue)) { + indexFields.add(IndexField.create(key, ASC)); + } else if (MINUS_ONE.equals(keyValue)) { + indexFields.add(IndexField.create(key, DESC)); + } + } + } + + String name = sourceDocument.get("name").toString(); + + boolean unique = sourceDocument.containsField("unique") ? (Boolean) sourceDocument.get("unique") : false; + boolean dropDuplicates = sourceDocument.containsField("dropDups") ? (Boolean) sourceDocument.get("dropDups") + : false; + boolean sparse = sourceDocument.containsField("sparse") ? (Boolean) sourceDocument.get("sparse") : false; + String language = sourceDocument.containsField("default_language") ? (String) sourceDocument.get("default_language") + : ""; + String partialFilter = sourceDocument.containsField("partialFilterExpression") + ? sourceDocument.get("partialFilterExpression").toString() : ""; + + IndexInfo info = new IndexInfo(indexFields, name, unique, dropDuplicates, sparse, language); + info.partialFilterExpression = partialFilter; + return info; + } + /** * Returns the individual index fields of the index. * @@ -79,7 +148,8 @@ public List getIndexFields() { */ public boolean isIndexForFields(Collection keys) { - Assert.notNull(keys); + Assert.notNull(keys, "Collection of keys must not be null!"); + List indexKeys = new ArrayList(indexFields.size()); for (IndexField field : indexFields) { @@ -113,10 +183,19 @@ public String getLanguage() { return language; } + /** + * @return + * @since 1.0 + */ + public String getPartialFilterExpression() { + return partialFilterExpression; + } + @Override public String toString() { return "IndexInfo [indexFields=" + indexFields + ", name=" + name + ", unique=" + unique + ", dropDuplicates=" - + dropDuplicates + ", sparse=" + sparse + ", language=" + language + "]"; + + dropDuplicates + ", sparse=" + sparse + ", language=" + language + ", partialFilterExpression=" + + partialFilterExpression + "]"; } @Override @@ -130,6 +209,7 @@ public int hashCode() { result = prime * result + (sparse ? 1231 : 1237); result = prime * result + (unique ? 1231 : 1237); result = prime * result + ObjectUtils.nullSafeHashCode(language); + result = prime * result + ObjectUtils.nullSafeHashCode(partialFilterExpression); return result; } @@ -171,6 +251,9 @@ public boolean equals(Object obj) { if (!ObjectUtils.nullSafeEquals(language, other.language)) { return false; } + if (!ObjectUtils.nullSafeEquals(partialFilterExpression, other.partialFilterExpression)) { + return false; + } return true; } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/Indexed.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/Indexed.java index a322622426..2133037c94 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/Indexed.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/Indexed.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2015 the original author or authors. + * Copyright 2011-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -38,8 +38,8 @@ /** * If set to true reject all documents that contain a duplicate value for the indexed field. * - * @see http://docs.mongodb.org/manual/core/index-unique/ * @return + * @see https://docs.mongodb.org/manual/core/index-unique/ */ boolean unique() default false; @@ -48,14 +48,14 @@ /** * If set to true index will skip over any document that is missing the indexed field. * - * @see http://docs.mongodb.org/manual/core/index-sparse/ * @return + * @see https://docs.mongodb.org/manual/core/index-sparse/ */ boolean sparse() default false; /** - * @see http://docs.mongodb.org/manual/core/index-creation/#index-creation-duplicate-dropping * @return + * @see https://docs.mongodb.org/manual/core/index-creation/#index-creation-duplicate-dropping */ boolean dropDups() default false; @@ -122,16 +122,16 @@ /** * If {@literal true} the index will be created in the background. * - * @see http://docs.mongodb.org/manual/core/indexes/#background-construction * @return + * @see https://docs.mongodb.org/manual/core/indexes/#background-construction */ boolean background() default false; /** * Configures the number of seconds after which the collection should expire. Defaults to -1 for no expiry. * - * @see http://docs.mongodb.org/manual/tutorial/expire-data/ * @return + * @see https://docs.mongodb.org/manual/tutorial/expire-data/ */ int expireAfterSeconds() default -1; } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/MongoMappingEventPublisher.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/MongoMappingEventPublisher.java index 26ce216809..4f19ebd6cd 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/MongoMappingEventPublisher.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/MongoMappingEventPublisher.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2015 the original author or authors. + * Copyright 2011-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,6 +34,7 @@ * * @author Jon Brisbin * @author Oliver Gierke + * @author Mark Paluch */ public class MongoMappingEventPublisher implements ApplicationEventPublisher { @@ -46,7 +47,7 @@ public class MongoMappingEventPublisher implements ApplicationEventPublisher { */ public MongoMappingEventPublisher(MongoPersistentEntityIndexCreator indexCreator) { - Assert.notNull(indexCreator); + Assert.notNull(indexCreator, "MongoPersistentEntityIndexCreator must not be null!"); this.indexCreator = indexCreator; } @@ -61,7 +62,7 @@ public void publishEvent(ApplicationEvent event) { } } - /* + /* * (non-Javadoc) * @see org.springframework.context.ApplicationEventPublisher#publishEvent(java.lang.Object) */ diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexCreator.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexCreator.java index 41018a8c8c..7febac08a9 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexCreator.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexCreator.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2015 the original author or authors. + * Copyright 2011-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -79,9 +79,9 @@ public MongoPersistentEntityIndexCreator(MongoMappingContext mappingContext, Mon public MongoPersistentEntityIndexCreator(MongoMappingContext mappingContext, MongoDbFactory mongoDbFactory, IndexResolver indexResolver) { - Assert.notNull(mongoDbFactory); - Assert.notNull(mappingContext); - Assert.notNull(indexResolver); + Assert.notNull(mappingContext, "MongoMappingContext must not be null!"); + Assert.notNull(mongoDbFactory, "MongoDbFactory must not be null!"); + Assert.notNull(indexResolver, "IndexResolver must not be null!"); this.mongoDbFactory = mongoDbFactory; this.mappingContext = mappingContext; diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/PartialIndexFilter.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/PartialIndexFilter.java new file mode 100644 index 0000000000..a7245766eb --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/PartialIndexFilter.java @@ -0,0 +1,75 @@ +/* + * 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.index; + +import lombok.AccessLevel; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; + +import org.springframework.data.mongodb.core.query.CriteriaDefinition; + +import com.mongodb.DBObject; + +/** + * {@link IndexFilter} implementation for usage with plain {@link DBObject} as well as {@link CriteriaDefinition} filter + * expressions. + * + * @author Christoph Strobl + * @since 1.10 + */ +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +public class PartialIndexFilter implements IndexFilter { + + private final @NonNull Object filterExpression; + + /** + * Create new {@link PartialIndexFilter} for given {@link DBObject filter expression}. + * + * @param where must not be {@literal null}. + * @return + */ + public static PartialIndexFilter of(DBObject where) { + return new PartialIndexFilter(where); + } + + /** + * Create new {@link PartialIndexFilter} for given {@link CriteriaDefinition filter expression}. + * + * @param where must not be {@literal null}. + * @return + */ + public static PartialIndexFilter of(CriteriaDefinition where) { + return new PartialIndexFilter(where); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.index.IndexFilter#getFilterObject() + */ + public DBObject getFilterObject() { + + if (filterExpression instanceof DBObject) { + return (DBObject) filterExpression; + } + + if (filterExpression instanceof CriteriaDefinition) { + return ((CriteriaDefinition) filterExpression).getCriteriaObject(); + } + + throw new IllegalArgumentException( + String.format("Unknown type %s used as filter expression.", filterExpression.getClass())); + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/TextIndexDefinition.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/TextIndexDefinition.java index 9fb64be3fb..fed1414f7b 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/TextIndexDefinition.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/TextIndexDefinition.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 the original author or authors. + * Copyright 2014-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -39,6 +39,7 @@ public class TextIndexDefinition implements IndexDefinition { private Set fieldSpecs; private String defaultLanguage; private String languageOverride; + private IndexFilter filter; TextIndexDefinition() { fieldSpecs = new LinkedHashSet(); @@ -129,6 +130,10 @@ public DBObject getIndexOptions() { options.put("language_override", languageOverride); } + if (filter != null) { + options.put("partialFilterExpression", filter.getFilterObject()); + } + return options; } @@ -288,8 +293,8 @@ public TextIndexDefinitionBuilder onField(String fieldname) { public TextIndexDefinitionBuilder onField(String fieldname, Float weight) { if (this.instance.fieldSpecs.contains(ALL_FIELDS)) { - throw new InvalidDataAccessApiUsageException(String.format("Cannot add %s to field spec for all fields.", - fieldname)); + throw new InvalidDataAccessApiUsageException( + String.format("Cannot add %s to field spec for all fields.", fieldname)); } this.instance.fieldSpecs.add(new TextIndexedFieldSpec(fieldname, weight)); @@ -300,8 +305,8 @@ public TextIndexDefinitionBuilder onField(String fieldname, Float weight) { * Define the default language to be used when indexing documents. * * @param language - * @see http://docs.mongodb.org/manual/tutorial/specify-language-for-text-index/#specify-default-language-text-index * @return + * @see https://docs.mongodb.org/manual/tutorial/specify-language-for-text-index/#specify-default-language-text-index */ public TextIndexDefinitionBuilder withDefaultLanguage(String language) { @@ -318,15 +323,30 @@ public TextIndexDefinitionBuilder withDefaultLanguage(String language) { public TextIndexDefinitionBuilder withLanguageOverride(String fieldname) { if (StringUtils.hasText(this.instance.languageOverride)) { - throw new InvalidDataAccessApiUsageException(String.format( - "Cannot set language override on %s as it is already defined on %s.", fieldname, - this.instance.languageOverride)); + throw new InvalidDataAccessApiUsageException( + String.format("Cannot set language override on %s as it is already defined on %s.", fieldname, + this.instance.languageOverride)); } this.instance.languageOverride = fieldname; return this; } + /** + * Only index the documents that meet the specified {@link IndexFilter filter expression}. + * + * @param filter can be {@literal null}. + * @return + * @see https://docs.mongodb.com/manual/core/index-partial/ + * @since 1.10 + */ + public TextIndexDefinitionBuilder partial(IndexFilter filter) { + + this.instance.filter = filter; + return this; + } + public TextIndexDefinition build() { return this.instance; } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentEntity.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentEntity.java index a1196fca46..71734ecc48 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentEntity.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentEntity.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2014 the original author or authors. + * Copyright 2011-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -51,6 +51,7 @@ * @author Oliver Gierke * @author Thomas Darimont * @author Christoph Strobl + * @author Mark Paluch */ public class BasicMongoPersistentEntity extends BasicPersistentEntity implements MongoPersistentEntity, ApplicationContextAware { @@ -138,7 +139,7 @@ public boolean hasTextScoreProperty() { return getTextScoreProperty() != null; } - /* + /* * (non-Javadoc) * @see org.springframework.data.mapping.model.BasicPersistentEntity#verify() */ @@ -200,7 +201,7 @@ public int compare(MongoPersistentProperty o1, MongoPersistentProperty o2) { @Override protected MongoPersistentProperty returnPropertyIfBetterIdPropertyCandidateOrNull(MongoPersistentProperty property) { - Assert.notNull(property); + Assert.notNull(property, "MongoPersistentProperty must not be null!"); if (!property.isIdProperty()) { return null; diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/MongoSimpleTypes.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/MongoSimpleTypes.java index c26f125164..e2892b4d05 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/MongoSimpleTypes.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/MongoSimpleTypes.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011 by the original author(s). + * Copyright (c) 2011-2017 by the original author(s). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,6 +26,8 @@ import org.bson.types.CodeWScope; import org.bson.types.ObjectId; import org.springframework.data.mapping.model.SimpleTypeHolder; +import org.springframework.data.mongodb.util.MongoClientVersion; +import org.springframework.util.ClassUtils; import com.mongodb.DBObject; import com.mongodb.DBRef; @@ -34,6 +36,7 @@ * Simple constant holder for a {@link SimpleTypeHolder} enriched with Mongo specific simple types. * * @author Oliver Gierke + * @author Christoph Strobl */ public abstract class MongoSimpleTypes { @@ -54,12 +57,17 @@ public abstract class MongoSimpleTypes { simpleTypes.add(Pattern.class); simpleTypes.add(Binary.class); simpleTypes.add(UUID.class); + + if (MongoClientVersion.isMongo34Driver()) { + simpleTypes + .add(ClassUtils.resolveClassName("org.bson.types.Decimal128", MongoSimpleTypes.class.getClassLoader())); + } + MONGO_SIMPLE_TYPES = Collections.unmodifiableSet(simpleTypes); } private static final Set> MONGO_SIMPLE_TYPES; public static final SimpleTypeHolder HOLDER = new SimpleTypeHolder(MONGO_SIMPLE_TYPES, true); - private MongoSimpleTypes() { - } + private MongoSimpleTypes() {} } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapreduce/GroupByResults.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapreduce/GroupByResults.java index a3dc65ee80..9b090b77f6 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapreduce/GroupByResults.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapreduce/GroupByResults.java @@ -1,11 +1,11 @@ /* - * Copyright 2011 - 2014 the original author or authors. + * Copyright 2011-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -27,6 +27,7 @@ * * @author Mark Pollack * @author Christoph Strobl + * @author Mark Paluch * @param The class in which the results are mapped onto, accessible via an {@link Iterator}. */ public class GroupByResults implements Iterable { @@ -40,10 +41,12 @@ public class GroupByResults implements Iterable { public GroupByResults(List mappedResults, DBObject rawResults) { - Assert.notNull(mappedResults); - Assert.notNull(rawResults); + Assert.notNull(mappedResults, "List of mapped results must not be null!"); + Assert.notNull(rawResults, "Raw results must not be null!"); + this.mappedResults = mappedResults; this.rawResults = rawResults; + parseKeys(); parseCount(); parseServerUsed(); diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapreduce/MapReduceResults.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapreduce/MapReduceResults.java index 4d7c1407eb..2827917528 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapreduce/MapReduceResults.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapreduce/MapReduceResults.java @@ -1,11 +1,11 @@ /* - * Copyright 2011-2015 the original author or authors. + * Copyright 2011-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -29,6 +29,7 @@ * @author Mark Pollack * @author Oliver Gierke * @author Christoph Strobl + * @author Mark Paluch * @param The class in which the results are mapped onto, accessible via an iterator. */ public class MapReduceResults implements Iterable { @@ -49,8 +50,8 @@ public class MapReduceResults implements Iterable { @Deprecated public MapReduceResults(List mappedResults, DBObject rawResults) { - Assert.notNull(mappedResults); - Assert.notNull(rawResults); + Assert.notNull(mappedResults, "List of mapped results must not be null!"); + Assert.notNull(rawResults, "Raw results must not be null!"); this.mappedResults = mappedResults; this.rawResults = rawResults; diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Criteria.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Criteria.java index 0836cdae9d..df4dd65566 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Criteria.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Criteria.java @@ -1,5 +1,5 @@ /* - * Copyright 2010-2015 the original author or authors. + * Copyright 2010-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -50,6 +50,7 @@ * @author Oliver Gierke * @author Thomas Darimont * @author Christoph Strobl + * @author Mark Paluch */ public class Criteria implements CriteriaDefinition { @@ -150,9 +151,9 @@ private boolean lastOperatorWasNot() { /** * Creates a criterion using the {@literal $ne} operator. * - * @see http://docs.mongodb.org/manual/reference/operator/query/ne/ * @param o * @return + * @see MongoDB Query operator: $ne */ public Criteria ne(Object o) { criteria.put("$ne", o); @@ -162,9 +163,9 @@ public Criteria ne(Object o) { /** * Creates a criterion using the {@literal $lt} operator. * - * @see http://docs.mongodb.org/manual/reference/operator/query/lt/ * @param o * @return + * @see MongoDB Query operator: $lt */ public Criteria lt(Object o) { criteria.put("$lt", o); @@ -174,9 +175,9 @@ public Criteria lt(Object o) { /** * Creates a criterion using the {@literal $lte} operator. * - * @see http://docs.mongodb.org/manual/reference/operator/query/lte/ * @param o * @return + * @see MongoDB Query operator: $lte */ public Criteria lte(Object o) { criteria.put("$lte", o); @@ -186,9 +187,9 @@ public Criteria lte(Object o) { /** * Creates a criterion using the {@literal $gt} operator. * - * @see http://docs.mongodb.org/manual/reference/operator/query/gt/ * @param o * @return + * @see MongoDB Query operator: $gt */ public Criteria gt(Object o) { criteria.put("$gt", o); @@ -198,9 +199,9 @@ public Criteria gt(Object o) { /** * Creates a criterion using the {@literal $gte} operator. * - * @see http://docs.mongodb.org/manual/reference/operator/query/gte/ * @param o * @return + * @see MongoDB Query operator: $gte */ public Criteria gte(Object o) { criteria.put("$gte", o); @@ -210,9 +211,9 @@ public Criteria gte(Object o) { /** * Creates a criterion using the {@literal $in} operator. * - * @see http://docs.mongodb.org/manual/reference/operator/query/in/ * @param o the values to match against * @return + * @see MongoDB Query operator: $in */ public Criteria in(Object... o) { if (o.length > 1 && o[1] instanceof Collection) { @@ -226,9 +227,9 @@ public Criteria in(Object... o) { /** * Creates a criterion using the {@literal $in} operator. * - * @see http://docs.mongodb.org/manual/reference/operator/query/in/ * @param c the collection containing the values to match against * @return + * @see MongoDB Query operator: $in */ public Criteria in(Collection c) { criteria.put("$in", c); @@ -238,9 +239,9 @@ public Criteria in(Collection c) { /** * Creates a criterion using the {@literal $nin} operator. * - * @see http://docs.mongodb.org/manual/reference/operator/query/nin/ * @param o * @return + * @see MongoDB Query operator: $nin */ public Criteria nin(Object... o) { return nin(Arrays.asList(o)); @@ -249,9 +250,9 @@ public Criteria nin(Object... o) { /** * Creates a criterion using the {@literal $nin} operator. * - * @see http://docs.mongodb.org/manual/reference/operator/query/nin/ * @param o * @return + * @see MongoDB Query operator: $nin */ public Criteria nin(Collection o) { criteria.put("$nin", o); @@ -261,10 +262,10 @@ public Criteria nin(Collection o) { /** * Creates a criterion using the {@literal $mod} operator. * - * @see http://docs.mongodb.org/manual/reference/operator/query/mod/ * @param value * @param remainder * @return + * @see MongoDB Query operator: $mod */ public Criteria mod(Number value, Number remainder) { List l = new ArrayList(); @@ -277,9 +278,9 @@ public Criteria mod(Number value, Number remainder) { /** * Creates a criterion using the {@literal $all} operator. * - * @see http://docs.mongodb.org/manual/reference/operator/query/all/ * @param o * @return + * @see MongoDB Query operator: $all */ public Criteria all(Object... o) { return all(Arrays.asList(o)); @@ -288,9 +289,9 @@ public Criteria all(Object... o) { /** * Creates a criterion using the {@literal $all} operator. * - * @see http://docs.mongodb.org/manual/reference/operator/query/all/ * @param o * @return + * @see MongoDB Query operator: $all */ public Criteria all(Collection o) { criteria.put("$all", o); @@ -300,9 +301,9 @@ public Criteria all(Collection o) { /** * Creates a criterion using the {@literal $size} operator. * - * @see http://docs.mongodb.org/manual/reference/operator/query/size/ * @param s * @return + * @see MongoDB Query operator: $size */ public Criteria size(int s) { criteria.put("$size", s); @@ -312,9 +313,9 @@ public Criteria size(int s) { /** * Creates a criterion using the {@literal $exists} operator. * - * @see http://docs.mongodb.org/manual/reference/operator/query/exists/ * @param b * @return + * @see MongoDB Query operator: $exists */ public Criteria exists(boolean b) { criteria.put("$exists", b); @@ -324,9 +325,9 @@ public Criteria exists(boolean b) { /** * Creates a criterion using the {@literal $type} operator. * - * @see http://docs.mongodb.org/manual/reference/operator/query/type/ * @param t * @return + * @see MongoDB Query operator: $type */ public Criteria type(int t) { criteria.put("$type", t); @@ -336,8 +337,8 @@ public Criteria type(int t) { /** * Creates a criterion using the {@literal $not} meta operator which affects the clause directly following * - * @see http://docs.mongodb.org/manual/reference/operator/query/not/ * @return + * @see MongoDB Query operator: $not */ public Criteria not() { return not(null); @@ -346,9 +347,9 @@ public Criteria not() { /** * Creates a criterion using the {@literal $not} operator. * - * @see http://docs.mongodb.org/manual/reference/operator/query/not/ * @param value * @return + * @see MongoDB Query operator: $not */ private Criteria not(Object value) { criteria.put("$not", value); @@ -358,9 +359,9 @@ private Criteria not(Object value) { /** * Creates a criterion using a {@literal $regex} operator. * - * @see http://docs.mongodb.org/manual/reference/operator/query/regex/ * @param re * @return + * @see MongoDB Query operator: $regex */ public Criteria regex(String re) { return regex(re, null); @@ -369,11 +370,10 @@ public Criteria regex(String re) { /** * Creates a criterion using a {@literal $regex} and {@literal $options} operator. * - * @see http://docs.mongodb.org/manual/reference/operator/query/regex/ - * @see http://docs.mongodb.org/manual/reference/operator/query/regex/#op._S_options * @param re * @param options * @return + * @see MongoDB Query operator: $regex */ public Criteria regex(String re, String options) { return regex(toPattern(re, options)); @@ -387,7 +387,7 @@ public Criteria regex(String re, String options) { */ public Criteria regex(Pattern pattern) { - Assert.notNull(pattern); + Assert.notNull(pattern, "Pattern must not be null!"); if (lastOperatorWasNot()) { return not(pattern); @@ -398,7 +398,9 @@ public Criteria regex(Pattern pattern) { } private Pattern toPattern(String regex, String options) { - Assert.notNull(regex); + + Assert.notNull(regex, "Regex string must not be null!"); + return Pattern.compile(regex, options == null ? 0 : BSON.regexFlags(options)); } @@ -406,13 +408,15 @@ private Pattern toPattern(String regex, String options) { * Creates a geospatial criterion using a {@literal $geoWithin $centerSphere} operation. This is only available for * Mongo 2.4 and higher. * - * @see http://docs.mongodb.org/manual/reference/operator/query/geoWithin/ - * @see http://docs.mongodb.org/manual/reference/operator/query/centerSphere/ * @param circle must not be {@literal null} * @return + * @see MongoDB Query operator: $geoWithin + * @see MongoDB Query operator: $centerSphere */ public Criteria withinSphere(Circle circle) { - Assert.notNull(circle); + + Assert.notNull(circle, "Circle must not be null!"); + criteria.put("$geoWithin", new GeoCommand(new Sphere(circle))); return this; } @@ -420,13 +424,14 @@ public Criteria withinSphere(Circle circle) { /** * Creates a geospatial criterion using a {@literal $geoWithin} operation. * - * @see http://docs.mongodb.org/manual/reference/operator/query/geoWithin/ * @param shape * @return + * @see MongoDB Query operator: $geoWithin */ public Criteria within(Shape shape) { - Assert.notNull(shape); + Assert.notNull(shape, "Shape must not be null!"); + criteria.put("$geoWithin", new GeoCommand(shape)); return this; } @@ -434,12 +439,14 @@ public Criteria within(Shape shape) { /** * Creates a geospatial criterion using a {@literal $near} operation. * - * @see http://docs.mongodb.org/manual/reference/operator/query/near/ * @param point must not be {@literal null} * @return + * @see MongoDB Query operator: $near */ public Criteria near(Point point) { - Assert.notNull(point); + + Assert.notNull(point, "Point must not be null!"); + criteria.put("$near", point); return this; } @@ -448,12 +455,14 @@ public Criteria near(Point point) { * Creates a geospatial criterion using a {@literal $nearSphere} operation. This is only available for Mongo 1.7 and * higher. * - * @see http://docs.mongodb.org/manual/reference/operator/query/nearSphere/ * @param point must not be {@literal null} * @return + * @see MongoDB Query operator: $nearSphere */ public Criteria nearSphere(Point point) { - Assert.notNull(point); + + Assert.notNull(point, "Point must not be null!"); + criteria.put("$nearSphere", point); return this; } @@ -477,9 +486,9 @@ public Criteria intersects(GeoJson geoJson) { /** * Creates a geo-spatial criterion using a {@literal $maxDistance} operation, for use with $near * - * @see http://docs.mongodb.org/manual/reference/operator/query/maxDistance/ * @param maxDistance * @return + * @see MongoDB Query operator: $maxDistance */ public Criteria maxDistance(double maxDistance) { @@ -514,9 +523,9 @@ public Criteria minDistance(double minDistance) { /** * Creates a criterion using the {@literal $elemMatch} operator * - * @see http://docs.mongodb.org/manual/reference/operator/query/elemMatch/ * @param c * @return + * @see MongoDB Query operator: $elemMatch */ public Criteria elemMatch(Criteria c) { criteria.put("$elemMatch", c.getCriteriaObject()); @@ -707,7 +716,7 @@ private boolean createNearCriteriaForCommand(String command, String operation, d return false; } - /* + /* * (non-Javadoc) * @see java.lang.Object#equals(java.lang.Object) */ @@ -770,7 +779,7 @@ private boolean isEqual(Object left, Object right) { return ObjectUtils.nullSafeEquals(left, right); } - /* + /* * (non-Javadoc) * @see java.lang.Object#hashCode() */ diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/NearQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/NearQuery.java index b847d192e6..22a571e90c 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/NearQuery.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/NearQuery.java @@ -1,11 +1,11 @@ /* - * Copyright 2011-2015 the original author or authors. + * Copyright 2011-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -49,11 +49,11 @@ public final class NearQuery { /** * Creates a new {@link NearQuery}. * - * @param point + * @param point must not be {@literal null}. */ private NearQuery(Point point, Metric metric) { - Assert.notNull(point); + Assert.notNull(point, "Point must not be null!"); this.point = point; this.spherical = false; @@ -108,7 +108,6 @@ public static NearQuery near(Point point) { * @return */ public static NearQuery near(Point point, Metric metric) { - Assert.notNull(point); return new NearQuery(point, metric); } @@ -185,7 +184,8 @@ public NearQuery maxDistance(double maxDistance) { */ public NearQuery maxDistance(double maxDistance, Metric metric) { - Assert.notNull(metric); + Assert.notNull(metric, "Metric must not be null!"); + return maxDistance(new Distance(maxDistance, metric)); } @@ -198,7 +198,7 @@ public NearQuery maxDistance(double maxDistance, Metric metric) { */ public NearQuery maxDistance(Distance distance) { - Assert.notNull(distance); + Assert.notNull(distance, "Distance must not be null!"); if (distance.getMetric() != Metrics.NEUTRAL) { this.spherical(true); @@ -241,7 +241,8 @@ public NearQuery minDistance(double minDistance) { */ public NearQuery minDistance(double minDistance, Metric metric) { - Assert.notNull(metric); + Assert.notNull(metric, "Metric must not be null!"); + return minDistance(new Distance(minDistance, metric)); } @@ -255,7 +256,7 @@ public NearQuery minDistance(double minDistance, Metric metric) { */ public NearQuery minDistance(Distance distance) { - Assert.notNull(distance); + Assert.notNull(distance, "Distance must not be null!"); if (distance.getMetric() != Metrics.NEUTRAL) { this.spherical(true); diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/TextCriteria.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/TextCriteria.java index 6fa51cb956..a39ac9ff31 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/TextCriteria.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/TextCriteria.java @@ -66,7 +66,7 @@ public static TextCriteria forDefaultLanguage() { /** * For a full list of supported languages see the mongodb reference manual for - * Text Search Languages. + * Text Search Languages. * * @param language * @return diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/TextQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/TextQuery.java index 8e54122cab..8808df8bca 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/TextQuery.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/TextQuery.java @@ -48,7 +48,7 @@ public TextQuery(String wordsAndPhrases) { /** * Creates new {@link TextQuery} in {@code language}.
        * For a full list of supported languages see the mongdodb reference manual for Text Search Languages. + * href="https://docs.mongodb.org/manual/reference/text-search-languages/">Text Search Languages. * * @param wordsAndPhrases * @param language @@ -62,7 +62,7 @@ public TextQuery(String wordsAndPhrases, String language) { /** * Creates new {@link TextQuery} using the {@code locale}s language.
        * For a full list of supported languages see the mongdodb reference manual for Text Search Languages. + * href="https://docs.mongodb.org/manual/reference/text-search-languages/">Text Search Languages. * * @param wordsAndPhrases * @param locale diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Update.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Update.java index 44f2718c44..5cc0e4f7a8 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Update.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Update.java @@ -1,5 +1,5 @@ /* - * Copyright 2010-2016 the original author or authors. + * Copyright 2010-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,6 +27,9 @@ import java.util.Set; import org.springframework.dao.InvalidDataAccessApiUsageException; +import org.springframework.data.domain.Sort; +import org.springframework.data.domain.Sort.Direction; +import org.springframework.data.domain.Sort.Order; import org.springframework.util.Assert; import org.springframework.util.StringUtils; @@ -44,6 +47,7 @@ * @author Thomas Darimont * @author Alexey Plotnik * @author Mark Paluch + * @author Pavel Vodrazka */ public class Update { @@ -102,10 +106,10 @@ public static Update fromDBObject(DBObject object, String... exclude) { /** * Update using the {@literal $set} update modifier * - * @see http://docs.mongodb.org/manual/reference/operator/update/set/ * @param key * @param value * @return + * @see MongoDB Update operator: $set */ public Update set(String key, Object value) { addMultiFieldOperation("$set", key, value); @@ -115,10 +119,10 @@ public Update set(String key, Object value) { /** * Update using the {@literal $setOnInsert} update modifier * - * @see http://docs.mongodb.org/manual/reference/operator/update/setOnInsert/ * @param key * @param value * @return + * @see MongoDB Update operator: $setOnInsert */ public Update setOnInsert(String key, Object value) { addMultiFieldOperation("$setOnInsert", key, value); @@ -128,9 +132,9 @@ public Update setOnInsert(String key, Object value) { /** * Update using the {@literal $unset} update modifier * - * @see http://docs.mongodb.org/manual/reference/operator/update/unset/ * @param key * @return + * @see MongoDB Update operator: $unset */ public Update unset(String key) { addMultiFieldOperation("$unset", key, 1); @@ -140,10 +144,10 @@ public Update unset(String key) { /** * Update using the {@literal $inc} update modifier * - * @see http://docs.mongodb.org/manual/reference/operator/update/inc/ * @param key * @param inc * @return + * @see MongoDB Update operator: $inc */ public Update inc(String key, Number inc) { addMultiFieldOperation("$inc", key, inc); @@ -153,10 +157,10 @@ public Update inc(String key, Number inc) { /** * Update using the {@literal $push} update modifier * - * @see http://docs.mongodb.org/manual/reference/operator/update/push/ * @param key * @param value * @return + * @see MongoDB Update operator: $push */ public Update push(String key, Object value) { addMultiFieldOperation("$push", key, value); @@ -168,10 +172,10 @@ public Update push(String key, Object value) { * Allows creation of {@code $push} command for single or multiple (using {@code $each}) values as well as using * {@code $position}. * - * @see http://docs.mongodb.org/manual/reference/operator/update/push/ - * @see http://docs.mongodb.org/manual/reference/operator/update/each/ * @param key * @return {@link PushOperatorBuilder} for given key + * @see MongoDB Update operator: $push + * @see MongoDB Update operator: $each */ public PushOperatorBuilder push(String key) { @@ -186,10 +190,10 @@ public PushOperatorBuilder push(String key) { * Note: In mongodb 2.4 the usage of {@code $pushAll} has been deprecated in favor of {@code $push $each}. * {@link #push(String)}) returns a builder that can be used to populate the {@code $each} object. * - * @see http://docs.mongodb.org/manual/reference/operator/update/pushAll/ * @param key * @param values * @return + * @see MongoDB Update operator: $pushAll */ public Update pushAll(String key, Object[] values) { addMultiFieldOperation("$pushAll", key, Arrays.copyOf(values, values.length)); @@ -211,10 +215,10 @@ public AddToSetBuilder addToSet(String key) { /** * Update using the {@literal $addToSet} update modifier * - * @see http://docs.mongodb.org/manual/reference/operator/update/addToSet/ * @param key * @param value * @return + * @see MongoDB Update operator: $addToSet */ public Update addToSet(String key, Object value) { addMultiFieldOperation("$addToSet", key, value); @@ -224,10 +228,10 @@ public Update addToSet(String key, Object value) { /** * Update using the {@literal $pop} update modifier * - * @see http://docs.mongodb.org/manual/reference/operator/update/pop/ * @param key * @param pos * @return + * @see MongoDB Update operator: $pop */ public Update pop(String key, Position pos) { addMultiFieldOperation("$pop", key, pos == Position.FIRST ? -1 : 1); @@ -237,10 +241,10 @@ public Update pop(String key, Position pos) { /** * Update using the {@literal $pull} update modifier * - * @see http://docs.mongodb.org/manual/reference/operator/update/pull/ * @param key * @param value * @return + * @see MongoDB Update operator: $pull */ public Update pull(String key, Object value) { addMultiFieldOperation("$pull", key, value); @@ -250,10 +254,10 @@ public Update pull(String key, Object value) { /** * Update using the {@literal $pullAll} update modifier * - * @see http://docs.mongodb.org/manual/reference/operator/update/pullAll/ * @param key * @param values * @return + * @see MongoDB Update operator: $pullAll */ public Update pullAll(String key, Object[] values) { addMultiFieldOperation("$pullAll", key, Arrays.copyOf(values, values.length)); @@ -263,10 +267,10 @@ public Update pullAll(String key, Object[] values) { /** * Update using the {@literal $rename} update modifier * - * @see http://docs.mongodb.org/manual/reference/operator/update/rename/ * @param oldName * @param newName * @return + * @see MongoDB Update operator: $rename */ public Update rename(String oldName, String newName) { addMultiFieldOperation("$rename", oldName, newName); @@ -276,10 +280,10 @@ public Update rename(String oldName, String newName) { /** * Update given key to current date using {@literal $currentDate} modifier. * - * @see http://docs.mongodb.org/manual/reference/operator/update/currentDate/ * @param key * @return * @since 1.6 + * @see MongoDB Update operator: $currentDate */ public Update currentDate(String key) { @@ -290,10 +294,10 @@ public Update currentDate(String key) { /** * Update given key to current date using {@literal $currentDate : { $type : "timestamp" }} modifier. * - * @see http://docs.mongodb.org/manual/reference/operator/update/currentDate/ * @param key * @return * @since 1.6 + * @see MongoDB Update operator: $currentDate */ public Update currentTimestamp(String key) { @@ -304,15 +308,15 @@ public Update currentTimestamp(String key) { /** * Multiply the value of given key by the given number. * - * @see http://docs.mongodb.org/manual/reference/operator/update/mul/ * @param key must not be {@literal null}. * @param multiplier must not be {@literal null}. * @return * @since 1.7 + * @see MongoDB Update operator: $mul */ public Update multiply(String key, Number multiplier) { - Assert.notNull(multiplier, "Multiplier must not be 'null'."); + Assert.notNull(multiplier, "Multiplier must not be null."); addMultiFieldOperation("$mul", key, multiplier.doubleValue()); return this; } @@ -320,16 +324,16 @@ public Update multiply(String key, Number multiplier) { /** * Update given key to the {@code value} if the {@code value} is greater than the current value of the field. * - * @see http://docs.mongodb.org/manual/reference/operator/update/max/ - * @see https://docs.mongodb.org/manual/reference/bson-types/#faq-dev-compare-order-for-bson-types * @param key must not be {@literal null}. * @param value must not be {@literal null}. * @return * @since 1.10 + * @see Comparison/Sort Order + * @see MongoDB Update operator: $max */ public Update max(String key, Object value) { - Assert.notNull(value, "Value for max operation must not be 'null'."); + Assert.notNull(value, "Value for max operation must not be null."); addMultiFieldOperation("$max", key, value); return this; } @@ -337,16 +341,16 @@ public Update max(String key, Object value) { /** * Update given key to the {@code value} if the {@code value} is less than the current value of the field. * - * @see http://docs.mongodb.org/manual/reference/operator/update/min/ - * @see https://docs.mongodb.org/manual/reference/bson-types/#faq-dev-compare-order-for-bson-types * @param key must not be {@literal null}. * @param value must not be {@literal null}. * @return * @since 1.10 + * @see Comparison/Sort Order + * @see MongoDB Update operator: $min */ public Update min(String key, Object value) { - Assert.notNull(value, "Value for min operation must not be 'null'."); + Assert.notNull(value, "Value for min operation must not be null."); addMultiFieldOperation("$min", key, value); return this; } @@ -661,6 +665,67 @@ public Object getValue() { } } + /** + * Implementation of {@link Modifier} representing {@code $sort}. + * + * @author Pavel Vodrazka + * @author Mark Paluch + * @since 1.10 + */ + private static class SortModifier implements Modifier { + + private final Object sort; + + /** + * Creates a new {@link SortModifier} instance given {@link Direction}. + * + * @param direction must not be {@literal null}. + */ + public SortModifier(Direction direction) { + + Assert.notNull(direction, "Direction must not be null!"); + this.sort = direction.isAscending() ? 1 : -1; + } + + /** + * Creates a new {@link SortModifier} instance given {@link Sort}. + * + * @param sort must not be {@literal null}. + */ + public SortModifier(Sort sort) { + + Assert.notNull(sort, "Sort must not be null!"); + + for (Order order : sort) { + + if (order.isIgnoreCase()) { + throw new IllegalArgumentException(String.format("Given sort contained an Order for %s with ignore case! " + + "MongoDB does not support sorting ignoring case currently!", order.getProperty())); + } + } + + this.sort = sort; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.query.Update.Modifier#getKey() + */ + @Override + public String getKey() { + return "$sort"; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.query.Update.Modifier#getValue() + */ + @Override + public Object getValue() { + return this.sort; + } + } + /** * Builder for creating {@code $push} modifiers * @@ -707,6 +772,36 @@ public PushOperatorBuilder slice(int count) { return this; } + /** + * Propagates {@code $sort} to {@code $push}. {@code $sort} requires the {@code $each} operator. Forces elements to + * be sorted by values in given {@literal direction}. + * + * @param direction must not be {@literal null}. + * @return never {@literal null}. + * @since 1.10 + */ + public PushOperatorBuilder sort(Direction direction) { + + Assert.notNull(direction, "Direction must not be null."); + this.modifiers.addModifier(new SortModifier(direction)); + return this; + } + + /** + * Propagates {@code $sort} to {@code $push}. {@code $sort} requires the {@code $each} operator. Forces document + * elements to be sorted in given {@literal order}. + * + * @param sort must not be {@literal null}. + * @return never {@literal null}. + * @since 1.10 + */ + public PushOperatorBuilder sort(Sort sort) { + + Assert.notNull(sort, "Sort must not be null."); + this.modifiers.addModifier(new SortModifier(sort)); + return this; + } + /** * Forces values to be added at the given {@literal position}. * diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/ExpressionNode.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/ExpressionNode.java index b323b9cf3b..9fb3f5b891 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/ExpressionNode.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/ExpressionNode.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,12 +23,15 @@ import org.springframework.expression.spel.ast.Literal; import org.springframework.expression.spel.ast.MethodReference; import org.springframework.expression.spel.ast.Operator; +import org.springframework.expression.spel.ast.OperatorNot; import org.springframework.util.Assert; /** * A value object for nodes in an expression. Allows iterating ove potentially available child {@link ExpressionNode}s. * * @author Oliver Gierke + * @author Christoph Strobl + * @author Mark Paluch */ public class ExpressionNode implements Iterable { @@ -79,6 +82,10 @@ public static ExpressionNode from(SpelNode node, ExpressionState state) { return new LiteralNode((Literal) node, state); } + if (node instanceof OperatorNot) { + return new NotOperatorNode((OperatorNot) node, state); + } + return new ExpressionNode(node, state); } @@ -122,6 +129,16 @@ public boolean isMathematicalOperation() { return false; } + /** + * Returns whether the {@link ExpressionNode} is a logical conjunction operation like {@code &&, ||}. + * + * @return + * @since 1.10 + */ + public boolean isLogicalOperator() { + return false; + } + /** * Returns whether the {@link ExpressionNode} is a literal. * @@ -157,7 +174,7 @@ public boolean hasChildren() { */ public ExpressionNode getChild(int index) { - Assert.isTrue(index >= 0); + Assert.isTrue(index >= 0, "Index must be greater or equal to zero!"); return from(node.getChild(index), state); } @@ -183,7 +200,7 @@ protected ExpressionNode from(SpelNode node) { return from(node, state); } - /* + /* * (non-Javadoc) * @see java.lang.Iterable#iterator() */ diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/LiteralNode.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/LiteralNode.java index 68c53860f3..1f55294524 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/LiteralNode.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/LiteralNode.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-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. @@ -15,7 +15,12 @@ */ package org.springframework.data.mongodb.core.spel; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + import org.springframework.expression.spel.ExpressionState; +import org.springframework.expression.spel.ast.BooleanLiteral; import org.springframework.expression.spel.ast.FloatLiteral; import org.springframework.expression.spel.ast.IntLiteral; import org.springframework.expression.spel.ast.Literal; @@ -26,13 +31,29 @@ /** * A node representing a literal in an expression. - * + * * @author Oliver Gierke + * @author Christoph Strobl */ public class LiteralNode extends ExpressionNode { + private static final Set> SUPPORTED_LITERAL_TYPES; private final Literal literal; + static { + + Set> supportedTypes = new HashSet>(7, 1); + supportedTypes.add(BooleanLiteral.class); + supportedTypes.add(FloatLiteral.class); + supportedTypes.add(IntLiteral.class); + supportedTypes.add(LongLiteral.class); + supportedTypes.add(NullLiteral.class); + supportedTypes.add(RealLiteral.class); + supportedTypes.add(StringLiteral.class); + + SUPPORTED_LITERAL_TYPES = Collections.unmodifiableSet(supportedTypes); + } + /** * Creates a new {@link LiteralNode} from the given {@link Literal} and {@link ExpressionState}. * @@ -66,7 +87,6 @@ public boolean isUnaryMinus(ExpressionNode parent) { */ @Override public boolean isLiteral() { - return literal instanceof FloatLiteral || literal instanceof RealLiteral || literal instanceof IntLiteral - || literal instanceof LongLiteral || literal instanceof StringLiteral || literal instanceof NullLiteral; + return SUPPORTED_LITERAL_TYPES.contains(literal.getClass()); } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/MethodReferenceNode.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/MethodReferenceNode.java index a32c49c3c1..fcdd5c15c8 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/MethodReferenceNode.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/MethodReferenceNode.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-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. @@ -15,44 +15,150 @@ */ package org.springframework.data.mongodb.core.spel; +import static org.springframework.data.mongodb.core.spel.MethodReferenceNode.AggregationMethodReference.*; + import java.util.Collections; import java.util.HashMap; import java.util.Map; import org.springframework.expression.spel.ExpressionState; import org.springframework.expression.spel.ast.MethodReference; +import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; /** * An {@link ExpressionNode} representing a method reference. - * + * * @author Oliver Gierke * @author Thomas Darimont + * @author Sebastien Gerard + * @author Christoph Strobl */ public class MethodReferenceNode extends ExpressionNode { - private static final Map FUNCTIONS; + private static final Map FUNCTIONS; static { - Map map = new HashMap(); - - map.put("concat", "$concat"); // Concatenates two strings. - map.put("strcasecmp", "$strcasecmp"); // Compares two strings and returns an integer that reflects the comparison. - map.put("substr", "$substr"); // Takes a string and returns portion of that string. - map.put("toLower", "$toLower"); // Converts a string to lowercase. - map.put("toUpper", "$toUpper"); // Converts a string to uppercase. - - map.put("dayOfYear", "$dayOfYear"); // Converts a date to a number between 1 and 366. - map.put("dayOfMonth", "$dayOfMonth"); // Converts a date to a number between 1 and 31. - map.put("dayOfWeek", "$dayOfWeek"); // Converts a date to a number between 1 and 7. - map.put("year", "$year"); // Converts a date to the full year. - map.put("month", "$month"); // Converts a date into a number between 1 and 12. - map.put("week", "$week"); // Converts a date into a number between 0 and 53 - map.put("hour", "$hour"); // Converts a date into a number between 0 and 23. - map.put("minute", "$minute"); // Converts a date into a number between 0 and 59. - map.put("second", "$second"); // Converts a date into a number between 0 and 59. May be 60 to account for leap - // seconds. - map.put("millisecond", "$millisecond"); // Returns the millisecond portion of a date as an integer between 0 and + Map map = new HashMap(); + + // BOOLEAN OPERATORS + map.put("and", arrayArgumentAggregationMethodReference().forOperator("$and")); + map.put("or", arrayArgumentAggregationMethodReference().forOperator("$or")); + map.put("not", arrayArgumentAggregationMethodReference().forOperator("$not")); + + // SET OPERATORS + map.put("setEquals", arrayArgumentAggregationMethodReference().forOperator("$setEquals")); + map.put("setIntersection", arrayArgumentAggregationMethodReference().forOperator("$setIntersection")); + map.put("setUnion", arrayArgumentAggregationMethodReference().forOperator("$setUnion")); + map.put("setDifference", arrayArgumentAggregationMethodReference().forOperator("$setDifference")); + // 2nd. + map.put("setIsSubset", arrayArgumentAggregationMethodReference().forOperator("$setIsSubset")); + map.put("anyElementTrue", arrayArgumentAggregationMethodReference().forOperator("$anyElementTrue")); + map.put("allElementsTrue", arrayArgumentAggregationMethodReference().forOperator("$allElementsTrue")); + + // COMPARISON OPERATORS + map.put("cmp", arrayArgumentAggregationMethodReference().forOperator("$cmp")); + map.put("eq", arrayArgumentAggregationMethodReference().forOperator("$eq")); + map.put("gt", arrayArgumentAggregationMethodReference().forOperator("$gt")); + map.put("gte", arrayArgumentAggregationMethodReference().forOperator("$gte")); + map.put("lt", arrayArgumentAggregationMethodReference().forOperator("$lt")); + map.put("lte", arrayArgumentAggregationMethodReference().forOperator("$lte")); + map.put("ne", arrayArgumentAggregationMethodReference().forOperator("$ne")); + + // ARITHMETIC OPERATORS + map.put("abs", singleArgumentAggregationMethodReference().forOperator("$abs")); + map.put("add", arrayArgumentAggregationMethodReference().forOperator("$add")); + map.put("ceil", singleArgumentAggregationMethodReference().forOperator("$ceil")); + map.put("divide", arrayArgumentAggregationMethodReference().forOperator("$divide")); + map.put("exp", singleArgumentAggregationMethodReference().forOperator("$exp")); + map.put("floor", singleArgumentAggregationMethodReference().forOperator("$floor")); + map.put("ln", singleArgumentAggregationMethodReference().forOperator("$ln")); + map.put("log", arrayArgumentAggregationMethodReference().forOperator("$log")); + map.put("log10", singleArgumentAggregationMethodReference().forOperator("$log10")); + map.put("mod", arrayArgumentAggregationMethodReference().forOperator("$mod")); + map.put("multiply", arrayArgumentAggregationMethodReference().forOperator("$multiply")); + map.put("pow", arrayArgumentAggregationMethodReference().forOperator("$pow")); + map.put("sqrt", singleArgumentAggregationMethodReference().forOperator("$sqrt")); + map.put("subtract", arrayArgumentAggregationMethodReference().forOperator("$subtract")); + map.put("trunc", singleArgumentAggregationMethodReference().forOperator("$trunc")); + + // STRING OPERATORS + map.put("concat", arrayArgumentAggregationMethodReference().forOperator("$concat")); + map.put("strcasecmp", arrayArgumentAggregationMethodReference().forOperator("$strcasecmp")); + map.put("substr", arrayArgumentAggregationMethodReference().forOperator("$substr")); + map.put("toLower", singleArgumentAggregationMethodReference().forOperator("$toLower")); + map.put("toUpper", singleArgumentAggregationMethodReference().forOperator("$toUpper")); + map.put("strcasecmp", arrayArgumentAggregationMethodReference().forOperator("$strcasecmp")); + map.put("indexOfBytes", arrayArgumentAggregationMethodReference().forOperator("$indexOfBytes")); + map.put("indexOfCP", arrayArgumentAggregationMethodReference().forOperator("$indexOfCP")); + map.put("split", arrayArgumentAggregationMethodReference().forOperator("$split")); + map.put("strLenBytes", singleArgumentAggregationMethodReference().forOperator("$strLenBytes")); + map.put("strLenCP", singleArgumentAggregationMethodReference().forOperator("$strLenCP")); + map.put("substrCP", arrayArgumentAggregationMethodReference().forOperator("$substrCP")); + + // TEXT SEARCH OPERATORS + map.put("meta", singleArgumentAggregationMethodReference().forOperator("$meta")); + + // ARRAY OPERATORS + map.put("arrayElemAt", arrayArgumentAggregationMethodReference().forOperator("$arrayElemAt")); + map.put("concatArrays", arrayArgumentAggregationMethodReference().forOperator("$concatArrays")); + map.put("filter", mapArgumentAggregationMethodReference().forOperator("$filter") // + .mappingParametersTo("input", "as", "cond")); + map.put("isArray", singleArgumentAggregationMethodReference().forOperator("$isArray")); + map.put("size", singleArgumentAggregationMethodReference().forOperator("$size")); + map.put("slice", arrayArgumentAggregationMethodReference().forOperator("$slice")); + map.put("reverseArray", singleArgumentAggregationMethodReference().forOperator("$reverseArray")); + map.put("reduce", mapArgumentAggregationMethodReference().forOperator("$reduce").mappingParametersTo("input", + "initialValue", "in")); + map.put("zip", mapArgumentAggregationMethodReference().forOperator("$zip").mappingParametersTo("inputs", + "useLongestLength", "defaults")); + map.put("in", arrayArgumentAggregationMethodReference().forOperator("$in")); + + // VARIABLE OPERATORS + map.put("map", mapArgumentAggregationMethodReference().forOperator("$map") // + .mappingParametersTo("input", "as", "in")); + map.put("let", mapArgumentAggregationMethodReference().forOperator("$let").mappingParametersTo("vars", "in")); + + // LITERAL OPERATORS + map.put("literal", singleArgumentAggregationMethodReference().forOperator("$literal")); + + // DATE OPERATORS + map.put("dayOfYear", singleArgumentAggregationMethodReference().forOperator("$dayOfYear")); + map.put("dayOfMonth", singleArgumentAggregationMethodReference().forOperator("$dayOfMonth")); + map.put("dayOfWeek", singleArgumentAggregationMethodReference().forOperator("$dayOfWeek")); + map.put("year", singleArgumentAggregationMethodReference().forOperator("$year")); + map.put("month", singleArgumentAggregationMethodReference().forOperator("$month")); + map.put("week", singleArgumentAggregationMethodReference().forOperator("$week")); + map.put("hour", singleArgumentAggregationMethodReference().forOperator("$hour")); + map.put("minute", singleArgumentAggregationMethodReference().forOperator("$minute")); + map.put("second", singleArgumentAggregationMethodReference().forOperator("$second")); + map.put("millisecond", singleArgumentAggregationMethodReference().forOperator("$millisecond")); + map.put("dateToString", mapArgumentAggregationMethodReference().forOperator("$dateToString") // + .mappingParametersTo("format", "date")); + map.put("isoDayOfWeek", singleArgumentAggregationMethodReference().forOperator("$isoDayOfWeek")); + map.put("isoWeek", singleArgumentAggregationMethodReference().forOperator("$isoWeek")); + map.put("isoWeekYear", singleArgumentAggregationMethodReference().forOperator("$isoWeekYear")); + + // CONDITIONAL OPERATORS + map.put("cond", mapArgumentAggregationMethodReference().forOperator("$cond") // + .mappingParametersTo("if", "then", "else")); + map.put("ifNull", arrayArgumentAggregationMethodReference().forOperator("$ifNull")); + + // GROUP OPERATORS + map.put("sum", arrayArgumentAggregationMethodReference().forOperator("$sum")); + map.put("avg", arrayArgumentAggregationMethodReference().forOperator("$avg")); + map.put("first", singleArgumentAggregationMethodReference().forOperator("$first")); + map.put("last", singleArgumentAggregationMethodReference().forOperator("$last")); + map.put("max", arrayArgumentAggregationMethodReference().forOperator("$max")); + map.put("min", arrayArgumentAggregationMethodReference().forOperator("$min")); + map.put("push", singleArgumentAggregationMethodReference().forOperator("$push")); + map.put("addToSet", singleArgumentAggregationMethodReference().forOperator("$addToSet")); + map.put("stdDevPop", arrayArgumentAggregationMethodReference().forOperator("$stdDevPop")); + map.put("stdDevSamp", arrayArgumentAggregationMethodReference().forOperator("$stdDevSamp")); + + // TYPE OPERATORS + map.put("type", singleArgumentAggregationMethodReference().forOperator("$type")); FUNCTIONS = Collections.unmodifiableMap(map); } @@ -63,13 +169,144 @@ public class MethodReferenceNode extends ExpressionNode { /** * Returns the name of the method. - * - * @return + * + * @Deprecated since 1.10. Please use {@link #getMethodReference()}. */ + @Deprecated public String getMethodName() { + AggregationMethodReference methodReference = getMethodReference(); + return methodReference != null ? methodReference.getMongoOperator() : null; + } + + /** + * Return the {@link AggregationMethodReference}. + * + * @return can be {@literal null}. + * @since 1.10 + */ + public AggregationMethodReference getMethodReference() { + String name = getName(); String methodName = name.substring(0, name.indexOf('(')); return FUNCTIONS.get(methodName); } + + /** + * @author Christoph Strobl + * @since 1.10 + */ + public static final class AggregationMethodReference { + + private final String mongoOperator; + private final ArgumentType argumentType; + private final String[] argumentMap; + + /** + * Creates new {@link AggregationMethodReference}. + * + * @param mongoOperator can be {@literal null}. + * @param argumentType can be {@literal null}. + * @param argumentMap can be {@literal null}. + */ + private AggregationMethodReference(String mongoOperator, ArgumentType argumentType, String[] argumentMap) { + + this.mongoOperator = mongoOperator; + this.argumentType = argumentType; + this.argumentMap = argumentMap; + } + + /** + * Get the MongoDB specific operator. + * + * @return can be {@literal null}. + */ + public String getMongoOperator() { + return this.mongoOperator; + } + + /** + * Get the {@link ArgumentType} used by the MongoDB. + * + * @return never {@literal null}. + */ + public ArgumentType getArgumentType() { + return this.argumentType; + } + + /** + * Get the property names in order order of appearance in resulting operation. + * + * @return never {@literal null}. + */ + public String[] getArgumentMap() { + return argumentMap != null ? argumentMap : new String[] {}; + } + + /** + * Create a new {@link AggregationMethodReference} for a {@link ArgumentType#SINGLE} argument. + * + * @return never {@literal null}. + */ + static AggregationMethodReference singleArgumentAggregationMethodReference() { + return new AggregationMethodReference(null, ArgumentType.SINGLE, null); + } + + /** + * Create a new {@link AggregationMethodReference} for an {@link ArgumentType#ARRAY} argument. + * + * @return never {@literal null}. + */ + static AggregationMethodReference arrayArgumentAggregationMethodReference() { + return new AggregationMethodReference(null, ArgumentType.ARRAY, null); + } + + /** + * Create a new {@link AggregationMethodReference} for a {@link ArgumentType#MAP} argument. + * + * @return never {@literal null}. + */ + static AggregationMethodReference mapArgumentAggregationMethodReference() { + return new AggregationMethodReference(null, ArgumentType.MAP, null); + } + + /** + * Create a new {@link AggregationMethodReference} for a given {@literal aggregationExpressionOperator} reusing + * previously set arguments. + * + * @param aggregationExpressionOperator should not be {@literal null}. + * @return never {@literal null}. + */ + AggregationMethodReference forOperator(String aggregationExpressionOperator) { + return new AggregationMethodReference(aggregationExpressionOperator, argumentType, argumentMap); + } + + /** + * Create a new {@link AggregationMethodReference} for mapping actual parameters within the AST to the given + * {@literal aggregationExpressionProperties} reusing previously set arguments.
        + * NOTE: Can only be applied to {@link AggregationMethodReference} of type + * {@link ArgumentType#MAP}. + * + * @param aggregationExpressionProperties should not be {@literal null}. + * @return never {@literal null}. + * @throws IllegalArgumentException + */ + AggregationMethodReference mappingParametersTo(String... aggregationExpressionProperties) { + + Assert.isTrue(ObjectUtils.nullSafeEquals(argumentType, ArgumentType.MAP), + "Parameter mapping can only be applied to AggregationMethodReference with MAPPED ArgumentType."); + return new AggregationMethodReference(mongoOperator, argumentType, aggregationExpressionProperties); + } + + /** + * The actual argument type to use when mapping parameters to MongoDB specific format. + * + * @author Christoph Strobl + * @since 1.10 + */ + public enum ArgumentType { + SINGLE, ARRAY, MAP + } + } + } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/NotOperatorNode.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/NotOperatorNode.java new file mode 100644 index 0000000000..1809307f51 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/NotOperatorNode.java @@ -0,0 +1,45 @@ +/* + * 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.spel; + +import org.springframework.expression.spel.ExpressionState; +import org.springframework.expression.spel.SpelNode; +import org.springframework.expression.spel.ast.OperatorNot; + +/** + * @author Christoph Strobl + * @since 1.10 + */ +public class NotOperatorNode extends ExpressionNode { + + private final OperatorNot operatorNode; + + /** + * Creates a new {@link ExpressionNode} from the given {@link OperatorNot} and {@link ExpressionState}. + * + * @param node must not be {@literal null}. + * @param state must not be {@literal null}. + */ + protected NotOperatorNode(OperatorNot node, ExpressionState state) { + + super(node, state); + this.operatorNode = node; + } + + public String getMongoOperator() { + return "$not"; + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/OperatorNode.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/OperatorNode.java index 55a11bd7ec..948c9544fe 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/OperatorNode.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/OperatorNode.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-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. @@ -17,44 +17,83 @@ import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; +import java.util.Set; import org.springframework.expression.spel.ExpressionState; +import org.springframework.expression.spel.ast.OpAnd; import org.springframework.expression.spel.ast.OpDivide; +import org.springframework.expression.spel.ast.OpEQ; +import org.springframework.expression.spel.ast.OpGE; +import org.springframework.expression.spel.ast.OpGT; +import org.springframework.expression.spel.ast.OpLE; +import org.springframework.expression.spel.ast.OpLT; import org.springframework.expression.spel.ast.OpMinus; import org.springframework.expression.spel.ast.OpModulus; import org.springframework.expression.spel.ast.OpMultiply; +import org.springframework.expression.spel.ast.OpNE; +import org.springframework.expression.spel.ast.OpOr; import org.springframework.expression.spel.ast.OpPlus; import org.springframework.expression.spel.ast.Operator; +import org.springframework.expression.spel.ast.OperatorPower; /** * An {@link ExpressionNode} representing an operator. - * + * * @author Oliver Gierke * @author Thomas Darimont + * @author Christoph Strobl */ public class OperatorNode extends ExpressionNode { private static final Map OPERATORS; + private static final Set SUPPORTED_MATH_OPERATORS; static { - Map map = new HashMap(6); + Map map = new HashMap(14, 1); map.put("+", "$add"); map.put("-", "$subtract"); map.put("*", "$multiply"); map.put("/", "$divide"); map.put("%", "$mod"); + map.put("^", "$pow"); + map.put("==", "$eq"); + map.put("!=", "$ne"); + map.put(">", "$gt"); + map.put(">=", "$gte"); + map.put("<", "$lt"); + map.put("<=", "$lte"); + + map.put("and", "$and"); + map.put("or", "$or"); OPERATORS = Collections.unmodifiableMap(map); + + Set set = new HashSet(12, 1); + set.add(OpMinus.class); + set.add(OpPlus.class); + set.add(OpMultiply.class); + set.add(OpDivide.class); + set.add(OpModulus.class); + set.add(OperatorPower.class); + set.add(OpNE.class); + set.add(OpEQ.class); + set.add(OpGT.class); + set.add(OpGE.class); + set.add(OpLT.class); + set.add(OpLE.class); + + SUPPORTED_MATH_OPERATORS = Collections.unmodifiableSet(set); } private final Operator operator; /** * Creates a new {@link OperatorNode} from the given {@link Operator} and {@link ExpressionState}. - * + * * @param node must not be {@literal null}. * @param state must not be {@literal null}. */ @@ -69,8 +108,16 @@ public class OperatorNode extends ExpressionNode { */ @Override public boolean isMathematicalOperation() { - return operator instanceof OpMinus || operator instanceof OpPlus || operator instanceof OpMultiply - || operator instanceof OpDivide || operator instanceof OpModulus; + return SUPPORTED_MATH_OPERATORS.contains(operator.getClass()); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.spel.ExpressionNode#isConjunctionOperator() + */ + @Override + public boolean isLogicalOperator() { + return operator instanceof OpOr || operator instanceof OpAnd; } /** @@ -88,6 +135,13 @@ public boolean isUnaryOperator() { * @return */ public String getMongoOperator() { + + if (!OPERATORS.containsKey(operator.getOperatorName())) { + throw new IllegalArgumentException(String.format( + "Unknown operator name. Cannot translate %s into its MongoDB aggregation function representation.", + operator.getOperatorName())); + } + return OPERATORS.get(operator.getOperatorName()); } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/gridfs/AntPath.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/gridfs/AntPath.java index de20bc35db..a9fa8f84bd 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/gridfs/AntPath.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/gridfs/AntPath.java @@ -1,5 +1,5 @@ /* - * Copyright 2011 the original author or authors. + * Copyright 2011-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,6 +24,7 @@ * Value object to abstract Ant paths. * * @author Oliver Gierke + * @author Mark Paluch */ class AntPath { @@ -38,7 +39,9 @@ class AntPath { * @param path must not be {@literal null}. */ public AntPath(String path) { - Assert.notNull(path); + + Assert.notNull(path, "Path must not be null!"); + this.path = path; } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/gridfs/GridFsOperations.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/gridfs/GridFsOperations.java index 3c94ef21ee..c1e16b5418 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/gridfs/GridFsOperations.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/gridfs/GridFsOperations.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2014 the original author or authors. + * Copyright 2011-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -121,7 +121,7 @@ public interface GridFsOperations extends ResourcePatternResolver { * Returns all files matching the given query. Note, that currently {@link Sort} criterias defined at the * {@link Query} will not be regarded as MongoDB does not support ordering for GridFS file access. * - * @see https://jira.mongodb.org/browse/JAVA-431 + * @see MongoDB Jira: JAVA-431 * @param query * @return */ diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/gridfs/GridFsTemplate.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/gridfs/GridFsTemplate.java index e523e8855a..602cc7b5ba 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/gridfs/GridFsTemplate.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/gridfs/GridFsTemplate.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2016 the original author or authors. + * Copyright 2011-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -46,6 +46,7 @@ * @author Thomas Darimont * @author Martin Baumgartner * @author Christoph Strobl + * @author Mark Paluch */ public class GridFsTemplate implements GridFsOperations, ResourcePatternResolver { @@ -73,8 +74,8 @@ public GridFsTemplate(MongoDbFactory dbFactory, MongoConverter converter) { */ public GridFsTemplate(MongoDbFactory dbFactory, MongoConverter converter, String bucket) { - Assert.notNull(dbFactory); - Assert.notNull(converter); + Assert.notNull(dbFactory, "MongoDbFactory must not be null!"); + Assert.notNull(converter, "MongoConverter must not be null!"); this.dbFactory = dbFactory; this.converter = converter; @@ -156,7 +157,7 @@ public GridFSFile store(InputStream content, String filename, DBObject metadata) */ public GridFSFile store(InputStream content, String filename, String contentType, DBObject metadata) { - Assert.notNull(content); + Assert.notNull(content, "InputStream must not be null!"); GridFSInputFile file = getGridFs().createFile(content); diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/CountQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/CountQuery.java new file mode 100644 index 0000000000..c3341f3099 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/CountQuery.java @@ -0,0 +1,48 @@ +/* + * 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.repository; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.core.annotation.AliasFor; + +/** + * Annotation to declare finder count queries directly on repository methods. Both attributes allow using a placeholder + * notation of {@code ?0}, {@code ?1} and so on. + * + * @author Fırat KÜÇÜK + * @author Oliver Gierke + * @since 1.10 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE }) +@Documented +@Query(count = true) +public @interface CountQuery { + + /** + * Takes a MongoDB JSON string to define the actual query to be executed. This one will take precedence over the + * method name then. Alias for {@link Query#value}. + * + * @return + */ + @AliasFor(annotation = Query.class) + String value() default ""; +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/DeleteQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/DeleteQuery.java new file mode 100644 index 0000000000..d33a18e950 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/DeleteQuery.java @@ -0,0 +1,48 @@ +/* + * 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.repository; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.core.annotation.AliasFor; + +/** + * Annotation to declare finder delete queries directly on repository methods. Both attributes allow using a placeholder + * notation of {@code ?0}, {@code ?1} and so on. + * + * @author Fırat KÜÇÜK + * @author Oliver Gierke + * @since 1.10 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE }) +@Documented +@Query(delete = true) +public @interface DeleteQuery { + + /** + * Takes a MongoDB JSON string to define the actual query to be executed. This one will take precedence over the + * method name then. Alias for {@link Query#value}. + * + * @return + */ + @AliasFor(annotation = Query.class) + String value() default ""; +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/ExistsQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/ExistsQuery.java new file mode 100644 index 0000000000..de937374d9 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/ExistsQuery.java @@ -0,0 +1,47 @@ +/* + * 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.repository; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.core.annotation.AliasFor; + +/** + * Annotation to declare finder exists queries directly on repository methods. Both attributes allow using a placeholder + * notation of {@code ?0}, {@code ?1} and so on. + * + * @author Mark Paluch + * @since 1.10 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE }) +@Documented +@Query(exists = true) +public @interface ExistsQuery { + + /** + * Takes a MongoDB JSON string to define the actual query to be executed. This one will take precedence over the + * method name then. Alias for {@link Query#value}. + * + * @return + */ + @AliasFor(annotation = Query.class) + String value() default ""; +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/MongoRepository.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/MongoRepository.java index 5361f3d6c6..35b3e6e31c 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/MongoRepository.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/MongoRepository.java @@ -1,5 +1,5 @@ /* - * Copyright 2010-2016 the original author or authors. + * Copyright 2010-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -58,7 +58,7 @@ public interface MongoRepository List findAll(Sort sort); /** - * Inserts the given a given entity. Assumes the instance to be new to be able to apply insertion optimizations. Use + * Inserts the given entity. Assumes the instance to be new to be able to apply insertion optimizations. Use * the returned instance for further operations as the save operation might have changed the entity instance * completely. Prefer using {@link #save(Object)} instead to avoid the usage of store-specific API. * diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/Query.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/Query.java index 97fb25ff16..a0ad7a905d 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/Query.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/Query.java @@ -62,6 +62,14 @@ */ boolean count() default false; + /** + * Returns whether the query defined should be executed as exists projection. + * + * @since 1.10 + * @return + */ + boolean exists() default false; + /** * Returns whether the query should delete matching documents. * diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/cdi/MongoRepositoryBean.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/cdi/MongoRepositoryBean.java index 0a43c72b2a..6a43fa025d 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/cdi/MongoRepositoryBean.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/cdi/MongoRepositoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2014 the original author or authors. + * Copyright 2012-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -53,11 +53,11 @@ public MongoRepositoryBean(Bean operations, Set qua super(qualifiers, repositoryType, beanManager, detector); - Assert.notNull(operations); + Assert.notNull(operations, "MongoOperations bean must not be null!"); this.operations = operations; } - /* + /* * (non-Javadoc) * @see org.springframework.data.repository.cdi.CdiRepositoryBean#create(javax.enterprise.context.spi.CreationalContext, java.lang.Class) */ diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractMongoQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractMongoQuery.java index d6b65be255..d59efb0450 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractMongoQuery.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractMongoQuery.java @@ -20,7 +20,9 @@ import org.springframework.data.mongodb.core.MongoOperations; import org.springframework.data.mongodb.core.query.Query; import org.springframework.data.mongodb.repository.query.MongoQueryExecution.CollectionExecution; +import org.springframework.data.mongodb.repository.query.MongoQueryExecution.CountExecution; import org.springframework.data.mongodb.repository.query.MongoQueryExecution.DeleteExecution; +import org.springframework.data.mongodb.repository.query.MongoQueryExecution.ExistsExecution; import org.springframework.data.mongodb.repository.query.MongoQueryExecution.GeoNearExecution; import org.springframework.data.mongodb.repository.query.MongoQueryExecution.PagedExecution; import org.springframework.data.mongodb.repository.query.MongoQueryExecution.PagingGeoNearExecution; @@ -40,6 +42,7 @@ * @author Oliver Gierke * @author Thomas Darimont * @author Christoph Strobl + * @author Mark Paluch */ public abstract class AbstractMongoQuery implements RepositoryQuery { @@ -123,8 +126,12 @@ private MongoQueryExecution getExecutionToWrap(Query query, MongoParameterAccess return new CollectionExecution(operations, accessor.getPageable()); } else if (method.isPageQuery()) { return new PagedExecution(operations, accessor.getPageable()); + } else if (isCountQuery()) { + return new CountExecution(operations); + } else if (isExistsQuery()) { + return new ExistsExecution(operations); } else { - return new SingleEntityExecution(operations, isCountQuery()); + return new SingleEntityExecution(operations); } } @@ -164,6 +171,14 @@ protected Query createCountQuery(ConvertingParameterAccessor accessor) { */ protected abstract boolean isCountQuery(); + /** + * Returns whether the query should get an exists projection applied. + * + * @return + * @since 1.10 + */ + protected abstract boolean isExistsQuery(); + /** * Return weather the query should delete matching documents. * diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ConvertingParameterAccessor.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ConvertingParameterAccessor.java index 15d0cd25e6..f49f4d69d6 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ConvertingParameterAccessor.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ConvertingParameterAccessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2015 the original author or authors. + * Copyright 2011-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -42,6 +42,7 @@ * @author Oliver Gierke * @author Christoph Strobl * @author Thomas Darimont + * @author Mark Paluch */ public class ConvertingParameterAccessor implements MongoParameterAccessor { @@ -56,41 +57,41 @@ public class ConvertingParameterAccessor implements MongoParameterAccessor { */ public ConvertingParameterAccessor(MongoWriter writer, MongoParameterAccessor delegate) { - Assert.notNull(writer); - Assert.notNull(delegate); + Assert.notNull(writer, "MongoWriter must not be null!"); + Assert.notNull(delegate, "MongoParameterAccessor must not be null!"); this.writer = writer; this.delegate = delegate; } /* - * (non-Javadoc) - * - * @see java.lang.Iterable#iterator() - */ + * (non-Javadoc) + * + * @see java.lang.Iterable#iterator() + */ public PotentiallyConvertingIterator iterator() { return new ConvertingIterator(delegate.iterator()); } /* - * (non-Javadoc) - * - * @see org.springframework.data.repository.query.ParameterAccessor#getPageable() - */ + * (non-Javadoc) + * + * @see org.springframework.data.repository.query.ParameterAccessor#getPageable() + */ public Pageable getPageable() { return delegate.getPageable(); } /* - * (non-Javadoc) - * - * @see org.springframework.data.repository.query.ParameterAccessor#getSort() - */ + * (non-Javadoc) + * + * @see org.springframework.data.repository.query.ParameterAccessor#getSort() + */ public Sort getSort() { return delegate.getSort(); } - /* + /* * (non-Javadoc) * @see org.springframework.data.repository.query.ParameterAccessor#getDynamicProjection() */ @@ -107,7 +108,7 @@ public Object getBindableValue(int index) { return getConvertedValue(delegate.getBindableValue(index), null); } - /* + /* * (non-Javadoc) * @see org.springframework.data.mongodb.repository.query.MongoParameterAccessor#getDistanceRange() */ @@ -116,7 +117,7 @@ public Range getDistanceRange() { return delegate.getDistanceRange(); } - /* + /* * (non-Javadoc) * @see org.springframework.data.mongodb.repository.MongoParameterAccessor#getGeoNearLocation() */ @@ -143,7 +144,7 @@ private Object getConvertedValue(Object value, TypeInformation typeInformatio return writer.convertToMongoType(value, typeInformation == null ? null : typeInformation.getActualType()); } - /* + /* * (non-Javadoc) * @see org.springframework.data.repository.query.ParameterAccessor#hasBindableNullValue() */ @@ -185,7 +186,7 @@ public Object next() { return delegate.next(); } - /* + /* * (non-Javadoc) * @see org.springframework.data.mongodb.repository.ConvertingParameterAccessor.PotentiallConvertingIterator#nextConverted() */ diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ExpressionEvaluatingParameterBinder.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ExpressionEvaluatingParameterBinder.java index 10c34bb391..154ecefdf8 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ExpressionEvaluatingParameterBinder.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ExpressionEvaluatingParameterBinder.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 the original author or authors. + * Copyright 2015-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,8 +15,17 @@ */ package org.springframework.data.mongodb.repository.query; -import java.util.Collections; +import lombok.EqualsAndHashCode; +import lombok.Value; +import lombok.experimental.UtilityClass; + +import java.util.ArrayList; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import javax.xml.bind.DatatypeConverter; @@ -30,15 +39,17 @@ import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; +import com.mongodb.DBObject; import com.mongodb.util.JSON; /** - * {@link ExpressionEvaluatingParameterBinder} allows to evaluate, convert and bind parameters to placholders within a + * {@link ExpressionEvaluatingParameterBinder} allows to evaluate, convert and bind parameters to placeholders within a * {@link String}. * * @author Christoph Strobl * @author Thomas Darimont * @author Oliver Gierke + * @author Mark Paluch * @since 1.9 */ class ExpressionEvaluatingParameterBinder { @@ -85,7 +96,7 @@ public String bind(String raw, MongoParameterAccessor accessor, BindingContext b * * @param input must not be {@literal null} or empty. * @param accessor must not be {@literal null}. - * @param bindings must not be {@literal null}. + * @param bindingContext must not be {@literal null}. * @return */ private String replacePlaceholders(String input, MongoParameterAccessor accessor, BindingContext bindingContext) { @@ -94,47 +105,82 @@ private String replacePlaceholders(String input, MongoParameterAccessor accessor return input; } - boolean isCompletlyParameterizedQuery = input.matches("^\\?\\d+$"); - StringBuilder result = new StringBuilder(input); + if (input.matches("^\\?\\d+$")) { + return getParameterValueForBinding(accessor, bindingContext.getParameters(), + bindingContext.getBindings().iterator().next()); + } + + Matcher matcher = createReplacementPattern(bindingContext.getBindings()).matcher(input); + StringBuffer buffer = new StringBuffer(); - for (ParameterBinding binding : bindingContext.getBindings()) { + int parameterIndex = 0; + while (matcher.find()) { - String parameter = binding.getParameter(); - int idx = result.indexOf(parameter); + Placeholder placeholder = extractPlaceholder(parameterIndex++, matcher); + ParameterBinding binding = bindingContext.getBindingFor(placeholder); + String valueForBinding = getParameterValueForBinding(accessor, bindingContext.getParameters(), binding); - if (idx == -1) { - continue; + // appendReplacement does not like unescaped $ sign and others, so we need to quote that stuff first + matcher.appendReplacement(buffer, Matcher.quoteReplacement(valueForBinding)); + if (StringUtils.hasText(placeholder.getSuffix())) { + buffer.append(placeholder.getSuffix()); } - String valueForBinding = getParameterValueForBinding(accessor, bindingContext.getParameters(), binding); + if (binding.isQuoted() || placeholder.isQuoted()) { + postProcessQuotedBinding(buffer, valueForBinding, + !binding.isExpression() ? accessor.getBindableValue(binding.getParameterIndex()) : null, + binding.isExpression()); + } + } - int start = idx; - int end = idx + parameter.length(); + matcher.appendTail(buffer); + return buffer.toString(); + } + + /** + * Sanitize String binding by replacing single quoted values with double quotes which prevents potential single quotes + * contained in replacement to interfere with the Json parsing. Also take care of complex objects by removing the + * quotation entirely. + * + * @param buffer the {@link StringBuffer} to operate upon. + * @param valueForBinding the actual binding value. + * @param raw the raw binding value + * @param isExpression {@literal true} if the binding value results from a SpEL expression. + */ + private void postProcessQuotedBinding(StringBuffer buffer, String valueForBinding, Object raw, boolean isExpression) { - // If the value to bind is an object literal we need to remove the quoting around the expression insertion point. - if (valueForBinding.startsWith("{") && !isCompletlyParameterizedQuery) { + int quotationMarkIndex = buffer.length() - valueForBinding.length() - 1; + char quotationMark = buffer.charAt(quotationMarkIndex); - // Is the insertion point actually surrounded by quotes? - char beforeStart = result.charAt(start - 1); - char afterEnd = result.charAt(end); + while (quotationMark != '\'' && quotationMark != '"') { - if ((beforeStart == '\'' || beforeStart == '"') && (afterEnd == '\'' || afterEnd == '"')) { + quotationMarkIndex--; - // Skip preceding and following quote - start -= 1; - end += 1; - } + if (quotationMarkIndex < 0) { + throw new IllegalArgumentException("Could not find opening quotes for quoted parameter"); } - result.replace(start, end, valueForBinding); + quotationMark = buffer.charAt(quotationMarkIndex); } - return result.toString(); + // remove quotation char before the complex object string + if (valueForBinding.startsWith("{") && (raw instanceof DBObject || isExpression)) { + + buffer.deleteCharAt(quotationMarkIndex); + + } else { + + if (quotationMark == '\'') { + buffer.replace(quotationMarkIndex, quotationMarkIndex + 1, "\""); + } + + buffer.append("\""); + } } /** * Returns the serialized value to be used for the given {@link ParameterBinding}. - * + * * @param accessor must not be {@literal null}. * @param parameters * @param binding must not be {@literal null}. @@ -148,7 +194,12 @@ private String getParameterValueForBinding(MongoParameterAccessor accessor, Mong : accessor.getBindableValue(binding.getParameterIndex()); if (value instanceof String && binding.isQuoted()) { - return (String) value; + + if (binding.isExpression() && ((String) value).startsWith("{")) { + return (String) value; + } + + return QuotedString.unquote(JSON.serialize(value)); } if (value instanceof byte[]) { @@ -167,7 +218,7 @@ private String getParameterValueForBinding(MongoParameterAccessor accessor, Mong /** * Evaluates the given {@code expressionString}. - * + * * @param expressionString must not be {@literal null} or empty. * @param parameters must not be {@literal null}. * @param parameterValues must not be {@literal null}. @@ -181,25 +232,83 @@ private Object evaluateExpression(String expressionString, MongoParameters param return expression.getValue(evaluationContext, Object.class); } + /** + * Creates a replacement {@link Pattern} for all {@link ParameterBinding#getParameter() binding parameters} including + * a potentially trailing quotation mark. + * + * @param bindings + * @return + */ + private Pattern createReplacementPattern(List bindings) { + + StringBuilder regex = new StringBuilder(); + + for (ParameterBinding binding : bindings) { + + regex.append("|"); + regex.append("(" + Pattern.quote(binding.getParameter()) + ")"); + regex.append("(\\W?['\"])?"); // potential quotation char (as in { foo : '?0' }). + } + + return Pattern.compile(regex.substring(1)); + } + + /** + * Extract the placeholder stripping any trailing trailing quotation mark that might have resulted from the + * {@link #createReplacementPattern(List) pattern} used. + * + * @param parameterIndex The actual parameter index. + * @param matcher The actual {@link Matcher}. + * @return + */ + private Placeholder extractPlaceholder(int parameterIndex, Matcher matcher) { + + if (matcher.groupCount() > 1) { + + String rawPlaceholder = matcher.group(parameterIndex * 2 + 1); + String suffix = matcher.group(parameterIndex * 2 + 2); + + if (!StringUtils.hasText(rawPlaceholder)) { + + rawPlaceholder = matcher.group(); + suffix = "" + rawPlaceholder.charAt(rawPlaceholder.length() - 1); + if (QuotedString.endsWithQuote(rawPlaceholder)) { + rawPlaceholder = QuotedString.unquoteSuffix(rawPlaceholder); + } + } + + if (StringUtils.hasText(suffix)) { + + boolean quoted = QuotedString.endsWithQuote(suffix); + + return Placeholder.of(parameterIndex, rawPlaceholder, quoted, + quoted ? QuotedString.unquoteSuffix(suffix) : suffix); + } + } + + return Placeholder.of(parameterIndex, matcher.group(), false, null); + } + /** * @author Christoph Strobl + * @author Mark Paluch * @since 1.9 */ static class BindingContext { final MongoParameters parameters; - final List bindings; + final Map bindings; /** * Creates new {@link BindingContext}. - * + * * @param parameters * @param bindings */ public BindingContext(MongoParameters parameters, List bindings) { this.parameters = parameters; - this.bindings = bindings; + this.bindings = mapBindings(bindings); } /** @@ -211,21 +320,113 @@ boolean hasBindings() { /** * Get unmodifiable list of {@link ParameterBinding}s. - * + * * @return never {@literal null}. */ public List getBindings() { - return Collections.unmodifiableList(bindings); + return new ArrayList(bindings.values()); + } + + /** + * Get the concrete {@link ParameterBinding} for a given {@literal placeholder}. + * + * @param placeholder must not be {@literal null}. + * @return + * @throws java.util.NoSuchElementException + * @since 1.10 + */ + ParameterBinding getBindingFor(Placeholder placeholder) { + + if (!bindings.containsKey(placeholder)) { + throw new NoSuchElementException(String.format("Could not to find binding for placeholder '%s'.", placeholder)); + } + + return bindings.get(placeholder); } /** * Get the associated {@link MongoParameters}. - * + * * @return */ public MongoParameters getParameters() { return parameters; } + private static Map mapBindings(List bindings) { + + Map map = new LinkedHashMap(bindings.size(), 1); + + int parameterIndex = 0; + for (ParameterBinding binding : bindings) { + map.put(Placeholder.of(parameterIndex++, binding.getParameter(), binding.isQuoted(), null), binding); + } + + return map; + } + } + + /** + * Encapsulates a quoted/unquoted parameter placeholder. + * + * @author Mark Paluch + * @since 1.9 + */ + @Value(staticConstructor = "of") + @EqualsAndHashCode(exclude = { "quoted", "suffix" }) + static class Placeholder { + + private int parameterIndex; + private final String parameter; + private final boolean quoted; + private final String suffix; + + /* + * (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return quoted ? String.format("'%s'", parameter + (suffix != null ? suffix : "")) + : parameter + (suffix != null ? suffix : ""); + } + + } + + /** + * Utility to handle quoted strings using single/double quotes. + * + * @author Mark Paluch + */ + @UtilityClass + static class QuotedString { + + /** + * @param string + * @return {@literal true} if {@literal string} ends with a single/double quote. + */ + static boolean endsWithQuote(String string) { + return string.endsWith("'") || string.endsWith("\""); + } + + /** + * Remove trailing quoting from {@literal quoted}. + * + * @param quoted + * @return {@literal quoted} with removed quotes. + */ + public static String unquoteSuffix(String quoted) { + return quoted.substring(0, quoted.length() - 1); + } + + /** + * Remove leading and trailing quoting from {@literal quoted}. + * + * @param quoted + * @return {@literal quoted} with removed quotes. + */ + public static String unquote(String quoted) { + return quoted.substring(1, quoted.length() - 1); + } } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryCreator.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryCreator.java index 9c5de69ae9..769b2da371 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryCreator.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryCreator.java @@ -1,5 +1,5 @@ /* - * Copyright 2010-2016 the original author or authors. + * Copyright 2010-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -47,6 +47,7 @@ import org.springframework.data.repository.query.parser.Part.Type; import org.springframework.data.repository.query.parser.PartTree; import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; /** * Custom query creator to create Mongo criterias. @@ -54,6 +55,7 @@ * @author Oliver Gierke * @author Thomas Darimont * @author Christoph Strobl + * @author Edward Prentice */ class MongoQueryCreator extends AbstractQueryCreator { @@ -91,7 +93,7 @@ public MongoQueryCreator(PartTree tree, ConvertingParameterAccessor accessor, super(tree, accessor); - Assert.notNull(context); + Assert.notNull(context, "MappingContext must not be null!"); this.accessor = accessor; this.isGeoNearQuery = isGeoNearQuery; @@ -298,7 +300,7 @@ private Criteria createLikeRegexCriteriaOrThrow(Part part, MongoPersistentProper criteria = criteria.not(); } - return addAppropriateLikeRegexTo(criteria, part, parameters.next().toString()); + return addAppropriateLikeRegexTo(criteria, part, parameters.next()); case NEVER: // intentional no-op @@ -326,7 +328,7 @@ private Criteria createContainingCriteria(Part part, MongoPersistentProperty pro return criteria.in(nextAsArray(parameters)); } - return addAppropriateLikeRegexTo(criteria, part, parameters.next().toString()); + return addAppropriateLikeRegexTo(criteria, part, parameters.next()); } /** @@ -337,9 +339,15 @@ private Criteria createContainingCriteria(Part part, MongoPersistentProperty pro * @param value * @return the criteria extended with the regex. */ - private Criteria addAppropriateLikeRegexTo(Criteria criteria, Part part, String value) { + private Criteria addAppropriateLikeRegexTo(Criteria criteria, Part part, Object value) { - return criteria.regex(toLikeRegex(value, part), toRegexOptions(part)); + if (value == null) { + + throw new IllegalArgumentException(String.format( + "Argument for creating $regex pattern for property '%s' must not be null!", part.getProperty().getSegment())); + } + + return criteria.regex(toLikeRegex(value.toString(), part), toRegexOptions(part)); } /** @@ -369,8 +377,10 @@ private String toRegexOptions(Part part) { */ @SuppressWarnings("unchecked") private T nextAs(Iterator iterator, Class type) { + Object parameter = iterator.next(); - if (parameter.getClass().isAssignableFrom(type)) { + + if (ClassUtils.isAssignable(type, parameter.getClass())) { return (T) parameter; } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryExecution.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryExecution.java index 76e98e8c7f..86c8ca1d39 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryExecution.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryExecution.java @@ -163,7 +163,6 @@ public long get() { static final class SingleEntityExecution implements MongoQueryExecution { private final MongoOperations operations; - private final boolean countProjection; /* * (non-Javadoc) @@ -171,7 +170,50 @@ static final class SingleEntityExecution implements MongoQueryExecution { */ @Override public Object execute(Query query, Class type, String collection) { - return countProjection ? operations.count(query, type, collection) : operations.findOne(query, type, collection); + return operations.findOne(query, type, collection); + } + } + + /** + * {@link MongoQueryExecution} to perform a count projection. + * + * @author Oliver Gierke + * @author Mark Paluch + * @since 1.10 + */ + @RequiredArgsConstructor + static final class CountExecution implements MongoQueryExecution { + + private final MongoOperations operations; + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.repository.query.AbstractMongoQuery.Execution#execute(org.springframework.data.mongodb.core.query.Query, java.lang.Class, java.lang.String) + */ + @Override + public Object execute(Query query, Class type, String collection) { + return operations.count(query, type, collection); + } + } + + /** + * {@link MongoQueryExecution} to perform an exists projection. + * + * @author Mark Paluch + * @since 1.10 + */ + @RequiredArgsConstructor + static final class ExistsExecution implements MongoQueryExecution { + + private final MongoOperations operations; + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.repository.query.AbstractMongoQuery.Execution#execute(org.springframework.data.mongodb.core.query.Query, java.lang.Class, java.lang.String) + */ + @Override + public Object execute(Query query, Class type, String collection) { + return operations.exists(query, type, collection); } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/PartTreeMongoQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/PartTreeMongoQuery.java index 4c1da7bd9b..f97b64afdb 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/PartTreeMongoQuery.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/PartTreeMongoQuery.java @@ -40,6 +40,7 @@ * @author Oliver Gierke * @author Christoph Strobl * @author Thomas Darimont + * @author Mark Paluch */ public class PartTreeMongoQuery extends AbstractMongoQuery { @@ -141,6 +142,15 @@ protected boolean isCountQuery() { return tree.isCountProjection(); } + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.repository.query.AbstractMongoQuery#isExistsQuery() + */ + @Override + protected boolean isExistsQuery() { + return tree.isExistsProjection(); + } + /* * (non-Javadoc) * @see org.springframework.data.mongodb.repository.query.AbstractMongoQuery#isDeleteQuery() diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQuery.java index 33d2e15e7d..c79f872ad1 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQuery.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQuery.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2015 the original author or authors. + * Copyright 2011-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. @@ -42,16 +42,18 @@ * @author Oliver Gierke * @author Christoph Strobl * @author Thomas Darimont + * @author Mark Paluch */ public class StringBasedMongoQuery extends AbstractMongoQuery { - private static final String COUND_AND_DELETE = "Manually defined query for %s cannot be both a count and delete query at the same time!"; + private static final String COUNT_EXISTS_AND_DELETE = "Manually defined query for %s cannot be a count and exists or delete query at the same time!"; private static final Logger LOG = LoggerFactory.getLogger(StringBasedMongoQuery.class); private static final ParameterBindingParser BINDING_PARSER = ParameterBindingParser.INSTANCE; private final String query; private final String fieldSpec; private final boolean isCountQuery; + private final boolean isExistsQuery; private final boolean isDeleteQuery; private final List queryParameterBindings; private final List fieldSpecParameterBindings; @@ -95,14 +97,27 @@ public StringBasedMongoQuery(String query, MongoQueryMethod method, MongoOperati this.fieldSpec = BINDING_PARSER.parseAndCollectParameterBindingsFromQueryIntoBindings( method.getFieldSpecification(), this.fieldSpecParameterBindings); - this.isCountQuery = method.hasAnnotatedQuery() ? method.getQueryAnnotation().count() : false; - this.isDeleteQuery = method.hasAnnotatedQuery() ? method.getQueryAnnotation().delete() : false; + this.parameterBinder = new ExpressionEvaluatingParameterBinder(expressionParser, evaluationContextProvider); - if (isCountQuery && isDeleteQuery) { - throw new IllegalArgumentException(String.format(COUND_AND_DELETE, method)); - } - this.parameterBinder = new ExpressionEvaluatingParameterBinder(expressionParser, evaluationContextProvider); + if (method.hasAnnotatedQuery()) { + + org.springframework.data.mongodb.repository.Query queryAnnotation = method.getQueryAnnotation(); + + this.isCountQuery = queryAnnotation.count(); + this.isExistsQuery = queryAnnotation.exists(); + this.isDeleteQuery = queryAnnotation.delete(); + + if (hasAmbiguousProjectionFlags(this.isCountQuery, this.isExistsQuery, this.isDeleteQuery)) { + throw new IllegalArgumentException(String.format(COUNT_EXISTS_AND_DELETE, method)); + } + + } else { + + this.isCountQuery = false; + this.isExistsQuery = false; + this.isDeleteQuery = false; + } } /* @@ -135,6 +150,15 @@ protected boolean isCountQuery() { return isCountQuery; } + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.repository.query.AbstractMongoQuery#isExistsQuery() + */ + @Override + protected boolean isExistsQuery() { + return isExistsQuery; + } + /* * (non-Javadoc) * @see org.springframework.data.mongodb.repository.query.AbstractMongoQuery#isDeleteQuery() @@ -144,12 +168,30 @@ protected boolean isDeleteQuery() { return this.isDeleteQuery; } + private static boolean hasAmbiguousProjectionFlags(boolean isCountQuery, boolean isExistsQuery, boolean isDeleteQuery) { + return countBooleanValues(isCountQuery, isExistsQuery, isDeleteQuery) > 1; + } + + private static int countBooleanValues(boolean... values) { + + int count = 0; + + for (boolean value : values) { + + if (value) { + count++; + } + } + + return count; + } + /** * A parser that extracts the parameter bindings from a given query string. * * @author Thomas Darimont */ - private static enum ParameterBindingParser { + private enum ParameterBindingParser { INSTANCE; @@ -256,7 +298,7 @@ private static void collectParameterReferencesIntoBindings(List MongoEntityInformation entityInformationFor( + MongoPersistentEntity entity, Class idType) { + + Assert.notNull(entity, "Entity must not be null!"); + + MappingMongoEntityInformation entityInformation = new MappingMongoEntityInformation( + (MongoPersistentEntity) entity, (Class) idType); + + return ClassUtils.isAssignable(Persistable.class, entity.getType()) + ? new PersistableMongoEntityInformation(entityInformation) : entityInformation; + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/MongoRepositoryFactory.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/MongoRepositoryFactory.java index 03d75f2c82..e39ebbedae 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/MongoRepositoryFactory.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/MongoRepositoryFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2010-2015 the original author or authors. + * Copyright 2010-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +20,7 @@ import java.io.Serializable; import java.lang.reflect.Method; +import org.springframework.data.domain.Persistable; import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.mapping.model.MappingException; import org.springframework.data.mongodb.core.MongoOperations; @@ -42,6 +43,7 @@ import org.springframework.data.repository.query.RepositoryQuery; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; /** * Factory to create {@link MongoRepository} instances. @@ -49,6 +51,7 @@ * @author Oliver Gierke * @author Thomas Darimont * @author Christoph Strobl + * @author Mark Paluch */ public class MongoRepositoryFactory extends RepositoryFactorySupport { @@ -64,7 +67,7 @@ public class MongoRepositoryFactory extends RepositoryFactorySupport { */ public MongoRepositoryFactory(MongoOperations mongoOperations) { - Assert.notNull(mongoOperations); + Assert.notNull(mongoOperations, "MongoOperations must not be null!"); this.operations = mongoOperations; this.mappingContext = mongoOperations.getConverter().getMappingContext(); @@ -95,7 +98,7 @@ protected Object getTargetRepository(RepositoryInformation information) { return getTargetRepositoryViaReflection(information, entityInformation, operations); } - /* + /* * (non-Javadoc) * @see org.springframework.data.repository.core.support.RepositoryFactorySupport#getQueryLookupStrategy(org.springframework.data.repository.query.QueryLookupStrategy.Key, org.springframework.data.repository.query.EvaluationContextProvider) */ @@ -123,8 +126,8 @@ private MongoEntityInformation getEntityInfo String.format("Could not lookup mapping metadata for domain class %s!", domainClass.getName())); } - return new MappingMongoEntityInformation((MongoPersistentEntity) entity, - information != null ? (Class) information.getIdType() : null); + return MongoEntityInformationSupport. entityInformationFor(entity, + information != null ? information.getIdType() : null); } /** @@ -147,7 +150,7 @@ public MongoQueryLookupStrategy(MongoOperations operations, EvaluationContextPro this.mappingContext = mappingContext; } - /* + /* * (non-Javadoc) * @see org.springframework.data.repository.query.QueryLookupStrategy#resolveQuery(java.lang.reflect.Method, org.springframework.data.repository.core.RepositoryMetadata, org.springframework.data.projection.ProjectionFactory, org.springframework.data.repository.core.NamedQueries) */ diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/MongoRepositoryFactoryBean.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/MongoRepositoryFactoryBean.java index abaee0270a..405c4e7f97 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/MongoRepositoryFactoryBean.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/MongoRepositoryFactoryBean.java @@ -30,13 +30,22 @@ * * @author Oliver Gierke */ -public class MongoRepositoryFactoryBean, S, ID extends Serializable> extends - RepositoryFactoryBeanSupport { +public class MongoRepositoryFactoryBean, S, ID extends Serializable> + extends RepositoryFactoryBeanSupport { private MongoOperations operations; private boolean createIndexesForQueryMethods = false; private boolean mappingContextConfigured = false; + /** + * Creates a new {@link MongoRepositoryFactoryBean} for the given repository interface. + * + * @param repositoryInterface must not be {@literal null}. + */ + public MongoRepositoryFactoryBean(Class repositoryInterface) { + super(repositoryInterface); + } + /** * Configures the {@link MongoOperations} to be used. * diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/PersistableMongoEntityInformation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/PersistableMongoEntityInformation.java new file mode 100644 index 0000000000..99305f4b44 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/PersistableMongoEntityInformation.java @@ -0,0 +1,105 @@ +/* + * Copyright 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.mongodb.repository.support; + +import lombok.NonNull; +import lombok.RequiredArgsConstructor; + +import java.io.Serializable; + +import org.springframework.data.domain.Persistable; +import org.springframework.data.mongodb.repository.query.MongoEntityInformation; + +/** + * {@link MongoEntityInformation} implementation wrapping an existing {@link MongoEntityInformation} considering + * {@link Persistable} types by delegating {@link #isNew(Object)} and {@link #getId(Object)} to the corresponding + * {@link Persistable#isNew()} and {@link Persistable#getId()} implementations. + * + * @author Christoph Strobl + * @author Oliver Gierke + * @since 1.10 + */ +@RequiredArgsConstructor +class PersistableMongoEntityInformation implements MongoEntityInformation { + + private final @NonNull MongoEntityInformation delegate; + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.repository.MongoEntityInformation#getCollectionName() + */ + @Override + public String getCollectionName() { + return delegate.getCollectionName(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.repository.MongoEntityInformation#getIdAttribute() + */ + @Override + public String getIdAttribute() { + return delegate.getIdAttribute(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.core.EntityInformation#isNew(java.lang.Object) + */ + @Override + @SuppressWarnings("unchecked") + public boolean isNew(T t) { + + if (t instanceof Persistable) { + return ((Persistable) t).isNew(); + } + + return delegate.isNew(t); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.core.EntityInformation#getId(java.lang.Object) + */ + @Override + @SuppressWarnings("unchecked") + public ID getId(T t) { + + if (t instanceof Persistable) { + return (ID) ((Persistable) t).getId(); + } + + return delegate.getId(t); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.core.support.PersistentEntityInformation#getIdType() + */ + @Override + public Class getIdType() { + return delegate.getIdType(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.core.support.EntityMetadata#getJavaType() + */ + @Override + public Class getJavaType() { + return delegate.getJavaType(); + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/QueryDslMongoRepository.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/QueryDslMongoRepository.java index cb6fbc6934..3d074c64b3 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/QueryDslMongoRepository.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/QueryDslMongoRepository.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2016 the original author or authors. + * Copyright 2011-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -80,7 +80,8 @@ public QueryDslMongoRepository(MongoEntityInformation entityInformation, super(entityInformation, mongoOperations); - Assert.notNull(resolver); + Assert.notNull(resolver, "EntityPathResolver must not be null!"); + EntityPath path = resolver.createPath(entityInformation.getJavaType()); this.builder = new PathBuilder(path.getType(), path.getMetadata()); @@ -124,7 +125,7 @@ public List findAll(Predicate predicate, Sort sort) { return applySorting(createQueryFor(predicate), sort).fetchResults().getResults(); } - /* + /* * (non-Javadoc) * @see org.springframework.data.querydsl.QueryDslPredicateExecutor#findAll(com.mysema.query.types.OrderSpecifier[]) */ @@ -187,7 +188,7 @@ public long count(Predicate predicate) { return createQueryFor(predicate).fetchCount(); } - /* + /* * (non-Javadoc) * @see org.springframework.data.querydsl.QueryDslPredicateExecutor#exists(com.mysema.query.types.Predicate) */ diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/QuerydslRepositorySupport.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/QuerydslRepositorySupport.java index bce77cd8b8..bbfe8dd9b7 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/QuerydslRepositorySupport.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/QuerydslRepositorySupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2015 the original author or authors. + * Copyright 2011-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,6 +27,7 @@ * Base class to create repository implementations based on Querydsl. * * @author Oliver Gierke + * @author Mark Paluch */ public abstract class QuerydslRepositorySupport { @@ -40,7 +41,7 @@ public abstract class QuerydslRepositorySupport { */ public QuerydslRepositorySupport(MongoOperations operations) { - Assert.notNull(operations); + Assert.notNull(operations, "MongoOperations must not be null!"); this.template = operations; this.context = operations.getConverter().getMappingContext(); @@ -54,7 +55,9 @@ public QuerydslRepositorySupport(MongoOperations operations) { * @return */ protected AbstractMongodbQuery> from(final EntityPath path) { - Assert.notNull(path); + + Assert.notNull(path, "EntityPath must not be null!"); + MongoPersistentEntity entity = context.getPersistentEntity(path.getType()); return from(path, entity.getCollection()); } @@ -68,8 +71,8 @@ protected AbstractMongodbQuery> from(final Enti */ protected AbstractMongodbQuery> from(final EntityPath path, String collection) { - Assert.notNull(path); - Assert.hasText(collection); + Assert.notNull(path, "EntityPath must not be null!"); + Assert.hasText(collection, "Collection name must not be null or empty!"); return new SpringDataMongodbQuery(template, path.getType(), collection); } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/SimpleMongoRepository.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/SimpleMongoRepository.java index 83665535c9..c6549dff61 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/SimpleMongoRepository.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/SimpleMongoRepository.java @@ -1,5 +1,5 @@ /* - * Copyright 2010-2016 the original author or authors. + * Copyright 2010-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -61,8 +61,8 @@ public class SimpleMongoRepository implements MongoR */ public SimpleMongoRepository(MongoEntityInformation metadata, MongoOperations mongoOperations) { - Assert.notNull(mongoOperations); - Assert.notNull(metadata); + Assert.notNull(metadata, "MongoEntityInformation must not be null!"); + Assert.notNull(mongoOperations, "MongoOperations must not be null!"); this.entityInformation = metadata; this.mongoOperations = mongoOperations; @@ -197,7 +197,7 @@ public List findAll() { return findAll(new Query()); } - /* + /* * (non-Javadoc) * @see org.springframework.data.repository.CrudRepository#findAll(java.lang.Iterable) */ @@ -231,7 +231,7 @@ public List findAll(Sort sort) { return findAll(new Query().with(sort)); } - /* + /* * (non-Javadoc) * @see org.springframework.data.mongodb.repository.MongoRepository#insert(java.lang.Object) */ @@ -244,7 +244,7 @@ public S insert(S entity) { return entity; } - /* + /* * (non-Javadoc) * @see org.springframework.data.mongodb.repository.MongoRepository#insert(java.lang.Iterable) */ diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/MongoClientVersion.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/MongoClientVersion.java index d230574911..272f885a7c 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/MongoClientVersion.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/MongoClientVersion.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 the original author or authors. + * Copyright 2015-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,16 +28,28 @@ public class MongoClientVersion { private static final boolean IS_MONGO_30 = ClassUtils.isPresent("com.mongodb.binding.SingleServerBinding", MongoClientVersion.class.getClassLoader()); + + private static final boolean IS_MONGO_34 = ClassUtils.isPresent("org.bson.types.Decimal128", + MongoClientVersion.class.getClassLoader()); + private static final boolean IS_ASYNC_CLIENT = ClassUtils.isPresent("com.mongodb.async.client.MongoClient", MongoClientVersion.class.getClassLoader()); /** - * @return |literal true} if MongoDB Java driver version 3.0 or later is on classpath. + * @return {@literal true} if MongoDB Java driver version 3.0 or later is on classpath. */ public static boolean isMongo3Driver() { return IS_MONGO_30; } + /** + * @return {@literal true} if MongoDB Java driver version 3.4 or later is on classpath. + * @since 1.10 + */ + public static boolean isMongo34Driver() { + return IS_MONGO_34; + } + /** * @return {lliteral true} if MongoDB Java driver is on classpath. */ diff --git a/spring-data-mongodb/src/main/resources/META-INF/spring.factories b/spring-data-mongodb/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000000..a581f2e3ac --- /dev/null +++ b/spring-data-mongodb/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.data.web.config.SpringDataJacksonModules=org.springframework.data.mongodb.core.GeoJsonConfiguration +org.springframework.data.repository.core.support.RepositoryFactorySupport=org.springframework.data.mongodb.repository.support.MongoRepositoryFactory diff --git a/spring-data-mongodb/src/test/java/ConfigClassInDefaultPackage.java b/spring-data-mongodb/src/test/java/ConfigClassInDefaultPackage.java index 4a77bc00e3..45ae3b7901 100644 --- a/spring-data-mongodb/src/test/java/ConfigClassInDefaultPackage.java +++ b/spring-data-mongodb/src/test/java/ConfigClassInDefaultPackage.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 the original author or authors. + * Copyright 2014-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,7 +22,6 @@ /** * Sample configuration class in default package. * - * @see DATAMONGO-877 * @author Oliver Gierke */ @Configuration diff --git a/spring-data-mongodb/src/test/java/ConfigClassInDefaultPackageUnitTests.java b/spring-data-mongodb/src/test/java/ConfigClassInDefaultPackageUnitTests.java index f9b9a78cab..4e8b9f25d5 100644 --- a/spring-data-mongodb/src/test/java/ConfigClassInDefaultPackageUnitTests.java +++ b/spring-data-mongodb/src/test/java/ConfigClassInDefaultPackageUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 the original author or authors. + * Copyright 2014-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,15 +19,11 @@ /** * Unit test for {@link ConfigClassInDefaultPackage}. * - * @see DATAMONGO-877 * @author Oliver Gierke */ public class ConfigClassInDefaultPackageUnitTests { - /** - * @see DATAMONGO-877 - */ - @Test + @Test // DATAMONGO-877 public void loadsConfigClassFromDefaultPackage() { new AnnotationConfigApplicationContext(ConfigClassInDefaultPackage.class).close(); } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/AbstractMongoConfigurationUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/AbstractMongoConfigurationUnitTests.java index 2bb1bd0a0d..bc9a3e216a 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/AbstractMongoConfigurationUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/AbstractMongoConfigurationUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2016 the original author or authors. + * Copyright 2012-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -55,10 +55,7 @@ public class AbstractMongoConfigurationUnitTests { @Rule public ExpectedException exception = ExpectedException.none(); - /** - * @see DATAMONGO-496 - */ - @Test + @Test // DATAMONGO-496 public void usesConfigClassPackageAsBaseMappingPackage() throws ClassNotFoundException { AbstractMongoConfiguration configuration = new SampleMongoConfiguration(); @@ -67,30 +64,21 @@ public void usesConfigClassPackageAsBaseMappingPackage() throws ClassNotFoundExc assertThat(configuration.getInitialEntitySet(), hasItem(Entity.class)); } - /** - * @see DATAMONGO-496 - */ - @Test + @Test // DATAMONGO-496 public void doesNotScanPackageIfMappingPackageIsNull() throws ClassNotFoundException { assertScanningDisabled(null); } - /** - * @see DATAMONGO-496 - */ - @Test + @Test // DATAMONGO-496 public void doesNotScanPackageIfMappingPackageIsEmpty() throws ClassNotFoundException { assertScanningDisabled(""); assertScanningDisabled(" "); } - /** - * @see DATAMONGO-569 - */ - @Test + @Test // DATAMONGO-569 public void containsMongoDbFactoryButNoMongoBean() { AbstractApplicationContext context = new AnnotationConfigApplicationContext(SampleMongoConfiguration.class); @@ -113,10 +101,7 @@ public void returnsUninitializedMappingContext() throws Exception { assertThat(context.getPersistentEntities(), is(not(emptyIterable()))); } - /** - * @see DATAMONGO-717 - */ - @Test + @Test // DATAMONGO-717 public void lifecycleCallbacksAreInvokedInAppropriateOrder() { AbstractApplicationContext context = new AnnotationConfigApplicationContext(SampleMongoConfiguration.class); @@ -128,10 +113,7 @@ public void lifecycleCallbacksAreInvokedInAppropriateOrder() { context.close(); } - /** - * @see DATAMONGO-725 - */ - @Test + @Test // DATAMONGO-725 public void shouldBeAbleToConfigureCustomTypeMapperViaJavaConfig() { AbstractApplicationContext context = new AnnotationConfigApplicationContext(SampleMongoConfiguration.class); @@ -143,18 +125,12 @@ public void shouldBeAbleToConfigureCustomTypeMapperViaJavaConfig() { context.close(); } - /** - * @see DATAMONGO-789 - */ - @Test + @Test // DATAMONGO-789 public void authenticationDatabaseShouldDefaultToNull() { assertThat(new SampleMongoConfiguration().getAuthenticationDatabaseName(), is(nullValue())); } - /** - * @see DATAMONGO-1470 - */ - @Test + @Test // DATAMONGO-1470 @SuppressWarnings("unchecked") public void allowsMultipleEntityBasePackages() throws ClassNotFoundException { diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/AuditingIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/AuditingIntegrationTests.java index 47cee10e51..fe84ccb3a1 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/AuditingIntegrationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/AuditingIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2015 the original author or authors. + * Copyright 2012-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,10 +35,7 @@ */ public class AuditingIntegrationTests { - /** - * @see DATAMONGO-577, DATAMONGO-800, DATAMONGO-883 - */ - @Test + @Test // DATAMONGO-577, DATAMONGO-800, DATAMONGO-883 public void enablesAuditingAndSetsPropertiesAccordingly() throws Exception { AbstractApplicationContext context = new ClassPathXmlApplicationContext("auditing.xml", getClass()); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/AuditingViaJavaConfigRepositoriesTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/AuditingViaJavaConfigRepositoriesTests.java index 288e09cb91..7f7bc5eebb 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/AuditingViaJavaConfigRepositoriesTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/AuditingViaJavaConfigRepositoriesTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -79,10 +79,7 @@ public void setup() { this.auditor = auditablePersonRepository.save(new AuditablePerson("auditor")); } - /** - * @see DATAMONGO-792, DATAMONGO-883 - */ - @Test + @Test // DATAMONGO-792, DATAMONGO-883 public void basicAuditing() { doReturn(this.auditor).when(this.auditorAware).getCurrentAuditor(); @@ -96,19 +93,13 @@ public void basicAuditing() { assertThat(savedUser.getCreatedAt(), is(notNullValue())); } - /** - * @see DATAMONGO-843 - */ - @Test + @Test // DATAMONGO-843 @SuppressWarnings("resource") public void auditingUsesFallbackMappingContextIfNoneConfiguredWithRepositories() { new AnnotationConfigApplicationContext(SimpleConfigWithRepositories.class); } - /** - * @see DATAMONGO-843 - */ - @Test + @Test // DATAMONGO-843 @SuppressWarnings("resource") public void auditingUsesFallbackMappingContextIfNoneConfigured() { new AnnotationConfigApplicationContext(SimpleConfig.class); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/GeoJsonConfigurationIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/GeoJsonConfigurationIntegrationTests.java index 7efa806778..bed30fc8ba 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/GeoJsonConfigurationIntegrationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/GeoJsonConfigurationIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 the original author or authors. + * Copyright 2015-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -43,10 +43,7 @@ static class Config {} @Autowired GeoJsonModule geoJsonModule; - /** - * @see DATAMONGO-1181 - */ - @Test + @Test // DATAMONGO-1181 public void picksUpGeoJsonModuleConfigurationByDefault() { assertThat(geoJsonModule, is(notNullValue())); } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MappingMongoConverterParserIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MappingMongoConverterParserIntegrationTests.java index 5082cc3ae9..6910aff4f5 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MappingMongoConverterParserIntegrationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MappingMongoConverterParserIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2014 the original author or authors. + * Copyright 2011-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -58,10 +58,7 @@ public class MappingMongoConverterParserIntegrationTests { DefaultListableBeanFactory factory; - /** - * @see DATAMONGO-243 - */ - @Test + @Test // DATAMONGO-243 public void allowsDbFactoryRefAttribute() { loadValidConfiguration(); @@ -69,10 +66,7 @@ public void allowsDbFactoryRefAttribute() { factory.getBean("converter"); } - /** - * @see DATAMONGO-725 - */ - @Test + @Test // DATAMONGO-725 public void hasCustomTypeMapper() { loadValidConfiguration(); @@ -82,10 +76,7 @@ public void hasCustomTypeMapper() { assertThat(converter.getTypeMapper(), is(customMongoTypeMapper)); } - /** - * @see DATAMONGO-301 - */ - @Test + @Test // DATAMONGO-301 public void scansForConverterAndSetsUpCustomConversionsAccordingly() { loadValidConfiguration(); @@ -94,10 +85,7 @@ public void scansForConverterAndSetsUpCustomConversionsAccordingly() { assertThat(conversions.hasCustomWriteTarget(Account.class), is(true)); } - /** - * @see DATAMONGO-607 - */ - @Test + @Test // DATAMONGO-607 public void activatesAbbreviatingPropertiesCorrectly() { loadValidConfiguration(); @@ -109,10 +97,7 @@ public void activatesAbbreviatingPropertiesCorrectly() { assertThat(strategy.getBeanClassName(), is(CamelCaseAbbreviatingFieldNamingStrategy.class.getName())); } - /** - * @see DATAMONGO-866 - */ - @Test + @Test // DATAMONGO-866 public void rejectsInvalidFieldNamingStrategyConfiguration() { exception.expect(BeanDefinitionParsingException.class); @@ -124,10 +109,7 @@ public void rejectsInvalidFieldNamingStrategyConfiguration() { reader.loadBeanDefinitions(new ClassPathResource("namespace/converter-invalid.xml")); } - /** - * @see DATAMONGO-892 - */ - @Test + @Test // DATAMONGO-892 public void shouldThrowBeanDefinitionParsingExceptionIfConverterDefinedAsNestedBean() { exception.expect(BeanDefinitionParsingException.class); @@ -136,18 +118,12 @@ public void shouldThrowBeanDefinitionParsingExceptionIfConverterDefinedAsNestedB loadNestedBeanConfiguration(); } - /** - * @see DATAMONGO-925, DATAMONGO-928 - */ - @Test + @Test // DATAMONGO-925, DATAMONGO-928 public void shouldSupportCustomFieldNamingStrategy() { assertStrategyReferenceSetFor("mappingConverterWithCustomFieldNamingStrategy"); } - /** - * @see DATAMONGO-925, DATAMONGO-928 - */ - @Test + @Test // DATAMONGO-925, DATAMONGO-928 public void shouldNotFailLoadingConfigIfAbbreviationIsDisabledAndStrategySet() { assertStrategyReferenceSetFor("mappingConverterWithCustomFieldNamingStrategyAndAbbreviationDisabled"); } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MappingMongoConverterParserValidationIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MappingMongoConverterParserValidationIntegrationTests.java index d9f34df89f..dda5299914 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MappingMongoConverterParserValidationIntegrationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MappingMongoConverterParserValidationIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2014 the original author or authors. + * Copyright 2012-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,7 +31,6 @@ * {@link org.springframework.data.mongodb.core.mapping.event.ValidatingMongoEventListener} by defining * {@code } in context XML. * - * @see DATAMONGO-36 * @author Maciej Walkowiak * @author Thomas Darimont * @author Oliver Gierke @@ -47,40 +46,28 @@ public void setUp() { reader = new XmlBeanDefinitionReader(factory); } - /** - * @see DATAMONGO-36 - */ - @Test + @Test // DATAMONGO-36 public void validatingEventListenerCreatedWithDefaultConfig() { reader.loadBeanDefinitions(new ClassPathResource("namespace/converter-default.xml")); assertThat(factory.getBean(BeanNames.VALIDATING_EVENT_LISTENER_BEAN_NAME), is(not(nullValue()))); } - /** - * @see DATAMONGO-36 - */ - @Test + @Test // DATAMONGO-36 public void validatingEventListenerCreatedWhenValidationEnabled() { reader.loadBeanDefinitions(new ClassPathResource("namespace/converter-validation-enabled.xml")); assertThat(factory.getBean(BeanNames.VALIDATING_EVENT_LISTENER_BEAN_NAME), is(not(nullValue()))); } - /** - * @see DATAMONGO-36 - */ - @Test(expected = NoSuchBeanDefinitionException.class) + @Test(expected = NoSuchBeanDefinitionException.class) // DATAMONGO-36 public void validatingEventListenersIsNotCreatedWhenDisabled() { reader.loadBeanDefinitions(new ClassPathResource("namespace/converter-validation-disabled.xml")); factory.getBean(BeanNames.VALIDATING_EVENT_LISTENER_BEAN_NAME); } - /** - * @see DATAMONGO-36 - */ - @Test + @Test // DATAMONGO-36 public void validatingEventListenerCreatedWithCustomTypeMapperConfig() { reader.loadBeanDefinitions(new ClassPathResource("namespace/converter-custom-typeMapper.xml")); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MongoAuditingRegistrarUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MongoAuditingRegistrarUnitTests.java index ad076c90d3..c918433ef7 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MongoAuditingRegistrarUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MongoAuditingRegistrarUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,7 +25,6 @@ /** * Unit tests for {@link JpaAuditingRegistrar}. * - * @see DATAMONGO-792 * @author Oliver Gierke */ @RunWith(MockitoJUnitRunner.class) @@ -36,12 +35,12 @@ public class MongoAuditingRegistrarUnitTests { @Mock AnnotationMetadata metadata; @Mock BeanDefinitionRegistry registry; - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-792 public void rejectsNullAnnotationMetadata() { registrar.registerBeanDefinitions(null, registry); } - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-792 public void rejectsNullBeanDefinitionRegistry() { registrar.registerBeanDefinitions(metadata, null); } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MongoClientParserIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MongoClientParserIntegrationTests.java index a2abd7c6e1..b3f9c05410 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MongoClientParserIntegrationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MongoClientParserIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 the original author or authors. + * Copyright 2015-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -51,10 +51,7 @@ public void setUp() { this.reader = new XmlBeanDefinitionReader(factory); } - /** - * @see DATAMONGO-1158 - */ - @Test + @Test // DATAMONGO-1158 public void createsMongoClientCorrectlyWhenGivenHostAndPort() { reader.loadBeanDefinitions(new ClassPathResource("namespace/mongoClient-bean.xml")); @@ -62,10 +59,7 @@ public void createsMongoClientCorrectlyWhenGivenHostAndPort() { assertThat(factory.getBean("mongo-client-with-host-and-port"), instanceOf(MongoClient.class)); } - /** - * @see DATAMONGO-1158 - */ - @Test + @Test // DATAMONGO-1158 public void createsMongoClientWithOptionsCorrectly() { reader.loadBeanDefinitions(new ClassPathResource("namespace/mongoClient-bean.xml")); @@ -84,10 +78,7 @@ public void createsMongoClientWithOptionsCorrectly() { } } - /** - * @see DATAMONGO-1158 - */ - @Test + @Test // DATAMONGO-1158 public void createsMongoClientWithDefaultsCorrectly() { reader.loadBeanDefinitions(new ClassPathResource("namespace/mongoClient-bean.xml")); @@ -105,10 +96,7 @@ public void createsMongoClientWithDefaultsCorrectly() { } } - /** - * @see DATAMONGO-1158 - */ - @Test + @Test // DATAMONGO-1158 public void createsMongoClientWithCredentialsCorrectly() { reader.loadBeanDefinitions(new ClassPathResource("namespace/mongoClient-bean.xml")); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MongoCredentialPropertyEditorUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MongoCredentialPropertyEditorUnitTests.java index ba9257cbe2..bcebbce444 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MongoCredentialPropertyEditorUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MongoCredentialPropertyEditorUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 the original author or authors. + * Copyright 2015-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -75,10 +75,7 @@ public void setUp() { this.editor = new MongoCredentialPropertyEditor(); } - /** - * @see DATAMONGO-1158 - */ - @Test + @Test // DATAMONGO-1158 public void shouldReturnNullValueForNullText() { editor.setAsText(null); @@ -86,10 +83,7 @@ public void shouldReturnNullValueForNullText() { assertThat(editor.getValue(), nullValue()); } - /** - * @see DATAMONGO-1158 - */ - @Test + @Test // DATAMONGO-1158 public void shouldReturnNullValueForEmptyText() { editor.setAsText(" "); @@ -97,26 +91,17 @@ public void shouldReturnNullValueForEmptyText() { assertThat(editor.getValue(), nullValue()); } - /** - * @see DATAMONGO-1158 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-1158 public void shouldThrowExceptionForMalformatedCredentialsString() { editor.setAsText("tyrion"); } - /** - * @see DATAMONGO-1158 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-1158 public void shouldThrowExceptionForMalformatedAuthMechanism() { editor.setAsText(USER_2_AUTH_STRING + "?uri.authMechanism=Targaryen"); } - /** - * @see DATAMONGO-1158 - */ - @Test + @Test // DATAMONGO-1158 @SuppressWarnings("unchecked") public void shouldReturnCredentialsValueCorrectlyWhenGivenSingleUserNamePasswordStringWithDatabaseAndNoOptions() { @@ -125,10 +110,7 @@ public void shouldReturnCredentialsValueCorrectlyWhenGivenSingleUserNamePassword assertThat((List) editor.getValue(), contains(USER_1_CREDENTIALS)); } - /** - * @see DATAMONGO-1158 - */ - @Test + @Test // DATAMONGO-1158 @SuppressWarnings("unchecked") public void shouldReturnCredentialsValueCorrectlyWhenGivenSingleUserNamePasswordStringWithDatabaseAndAuthOptions() { @@ -137,10 +119,7 @@ public void shouldReturnCredentialsValueCorrectlyWhenGivenSingleUserNamePassword assertThat((List) editor.getValue(), contains(USER_1_CREDENTIALS_PLAIN_AUTH)); } - /** - * @see DATAMONGO-1158 - */ - @Test + @Test // DATAMONGO-1158 @SuppressWarnings("unchecked") public void shouldReturnCredentialsValueCorrectlyWhenGivenMultipleUserNamePasswordStringWithDatabaseAndNoOptions() { @@ -150,10 +129,7 @@ public void shouldReturnCredentialsValueCorrectlyWhenGivenMultipleUserNamePasswo assertThat((List) editor.getValue(), contains(USER_1_CREDENTIALS, USER_2_CREDENTIALS)); } - /** - * @see DATAMONGO-1158 - */ - @Test + @Test // DATAMONGO-1158 @SuppressWarnings("unchecked") public void shouldReturnCredentialsValueCorrectlyWhenGivenMultipleUserNamePasswordStringWithDatabaseAndAuthOptions() { @@ -164,10 +140,7 @@ public void shouldReturnCredentialsValueCorrectlyWhenGivenMultipleUserNamePasswo contains(USER_1_CREDENTIALS_PLAIN_AUTH, USER_2_CREDENTIALS_CR_AUTH)); } - /** - * @see DATAMONGO-1158 - */ - @Test + @Test // DATAMONGO-1158 @SuppressWarnings("unchecked") public void shouldReturnCredentialsValueCorrectlyWhenGivenMultipleUserNamePasswordStringWithDatabaseAndMixedOptions() { @@ -177,10 +150,7 @@ public void shouldReturnCredentialsValueCorrectlyWhenGivenMultipleUserNamePasswo assertThat((List) editor.getValue(), contains(USER_1_CREDENTIALS_PLAIN_AUTH, USER_2_CREDENTIALS)); } - /** - * @see DATAMONGO-1257 - */ - @Test + @Test // DATAMONGO-1257 @SuppressWarnings("unchecked") public void shouldReturnCredentialsValueCorrectlyWhenGivenMultipleQuotedUserNamePasswordStringWithDatabaseAndNoOptions() { @@ -190,10 +160,7 @@ public void shouldReturnCredentialsValueCorrectlyWhenGivenMultipleQuotedUserName assertThat((List) editor.getValue(), contains(USER_1_CREDENTIALS, USER_2_CREDENTIALS)); } - /** - * @see DATAMONGO-1257 - */ - @Test + @Test // DATAMONGO-1257 @SuppressWarnings("unchecked") public void shouldReturnCredentialsValueCorrectlyWhenGivenSingleQuotedUserNamePasswordStringWithDatabaseAndNoOptions() { @@ -202,10 +169,7 @@ public void shouldReturnCredentialsValueCorrectlyWhenGivenSingleQuotedUserNamePa assertThat((List) editor.getValue(), contains(USER_1_CREDENTIALS)); } - /** - * @see DATAMONGO-1257 - */ - @Test + @Test // DATAMONGO-1257 @SuppressWarnings("unchecked") public void shouldReturnX509CredentialsCorrectly() { @@ -214,10 +178,7 @@ public void shouldReturnX509CredentialsCorrectly() { assertThat((List) editor.getValue(), contains(USER_3_CREDENTIALS_X509_AUTH)); } - /** - * @see DATAMONGO-1257 - */ - @Test + @Test // DATAMONGO-1257 @SuppressWarnings("unchecked") public void shouldReturnX509CredentialsCorrectlyWhenNoDbSpecified() { @@ -226,10 +187,7 @@ public void shouldReturnX509CredentialsCorrectlyWhenNoDbSpecified() { assertThat((List) editor.getValue(), contains(MongoCredential.createMongoX509Credential("tyrion"))); } - /** - * @see DATAMONGO-1257 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-1257 public void shouldThrowExceptionWhenNoDbSpecifiedForMongodbCR() { editor.setAsText("tyrion?uri.authMechanism=MONGODB-CR"); @@ -237,10 +195,7 @@ public void shouldThrowExceptionWhenNoDbSpecifiedForMongodbCR() { editor.getValue(); } - /** - * @see DATAMONGO-1257 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-1257 public void shouldThrowExceptionWhenDbIsEmptyForMongodbCR() { editor.setAsText("tyrion@?uri.authMechanism=MONGODB-CR"); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MongoDbFactoryNoDatabaseRunningTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MongoDbFactoryNoDatabaseRunningTests.java index 6e0972c5dd..c364fdc5fc 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MongoDbFactoryNoDatabaseRunningTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MongoDbFactoryNoDatabaseRunningTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2014 the original author or authors. + * Copyright 2011-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -39,10 +39,7 @@ public class MongoDbFactoryNoDatabaseRunningTests { @Autowired MongoTemplate mongoTemplate; - /** - * @see DATAMONGO-139 - */ - @Test + @Test // DATAMONGO-139 public void startsUpWithoutADatabaseRunning() { assertThat(mongoTemplate.getClass().getName(), is("org.springframework.data.mongodb.core.MongoTemplate")); } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MongoDbFactoryParserIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MongoDbFactoryParserIntegrationTests.java index a98a2c06d0..3f013ac207 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MongoDbFactoryParserIntegrationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MongoDbFactoryParserIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2015 the original author or authors. + * Copyright 2011-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -91,10 +91,7 @@ public void parsesCustomWriteConcern() { assertWriteConcern(ctx, new WriteConcern("rack1")); } - /** - * @see DATAMONGO-331 - */ - @Test + @Test // DATAMONGO-331 public void readsReplicasWriteConcernCorrectly() { AbstractApplicationContext ctx = new ClassPathXmlApplicationContext( @@ -122,10 +119,7 @@ public void createsDbFactoryBean() { factory.getBean("first"); } - /** - * @see DATAMONGO-280 - */ - @Test + @Test // DATAMONGO-280 @SuppressWarnings("deprecation") public void parsesMaxAutoConnectRetryTimeCorrectly() { @@ -134,10 +128,7 @@ public void parsesMaxAutoConnectRetryTimeCorrectly() { assertThat(ReflectiveMongoOptionsInvokerTestUtil.getMaxAutoConnectRetryTime(mongo.getMongoOptions()), is(27L)); } - /** - * @see DATAMONGO-295 - */ - @Test + @Test // DATAMONGO-295 public void setsUpMongoDbFactoryUsingAMongoUri() { reader.loadBeanDefinitions(new ClassPathResource("namespace/mongo-uri.xml")); @@ -149,10 +140,7 @@ public void setsUpMongoDbFactoryUsingAMongoUri() { assertThat(argument, is(notNullValue())); } - /** - * @see DATAMONGO-306 - */ - @Test + @Test // DATAMONGO-306 public void setsUpMongoDbFactoryUsingAMongoUriWithoutCredentials() { reader.loadBeanDefinitions(new ClassPathResource("namespace/mongo-uri-no-credentials.xml")); @@ -168,18 +156,12 @@ public void setsUpMongoDbFactoryUsingAMongoUriWithoutCredentials() { assertThat(db.getName(), is("database")); } - /** - * @see DATAMONGO-295 - */ - @Test(expected = BeanDefinitionParsingException.class) + @Test(expected = BeanDefinitionParsingException.class) // DATAMONGO-295 public void rejectsUriPlusDetailedConfiguration() { reader.loadBeanDefinitions(new ClassPathResource("namespace/mongo-uri-and-details.xml")); } - /** - * @see DATAMONGO-1218 - */ - @Test + @Test // DATAMONGO-1218 public void setsUpMongoDbFactoryUsingAMongoClientUri() { reader.loadBeanDefinitions(new ClassPathResource("namespace/mongo-client-uri.xml")); @@ -191,18 +173,12 @@ public void setsUpMongoDbFactoryUsingAMongoClientUri() { assertThat(argument, is(notNullValue())); } - /** - * @see DATAMONGO-1218 - */ - @Test(expected = BeanDefinitionParsingException.class) + @Test(expected = BeanDefinitionParsingException.class) // DATAMONGO-1218 public void rejectsClientUriPlusDetailedConfiguration() { reader.loadBeanDefinitions(new ClassPathResource("namespace/mongo-client-uri-and-details.xml")); } - /** - * @see DATAMONGO-1293 - */ - @Test + @Test // DATAMONGO-1293 public void setsUpClientUriWithId() { reader.loadBeanDefinitions(new ClassPathResource("namespace/mongo-client-uri-and-id.xml")); @@ -214,10 +190,7 @@ public void setsUpClientUriWithId() { assertThat(argument, is(notNullValue())); } - /** - * @see DATAMONGO-1293 - */ - @Test + @Test // DATAMONGO-1293 public void setsUpUriWithId() { reader.loadBeanDefinitions(new ClassPathResource("namespace/mongo-uri-and-id.xml")); @@ -229,18 +202,12 @@ public void setsUpUriWithId() { assertThat(argument, is(notNullValue())); } - /** - * @see DATAMONGO-1293 - */ - @Test(expected = BeanDefinitionParsingException.class) + @Test(expected = BeanDefinitionParsingException.class) // DATAMONGO-1293 public void rejectsClientUriPlusDetailedConfigurationAndWriteConcern() { reader.loadBeanDefinitions(new ClassPathResource("namespace/mongo-client-uri-write-concern-and-details.xml")); } - /** - * @see DATAMONGO-1293 - */ - @Test(expected = BeanDefinitionParsingException.class) + @Test(expected = BeanDefinitionParsingException.class) // DATAMONGO-1293 public void rejectsUriPlusDetailedConfigurationAndWriteConcern() { reader.loadBeanDefinitions(new ClassPathResource("namespace/mongo-client-uri-write-concern-and-details.xml")); } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MongoNamespaceTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MongoNamespaceTests.java index 2debb0afcd..1b64630466 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MongoNamespaceTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MongoNamespaceTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2010-2015 the original author or authors. + * Copyright 2010-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -90,10 +90,7 @@ public void testMongoSingletonWithAttributes() throws Exception { options.getSocketFactory() instanceof SSLSocketFactory); } - /** - * @see DATAMONGO-764 - */ - @Test + @Test // DATAMONGO-764 public void testMongoSingletonWithSslEnabled() throws Exception { assertTrue(ctx.containsBean("mongoSsl")); @@ -103,10 +100,7 @@ public void testMongoSingletonWithSslEnabled() throws Exception { assertTrue("socketFactory should be a SSLSocketFactory", options.getSocketFactory() instanceof SSLSocketFactory); } - /** - * @see DATAMONGO-1490 - */ - @Test + @Test // DATAMONGO-1490 public void testMongoClientSingletonWithSslEnabled() { assertTrue(ctx.containsBean("mongoClientSsl")); @@ -116,10 +110,7 @@ public void testMongoClientSingletonWithSslEnabled() { assertTrue("socketFactory should be a SSLSocketFactory", options.getSocketFactory() instanceof SSLSocketFactory); } - /** - * @see DATAMONGO-764 - */ - @Test + @Test // DATAMONGO-764 public void testMongoSingletonWithSslEnabledAndCustomSslSocketFactory() throws Exception { assertTrue(ctx.containsBean("mongoSslWithCustomSslFactory")); @@ -145,10 +136,7 @@ public void testSecondMongoDbFactory() { assertEquals("database", getField(dbf, "databaseName")); } - /** - * @see DATAMONGO-789 - */ - @Test + @Test // DATAMONGO-789 public void testThirdMongoDbFactory() { assertTrue(ctx.containsBean("thirdMongoDbFactory")); @@ -163,10 +151,7 @@ public void testThirdMongoDbFactory() { assertEquals("admin", getField(dbf, "authenticationDatabaseName")); } - /** - * @see DATAMONGO-140 - */ - @Test + @Test // DATAMONGO-140 public void testMongoTemplateFactory() { assertTrue(ctx.containsBean("mongoTemplate")); @@ -179,10 +164,7 @@ public void testMongoTemplateFactory() { assertNotNull(converter); } - /** - * @see DATAMONGO-140 - */ - @Test + @Test // DATAMONGO-140 public void testSecondMongoTemplateFactory() { assertTrue(ctx.containsBean("anotherMongoTemplate")); @@ -195,10 +177,7 @@ public void testSecondMongoTemplateFactory() { assertEquals(WriteConcern.SAFE, writeConcern); } - /** - * @see DATAMONGO-628 - */ - @Test + @Test // DATAMONGO-628 public void testGridFsTemplateFactory() { assertTrue(ctx.containsBean("gridFsTemplate")); @@ -211,10 +190,7 @@ public void testGridFsTemplateFactory() { assertNotNull(converter); } - /** - * @see DATAMONGO-628 - */ - @Test + @Test // DATAMONGO-628 public void testSecondGridFsTemplateFactory() { assertTrue(ctx.containsBean("secondGridFsTemplate")); @@ -228,10 +204,7 @@ public void testSecondGridFsTemplateFactory() { assertNotNull(converter); } - /** - * @see DATAMONGO-823 - */ - @Test + @Test // DATAMONGO-823 public void testThirdGridFsTemplateFactory() { assertTrue(ctx.containsBean("thirdGridFsTemplate")); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MongoParserIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MongoParserIntegrationTests.java index 0ca28717e4..b8d1b525da 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MongoParserIntegrationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MongoParserIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011 the original author or authors. + * Copyright 2011-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -62,10 +62,7 @@ public void readsMongoAttributesCorrectly() { factory.getBean("mongo"); } - /** - * @see DATAMONGO-343 - */ - @Test + @Test // DATAMONGO-343 public void readsServerAddressesCorrectly() { reader.loadBeanDefinitions(new ClassPathResource("namespace/mongo-bean.xml")); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/ReadPreferencePropertyEditorUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/ReadPreferencePropertyEditorUnitTests.java index 6f8f0c9548..a334dbab84 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/ReadPreferencePropertyEditorUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/ReadPreferencePropertyEditorUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 the original author or authors. + * Copyright 2015-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -41,10 +41,7 @@ public void setUp() { editor = new ReadPreferencePropertyEditor(); } - /** - * @see DATAMONGO-1158 - */ - @Test + @Test // DATAMONGO-1158 public void shouldThrowExceptionOnUndefinedPreferenceString() { expectedException.expect(IllegalArgumentException.class); @@ -54,10 +51,7 @@ public void shouldThrowExceptionOnUndefinedPreferenceString() { editor.setAsText("foo"); } - /** - * @see DATAMONGO-1158 - */ - @Test + @Test // DATAMONGO-1158 public void shouldAllowUsageNativePreferenceStrings() { editor.setAsText("secondary"); @@ -65,10 +59,7 @@ public void shouldAllowUsageNativePreferenceStrings() { assertThat(editor.getValue(), is((Object) ReadPreference.secondary())); } - /** - * @see DATAMONGO-1158 - */ - @Test + @Test // DATAMONGO-1158 public void shouldAllowUsageOfUppcaseEnumStringsForPreferences() { editor.setAsText("NEAREST"); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/ServerAddressPropertyEditorUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/ServerAddressPropertyEditorUnitTests.java index 19565433c2..34fa52a861 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/ServerAddressPropertyEditorUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/ServerAddressPropertyEditorUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2014 the original author or authors. + * Copyright 2012-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -48,11 +48,7 @@ public void setUp() { editor = new ServerAddressPropertyEditor(); } - /** - * @see DATAMONGO-454 - * @see DATAMONGO-1062 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-454, DATAMONGO-1062 public void rejectsAddressConfigWithoutASingleParsableAndResolvableServerAddress() { String unknownHost1 = "gugu.nonexistant.example.org"; @@ -63,40 +59,28 @@ public void rejectsAddressConfigWithoutASingleParsableAndResolvableServerAddress editor.setAsText(unknownHost1 + "," + unknownHost2); } - /** - * @see DATAMONGO-454 - */ - @Test + @Test // DATAMONGO-454 public void skipsUnparsableAddressIfAtLeastOneIsParsable() throws UnknownHostException { editor.setAsText("foo, localhost"); assertSingleAddressOfLocalhost(editor.getValue()); } - /** - * @see DATAMONGO-454 - */ - @Test + @Test // DATAMONGO-454 public void handlesEmptyAddressAsParseError() throws UnknownHostException { editor.setAsText(", localhost"); assertSingleAddressOfLocalhost(editor.getValue()); } - /** - * @see DATAMONGO-693 - */ - @Test + @Test // DATAMONGO-693 public void interpretEmptyStringAsNull() { editor.setAsText(""); assertNull(editor.getValue()); } - /** - * @see DATAMONGO-808 - */ - @Test + @Test // DATAMONGO-808 public void handleIPv6HostaddressLoopbackShort() throws UnknownHostException { String hostAddress = "::1"; @@ -105,10 +89,7 @@ public void handleIPv6HostaddressLoopbackShort() throws UnknownHostException { assertSingleAddressWithPort(hostAddress, null, editor.getValue()); } - /** - * @see DATAMONGO-808 - */ - @Test + @Test // DATAMONGO-808 public void handleIPv6HostaddressLoopbackShortWithPort() throws UnknownHostException { String hostAddress = "::1"; @@ -120,10 +101,8 @@ public void handleIPv6HostaddressLoopbackShortWithPort() throws UnknownHostExcep /** * Here we detect no port since the last segment of the address contains leading zeros. - * - * @see DATAMONGO-808 */ - @Test + @Test // DATAMONGO-808 public void handleIPv6HostaddressLoopbackLong() throws UnknownHostException { String hostAddress = "0000:0000:0000:0000:0000:0000:0000:0001"; @@ -132,10 +111,7 @@ public void handleIPv6HostaddressLoopbackLong() throws UnknownHostException { assertSingleAddressWithPort(hostAddress, null, editor.getValue()); } - /** - * @see DATAMONGO-808 - */ - @Test + @Test // DATAMONGO-808 public void handleIPv6HostaddressLoopbackLongWithBrackets() throws UnknownHostException { String hostAddress = "[0000:0000:0000:0000:0000:0000:0000:0001]"; @@ -146,10 +122,8 @@ public void handleIPv6HostaddressLoopbackLongWithBrackets() throws UnknownHostEx /** * We can't tell whether the last part of the hostAddress represents a port or not. - * - * @see DATAMONGO-808 */ - @Test + @Test // DATAMONGO-808 public void shouldFailToHandleAmbiguousIPv6HostaddressLongWithoutPortAndWithoutBrackets() throws UnknownHostException { expectedException.expect(IllegalArgumentException.class); @@ -158,10 +132,7 @@ public void shouldFailToHandleAmbiguousIPv6HostaddressLongWithoutPortAndWithoutB editor.setAsText(hostAddress); } - /** - * @see DATAMONGO-808 - */ - @Test + @Test // DATAMONGO-808 public void handleIPv6HostaddressExampleAddressWithPort() throws UnknownHostException { String hostAddress = "0000:0000:0000:0000:0000:0000:0000:0001"; @@ -171,10 +142,7 @@ public void handleIPv6HostaddressExampleAddressWithPort() throws UnknownHostExce assertSingleAddressWithPort(hostAddress, port, editor.getValue()); } - /** - * @see DATAMONGO-808 - */ - @Test + @Test // DATAMONGO-808 public void handleIPv6HostaddressExampleAddressInBracketsWithPort() throws UnknownHostException { String hostAddress = "[0000:0000:0000:0000:0000:0000:0000:0001]"; diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/CloseableIterableCursorAdapterUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/CloseableIterableCursorAdapterUnitTests.java index 1ac451b619..daca6484ff 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/CloseableIterableCursorAdapterUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/CloseableIterableCursorAdapterUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 the original author or authors. + * Copyright 2015-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,7 +33,6 @@ * Unit tests for {@link CloseableIterableCursorAdapter}. * * @author Oliver Gierke - * @see DATAMONGO-1276 */ @RunWith(MockitoJUnitRunner.class) public class CloseableIterableCursorAdapterUnitTests { @@ -51,30 +50,21 @@ public void setUp() { this.adapter = new CloseableIterableCursorAdapter(cursor, exceptionTranslator, callback); } - /** - * @see DATAMONGO-1276 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-1276 public void propagatesOriginalExceptionFromAdapterDotNext() { cursor.next(); adapter.next(); } - /** - * @see DATAMONGO-1276 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-1276 public void propagatesOriginalExceptionFromAdapterDotHasNext() { cursor.hasNext(); adapter.hasNext(); } - /** - * @see DATAMONGO-1276 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-1276 public void propagatesOriginalExceptionFromAdapterDotClose() { cursor.close(); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DBObjectTestUtils.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DBObjectTestUtils.java index f35391e626..f3701bbe16 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DBObjectTestUtils.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DBObjectTestUtils.java @@ -18,6 +18,8 @@ import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; +import java.util.List; + import com.mongodb.BasicDBList; import com.mongodb.DBObject; @@ -54,6 +56,10 @@ public static BasicDBList getAsDBList(DBObject source, String key) { return getTypedValue(source, key, BasicDBList.class); } + public static List getAsList(DBObject source, String key) { + return getTypedValue(source, key, List.class); + } + /** * Expects the list element with the given index to be a non-{@literal null} {@link DBObject} and returns it. * diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultBulkOperationsIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultBulkOperationsIntegrationTests.java index 0f3f0b2043..e4b9d9e168 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultBulkOperationsIntegrationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultBulkOperationsIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2016 the original author or authors. + * Copyright 2015-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -46,6 +46,7 @@ * * @author Tobias Trelle * @author Oliver Gierke + * @author Christoph Strobl */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:infrastructure.xml") @@ -64,34 +65,22 @@ public void setUp() { this.collection.remove(new BasicDBObject()); } - /** - * @see DATAMONGO-934 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-934 public void rejectsNullMongoOperations() { new DefaultBulkOperations(null, null, COLLECTION_NAME, null); } - /** - * @see DATAMONGO-934 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-934 public void rejectsNullCollectionName() { new DefaultBulkOperations(operations, null, null, null); } - /** - * @see DATAMONGO-934 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-934 public void rejectsEmptyCollectionName() { new DefaultBulkOperations(operations, null, "", null); } - /** - * @see DATAMONGO-934 - */ - @Test + @Test // DATAMONGO-934 public void insertOrdered() { List documents = Arrays.asList(newDoc("1"), newDoc("2")); @@ -99,10 +88,7 @@ public void insertOrdered() { assertThat(createBulkOps(BulkMode.ORDERED).insert(documents).execute().getInsertedCount(), is(2)); } - /** - * @see DATAMONGO-934 - */ - @Test + @Test // DATAMONGO-934 public void insertOrderedFails() { List documents = Arrays.asList(newDoc("1"), newDoc("1"), newDoc("2")); @@ -117,10 +103,7 @@ public void insertOrderedFails() { } } - /** - * @see DATAMONGO-934 - */ - @Test + @Test // DATAMONGO-934 public void insertUnOrdered() { List documents = Arrays.asList(newDoc("1"), newDoc("2")); @@ -128,10 +111,7 @@ public void insertUnOrdered() { assertThat(createBulkOps(BulkMode.UNORDERED).insert(documents).execute().getInsertedCount(), is(2)); } - /** - * @see DATAMONGO-934 - */ - @Test + @Test // DATAMONGO-934 public void insertUnOrderedContinuesOnError() { List documents = Arrays.asList(newDoc("1"), newDoc("1"), newDoc("2")); @@ -146,10 +126,7 @@ public void insertUnOrderedContinuesOnError() { } } - /** - * @see DATAMONGO-934 - */ - @Test + @Test // DATAMONGO-934 public void upsertDoesUpdate() { insertSomeDocuments(); @@ -166,10 +143,7 @@ public void upsertDoesUpdate() { assertThat(result.getUpserts().size(), is(0)); } - /** - * @see DATAMONGO-934 - */ - @Test + @Test // DATAMONGO-934 public void upsertDoesInsert() { BulkWriteResult result = createBulkOps(BulkMode.ORDERED).// @@ -183,60 +157,40 @@ public void upsertDoesInsert() { assertThat(result.getUpserts().size(), is(1)); } - /** - * @see DATAMONGO-934 - */ - @Test + @Test // DATAMONGO-934 public void updateOneOrdered() { testUpdate(BulkMode.ORDERED, false, 2); } - /** - * @see DATAMONGO-934 - */ - @Test + @Test // DATAMONGO-934 public void updateMultiOrdered() { testUpdate(BulkMode.ORDERED, true, 4); } - /** - * @see DATAMONGO-934 - */ - @Test + @Test // DATAMONGO-934 public void updateOneUnOrdered() { testUpdate(BulkMode.UNORDERED, false, 2); } - /** - * @see DATAMONGO-934 - */ - @Test + @Test // DATAMONGO-934 public void updateMultiUnOrdered() { testUpdate(BulkMode.UNORDERED, true, 4); } - /** - * @see DATAMONGO-934 - */ - @Test + @Test // DATAMONGO-934 public void removeOrdered() { testRemove(BulkMode.ORDERED); } - /** - * @see DATAMONGO-934 - */ - @Test + @Test // DATAMONGO-934 public void removeUnordered() { testRemove(BulkMode.UNORDERED); } /** * If working on the same set of documents, only an ordered bulk operation will yield predictable results. - * - * @see DATAMONGO-934 */ - @Test + @Test // DATAMONGO-934 public void mixedBulkOrdered() { BulkWriteResult result = createBulkOps(BulkMode.ORDERED).insert(newDoc("1", "v1")).// @@ -270,6 +224,22 @@ public void mixedBulkOrderedWithList() { assertThat(result.getRemovedCount(), is(1)); } + @Test // DATAMONGO-1534 + public void insertShouldConsiderInheritance() { + + SpecialDoc specialDoc = new SpecialDoc(); + specialDoc.id = "id-special"; + specialDoc.value = "normal-value"; + specialDoc.specialValue = "special-value"; + + createBulkOps(BulkMode.ORDERED).insert(Arrays.asList(specialDoc)).execute(); + + BaseDoc doc = operations.findOne(where("_id", specialDoc.id), BaseDoc.class, COLLECTION_NAME); + + assertThat(doc, notNullValue()); + assertThat(doc, instanceOf(SpecialDoc.class)); + } + private void testUpdate(BulkMode mode, boolean multi, int expectedUpdates) { BulkOperations bulkOps = createBulkOps(mode); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultIndexOperationsIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultIndexOperationsIntegrationTests.java index 744387de80..04ab43f423 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultIndexOperationsIntegrationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultIndexOperationsIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2015 the original author or authors. + * Copyright 2014-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,18 +17,28 @@ import static org.hamcrest.CoreMatchers.*; import static org.junit.Assert.*; +import static org.junit.Assume.*; import static org.springframework.data.mongodb.core.ReflectiveDBCollectionInvoker.*; +import static org.springframework.data.mongodb.core.index.PartialIndexFilter.*; +import static org.springframework.data.mongodb.core.query.Criteria.*; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Sort.Direction; +import org.springframework.data.mongodb.core.index.Index; +import org.springframework.data.mongodb.core.index.IndexDefinition; import org.springframework.data.mongodb.core.index.IndexInfo; +import org.springframework.data.mongodb.core.mapping.Document; +import org.springframework.data.mongodb.core.mapping.Field; +import org.springframework.data.util.Version; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.util.ObjectUtils; import com.mongodb.BasicDBObject; +import com.mongodb.CommandResult; import com.mongodb.DBCollection; import com.mongodb.DBObject; @@ -42,6 +52,9 @@ @ContextConfiguration("classpath:infrastructure.xml") public class DefaultIndexOperationsIntegrationTests { + private static final Version THREE_DOT_TWO = new Version(3, 2); + private static Version mongoVersion; + static final DBObject GEO_SPHERE_2D = new BasicDBObject("loaction", "2dsphere"); @Autowired MongoTemplate template; @@ -51,6 +64,7 @@ public class DefaultIndexOperationsIntegrationTests { @Before public void setUp() { + queryMongoVersionIfNecessary(); String collectionName = this.template.getCollectionName(DefaultIndexOperationsIntegrationTestsSample.class); this.collection = this.template.getDb().getCollection(collectionName); @@ -59,10 +73,15 @@ public void setUp() { this.indexOps = new DefaultIndexOperations(template, collectionName); } - /** - * @see DATAMONGO-1008 - */ - @Test + private void queryMongoVersionIfNecessary() { + + if (mongoVersion == null) { + CommandResult result = template.executeCommand("{ buildInfo: 1 }"); + mongoVersion = Version.parse(result.get("version").toString()); + } + } + + @Test // DATAMONGO-1008 public void getIndexInfoShouldBeAbleToRead2dsphereIndex() { collection.createIndex(GEO_SPHERE_2D); @@ -71,6 +90,66 @@ public void getIndexInfoShouldBeAbleToRead2dsphereIndex() { assertThat(info.getIndexFields().get(0).isGeo(), is(true)); } + @Test // DATAMONGO-1467 + public void shouldApplyPartialFilterCorrectly() { + + assumeThat(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_TWO), is(true)); + + IndexDefinition id = new Index().named("partial-with-criteria").on("k3y", Direction.ASC) + .partial(of(where("q-t-y").gte(10))); + + indexOps.ensureIndex(id); + + IndexInfo info = findAndReturnIndexInfo(indexOps.getIndexInfo(), "partial-with-criteria"); + assertThat(info.getPartialFilterExpression(), is(equalTo("{ \"q-t-y\" : { \"$gte\" : 10}}"))); + } + + @Test // DATAMONGO-1467 + public void shouldApplyPartialFilterWithMappedPropertyCorrectly() { + + assumeThat(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_TWO), is(true)); + + IndexDefinition id = new Index().named("partial-with-mapped-criteria").on("k3y", Direction.ASC) + .partial(of(where("quantity").gte(10))); + + indexOps.ensureIndex(id); + + IndexInfo info = findAndReturnIndexInfo(indexOps.getIndexInfo(), "partial-with-mapped-criteria"); + assertThat(info.getPartialFilterExpression(), is(equalTo("{ \"qty\" : { \"$gte\" : 10}}"))); + } + + @Test // DATAMONGO-1467 + public void shouldApplyPartialDBOFilterCorrectly() { + + assumeThat(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_TWO), is(true)); + + IndexDefinition id = new Index().named("partial-with-dbo").on("k3y", Direction.ASC) + .partial(of(new BasicDBObject("qty", new BasicDBObject("$gte", 10)))); + + indexOps.ensureIndex(id); + + IndexInfo info = findAndReturnIndexInfo(indexOps.getIndexInfo(), "partial-with-dbo"); + assertThat(info.getPartialFilterExpression(), is(equalTo("{ \"qty\" : { \"$gte\" : 10}}"))); + } + + @Test // DATAMONGO-1467 + public void shouldFavorExplicitMappingHintViaClass() { + + assumeThat(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_TWO), is(true)); + + IndexDefinition id = new Index().named("partial-with-inheritance").on("k3y", Direction.ASC) + .partial(of(where("age").gte(10))); + + indexOps = new DefaultIndexOperations(template, + this.template.getCollectionName(DefaultIndexOperationsIntegrationTestsSample.class), + MappingToSameCollection.class); + + indexOps.ensureIndex(id); + + IndexInfo info = findAndReturnIndexInfo(indexOps.getIndexInfo(), "partial-with-inheritance"); + assertThat(info.getPartialFilterExpression(), is(equalTo("{ \"a_g_e\" : { \"$gte\" : 10}}"))); + } + private IndexInfo findAndReturnIndexInfo(DBObject keys) { return findAndReturnIndexInfo(indexOps.getIndexInfo(), keys); } @@ -89,5 +168,15 @@ private static IndexInfo findAndReturnIndexInfo(Iterable candidates, throw new AssertionError(String.format("Index with %s was not found", name)); } - static class DefaultIndexOperationsIntegrationTestsSample {} + @Document(collection = "default-index-operations-tests") + static class DefaultIndexOperationsIntegrationTestsSample { + + @Field("qty") Integer quantity; + } + + @Document(collection = "default-index-operations-tests") + static class MappingToSameCollection extends DefaultIndexOperationsIntegrationTestsSample { + + @Field("a_g_e") Integer age; + } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultScriptOperationsTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultScriptOperationsTests.java index 04834ae69a..9bbaa7c9eb 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultScriptOperationsTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultScriptOperationsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2016 the original author or authors. + * Copyright 2014-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -82,18 +82,12 @@ public void setUp() { this.scriptOps = new DefaultScriptOperations(template); } - /** - * @see DATAMONGO-479 - */ - @Test + @Test // DATAMONGO-479 public void executeShouldDirectlyRunExecutableMongoScript() { assertThat(scriptOps.execute(EXECUTABLE_SCRIPT, 10), is((Object) 10D)); } - /** - * @see DATAMONGO-479 - */ - @Test + @Test // DATAMONGO-479 public void saveShouldStoreCallableScriptCorrectly() { Query query = query(where("_id").is(SCRIPT_NAME)); @@ -104,10 +98,7 @@ public void saveShouldStoreCallableScriptCorrectly() { assumeThat(template.exists(query, JAVASCRIPT_COLLECTION_NAME), is(true)); } - /** - * @see DATAMONGO-479 - */ - @Test + @Test // DATAMONGO-479 public void saveShouldStoreExecutableScriptCorrectly() { NamedMongoScript script = scriptOps.register(EXECUTABLE_SCRIPT); @@ -116,10 +107,7 @@ public void saveShouldStoreExecutableScriptCorrectly() { assumeThat(template.exists(query, JAVASCRIPT_COLLECTION_NAME), is(true)); } - /** - * @see DATAMONGO-479 - */ - @Test + @Test // DATAMONGO-479 public void executeShouldRunCallableScriptThatHasBeenSavedBefore() { scriptOps.register(CALLABLE_SCRIPT); @@ -132,10 +120,7 @@ public void executeShouldRunCallableScriptThatHasBeenSavedBefore() { assertThat(result, is((Object) 10D)); } - /** - * @see DATAMONGO-479 - */ - @Test + @Test // DATAMONGO-479 public void existsShouldReturnTrueIfScriptAvailableOnServer() { scriptOps.register(CALLABLE_SCRIPT); @@ -143,18 +128,12 @@ public void existsShouldReturnTrueIfScriptAvailableOnServer() { assertThat(scriptOps.exists(SCRIPT_NAME), is(true)); } - /** - * @see DATAMONGO-479 - */ - @Test + @Test // DATAMONGO-479 public void existsShouldReturnFalseIfScriptNotAvailableOnServer() { assertThat(scriptOps.exists(SCRIPT_NAME), is(false)); } - /** - * @see DATAMONGO-479 - */ - @Test + @Test // DATAMONGO-479 public void callShouldExecuteExistingScript() { scriptOps.register(CALLABLE_SCRIPT); @@ -164,18 +143,12 @@ public void callShouldExecuteExistingScript() { assertThat(result, is((Object) 10D)); } - /** - * @see DATAMONGO-479 - */ - @Test(expected = UncategorizedDataAccessException.class) + @Test(expected = UncategorizedDataAccessException.class) // DATAMONGO-479 public void callShouldThrowExceptionWhenCallingScriptThatDoesNotExist() { scriptOps.call(SCRIPT_NAME, 10); } - /** - * @see DATAMONGO-479 - */ - @Test + @Test // DATAMONGO-479 public void scriptNamesShouldContainNameOfRegisteredScript() { scriptOps.register(CALLABLE_SCRIPT); @@ -183,18 +156,12 @@ public void scriptNamesShouldContainNameOfRegisteredScript() { assertThat(scriptOps.getScriptNames(), hasItems("echo")); } - /** - * @see DATAMONGO-479 - */ - @Test + @Test // DATAMONGO-479 public void scriptNamesShouldReturnEmptySetWhenNoScriptRegistered() { assertThat(scriptOps.getScriptNames(), is(empty())); } - /** - * @see DATAMONGO-1465 - */ - @Test + @Test // DATAMONGO-1465 public void executeShouldNotQuoteStrings() { assertThat(scriptOps.execute(EXECUTABLE_SCRIPT, "spring-data"), is((Object) "spring-data")); } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultScriptOperationsUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultScriptOperationsUnitTests.java index 833bf1028a..3a42d72122 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultScriptOperationsUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultScriptOperationsUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2015 the original author or authors. + * Copyright 2014-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -47,26 +47,17 @@ public void setUp() { this.scriptOps = new DefaultScriptOperations(mongoOperations); } - /** - * @see DATAMONGO-479 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-479 public void rejectsNullExecutableMongoScript() { scriptOps.register((ExecutableMongoScript) null); } - /** - * @see DATAMONGO-479 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-479 public void rejectsNullNamedMongoScript() { scriptOps.register((NamedMongoScript) null); } - /** - * @see DATAMONGO-479 - */ - @Test + @Test // DATAMONGO-479 public void saveShouldUseCorrectCollectionName() { scriptOps.register(new NamedMongoScript("foo", "function...")); @@ -74,10 +65,7 @@ public void saveShouldUseCorrectCollectionName() { verify(mongoOperations, times(1)).save(any(NamedMongoScript.class), eq("system.js")); } - /** - * @see DATAMONGO-479 - */ - @Test + @Test // DATAMONGO-479 public void saveShouldGenerateScriptNameForExecutableMongoScripts() { scriptOps.register(new ExecutableMongoScript("function...")); @@ -88,42 +76,27 @@ public void saveShouldGenerateScriptNameForExecutableMongoScripts() { Assert.assertThat(captor.getValue().getName(), notNullValue()); } - /** - * @see DATAMONGO-479 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-479 public void executeShouldThrowExceptionWhenScriptIsNull() { scriptOps.execute(null); } - /** - * @see DATAMONGO-479 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-479 public void existsShouldThrowExceptionWhenScriptNameIsNull() { scriptOps.exists(null); } - /** - * @see DATAMONGO-479 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-479 public void existsShouldThrowExceptionWhenScriptNameIsEmpty() { scriptOps.exists(""); } - /** - * @see DATAMONGO-479 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-479 public void callShouldThrowExceptionWhenScriptNameIsNull() { scriptOps.call(null); } - /** - * @see DATAMONGO-479 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-479 public void callShouldThrowExceptionWhenScriptNameIsEmpty() { scriptOps.call(""); } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/GeoCommandStatisticsUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/GeoCommandStatisticsUnitTests.java index 7fd4a093e1..ec4d05e091 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/GeoCommandStatisticsUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/GeoCommandStatisticsUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 the original author or authors. + * Copyright 2016-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,18 +30,12 @@ */ public class GeoCommandStatisticsUnitTests { - /** - * @see DATAMONGO-1361 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-1361 public void rejectsNullCommandResult() { GeoCommandStatistics.from(null); } - /** - * @see DATAMONGO-1361 - */ - @Test + @Test // DATAMONGO-1361 public void fallsBackToNanIfNoAverageDistanceIsAvailable() { GeoCommandStatistics statistics = GeoCommandStatistics.from(new BasicDBObject("stats", null)); @@ -51,10 +45,7 @@ public void fallsBackToNanIfNoAverageDistanceIsAvailable() { assertThat(statistics.getAverageDistance(), is(Double.NaN)); } - /** - * @see DATAMONGO-1361 - */ - @Test + @Test // DATAMONGO-1361 public void returnsAverageDistanceIfPresent() { GeoCommandStatistics statistics = GeoCommandStatistics diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoClientOptionsFactoryBeanIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoClientOptionsFactoryBeanIntegrationTests.java index 7bd5388d56..ab58426464 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoClientOptionsFactoryBeanIntegrationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoClientOptionsFactoryBeanIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 the original author or authors. + * Copyright 2015-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,10 +33,7 @@ */ public class MongoClientOptionsFactoryBeanIntegrationTests { - /** - * @see DATAMONGO-1158 - */ - @Test + @Test // DATAMONGO-1158 public void convertsReadPreferenceConcernCorrectly() { RootBeanDefinition definition = new RootBeanDefinition(MongoClientOptionsFactoryBean.class); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoDbUtilsIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoDbUtilsIntegrationTests.java index f2032acc57..f292fd842d 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoDbUtilsIntegrationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoDbUtilsIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2015 the original author or authors. + * Copyright 2012-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -89,10 +89,7 @@ public Void doInDB(DB db) throws MongoException, DataAccessException { }); } - /** - * @see DATAMONGO-585 - */ - @Test + @Test // DATAMONGO-585 public void authenticatesCorrectlyInMultithreadedEnvironment() throws Exception { // Create sample user @@ -131,10 +128,7 @@ public Void call() throws Exception { } } - /** - * @see DATAMONGO-789 - */ - @Test + @Test // DATAMONGO-789 public void authenticatesCorrectlyWithAuthenticationDB() throws Exception { // Create sample user diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoDbUtilsUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoDbUtilsUnitTests.java index b7cf04196c..e92d77294c 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoDbUtilsUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoDbUtilsUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2015 the original author or authors. + * Copyright 2012-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -89,10 +89,7 @@ public void returnsSameInstanceForSameDatabaseName() { assertThat(MongoDbUtils.getDB(mongo, "first"), is(sameInstance(first))); } - /** - * @see DATAMONGO-737 - */ - @Test + @Test // DATAMONGO-737 public void handlesTransactionSynchronizationLifecycle() { // ensure transaction synchronization manager has no registered @@ -121,10 +118,7 @@ public void handlesTransactionSynchronizationLifecycle() { assertThat(TransactionSynchronizationManager.getResourceMap().isEmpty(), is(true)); } - /** - * @see DATAMONGO-737 - */ - @Test + @Test // DATAMONGO-737 public void handlesTransactionSynchronizationsLifecycle() { // ensure transaction synchronization manager has no registered @@ -154,10 +148,7 @@ public void handlesTransactionSynchronizationsLifecycle() { assertThat(TransactionSynchronizationManager.getResourceMap().isEmpty(), is(true)); } - /** - * @see DATAMONGO-1218 - */ - @Test + @Test // DATAMONGO-1218 @SuppressWarnings("deprecation") public void getDBDAuthenticateViaAuthDbWhenCalledWithMongoInstance() { @@ -174,10 +165,7 @@ public void getDBDAuthenticateViaAuthDbWhenCalledWithMongoInstance() { verify(mongo, times(1)).getDB("authdb"); } - /** - * @see DATAMONGO-1218 - */ - @Test + @Test // DATAMONGO-1218 @SuppressWarnings("deprecation") public void getDBDShouldSkipAuthenticationViaAuthDbWhenCalledWithMongoClientInstance() { diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoFactoryBeanIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoFactoryBeanIntegrationTests.java index 1d60455231..f31205b397 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoFactoryBeanIntegrationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoFactoryBeanIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2013 the original author or authors. + * Copyright 2012-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -37,10 +37,7 @@ */ public class MongoFactoryBeanIntegrationTests { - /** - * @see DATAMONGO-408 - */ - @Test + @Test // DATAMONGO-408 public void convertsWriteConcernCorrectly() { RootBeanDefinition definition = new RootBeanDefinition(MongoFactoryBean.class); @@ -54,10 +51,7 @@ public void convertsWriteConcernCorrectly() { assertThat(ReflectionTestUtils.getField(bean, "writeConcern"), is((Object) WriteConcern.SAFE)); } - /** - * @see DATAMONGO-693 - */ - @Test + @Test // DATAMONGO-693 public void createMongoInstanceWithHostAndEmptyReplicaSets() { RootBeanDefinition definition = new RootBeanDefinition(MongoFactoryBean.class); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoOperationsUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoOperationsUnitTests.java index 9614330608..9d2d82ab04 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoOperationsUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoOperationsUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2014 the original author or authors. + * Copyright 2011-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -302,10 +302,7 @@ public void doWith(MongoOperations operations) { }.assertDataAccessException(); } - /** - * @see DATAMONGO-341 - */ - @Test + @Test // DATAMONGO-341 public void geoNearRejectsNullNearQuery() { new Execution() { @@ -316,10 +313,7 @@ public void doWith(MongoOperations operations) { }.assertDataAccessException(); } - /** - * @see DATAMONGO-341 - */ - @Test + @Test // DATAMONGO-341 public void geoNearRejectsNullNearQueryifCollectionGiven() { new Execution() { @@ -330,10 +324,7 @@ public void doWith(MongoOperations operations) { }.assertDataAccessException(); } - /** - * @see DATAMONGO-341 - */ - @Test + @Test // DATAMONGO-341 public void geoNearRejectsNullEntityClass() { final NearQuery query = NearQuery.near(new Point(10, 20)); @@ -346,10 +337,7 @@ public void doWith(MongoOperations operations) { }.assertDataAccessException(); } - /** - * @see DATAMONGO-341 - */ - @Test + @Test // DATAMONGO-341 public void geoNearRejectsNullEntityClassIfCollectionGiven() { final NearQuery query = NearQuery.near(new Point(10, 20)); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoOptionsFactoryBeanUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoOptionsFactoryBeanUnitTests.java index 19db55a393..c490ec5671 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoOptionsFactoryBeanUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoOptionsFactoryBeanUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2015 the original author or authors. + * Copyright 2011-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -43,11 +43,7 @@ public static void validateMongoDriver() { assumeFalse(isMongo3Driver()); } - /** - * @throws Exception - * @see DATADOC-280 - */ - @Test + @Test // DATADOC-280 public void setsMaxConnectRetryTime() throws Exception { MongoOptionsFactoryBean bean = new MongoOptionsFactoryBean(); @@ -58,11 +54,7 @@ public void setsMaxConnectRetryTime() throws Exception { assertThat(getMaxAutoConnectRetryTime(options), is(27L)); } - /** - * @throws Exception - * @see DATAMONGO-764 - */ - @Test + @Test // DATAMONGO-764 public void testSslConnection() throws Exception { MongoOptionsFactoryBean bean = new MongoOptionsFactoryBean(); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateTests.java index 385b501ec6..a281b9d627 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2016 the original author or authors. + * Copyright 2011-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,6 +24,11 @@ import static org.springframework.data.mongodb.core.query.Query.*; import static org.springframework.data.mongodb.core.query.Update.*; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import java.lang.reflect.InvocationTargetException; import java.math.BigDecimal; import java.math.BigInteger; import java.util.ArrayList; @@ -82,10 +87,12 @@ import org.springframework.data.mongodb.core.query.Criteria; import org.springframework.data.mongodb.core.query.Query; import org.springframework.data.mongodb.core.query.Update; +import org.springframework.data.mongodb.util.MongoClientVersion; import org.springframework.data.util.CloseableIterator; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.util.ClassUtils; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; @@ -101,10 +108,6 @@ import com.mongodb.WriteConcern; import com.mongodb.WriteResult; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.NoArgsConstructor; - /** * Integration test for {@link MongoTemplate}. * @@ -125,6 +128,9 @@ public class MongoTemplateTests { .parse("2.4"); private static final org.springframework.data.util.Version TWO_DOT_EIGHT = org.springframework.data.util.Version .parse("2.8"); + private static final org.springframework.data.util.Version THREE_DOT_FOUR= org.springframework.data.util.Version + .parse("3.4"); + @Autowired MongoTemplate template; @Autowired MongoDbFactory factory; @@ -243,10 +249,7 @@ public void bogusUpdateDoesNotTriggerException() throws Exception { mongoTemplate.updateFirst(q, u, Person.class); } - /** - * @see DATAMONGO-480 - */ - @Test + @Test // DATAMONGO-480 public void throwsExceptionForDuplicateIds() { MongoTemplate template = new MongoTemplate(factory); @@ -265,11 +268,7 @@ public void throwsExceptionForDuplicateIds() { } } - /** - * @see DATAMONGO-480 - * @see DATAMONGO-799 - */ - @Test + @Test // DATAMONGO-480, DATAMONGO-799 public void throwsExceptionForUpdateWithInvalidPushOperator() { MongoTemplate template = new MongoTemplate(factory); @@ -291,10 +290,7 @@ public void throwsExceptionForUpdateWithInvalidPushOperator() { template.updateFirst(query, upd, Person.class); } - /** - * @see DATAMONGO-480 - */ - @Test + @Test // DATAMONGO-480 public void throwsExceptionForIndexViolationIfConfigured() { MongoTemplate template = new MongoTemplate(factory); @@ -317,10 +313,7 @@ public void throwsExceptionForIndexViolationIfConfigured() { } } - /** - * @see DATAMONGO-480 - */ - @Test + @Test // DATAMONGO-480 public void rejectsDuplicateIdInInsertAll() { thrown.expect(DataIntegrityViolationException.class); @@ -394,10 +387,7 @@ public void testEnsureIndex() throws Exception { assertThat(field, is(IndexField.create("age", Direction.DESC))); } - /** - * @see DATAMONGO-746 - */ - @Test + @Test // DATAMONGO-746 public void testReadIndexInfoForIndicesCreatedViaMongoShellCommands() throws Exception { String command = "db." + template.getCollectionName(Person.class) @@ -578,9 +568,7 @@ private void testProperHandlingOfDifferentIdTypes(MongoTemplate mongoTemplate) t assertThat(p9q.getId(), is(p9.getId())); checkCollectionContents(PersonWithIdPropertyOfTypeInteger.class, 1); - /* - * @see DATAMONGO-602 - */ + // DATAMONGO-602 // BigInteger id - provided PersonWithIdPropertyOfTypeBigInteger p9bi = new PersonWithIdPropertyOfTypeBigInteger(); p9bi.setFirstName("Sven_9bi"); @@ -650,10 +638,7 @@ private void checkCollectionContents(Class entityClass, int count) { assertThat(template.findAll(entityClass).size(), is(count)); } - /** - * @see DATAMONGO-234 - */ - @Test + @Test // DATAMONGO-234 public void testFindAndUpdate() { template.insert(new Person("Tom", 21)); @@ -821,10 +806,7 @@ public void testUsingAnInQueryWithLongId() throws Exception { assertThat(results3.size(), is(2)); } - /** - * @see DATAMONGO-602 - */ - @Test + @Test // DATAMONGO-602 public void testUsingAnInQueryWithBigIntegerId() throws Exception { template.remove(new Query(), PersonWithIdPropertyOfTypeBigInteger.class); @@ -1141,18 +1123,12 @@ public Object doInCollection(DBCollection collection) throws MongoException, Dat }); } - /** - * @see DATADOC-166 - */ - @Test + @Test // DATADOC-166 public void removingNullIsANoOp() { template.remove(null); } - /** - * @see DATADOC-240, DATADOC-212 - */ - @Test + @Test // DATADOC-240, DATADOC-212 public void updatesObjectIdsCorrectly() { PersonWithIdPropertyOfTypeObjectId person = new PersonWithIdPropertyOfTypeObjectId(); @@ -1211,10 +1187,7 @@ public MongoAction getMongoAction() { } } - /** - * @see DATADOC-246 - */ - @Test + @Test // DATADOC-246 public void updatesDBRefsCorrectly() { DBRef first = new DBRef("foo", new ObjectId()); @@ -1227,10 +1200,7 @@ class ClassWithDBRefs { List dbrefs; } - /** - * @see DATADOC-202 - */ - @Test + @Test // DATADOC-202 public void executeDocument() { template.insert(new Person("Tom")); template.insert(new Person("Dick")); @@ -1248,10 +1218,7 @@ public void processDocument(DBObject dbObject) { // template.remove(new Query(), Person.class); } - /** - * @see DATADOC-202 - */ - @Test + @Test // DATADOC-202 public void executeDocumentWithCursorPreparer() { template.insert(new Person("Tom")); template.insert(new Person("Dick")); @@ -1276,10 +1243,7 @@ public DBCursor prepare(DBCursor cursor) { // template.remove(new Query(), Person.class); } - /** - * @see DATADOC-183 - */ - @Test + @Test // DATADOC-183 public void countsDocumentsCorrectly() { assertThat(template.count(new Query(), Person.class), is(0L)); @@ -1294,26 +1258,17 @@ public void countsDocumentsCorrectly() { assertThat(template.count(query(where("firstName").is("Carter")), Person.class), is(1L)); } - /** - * @see DATADOC-183 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATADOC-183 public void countRejectsNullEntityClass() { template.count(null, (Class) null); } - /** - * @see DATADOC-183 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATADOC-183 public void countRejectsEmptyCollectionName() { template.count(null, ""); } - /** - * @see DATADOC-183 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATADOC-183 public void countRejectsNullCollectionName() { template.count(null, (String) null); } @@ -1331,10 +1286,7 @@ public void returnsEntityWhenQueryingForDateTime() { assertThat(testClassList.get(0).myDate, is(testClass.myDate)); } - /** - * @see DATADOC-230 - */ - @Test + @Test // DATADOC-230 public void removesEntityFromCollection() { template.remove(new Query(), "mycollection"); @@ -1348,10 +1300,7 @@ public void removesEntityFromCollection() { assertThat(template.findAll(Person.class, "mycollection").isEmpty(), is(true)); } - /** - * @see DATADOC-349 - */ - @Test + @Test // DATADOC-349 public void removesEntityWithAnnotatedIdIfIdNeedsMassaging() { String id = new ObjectId().toString(); @@ -1367,10 +1316,7 @@ public void removesEntityWithAnnotatedIdIfIdNeedsMassaging() { assertThat(template.findOne(query(where("id").is(id)), Sample.class), is(nullValue())); } - /** - * @see DATAMONGO-423 - */ - @Test + @Test // DATAMONGO-423 public void executesQueryWithNegatedRegexCorrectly() { Sample first = new Sample(); @@ -1389,10 +1335,7 @@ public void executesQueryWithNegatedRegexCorrectly() { assertThat(result.get(0).field, is("Beauford")); } - /** - * @see DATAMONGO-447 - */ - @Test + @Test // DATAMONGO-447 public void storesAndRemovesTypeWithComplexId() { MyId id = new MyId(); @@ -1406,10 +1349,7 @@ public void storesAndRemovesTypeWithComplexId() { template.remove(query(where("id").is(id)), TypeWithMyId.class); } - /** - * @see DATAMONGO-506 - */ - @Test + @Test // DATAMONGO-506 public void exceutesBasicQueryCorrectly() { Address address = new Address(); @@ -1435,10 +1375,7 @@ public void exceutesBasicQueryCorrectly() { assertThat(result.get(0), hasProperty("name", is("Oleg"))); } - /** - * @see DATAMONGO-279 - */ - @Test(expected = OptimisticLockingFailureException.class) + @Test(expected = OptimisticLockingFailureException.class) // DATAMONGO-279 public void optimisticLockingHandling() { // Init version @@ -1473,10 +1410,7 @@ public void optimisticLockingHandling() { template.save(person); } - /** - * @see DATAMONGO-562 - */ - @Test + @Test // DATAMONGO-562 public void optimisticLockingHandlingWithExistingId() { PersonWithVersionPropertyOfTypeInteger person = new PersonWithVersionPropertyOfTypeInteger(); @@ -1486,10 +1420,7 @@ public void optimisticLockingHandlingWithExistingId() { template.save(person); } - /** - * @see DATAMONGO-617 - */ - @Test + @Test // DATAMONGO-617 public void doesNotFailOnVersionInitForUnversionedEntity() { DBObject dbObject = new BasicDBObject(); @@ -1498,10 +1429,7 @@ public void doesNotFailOnVersionInitForUnversionedEntity() { template.insert(dbObject, template.determineCollectionName(PersonWithVersionPropertyOfTypeInteger.class)); } - /** - * @see DATAMONGO-539 - */ - @Test + @Test // DATAMONGO-539 public void removesObjectFromExplicitCollection() { String collectionName = "explicit"; @@ -1516,9 +1444,7 @@ public void removesObjectFromExplicitCollection() { assertThat(template.findAll(PersonWithConvertedId.class, collectionName).isEmpty(), is(true)); } - /** - * @see DATAMONGO-549 - */ + // DATAMONGO-549 public void savesMapCorrectly() { Map map = new HashMap(); @@ -1527,26 +1453,17 @@ public void savesMapCorrectly() { template.save(map, "maps"); } - /** - * @see DATAMONGO-549 - */ - @Test(expected = MappingException.class) + @Test(expected = MappingException.class) // DATAMONGO-549 public void savesMongoPrimitiveObjectCorrectly() { template.save(new Object(), "collection"); } - /** - * @see DATAMONGO-549 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-549 public void rejectsNullObjectToBeSaved() { template.save(null); } - /** - * @see DATAMONGO-550 - */ - @Test + @Test // DATAMONGO-550 public void savesPlainDbObjectCorrectly() { DBObject dbObject = new BasicDBObject("foo", "bar"); @@ -1555,10 +1472,7 @@ public void savesPlainDbObjectCorrectly() { assertThat(dbObject.containsField("_id"), is(true)); } - /** - * @see DATAMONGO-550 - */ - @Test(expected = InvalidDataAccessApiUsageException.class) + @Test(expected = InvalidDataAccessApiUsageException.class) // DATAMONGO-550 public void rejectsPlainObjectWithOutExplicitCollection() { DBObject dbObject = new BasicDBObject("foo", "bar"); @@ -1567,10 +1481,7 @@ public void rejectsPlainObjectWithOutExplicitCollection() { template.findById(dbObject.get("_id"), DBObject.class); } - /** - * @see DATAMONGO-550 - */ - @Test + @Test // DATAMONGO-550 public void readsPlainDbObjectById() { DBObject dbObject = new BasicDBObject("foo", "bar"); @@ -1581,26 +1492,17 @@ public void readsPlainDbObjectById() { assertThat(result.get("_id"), is(dbObject.get("_id"))); } - /** - * @see DATAMONGO-551 - */ - @Test + @Test // DATAMONGO-551 public void writesPlainString() { template.save("{ 'foo' : 'bar' }", "collection"); } - /** - * @see DATAMONGO-551 - */ - @Test(expected = MappingException.class) + @Test(expected = MappingException.class) // DATAMONGO-551 public void rejectsNonJsonStringForSave() { template.save("Foobar!", "collection"); } - /** - * @see DATAMONGO-588 - */ - @Test + @Test // DATAMONGO-588 public void initializesVersionOnInsert() { PersonWithVersionPropertyOfTypeInteger person = new PersonWithVersionPropertyOfTypeInteger(); @@ -1611,10 +1513,7 @@ public void initializesVersionOnInsert() { assertThat(person.version, is(0)); } - /** - * @see DATAMONGO-588 - */ - @Test + @Test // DATAMONGO-588 public void initializesVersionOnBatchInsert() { PersonWithVersionPropertyOfTypeInteger person = new PersonWithVersionPropertyOfTypeInteger(); @@ -1625,20 +1524,14 @@ public void initializesVersionOnBatchInsert() { assertThat(person.version, is(0)); } - /** - * @see DATAMONGO-568 - */ - @Test + @Test // DATAMONGO-568 public void queryCantBeNull() { List result = template.findAll(PersonWithIdPropertyOfTypeObjectId.class); assertThat(template.find(null, PersonWithIdPropertyOfTypeObjectId.class), is(result)); } - /** - * @see DATAMONGO-620 - */ - @Test + @Test // DATAMONGO-620 public void versionsObjectIntoDedicatedCollection() { PersonWithVersionPropertyOfTypeInteger person = new PersonWithVersionPropertyOfTypeInteger(); @@ -1651,10 +1544,7 @@ public void versionsObjectIntoDedicatedCollection() { assertThat(person.version, is(1)); } - /** - * @see DATAMONGO-621 - */ - @Test + @Test // DATAMONGO-621 public void correctlySetsLongVersionProperty() { PersonWithVersionPropertyOfTypeLong person = new PersonWithVersionPropertyOfTypeLong(); @@ -1664,10 +1554,7 @@ public void correctlySetsLongVersionProperty() { assertThat(person.version, is(0L)); } - /** - * @see DATAMONGO-622 - */ - @Test(expected = DuplicateKeyException.class) + @Test(expected = DuplicateKeyException.class) // DATAMONGO-622 public void preventsDuplicateInsert() { template.setWriteConcern(WriteConcern.SAFE); @@ -1682,10 +1569,7 @@ public void preventsDuplicateInsert() { template.save(person); } - /** - * @see DATAMONGO-629 - */ - @Test + @Test // DATAMONGO-629 public void countAndFindWithoutTypeInformation() { Person person = new Person(); @@ -1698,10 +1582,7 @@ public void countAndFindWithoutTypeInformation() { assertThat(template.count(query, collectionName), is(1L)); } - /** - * @see DATAMONGO-571 - */ - @Test + @Test // DATAMONGO-571 public void nullsPropertiesForVersionObjectUpdates() { VersionedPerson person = new VersionedPerson(); @@ -1718,10 +1599,7 @@ public void nullsPropertiesForVersionObjectUpdates() { assertThat(person.lastname, is(nullValue())); } - /** - * @see DATAMONGO-571 - */ - @Test + @Test // DATAMONGO-571 public void nullsValuesForUpdatesOfUnversionedEntity() { Person person = new Person("Dave"); @@ -1734,10 +1612,7 @@ public void nullsValuesForUpdatesOfUnversionedEntity() { assertThat(person.getFirstName(), is(nullValue())); } - /** - * @see DATAMONGO-679 - */ - @Test + @Test // DATAMONGO-679 public void savesJsonStringCorrectly() { DBObject dbObject = new BasicDBObject().append("first", "first").append("second", "second"); @@ -1762,10 +1637,7 @@ public void executesExistsCorrectly() { assertThat(template.exists(query, Sample.class, template.getCollectionName(Sample.class)), is(true)); } - /** - * @see DATAMONGO-675 - */ - @Test + @Test // DATAMONGO-675 public void updateConsidersMappingAnnotations() { TypeWithFieldAnnotation entity = new TypeWithFieldAnnotation(); @@ -1781,10 +1653,7 @@ public void updateConsidersMappingAnnotations() { assertThat(result.emailAddress, is("new")); } - /** - * @see DATAMONGO-671 - */ - @Test + @Test // DATAMONGO-671 public void findsEntityByDateReference() { TypeWithDate entity = new TypeWithDate(); @@ -1798,10 +1667,7 @@ public void findsEntityByDateReference() { assertThat(result.get(0).date, is(notNullValue())); } - /** - * @see DATAMONGO-540 - */ - @Test + @Test // DATAMONGO-540 public void findOneAfterUpsertForNonExistingObjectReturnsTheInsertedObject() { String idValue = "4711"; @@ -1818,10 +1684,7 @@ public void findOneAfterUpsertForNonExistingObjectReturnsTheInsertedObject() { assertThat(result.id, is(idValue)); } - /** - * @see DATAMONGO-392 - */ - @Test + @Test // DATAMONGO-392 public void updatesShouldRetainTypeInformation() { Document doc = new Document(); @@ -1842,10 +1705,7 @@ public void updatesShouldRetainTypeInformation() { assertThat(result.model.value(), is(newModelValue)); } - /** - * @see DATAMONGO-702 - */ - @Test + @Test // DATAMONGO-702 public void queryShouldSupportRealAndAliasedPropertyNamesForFieldInclusions() { ObjectWith3AliasedFields obj = new ObjectWith3AliasedFields(); @@ -1869,10 +1729,7 @@ public void queryShouldSupportRealAndAliasedPropertyNamesForFieldInclusions() { assertThat(result.property3, is(obj.property3)); } - /** - * @see DATAMONGO-702 - */ - @Test + @Test // DATAMONGO-702 public void queryShouldSupportRealAndAliasedPropertyNamesForFieldExclusions() { ObjectWith3AliasedFields obj = new ObjectWith3AliasedFields(); @@ -1896,10 +1753,7 @@ public void queryShouldSupportRealAndAliasedPropertyNamesForFieldExclusions() { assertThat(result.property3, is(nullValue())); } - /** - * @see DATAMONGO-702 - */ - @Test + @Test // DATAMONGO-702 public void findMultipleWithQueryShouldSupportRealAndAliasedPropertyNamesForFieldExclusions() { ObjectWith3AliasedFields obj0 = new ObjectWith3AliasedFields(); @@ -1941,10 +1795,7 @@ public void findMultipleWithQueryShouldSupportRealAndAliasedPropertyNamesForFiel assertThat(result1.property3, is(nullValue())); } - /** - * @see DATAMONGO-702 - */ - @Test + @Test // DATAMONGO-702 public void queryShouldSupportNestedPropertyNamesForFieldInclusions() { ObjectWith3AliasedFieldsAndNestedAddress obj = new ObjectWith3AliasedFieldsAndNestedAddress(); @@ -1977,10 +1828,7 @@ public void queryShouldSupportNestedPropertyNamesForFieldInclusions() { assertThat(result.address.state, is(stateValue)); } - /** - * @see DATAMONGO-709 - */ - @Test + @Test // DATAMONGO-709 public void aQueryRestrictedWithOneRestrictedResultTypeShouldReturnOnlyInstancesOfTheRestrictedType() { BaseDoc doc0 = new BaseDoc(); @@ -2006,10 +1854,7 @@ public void aQueryRestrictedWithOneRestrictedResultTypeShouldReturnOnlyInstances assertThat(result.get(0), is(instanceOf(SpecialDoc.class))); } - /** - * @see DATAMONGO-709 - */ - @Test + @Test // DATAMONGO-709 public void aQueryRestrictedWithMultipleRestrictedResultTypesShouldReturnOnlyInstancesOfTheRestrictedTypes() { BaseDoc doc0 = new BaseDoc(); @@ -2036,10 +1881,7 @@ public void aQueryRestrictedWithMultipleRestrictedResultTypesShouldReturnOnlyIns assertThat(result.get(1).getClass(), is((Object) VerySpecialDoc.class)); } - /** - * @see DATAMONGO-709 - */ - @Test + @Test // DATAMONGO-709 public void aQueryWithNoRestrictedResultTypesShouldReturnAllInstancesWithinTheGivenCollection() { BaseDoc doc0 = new BaseDoc(); @@ -2067,10 +1909,7 @@ public void aQueryWithNoRestrictedResultTypesShouldReturnAllInstancesWithinTheGi assertThat(result.get(2).getClass(), is((Object) VerySpecialDoc.class)); } - /** - * @see DATAMONGO-771 - */ - @Test + @Test // DATAMONGO-771 public void allowInsertWithPlainJsonString() { String id = "4711"; @@ -2085,10 +1924,7 @@ public void allowInsertWithPlainJsonString() { assertThat(result.get(0).field, is(value)); } - /** - * @see DATAMONGO-816 - */ - @Test + @Test // DATAMONGO-816 public void shouldExecuteQueryShouldMapQueryBeforeQueryExecution() { ObjectWithEnumValue o = new ObjectWithEnumValue(); @@ -2112,10 +1948,7 @@ public void processDocument(DBObject dbObject) throws MongoException, DataAccess }); } - /** - * @see DATAMONGO-811 - */ - @Test + @Test // DATAMONGO-811 public void updateFirstShouldIncreaseVersionForVersionedEntity() { VersionedPerson person = new VersionedPerson(); @@ -2135,10 +1968,7 @@ public void updateFirstShouldIncreaseVersionForVersionedEntity() { assertThat(personAfterUpdateFirst.lastname, is("Bubu")); } - /** - * @see DATAMONGO-811 - */ - @Test + @Test // DATAMONGO-811 public void updateFirstShouldIncreaseVersionOnlyForFirstMatchingEntity() { VersionedPerson person1 = new VersionedPerson(); @@ -2162,10 +1992,7 @@ public void updateFirstShouldIncreaseVersionOnlyForFirstMatchingEntity() { } } - /** - * @see DATAMONGO-811 - */ - @Test + @Test // DATAMONGO-811 public void updateMultiShouldIncreaseVersionOfAllUpdatedEntities() { VersionedPerson person1 = new VersionedPerson(); @@ -2185,10 +2012,7 @@ public void updateMultiShouldIncreaseVersionOfAllUpdatedEntities() { } } - /** - * @see DATAMONGO-686 - */ - @Test + @Test // DATAMONGO-686 public void itShouldBePossibleToReuseAnExistingQuery() { Sample sample = new Sample(); @@ -2208,10 +2032,7 @@ public void itShouldBePossibleToReuseAnExistingQuery() { assertThat(template.find(query, Sample.class), is(not(empty()))); } - /** - * @see DATAMONGO-807 - */ - @Test + @Test // DATAMONGO-807 public void findAndModifyShouldRetrainTypeInformationWithinUpdatedType() { Document document = new Document(); @@ -2228,10 +2049,7 @@ public void findAndModifyShouldRetrainTypeInformationWithinUpdatedType() { assertThat(retrieved.model.value(), equalTo("value2")); } - /** - * @see DATAMONGO-1210 - */ - @Test + @Test // DATAMONGO-1210 public void findAndModifyShouldRetainTypeInformationWithinUpdatedTypeOnDocumentWithNestedCollectionWhenWholeCollectionIsReplaced() { DocumentWithNestedCollection doc = new DocumentWithNestedCollection(); @@ -2265,10 +2083,7 @@ public void findAndModifyShouldRetainTypeInformationWithinUpdatedTypeOnDocumentW assertThat(retrieved.models.get(0).get("key2").value(), equalTo("value2")); } - /** - * @see DATAMONGO-1210 - */ - @Test + @Test // DATAMONGO-1210 public void findAndModifyShouldRetainTypeInformationWithinUpdatedTypeOnDocumentWithNestedCollectionWhenFirstElementIsReplaced() { DocumentWithNestedCollection doc = new DocumentWithNestedCollection(); @@ -2302,10 +2117,7 @@ public void findAndModifyShouldRetainTypeInformationWithinUpdatedTypeOnDocumentW assertThat(retrieved.models.get(0).get("key2").value(), equalTo("value2")); } - /** - * @see DATAMONGO-1210 - */ - @Test + @Test // DATAMONGO-1210 public void findAndModifyShouldAddTypeInformationOnDocumentWithNestedCollectionObjectInsertedAtSecondIndex() { DocumentWithNestedCollection doc = new DocumentWithNestedCollection(); @@ -2338,10 +2150,7 @@ public void findAndModifyShouldAddTypeInformationOnDocumentWithNestedCollectionO assertThat(retrieved.models.get(1).get("key2").value(), equalTo("value2")); } - /** - * @see DATAMONGO-1210 - */ - @Test + @Test // DATAMONGO-1210 public void findAndModifyShouldRetainTypeInformationWithinUpdatedTypeOnEmbeddedDocumentWithCollectionWhenUpdatingPositionedElement() throws Exception { @@ -2368,10 +2177,7 @@ public void findAndModifyShouldRetainTypeInformationWithinUpdatedTypeOnEmbeddedD assertThat(retrieved.embeddedDocument.models.get(0).value(), is("value2")); } - /** - * @see DATAMONGO-1210 - */ - @Test + @Test // DATAMONGO-1210 public void findAndModifyShouldAddTypeInformationWithinUpdatedTypeOnEmbeddedDocumentWithCollectionWhenUpdatingSecondElement() throws Exception { @@ -2399,10 +2205,7 @@ public void findAndModifyShouldAddTypeInformationWithinUpdatedTypeOnEmbeddedDocu assertThat(retrieved.embeddedDocument.models.get(1).value(), is("value2")); } - /** - * @see DATAMONGO-1210 - */ - @Test + @Test // DATAMONGO-1210 public void findAndModifyShouldAddTypeInformationWithinUpdatedTypeOnEmbeddedDocumentWithCollectionWhenRewriting() throws Exception { @@ -2429,10 +2232,7 @@ public void findAndModifyShouldAddTypeInformationWithinUpdatedTypeOnEmbeddedDocu assertThat(retrieved.embeddedDocument.models.get(0).value(), is("value2")); } - /** - * @see DATAMONGO-1210 - */ - @Test + @Test // DATAMONGO-1210 public void findAndModifyShouldAddTypeInformationWithinUpdatedTypeOnDocumentWithNestedLists() { DocumentWithNestedList doc = new DocumentWithNestedList(); @@ -2465,10 +2265,7 @@ public void findAndModifyShouldAddTypeInformationWithinUpdatedTypeOnDocumentWith assertThat(retrieved.models.get(0).get(1).value(), equalTo("value2")); } - /** - * @see DATAMONGO-407 - */ - @Test + @Test // DATAMONGO-407 public void updatesShouldRetainTypeInformationEvenForCollections() { List models = Arrays. asList(new ModelA("foo")); @@ -2493,10 +2290,7 @@ public void updatesShouldRetainTypeInformationEvenForCollections() { assertThat(result.models.get(0).value(), is(newModelValue)); } - /** - * @see DATAMONGO-812 - */ - @Test + @Test // DATAMONGO-812 public void updateMultiShouldAddValuesCorrectlyWhenUsingPushEachWithComplexTypes() { assumeThat(mongoVersion.isGreaterThanOrEqualTo(TWO_DOT_FOUR), is(true)); @@ -2512,10 +2306,7 @@ public void updateMultiShouldAddValuesCorrectlyWhenUsingPushEachWithComplexTypes assertThat(template.findOne(query, DocumentWithCollection.class).models, hasSize(3)); } - /** - * @see DATAMONGO-812 - */ - @Test + @Test // DATAMONGO-812 public void updateMultiShouldAddValuesCorrectlyWhenUsingPushEachWithSimpleTypes() { assumeThat(mongoVersion.isGreaterThanOrEqualTo(TWO_DOT_FOUR), is(true)); @@ -2533,10 +2324,7 @@ public void updateMultiShouldAddValuesCorrectlyWhenUsingPushEachWithSimpleTypes( assertThat(template.findOne(query, DocumentWithCollectionOfSimpleType.class).values, hasSize(3)); } - /** - * @see DATAMONOGO-828 - */ - @Test + @Test // DATAMONOGO-828 public void updateFirstShouldDoNothingWhenCalledForEntitiesThatDoNotExist() { Query q = query(where("id").is(Long.MIN_VALUE)); @@ -2545,10 +2333,7 @@ public void updateFirstShouldDoNothingWhenCalledForEntitiesThatDoNotExist() { assertThat(template.findOne(q, VersionedPerson.class), nullValue()); } - /** - * @see DATAMONGO-354 - */ - @Test + @Test // DATAMONGO-354 public void testUpdateShouldAllowMultiplePushAll() { DocumentWithMultipleCollections doc = new DocumentWithMultipleCollections(); @@ -2570,10 +2355,7 @@ public void testUpdateShouldAllowMultiplePushAll() { } - /** - * @see DATAMONGO-404 - */ - @Test + @Test // DATAMONGO-404 public void updateWithPullShouldRemoveNestedItemFromDbRefAnnotatedCollection() { Sample sample1 = new Sample("1", "A"); @@ -2602,10 +2384,7 @@ public void updateWithPullShouldRemoveNestedItemFromDbRefAnnotatedCollection() { assertThat(result.dbRefAnnotatedList.get(0).id, is((Object) "1")); } - /** - * @see DATAMONGO-404 - */ - @Test + @Test // DATAMONGO-404 public void updateWithPullShouldRemoveNestedItemFromDbRefAnnotatedCollectionWhenGivenAnIdValueOfComponentTypeEntity() { Sample sample1 = new Sample("1", "A"); @@ -2634,10 +2413,7 @@ public void updateWithPullShouldRemoveNestedItemFromDbRefAnnotatedCollectionWhen assertThat(result.dbRefAnnotatedList.get(0).id, is((Object) "1")); } - /** - * @see DATAMONGO-852 - */ - @Test + @Test // DATAMONGO-852 public void updateShouldNotBumpVersionNumberIfVersionPropertyIncludedInUpdate() { VersionedPerson person = new VersionedPerson(); @@ -2657,10 +2433,7 @@ public void updateShouldNotBumpVersionNumberIfVersionPropertyIncludedInUpdate() assertThat(personAfterUpdateFirst.lastname, is("Bubu")); } - /** - * @see DATAMONGO-468 - */ - @Test + @Test // DATAMONGO-468 public void shouldBeAbleToUpdateDbRefPropertyWithDomainObject() { Sample sample1 = new Sample("1", "A"); @@ -2686,10 +2459,7 @@ public void shouldBeAbleToUpdateDbRefPropertyWithDomainObject() { assertThat(updatedDoc.dbRefProperty.field, is(sample2.field)); } - /** - * @see DATAMONGO-862 - */ - @Test + @Test // DATAMONGO-862 public void testUpdateShouldWorkForPathsOnInterfaceMethods() { DocumentWithCollection document = new DocumentWithCollection(Arrays. asList(new ModelA("spring"), @@ -2705,10 +2475,7 @@ public void testUpdateShouldWorkForPathsOnInterfaceMethods() { assertThat(result.models.get(0).value(), is("mongodb")); } - /** - * @see DATAMONGO-773 - */ - @Test + @Test // DATAMONGO-773 public void testShouldSupportQueryWithIncludedDbRefField() { Sample sample = new Sample("47111", "foo"); @@ -2732,10 +2499,7 @@ public void testShouldSupportQueryWithIncludedDbRefField() { assertThat(result.get(0).dbRefProperty.field, is(sample.field)); } - /** - * @see DATAMONGO-566 - */ - @Test + @Test // DATAMONGO-566 public void testFindAllAndRemoveFullyReturnsAndRemovesDocuments() { Sample spring = new Sample("100", "spring"); @@ -2755,10 +2519,7 @@ public void testFindAllAndRemoveFullyReturnsAndRemovesDocuments() { assertThat(template.getDb().getCollection("sample").find(new BasicDBObject("field", "data")).count(), is(1)); } - /** - * @see DATAMONGO-1001 - */ - @Test + @Test // DATAMONGO-1001 public void shouldAllowSavingOfLazyLoadedDbRefs() { template.dropCollection(SomeTemplate.class); @@ -2786,10 +2547,7 @@ public void shouldAllowSavingOfLazyLoadedDbRefs() { } - /** - * @see DATAMONGO-880 - */ - @Test + @Test // DATAMONGO-880 public void savingAndReassigningLazyLoadingProxies() { template.dropCollection(SomeTemplate.class); @@ -2822,10 +2580,7 @@ public void savingAndReassigningLazyLoadingProxies() { assertThat(savedMessage.normalContent.text, is(content.text)); } - /** - * @see DATAMONGO-884 - */ - @Test + @Test // DATAMONGO-884 public void callingNonObjectMethodsOnLazyLoadingProxyShouldReturnNullIfUnderlyingDbrefWasDeletedInbetween() { template.dropCollection(SomeTemplate.class); @@ -2851,10 +2606,7 @@ public void callingNonObjectMethodsOnLazyLoadingProxyShouldReturnNullIfUnderlyin assertThat(savedTmpl.getContent().getText(), is(nullValue())); } - /** - * @see DATAMONGO-471 - */ - @Test + @Test // DATAMONGO-471 public void updateMultiShouldAddValuesCorrectlyWhenUsingAddToSetWithEach() { DocumentWithCollectionOfSimpleType document = new DocumentWithCollectionOfSimpleType(); @@ -2870,10 +2622,7 @@ public void updateMultiShouldAddValuesCorrectlyWhenUsingAddToSetWithEach() { assertThat(template.findOne(query, DocumentWithCollectionOfSimpleType.class).values, hasSize(3)); } - /** - * @see DATAMONGO-1210 - */ - @Test + @Test // DATAMONGO-1210 public void findAndModifyAddToSetWithEachShouldNotAddDuplicatesNorTypeHintForSimpleDocuments() { DocumentWithCollectionOfSamples doc = new DocumentWithCollectionOfSamples(); @@ -2897,10 +2646,7 @@ public void findAndModifyAddToSetWithEachShouldNotAddDuplicatesNorTypeHintForSim assertThat(retrieved.samples.get(1).field, is("sample2")); } - /** - * @see DATAMONGO-888 - */ - @Test + @Test // DATAMONGO-888 public void sortOnIdFieldPropertyShouldBeMappedCorrectly() { DoucmentWithNamedIdField one = new DoucmentWithNamedIdField(); @@ -2918,10 +2664,7 @@ public void sortOnIdFieldPropertyShouldBeMappedCorrectly() { assertThat(template.find(query, DoucmentWithNamedIdField.class), contains(two, one)); } - /** - * @see DATAMONGO-888 - */ - @Test + @Test // DATAMONGO-888 public void sortOnAnnotatedFieldPropertyShouldBeMappedCorrectly() { DoucmentWithNamedIdField one = new DoucmentWithNamedIdField(); @@ -2939,10 +2682,7 @@ public void sortOnAnnotatedFieldPropertyShouldBeMappedCorrectly() { assertThat(template.find(query, DoucmentWithNamedIdField.class), contains(two, one)); } - /** - * @see DATAMONGO-913 - */ - @Test + @Test // DATAMONGO-913 public void shouldRetrieveInitializedValueFromDbRefAssociationAfterLoad() { SomeContent content = new SomeContent(); @@ -2967,10 +2707,7 @@ public void shouldRetrieveInitializedValueFromDbRefAssociationAfterLoad() { assertThat(result.getContent().getText(), is(content.getText())); } - /** - * @see DATAMONGO-913 - */ - @Test + @Test // DATAMONGO-913 public void shouldReuseExistingDBRefInQueryFromDbRefAssociationAfterLoad() { SomeContent content = new SomeContent(); @@ -2995,10 +2732,7 @@ public void shouldReuseExistingDBRefInQueryFromDbRefAssociationAfterLoad() { assertThat(result.getContent().getName(), is(content.getName())); } - /** - * @see DATAMONGO-970 - */ - @Test + @Test // DATAMONGO-970 public void insertsAndRemovesBasicDbObjectCorrectly() { BasicDBObject object = new BasicDBObject("key", "value"); @@ -3011,10 +2745,7 @@ public void insertsAndRemovesBasicDbObjectCorrectly() { assertThat(template.findAll(DBObject.class, "collection"), hasSize(0)); } - /** - * @see DATAMONGO-1207 - */ - @Test + @Test // DATAMONGO-1207 public void ignoresNullElementsForInsertAll() { Address newYork = new Address("NY", "New York"); @@ -3028,10 +2759,7 @@ public void ignoresNullElementsForInsertAll() { assertThat(result, hasItems(newYork, washington)); } - /** - * @see DATAMONGO-1208 - */ - @Test + @Test // DATAMONGO-1208 public void takesSortIntoAccountWhenStreaming() { Person youngestPerson = new Person("John", 20); @@ -3047,10 +2775,7 @@ public void takesSortIntoAccountWhenStreaming() { assertThat(stream.next().getAge(), is(oldestPerson.getAge())); } - /** - * @see DATAMONGO-1208 - */ - @Test + @Test // DATAMONGO-1208 public void takesLimitIntoAccountWhenStreaming() { Person youngestPerson = new Person("John", 20); @@ -3066,10 +2791,7 @@ public void takesLimitIntoAccountWhenStreaming() { assertThat(stream.hasNext(), is(false)); } - /** - * @see DATAMONGO-1204 - */ - @Test + @Test // DATAMONGO-1204 public void resolvesCyclicDBRefCorrectly() { SomeMessage message = new SomeMessage(); @@ -3091,10 +2813,7 @@ public void resolvesCyclicDBRefCorrectly() { assertThat(contentLoaded.dbrefMessage.id, is(messageLoaded.id)); } - /** - * @see DATAMONGO-1287 - */ - @Test + @Test // DATAMONGO-1287 public void shouldReuseAlreadyResolvedLazyLoadedDBRefWhenUsedAsPersistenceConstrcutorArgument() { Document docInCtor = new Document(); @@ -3112,10 +2831,7 @@ public void shouldReuseAlreadyResolvedLazyLoadedDBRefWhenUsedAsPersistenceConstr assertThat(loaded.refToDocNotUsedInCtor, nullValue()); } - /** - * @see DATAMONGO-1287 - */ - @Test + @Test // DATAMONGO-1287 public void shouldNotReuseLazyLoadedDBRefWhenTypeUsedInPersistenceConstrcutorButValueRefersToAnotherProperty() { Document docNotUsedInCtor = new Document(); @@ -3134,10 +2850,7 @@ public void shouldNotReuseLazyLoadedDBRefWhenTypeUsedInPersistenceConstrcutorBut assertThat(loaded.refToDocUsedInCtor, nullValue()); } - /** - * @see DATAMONGO-1287 - */ - @Test + @Test // DATAMONGO-1287 public void shouldRespectParamterValueWhenAttemptingToReuseLazyLoadedDBRefUsedInPersistenceConstrcutor() { Document docInCtor = new Document(); @@ -3160,10 +2873,7 @@ public void shouldRespectParamterValueWhenAttemptingToReuseLazyLoadedDBRefUsedIn assertThat(loaded.refToDocNotUsedInCtor, instanceOf(LazyLoadingProxy.class)); } - /** - * @see DATAMONGO-1401 - */ - @Test + @Test // DATAMONGO-1401 public void updateShouldWorkForTypesContainingGeoJsonTypes() { WithGeoJson wgj = new WithGeoJson(); @@ -3179,10 +2889,7 @@ public void updateShouldWorkForTypesContainingGeoJsonTypes() { assertThat(template.findOne(query(where("id").is(wgj.id)), WithGeoJson.class).point, is(equalTo(wgj.point))); } - /** - * @see DATAMONGO-1404 - */ - @Test + @Test // DATAMONGO-1404 public void updatesDateValueCorrectlyWhenUsingMinOperator() { Calendar cal = Calendar.getInstance(Locale.US); @@ -3197,10 +2904,7 @@ public void updatesDateValueCorrectlyWhenUsingMinOperator() { assertThat(loaded.date, equalTo(cal.getTime())); } - /** - * @see DATAMONGO-1404 - */ - @Test + @Test // DATAMONGO-1404 public void updatesNumericValueCorrectlyWhenUsingMinOperator() { TypeWithNumbers twn = new TypeWithNumbers(); @@ -3239,10 +2943,7 @@ public void updatesNumericValueCorrectlyWhenUsingMinOperator() { assertThat(loaded.bigDeciamVal, equalTo(new BigDecimal("690"))); } - /** - * @see DATAMONGO-1404 - */ - @Test + @Test // DATAMONGO-1404 public void updatesDateValueCorrectlyWhenUsingMaxOperator() { Calendar cal = Calendar.getInstance(Locale.US); @@ -3259,10 +2960,7 @@ public void updatesDateValueCorrectlyWhenUsingMaxOperator() { assertThat(loaded.date, equalTo(cal.getTime())); } - /** - * @see DATAMONGO-1404 - */ - @Test + @Test // DATAMONGO-1404 public void updatesNumericValueCorrectlyWhenUsingMaxOperator() { TypeWithNumbers twn = new TypeWithNumbers(); @@ -3301,10 +2999,7 @@ public void updatesNumericValueCorrectlyWhenUsingMaxOperator() { assertThat(loaded.bigDeciamVal, equalTo(new BigDecimal("790"))); } - /** - * @see DATAMONGO-1404 - */ - @Test + @Test // DATAMONGO-1404 public void updatesBigNumberValueUsingStringComparisonWhenUsingMaxOperator() { TypeWithNumbers twn = new TypeWithNumbers(); @@ -3328,10 +3023,7 @@ public void updatesBigNumberValueUsingStringComparisonWhenUsingMaxOperator() { assertThat(loaded.bigDeciamVal, equalTo(new BigDecimal("80"))); } - /** - * @see DATAMONGO-1404 - */ - @Test + @Test // DATAMONGO-1404 public void updatesBigNumberValueUsingStringComparisonWhenUsingMinOperator() { TypeWithNumbers twn = new TypeWithNumbers(); @@ -3355,10 +3047,7 @@ public void updatesBigNumberValueUsingStringComparisonWhenUsingMinOperator() { assertThat(loaded.bigDeciamVal, equalTo(new BigDecimal("800"))); } - /** - * @see DATAMONGO-1431 - */ - @Test + @Test // DATAMONGO-1431 public void streamExecutionUsesExplicitCollectionName() { template.remove(new Query(), "some_special_collection"); @@ -3379,10 +3068,7 @@ public void streamExecutionUsesExplicitCollectionName() { assertThat(stream.hasNext(), is(false)); } - /** - * @see DATAMONGO-1194 - */ - @Test + @Test // DATAMONGO-1194 public void shouldFetchListOfReferencesCorrectly() { Sample one = new Sample("1", "jon snow"); @@ -3399,10 +3085,7 @@ public void shouldFetchListOfReferencesCorrectly() { assertThat(template.findOne(query(where("id").is(source.id)), DocumentWithDBRefCollection.class), is(source)); } - /** - * @see DATAMONGO-1194 - */ - @Test + @Test // DATAMONGO-1194 public void shouldFetchListOfLazyReferencesCorrectly() { Sample one = new Sample("1", "jon snow"); @@ -3423,10 +3106,7 @@ public void shouldFetchListOfLazyReferencesCorrectly() { assertThat(target.getLazyDbRefAnnotatedList(), contains(two, one)); } - /** - * @see DATAMONGO-1194 - */ - @Test + @Test // DATAMONGO-1194 public void shouldFetchMapOfLazyReferencesCorrectly() { Sample one = new Sample("1", "jon snow"); @@ -3448,10 +3128,7 @@ public void shouldFetchMapOfLazyReferencesCorrectly() { assertThat(target.lazyDbRefAnnotatedMap.values(), contains(two, one)); } - /** - * @see DATAMONGO-1513 - */ - @Test + @Test // DATAMONGO-1513 @DirtiesContext public void populatesIdsAddedByEventListener() { @@ -3470,6 +3147,28 @@ public void onBeforeSave(BeforeSaveEvent event) { assertThat(document.id, is(notNullValue())); } + /** + * @see DATAMONGO-1517 + */ + @Test + public void decimal128TypeShouldBeSavedAndLoadedCorrectly() + throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { + + assumeThat(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_FOUR), is(true)); + assumeThat(MongoClientVersion.isMongo34Driver(), is(true)); + + Class decimal128Type = ClassUtils.resolveClassName("org.bson.types.Decimal128", null); + + WithObjectTypeProperty source = new WithObjectTypeProperty(); + source.id = "decimal128-property-value"; + source.value = decimal128Type.getConstructor(BigDecimal.class).newInstance(new BigDecimal(100)); + + template.save(source); + + WithObjectTypeProperty loaded = template.findOne(query(where("id").is(source.id)), WithObjectTypeProperty.class); + assertThat(loaded.getValue(), instanceOf(decimal128Type)); + } + static class TypeWithNumbers { @Id String id; @@ -3534,18 +3233,18 @@ static class DocumentWithDBRefCollection { @Id public String id; - @Field("db_ref_list") /** @see DATAMONGO-1058 */ + @Field("db_ref_list") // DATAMONGO-1058 @org.springframework.data.mongodb.core.mapping.DBRef // public List dbRefAnnotatedList; @org.springframework.data.mongodb.core.mapping.DBRef // public Sample dbRefProperty; - @Field("lazy_db_ref_list") /** @see DATAMONGO-1194 */ + @Field("lazy_db_ref_list") // DATAMONGO-1194 @org.springframework.data.mongodb.core.mapping.DBRef(lazy = true) // public List lazyDbRefAnnotatedList; - @Field("lazy_db_ref_map") /** @see DATAMONGO-1194 */ + @Field("lazy_db_ref_map") // DATAMONGO-1194 @org.springframework.data.mongodb.core.mapping.DBRef(lazy = true) public Map lazyDbRefAnnotatedMap; } @@ -3839,4 +3538,11 @@ static class WithGeoJson { String description; GeoJsonPoint point; } + + @Data + static class WithObjectTypeProperty { + + @Id String id; + Object value; + } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateUnitTests.java index 40af85d3e1..a4bc0595ca 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2010-2015 the original author or authors. + * Copyright 2010-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -144,20 +144,14 @@ public void rejectsNotFoundMapReduceResource() { template.mapReduce("foo", "classpath:doesNotExist.js", "function() {}", Person.class); } - /** - * @see DATAMONGO-322 - */ - @Test(expected = InvalidDataAccessApiUsageException.class) + @Test(expected = InvalidDataAccessApiUsageException.class) // DATAMONGO-322 public void rejectsEntityWithNullIdIfNotSupportedIdType() { Object entity = new NotAutogenerateableId(); template.save(entity); } - /** - * @see DATAMONGO-322 - */ - @Test + @Test // DATAMONGO-322 public void storesEntityWithSetIdAlthoughNotAutogenerateable() { NotAutogenerateableId entity = new NotAutogenerateableId(); @@ -166,10 +160,7 @@ public void storesEntityWithSetIdAlthoughNotAutogenerateable() { template.save(entity); } - /** - * @see DATAMONGO-322 - */ - @Test + @Test // DATAMONGO-322 public void autogeneratesIdForEntityWithAutogeneratableId() { this.converter.afterPropertiesSet(); @@ -184,10 +175,7 @@ public void autogeneratesIdForEntityWithAutogeneratableId() { assertThat(entity.id, is(notNullValue())); } - /** - * @see DATAMONGO-374 - */ - @Test + @Test // DATAMONGO-374 public void convertsUpdateConstraintsUsingConverters() { CustomConversions conversions = new CustomConversions(Collections.singletonList(MyConverter.INSTANCE)); @@ -205,10 +193,7 @@ public void convertsUpdateConstraintsUsingConverters() { verify(collection, times(1)).update(Mockito.any(DBObject.class), eq(reference), anyBoolean(), anyBoolean()); } - /** - * @see DATAMONGO-474 - */ - @Test + @Test // DATAMONGO-474 public void setsUnpopulatedIdField() { NotAutogenerateableId entity = new NotAutogenerateableId(); @@ -217,10 +202,7 @@ public void setsUnpopulatedIdField() { assertThat(entity.id, is(5)); } - /** - * @see DATAMONGO-474 - */ - @Test + @Test // DATAMONGO-474 public void doesNotSetAlreadyPopulatedId() { NotAutogenerateableId entity = new NotAutogenerateableId(); @@ -230,10 +212,7 @@ public void doesNotSetAlreadyPopulatedId() { assertThat(entity.id, is(5)); } - /** - * @see DATAMONGO-868 - */ - @Test + @Test // DATAMONGO-868 public void findAndModifyShouldBumpVersionByOneWhenVersionFieldNotIncludedInUpdate() { VersionedEntity v = new VersionedEntity(); @@ -250,10 +229,7 @@ public void findAndModifyShouldBumpVersionByOneWhenVersionFieldNotIncludedInUpda Assert.assertThat(captor.getValue().get("$inc"), Is. is(new BasicDBObject("version", 1L))); } - /** - * @see DATAMONGO-868 - */ - @Test + @Test // DATAMONGO-868 public void findAndModifyShouldNotBumpVersionByOneWhenVersionFieldAlreadyIncludedInUpdate() { VersionedEntity v = new VersionedEntity(); @@ -270,10 +246,7 @@ public void findAndModifyShouldNotBumpVersionByOneWhenVersionFieldAlreadyInclude Assert.assertThat(captor.getValue().get("$inc"), nullValue()); } - /** - * @see DATAMONGO-533 - */ - @Test + @Test // DATAMONGO-533 public void registersDefaultEntityIndexCreatorIfApplicationContextHasOneForDifferentMappingContext() { GenericApplicationContext applicationContext = new GenericApplicationContext(); @@ -300,10 +273,7 @@ public boolean matches(Object argument) { })); } - /** - * @see DATAMONGO-566 - */ - @Test + @Test // DATAMONGO-566 public void findAllAndRemoveShouldRetrieveMatchingDocumentsPriorToRemoval() { BasicQuery query = new BasicQuery("{'foo':'bar'}"); @@ -311,10 +281,7 @@ public void findAllAndRemoveShouldRetrieveMatchingDocumentsPriorToRemoval() { verify(collection, times(1)).find(Matchers.eq(query.getQueryObject())); } - /** - * @see DATAMONGO-566 - */ - @Test + @Test // DATAMONGO-566 public void findAllAndRemoveShouldRemoveDocumentsReturedByFindQuery() { Mockito.when(cursor.hasNext()).thenReturn(true).thenReturn(true).thenReturn(false); @@ -331,20 +298,14 @@ public void findAllAndRemoveShouldRemoveDocumentsReturedByFindQuery() { assertThat((Object[]) idField.get("$in"), is(new Object[] { Integer.valueOf(0), Integer.valueOf(1) })); } - /** - * @see DATAMONGO-566 - */ - @Test + @Test // DATAMONGO-566 public void findAllAndRemoveShouldNotTriggerRemoveIfFindResultIsEmpty() { template.findAllAndRemove(new BasicQuery("{'foo':'bar'}"), VersionedEntity.class); verify(collection, never()).remove(Mockito.any(DBObject.class)); } - /** - * @see DATAMONGO-948 - */ - @Test + @Test // DATAMONGO-948 public void sortShouldBeTakenAsIsWhenExecutingQueryWithoutSpecificTypeInformation() { Query query = Query.query(Criteria.where("foo").is("bar")).with(new Sort("foo")); @@ -361,10 +322,7 @@ public void processDocument(DBObject dbObject) throws MongoException, DataAccess assertThat(captor.getValue(), equalTo(new BasicDBObjectBuilder().add("foo", 1).get())); } - /** - * @see DATAMONGO-1166 - */ - @Test + @Test // DATAMONGO-1166 public void aggregateShouldHonorReadPreferenceWhenSet() { when(db.command(Mockito.any(DBObject.class), Mockito.any(ReadPreference.class))).thenReturn( @@ -377,10 +335,7 @@ public void aggregateShouldHonorReadPreferenceWhenSet() { verify(this.db, times(1)).command(Mockito.any(DBObject.class), eq(ReadPreference.secondary())); } - /** - * @see DATAMONGO-1166 - */ - @Test + @Test // DATAMONGO-1166 public void aggregateShouldIgnoreReadPreferenceWhenNotSet() { when(db.command(Mockito.any(DBObject.class), Mockito.any(ReadPreference.class))).thenReturn( @@ -392,10 +347,7 @@ public void aggregateShouldIgnoreReadPreferenceWhenNotSet() { verify(this.db, times(1)).command(Mockito.any(DBObject.class)); } - /** - * @see DATAMONGO-1166 - */ - @Test + @Test // DATAMONGO-1166 public void geoNearShouldHonorReadPreferenceWhenSet() { when(db.command(Mockito.any(DBObject.class), Mockito.any(ReadPreference.class))).thenReturn( @@ -409,10 +361,7 @@ public void geoNearShouldHonorReadPreferenceWhenSet() { verify(this.db, times(1)).command(Mockito.any(DBObject.class), eq(ReadPreference.secondary())); } - /** - * @see DATAMONGO-1166 - */ - @Test + @Test // DATAMONGO-1166 public void geoNearShouldIgnoreReadPreferenceWhenNotSet() { when(db.command(Mockito.any(DBObject.class), Mockito.any(ReadPreference.class))).thenReturn( @@ -425,10 +374,7 @@ public void geoNearShouldIgnoreReadPreferenceWhenNotSet() { verify(this.db, times(1)).command(Mockito.any(DBObject.class)); } - /** - * @see DATAMONGO-1334 - */ - @Test + @Test // DATAMONGO-1334 public void mapReduceShouldUseZeroAsDefaultLimit() { ArgumentCaptor captor = ArgumentCaptor.forClass(MapReduceCommand.class); @@ -446,10 +392,7 @@ public void mapReduceShouldUseZeroAsDefaultLimit() { assertThat(captor.getValue().getLimit(), is(0)); } - /** - * @see DATAMONGO-1334 - */ - @Test + @Test // DATAMONGO-1334 public void mapReduceShouldPickUpLimitFromQuery() { ArgumentCaptor captor = ArgumentCaptor.forClass(MapReduceCommand.class); @@ -468,10 +411,7 @@ public void mapReduceShouldPickUpLimitFromQuery() { assertThat(captor.getValue().getLimit(), is(100)); } - /** - * @see DATAMONGO-1334 - */ - @Test + @Test // DATAMONGO-1334 public void mapReduceShouldPickUpLimitFromOptions() { ArgumentCaptor captor = ArgumentCaptor.forClass(MapReduceCommand.class); @@ -489,10 +429,7 @@ public void mapReduceShouldPickUpLimitFromOptions() { assertThat(captor.getValue().getLimit(), is(1000)); } - /** - * @see DATAMONGO-1334 - */ - @Test + @Test // DATAMONGO-1334 public void mapReduceShouldPickUpLimitFromOptionsWhenQueryIsNotPresent() { ArgumentCaptor captor = ArgumentCaptor.forClass(MapReduceCommand.class); @@ -508,10 +445,7 @@ public void mapReduceShouldPickUpLimitFromOptionsWhenQueryIsNotPresent() { assertThat(captor.getValue().getLimit(), is(1000)); } - /** - * @see DATAMONGO-1334 - */ - @Test + @Test // DATAMONGO-1334 public void mapReduceShouldPickUpLimitFromOptionsEvenWhenQueryDefinesItDifferently() { ArgumentCaptor captor = ArgumentCaptor.forClass(MapReduceCommand.class); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/NoExplicitIdTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/NoExplicitIdTests.java index de8b22176c..c8e72686f3 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/NoExplicitIdTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/NoExplicitIdTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 the original author or authors. + * Copyright 2015-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -68,10 +68,7 @@ public void setUp() { mongoOps.dropCollection(TypeWithoutIdProperty.class); } - /** - * @see DATAMONGO-1289 - */ - @Test + @Test // DATAMONGO-1289 public void saveAndRetrieveTypeWithoutIdPorpertyViaTemplate() { TypeWithoutIdProperty noid = new TypeWithoutIdProperty(); @@ -85,10 +82,7 @@ public void saveAndRetrieveTypeWithoutIdPorpertyViaTemplate() { assertThat(retrieved.someString, is(noid.someString)); } - /** - * @see DATAMONGO-1289 - */ - @Test + @Test // DATAMONGO-1289 public void saveAndRetrieveTypeWithoutIdPorpertyViaRepository() { TypeWithoutIdProperty noid = new TypeWithoutIdProperty(); @@ -100,10 +94,7 @@ public void saveAndRetrieveTypeWithoutIdPorpertyViaRepository() { assertThat(retrieved.someString, is(noid.someString)); } - /** - * @see DATAMONGO-1289 - */ - @Test + @Test // DATAMONGO-1289 @SuppressWarnings("unchecked") public void saveAndRetrieveTypeWithoutIdPorpertyViaRepositoryFindOne() { diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/QueryByExampleTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/QueryByExampleTests.java index faa45f77ff..11e2be5733 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/QueryByExampleTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/QueryByExampleTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 the original author or authors. + * Copyright 2016-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -72,10 +72,7 @@ public void setUp() throws UnknownHostException { operations.save(p3); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void findByExampleShouldWorkForSimpleProperty() { Person sample = new Person(); @@ -88,10 +85,7 @@ public void findByExampleShouldWorkForSimpleProperty() { assertThat(result, hasItems(p1, p3)); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void findByExampleShouldWorkForMultipleProperties() { Person sample = new Person(); @@ -105,10 +99,7 @@ public void findByExampleShouldWorkForMultipleProperties() { assertThat(result, hasItem(p3)); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void findByExampleShouldWorkForIdProperty() { Person p4 = new Person(); @@ -124,10 +115,7 @@ public void findByExampleShouldWorkForIdProperty() { assertThat(result, hasItem(p4)); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void findByExampleShouldReturnEmptyListIfNotMatching() { Person sample = new Person(); @@ -140,10 +128,7 @@ public void findByExampleShouldReturnEmptyListIfNotMatching() { assertThat(result, is(empty())); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void findByExampleShouldReturnEverythingWhenSampleIsEmpty() { Person sample = new Person(); @@ -155,10 +140,7 @@ public void findByExampleShouldReturnEverythingWhenSampleIsEmpty() { assertThat(result, hasItems(p1, p2, p3)); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void findByExampleWithCriteria() { Person sample = new Person(); @@ -170,10 +152,7 @@ public void findByExampleWithCriteria() { assertThat(result.size(), is(1)); } - /** - * @see DATAMONGO-1459 - */ - @Test + @Test // DATAMONGO-1459 public void findsExampleUsingAnyMatch() { Person probe = new Person(); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/QueryCursorPreparerUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/QueryCursorPreparerUnitTests.java index 21ca3f8c03..e534c8fc82 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/QueryCursorPreparerUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/QueryCursorPreparerUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2016 the original author or authors. + * Copyright 2011-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -55,10 +55,7 @@ public void setUp() { when(cursor.copy()).thenReturn(cursorToUse); } - /** - * @see DATAMONGO-185 - */ - @Test + @Test // DATAMONGO-185 public void appliesHintsCorrectly() { Query query = query(where("foo").is("bar")).withHint("hint"); @@ -68,10 +65,7 @@ public void appliesHintsCorrectly() { verify(cursorToUse).hint("hint"); } - /** - * @see DATAMONGO-957 - */ - @Test + @Test // DATAMONGO-957 public void doesNotApplyMetaWhenEmpty() { Query query = query(where("foo").is("bar")); @@ -83,10 +77,7 @@ public void doesNotApplyMetaWhenEmpty() { verify(cursorToUse, never()).addSpecial(any(String.class), anyObject()); } - /** - * @see DATAMONGO-957 - */ - @Test + @Test // DATAMONGO-957 public void appliesMaxScanCorrectly() { Query query = query(where("foo").is("bar")).maxScan(100); @@ -96,10 +87,7 @@ public void appliesMaxScanCorrectly() { verify(cursorToUse).addSpecial(eq("$maxScan"), eq(100L)); } - /** - * @see DATAMONGO-957 - */ - @Test + @Test // DATAMONGO-957 public void appliesMaxTimeCorrectly() { Query query = query(where("foo").is("bar")).maxTime(1, TimeUnit.SECONDS); @@ -109,10 +97,7 @@ public void appliesMaxTimeCorrectly() { verify(cursorToUse).addSpecial(eq("$maxTimeMS"), eq(1000L)); } - /** - * @see DATAMONGO-957 - */ - @Test + @Test // DATAMONGO-957 public void appliesCommentCorrectly() { Query query = query(where("foo").is("bar")).comment("spring data"); @@ -122,10 +107,7 @@ public void appliesCommentCorrectly() { verify(cursorToUse).addSpecial(eq("$comment"), eq("spring data")); } - /** - * @see DATAMONGO-957 - */ - @Test + @Test // DATAMONGO-957 public void appliesSnapshotCorrectly() { Query query = query(where("foo").is("bar")).useSnapshot(); @@ -135,10 +117,7 @@ public void appliesSnapshotCorrectly() { verify(cursorToUse).addSpecial(eq("$snapshot"), eq(true)); } - /** - * @see DATAMONGO-1480 - */ - @Test + @Test // DATAMONGO-1480 public void appliesNoCursorTimeoutCorrectly() { Query query = query(where("foo").is("bar")).noCursorTimeout(); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/SerializationUtilsUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/SerializationUtilsUnitTests.java index 0b42b27c03..8d12f96b6b 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/SerializationUtilsUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/SerializationUtilsUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2016 the original author or authors. + * Copyright 2012-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -64,10 +64,7 @@ public void writesCollection() { assertThat(serializeToJsonSafely(dbObject), is(expectedOutput)); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void flattenMapShouldFlatOutNestedStructureCorrectly() { DBObject dbo = new BasicDBObjectBuilder().add("_id", 1).add("nested", new BasicDBObject("value", "conflux")).get(); @@ -76,10 +73,7 @@ public void flattenMapShouldFlatOutNestedStructureCorrectly() { assertThat(flattenMap(dbo), hasEntry("nested.value", (Object) "conflux")); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void flattenMapShouldFlatOutNestedStructureWithListCorrectly() { BasicDBList dbl = new BasicDBList(); @@ -91,10 +85,7 @@ public void flattenMapShouldFlatOutNestedStructureWithListCorrectly() { assertThat(flattenMap(dbo), hasEntry("nested.value", (Object) dbl)); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void flattenMapShouldLeaveKeywordsUntouched() { DBObject dbo = new BasicDBObjectBuilder().add("_id", 1).add("nested", new BasicDBObject("$regex", "^conflux$")) @@ -107,10 +98,7 @@ public void flattenMapShouldLeaveKeywordsUntouched() { assertThat(((Map) map.get("nested")).get("$regex"), is((Object) "^conflux$")); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void flattenMapShouldAppendCommandsCorrectly() { DBObject dbo = new BasicDBObjectBuilder().add("_id", 1) @@ -124,10 +112,7 @@ public void flattenMapShouldAppendCommandsCorrectly() { assertThat(((Map) map.get("nested")).get("$options"), is((Object) "i")); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void flattenMapShouldReturnEmptyMapWhenSourceIsNull() { assertThat(flattenMap(null).isEmpty(), is(true)); } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/SimpleMongoDbFactoryUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/SimpleMongoDbFactoryUnitTests.java index 9967de75ec..ab753b1b22 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/SimpleMongoDbFactoryUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/SimpleMongoDbFactoryUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2015 the original author or authors. + * Copyright 2011-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -49,19 +49,13 @@ public class SimpleMongoDbFactoryUnitTests { public @Rule ExpectedException expectedException = ExpectedException.none(); @Mock Mongo mongo; - /** - * @see DATADOC-254 - */ - @Test + @Test // DATADOC-254 public void rejectsIllegalDatabaseNames() { rejectsDatabaseName("foo.bar"); rejectsDatabaseName("foo!bar"); } - /** - * @see DATADOC-254 - */ - @Test + @Test // DATADOC-254 @SuppressWarnings("deprecation") public void allowsDatabaseNames() { new SimpleMongoDbFactory(mongo, "foo-bar"); @@ -69,11 +63,7 @@ public void allowsDatabaseNames() { new SimpleMongoDbFactory(mongo, "foo01231bar"); } - /** - * @see DATADOC-295 - * @throws UnknownHostException - */ - @Test + @Test // DATADOC-295 @SuppressWarnings("deprecation") public void mongoUriConstructor() throws UnknownHostException { @@ -84,10 +74,7 @@ public void mongoUriConstructor() throws UnknownHostException { assertThat(getField(mongoDbFactory, "databaseName").toString(), is("myDatabase")); } - /** - * @see DATAMONGO-789 - */ - @Test + @Test // DATAMONGO-789 @SuppressWarnings("deprecation") public void defaultsAuthenticationDatabaseToDatabase() { @@ -95,10 +82,7 @@ public void defaultsAuthenticationDatabaseToDatabase() { assertThat(getField(factory, "authenticationDatabaseName"), is((Object) "foo")); } - /** - * @see DATAMONGO-1158 - */ - @Test + @Test // DATAMONGO-1158 public void constructsMongoClientAccordingToMongoUri() throws UnknownHostException { MongoClientURI uri = new MongoClientURI("mongodb://myUserName:myPassWord@127.0.0.1:27017/myDataBase.myCollection"); @@ -107,10 +91,7 @@ public void constructsMongoClientAccordingToMongoUri() throws UnknownHostExcepti assertThat(getField(factory, "databaseName").toString(), is("myDataBase")); } - /** - * @see DATAMONGO-1158 - */ - @Test + @Test // DATAMONGO-1158 public void shouldDefaultAuthenticationDbNameToDbNameWhenUsingMongoClient() throws UnknownHostException { MongoClient clientMock = mock(MongoClient.class); @@ -119,10 +100,7 @@ public void shouldDefaultAuthenticationDbNameToDbNameWhenUsingMongoClient() thro assertThat(getField(factory, "authenticationDatabaseName").toString(), is("FooBar")); } - /** - * @see DATAMONGO-1260 - */ - @Test + @Test // DATAMONGO-1260 public void rejectsMongoClientWithUserCredentials() { expectedException.expect(InvalidDataAccessApiUsageException.class); @@ -131,10 +109,7 @@ public void rejectsMongoClientWithUserCredentials() { new SimpleMongoDbFactory(mock(MongoClient.class), "cairhienin", new UserCredentials("moiraine", "sedai")); } - /** - * @see DATAMONGO-1260 - */ - @Test + @Test // DATAMONGO-1260 public void rejectsMongoClientWithUserCredentialsAndAuthDb() { expectedException.expect(InvalidDataAccessApiUsageException.class); @@ -143,18 +118,12 @@ public void rejectsMongoClientWithUserCredentialsAndAuthDb() { new SimpleMongoDbFactory(mock(MongoClient.class), "malkieri", new UserCredentials("lan", "mandragoran"), "authdb"); } - /** - * @see DATAMONGO-1260 - */ - @Test + @Test // DATAMONGO-1260 public void shouldNotRejectMongoClientWithNoCredentials() { new SimpleMongoDbFactory(mock(MongoClient.class), "andoran", UserCredentials.NO_CREDENTIALS); } - /** - * @see DATAMONGO-1260 - */ - @Test + @Test // DATAMONGO-1260 public void shouldNotRejectMongoClientWithEmptyUserCredentials() { new SimpleMongoDbFactory(mock(MongoClient.class), "shangtai", new UserCredentials("", "")); } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationOptionsTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationOptionsTests.java index ea3f556d4b..45549e1146 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationOptionsTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationOptionsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 the original author or authors. + * Copyright 2014-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -42,10 +42,7 @@ public void setup() { } - /** - * @see DATAMONGO-960 - */ - @Test + @Test // DATAMONGO-960 public void aggregationOptionsBuilderShouldSetOptionsAccordingly() { assertThat(aggregationOptions.isAllowDiskUse(), is(true)); @@ -53,10 +50,7 @@ public void aggregationOptionsBuilderShouldSetOptionsAccordingly() { assertThat(aggregationOptions.getCursor(), is((DBObject) new BasicDBObject("foo", 1))); } - /** - * @see DATAMONGO-960 - */ - @Test + @Test // DATAMONGO-960 public void aggregationOptionsToString() { assertThat(aggregationOptions.toString(), is("{ \"allowDiskUse\" : true , \"explain\" : true , \"cursor\" : { \"foo\" : 1}}")); 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 6a4b8ae81c..9b97663705 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 @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,11 +24,14 @@ import static org.springframework.data.mongodb.core.query.Criteria.*; import static org.springframework.data.mongodb.test.util.IsBsonObject.*; +import lombok.Builder; + import java.io.BufferedInputStream; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Scanner; @@ -55,6 +58,8 @@ 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.aggregation.BucketAutoOperation.Granularities; +import org.springframework.data.mongodb.core.aggregation.VariableOperators.Let.ExpressionVariable; import org.springframework.data.mongodb.core.index.GeospatialIndex; import org.springframework.data.mongodb.core.mapping.Document; import org.springframework.data.mongodb.core.query.Criteria; @@ -65,7 +70,9 @@ import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import com.mongodb.BasicDBList; import com.mongodb.BasicDBObject; +import com.mongodb.BasicDBObjectBuilder; import com.mongodb.CommandResult; import com.mongodb.DBCollection; import com.mongodb.DBObject; @@ -74,8 +81,7 @@ /** * Tests for {@link MongoTemplate#aggregate(String, AggregationPipeline, Class)}. - * - * @see DATAMONGO-586 + * * @author Tobias Trelle * @author Thomas Darimont * @author Oliver Gierke @@ -92,6 +98,7 @@ public class AggregationTests { private static final Version TWO_DOT_FOUR = new Version(2, 4); private static final Version TWO_DOT_SIX = new Version(2, 6); private static final Version THREE_DOT_TWO = new Version(3, 2); + private static final Version THREE_DOT_FOUR = new Version(3, 4); private static boolean initialized = false; @@ -135,13 +142,17 @@ private void cleanDb() { mongoTemplate.dropCollection(MeterData.class); mongoTemplate.dropCollection(LineItem.class); mongoTemplate.dropCollection(InventoryItem.class); + mongoTemplate.dropCollection(Sales.class); + mongoTemplate.dropCollection(Sales2.class); + mongoTemplate.dropCollection(Employee.class); + mongoTemplate.dropCollection(Art.class); } /** - * Imports the sample dataset (zips.json) if necessary (e.g. if it doen't exist yet). The dataset can originally be + * Imports the sample dataset (zips.json) if necessary (e.g. if it doesn't exist yet). The dataset can originally be * found on the mongodb aggregation framework example website: * - * @see http://docs.mongodb.org/manual/tutorial/aggregation-examples/. + * @see MongoDB Aggregation Examples */ private void initSampleDataIfNecessary() { @@ -180,22 +191,22 @@ public Void doInCollection(DBCollection collection) throws MongoException, DataA } } - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-586 public void shouldHandleMissingInputCollection() { mongoTemplate.aggregate(newAggregation(), (String) null, TagCount.class); } - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-586 public void shouldHandleMissingAggregationPipeline() { mongoTemplate.aggregate(null, INPUT_COLLECTION, TagCount.class); } - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-586 public void shouldHandleMissingEntityClass() { mongoTemplate.aggregate(newAggregation(), INPUT_COLLECTION, null); } - @Test + @Test // DATAMONGO-586 public void shouldAggregate() { createTagDocuments(); @@ -224,7 +235,7 @@ public void shouldAggregate() { assertTagCount("nosql", 1, tagCount.get(2)); } - @Test + @Test // DATAMONGO-586 public void shouldAggregateEmptyCollection() { Aggregation aggregation = newAggregation(// @@ -247,10 +258,7 @@ public void shouldAggregateEmptyCollection() { assertThat(tagCount.size(), is(0)); } - /** - * @see DATAMONGO-1391 - */ - @Test + @Test // DATAMONGO-1391 public void shouldUnwindWithIndex() { assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_TWO)); @@ -278,10 +286,7 @@ public void shouldUnwindWithIndex() { assertThat(tagCount.size(), is(3)); } - /** - * @see DATAMONGO-1391 - */ - @Test + @Test // DATAMONGO-1391 public void shouldUnwindPreserveEmpty() { assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_TWO)); @@ -309,7 +314,7 @@ public void shouldUnwindPreserveEmpty() { assertThat(tagCount.get(3), isBsonObject().notContaining("n")); } - @Test + @Test // DATAMONGO-586 public void shouldDetectResultMismatch() { createTagDocuments(); @@ -334,10 +339,10 @@ public void shouldDetectResultMismatch() { assertTagCount(null, 0, tagCount.get(1)); } - @Test + @Test // DATAMONGO-586 public void complexAggregationFrameworkUsageLargestAndSmallestCitiesByState() { /* - //complex mongodb aggregation framework example from http://docs.mongodb.org/manual/tutorial/aggregation-examples/#largest-and-smallest-cities-by-state + //complex mongodb aggregation framework example from https://docs.mongodb.org/manual/tutorial/aggregation-examples/#largest-and-smallest-cities-by-state db.zipInfo.aggregate( { $group: { @@ -442,11 +447,11 @@ public void complexAggregationFrameworkUsageLargestAndSmallestCitiesByState() { assertThat(lastZipInfoStats.biggestCity.population, is(70185)); } - @Test + @Test // DATAMONGO-586 public void findStatesWithPopulationOver10MillionAggregationExample() { /* //complex mongodb aggregation framework example from - http://docs.mongodb.org/manual/tutorial/aggregation-examples/#largest-and-smallest-cities-by-state + https://docs.mongodb.org/manual/tutorial/aggregation-examples/#largest-and-smallest-cities-by-state db.zipcodes.aggregate( { @@ -489,10 +494,10 @@ public void findStatesWithPopulationOver10MillionAggregationExample() { } /** - * @see DATAMONGO-861 - * @see https://docs.mongodb.com/manual/reference/operator/aggregation/cond/#example + * @see MongoDB Aggregation + * Framework: $cond */ - @Test + @Test // DATAMONGO-861 public void aggregationUsingConditionalProjectionToCalculateDiscount() { /* @@ -519,7 +524,7 @@ public void aggregationUsingConditionalProjectionToCalculateDiscount() { TypedAggregation aggregation = newAggregation(InventoryItem.class, // project("item") // .and("discount")// - .applyCondition(ConditionalOperator.newBuilder().when(Criteria.where("qty").gte(250)) // + .applyCondition(ConditionalOperators.Cond.newBuilder().when(Criteria.where("qty").gte(250)) // .then(30) // .otherwise(20))); @@ -542,10 +547,10 @@ public void aggregationUsingConditionalProjectionToCalculateDiscount() { } /** - * @see DATAMONGO-861 - * @see https://docs.mongodb.com/manual/reference/operator/aggregation/ifNull/#example + * @see MongoDB Aggregation + * Framework: $ifNull */ - @Test + @Test // DATAMONGO-861 public void aggregationUsingIfNullToProjectSaneDefaults() { /* @@ -567,7 +572,7 @@ public void aggregationUsingIfNullToProjectSaneDefaults() { TypedAggregation aggregation = newAggregation(InventoryItem.class, // project("item") // - .and(ifNull("description", "Unspecified")) // + .and(ConditionalOperators.ifNull("description").then("Unspecified")) // .as("description")// ); @@ -585,16 +590,13 @@ public void aggregationUsingIfNullToProjectSaneDefaults() { assertThat(second.get("description"), is((Object) "Unspecified")); } - /** - * @see DATAMONGO-861 - */ - @Test + @Test // DATAMONGO-861 public void aggregationUsingConditionalProjection() { TypedAggregation aggregation = newAggregation(ZipInfo.class, // project() // .and("largePopulation")// - .applyCondition(ConditionalOperator.newBuilder().when(Criteria.where("population").gte(20000)) // + .applyCondition(ConditionalOperators.when(Criteria.where("population").gte(20000)) // .then(true) // .otherwise(false)) // .and("population").as("population")); @@ -610,18 +612,15 @@ public void aggregationUsingConditionalProjection() { assertThat(firstZipInfoStats.get("population"), is((Object) 6055)); } - /** - * @see DATAMONGO-861 - */ - @Test + @Test // DATAMONGO-861 public void aggregationUsingNestedConditionalProjection() { TypedAggregation aggregation = newAggregation(ZipInfo.class, // project() // .and("size")// - .applyCondition(ConditionalOperator.newBuilder().when(Criteria.where("population").gte(20000)) // - .then(ConditionalOperator.newBuilder().when(Criteria.where("population").gte(200000)).then("huge") - .otherwise("small")) // + .applyCondition(ConditionalOperators.when(Criteria.where("population").gte(20000)) // + .then( + ConditionalOperators.when(Criteria.where("population").gte(200000)).then("huge").otherwise("small")) // .otherwise("small")) // .and("population").as("population")); @@ -636,10 +635,7 @@ public void aggregationUsingNestedConditionalProjection() { assertThat(firstZipInfoStats.get("population"), is((Object) 6055)); } - /** - * @see DATAMONGO-861 - */ - @Test + @Test // DATAMONGO-861 public void aggregationUsingIfNullProjection() { mongoTemplate.insert(new LineItem("id", "caption", 0)); @@ -648,7 +644,7 @@ public void aggregationUsingIfNullProjection() { TypedAggregation aggregation = newAggregation(LineItem.class, // project("id") // .and("caption")// - .applyCondition(ifNull(field("caption"), "unknown")), + .applyCondition(ConditionalOperators.ifNull("caption").then("unknown")), sort(ASC, "id")); assertThat(aggregation.toString(), is(notNullValue())); @@ -663,10 +659,7 @@ public void aggregationUsingIfNullProjection() { assertThat((String) idonly.get("caption"), is(equalTo("unknown"))); } - /** - * @see DATAMONGO-861 - */ - @Test + @Test // DATAMONGO-861 public void aggregationUsingIfNullReplaceWithFieldReferenceProjection() { mongoTemplate.insert(new LineItem("id", "caption", 0)); @@ -675,7 +668,7 @@ public void aggregationUsingIfNullReplaceWithFieldReferenceProjection() { TypedAggregation aggregation = newAggregation(LineItem.class, // project("id") // .and("caption")// - .applyCondition(ifNull(field("caption"), field("id"))), + .applyCondition(ConditionalOperators.ifNull("caption").thenValueOf("id")), sort(ASC, "id")); assertThat(aggregation.toString(), is(notNullValue())); @@ -690,10 +683,7 @@ public void aggregationUsingIfNullReplaceWithFieldReferenceProjection() { assertThat((String) idonly.get("caption"), is(equalTo("idonly"))); } - /** - * @see DATAMONGO-861 - */ - @Test + @Test // DATAMONGO-861 public void shouldAllowGroupingUsingConditionalExpressions() { mongoTemplate.dropCollection(CarPerson.class); @@ -711,12 +701,16 @@ public void shouldAllowGroupingUsingConditionalExpressions() { TypedAggregation agg = Aggregation.newAggregation(CarPerson.class, unwind("descriptors.carDescriptor.entries"), // project() // - .and(new ConditionalOperator(Criteria.where("descriptors.carDescriptor.entries.make").is("MAKE1"), "good", - "meh")) + .and(ConditionalOperators // + .when(Criteria.where("descriptors.carDescriptor.entries.make").is("MAKE1")).then("good") + .otherwise("meh")) .as("make") // .and("descriptors.carDescriptor.entries.model").as("model") // .and("descriptors.carDescriptor.entries.year").as("year"), // - group("make").avg(new ConditionalOperator(Criteria.where("year").gte(2012), 1, 9000)).as("score"), + group("make").avg(ConditionalOperators // + .when(Criteria.where("year").gte(2012)) // + .then(1) // + .otherwise(9000)).as("score"), sort(ASC, "make")); AggregationResults result = mongoTemplate.aggregate(agg, DBObject.class); @@ -733,9 +727,11 @@ public void shouldAllowGroupingUsingConditionalExpressions() { } /** - * @see http://docs.mongodb.org/manual/tutorial/aggregation-examples/#return-the-five-most-common-likes + * @see Return + * the Five Most Common “Likes” */ - @Test + @Test // DATAMONGO-586 public void returnFiveMostCommonLikesAggregationFrameworkExample() { createUserWithLikesDocuments(); @@ -767,7 +763,7 @@ protected TypedAggregation createUsersWithCommonLikesAggregation( ); } - @Test + @Test // DATAMONGO-586 public void arithmenticOperatorsInProjectionExample() { Product product = new Product("P1", "A", 1.99, 3, 0.05, 0.19); @@ -809,10 +805,7 @@ public void arithmenticOperatorsInProjectionExample() { assertThat((Integer) resultList.get(0).get("spaceUnitsModSpaceUnits"), is(product.spaceUnits % product.spaceUnits)); } - /** - * @see DATAMONGO-774 - */ - @Test + @Test // DATAMONGO-774 public void expressionsInProjectionExample() { Product product = new Product("P1", "A", 1.99, 3, 0.05, 0.19); @@ -844,10 +837,7 @@ public void expressionsInProjectionExample() { is((product.netPrice * 0.8 + 1.2) * 1.19)); } - /** - * @see DATAMONGO-774 - */ - @Test + @Test // DATAMONGO-774 public void stringExpressionsInProjectionExample() { assumeTrue(mongoVersion.isGreaterThanOrEqualTo(TWO_DOT_FOUR)); @@ -869,10 +859,7 @@ public void stringExpressionsInProjectionExample() { assertThat((String) resultList.get(0).get("name_bubu"), is(product.name + "_bubu")); } - /** - * @see DATAMONGO-774 - */ - @Test + @Test // DATAMONGO-774 public void expressionsInProjectionExampleShowcase() { Product product = new Product("P1", "A", 1.99, 3, 0.05, 0.19); @@ -914,12 +901,11 @@ public void shouldThrowExceptionIfUnknownFieldIsReferencedInArithmenticExpressio } /** - * @see DATAMONGO-753 - * @see http - * ://stackoverflow.com/questions/18653574/spring-data-mongodb-aggregation-framework-invalid-reference-in-group - * -operati + * @see Spring + * Data MongoDB - Aggregation Framework - invalid reference in group Operation */ - @Test + @Test // DATAMONGO-753 public void allowsNestedFieldReferencesAsGroupIdsInGroupExpressions() { mongoTemplate.insert(new DATAMONGO753().withPDs(new PD("A", 1), new PD("B", 1), new PD("C", 1))); @@ -944,12 +930,11 @@ public void allowsNestedFieldReferencesAsGroupIdsInGroupExpressions() { } /** - * @see DATAMONGO-753 - * @see http - * ://stackoverflow.com/questions/18653574/spring-data-mongodb-aggregation-framework-invalid-reference-in-group - * -operati + * @see Spring + * Data MongoDB - Aggregation Framework - invalid reference in group Operation */ - @Test + @Test // DATAMONGO-753 public void aliasesNestedFieldInProjectionImmediately() { mongoTemplate.insert(new DATAMONGO753().withPDs(new PD("A", 1), new PD("B", 1), new PD("C", 1))); @@ -968,10 +953,7 @@ public void aliasesNestedFieldInProjectionImmediately() { } } - /** - * @DATAMONGO-774 - */ - @Test + @Test // DATAMONGO-774 public void shouldPerformDateProjectionOperatorsCorrectly() throws ParseException { assumeTrue(mongoVersion.isGreaterThanOrEqualTo(TWO_DOT_FOUR)); @@ -1000,10 +982,7 @@ public void shouldPerformDateProjectionOperatorsCorrectly() throws ParseExceptio assertThat((String) dbo.get("toUpper"), is("ABC")); } - /** - * @DATAMONGO-774 - */ - @Test + @Test // DATAMONGO-774 public void shouldPerformStringProjectionOperatorsCorrectly() throws ParseException { assumeTrue(mongoVersion.isGreaterThanOrEqualTo(TWO_DOT_FOUR)); @@ -1042,10 +1021,30 @@ public void shouldPerformStringProjectionOperatorsCorrectly() throws ParseExcept assertThat((Integer) dbo.get("millisecond"), is(789)); } - /** - * @see DATAMONGO-788 - */ - @Test + @Test // DATAMONGO-1550 + public void shouldPerformReplaceRootOperatorCorrectly() throws ParseException { + + assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_FOUR)); + + Data data = new Data(); + DataItem dataItem = new DataItem(); + dataItem.primitiveIntValue = 42; + data.item = dataItem; + mongoTemplate.insert(data); + + TypedAggregation agg = newAggregation(Data.class, project("item"), // + replaceRoot("item"), // + project().and("primitiveIntValue").as("my_primitiveIntValue")); + + AggregationResults results = mongoTemplate.aggregate(agg, DBObject.class); + DBObject dbo = results.getUniqueMappedResult(); + + assertThat(dbo, is(notNullValue())); + assertThat((Integer) dbo.get("my_primitiveIntValue"), is(42)); + assertThat((Integer) dbo.keySet().size(), is(1)); + } + + @Test // DATAMONGO-788 public void referencesToGroupIdsShouldBeRenderedProperly() { mongoTemplate.insert(new DATAMONGO788(1, 1)); @@ -1072,10 +1071,7 @@ public void referencesToGroupIdsShouldBeRenderedProperly() { assertThat((Integer) items.get(1).get("y"), is(1)); } - /** - * @see DATAMONGO-806 - */ - @Test + @Test // DATAMONGO-806 public void shouldAllowGroupByIdFields() { mongoTemplate.dropCollection(User.class); @@ -1106,10 +1102,7 @@ public void shouldAllowGroupByIdFields() { assertThat(String.valueOf(firstItem.get("_id")), is("u1")); } - /** - * @see DATAMONGO-840 - */ - @Test + @Test // DATAMONGO-840 public void shouldAggregateOrderDataToAnInvoice() { mongoTemplate.dropCollection(Order.class); @@ -1146,10 +1139,7 @@ public void shouldAggregateOrderDataToAnInvoice() { assertThat(invoice.getTotalAmount(), is(closeTo(9.877, 000001))); } - /** - * @see DATAMONGO-924 - */ - @Test + @Test // DATAMONGO-924 public void shouldAllowGroupingByAliasedFieldDefinedInFormerAggregationStage() { mongoTemplate.dropCollection(CarPerson.class); @@ -1180,10 +1170,7 @@ public void shouldAllowGroupingByAliasedFieldDefinedInFormerAggregationStage() { assertThat(result.getMappedResults(), hasSize(3)); } - /** - * @see DATAMONGO-960 - */ - @Test + @Test // DATAMONGO-960 public void returnFiveMostCommonLikesAggregationFrameworkExampleWithSortOnDiskOptionEnabled() { assumeTrue(mongoVersion.isGreaterThanOrEqualTo(TWO_DOT_SIX)); @@ -1208,10 +1195,7 @@ public void returnFiveMostCommonLikesAggregationFrameworkExampleWithSortOnDiskOp assertLikeStats(result.getMappedResults().get(4), "e", 3); } - /** - * @see DATAMONGO-960 - */ - @Test + @Test // DATAMONGO-960 public void returnFiveMostCommonLikesShouldReturnStageExecutionInformationWithExplainOptionEnabled() { assumeTrue(mongoVersion.isGreaterThanOrEqualTo(TWO_DOT_SIX)); @@ -1231,10 +1215,7 @@ public void returnFiveMostCommonLikesShouldReturnStageExecutionInformationWithEx assertThat(rawResult.containsField("stages"), is(true)); } - /** - * @see DATAMONGO-954 - */ - @Test + @Test // DATAMONGO-954 public void shouldSupportReturningCurrentAggregationRoot() { assumeTrue(mongoVersion.isGreaterThanOrEqualTo(TWO_DOT_SIX)); @@ -1259,11 +1240,9 @@ public void shouldSupportReturningCurrentAggregationRoot() { } /** - * @see DATAMONGO-954 - * @see http - * ://stackoverflow.com/questions/24185987/using-root-inside-spring-data-mongodb-for-retrieving-whole-document + * {@link http://stackoverflow.com/questions/24185987/using-root-inside-spring-data-mongodb-for-retrieving-whole-document} */ - @Test + @Test // DATAMONGO-954 public void shouldSupportReturningCurrentAggregationRootInReference() { assumeTrue(mongoVersion.isGreaterThanOrEqualTo(TWO_DOT_SIX)); @@ -1284,10 +1263,28 @@ public void shouldSupportReturningCurrentAggregationRootInReference() { assertThat(result.getMappedResults(), hasSize(2)); } - /** - * @see DATAMONGO-975 - */ - @Test + @Test // DATAMONGO-1549 + public void shouldApplyCountCorrectly() { + + assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_FOUR)); + + mongoTemplate.save(new Reservation("0123", "42", 100)); + mongoTemplate.save(new Reservation("0360", "43", 200)); + mongoTemplate.save(new Reservation("0360", "44", 300)); + + Aggregation agg = newAggregation( // + count().as("documents"), // + project("documents") // + .andExpression("documents * 2").as("twice")); + AggregationResults result = mongoTemplate.aggregate(agg, Reservation.class, DBObject.class); + + assertThat(result.getMappedResults(), hasSize(1)); + + DBObject dbObject = result.getMappedResults().get(0); + assertThat(dbObject, isBsonObject().containing("documents", 3).containing("twice", 6)); + } + + @Test // DATAMONGO-975 public void shouldRetrieveDateTimeFragementsCorrectly() throws Exception { mongoTemplate.dropCollection(ObjectWithDate.class); @@ -1339,10 +1336,7 @@ public void shouldRetrieveDateTimeFragementsCorrectly() throws Exception { assertThat(dbo.get("dayOfYearPlus1DayManually"), is((Object) dateTime.plusDays(1).getDayOfYear())); } - /** - * @see DATAMONGO-1127 - */ - @Test + @Test // DATAMONGO-1127 public void shouldSupportGeoNearQueriesForAggregationWithDistanceField() { mongoTemplate.insert(new Venue("Penn Station", -73.99408, 40.75057)); @@ -1363,10 +1357,7 @@ public void shouldSupportGeoNearQueriesForAggregationWithDistanceField() { assertThat((Double) firstResult.get("distance"), closeTo(117.620092203928, 0.00001)); } - /** - * @see DATAMONGO-1133 - */ - @Test + @Test // DATAMONGO-1133 public void shouldHonorFieldAliasesForFieldReferences() { mongoTemplate.insert(new MeterData("m1", "counter1", 42)); @@ -1386,10 +1377,7 @@ public void shouldHonorFieldAliasesForFieldReferences() { assertThat(result.get("totalValue"), is(equalTo((Object) 100.0))); } - /** - * @see DATAMONGO-1326 - */ - @Test + @Test // DATAMONGO-1326 public void shouldLookupPeopleCorectly() { assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_TWO)); @@ -1410,10 +1398,7 @@ public void shouldLookupPeopleCorectly() { assertThat(firstItem, isBsonObject().containing("linkedPerson.[0].firstname", "u1")); } - /** - * @see DATAMONGO-1326 - */ - @Test + @Test // DATAMONGO-1326 public void shouldGroupByAndLookupPeopleCorectly() { assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_TWO)); @@ -1435,10 +1420,7 @@ public void shouldGroupByAndLookupPeopleCorectly() { assertThat(firstItem, isBsonObject().containing("linkedPerson.[0].firstname", "u1")); } - /** - * @see DATAMONGO-1418 - */ - @Test + @Test // DATAMONGO-1418 public void shouldCreateOutputCollection() { assumeTrue(mongoVersion.isGreaterThanOrEqualTo(TWO_DOT_SIX)); @@ -1467,10 +1449,7 @@ public void shouldCreateOutputCollection() { mongoTemplate.dropCollection(tempOutCollection); } - /** - * @see DATAMONGO-1418 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-1418 public void outShouldOutBeTheLastOperation() { newAggregation(match(new Criteria()), // @@ -1479,10 +1458,7 @@ public void outShouldOutBeTheLastOperation() { skip(100L)); } - /** - * @see DATAMONGO-1457 - */ - @Test + @Test // DATAMONGO-1457 public void sliceShouldBeAppliedCorrectly() { assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_TWO)); @@ -1500,6 +1476,213 @@ public void sliceShouldBeAppliedCorrectly() { } } + @Test // DATAMONGO-1491 + public void filterShouldBeAppliedCorrectly() { + + assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_TWO)); + + Item item43 = Item.builder().itemId("43").quantity(2).price(2L).build(); + Item item2 = Item.builder().itemId("2").quantity(1).price(240L).build(); + Sales sales1 = Sales.builder().id("0") + .items(Arrays.asList( // + item43, item2)) // + .build(); + + Item item23 = Item.builder().itemId("23").quantity(3).price(110L).build(); + Item item103 = Item.builder().itemId("103").quantity(4).price(5L).build(); + Item item38 = Item.builder().itemId("38").quantity(1).price(300L).build(); + Sales sales2 = Sales.builder().id("1").items(Arrays.asList( // + item23, item103, item38)).build(); + + Item item4 = Item.builder().itemId("4").quantity(1).price(23L).build(); + Sales sales3 = Sales.builder().id("2").items(Arrays.asList( // + item4)).build(); + + mongoTemplate.insert(Arrays.asList(sales1, sales2, sales3), Sales.class); + + TypedAggregation agg = newAggregation(Sales.class, project().and("items") + .filter("item", AggregationFunctionExpressions.GTE.of(field("item.price"), 100)).as("items")); + + assertThat(mongoTemplate.aggregate(agg, Sales.class).getMappedResults(), + contains(Sales.builder().id("0").items(Collections.singletonList(item2)).build(), + Sales.builder().id("1").items(Arrays.asList(item23, item38)).build(), + Sales.builder().id("2").items(Collections. emptyList()).build())); + } + + @Test // DATAMONGO-1538 + public void letShouldBeAppliedCorrectly() { + + assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_TWO)); + + Sales2 sales1 = Sales2.builder().id("1").price(10).tax(0.5F).applyDiscount(true).build(); + Sales2 sales2 = Sales2.builder().id("2").price(10).tax(0.25F).applyDiscount(false).build(); + + mongoTemplate.insert(Arrays.asList(sales1, sales2), Sales2.class); + + ExpressionVariable total = ExpressionVariable.newVariable("total") + .forExpression(AggregationFunctionExpressions.ADD.of(Fields.field("price"), Fields.field("tax"))); + ExpressionVariable discounted = ExpressionVariable.newVariable("discounted") + .forExpression(ConditionalOperators.Cond.when("applyDiscount").then(0.9D).otherwise(1.0D)); + + TypedAggregation agg = Aggregation.newAggregation(Sales2.class, + Aggregation.project() + .and(VariableOperators.Let.define(total, discounted).andApply( + AggregationFunctionExpressions.MULTIPLY.of(Fields.field("total"), Fields.field("discounted")))) + .as("finalTotal")); + + AggregationResults result = mongoTemplate.aggregate(agg, DBObject.class); + assertThat(result.getMappedResults(), + contains(new BasicDBObjectBuilder().add("_id", "1").add("finalTotal", 9.450000000000001D).get(), + new BasicDBObjectBuilder().add("_id", "2").add("finalTotal", 10.25D).get())); + } + + @Test // DATAMONGO-1551 + public void graphLookupShouldBeAppliedCorrectly() { + + assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_FOUR)); + + Employee em1 = Employee.builder().id(1).name("Dev").build(); + Employee em2 = Employee.builder().id(2).name("Eliot").reportsTo("Dev").build(); + Employee em4 = Employee.builder().id(4).name("Andrew").reportsTo("Eliot").build(); + + mongoTemplate.insert(Arrays.asList(em1, em2, em4), Employee.class); + + TypedAggregation agg = Aggregation.newAggregation(Employee.class, + match(Criteria.where("name").is("Andrew")), // + Aggregation.graphLookup("employee") // + .startWith("reportsTo") // + .connectFrom("reportsTo") // + .connectTo("name") // + .depthField("depth") // + .maxDepth(5) // + .as("reportingHierarchy")); + + AggregationResults result = mongoTemplate.aggregate(agg, DBObject.class); + + DBObject object = result.getUniqueMappedResult(); + BasicDBList list = (BasicDBList) object.get("reportingHierarchy"); + + assertThat(object, isBsonObject().containing("reportingHierarchy", List.class)); + assertThat((DBObject) list.get(0), isBsonObject().containing("name", "Dev").containing("depth", 1L)); + assertThat((DBObject) list.get(1), isBsonObject().containing("name", "Eliot").containing("depth", 0L)); + } + + @Test // DATAMONGO-1552 + public void bucketShouldCollectDocumentsIntoABucket() { + + assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_FOUR)); + + Art a1 = Art.builder().id(1).title("The Pillars of Society").artist("Grosz").year(1926).price(199.99).build(); + Art a2 = Art.builder().id(2).title("Melancholy III").artist("Munch").year(1902).price(280.00).build(); + Art a3 = Art.builder().id(3).title("Dancer").artist("Miro").year(1925).price(76.04).build(); + Art a4 = Art.builder().id(4).title("The Great Wave off Kanagawa").artist("Hokusai").price(167.30).build(); + + mongoTemplate.insert(Arrays.asList(a1, a2, a3, a4), Art.class); + + TypedAggregation aggregation = newAggregation(Art.class, // + bucket("price") // + .withBoundaries(0, 100, 200) // + .withDefaultBucket("other") // + .andOutputCount().as("count") // + .andOutput("title").push().as("titles") // + .andOutputExpression("price * 10").sum().as("sum")); + + AggregationResults result = mongoTemplate.aggregate(aggregation, DBObject.class); + assertThat(result.getMappedResults().size(), is(3)); + + // { "_id" : 0 , "count" : 1 , "titles" : [ "Dancer"] , "sum" : 760.4000000000001} + DBObject bound0 = result.getMappedResults().get(0); + assertThat(bound0, isBsonObject().containing("count", 1).containing("titles.[0]", "Dancer")); + assertThat((Double) bound0.get("sum"), is(closeTo(760.40, 0.1))); + + // { "_id" : 100 , "count" : 2 , "titles" : [ "The Pillars of Society" , "The Great Wave off Kanagawa"] , "sum" : + // 3672.9} + DBObject bound100 = result.getMappedResults().get(1); + assertThat(bound100, isBsonObject().containing("count", 2).containing("_id", 100)); + assertThat((List) bound100.get("titles"), + hasItems("The Pillars of Society", "The Great Wave off Kanagawa")); + assertThat((Double) bound100.get("sum"), is(closeTo(3672.9, 0.1))); + } + + @Test // DATAMONGO-1552 + public void bucketAutoShouldCollectDocumentsIntoABucket() { + + assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_FOUR)); + + Art a1 = Art.builder().id(1).title("The Pillars of Society").artist("Grosz").year(1926).price(199.99).build(); + Art a2 = Art.builder().id(2).title("Melancholy III").artist("Munch").year(1902).price(280.00).build(); + Art a3 = Art.builder().id(3).title("Dancer").artist("Miro").year(1925).price(76.04).build(); + Art a4 = Art.builder().id(4).title("The Great Wave off Kanagawa").artist("Hokusai").price(167.30).build(); + + mongoTemplate.insert(Arrays.asList(a1, a2, a3, a4), Art.class); + + TypedAggregation aggregation = newAggregation(Art.class, // + bucketAuto(ArithmeticOperators.Multiply.valueOf("price").multiplyBy(10), 3) // + .withGranularity(Granularities.E12) // + .andOutputCount().as("count") // + .andOutput("title").push().as("titles") // + .andOutputExpression("price * 10").sum().as("sum")); + + AggregationResults result = mongoTemplate.aggregate(aggregation, DBObject.class); + assertThat(result.getMappedResults().size(), is(3)); + + // { "min" : 680.0 , "max" : 820.0 , "count" : 1 , "titles" : [ "Dancer"] , "sum" : 760.4000000000001} + DBObject bound0 = result.getMappedResults().get(0); + assertThat(bound0, isBsonObject().containing("count", 1).containing("titles.[0]", "Dancer").containing("min", 680.0) + .containing("max")); + + // { "min" : 820.0 , "max" : 1800.0 , "count" : 1 , "titles" : [ "The Great Wave off Kanagawa"] , "sum" : 1673.0} + DBObject bound1 = result.getMappedResults().get(1); + assertThat(bound1, isBsonObject().containing("count", 1).containing("min", 820.0)); + assertThat((List) bound1.get("titles"), hasItems("The Great Wave off Kanagawa")); + assertThat((Double) bound1.get("sum"), is(closeTo(1673.0, 0.1))); + } + + @Test // DATAMONGO-1552 + public void facetShouldCreateFacets() { + + assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_FOUR)); + + Art a1 = Art.builder().id(1).title("The Pillars of Society").artist("Grosz").year(1926).price(199.99).build(); + Art a2 = Art.builder().id(2).title("Melancholy III").artist("Munch").year(1902).price(280.00).build(); + Art a3 = Art.builder().id(3).title("Dancer").artist("Miro").year(1925).price(76.04).build(); + Art a4 = Art.builder().id(4).title("The Great Wave off Kanagawa").artist("Hokusai").price(167.30).build(); + + mongoTemplate.insert(Arrays.asList(a1, a2, a3, a4), Art.class); + + BucketAutoOperation bucketPrice = bucketAuto(ArithmeticOperators.Multiply.valueOf("price").multiplyBy(10), 3) // + .withGranularity(Granularities.E12) // + .andOutputCount().as("count") // + .andOutput("title").push().as("titles") // + .andOutputExpression("price * 10") // + .sum().as("sum"); + + TypedAggregation aggregation = newAggregation(Art.class, // + project("title", "artist", "year", "price"), // + facet(bucketPrice).as("categorizeByPrice") // + .and(bucketAuto("year", 3)).as("categorizeByYear")); + + AggregationResults result = mongoTemplate.aggregate(aggregation, DBObject.class); + assertThat(result.getMappedResults().size(), is(1)); + + DBObject mappedResult = result.getUniqueMappedResult(); + + // [ { "_id" : { "min" : 680.0 , "max" : 820.0} , "count" : 1 , "titles" : [ "Dancer"] , "sum" : 760.4000000000001} + // , + // { "_id" : { "min" : 820.0 , "max" : 1800.0} , "count" : 1 , "titles" : [ "The Great Wave off Kanagawa"] , "sum" : + // 1673.0} , + // { "_id" : { "min" : 1800.0 , "max" : 3300.0} , "count" : 2 , "titles" : [ "The Pillars of Society" , "Melancholy + // III"] , "sum" : 4799.9}] + BasicDBList categorizeByPrice = (BasicDBList) mappedResult.get("categorizeByPrice"); + assertThat(categorizeByPrice, hasSize(3)); + + // [ { "_id" : { "min" : null , "max" : 1902} , "count" : 1} , + // { "_id" : { "min" : 1902 , "max" : 1925} , "count" : 1} , + // { "_id" : { "min" : 1925 , "max" : 1926} , "count" : 2}] + BasicDBList categorizeByYear = (BasicDBList) mappedResult.get("categorizeByYear"); + assertThat(categorizeByYear, hasSize(3)); + } + private void createUsersWithReferencedPersons() { mongoTemplate.dropCollection(User.class); @@ -1604,9 +1787,7 @@ public DATAMONGO788(int x, int y) { } } - /** - * @see DATAMONGO-806 - */ + // DATAMONGO-806 static class User { @Id String id; @@ -1620,9 +1801,7 @@ public User(String id, PushMessage... msgs) { } } - /** - * @see DATAMONGO-806 - */ + // DATAMONGO-806 static class PushMessage { @Id String id; @@ -1712,9 +1891,7 @@ public ObjectWithDate(Date dateValue) { } } - /** - * @see DATAMONGO-861 - */ + // DATAMONGO-861 @Document(collection = "inventory") static class InventoryItem { @@ -1740,4 +1917,57 @@ public InventoryItem(int id, String item, String description, int qty) { this.qty = qty; } } + + // DATAMONGO-1491 + @lombok.Data + @Builder + static class Sales { + + @Id String id; + List items; + } + + // DATAMONGO-1491 + @lombok.Data + @Builder + static class Item { + + @org.springframework.data.mongodb.core.mapping.Field("item_id") // + String itemId; + Integer quantity; + Long price; + } + + // DATAMONGO-1538 + @lombok.Data + @Builder + static class Sales2 { + + String id; + Integer price; + Float tax; + boolean applyDiscount; + } + + // DATAMONGO-1551 + @lombok.Data + @Builder + static class Employee { + + int id; + String name; + String reportsTo; + } + + // DATAMONGO-1552 + @lombok.Data + @Builder + static class Art { + + int id; + String title; + String artist; + Integer year; + double price; + } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationUnitTests.java index 84408bbcb8..9af74d70ff 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,7 +19,6 @@ import static org.junit.Assert.*; import static org.springframework.data.mongodb.core.DBObjectTestUtils.*; import static org.springframework.data.mongodb.core.aggregation.Aggregation.*; -import static org.springframework.data.mongodb.core.aggregation.Fields.*; import static org.springframework.data.mongodb.core.query.Criteria.*; import static org.springframework.data.mongodb.test.util.IsBsonObject.*; @@ -32,10 +31,12 @@ import org.junit.rules.ExpectedException; import org.springframework.data.domain.Sort.Direction; import org.springframework.data.mongodb.core.query.Criteria; +import org.springframework.data.mongodb.test.util.BasicDbListBuilder; import com.mongodb.BasicDBObject; import com.mongodb.BasicDBObjectBuilder; import com.mongodb.DBObject; +import com.mongodb.util.JSON; /** * Unit tests for {@link Aggregation}. @@ -69,10 +70,7 @@ public void rejectsNoTypedAggregationOperation() { newAggregation(String.class, new AggregationOperation[0]); } - /** - * @see DATAMONGO-753 - */ - @Test + @Test // DATAMONGO-753 public void checkForCorrectFieldScopeTransfer() { exception.expect(IllegalArgumentException.class); @@ -86,10 +84,7 @@ public void checkForCorrectFieldScopeTransfer() { ).toDbObject("foo", Aggregation.DEFAULT_CONTEXT); // -> triggers IllegalArgumentException } - /** - * @see DATAMONGO-753 - */ - @Test + @Test // DATAMONGO-753 public void unwindOperationShouldNotChangeAvailableFields() { newAggregation( // @@ -99,10 +94,7 @@ public void unwindOperationShouldNotChangeAvailableFields() { ).toDbObject("foo", Aggregation.DEFAULT_CONTEXT); } - /** - * @see DATAMONGO-1391 - */ - @Test + @Test // DATAMONGO-1391 public void unwindOperationWithIndexShouldPreserveFields() { newAggregation( // @@ -112,10 +104,7 @@ public void unwindOperationWithIndexShouldPreserveFields() { ).toDbObject("foo", Aggregation.DEFAULT_CONTEXT); } - /** - * @see DATAMONGO-1391 - */ - @Test + @Test // DATAMONGO-1391 public void unwindOperationWithIndexShouldAddIndexField() { newAggregation( // @@ -125,10 +114,7 @@ public void unwindOperationWithIndexShouldAddIndexField() { ).toDbObject("foo", Aggregation.DEFAULT_CONTEXT); } - /** - * @see DATAMONGO-1391 - */ - @Test + @Test // DATAMONGO-1391 public void fullUnwindOperationShouldBuildCorrectClause() { DBObject agg = newAggregation( // @@ -142,10 +128,7 @@ public void fullUnwindOperationShouldBuildCorrectClause() { containing("preserveNullAndEmptyArrays", true)); } - /** - * @see DATAMONGO-1391 - */ - @Test + @Test // DATAMONGO-1391 public void unwindOperationWithPreserveNullShouldBuildCorrectClause() { DBObject agg = newAggregation( // @@ -157,10 +140,19 @@ public void unwindOperationWithPreserveNullShouldBuildCorrectClause() { isBsonObject().notContaining("includeArrayIndex").containing("preserveNullAndEmptyArrays", true)); } - /** - * @see DATAMONGO-753 - */ - @Test + @Test // DATAMONGO-1550 + public void replaceRootOperationShouldBuildCorrectClause() { + + DBObject agg = newAggregation( // + replaceRoot().withDocument().andValue("value").as("field")) // + .toDbObject("foo", Aggregation.DEFAULT_CONTEXT); + + @SuppressWarnings("unchecked") + DBObject unwind = ((List) agg.get("pipeline")).get(0); + assertThat(unwind, isBsonObject().containing("$replaceRoot.newRoot", new BasicDBObject("field", "value"))); + } + + @Test // DATAMONGO-753 public void matchOperationShouldNotChangeAvailableFields() { newAggregation( // @@ -170,10 +162,7 @@ public void matchOperationShouldNotChangeAvailableFields() { ).toDbObject("foo", Aggregation.DEFAULT_CONTEXT); } - /** - * @see DATAMONGO-788 - */ - @Test + @Test // DATAMONGO-788 public void referencesToGroupIdsShouldBeRenderedAsReferences() { DBObject agg = newAggregation( // @@ -189,10 +178,7 @@ public void referencesToGroupIdsShouldBeRenderedAsReferences() { assertThat(fields.get("a"), is((Object) "$_id.a")); } - /** - * @see DATAMONGO-791 - */ - @Test + @Test // DATAMONGO-791 public void allowAggregationOperationsToBePassedAsIterable() { List ops = new ArrayList(); @@ -209,10 +195,7 @@ public void allowAggregationOperationsToBePassedAsIterable() { assertThat(fields.get("a"), is((Object) "$_id.a")); } - /** - * @see DATAMONGO-791 - */ - @Test + @Test // DATAMONGO-791 public void allowTypedAggregationOperationsToBePassedAsIterable() { List ops = new ArrayList(); @@ -229,10 +212,7 @@ public void allowTypedAggregationOperationsToBePassedAsIterable() { assertThat(fields.get("a"), is((Object) "$_id.a")); } - /** - * @see DATAMONGO-838 - */ - @Test + @Test // DATAMONGO-838 public void expressionBasedFieldsShouldBeReferencableInFollowingOperations() { DBObject agg = newAggregation( // @@ -246,10 +226,7 @@ public void expressionBasedFieldsShouldBeReferencableInFollowingOperations() { assertThat(fields.get("foosum"), is((Object) new BasicDBObject("$sum", "$foo"))); } - /** - * @see DATAMONGO-908 - */ - @Test + @Test // DATAMONGO-908 public void shouldSupportReferingToNestedPropertiesInGroupOperation() { DBObject agg = newAggregation( // @@ -267,10 +244,42 @@ public void shouldSupportReferingToNestedPropertiesInGroupOperation() { assertThat(id.get("ruleType"), is((Object) "$rules.ruleType")); } - /** - * @see DATAMONGO-924 - */ - @Test + @Test // DATAMONGO-1585 + public void shouldSupportSortingBySyntheticAndExposedGroupFields() { + + DBObject agg = newAggregation( // + group("cmsParameterId").addToSet("title").as("titles"), // + sort(Direction.ASC, "cmsParameterId", "titles") // + ).toDbObject("foo", Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(notNullValue())); + + DBObject sort = ((List) agg.get("pipeline")).get(1); + + assertThat(getAsDBObject(sort, "$sort"), is(JSON.parse("{ \"_id.cmsParameterId\" : 1 , \"titles\" : 1}"))); + } + + @Test // DATAMONGO-1585 + public void shouldSupportSortingByProjectedFields() { + + DBObject agg = newAggregation( // + project("cmsParameterId") // + .and(SystemVariable.CURRENT + ".titles").as("titles") // + .and("field").as("alias"), // + sort(Direction.ASC, "cmsParameterId", "titles", "alias") // + ).toDbObject("foo", Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(notNullValue())); + + DBObject sort = ((List) agg.get("pipeline")).get(1); + + assertThat(getAsDBObject(sort, "$sort"), + isBsonObject().containing("cmsParameterId", 1) // + .containing("titles", 1) // + .containing("alias", 1)); + } + + @Test // DATAMONGO-924 public void referencingProjectionAliasesFromPreviousStepShouldReferToTheSameFieldTarget() { DBObject agg = newAggregation( // @@ -285,10 +294,7 @@ public void referencingProjectionAliasesFromPreviousStepShouldReferToTheSameFiel assertThat(projection1, is((DBObject) new BasicDBObject("b", "$ba"))); } - /** - * @see DATAMONGO-960 - */ - @Test + @Test // DATAMONGO-960 public void shouldRenderAggregationWithDefaultOptionsCorrectly() { DBObject agg = newAggregation( // @@ -299,10 +305,7 @@ public void shouldRenderAggregationWithDefaultOptionsCorrectly() { is("{ \"aggregate\" : \"foo\" , \"pipeline\" : [ { \"$project\" : { \"aa\" : \"$a\"}}]}")); } - /** - * @see DATAMONGO-960 - */ - @Test + @Test // DATAMONGO-960 public void shouldRenderAggregationWithCustomOptionsCorrectly() { AggregationOptions aggregationOptions = newAggregationOptions().explain(true).cursor(new BasicDBObject("foo", 1)) @@ -323,17 +326,14 @@ public void shouldRenderAggregationWithCustomOptionsCorrectly() { )); } - /** - * @see DATAMONGO-954 - */ - @Test + @Test // DATAMONGO-954, DATAMONGO-1585 public void shouldSupportReferencingSystemVariables() { DBObject agg = newAggregation( // project("someKey") // .and("a").as("a1") // .and(Aggregation.CURRENT + ".a").as("a2") // - , sort(Direction.DESC, "a") // + , sort(Direction.DESC, "a1") // , group("someKey").first(Aggregation.ROOT).as("doc") // ).toDbObject("foo", Aggregation.DEFAULT_CONTEXT); @@ -342,17 +342,14 @@ public void shouldSupportReferencingSystemVariables() { is((DBObject) new BasicDBObject("someKey", 1).append("a1", "$a").append("a2", "$$CURRENT.a"))); DBObject sort = extractPipelineElement(agg, 1, "$sort"); - assertThat(sort, is((DBObject) new BasicDBObject("a", -1))); + assertThat(sort, is((DBObject) new BasicDBObject("a1", -1))); DBObject group = extractPipelineElement(agg, 2, "$group"); assertThat(group, is((DBObject) new BasicDBObject("_id", "$someKey").append("doc", new BasicDBObject("$first", "$$ROOT")))); } - /** - * @see DATAMONGO-1254 - */ - @Test + @Test // DATAMONGO-1254 public void shouldExposeAliasedFieldnameForProjectionsIncludingOperationsDownThePipeline() { DBObject agg = Aggregation.newAggregation(// @@ -366,10 +363,7 @@ public void shouldExposeAliasedFieldnameForProjectionsIncludingOperationsDownThe assertThat(getAsDBObject(group, "count"), is(new BasicDBObjectBuilder().add("$sum", "$tags_count").get())); } - /** - * @see DATAMONGO-1254 - */ - @Test + @Test // DATAMONGO-1254 public void shouldUseAliasedFieldnameForProjectionsIncludingOperationsDownThePipelineWhenUsingSpEL() { DBObject agg = Aggregation.newAggregation(// @@ -383,15 +377,14 @@ public void shouldUseAliasedFieldnameForProjectionsIncludingOperationsDownThePip assertThat(getAsDBObject(group, "count"), is(new BasicDBObjectBuilder().add("$sum", "$tags_count").get())); } - /** - * @see DATAMONGO-861 - */ - @Test + @Test // DATAMONGO-861 public void conditionExpressionBasedFieldsShouldBeReferencableInFollowingOperations() { DBObject agg = newAggregation( // project("a"), // - group("a").first(conditional(Criteria.where("a").gte(42), "answer", "no-answer")).as("foosum") // + group("a") + .first(ConditionalOperators.when(Criteria.where("a").gte(42)).thenValueOf("answer").otherwise("no-answer")) + .as("foosum") // ).toDbObject("foo", Aggregation.DEFAULT_CONTEXT); @SuppressWarnings("unchecked") @@ -402,14 +395,11 @@ public void conditionExpressionBasedFieldsShouldBeReferencableInFollowingOperati assertThat(getAsDBObject(fields, "foosum"), isBsonObject().containing("$first.$cond.else", "no-answer")); } - /** - * @see DATAMONGO-861 - */ - @Test + @Test // DATAMONGO-861 public void shouldRenderProjectionConditionalExpressionCorrectly() { DBObject agg = Aggregation.newAggregation(// - project().and(ConditionalOperator.newBuilder() // + project().and(ConditionalOperators.Cond.newBuilder() // .when("isYellow") // .then("bright") // .otherwise("dark")).as("color")) @@ -424,15 +414,12 @@ public void shouldRenderProjectionConditionalExpressionCorrectly() { assertThat(getAsDBObject(project, "color"), isBsonObject().containing("$cond", expectedCondition)); } - /** - * @see DATAMONGO-861 - */ - @Test + @Test // DATAMONGO-861 public void shouldRenderProjectionConditionalCorrectly() { DBObject agg = Aggregation.newAggregation(// project().and("color") - .applyCondition(ConditionalOperator.newBuilder() // + .applyCondition(ConditionalOperators.Cond.newBuilder() // .when("isYellow") // .then("bright") // .otherwise("dark"))) @@ -447,16 +434,14 @@ public void shouldRenderProjectionConditionalCorrectly() { assertThat(getAsDBObject(project, "color"), isBsonObject().containing("$cond", expectedCondition)); } - /** - * @see DATAMONGO-861 - */ - @Test + @Test // DATAMONGO-861 public void shouldRenderProjectionConditionalWithCriteriaCorrectly() { DBObject agg = Aggregation .newAggregation(project()// .and("color")// - .applyCondition(conditional(Criteria.where("key").gt(5), "bright", "dark"))) // + .applyCondition(ConditionalOperators.Cond.newBuilder().when(Criteria.where("key").gt(5)) // + .then("bright").otherwise("dark"))) // .toDbObject("foo", Aggregation.DEFAULT_CONTEXT); DBObject project = extractPipelineElement(agg, 0, "$project"); @@ -468,17 +453,17 @@ public void shouldRenderProjectionConditionalWithCriteriaCorrectly() { assertThat(getAsDBObject(project, "color"), isBsonObject().containing("$cond", expectedCondition)); } - /** - * @see DATAMONGO-861 - */ - @Test + @Test // DATAMONGO-861 public void referencingProjectionAliasesShouldRenderProjectionConditionalWithFieldReferenceCorrectly() { DBObject agg = Aggregation .newAggregation(// project().and("color").as("chroma"), project().and("luminosity") // - .applyCondition(conditional(field("chroma"), "bright", "dark"))) // + .applyCondition(ConditionalOperators // + .when("chroma") // + .thenValueOf("bright") // + .otherwise("dark"))) // .toDbObject("foo", Aggregation.DEFAULT_CONTEXT); DBObject project = extractPipelineElement(agg, 1, "$project"); @@ -490,17 +475,17 @@ public void referencingProjectionAliasesShouldRenderProjectionConditionalWithFie assertThat(getAsDBObject(project, "luminosity"), isBsonObject().containing("$cond", expectedCondition)); } - /** - * @see DATAMONGO-861 - */ - @Test + @Test // DATAMONGO-861 public void referencingProjectionAliasesShouldRenderProjectionConditionalWithCriteriaReferenceCorrectly() { DBObject agg = Aggregation .newAggregation(// project().and("color").as("chroma"), project().and("luminosity") // - .applyCondition(conditional(Criteria.where("chroma").is(100), "bright", "dark"))) // + .applyCondition(ConditionalOperators.Cond.newBuilder() + .when(Criteria.where("chroma") // + .is(100)) // + .then("bright").otherwise("dark"))) // .toDbObject("foo", Aggregation.DEFAULT_CONTEXT); DBObject project = extractPipelineElement(agg, 1, "$project"); @@ -512,17 +497,16 @@ public void referencingProjectionAliasesShouldRenderProjectionConditionalWithCri assertThat(getAsDBObject(project, "luminosity"), isBsonObject().containing("$cond", expectedCondition)); } - /** - * @see DATAMONGO-861 - */ - @Test + @Test // DATAMONGO-861 public void shouldRenderProjectionIfNullWithFieldReferenceCorrectly() { DBObject agg = Aggregation .newAggregation(// project().and("color"), // project().and("luminosity") // - .applyCondition(ifNull(field("chroma"), "unknown"))) // + .applyCondition(ConditionalOperators // + .ifNull("chroma") // + .then("unknown"))) // .toDbObject("foo", Aggregation.DEFAULT_CONTEXT); DBObject project = extractPipelineElement(agg, 1, "$project"); @@ -531,17 +515,15 @@ public void shouldRenderProjectionIfNullWithFieldReferenceCorrectly() { isBsonObject().containing("$ifNull", Arrays. asList("$chroma", "unknown"))); } - /** - * @see DATAMONGO-861 - */ - @Test + @Test // DATAMONGO-861 public void shouldRenderProjectionIfNullWithFallbackFieldReferenceCorrectly() { DBObject agg = Aggregation .newAggregation(// project("fallback").and("color").as("chroma"), project().and("luminosity") // - .applyCondition(ifNull(field("chroma"), field("fallback")))) // + .applyCondition(ConditionalOperators.ifNull("chroma") // + .thenValueOf("fallback"))) // .toDbObject("foo", Aggregation.DEFAULT_CONTEXT); DBObject project = extractPipelineElement(agg, 1, "$project"); @@ -550,6 +532,38 @@ public void shouldRenderProjectionIfNullWithFallbackFieldReferenceCorrectly() { isBsonObject().containing("$ifNull", Arrays.asList("$chroma", "$fallback"))); } + @Test // DATAMONGO-1552 + public void shouldHonorDefaultCountField() { + + DBObject agg = Aggregation + .newAggregation(// + bucket("year"), // + project("count")) // + .toDbObject("foo", Aggregation.DEFAULT_CONTEXT); + + DBObject project = extractPipelineElement(agg, 1, "$project"); + + assertThat(project, isBsonObject().containing("count", 1)); + } + + @Test // DATAMONGO-1533 + public void groupOperationShouldAllowUsageOfDerivedSpELAggregationExpression() { + + DBObject agg = newAggregation( // + project("a"), // + group("a").first(AggregationSpELExpression.expressionOf("cond(a >= 42, 'answer', 'no-answer')")).as("foosum") // + ).toDbObject("foo", Aggregation.DEFAULT_CONTEXT); + + @SuppressWarnings("unchecked") + DBObject secondProjection = ((List) agg.get("pipeline")).get(1); + DBObject fields = getAsDBObject(secondProjection, "$group"); + assertThat(getAsDBObject(fields, "foosum"), isBsonObject().containing("$first")); + assertThat(getAsDBObject(fields, "foosum"), isBsonObject().containing("$first.$cond.if", + new BasicDBObject("$gte", new BasicDbListBuilder().add("$a").add(42).get()))); + assertThat(getAsDBObject(fields, "foosum"), isBsonObject().containing("$first.$cond.then", "answer")); + assertThat(getAsDBObject(fields, "foosum"), isBsonObject().containing("$first.$cond.else", "no-answer")); + } + private DBObject extractPipelineElement(DBObject agg, int index, String operation) { List pipeline = (List) agg.get("pipeline"); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/BucketAutoOperationUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/BucketAutoOperationUnitTests.java new file mode 100644 index 0000000000..30fe6f1589 --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/BucketAutoOperationUnitTests.java @@ -0,0 +1,115 @@ +/* + * Copyright 2016-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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.core.Is.*; +import static org.junit.Assert.*; +import static org.springframework.data.mongodb.core.DBObjectTestUtils.getAsDBObject; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.*; + +import org.junit.Test; +import org.springframework.data.mongodb.core.aggregation.BucketAutoOperation.Granularities; + +import com.mongodb.DBObject; +import com.mongodb.util.JSON; + +/** + * Unit tests for {@link BucketAutoOperation}. + * + * @author Mark Paluch + */ +public class BucketAutoOperationUnitTests { + + @Test(expected = IllegalArgumentException.class) // DATAMONGO-1552 + public void rejectsNullFields() { + new BucketAutoOperation((Field) null, 0); + } + + @Test(expected = IllegalArgumentException.class) // DATAMONGO-1552 + public void rejectsNonPositiveIntegerNullFields() { + new BucketAutoOperation(Fields.field("field"), 0); + } + + @Test // DATAMONGO-1552 + public void shouldRenderBucketOutputExpressions() { + + BucketAutoOperation operation = Aggregation.bucketAuto("field", 5) // + .andOutputExpression("(netPrice + surCharge) * taxrate * [0]", 2).as("grossSalesPrice") // + .andOutput("title").push().as("titles"); + + DBObject dbObject = operation.toDBObject(Aggregation.DEFAULT_CONTEXT); + assertThat(extractOutput(dbObject), is(JSON.parse( + "{ \"grossSalesPrice\" : { \"$multiply\" : [ { \"$add\" : [ \"$netPrice\" , \"$surCharge\"]} , \"$taxrate\" , 2]} , \"titles\" : { $push: \"$title\" } }}"))); + } + + @Test(expected = IllegalStateException.class) // DATAMONGO-1552 + public void shouldRenderEmptyAggregationExpression() { + bucket("groupby").andOutput("field").as("alias"); + } + + @Test // DATAMONGO-1552 + public void shouldRenderBucketOutputOperators() { + + BucketAutoOperation operation = Aggregation.bucketAuto("field", 5) // + .andOutputCount().as("titles"); + + DBObject dbObject = operation.toDBObject(Aggregation.DEFAULT_CONTEXT); + assertThat(extractOutput(dbObject), is(JSON.parse("{ titles : { $sum: 1 } }"))); + } + + @Test // DATAMONGO-1552 + public void shouldRenderCorrectly() { + + DBObject agg = bucketAuto("field", 1).withBuckets(5).toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $bucketAuto: { groupBy: \"$field\", buckets: 5 } }"))); + } + + @Test // DATAMONGO-1552 + public void shouldRenderGranulariy() { + + DBObject agg = bucketAuto("field", 1) // + .withGranularity(Granularities.E24) // + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $bucketAuto: { buckets: 1, granularity: \"E24\", groupBy: \"$field\" } }"))); + } + + @Test // DATAMONGO-1552 + public void shouldRenderSumOperator() { + + BucketAutoOperation operation = bucketAuto("field", 5) // + .andOutput("score").sum().as("cummulated_score"); + + DBObject dbObject = operation.toDBObject(Aggregation.DEFAULT_CONTEXT); + assertThat(extractOutput(dbObject), is(JSON.parse("{ cummulated_score : { $sum: \"$score\" } }"))); + } + + @Test // DATAMONGO-1552 + public void shouldRenderSumWithOwnOutputExpression() { + + BucketAutoOperation operation = bucketAuto("field", 5) // + .andOutputExpression("netPrice + tax").apply("$multiply", 5).as("total"); + + DBObject dbObject = operation.toDBObject(Aggregation.DEFAULT_CONTEXT); + assertThat(extractOutput(dbObject), + is(JSON.parse("{ total : { $multiply: [ {$add : [\"$netPrice\", \"$tax\"]}, 5] } }"))); + } + + private static DBObject extractOutput(DBObject fromBucketClause) { + return getAsDBObject(getAsDBObject(fromBucketClause, "$bucketAuto"), "output"); + } +} diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/BucketOperationUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/BucketOperationUnitTests.java new file mode 100644 index 0000000000..f6d50bc873 --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/BucketOperationUnitTests.java @@ -0,0 +1,213 @@ +/* + * Copyright 2016-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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.hamcrest.core.Is.is; +import static org.junit.Assert.*; +import static org.springframework.data.mongodb.core.DBObjectTestUtils.*; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.*; + +import org.junit.Test; + +import com.mongodb.DBObject; +import com.mongodb.util.JSON; + +/** + * Unit tests for {@link BucketOperation}. + * + * @author Mark Paluch + */ +public class BucketOperationUnitTests { + + @Test(expected = IllegalArgumentException.class) // DATAMONGO-1552 + public void rejectsNullFields() { + new BucketOperation((Field) null); + } + + @Test // DATAMONGO-1552 + public void shouldRenderBucketOutputExpressions() { + + BucketOperation operation = Aggregation.bucket("field") // + .andOutputExpression("(netPrice + surCharge) * taxrate * [0]", 2).as("grossSalesPrice") // + .andOutput("title").push().as("titles"); + + DBObject dbObject = operation.toDBObject(Aggregation.DEFAULT_CONTEXT); + assertThat(extractOutput(dbObject), is(JSON.parse( + "{ \"grossSalesPrice\" : { \"$multiply\" : [ { \"$add\" : [ \"$netPrice\" , \"$surCharge\"]} , \"$taxrate\" , 2]} , \"titles\" : { $push: \"$title\" } }}"))); + } + + @Test(expected = IllegalStateException.class) // DATAMONGO-1552 + public void shouldRenderEmptyAggregationExpression() { + bucket("groupby").andOutput("field").as("alias"); + } + + @Test // DATAMONGO-1552 + public void shouldRenderBucketOutputOperators() { + + BucketOperation operation = Aggregation.bucket("field") // + .andOutputCount().as("titles"); + + DBObject dbObject = operation.toDBObject(Aggregation.DEFAULT_CONTEXT); + assertThat(extractOutput(dbObject), is(JSON.parse("{ titles : { $sum: 1 } }"))); + } + + @Test // DATAMONGO-1552 + public void shouldRenderSumAggregationExpression() { + + DBObject agg = bucket("field") // + .andOutput(ArithmeticOperators.valueOf("quizzes").sum()).as("quizTotal") // + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse( + "{ $bucket: { groupBy: \"$field\", boundaries: [], output : { quizTotal: { $sum: \"$quizzes\"} } } }"))); + } + + @Test // DATAMONGO-1552 + public void shouldRenderDefault() { + + DBObject agg = bucket("field").withDefaultBucket("default bucket").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, + is(JSON.parse("{ $bucket: { groupBy: \"$field\", boundaries: [], default: \"default bucket\" } }"))); + } + + @Test // DATAMONGO-1552 + public void shouldRenderBoundaries() { + + DBObject agg = bucket("field") // + .withDefaultBucket("default bucket") // + .withBoundaries(0) // + .withBoundaries(10, 20).toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, + is(JSON.parse("{ $bucket: { boundaries: [0, 10, 20], default: \"default bucket\", groupBy: \"$field\" } }"))); + } + + @Test // DATAMONGO-1552 + public void shouldRenderSumOperator() { + + BucketOperation operation = bucket("field") // + .andOutput("score").sum().as("cummulated_score"); + + DBObject dbObject = operation.toDBObject(Aggregation.DEFAULT_CONTEXT); + assertThat(extractOutput(dbObject), is(JSON.parse("{ cummulated_score : { $sum: \"$score\" } }"))); + } + + @Test // DATAMONGO-1552 + public void shouldRenderSumWithValueOperator() { + + BucketOperation operation = bucket("field") // + .andOutput("score").sum(4).as("cummulated_score"); + + DBObject dbObject = operation.toDBObject(Aggregation.DEFAULT_CONTEXT); + assertThat(extractOutput(dbObject), is(JSON.parse("{ cummulated_score : { $sum: 4 } }"))); + } + + @Test // DATAMONGO-1552 + public void shouldRenderAvgOperator() { + + BucketOperation operation = bucket("field") // + .andOutput("score").avg().as("average"); + + DBObject dbObject = operation.toDBObject(Aggregation.DEFAULT_CONTEXT); + assertThat(extractOutput(dbObject), is(JSON.parse("{ average : { $avg: \"$score\" } }"))); + } + + @Test // DATAMONGO-1552 + public void shouldRenderFirstOperator() { + + BucketOperation operation = bucket("field") // + .andOutput("title").first().as("first_title"); + + DBObject dbObject = operation.toDBObject(Aggregation.DEFAULT_CONTEXT); + assertThat(extractOutput(dbObject), is(JSON.parse("{ first_title : { $first: \"$title\" } }"))); + } + + @Test // DATAMONGO-1552 + public void shouldRenderLastOperator() { + + BucketOperation operation = bucket("field") // + .andOutput("title").last().as("last_title"); + + DBObject dbObject = operation.toDBObject(Aggregation.DEFAULT_CONTEXT); + assertThat(extractOutput(dbObject), is(JSON.parse("{ last_title : { $last: \"$title\" } }"))); + } + + @Test // DATAMONGO-1552 + public void shouldRenderMinOperator() { + + BucketOperation operation = bucket("field") // + .andOutput("score").min().as("min_score"); + + DBObject dbObject = operation.toDBObject(Aggregation.DEFAULT_CONTEXT); + assertThat(extractOutput(dbObject), is(JSON.parse("{ min_score : { $min: \"$score\" } }"))); + } + + @Test // DATAMONGO-1552 + public void shouldRenderPushOperator() { + + BucketOperation operation = bucket("field") // + .andOutput("title").push().as("titles"); + + DBObject dbObject = operation.toDBObject(Aggregation.DEFAULT_CONTEXT); + assertThat(extractOutput(dbObject), is(JSON.parse("{ titles : { $push: \"$title\" } }"))); + } + + @Test // DATAMONGO-1552 + public void shouldRenderAddToSetOperator() { + + BucketOperation operation = bucket("field") // + .andOutput("title").addToSet().as("titles"); + + DBObject dbObject = operation.toDBObject(Aggregation.DEFAULT_CONTEXT); + assertThat(extractOutput(dbObject), is(JSON.parse("{ titles : { $addToSet: \"$title\" } }"))); + } + + @Test // DATAMONGO-1552 + public void shouldRenderSumWithExpression() { + + BucketOperation operation = bucket("field") // + .andOutputExpression("netPrice + tax").sum().as("total"); + + DBObject dbObject = operation.toDBObject(Aggregation.DEFAULT_CONTEXT); + assertThat(extractOutput(dbObject), is(JSON.parse("{ total : { $sum: { $add : [\"$netPrice\", \"$tax\"]} } }"))); + } + + @Test // DATAMONGO-1552 + public void shouldRenderSumWithOwnOutputExpression() { + + BucketOperation operation = bucket("field") // + .andOutputExpression("netPrice + tax").apply("$multiply", 5).as("total"); + + DBObject dbObject = operation.toDBObject(Aggregation.DEFAULT_CONTEXT); + assertThat(extractOutput(dbObject), + is(JSON.parse("{ total : { $multiply: [ {$add : [\"$netPrice\", \"$tax\"]}, 5] } }"))); + } + + @Test // DATAMONGO-1552 + public void shouldExposeDefaultCountField() { + + BucketOperation operation = bucket("field"); + + assertThat(operation.getFields().exposesSingleFieldOnly(), is(true)); + assertThat(operation.getFields().getField("count"), is(notNullValue())); + } + + private static DBObject extractOutput(DBObject fromBucketClause) { + return getAsDBObject(getAsDBObject(fromBucketClause, "$bucket"), "output"); + } +} diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ConditionalOperatorUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/CondExpressionUnitTests.java similarity index 70% rename from spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ConditionalOperatorUnitTests.java rename to spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/CondExpressionUnitTests.java index 475559a0a1..45afe8873d 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ConditionalOperatorUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/CondExpressionUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 the original author or authors. + * Copyright 2016-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,88 +16,50 @@ package org.springframework.data.mongodb.core.aggregation; import static org.junit.Assert.*; -import static org.springframework.data.mongodb.core.aggregation.ConditionalOperator.*; +import static org.springframework.data.mongodb.core.aggregation.ConditionalOperators.Cond.*; import static org.springframework.data.mongodb.test.util.IsBsonObject.*; import java.util.Arrays; import org.junit.Test; +import org.springframework.data.mongodb.core.aggregation.ConditionalOperators.Cond; import org.springframework.data.mongodb.core.query.Criteria; import com.mongodb.BasicDBObject; import com.mongodb.DBObject; /** - * Unit tests for {@link ConditionalOperator}. + * Unit tests for {@link Cond}. * * @author Mark Paluch * @author Christoph Strobl */ -public class ConditionalOperatorUnitTests { - - /** - * @see DATAMONGO-861 - */ - @Test(expected = IllegalArgumentException.class) - public void shouldRejectNullCondition() { - new ConditionalOperator((Field) null, "", ""); - } - - /** - * @see DATAMONGO-861 - */ - @Test(expected = IllegalArgumentException.class) - public void shouldRejectThenValue() { - new ConditionalOperator(Fields.field("field"), null, ""); - } +public class CondExpressionUnitTests { - /** - * @see DATAMONGO-861 - */ - @Test(expected = IllegalArgumentException.class) - public void shouldRejectOtherwiseValue() { - new ConditionalOperator(Fields.field("field"), "", null); - } - - /** - * @see DATAMONGO-861 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-861 public void builderRejectsEmptyFieldName() { newBuilder().when(""); } - /** - * @see DATAMONGO-861 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-861 public void builderRejectsNullFieldName() { newBuilder().when((DBObject) null); } - /** - * @see DATAMONGO-861 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-861 public void builderRejectsNullCriteriaName() { newBuilder().when((Criteria) null); } - /** - * @see DATAMONGO-861 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-861 public void builderRejectsBuilderAsThenValue() { newBuilder().when("isYellow").then(newBuilder().when("field").then("then-value")).otherwise("otherwise"); } - /** - * @see DATAMONGO-861 - */ - @Test + @Test // DATAMONGO-861, DATAMONGO-1542 public void simpleBuilderShouldRenderCorrectly() { - ConditionalOperator operator = newBuilder().when("isYellow").then("bright").otherwise("dark"); + Cond operator = ConditionalOperators.when("isYellow").thenValueOf("bright").otherwise("dark"); DBObject dbObject = operator.toDbObject(Aggregation.DEFAULT_CONTEXT); DBObject expectedCondition = new BasicDBObject() // @@ -108,13 +70,10 @@ public void simpleBuilderShouldRenderCorrectly() { assertThat(dbObject, isBsonObject().containing("$cond", expectedCondition)); } - /** - * @see DATAMONGO-861 - */ - @Test + @Test // DATAMONGO-861, DATAMONGO-1542 public void simpleCriteriaShouldRenderCorrectly() { - ConditionalOperator operator = newBuilder().when(Criteria.where("luminosity").gte(100)).then("bright") + Cond operator = ConditionalOperators.when(Criteria.where("luminosity").gte(100)).thenValueOf("bright") .otherwise("dark"); DBObject dbObject = operator.toDbObject(Aggregation.DEFAULT_CONTEXT); @@ -126,17 +85,13 @@ public void simpleCriteriaShouldRenderCorrectly() { assertThat(dbObject, isBsonObject().containing("$cond", expectedCondition)); } - /** - * @see DATAMONGO-861 - */ - @Test + @Test // DATAMONGO-861 public void andCriteriaShouldRenderCorrectly() { - ConditionalOperator operator = newBuilder() // - .when(Criteria.where("luminosity").gte(100) // - .andOperator(Criteria.where("hue").is(50), // - Criteria.where("saturation").lt(11))) - .then("bright").otherwise("dark"); + Cond operator = ConditionalOperators.when(Criteria.where("luminosity").gte(100) // + .andOperator(Criteria.where("hue").is(50), // + Criteria.where("saturation").lt(11))) + .thenValueOf("bright").otherwiseValueOf("dark-field"); DBObject dbObject = operator.toDbObject(Aggregation.DEFAULT_CONTEXT); @@ -147,20 +102,17 @@ public void andCriteriaShouldRenderCorrectly() { DBObject expectedCondition = new BasicDBObject() // .append("if", Arrays. asList(luminosity, new BasicDBObject("$and", Arrays.asList(hue, saturation)))) // .append("then", "bright") // - .append("else", "dark"); + .append("else", "$dark-field"); assertThat(dbObject, isBsonObject().containing("$cond", expectedCondition)); } - /** - * @see DATAMONGO-861 - */ - @Test + @Test // DATAMONGO-861, DATAMONGO-1542 public void twoArgsCriteriaShouldRenderCorrectly() { Criteria criteria = Criteria.where("luminosity").gte(100) // .and("saturation").and("chroma").is(200); - ConditionalOperator operator = newBuilder().when(criteria).then("bright").otherwise("dark"); + Cond operator = ConditionalOperators.when(criteria).thenValueOf("bright").otherwise("dark"); DBObject dbObject = operator.toDbObject(Aggregation.DEFAULT_CONTEXT); @@ -175,15 +127,11 @@ public void twoArgsCriteriaShouldRenderCorrectly() { assertThat(dbObject, isBsonObject().containing("$cond", expectedCondition)); } - /** - * @see DATAMONGO-861 - */ - @Test + @Test // DATAMONGO-861, DATAMONGO-1542 public void nestedCriteriaShouldRenderCorrectly() { - ConditionalOperator operator = newBuilder() // - .when(Criteria.where("luminosity").gte(100)) // - .then(newBuilder() // + Cond operator = ConditionalOperators.when(Criteria.where("luminosity").gte(100)) // + .thenValueOf(newBuilder() // .when(Criteria.where("luminosity").gte(200)) // .then("verybright") // .otherwise("not-so-bright")) // diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/CountOperationUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/CountOperationUnitTests.java new file mode 100644 index 0000000000..73a2201d9e --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/CountOperationUnitTests.java @@ -0,0 +1,53 @@ +/* + * Copyright 2016-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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 com.mongodb.util.JSON; + +/** + * Unit tests for {@link CountOperation}. + * + * @author Mark Paluch + */ +public class CountOperationUnitTests { + + @Test(expected = IllegalArgumentException.class) // DATAMONGO-1549 + public void rejectsEmptyFieldName() { + new CountOperation(""); + } + + @Test // DATAMONGO-1549 + public void shouldRenderCorrectly() { + + CountOperation countOperation = new CountOperation("field"); + assertThat(countOperation.toDBObject(Aggregation.DEFAULT_CONTEXT), is(JSON.parse("{$count : \"field\" }"))); + } + + @Test // DATAMONGO-1549 + public void countExposesFields() { + + CountOperation countOperation = new CountOperation("field"); + + assertThat(countOperation.getFields().exposesNoFields(), is(false)); + assertThat(countOperation.getFields().exposesSingleFieldOnly(), is(true)); + assertThat(countOperation.getFields().getField("field"), notNullValue()); + } +} diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/FacetOperationUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/FacetOperationUnitTests.java new file mode 100644 index 0000000000..83b5f8ab65 --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/FacetOperationUnitTests.java @@ -0,0 +1,105 @@ +/* + * Copyright 2016-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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.MatcherAssert.*; +import static org.hamcrest.Matchers.*; +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; +import com.mongodb.util.JSON; + +/** + * Unit tests for {@link FacetOperation}. + * + * @author Mark Paluch + * @soundtrack Stanley Foort - You Make Me Believe In Magic (Extended Mix) + */ +public class FacetOperationUnitTests { + + @Test // DATAMONGO-1552 + public void shouldRenderCorrectly() throws Exception { + + FacetOperation facetOperation = new FacetOperation() + .and(match(Criteria.where("price").exists(true)), // + bucket("price") // + .withBoundaries(0, 150, 200, 300, 400) // + .withDefaultBucket("Other") // + .andOutputCount().as("count") // + .andOutput("title").push().as("titles")) // + .as("categorizedByPrice") // + .and(bucketAuto("year", 5)).as("categorizedByYears"); + + DBObject dbObject = facetOperation.toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(dbObject, + is(JSON.parse("{ $facet: { categorizedByPrice: [" + "{ $match: { price: { $exists: true } } }, " + + "{ $bucket: { boundaries: [ 0, 150, 200, 300, 400 ], groupBy: \"$price\", default: \"Other\", " + + "output: { count: { $sum: 1 }, titles: { $push: \"$title\" } } } } ]," + + "categorizedByYears: [ { $bucketAuto: { buckets: 5, groupBy: \"$year\" } } ] } }"))); + } + + @Test // DATAMONGO-1552 + public void shouldRenderEmpty() throws Exception { + + FacetOperation facetOperation = facet(); + + DBObject dbObject = facetOperation.toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(dbObject, is(JSON.parse("{ $facet: { } }"))); + } + + @Test(expected = IllegalArgumentException.class) // DATAMONGO-1552 + public void shouldRejectNonExistingFields() throws Exception { + + FacetOperation facetOperation = new FacetOperation() + .and(project("price"), // + bucket("price") // + .withBoundaries(0, 150, 200, 300, 400) // + .withDefaultBucket("Other") // + .andOutputCount().as("count") // + .andOutput("title").push().as("titles")) // + .as("categorizedByPrice"); + + DBObject dbObject = facetOperation.toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(dbObject, + is(JSON.parse("{ $facet: { categorizedByPrice: [" + "{ $match: { price: { $exists: true } } }, " + + "{ $bucket: {boundaries: [ 0, 150, 200, 300, 400 ], groupBy: \"$price\", default: \"Other\", " + + "output: { count: { $sum: 1 }, titles: { $push: \"$title\" } } } } ]," + + "categorizedByYears: [ { $bucketAuto: { buckets: 5, groupBy: \"$year\" } } ] } }"))); + } + + @Test // DATAMONGO-1552 + public void shouldHonorProjectedFields() { + + FacetOperation facetOperation = new FacetOperation() + .and(project("price").and("title").as("name"), // + bucketAuto("price", 5) // + .andOutput("name").push().as("titles")) // + .as("categorizedByPrice"); + + DBObject dbObject = facetOperation.toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(dbObject, + is(JSON.parse("{ $facet: { categorizedByPrice: [" + "{ $project: { price: 1, name: \"$title\" } }, " + + "{ $bucketAuto: { buckets: 5, groupBy: \"$price\", " + + "output: { titles: { $push: \"$name\" } } } } ] } }"))); + } +} diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/FieldsUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/FieldsUnitTests.java index 77cd200207..014e0dd95c 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/FieldsUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/FieldsUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -106,28 +106,19 @@ public void rejectsAmbiguousFieldNames() { fields("b", "a.b"); } - /** - * @see DATAMONGO-774 - */ - @Test + @Test // DATAMONGO-774 public void stripsLeadingDollarsFromName() { assertThat(Fields.field("$name").getName(), is("name")); assertThat(Fields.field("$$$$name").getName(), is("name")); } - /** - * @see DATAMONGO-774 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-774 public void rejectsNameConsistingOfDollarOnly() { Fields.field("$"); } - /** - * @see DATAMONGO-774 - */ - @Test + @Test // DATAMONGO-774 public void stripsLeadingDollarsFromTarget() { assertThat(Fields.field("$target").getTarget(), is("target")); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/FilterExpressionUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/FilterExpressionUnitTests.java new file mode 100644 index 0000000000..e2b389327d --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/FilterExpressionUnitTests.java @@ -0,0 +1,132 @@ +/* + * 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 static org.hamcrest.core.Is.*; +import static org.junit.Assert.*; +import static org.springframework.data.mongodb.core.aggregation.ArrayOperators.Filter.*; + +import java.util.Arrays; +import java.util.List; + +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.DBObjectTestUtils; +import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver; +import org.springframework.data.mongodb.core.convert.MappingMongoConverter; +import org.springframework.data.mongodb.core.convert.QueryMapper; +import org.springframework.data.mongodb.core.mapping.MongoMappingContext; + +import com.mongodb.DBObject; +import com.mongodb.util.JSON; + +/** + * @author Christoph Strobl + */ +@RunWith(MockitoJUnitRunner.class) +public class FilterExpressionUnitTests { + + @Mock MongoDbFactory mongoDbFactory; + + private AggregationOperationContext aggregationContext; + private MongoMappingContext mappingContext; + + @Before + public void setUp() { + + mappingContext = new MongoMappingContext(); + aggregationContext = new TypeBasedAggregationOperationContext(Sales.class, mappingContext, + new QueryMapper(new MappingMongoConverter(new DefaultDbRefResolver(mongoDbFactory), mappingContext))); + } + + @Test // DATAMONGO-1491 + public void shouldConstructFilterExpressionCorrectly() { + + TypedAggregation agg = Aggregation.newAggregation(Sales.class, + Aggregation.project() + .and(filter("items").as("item").by(AggregationFunctionExpressions.GTE.of(Fields.field("item.price"), 100))) + .as("items")); + + DBObject dbo = agg.toDbObject("sales", aggregationContext); + + List pipeline = DBObjectTestUtils.getAsList(dbo, "pipeline"); + DBObject $project = DBObjectTestUtils.getAsDBObject((DBObject) pipeline.get(0), "$project"); + DBObject items = DBObjectTestUtils.getAsDBObject($project, "items"); + DBObject $filter = DBObjectTestUtils.getAsDBObject(items, "$filter"); + + DBObject expected = (DBObject) JSON.parse("{" + // + "input: \"$items\"," + // + "as: \"item\"," + // + "cond: { $gte: [ \"$$item.price\", 100 ] }" + // + "}"); + + assertThat($filter, is(expected)); + } + + @Test // DATAMONGO-1491 + public void shouldConstructFilterExpressionCorrectlyWhenUsingFilterOnProjectionBuilder() { + + TypedAggregation agg = Aggregation.newAggregation(Sales.class, Aggregation.project().and("items") + .filter("item", AggregationFunctionExpressions.GTE.of(Fields.field("item.price"), 100)).as("items")); + + DBObject dbo = agg.toDbObject("sales", aggregationContext); + + List pipeline = DBObjectTestUtils.getAsList(dbo, "pipeline"); + DBObject $project = DBObjectTestUtils.getAsDBObject((DBObject) pipeline.get(0), "$project"); + DBObject items = DBObjectTestUtils.getAsDBObject($project, "items"); + DBObject $filter = DBObjectTestUtils.getAsDBObject(items, "$filter"); + + DBObject expected = (DBObject) JSON.parse("{" + // + "input: \"$items\"," + // + "as: \"item\"," + // + "cond: { $gte: [ \"$$item.price\", 100 ] }" + // + "}"); + + assertThat($filter, is(expected)); + } + + @Test // DATAMONGO-1491 + public void shouldConstructFilterExpressionCorrectlyWhenInputMapToArray() { + + TypedAggregation agg = Aggregation.newAggregation(Sales.class, + Aggregation.project().and(filter(Arrays. asList(1, "a", 2, null, 3.1D, 4, "5")).as("num") + .by(AggregationFunctionExpressions.GTE.of(Fields.field("num"), 3))).as("items")); + + DBObject dbo = agg.toDbObject("sales", aggregationContext); + + List pipeline = DBObjectTestUtils.getAsList(dbo, "pipeline"); + DBObject $project = DBObjectTestUtils.getAsDBObject((DBObject) pipeline.get(0), "$project"); + DBObject items = DBObjectTestUtils.getAsDBObject($project, "items"); + DBObject $filter = DBObjectTestUtils.getAsDBObject(items, "$filter"); + + DBObject expected = (DBObject) JSON.parse("{" + // + "input: [ 1, \"a\", 2, null, 3.1, 4, \"5\" ]," + // + "as: \"num\"," + // + "cond: { $gte: [ \"$$num\", 3 ] }" + // + "}"); + + assertThat($filter, is(expected)); + } + + static class Sales { + + List items; + } +} diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/GeoNearOperationUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/GeoNearOperationUnitTests.java index 95e2f13a73..f676ed5a93 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/GeoNearOperationUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/GeoNearOperationUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2015 the original author or authors. + * Copyright 2013-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,10 +33,7 @@ */ public class GeoNearOperationUnitTests { - /** - * @see DATAMONGO-1127 - */ - @Test + @Test // DATAMONGO-1127 public void rendersNearQueryAsAggregationOperation() { NearQuery query = NearQuery.near(10.0, 10.0); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/GraphLookupOperationUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/GraphLookupOperationUnitTests.java new file mode 100644 index 0000000000..f4e91860f0 --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/GraphLookupOperationUnitTests.java @@ -0,0 +1,136 @@ +/* + * Copyright 2016-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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.core.Is.*; +import static org.junit.Assert.*; +import static org.springframework.data.mongodb.test.util.IsBsonObject.*; + +import org.junit.Test; +import org.springframework.data.mongodb.core.Person; +import org.springframework.data.mongodb.core.query.Criteria; + +import com.mongodb.BasicDBObject; +import com.mongodb.DBObject; +import com.mongodb.util.JSON; + +/** + * Unit tests for {@link GraphLookupOperation}. + * + * @author Mark Paluch + * @author Christoph Strobl + */ +public class GraphLookupOperationUnitTests { + + @Test(expected = IllegalArgumentException.class) // DATAMONGO-1551 + public void rejectsNullFromCollection() { + GraphLookupOperation.builder().from(null); + } + + @Test // DATAMONGO-1551 + public void shouldRenderCorrectly() { + + GraphLookupOperation graphLookupOperation = GraphLookupOperation.builder() // + .from("employees") // + .startWith("reportsTo") // + .connectFrom("reportsTo") // + .connectTo("name") // + .depthField("depth") // + .maxDepth(42) // + .as("reportingHierarchy"); + + DBObject dbObject = graphLookupOperation.toDBObject(Aggregation.DEFAULT_CONTEXT); + assertThat(dbObject, + isBsonObject().containing("$graphLookup.depthField", "depth").containing("$graphLookup.maxDepth", 42L)); + } + + @Test // DATAMONGO-1551 + public void shouldRenderCriteriaCorrectly() { + + GraphLookupOperation graphLookupOperation = GraphLookupOperation.builder() // + .from("employees") // + .startWith("reportsTo") // + .connectFrom("reportsTo") // + .connectTo("name") // + .restrict(Criteria.where("key").is("value")) // + .as("reportingHierarchy"); + + DBObject dbObject = graphLookupOperation.toDBObject(Aggregation.DEFAULT_CONTEXT); + assertThat(dbObject, + isBsonObject().containing("$graphLookup.restrictSearchWithMatch", new BasicDBObject("key", "value"))); + } + + @Test // DATAMONGO-1551 + public void shouldRenderArrayOfStartsWithCorrectly() { + + GraphLookupOperation graphLookupOperation = GraphLookupOperation.builder() // + .from("employees") // + .startWith("reportsTo", "boss") // + .connectFrom("reportsTo") // + .connectTo("name") // + .as("reportingHierarchy"); + + DBObject dbObject = graphLookupOperation.toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(dbObject, + is(JSON.parse("{ $graphLookup : { from: \"employees\", startWith: [\"$reportsTo\", \"$boss\"], " + + "connectFromField: \"reportsTo\", connectToField: \"name\", as: \"reportingHierarchy\" } }"))); + } + + @Test // DATAMONGO-1551 + public void shouldRenderMixedArrayOfStartsWithCorrectly() { + + GraphLookupOperation graphLookupOperation = GraphLookupOperation.builder() // + .from("employees") // + .startWith("reportsTo", LiteralOperators.Literal.asLiteral("$boss")) // + .connectFrom("reportsTo") // + .connectTo("name") // + .as("reportingHierarchy"); + + DBObject dbObject = graphLookupOperation.toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(dbObject, + is(JSON.parse("{ $graphLookup : { from: \"employees\", startWith: [\"$reportsTo\", { $literal: \"$boss\"}], " + + "connectFromField: \"reportsTo\", connectToField: \"name\", as: \"reportingHierarchy\" } }"))); + } + + @Test(expected = IllegalArgumentException.class) // DATAMONGO-1551 + public void shouldRejectUnknownTypeInMixedArrayOfStartsWithCorrectly() { + + GraphLookupOperation graphLookupOperation = GraphLookupOperation.builder() // + .from("employees") // + .startWith("reportsTo", new Person()) // + .connectFrom("reportsTo") // + .connectTo("name") // + .as("reportingHierarchy"); + } + + @Test // DATAMONGO-1551 + public void shouldRenderStartWithAggregationExpressions() { + + GraphLookupOperation graphLookupOperation = GraphLookupOperation.builder() // + .from("employees") // + .startWith(LiteralOperators.Literal.asLiteral("hello")) // + .connectFrom("reportsTo") // + .connectTo("name") // + .as("reportingHierarchy"); + + DBObject dbObject = graphLookupOperation.toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(dbObject, is(JSON.parse("{ $graphLookup : { from: \"employees\", startWith: { $literal: \"hello\"}, " + + "connectFromField: \"reportsTo\", connectToField: \"name\", as: \"reportingHierarchy\" } }"))); + } +} diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/GroupOperationUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/GroupOperationUnitTests.java index e2bcb939dd..ad765224c0 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/GroupOperationUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/GroupOperationUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2015 the original author or authors. + * Copyright 2013-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,6 +33,7 @@ * * @author Oliver Gierke * @author Thomas Darimont + * @author Gustavo de Geus */ public class GroupOperationUnitTests { @@ -41,10 +42,7 @@ public void rejectsNullFields() { new GroupOperation((Fields) null); } - /** - * @see DATAMONGO-759 - */ - @Test + @Test // DATAMONGO-759 public void groupOperationWithNoGroupIdFieldsShouldGenerateNullAsGroupId() { GroupOperation operation = new GroupOperation(Fields.from()); @@ -56,10 +54,7 @@ public void groupOperationWithNoGroupIdFieldsShouldGenerateNullAsGroupId() { assertThat(groupClause.get(UNDERSCORE_ID), is(nullValue())); } - /** - * @see DATAMONGO-759 - */ - @Test + @Test // DATAMONGO-759 public void groupOperationWithNoGroupIdFieldsButAdditionalFieldsShouldGenerateNullAsGroupId() { GroupOperation operation = new GroupOperation(Fields.from()).count().as("cnt").last("foo").as("foo"); @@ -187,10 +182,7 @@ public void groupOperationAddToSetWithValue() { assertThat(push, is((DBObject) new BasicDBObject("$addToSet", 42))); } - /** - * @see DATAMONGO-979 - */ - @Test + @Test // DATAMONGO-979 public void shouldRenderSizeExpressionInGroup() { GroupOperation groupOperation = Aggregation // @@ -204,6 +196,28 @@ public void shouldRenderSizeExpressionInGroup() { assertThat(tagsCount.get("$first"), is((Object) new BasicDBObject("$size", Arrays.asList("$tags")))); } + @Test // DATAMONGO-1327 + public void groupOperationStdDevSampWithValue() { + + GroupOperation groupOperation = Aggregation.group("a", "b").stdDevSamp("field").as("fieldStdDevSamp"); + + DBObject groupClause = extractDbObjectFromGroupOperation(groupOperation); + DBObject push = DBObjectTestUtils.getAsDBObject(groupClause, "fieldStdDevSamp"); + + assertThat(push, is((DBObject) new BasicDBObject("$stdDevSamp", "$field"))); + } + + @Test // DATAMONGO-1327 + public void groupOperationStdDevPopWithValue() { + + GroupOperation groupOperation = Aggregation.group("a", "b").stdDevPop("field").as("fieldStdDevPop"); + + DBObject groupClause = extractDbObjectFromGroupOperation(groupOperation); + DBObject push = DBObjectTestUtils.getAsDBObject(groupClause, "fieldStdDevPop"); + + assertThat(push, is((DBObject) new BasicDBObject("$stdDevPop", "$field"))); + } + private DBObject extractDbObjectFromGroupOperation(GroupOperation groupOperation) { DBObject dbObject = groupOperation.toDBObject(Aggregation.DEFAULT_CONTEXT); DBObject groupClause = DBObjectTestUtils.getAsDBObject(dbObject, "$group"); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/IfNullOperatorUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/IfNullOperatorUnitTests.java deleted file mode 100644 index a7809644cb..0000000000 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/IfNullOperatorUnitTests.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * 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 static org.junit.Assert.*; -import static org.springframework.data.mongodb.test.util.IsBsonObject.*; - -import java.util.Arrays; - -import org.junit.Test; - -import com.mongodb.DBObject; - -/** - * Unit tests for {@link IfNullOperator}. - * - * @author Mark Paluch - * @author Christoph Strobl - */ -public class IfNullOperatorUnitTests { - - /** - * @see DATAMONGO-861 - */ - @Test(expected = IllegalArgumentException.class) - public void shouldRejectNullCondition() { - new IfNullOperator(null, ""); - } - - /** - * @see DATAMONGO-861 - */ - @Test(expected = IllegalArgumentException.class) - public void shouldRejectThenValue() { - new IfNullOperator(Fields.field("aa"), null); - } - - /** - * @see DATAMONGO-861 - */ - @Test - public void simpleIfNullShouldRenderCorrectly() { - - IfNullOperator operator = IfNullOperator.newBuilder() // - .ifNull("optional") // - .thenReplaceWith("a more sophisticated value"); - - DBObject dbObject = operator.toDbObject(Aggregation.DEFAULT_CONTEXT); - - assertThat(dbObject, - isBsonObject().containing("$ifNull", Arrays. asList("$optional", "a more sophisticated value"))); - } - - /** - * @see DATAMONGO-861 - */ - @Test - public void fieldReplacementIfNullShouldRenderCorrectly() { - - IfNullOperator operator = IfNullOperator.newBuilder() // - .ifNull(Fields.field("optional")) // - .thenReplaceWith(Fields.field("never-null")); - - DBObject dbObject = operator.toDbObject(Aggregation.DEFAULT_CONTEXT); - - assertThat(dbObject, isBsonObject().containing("$ifNull", Arrays. asList("$optional", "$never-null"))); - } -} diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/LookupOperationUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/LookupOperationUnitTests.java index 9ff31ecaab..2784f172d3 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/LookupOperationUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/LookupOperationUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 the original author or authors. + * Copyright 2016-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,42 +33,27 @@ */ public class LookupOperationUnitTests { - /** - * @see DATAMONGO-1326 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-1326 public void rejectsNullForFrom() { new LookupOperation(null, Fields.field("localField"), Fields.field("foreignField"), Fields.field("as")); } - /** - * @see DATAMONGO-1326 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-1326 public void rejectsNullLocalFieldField() { new LookupOperation(Fields.field("from"), null, Fields.field("foreignField"), Fields.field("as")); } - /** - * @see DATAMONGO-1326 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-1326 public void rejectsNullForeignField() { new LookupOperation(Fields.field("from"), Fields.field("localField"), null, Fields.field("as")); } - /** - * @see DATAMONGO-1326 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-1326 public void rejectsNullForAs() { new LookupOperation(Fields.field("from"), Fields.field("localField"), Fields.field("foreignField"), null); } - /** - * @see DATAMONGO-1326 - */ - @Test + @Test // DATAMONGO-1326 public void lookupOperationWithValues() { LookupOperation lookupOperation = Aggregation.lookup("a", "b", "c", "d"); @@ -82,10 +67,7 @@ public void lookupOperationWithValues() { .containing("as", "d")); } - /** - * @see DATAMONGO-1326 - */ - @Test + @Test // DATAMONGO-1326 public void lookupOperationExposesAsField() { LookupOperation lookupOperation = Aggregation.lookup("a", "b", "c", "d"); @@ -102,42 +84,27 @@ private DBObject extractDbObjectFromLookupOperation(LookupOperation lookupOperat return lookupClause; } - /** - * @see DATAMONGO-1326 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-1326 public void builderRejectsNullFromField() { LookupOperation.newLookup().from(null); } - /** - * @see DATAMONGO-1326 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-1326 public void builderRejectsNullLocalField() { LookupOperation.newLookup().from("a").localField(null); } - /** - * @see DATAMONGO-1326 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-1326 public void builderRejectsNullForeignField() { LookupOperation.newLookup().from("a").localField("b").foreignField(null); } - /** - * @see DATAMONGO-1326 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-1326 public void builderRejectsNullAsField() { LookupOperation.newLookup().from("a").localField("b").foreignField("c").as(null); } - /** - * @see DATAMONGO-1326 - */ - @Test + @Test // DATAMONGO-1326 public void lookupBuilderBuildsCorrectClause() { LookupOperation lookupOperation = LookupOperation.newLookup().from("a").localField("b").foreignField("c").as("d"); @@ -151,10 +118,7 @@ public void lookupBuilderBuildsCorrectClause() { .containing("as", "d")); } - /** - * @see DATAMONGO-1326 - */ - @Test + @Test // DATAMONGO-1326 public void lookupBuilderExposesFields() { LookupOperation lookupOperation = LookupOperation.newLookup().from("a").localField("b").foreignField("c").as("d"); 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 index 0d5775d359..485eea0a27 100644 --- 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 @@ -1,5 +1,5 @@ /* - * Copyright 2016 the original author or authors. + * Copyright 2016-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,10 +24,7 @@ */ public class OutOperationUnitTest { - /** - * @see DATAMONGO-1418 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-1418 public void shouldCheckNPEInCreation() { new OutOperation(null); } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ProjectionOperationUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ProjectionOperationUnitTests.java index bc1a06e51f..668f1feb1b 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ProjectionOperationUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ProjectionOperationUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,20 +16,32 @@ package org.springframework.data.mongodb.core.aggregation; import static org.hamcrest.Matchers.*; +import static org.hamcrest.core.Is.is; import static org.junit.Assert.*; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.*; import static org.springframework.data.mongodb.core.aggregation.AggregationFunctionExpressions.*; import static org.springframework.data.mongodb.core.aggregation.Fields.*; +import static org.springframework.data.mongodb.core.aggregation.VariableOperators.Let.ExpressionVariable.*; +import static org.springframework.data.mongodb.test.util.IsBsonObject.*; import static org.springframework.data.mongodb.util.DBObjectUtils.*; import java.util.Arrays; import java.util.List; +import org.hamcrest.Matchers; import org.junit.Test; +import org.springframework.data.domain.Range; import org.springframework.data.mongodb.core.DBObjectTestUtils; +import org.springframework.data.mongodb.core.aggregation.ArrayOperators.Reduce.PropertyExpression; +import org.springframework.data.mongodb.core.aggregation.ArrayOperators.Reduce.Variable; +import org.springframework.data.mongodb.core.aggregation.ConditionalOperators.Switch.CaseOperator; import org.springframework.data.mongodb.core.aggregation.ProjectionOperation.ProjectionOperationBuilder; +import org.springframework.data.mongodb.core.aggregation.VariableOperators.Let.ExpressionVariable; import com.mongodb.BasicDBObject; +import com.mongodb.BasicDBObjectBuilder; import com.mongodb.DBObject; +import com.mongodb.util.JSON; /** * Unit tests for {@link ProjectionOperation}. @@ -37,6 +49,7 @@ * @author Oliver Gierke * @author Thomas Darimont * @author Christoph Strobl + * @author Mark Paluch */ public class ProjectionOperationUnitTests { @@ -47,12 +60,12 @@ public class ProjectionOperationUnitTests { static final String DIVIDE = "$divide"; static final String PROJECT = "$project"; - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-586 public void rejectsNullFields() { new ProjectionOperation(null); } - @Test + @Test // DATAMONGO-586 public void declaresBackReferenceCorrectly() { ProjectionOperation operation = new ProjectionOperation(); @@ -63,7 +76,7 @@ public void declaresBackReferenceCorrectly() { assertThat(projectClause.get("prop"), is((Object) Fields.UNDERSCORE_ID_REF)); } - @Test + @Test // DATAMONGO-586 public void alwaysUsesExplicitReference() { ProjectionOperation operation = new ProjectionOperation(Fields.fields("foo").and("bar", "foobar")); @@ -75,7 +88,7 @@ public void alwaysUsesExplicitReference() { assertThat(projectClause.get("bar"), is((Object) "$foobar")); } - @Test + @Test // DATAMONGO-586 public void aliasesSimpleFieldProjection() { ProjectionOperation operation = new ProjectionOperation(); @@ -86,7 +99,7 @@ public void aliasesSimpleFieldProjection() { assertThat(projectClause.get("bar"), is((Object) "$foo")); } - @Test + @Test // DATAMONGO-586 public void aliasesArithmeticProjection() { ProjectionOperation operation = new ProjectionOperation(); @@ -101,6 +114,7 @@ public void aliasesArithmeticProjection() { assertThat(addClause.get(1), is((Object) 41)); } + @Test // DATAMONGO-586 public void arithmenticProjectionOperationWithoutAlias() { String fieldName = "a"; @@ -113,7 +127,7 @@ public void arithmenticProjectionOperationWithoutAlias() { assertThat(oper.get(ADD), is((Object) Arrays. asList("$a", 1))); } - @Test + @Test // DATAMONGO-586 public void arithmenticProjectionOperationPlus() { String fieldName = "a"; @@ -127,7 +141,7 @@ public void arithmenticProjectionOperationPlus() { assertThat(oper.get(ADD), is((Object) Arrays. asList("$a", 1))); } - @Test + @Test // DATAMONGO-586 public void arithmenticProjectionOperationMinus() { String fieldName = "a"; @@ -141,7 +155,7 @@ public void arithmenticProjectionOperationMinus() { assertThat(oper.get(SUBTRACT), is((Object) Arrays. asList("$a", 1))); } - @Test + @Test // DATAMONGO-586 public void arithmenticProjectionOperationMultiply() { String fieldName = "a"; @@ -155,7 +169,7 @@ public void arithmenticProjectionOperationMultiply() { assertThat(oper.get(MULTIPLY), is((Object) Arrays. asList("$a", 1))); } - @Test + @Test // DATAMONGO-586 public void arithmenticProjectionOperationDivide() { String fieldName = "a"; @@ -169,13 +183,13 @@ public void arithmenticProjectionOperationDivide() { assertThat(oper.get(DIVIDE), is((Object) Arrays. asList("$a", 1))); } - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-586 public void arithmenticProjectionOperationDivideByZeroException() { new ProjectionOperation().and("a").divide(0); } - @Test + @Test // DATAMONGO-586 public void arithmenticProjectionOperationMod() { String fieldName = "a"; @@ -189,19 +203,13 @@ public void arithmenticProjectionOperationMod() { assertThat(oper.get(MOD), is((Object) Arrays. asList("$a", 3))); } - /** - * @see DATAMONGO-758 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-758 public void excludeShouldThrowExceptionForFieldsOtherThanUnderscoreId() { new ProjectionOperation().andExclude("foo"); } - /** - * @see DATAMONGO-758 - */ - @Test + @Test // DATAMONGO-758 public void excludeShouldAllowExclusionOfUnderscoreId() { ProjectionOperation projectionOp = new ProjectionOperation().andExclude(Fields.UNDERSCORE_ID); @@ -210,10 +218,7 @@ public void excludeShouldAllowExclusionOfUnderscoreId() { assertThat((Integer) projectClause.get(Fields.UNDERSCORE_ID), is(0)); } - /** - * @see DATAMONGO-757 - */ - @Test + @Test // DATAMONGO-757 public void usesImplictAndExplicitFieldAliasAndIncludeExclude() { ProjectionOperation operation = Aggregation.project("foo").and("foobar").as("bar").andInclude("inc1", "inc2") @@ -235,10 +240,7 @@ public void arithmenticProjectionOperationModByZeroException() { new ProjectionOperation().and("a").mod(0); } - /** - * @see DATAMONGO-769 - */ - @Test + @Test // DATAMONGO-769 public void allowArithmeticOperationsWithFieldReferences() { ProjectionOperation operation = Aggregation.project() // @@ -263,10 +265,7 @@ public void allowArithmeticOperationsWithFieldReferences() { is(new BasicDBObject("$mod", dbList("$foo", "$bar")))); } - /** - * @see DATAMONGO-774 - */ - @Test + @Test // DATAMONGO-774 public void projectionExpressions() { ProjectionOperation operation = Aggregation.project() // @@ -274,15 +273,11 @@ public void projectionExpressions() { .and("foo").as("bar"); // DBObject dbObject = operation.toDBObject(Aggregation.DEFAULT_CONTEXT); - assertThat( - dbObject.toString(), - is("{ \"$project\" : { \"grossSalesPrice\" : { \"$multiply\" : [ { \"$add\" : [ \"$netPrice\" , \"$surCharge\"]} , \"$taxrate\" , 2]} , \"bar\" : \"$foo\"}}")); + assertThat(dbObject.toString(), is( + "{ \"$project\" : { \"grossSalesPrice\" : { \"$multiply\" : [ { \"$add\" : [ \"$netPrice\" , \"$surCharge\"]} , \"$taxrate\" , 2]} , \"bar\" : \"$foo\"}}")); } - /** - * @see DATAMONGO-975 - */ - @Test + @Test // DATAMONGO-975 public void shouldRenderDateTimeFragmentExtractionsForSimpleFieldProjectionsCorrectly() { ProjectionOperation operation = Aggregation.project() // @@ -315,10 +310,7 @@ public void shouldRenderDateTimeFragmentExtractionsForSimpleFieldProjectionsCorr assertThat(projected.get("dayOfWeek"), is((Object) new BasicDBObject("$dayOfWeek", Arrays.asList("$date")))); } - /** - * @see DATAMONGO-975 - */ - @Test + @Test // DATAMONGO-975 public void shouldRenderDateTimeFragmentExtractionsForExpressionProjectionsCorrectly() throws Exception { ProjectionOperation operation = Aggregation.project() // @@ -331,16 +323,11 @@ public void shouldRenderDateTimeFragmentExtractionsForExpressionProjectionsCorre assertThat(dbObject, is(notNullValue())); DBObject projected = exctractOperation("$project", dbObject); - assertThat( - projected.get("dayOfYearPlus1Day"), - is((Object) new BasicDBObject("$dayOfYear", Arrays.asList(new BasicDBObject("$add", Arrays. asList( - "$date", 86400000)))))); + assertThat(projected.get("dayOfYearPlus1Day"), is((Object) new BasicDBObject("$dayOfYear", + Arrays.asList(new BasicDBObject("$add", Arrays. asList("$date", 86400000)))))); } - /** - * @see DATAMONGO-979 - */ - @Test + @Test // DATAMONGO-979 public void shouldRenderSizeExpressionInProjection() { ProjectionOperation operation = Aggregation // @@ -355,10 +342,7 @@ public void shouldRenderSizeExpressionInProjection() { assertThat(projected.get("tags_count"), is((Object) new BasicDBObject("$size", Arrays.asList("$tags")))); } - /** - * @see DATAMONGO-979 - */ - @Test + @Test // DATAMONGO-979 public void shouldRenderGenericSizeExpressionInProjection() { ProjectionOperation operation = Aggregation // @@ -372,10 +356,7 @@ public void shouldRenderGenericSizeExpressionInProjection() { assertThat(projected.get("tags_count"), is((Object) new BasicDBObject("$size", Arrays.asList("$tags")))); } - /** - * @see DATAMONGO-1457 - */ - @Test + @Test // DATAMONGO-1457 public void shouldRenderSliceCorrectly() throws Exception { ProjectionOperation operation = Aggregation.project().and("field").slice(10).as("renamed"); @@ -387,10 +368,7 @@ public void shouldRenderSliceCorrectly() throws Exception { is((Object) new BasicDBObject("$slice", Arrays. asList("$field", 10)))); } - /** - * @see DATAMONGO-1457 - */ - @Test + @Test // DATAMONGO-1457 public void shouldRenderSliceWithPositionCorrectly() throws Exception { ProjectionOperation operation = Aggregation.project().and("field").slice(10, 5).as("renamed"); @@ -402,7 +380,1298 @@ public void shouldRenderSliceWithPositionCorrectly() throws Exception { is((Object) new BasicDBObject("$slice", Arrays. asList("$field", 5, 10)))); } - private static DBObject exctractOperation(String field, DBObject fromProjectClause) { - return (DBObject) fromProjectClause.get(field); + @Test // DATAMONGO-784 + public void shouldRenderCmpCorrectly() { + + ProjectionOperation operation = Aggregation.project().and("field").cmp(10).as("cmp10"); + + assertThat(operation.toDBObject(Aggregation.DEFAULT_CONTEXT), + isBsonObject().containing("$project.cmp10.$cmp.[0]", "$field").containing("$project.cmp10.$cmp.[1]", 10)); + } + + @Test // DATAMONGO-784 + public void shouldRenderEqCorrectly() { + + ProjectionOperation operation = Aggregation.project().and("field").eq(10).as("eq10"); + + assertThat(operation.toDBObject(Aggregation.DEFAULT_CONTEXT), + isBsonObject().containing("$project.eq10.$eq.[0]", "$field").containing("$project.eq10.$eq.[1]", 10)); + } + + @Test // DATAMONGO-784 + public void shouldRenderGtCorrectly() { + + ProjectionOperation operation = Aggregation.project().and("field").gt(10).as("gt10"); + + assertThat(operation.toDBObject(Aggregation.DEFAULT_CONTEXT), + isBsonObject().containing("$project.gt10.$gt.[0]", "$field").containing("$project.gt10.$gt.[1]", 10)); + } + + @Test // DATAMONGO-784 + public void shouldRenderGteCorrectly() { + + ProjectionOperation operation = Aggregation.project().and("field").gte(10).as("gte10"); + + assertThat(operation.toDBObject(Aggregation.DEFAULT_CONTEXT), + isBsonObject().containing("$project.gte10.$gte.[0]", "$field").containing("$project.gte10.$gte.[1]", 10)); + } + + @Test // DATAMONGO-784 + public void shouldRenderLtCorrectly() { + + ProjectionOperation operation = Aggregation.project().and("field").lt(10).as("lt10"); + + assertThat(operation.toDBObject(Aggregation.DEFAULT_CONTEXT), + isBsonObject().containing("$project.lt10.$lt.[0]", "$field").containing("$project.lt10.$lt.[1]", 10)); + } + + @Test // DATAMONGO-784 + public void shouldRenderLteCorrectly() { + + ProjectionOperation operation = Aggregation.project().and("field").lte(10).as("lte10"); + + assertThat(operation.toDBObject(Aggregation.DEFAULT_CONTEXT), + isBsonObject().containing("$project.lte10.$lte.[0]", "$field").containing("$project.lte10.$lte.[1]", 10)); + } + + @Test // DATAMONGO-784 + public void shouldRenderNeCorrectly() { + + ProjectionOperation operation = Aggregation.project().and("field").ne(10).as("ne10"); + + assertThat(operation.toDBObject(Aggregation.DEFAULT_CONTEXT), + isBsonObject().containing("$project.ne10.$ne.[0]", "$field").containing("$project.ne10.$ne.[1]", 10)); + } + + @Test // DATAMONGO-1536 + public void shouldRenderSetEquals() { + + DBObject agg = project("A", "B").and("A").equalsArrays("B").as("sameElements") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { A: 1, B: 1, sameElements: { $setEquals: [ \"$A\", \"$B\" ] }}}"))); + } + + @Test // DATAMONGO-1536 + public void shouldRenderSetEqualsAggregationExpresssion() { + + DBObject agg = project("A", "B").and(SetOperators.arrayAsSet("A").isEqualTo("B")).as("sameElements") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { A: 1, B: 1, sameElements: { $setEquals: [ \"$A\", \"$B\" ] }}}"))); + } + + @Test // DATAMONGO-1536 + public void shouldRenderSetIntersection() { + + DBObject agg = project("A", "B").and("A").intersectsArrays("B").as("commonToBoth") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, + is(JSON.parse("{ $project: { A: 1, B: 1, commonToBoth: { $setIntersection: [ \"$A\", \"$B\" ] }}}"))); + } + + @Test // DATAMONGO-1536 + public void shouldRenderSetIntersectionAggregationExpresssion() { + + DBObject agg = project("A", "B").and(SetOperators.arrayAsSet("A").intersects("B")).as("commonToBoth") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, + is(JSON.parse("{ $project: { A: 1, B: 1, commonToBoth: { $setIntersection: [ \"$A\", \"$B\" ] }}}"))); + } + + @Test // DATAMONGO-1536 + public void shouldRenderSetUnion() { + + DBObject agg = project("A", "B").and("A").unionArrays("B").as("allValues").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { A: 1, B: 1, allValues: { $setUnion: [ \"$A\", \"$B\" ] }}}"))); + } + + @Test // DATAMONGO-1536 + public void shouldRenderSetUnionAggregationExpresssion() { + + DBObject agg = project("A", "B").and(SetOperators.arrayAsSet("A").union("B")).as("allValues") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { A: 1, B: 1, allValues: { $setUnion: [ \"$A\", \"$B\" ] }}}"))); + } + + @Test // DATAMONGO-1536 + public void shouldRenderSetDifference() { + + DBObject agg = project("A", "B").and("B").differenceToArray("A").as("inBOnly") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { A: 1, B: 1, inBOnly: { $setDifference: [ \"$B\", \"$A\" ] }}}"))); + } + + @Test // DATAMONGO-1536 + public void shouldRenderSetDifferenceAggregationExpresssion() { + + DBObject agg = project("A", "B").and(SetOperators.arrayAsSet("B").differenceTo("A")).as("inBOnly") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { A: 1, B: 1, inBOnly: { $setDifference: [ \"$B\", \"$A\" ] }}}"))); + } + + @Test // DATAMONGO-1536 + public void shouldRenderSetIsSubset() { + + DBObject agg = project("A", "B").and("A").subsetOfArray("B").as("aIsSubsetOfB") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { A: 1, B: 1, aIsSubsetOfB: { $setIsSubset: [ \"$A\", \"$B\" ] }}}"))); + } + + @Test // DATAMONGO-1536 + public void shouldRenderSetIsSubsetAggregationExpresssion() { + + DBObject agg = project("A", "B").and(SetOperators.arrayAsSet("A").isSubsetOf("B")).as("aIsSubsetOfB") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { A: 1, B: 1, aIsSubsetOfB: { $setIsSubset: [ \"$A\", \"$B\" ] }}}"))); + } + + @Test // DATAMONGO-1536 + public void shouldRenderAnyElementTrue() { + + DBObject agg = project("responses").and("responses").anyElementInArrayTrue().as("isAnyTrue") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { responses: 1, isAnyTrue: { $anyElementTrue: [ \"$responses\" ] }}}"))); + } + + @Test // DATAMONGO-1536 + public void shouldRenderAnyElementTrueAggregationExpresssion() { + + DBObject agg = project("responses").and(SetOperators.arrayAsSet("responses").anyElementTrue()).as("isAnyTrue") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { responses: 1, isAnyTrue: { $anyElementTrue: [ \"$responses\" ] }}}"))); + } + + @Test // DATAMONGO-1536 + public void shouldRenderAllElementsTrue() { + + DBObject agg = project("responses").and("responses").allElementsInArrayTrue().as("isAllTrue") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, + is(JSON.parse("{ $project: { responses: 1, isAllTrue: { $allElementsTrue: [ \"$responses\" ] }}}"))); + } + + @Test // DATAMONGO-1536 + public void shouldRenderAllElementsTrueAggregationExpresssion() { + + DBObject agg = project("responses").and(SetOperators.arrayAsSet("responses").allElementsTrue()).as("isAllTrue") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, + is(JSON.parse("{ $project: { responses: 1, isAllTrue: { $allElementsTrue: [ \"$responses\" ] }}}"))); + } + + @Test // DATAMONGO-1536 + public void shouldRenderAbs() { + + DBObject agg = project().and("anyNumber").absoluteValue().as("absoluteValue") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { absoluteValue : { $abs: \"$anyNumber\" }}}"))); + } + + @Test // DATAMONGO-1536 + public void shouldRenderAbsAggregationExpresssion() { + + DBObject agg = project() + .and( + ArithmeticOperators.valueOf(AggregationFunctionExpressions.SUBTRACT.of(field("start"), field("end"))).abs()) + .as("delta").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { delta: { $abs: { $subtract: [ \"$start\", \"$end\" ] } } }}"))); + } + + @Test // DATAMONGO-1536 + public void shouldRenderAddAggregationExpresssion() { + + DBObject agg = project().and(ArithmeticOperators.valueOf("price").add("fee")).as("total") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse(" { $project: { total: { $add: [ \"$price\", \"$fee\" ] } } }"))); + } + + @Test // DATAMONGO-1536 + public void shouldRenderCeil() { + + DBObject agg = project().and("anyNumber").ceil().as("ceilValue").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { ceilValue : { $ceil: \"$anyNumber\" }}}"))); + } + + @Test // DATAMONGO-1536 + public void shouldRenderCeilAggregationExpresssion() { + + DBObject agg = project().and( + ArithmeticOperators.valueOf(AggregationFunctionExpressions.SUBTRACT.of(field("start"), field("end"))).ceil()) + .as("delta").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { delta: { $ceil: { $subtract: [ \"$start\", \"$end\" ] } } }}"))); + } + + @Test // DATAMONGO-1536 + public void shouldRenderDivide() { + + DBObject agg = project().and("value") + .divide(AggregationFunctionExpressions.SUBTRACT.of(field("start"), field("end"))).as("result") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, + is(JSON.parse("{ $project: { result: { $divide: [ \"$value\", { $subtract: [ \"$start\", \"$end\" ] }] } }}"))); + } + + @Test // DATAMONGO-1536 + public void shouldRenderDivideAggregationExpresssion() { + + DBObject agg = project() + .and(ArithmeticOperators.valueOf("anyNumber") + .divideBy(AggregationFunctionExpressions.SUBTRACT.of(field("start"), field("end")))) + .as("result").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON + .parse("{ $project: { result: { $divide: [ \"$anyNumber\", { $subtract: [ \"$start\", \"$end\" ] }] } }}"))); + } + + @Test // DATAMONGO-1536 + public void shouldRenderExp() { + + DBObject agg = project().and("value").exp().as("result").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { result: { $exp: \"$value\" } }}"))); + } + + @Test // DATAMONGO-1536 + public void shouldRenderExpAggregationExpresssion() { + + DBObject agg = project() + .and( + ArithmeticOperators.valueOf(AggregationFunctionExpressions.SUBTRACT.of(field("start"), field("end"))).exp()) + .as("result").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { result: { $exp: { $subtract: [ \"$start\", \"$end\" ] } } }}"))); + } + + @Test // DATAMONGO-1536 + public void shouldRenderFloor() { + + DBObject agg = project().and("value").floor().as("result").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { result: { $floor: \"$value\" } }}"))); + } + + @Test // DATAMONGO-1536 + public void shouldRenderFloorAggregationExpresssion() { + + DBObject agg = project().and( + ArithmeticOperators.valueOf(AggregationFunctionExpressions.SUBTRACT.of(field("start"), field("end"))).floor()) + .as("result").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { result: { $floor: { $subtract: [ \"$start\", \"$end\" ] } } }}"))); + } + + @Test // DATAMONGO-1536 + public void shouldRenderLn() { + + DBObject agg = project().and("value").ln().as("result").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { result: { $ln: \"$value\"} }}"))); + } + + @Test // DATAMONGO-1536 + public void shouldRenderLnAggregationExpresssion() { + + DBObject agg = project() + .and(ArithmeticOperators.valueOf(AggregationFunctionExpressions.SUBTRACT.of(field("start"), field("end"))).ln()) + .as("result").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { result: { $ln: { $subtract: [ \"$start\", \"$end\" ] } } }}"))); + } + + @Test // DATAMONGO-1536 + public void shouldRenderLog() { + + DBObject agg = project().and("value").log(2).as("result").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { result: { $log: [ \"$value\", 2] } }}"))); + } + + @Test // DATAMONGO-1536 + public void shouldRenderLogAggregationExpresssion() { + + DBObject agg = project().and( + ArithmeticOperators.valueOf(AggregationFunctionExpressions.SUBTRACT.of(field("start"), field("end"))).log(2)) + .as("result").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { result: { $log: [ { $subtract: [ \"$start\", \"$end\" ] }, 2] } }}"))); + } + + @Test // DATAMONGO-1536 + public void shouldRenderLog10() { + + DBObject agg = project().and("value").log10().as("result").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { result: { $log10: \"$value\" } }}"))); + } + + @Test // DATAMONGO-1536 + public void shouldRenderLog10AggregationExpresssion() { + + DBObject agg = project().and( + ArithmeticOperators.valueOf(AggregationFunctionExpressions.SUBTRACT.of(field("start"), field("end"))).log10()) + .as("result").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { result: { $log10: { $subtract: [ \"$start\", \"$end\" ] } } }}"))); + } + + @Test // DATAMONGO-1536 + public void shouldRenderMod() { + + DBObject agg = project().and("value").mod(AggregationFunctionExpressions.SUBTRACT.of(field("start"), field("end"))) + .as("result").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, + is(JSON.parse("{ $project: { result: { $mod: [\"$value\", { $subtract: [ \"$start\", \"$end\" ] }] } }}"))); + } + + @Test // DATAMONGO-1536 + public void shouldRenderModAggregationExpresssion() { + + DBObject agg = project().and( + ArithmeticOperators.valueOf(AggregationFunctionExpressions.SUBTRACT.of(field("start"), field("end"))).mod(2)) + .as("result").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { result: { $mod: [{ $subtract: [ \"$start\", \"$end\" ] }, 2] } }}"))); + } + + @Test // DATAMONGO-1536 + public void shouldRenderMultiply() { + + DBObject agg = project().and("value") + .multiply(AggregationFunctionExpressions.SUBTRACT.of(field("start"), field("end"))).as("result") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is( + JSON.parse("{ $project: { result: { $multiply: [\"$value\", { $subtract: [ \"$start\", \"$end\" ] }] } }}"))); + } + + @Test // DATAMONGO-1536 + public void shouldRenderMultiplyAggregationExpresssion() { + + DBObject agg = project() + .and(ArithmeticOperators.valueOf(AggregationFunctionExpressions.SUBTRACT.of(field("start"), field("end"))) + .multiplyBy(2).multiplyBy("refToAnotherNumber")) + .as("result").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse( + "{ $project: { result: { $multiply: [{ $subtract: [ \"$start\", \"$end\" ] }, 2, \"$refToAnotherNumber\"] } }}"))); + } + + @Test // DATAMONGO-1536 + public void shouldRenderPow() { + + DBObject agg = project().and("value").pow(2).as("result").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { result: { $pow: [\"$value\", 2] } }}"))); + } + + @Test // DATAMONGO-1536 + public void shouldRenderPowAggregationExpresssion() { + + DBObject agg = project().and( + ArithmeticOperators.valueOf(AggregationFunctionExpressions.SUBTRACT.of(field("start"), field("end"))).pow(2)) + .as("result").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { result: { $pow: [{ $subtract: [ \"$start\", \"$end\" ] }, 2] } }}"))); + } + + @Test // DATAMONGO-1536 + public void shouldRenderSqrt() { + + DBObject agg = project().and("value").sqrt().as("result").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { result: { $sqrt: \"$value\" } }}"))); + } + + @Test // DATAMONGO-1536 + public void shouldRenderSqrtAggregationExpresssion() { + + DBObject agg = project().and( + ArithmeticOperators.valueOf(AggregationFunctionExpressions.SUBTRACT.of(field("start"), field("end"))).sqrt()) + .as("result").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { result: { $sqrt: { $subtract: [ \"$start\", \"$end\" ] } } }}"))); + } + + @Test // DATAMONGO-1536 + public void shouldRenderSubtract() { + + DBObject agg = project().and("numericField").minus(AggregationFunctionExpressions.SIZE.of(field("someArray"))) + .as("result").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, + is(JSON.parse("{ $project: { result: { $subtract: [ \"$numericField\", { $size : [\"$someArray\"]}] } } }"))); + } + + @Test // DATAMONGO-1536 + public void shouldRenderSubtractAggregationExpresssion() { + + DBObject agg = project() + .and(ArithmeticOperators.valueOf("numericField") + .subtract(AggregationFunctionExpressions.SIZE.of(field("someArray")))) + .as("result").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, + is(JSON.parse("{ $project: { result: { $subtract: [ \"$numericField\", { $size : [\"$someArray\"]}] } } }"))); + } + + @Test // DATAMONGO-1536 + public void shouldRenderTrunc() { + + DBObject agg = project().and("value").trunc().as("result").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { result : { $trunc: \"$value\" }}}"))); + } + + @Test // DATAMONGO-1536 + public void shouldRenderTruncAggregationExpresssion() { + + DBObject agg = project().and( + ArithmeticOperators.valueOf(AggregationFunctionExpressions.SUBTRACT.of(field("start"), field("end"))).trunc()) + .as("result").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { result: { $trunc: { $subtract: [ \"$start\", \"$end\" ] } } }}"))); + } + + @Test // DATAMONGO-1536 + public void shouldRenderConcat() { + + DBObject agg = project().and("item").concat(" - ", field("description")).as("itemDescription") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, + is(JSON.parse("{ $project: { itemDescription: { $concat: [ \"$item\", \" - \", \"$description\" ] } } }"))); + + } + + @Test // DATAMONGO-1536 + public void shouldRenderConcatAggregationExpression() { + + DBObject agg = project().and(StringOperators.valueOf("item").concat(" - ").concatValueOf("description")) + .as("itemDescription").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, + is(JSON.parse("{ $project: { itemDescription: { $concat: [ \"$item\", \" - \", \"$description\" ] } } }"))); + + } + + @Test // DATAMONGO-1536 + public void shouldRenderSubstr() { + + DBObject agg = project().and("quarter").substring(0, 2).as("yearSubstring").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { yearSubstring: { $substr: [ \"$quarter\", 0, 2 ] } } }"))); + } + + @Test // DATAMONGO-1536 + public void shouldRenderSubstrAggregationExpression() { + + DBObject agg = project().and(StringOperators.valueOf("quarter").substring(0, 2)).as("yearSubstring") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { yearSubstring: { $substr: [ \"$quarter\", 0, 2 ] } } }"))); + } + + @Test // DATAMONGO-1536 + public void shouldRenderToLower() { + + DBObject agg = project().and("item").toLower().as("item").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { item: { $toLower: \"$item\" } } }"))); + } + + @Test // DATAMONGO-1536 + public void shouldRenderToLowerAggregationExpression() { + + DBObject agg = project().and(StringOperators.valueOf("item").toLower()).as("item") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { item: { $toLower: \"$item\" } } }"))); + } + + @Test // DATAMONGO-1536 + public void shouldRenderToUpper() { + + DBObject agg = project().and("item").toUpper().as("item").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { item: { $toUpper: \"$item\" } } }"))); + } + + @Test // DATAMONGO-1536 + public void shouldRenderToUpperAggregationExpression() { + + DBObject agg = project().and(StringOperators.valueOf("item").toUpper()).as("item") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { item: { $toUpper: \"$item\" } } }"))); + } + + @Test // DATAMONGO-1536 + public void shouldRenderStrCaseCmp() { + + DBObject agg = project().and("quarter").strCaseCmp("13q4").as("comparisonResult") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { comparisonResult: { $strcasecmp: [ \"$quarter\", \"13q4\" ] } } }"))); + } + + @Test // DATAMONGO-1536 + public void shouldRenderStrCaseCmpAggregationExpression() { + + DBObject agg = project().and(StringOperators.valueOf("quarter").strCaseCmp("13q4")).as("comparisonResult") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { comparisonResult: { $strcasecmp: [ \"$quarter\", \"13q4\" ] } } }"))); + } + + @Test // DATAMONGO-1536 + public void shouldRenderArrayElementAt() { + + DBObject agg = project().and("favorites").arrayElementAt(0).as("first").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { first: { $arrayElemAt: [ \"$favorites\", 0 ] } } }"))); + } + + @Test // DATAMONGO-1536 + public void shouldRenderArrayElementAtAggregationExpression() { + + DBObject agg = project().and(ArrayOperators.arrayOf("favorites").elementAt(0)).as("first") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { first: { $arrayElemAt: [ \"$favorites\", 0 ] } } }"))); } + + @Test // DATAMONGO-1536 + public void shouldRenderConcatArrays() { + + DBObject agg = project().and("instock").concatArrays("ordered").as("items").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { items: { $concatArrays: [ \"$instock\", \"$ordered\" ] } } }"))); + } + + @Test // DATAMONGO-1536 + public void shouldRenderConcatArraysAggregationExpression() { + + DBObject agg = project().and(ArrayOperators.arrayOf("instock").concat("ordered")).as("items") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { items: { $concatArrays: [ \"$instock\", \"$ordered\" ] } } }"))); + } + + @Test // DATAMONGO-1536 + public void shouldRenderIsArray() { + + DBObject agg = project().and("instock").isArray().as("isAnArray").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { isAnArray: { $isArray: \"$instock\" } } }"))); + } + + @Test // DATAMONGO-1536 + public void shouldRenderIsArrayAggregationExpression() { + + DBObject agg = project().and(ArrayOperators.arrayOf("instock").isArray()).as("isAnArray") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { isAnArray: { $isArray: \"$instock\" } } }"))); + } + + @Test // DATAMONGO-1536 + public void shouldRenderSizeAggregationExpression() { + + DBObject agg = project().and(ArrayOperators.arrayOf("instock").length()).as("arraySize") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { arraySize: { $size: \"$instock\" } } }"))); + } + + @Test // DATAMONGO-1536 + public void shouldRenderSliceAggregationExpression() { + + DBObject agg = project().and(ArrayOperators.arrayOf("favorites").slice().itemCount(3)).as("threeFavorites") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { threeFavorites: { $slice: [ \"$favorites\", 3 ] } } }"))); + } + + @Test // DATAMONGO-1536 + public void shouldRenderSliceWithPositionAggregationExpression() { + + DBObject agg = project().and(ArrayOperators.arrayOf("favorites").slice().offset(2).itemCount(3)) + .as("threeFavorites").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { threeFavorites: { $slice: [ \"$favorites\", 2, 3 ] } } }"))); + } + + @Test // DATAMONGO-1536 + public void shouldRenderLiteral() { + + DBObject agg = project().and("$1").asLiteral().as("literalOnly").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { literalOnly: { $literal: \"$1\"} } }"))); + } + + @Test // DATAMONGO-1536 + public void shouldRenderLiteralAggregationExpression() { + + DBObject agg = project().and(LiteralOperators.valueOf("$1").asLiteral()).as("literalOnly") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { literalOnly: { $literal: \"$1\"} } }"))); + } + + @Test // DATAMONGO-1536 + public void shouldRenderDayOfYearAggregationExpression() { + + DBObject agg = project().and(DateOperators.dateOf("date").dayOfYear()).as("dayOfYear") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { dayOfYear: { $dayOfYear: \"$date\" } } }"))); + } + + @Test // DATAMONGO-1536 + public void shouldRenderDayOfMonthAggregationExpression() { + + DBObject agg = project().and(DateOperators.dateOf("date").dayOfMonth()).as("day") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { day: { $dayOfMonth: \"$date\" }} }"))); + } + + @Test // DATAMONGO-1536 + public void shouldRenderDayOfWeekAggregationExpression() { + + DBObject agg = project().and(DateOperators.dateOf("date").dayOfWeek()).as("dayOfWeek") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { dayOfWeek: { $dayOfWeek: \"$date\" } } }"))); + } + + @Test // DATAMONGO-1536 + public void shouldRenderYearAggregationExpression() { + + DBObject agg = project().and(DateOperators.dateOf("date").year()).as("year") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { year: { $year: \"$date\" } } }"))); + } + + @Test // DATAMONGO-1536 + public void shouldRenderMonthAggregationExpression() { + + DBObject agg = project().and(DateOperators.dateOf("date").month()).as("month") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { month: { $month: \"$date\" } } }"))); + } + + @Test // DATAMONGO-1536 + public void shouldRenderWeekAggregationExpression() { + + DBObject agg = project().and(DateOperators.dateOf("date").week()).as("week") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { week: { $week: \"$date\" } } }"))); + } + + @Test // DATAMONGO-1536 + public void shouldRenderHourAggregationExpression() { + + DBObject agg = project().and(DateOperators.dateOf("date").hour()).as("hour") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { hour: { $hour: \"$date\" } } }"))); + } + + @Test // DATAMONGO-1536 + public void shouldRenderMinuteAggregationExpression() { + + DBObject agg = project().and(DateOperators.dateOf("date").minute()).as("minute") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { minute: { $minute: \"$date\" } } }"))); + } + + @Test // DATAMONGO-1536 + public void shouldRenderSecondAggregationExpression() { + + DBObject agg = project().and(DateOperators.dateOf("date").second()).as("second") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { second: { $second: \"$date\" } } }"))); + } + + @Test // DATAMONGO-1536 + public void shouldRenderMillisecondAggregationExpression() { + + DBObject agg = project().and(DateOperators.dateOf("date").millisecond()).as("msec") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { msec: { $millisecond: \"$date\" } } }"))); + } + + @Test // DATAMONGO-1536 + public void shouldRenderDateToString() { + + DBObject agg = project().and("date").dateAsFormattedString("%H:%M:%S:%L").as("time") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, + is(JSON.parse("{ $project: { time: { $dateToString: { format: \"%H:%M:%S:%L\", date: \"$date\" } } } }"))); + } + + @Test // DATAMONGO-1536 + public void shouldRenderDateToStringAggregationExpression() { + + DBObject agg = project().and(DateOperators.dateOf("date").toString("%H:%M:%S:%L")).as("time") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, + is(JSON.parse("{ $project: { time: { $dateToString: { format: \"%H:%M:%S:%L\", date: \"$date\" } } } }"))); + } + + @Test // DATAMONGO-1536 + public void shouldRenderSumAggregationExpression() { + + DBObject agg = project().and(ArithmeticOperators.valueOf("quizzes").sum()).as("quizTotal") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { quizTotal: { $sum: \"$quizzes\"} } }"))); + } + + @Test // DATAMONGO-1536 + public void shouldRenderSumWithMultipleArgsAggregationExpression() { + + DBObject agg = project().and(ArithmeticOperators.valueOf("final").sum().and("midterm")).as("examTotal") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { examTotal: { $sum: [ \"$final\", \"$midterm\" ] } } }"))); + } + + @Test // DATAMONGO-1536 + public void shouldRenderAvgAggregationExpression() { + + DBObject agg = project().and(ArithmeticOperators.valueOf("quizzes").avg()).as("quizAvg") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { quizAvg: { $avg: \"$quizzes\"} } }"))); + } + + @Test // DATAMONGO-1536 + public void shouldRenderAvgWithMultipleArgsAggregationExpression() { + + DBObject agg = project().and(ArithmeticOperators.valueOf("final").avg().and("midterm")).as("examAvg") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { examAvg: { $avg: [ \"$final\", \"$midterm\" ] } } }"))); + } + + @Test // DATAMONGO-1536 + public void shouldRenderMaxAggregationExpression() { + + DBObject agg = project().and(ArithmeticOperators.valueOf("quizzes").max()).as("quizMax") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { quizMax: { $max: \"$quizzes\"} } }"))); + } + + @Test // DATAMONGO-1536 + public void shouldRenderMaxWithMultipleArgsAggregationExpression() { + + DBObject agg = project().and(ArithmeticOperators.valueOf("final").max().and("midterm")).as("examMax") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { examMax: { $max: [ \"$final\", \"$midterm\" ] } } }"))); + } + + @Test // DATAMONGO-1536 + public void shouldRenderMinAggregationExpression() { + + DBObject agg = project().and(ArithmeticOperators.valueOf("quizzes").min()).as("quizMin") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { quizMin: { $min: \"$quizzes\"} } }"))); + } + + @Test // DATAMONGO-1536 + public void shouldRenderMinWithMultipleArgsAggregationExpression() { + + DBObject agg = project().and(ArithmeticOperators.valueOf("final").min().and("midterm")).as("examMin") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { examMin: { $min: [ \"$final\", \"$midterm\" ] } } }"))); + } + + @Test // DATAMONGO-1536 + public void shouldRenderStdDevPopAggregationExpression() { + + DBObject agg = project().and(ArithmeticOperators.valueOf("scores").stdDevPop()).as("stdDev") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { stdDev: { $stdDevPop: \"$scores\"} } }"))); + } + + @Test // DATAMONGO-1536 + public void shouldRenderStdDevSampAggregationExpression() { + + DBObject agg = project().and(ArithmeticOperators.valueOf("scores").stdDevSamp()).as("stdDev") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { stdDev: { $stdDevSamp: \"$scores\"} } }"))); + } + + @Test // DATAMONGO-1536 + public void shouldRenderCmpAggregationExpression() { + + DBObject agg = project().and(ComparisonOperators.valueOf("qty").compareToValue(250)).as("cmp250") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { cmp250: { $cmp: [\"$qty\", 250]} } }"))); + } + + @Test // DATAMONGO-1536 + public void shouldRenderEqAggregationExpression() { + + DBObject agg = project().and(ComparisonOperators.valueOf("qty").equalToValue(250)).as("eq250") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { eq250: { $eq: [\"$qty\", 250]} } }"))); + } + + @Test // DATAMONGO-1536 + public void shouldRenderGtAggregationExpression() { + + DBObject agg = project().and(ComparisonOperators.valueOf("qty").greaterThanValue(250)).as("gt250") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { gt250: { $gt: [\"$qty\", 250]} } }"))); + } + + @Test // DATAMONGO-1536 + public void shouldRenderGteAggregationExpression() { + + DBObject agg = project().and(ComparisonOperators.valueOf("qty").greaterThanEqualToValue(250)).as("gte250") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { gte250: { $gte: [\"$qty\", 250]} } }"))); + } + + @Test // DATAMONGO-1536 + public void shouldRenderLtAggregationExpression() { + + DBObject agg = project().and(ComparisonOperators.valueOf("qty").lessThanValue(250)).as("lt250") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { lt250: { $lt: [\"$qty\", 250]} } }"))); + } + + @Test // DATAMONGO-1536 + public void shouldRenderLteAggregationExpression() { + + DBObject agg = project().and(ComparisonOperators.valueOf("qty").lessThanEqualToValue(250)).as("lte250") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { lte250: { $lte: [\"$qty\", 250]} } }"))); + } + + @Test // DATAMONGO-1536 + public void shouldRenderNeAggregationExpression() { + + DBObject agg = project().and(ComparisonOperators.valueOf("qty").notEqualToValue(250)).as("ne250") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { ne250: { $ne: [\"$qty\", 250]} } }"))); + } + + @Test // DATAMONGO-1536 + public void shouldRenderLogicAndAggregationExpression() { + + DBObject agg = project() + .and(BooleanOperators.valueOf(ComparisonOperators.valueOf("qty").greaterThanValue(100)) + .and(ComparisonOperators.valueOf("qty").lessThanValue(250))) + .as("result").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is( + JSON.parse("{ $project: { result: { $and: [ { $gt: [ \"$qty\", 100 ] }, { $lt: [ \"$qty\", 250 ] } ] } } }"))); + } + + @Test // DATAMONGO-1536 + public void shouldRenderLogicOrAggregationExpression() { + + DBObject agg = project() + .and(BooleanOperators.valueOf(ComparisonOperators.valueOf("qty").greaterThanValue(250)) + .or(ComparisonOperators.valueOf("qty").lessThanValue(200))) + .as("result").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is( + JSON.parse("{ $project: { result: { $or: [ { $gt: [ \"$qty\", 250 ] }, { $lt: [ \"$qty\", 200 ] } ] } } }"))); + } + + @Test // DATAMONGO-1536 + public void shouldRenderNotAggregationExpression() { + + DBObject agg = project().and(BooleanOperators.not(ComparisonOperators.valueOf("qty").greaterThanValue(250))) + .as("result").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { result: { $not: [ { $gt: [ \"$qty\", 250 ] } ] } } }"))); + } + + @Test // DATAMONGO-1540 + public void shouldRenderMapAggregationExpression() { + + DBObject agg = Aggregation.project() + .and(VariableOperators.mapItemsOf("quizzes").as("grade") + .andApply(AggregationFunctionExpressions.ADD.of(field("grade"), 2))) + .as("adjustedGrades").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse( + "{ $project:{ adjustedGrades:{ $map: { input: \"$quizzes\", as: \"grade\",in: { $add: [ \"$$grade\", 2 ] }}}}}"))); + } + + @Test // DATAMONGO-1540 + public void shouldRenderMapAggregationExpressionOnExpression() { + + DBObject agg = Aggregation.project() + .and(VariableOperators.mapItemsOf(AggregationFunctionExpressions.SIZE.of("foo")).as("grade") + .andApply(AggregationFunctionExpressions.ADD.of(field("grade"), 2))) + .as("adjustedGrades").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse( + "{ $project:{ adjustedGrades:{ $map: { input: { $size : [\"foo\"]}, as: \"grade\",in: { $add: [ \"$$grade\", 2 ] }}}}}"))); + } + + @Test // DATAMONGO-861, DATAMONGO-1542 + public void shouldRenderIfNullConditionAggregationExpression() { + + DBObject agg = project().and( + ConditionalOperators.ifNull(ArrayOperators.arrayOf("array").elementAt(1)).then("a more sophisticated value")) + .as("result").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse( + "{ $project: { result: { $ifNull: [ { $arrayElemAt: [\"$array\", 1] }, \"a more sophisticated value\" ] } } }"))); + } + + @Test // DATAMONGO-1542 + public void shouldRenderIfNullValueAggregationExpression() { + + DBObject agg = project() + .and(ConditionalOperators.ifNull("field").then(ArrayOperators.arrayOf("array").elementAt(1))).as("result") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, + is(JSON.parse("{ $project: { result: { $ifNull: [ \"$field\", { $arrayElemAt: [\"$array\", 1] } ] } } }"))); + } + + @Test // DATAMONGO-861, DATAMONGO-1542 + public void fieldReplacementIfNullShouldRenderCorrectly() { + + DBObject agg = project().and(ConditionalOperators.ifNull("optional").thenValueOf("$never-null")).as("result") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { result: { $ifNull: [ \"$optional\", \"$never-null\" ] } } }"))); + } + + @Test // DATAMONGO-1538 + public void shouldRenderLetExpressionCorrectly() { + + DBObject agg = Aggregation.project() + .and(VariableOperators + .define( + newVariable("total") + .forExpression(AggregationFunctionExpressions.ADD.of(Fields.field("price"), Fields.field("tax"))), + newVariable("discounted") + .forExpression(ConditionalOperators.Cond.when("applyDiscount").then(0.9D).otherwise(1.0D))) + .andApply(AggregationFunctionExpressions.MULTIPLY.of(Fields.field("total"), Fields.field("discounted")))) // + .as("finalTotal").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, + is(JSON.parse("{ $project:{ \"finalTotal\" : { \"$let\": {" + // + "\"vars\": {" + // + "\"total\": { \"$add\": [ \"$price\", \"$tax\" ] }," + // + "\"discounted\": { \"$cond\": { \"if\": \"$applyDiscount\", \"then\": 0.9, \"else\": 1.0 } }" + // + "}," + // + "\"in\": { \"$multiply\": [ \"$$total\", \"$$discounted\" ] }" + // + "}}}}"))); + } + + @Test // DATAMONGO-1538 + public void shouldRenderLetExpressionCorrectlyWhenUsingLetOnProjectionBuilder() { + + ExpressionVariable var1 = newVariable("total") + .forExpression(AggregationFunctionExpressions.ADD.of(Fields.field("price"), Fields.field("tax"))); + + ExpressionVariable var2 = newVariable("discounted") + .forExpression(ConditionalOperators.Cond.when("applyDiscount").then(0.9D).otherwise(1.0D)); + + DBObject agg = Aggregation.project().and("foo") + .let(Arrays.asList(var1, var2), + AggregationFunctionExpressions.MULTIPLY.of(Fields.field("total"), Fields.field("discounted"))) + .as("finalTotal").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, + is(JSON.parse("{ $project:{ \"finalTotal\" : { \"$let\": {" + // + "\"vars\": {" + // + "\"total\": { \"$add\": [ \"$price\", \"$tax\" ] }," + // + "\"discounted\": { \"$cond\": { \"if\": \"$applyDiscount\", \"then\": 0.9, \"else\": 1.0 } }" + // + "}," + // + "\"in\": { \"$multiply\": [ \"$$total\", \"$$discounted\" ] }" + // + "}}}}"))); + } + + @Test // DATAMONGO-1548 + public void shouldRenderIndexOfBytesCorrectly() { + + DBObject agg = project().and(StringOperators.valueOf("item").indexOf("foo")).as("byteLocation") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, + Matchers.is(JSON.parse("{ $project: { byteLocation: { $indexOfBytes: [ \"$item\", \"foo\" ] } } }"))); + } + + @Test // DATAMONGO-1548 + public void shouldRenderIndexOfBytesWithRangeCorrectly() { + + DBObject agg = project().and(StringOperators.valueOf("item").indexOf("foo").within(new Range(5L, 9L))) + .as("byteLocation").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, isBsonObject().containing("$project.byteLocation.$indexOfBytes.[2]", 5L) + .containing("$project.byteLocation.$indexOfBytes.[3]", 9L)); + } + + @Test // DATAMONGO-1548 + public void shouldRenderIndexOfCPCorrectly() { + + DBObject agg = project().and(StringOperators.valueOf("item").indexOfCP("foo")).as("cpLocation") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, Matchers.is(JSON.parse("{ $project: { cpLocation: { $indexOfCP: [ \"$item\", \"foo\" ] } } }"))); + } + + @Test // DATAMONGO-1548 + public void shouldRenderIndexOfCPWithRangeCorrectly() { + + DBObject agg = project().and(StringOperators.valueOf("item").indexOfCP("foo").within(new Range(5L, 9L))) + .as("cpLocation").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, isBsonObject().containing("$project.cpLocation.$indexOfCP.[2]", 5L) + .containing("$project.cpLocation.$indexOfCP.[3]", 9L)); + } + + @Test // DATAMONGO-1548 + public void shouldRenderSplitCorrectly() { + + DBObject agg = project().and(StringOperators.valueOf("city").split(", ")).as("city_state") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, Matchers.is(JSON.parse("{ $project : { city_state : { $split: [\"$city\", \", \"] }} }"))); + } + + @Test // DATAMONGO-1548 + public void shouldRenderStrLenBytesCorrectly() { + + DBObject agg = project().and(StringOperators.valueOf("name").length()).as("length") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, Matchers.is(JSON.parse("{ $project : { \"length\": { $strLenBytes: \"$name\" } } }"))); + } + + @Test // DATAMONGO-1548 + public void shouldRenderStrLenCPCorrectly() { + + DBObject agg = project().and(StringOperators.valueOf("name").lengthCP()).as("length") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, Matchers.is(JSON.parse("{ $project : { \"length\": { $strLenCP: \"$name\" } } }"))); + } + + @Test // DATAMONGO-1548 + public void shouldRenderSubstrCPCorrectly() { + + DBObject agg = project().and(StringOperators.valueOf("quarter").substringCP(0, 2)).as("yearSubstring") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, Matchers.is(JSON.parse("{ $project : { yearSubstring: { $substrCP: [ \"$quarter\", 0, 2 ] } } }"))); + } + + @Test // DATAMONGO-1548 + public void shouldRenderIndexOfArrayCorrectly() { + + DBObject agg = project().and(ArrayOperators.arrayOf("items").indexOf(2)).as("index") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, Matchers.is(JSON.parse("{ $project : { index: { $indexOfArray: [ \"$items\", 2 ] } } }"))); + } + + @Test // DATAMONGO-1548 + public void shouldRenderRangeCorrectly() { + + DBObject agg = project().and(ArrayOperators.RangeOperator.rangeStartingAt(0L).to("distance").withStepSize(25L)) + .as("rest_stops").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, isBsonObject().containing("$project.rest_stops.$range.[0]", 0L) + .containing("$project.rest_stops.$range.[1]", "$distance").containing("$project.rest_stops.$range.[2]", 25L)); + } + + @Test // DATAMONGO-1548 + public void shouldRenderReverseArrayCorrectly() { + + DBObject agg = project().and(ArrayOperators.arrayOf("favorites").reverse()).as("reverseFavorites") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, Matchers.is(JSON.parse("{ $project : { reverseFavorites: { $reverseArray: \"$favorites\" } } }"))); + } + + @Test // DATAMONGO-1548 + public void shouldRenderReduceWithSimpleObjectCorrectly() { + + DBObject agg = project() + .and(ArrayOperators.arrayOf("probabilityArr") + .reduce(ArithmeticOperators.valueOf("$$value").multiplyBy("$$this")).startingWith(1)) + .as("results").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, Matchers.is(JSON.parse( + "{ $project : { \"results\": { $reduce: { input: \"$probabilityArr\", initialValue: 1, in: { $multiply: [ \"$$value\", \"$$this\" ] } } } } }"))); + } + + @Test // DATAMONGO-1548 + public void shouldRenderReduceWithComplexObjectCorrectly() { + + PropertyExpression sum = PropertyExpression.property("sum").definedAs( + ArithmeticOperators.valueOf(Variable.VALUE.referringTo("sum").getName()).add(Variable.THIS.getName())); + PropertyExpression product = PropertyExpression.property("product").definedAs(ArithmeticOperators + .valueOf(Variable.VALUE.referringTo("product").getName()).multiplyBy(Variable.THIS.getName())); + + DBObject agg = project() + .and(ArrayOperators.arrayOf("probabilityArr").reduce(sum, product) + .startingWith(new BasicDBObjectBuilder().add("sum", 5).add("product", 2).get())) + .as("results").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, Matchers.is(JSON.parse( + "{ $project : { \"results\": { $reduce: { input: \"$probabilityArr\", initialValue: { \"sum\" : 5 , \"product\" : 2} , in: { \"sum\": { $add : [\"$$value.sum\", \"$$this\"] }, \"product\": { $multiply: [ \"$$value.product\", \"$$this\" ] } } } } } }"))); + } + + @Test // DATAMONGO-1548 + public void shouldRenderZipCorrectly() { + + AggregationExpression elemAt0 = ArrayOperators.arrayOf("matrix").elementAt(0); + AggregationExpression elemAt1 = ArrayOperators.arrayOf("matrix").elementAt(1); + AggregationExpression elemAt2 = ArrayOperators.arrayOf("matrix").elementAt(2); + + DBObject agg = project().and( + ArrayOperators.arrayOf(elemAt0).zipWith(elemAt1, elemAt2).useLongestLength().defaultTo(new Object[] { 1, 2 })) + .as("transposed").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, Matchers.is(JSON.parse( + "{ $project : { transposed: { $zip: { inputs: [ { $arrayElemAt: [ \"$matrix\", 0 ] }, { $arrayElemAt: [ \"$matrix\", 1 ] }, { $arrayElemAt: [ \"$matrix\", 2 ] } ], useLongestLength : true, defaults: [1,2] } } } }"))); + } + + @Test // DATAMONGO-1548 + public void shouldRenderInCorrectly() { + + DBObject agg = project().and(ArrayOperators.arrayOf("in_stock").containsValue("bananas")).as("has_bananas") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, + Matchers.is(JSON.parse("{ $project : { has_bananas : { $in : [\"bananas\", \"$in_stock\" ] } } }"))); + } + + @Test // DATAMONGO-1548 + public void shouldRenderIsoDayOfWeekCorrectly() { + + DBObject agg = project().and(DateOperators.dateOf("birthday").isoDayOfWeek()).as("dayOfWeek") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, Matchers.is(JSON.parse("{ $project : { dayOfWeek: { $isoDayOfWeek: \"$birthday\" } } }"))); + } + + @Test // DATAMONGO-1548 + public void shouldRenderIsoWeekCorrectly() { + + DBObject agg = project().and(DateOperators.dateOf("date").isoWeek()).as("weekNumber") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, Matchers.is(JSON.parse("{ $project : { weekNumber: { $isoWeek: \"$date\" } } }"))); + } + + @Test // DATAMONGO-1548 + public void shouldRenderIsoWeekYearCorrectly() { + + DBObject agg = project().and(DateOperators.dateOf("date").isoWeekYear()).as("yearNumber") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, Matchers.is(JSON.parse("{ $project : { yearNumber: { $isoWeekYear: \"$date\" } } }"))); + } + + @Test // DATAMONGO-1548 + public void shouldRenderSwitchCorrectly() { + + String expected = "$switch:\n" + // + "{\n" + // + " branches: [\n" + // + " {\n" + // + " case: { $gte : [ { $avg : \"$scores\" }, 90 ] },\n" + // + " then: \"Doing great!\"\n" + // + " },\n" + // + " {\n" + // + " case: { $and : [ { $gte : [ { $avg : \"$scores\" }, 80 ] },\n" + // + " { $lt : [ { $avg : \"$scores\" }, 90 ] } ] },\n" + // + " then: \"Doing pretty well.\"\n" + // + " },\n" + // + " {\n" + // + " case: { $lt : [ { $avg : \"$scores\" }, 80 ] },\n" + // + " then: \"Needs improvement.\"\n" + // + " }\n" + // + " ],\n" + // + " default: \"No scores found.\"\n" + // + " }\n" + // + "}"; + + CaseOperator cond1 = CaseOperator + .when(ComparisonOperators.Gte.valueOf(AccumulatorOperators.Avg.avgOf("scores")).greaterThanEqualToValue(90)) + .then("Doing great!"); + CaseOperator cond2 = CaseOperator + .when(BooleanOperators.And.and( + ComparisonOperators.Gte.valueOf(AccumulatorOperators.Avg.avgOf("scores")).greaterThanEqualToValue(80), + ComparisonOperators.Lt.valueOf(AccumulatorOperators.Avg.avgOf("scores")).lessThanValue(90))) + .then("Doing pretty well."); + CaseOperator cond3 = CaseOperator + .when(ComparisonOperators.Lt.valueOf(AccumulatorOperators.Avg.avgOf("scores")).lessThanValue(80)) + .then("Needs improvement."); + + DBObject agg = project().and(ConditionalOperators.switchCases(cond1, cond2, cond3).defaultTo("No scores found.")) + .as("summary").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, Matchers.is(JSON.parse("{ $project : { summary: {" + expected + "} } }"))); + } + + @Test // DATAMONGO-1548 + public void shouldTypeCorrectly() { + + DBObject agg = project().and(DataTypeOperators.Type.typeOf("a")).as("a").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, Matchers.is(JSON.parse("{ $project : { a: { $type: \"$a\" } } }"))); + } + + private static DBObject exctractOperation(String field, DBObject fromProjectClause) { + return (DBObject) fromProjectClause.get(field); + } + } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ReplaceRootOperationUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ReplaceRootOperationUnitTests.java new file mode 100644 index 0000000000..90376d5ddb --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ReplaceRootOperationUnitTests.java @@ -0,0 +1,104 @@ +/* + * Copyright 2016-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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.ReplaceRootOperation.ReplaceRootDocumentOperation; + +import com.mongodb.BasicDBObject; +import com.mongodb.DBObject; +import com.mongodb.util.JSON; + +/** + * Unit tests for {@link ReplaceRootOperation}. + * + * @author Mark Paluch + */ +public class ReplaceRootOperationUnitTests { + + @Test(expected = IllegalArgumentException.class) // DATAMONGO-1550 + public void rejectsNullField() { + new ReplaceRootOperation((Field) null); + } + + @Test(expected = IllegalArgumentException.class) // DATAMONGO-1550 + public void rejectsNullExpression() { + new ReplaceRootOperation((AggregationExpression) null); + } + + @Test // DATAMONGO-1550 + public void shouldRenderCorrectly() { + + ReplaceRootOperation operation = ReplaceRootDocumentOperation.builder() + .withDocument(new BasicDBObject("hello", "world")); + DBObject dbObject = operation.toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(dbObject, is(JSON.parse("{ $replaceRoot : { newRoot: { hello: \"world\" } } }"))); + } + + @Test // DATAMONGO-1550 + public void shouldRenderExpressionCorrectly() { + + ReplaceRootOperation operation = new ReplaceRootOperation(VariableOperators // + .mapItemsOf("array") // + .as("element") // + .andApply(AggregationFunctionExpressions.MULTIPLY.of("$$element", 10))); + + DBObject dbObject = operation.toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(dbObject, is(JSON.parse("{ $replaceRoot : { newRoot : { " + + "$map : { input : \"$array\" , as : \"element\" , in : { $multiply : [ \"$$element\" , 10]} } " + "} } }"))); + } + + @Test // DATAMONGO-1550 + public void shouldComposeDocument() { + + ReplaceRootOperation operation = ReplaceRootDocumentOperation.builder().withDocument() // + .andValue("value").as("key") // + .and(AggregationFunctionExpressions.MULTIPLY.of("$$element", 10)).as("multiply"); + + DBObject dbObject = operation.toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(dbObject, is(JSON + .parse("{ $replaceRoot : { newRoot: { key: \"value\", multiply: { $multiply : [ \"$$element\" , 10]} } } }"))); + } + + @Test // DATAMONGO-1550 + public void shouldComposeSubDocument() { + + DBObject partialReplacement = new BasicDBObject("key", "override").append("key2", "value2"); + + ReplaceRootOperation operation = ReplaceRootDocumentOperation.builder().withDocument() // + .andValue("value").as("key") // + .andValuesOf(partialReplacement); + + DBObject dbObject = operation.toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(dbObject, is(JSON.parse("{ $replaceRoot : { newRoot: { key: \"override\", key2: \"value2\"} } } }"))); + } + + @Test // DATAMONGO-1550 + public void shouldNotExposeFields() { + + ReplaceRootOperation operation = new ReplaceRootOperation(Fields.field("field")); + + assertThat(operation.getFields().exposesNoFields(), is(true)); + assertThat(operation.getFields().exposesSingleFieldOnly(), is(false)); + } +} diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/SpelExpressionTransformerIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/SpelExpressionTransformerIntegrationTests.java index a5260b712d..3710b6899c 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/SpelExpressionTransformerIntegrationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/SpelExpressionTransformerIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -37,7 +37,6 @@ /** * Integration tests for {@link SpelExpressionTransformer}. * - * @see DATAMONGO-774 * @author Thomas Darimont */ @RunWith(SpringJUnit4ClassRunner.class) @@ -57,7 +56,7 @@ public void setUp() { this.dbRefResolver = new DefaultDbRefResolver(mongoDbFactory); } - @Test + @Test // DATAMONGO-774 public void shouldConvertCompoundExpressionToPropertyPath() { MappingMongoConverter converter = new MappingMongoConverter(dbRefResolver, new MongoMappingContext()); @@ -67,7 +66,7 @@ public void shouldConvertCompoundExpressionToPropertyPath() { is("$item.primitiveIntValue")); } - @Test + @Test // DATAMONGO-774 public void shouldThrowExceptionIfNestedPropertyCannotBeFound() { exception.expect(MappingException.class); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/SpelExpressionTransformerUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/SpelExpressionTransformerUnitTests.java index ad72d97fa4..7f7423a993 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/SpelExpressionTransformerUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/SpelExpressionTransformerUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2014 the original author or authors. + * Copyright 2013-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,9 +26,9 @@ /** * Unit tests for {@link SpelExpressionTransformer}. * - * @see DATAMONGO-774 * @author Thomas Darimont * @author Oliver Gierke + * @author Christoph Strobl */ public class SpelExpressionTransformerUnitTests { @@ -47,7 +47,7 @@ public void setup() { this.data.item.primitiveIntValue = 21; } - @Test + @Test // DATAMONGO-774 public void shouldRenderConstantExpression() { assertThat(transform("1"), is("1")); @@ -57,7 +57,7 @@ public void shouldRenderConstantExpression() { assertThat(transform("null"), is(nullValue())); } - @Test + @Test // DATAMONGO-774 public void shouldSupportKnownOperands() { assertThat(transform("a + b"), is("{ \"$add\" : [ \"$a\" , \"$b\"]}")); @@ -67,47 +67,45 @@ public void shouldSupportKnownOperands() { assertThat(transform("a % b"), is("{ \"$mod\" : [ \"$a\" , \"$b\"]}")); } - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-774 public void shouldThrowExceptionOnUnknownOperand() { - transform("a ^ 1"); + transform("a++"); } - @Test + @Test // DATAMONGO-774 public void shouldRenderSumExpression() { assertThat(transform("a + 1"), is("{ \"$add\" : [ \"$a\" , 1]}")); } - @Test + @Test // DATAMONGO-774 public void shouldRenderFormula() { - assertThat( - transform("(netPrice + surCharge) * taxrate + 42"), - is("{ \"$add\" : [ { \"$multiply\" : [ { \"$add\" : [ \"$netPrice\" , \"$surCharge\"]} , \"$taxrate\"]} , 42]}")); + assertThat(transform("(netPrice + surCharge) * taxrate + 42"), is( + "{ \"$add\" : [ { \"$multiply\" : [ { \"$add\" : [ \"$netPrice\" , \"$surCharge\"]} , \"$taxrate\"]} , 42]}")); } - @Test + @Test // DATAMONGO-774 public void shouldRenderFormulaInCurlyBrackets() { - assertThat( - transform("{(netPrice + surCharge) * taxrate + 42}"), - is("{ \"$add\" : [ { \"$multiply\" : [ { \"$add\" : [ \"$netPrice\" , \"$surCharge\"]} , \"$taxrate\"]} , 42]}")); + assertThat(transform("{(netPrice + surCharge) * taxrate + 42}"), is( + "{ \"$add\" : [ { \"$multiply\" : [ { \"$add\" : [ \"$netPrice\" , \"$surCharge\"]} , \"$taxrate\"]} , 42]}")); } - @Test + @Test // DATAMONGO-774 public void shouldRenderFieldReference() { assertThat(transform("foo"), is("$foo")); assertThat(transform("$foo"), is("$foo")); } - @Test + @Test // DATAMONGO-774 public void shouldRenderNestedFieldReference() { assertThat(transform("foo.bar"), is("$foo.bar")); assertThat(transform("$foo.bar"), is("$foo.bar")); } - @Test + @Test // DATAMONGO-774 @Ignore public void shouldRenderNestedIndexedFieldReference() { @@ -115,47 +113,46 @@ public void shouldRenderNestedIndexedFieldReference() { assertThat(transform("foo[3].bar"), is("$foo[3].bar")); } - @Test + @Test // DATAMONGO-774 public void shouldRenderConsecutiveOperation() { assertThat(transform("1 + 1 + 1"), is("{ \"$add\" : [ 1 , 1 , 1]}")); } - @Test + @Test // DATAMONGO-774 public void shouldRenderComplexExpression0() { assertThat(transform("-(1 + q)"), is("{ \"$multiply\" : [ -1 , { \"$add\" : [ 1 , \"$q\"]}]}")); } - @Test + @Test // DATAMONGO-774 public void shouldRenderComplexExpression1() { assertThat(transform("1 + (q + 1) / (q - 1)"), is("{ \"$add\" : [ 1 , { \"$divide\" : [ { \"$add\" : [ \"$q\" , 1]} , { \"$subtract\" : [ \"$q\" , 1]}]}]}")); } - @Test + @Test // DATAMONGO-774 public void shouldRenderComplexExpression2() { - assertThat( - transform("(q + 1 + 4 - 5) / (q + 1 + 3 + 4)"), - is("{ \"$divide\" : [ { \"$subtract\" : [ { \"$add\" : [ \"$q\" , 1 , 4]} , 5]} , { \"$add\" : [ \"$q\" , 1 , 3 , 4]}]}")); + assertThat(transform("(q + 1 + 4 - 5) / (q + 1 + 3 + 4)"), is( + "{ \"$divide\" : [ { \"$subtract\" : [ { \"$add\" : [ \"$q\" , 1 , 4]} , 5]} , { \"$add\" : [ \"$q\" , 1 , 3 , 4]}]}")); } - @Test + @Test // DATAMONGO-774 public void shouldRenderBinaryExpressionWithMixedSignsCorrectly() { assertThat(transform("-4 + 1"), is("{ \"$add\" : [ -4 , 1]}")); assertThat(transform("1 + -4"), is("{ \"$add\" : [ 1 , -4]}")); } - @Test + @Test // DATAMONGO-774 public void shouldRenderConsecutiveOperationsInComplexExpression() { assertThat(transform("1 + 1 + (1 + 1 + 1) / q"), is("{ \"$add\" : [ 1 , 1 , { \"$divide\" : [ { \"$add\" : [ 1 , 1 , 1]} , \"$q\"]}]}")); } - @Test + @Test // DATAMONGO-774 public void shouldRenderParameterExpressionResults() { assertThat(transform("[0] + [1] + [2]", 1, 2, 3), is("{ \"$add\" : [ 1 , 2 , 3]}")); } @@ -167,7 +164,7 @@ public void shouldRenderNestedParameterExpressionResults() { is("{ \"$add\" : [ 42 , 1.2345 , 23]}")); } - @Test + @Test // DATAMONGO-774 public void shouldRenderNestedParameterExpressionResultsInNestedExpressions() { assertThat( @@ -175,10 +172,7 @@ public void shouldRenderNestedParameterExpressionResultsInNestedExpressions() { is("{ \"$multiply\" : [ { \"$add\" : [ 1 , 42 , 1.2345]} , 23]}")); } - /** - * @see DATAMONGO-840 - */ - @Test + @Test // DATAMONGO-840 public void shouldRenderCompoundExpressionsWithIndexerAndFieldReference() { Person person = new Person(); @@ -186,24 +180,524 @@ public void shouldRenderCompoundExpressionsWithIndexerAndFieldReference() { assertThat(transform("[0].age + a.c", person), is("{ \"$add\" : [ 10 , \"$a.c\"]}")); } - /** - * @see DATAMONGO-840 - */ - @Test + @Test // DATAMONGO-840 public void shouldRenderCompoundExpressionsWithOnlyFieldReferences() { assertThat(transform("a.b + a.c"), is("{ \"$add\" : [ \"$a.b\" , \"$a.c\"]}")); } - @Test - public void shouldRenderStringFunctions() { + @Test // DATAMONGO-1530 + public void shouldRenderMethodReferenceNodeAnd() { + assertThat(transform("and(a, b)"), is("{ \"$and\" : [ \"$a\" , \"$b\"]}")); + } + + @Test // DATAMONGO-1530 + public void shouldRenderMethodReferenceNodeOr() { + assertThat(transform("or(a, b)"), is("{ \"$or\" : [ \"$a\" , \"$b\"]}")); + } + + @Test // DATAMONGO-1530 + public void shouldRenderMethodReferenceNodeNot() { + assertThat(transform("not(a)"), is("{ \"$not\" : [ \"$a\"]}")); + } + + @Test // DATAMONGO-1530 + public void shouldRenderMethodReferenceNodeSetEquals() { + assertThat(transform("setEquals(a, b)"), is("{ \"$setEquals\" : [ \"$a\" , \"$b\"]}")); + } + + @Test // DATAMONGO-1530 + public void shouldRenderMethodReferenceNodeSetEqualsForArrays() { + assertThat(transform("setEquals(new int[]{1,2,3}, new int[]{4,5,6})"), + is("{ \"$setEquals\" : [ [ 1 , 2 , 3] , [ 4 , 5 , 6]]}")); + } + + @Test // DATAMONGO-1530 + public void shouldRenderMethodReferenceNodeSetEqualsMixedArrays() { + assertThat(transform("setEquals(a, new int[]{4,5,6})"), is("{ \"$setEquals\" : [ \"$a\" , [ 4 , 5 , 6]]}")); + } + + @Test // DATAMONGO-1530 + public void shouldRenderMethodReferenceSetIntersection() { + assertThat(transform("setIntersection(a, new int[]{4,5,6})"), + is("{ \"$setIntersection\" : [ \"$a\" , [ 4 , 5 , 6]]}")); + } + + @Test // DATAMONGO-1530 + public void shouldRenderMethodReferenceSetUnion() { + assertThat(transform("setUnion(a, new int[]{4,5,6})"), is("{ \"$setUnion\" : [ \"$a\" , [ 4 , 5 , 6]]}")); + } + + @Test // DATAMONGO-1530 + public void shouldRenderMethodReferenceSeDifference() { + assertThat(transform("setDifference(a, new int[]{4,5,6})"), is("{ \"$setDifference\" : [ \"$a\" , [ 4 , 5 , 6]]}")); + } + + @Test // DATAMONGO-1530 + public void shouldRenderMethodReferenceSetIsSubset() { + assertThat(transform("setIsSubset(a, new int[]{4,5,6})"), is("{ \"$setIsSubset\" : [ \"$a\" , [ 4 , 5 , 6]]}")); + } + + @Test // DATAMONGO-1530 + public void shouldRenderMethodReferenceAnyElementTrue() { + assertThat(transform("anyElementTrue(a)"), is("{ \"$anyElementTrue\" : [ \"$a\"]}")); + } + + @Test // DATAMONGO-1530 + public void shouldRenderMethodReferenceAllElementsTrue() { + assertThat(transform("allElementsTrue(a, new int[]{4,5,6})"), + is("{ \"$allElementsTrue\" : [ \"$a\" , [ 4 , 5 , 6]]}")); + } + + @Test // DATAMONGO-1530 + public void shouldRenderMethodReferenceCmp() { + assertThat(transform("cmp(a, 250)"), is("{ \"$cmp\" : [ \"$a\" , 250]}")); + } + + @Test // DATAMONGO-1530 + public void shouldRenderMethodReferenceEq() { + assertThat(transform("eq(a, 250)"), is("{ \"$eq\" : [ \"$a\" , 250]}")); + } + + @Test // DATAMONGO-1530 + public void shouldRenderMethodReferenceGt() { + assertThat(transform("gt(a, 250)"), is("{ \"$gt\" : [ \"$a\" , 250]}")); + } + + @Test // DATAMONGO-1530 + public void shouldRenderMethodReferenceGte() { + assertThat(transform("gte(a, 250)"), is("{ \"$gte\" : [ \"$a\" , 250]}")); + } + + @Test // DATAMONGO-1530 + public void shouldRenderMethodReferenceLt() { + assertThat(transform("lt(a, 250)"), is("{ \"$lt\" : [ \"$a\" , 250]}")); + } - assertThat(transform("concat(a, b)"), is("{ \"$concat\" : [ \"$a\" , \"$b\"]}")); - assertThat(transform("substr(a, 1, 2)"), is("{ \"$substr\" : [ \"$a\" , 1 , 2]}")); + @Test // DATAMONGO-1530 + public void shouldRenderMethodReferenceLte() { + assertThat(transform("lte(a, 250)"), is("{ \"$lte\" : [ \"$a\" , 250]}")); + } + + @Test // DATAMONGO-1530 + public void shouldRenderMethodReferenceNe() { + assertThat(transform("ne(a, 250)"), is("{ \"$ne\" : [ \"$a\" , 250]}")); + } + + @Test // DATAMONGO-1530 + public void shouldRenderMethodReferenceAbs() { + assertThat(transform("abs(1)"), is("{ \"$abs\" : 1}")); + } + + @Test // DATAMONGO-1530 + public void shouldRenderMethodReferenceAdd() { + assertThat(transform("add(a, 250)"), is("{ \"$add\" : [ \"$a\" , 250]}")); + } + + @Test // DATAMONGO-1530 + public void shouldRenderMethodReferenceCeil() { + assertThat(transform("ceil(7.8)"), is("{ \"$ceil\" : 7.8}")); + } + + @Test // DATAMONGO-1530 + public void shouldRenderMethodReferenceDivide() { + assertThat(transform("divide(a, 250)"), is("{ \"$divide\" : [ \"$a\" , 250]}")); + } + + @Test // DATAMONGO-1530 + public void shouldRenderMethodReferenceExp() { + assertThat(transform("exp(2)"), is("{ \"$exp\" : 2}")); + } + + @Test // DATAMONGO-1530 + public void shouldRenderMethodReferenceFloor() { + assertThat(transform("floor(2)"), is("{ \"$floor\" : 2}")); + } + + @Test // DATAMONGO-1530 + public void shouldRenderMethodReferenceLn() { + assertThat(transform("ln(2)"), is("{ \"$ln\" : 2}")); + } + + @Test // DATAMONGO-1530 + public void shouldRenderMethodReferenceLog() { + assertThat(transform("log(100, 10)"), is("{ \"$log\" : [ 100 , 10]}")); + } + + @Test // DATAMONGO-1530 + public void shouldRenderMethodReferenceLog10() { + assertThat(transform("log10(100)"), is("{ \"$log10\" : 100}")); + } + + @Test // DATAMONGO-1530 + public void shouldRenderMethodReferenceNodeMod() { + assertThat(transform("mod(a, b)"), is("{ \"$mod\" : [ \"$a\" , \"$b\"]}")); + } + + @Test // DATAMONGO-1530 + public void shouldRenderMethodReferenceNodeMultiply() { + assertThat(transform("multiply(a, b)"), is("{ \"$multiply\" : [ \"$a\" , \"$b\"]}")); + } + + @Test // DATAMONGO-1530 + public void shouldRenderMethodReferenceNodePow() { + assertThat(transform("pow(a, 2)"), is("{ \"$pow\" : [ \"$a\" , 2]}")); + } + + @Test // DATAMONGO-1530 + public void shouldRenderMethodReferenceSqrt() { + assertThat(transform("sqrt(2)"), is("{ \"$sqrt\" : 2}")); + } + + @Test // DATAMONGO-1530 + public void shouldRenderMethodReferenceNodeSubtract() { + assertThat(transform("subtract(a, b)"), is("{ \"$subtract\" : [ \"$a\" , \"$b\"]}")); + } + + @Test // DATAMONGO-1530 + public void shouldRenderMethodReferenceTrunc() { + assertThat(transform("trunc(2.1)"), is("{ \"$trunc\" : 2.1}")); + } + + @Test // DATAMONGO-1530 + public void shouldRenderMethodReferenceNodeConcat() { + assertThat(transform("concat(a, b, 'c')"), is("{ \"$concat\" : [ \"$a\" , \"$b\" , \"c\"]}")); + } + + @Test // DATAMONGO-1530 + public void shouldRenderMethodReferenceNodeSubstrc() { + assertThat(transform("substr(a, 0, 1)"), is("{ \"$substr\" : [ \"$a\" , 0 , 1]}")); + } + + @Test // DATAMONGO-1530 + public void shouldRenderMethodReferenceToLower() { + assertThat(transform("toLower(a)"), is("{ \"$toLower\" : \"$a\"}")); + } + + @Test // DATAMONGO-1530 + public void shouldRenderMethodReferenceToUpper() { + assertThat(transform("toUpper(a)"), is("{ \"$toUpper\" : \"$a\"}")); + } + + @Test // DATAMONGO-1530 + public void shouldRenderMethodReferenceNodeStrCaseCmp() { assertThat(transform("strcasecmp(a, b)"), is("{ \"$strcasecmp\" : [ \"$a\" , \"$b\"]}")); - assertThat(transform("toLower(a)"), is("{ \"$toLower\" : [ \"$a\"]}")); - assertThat(transform("toUpper(a)"), is("{ \"$toUpper\" : [ \"$a\"]}")); - assertThat(transform("toUpper(toLower(a))"), is("{ \"$toUpper\" : [ { \"$toLower\" : [ \"$a\"]}]}")); + } + + @Test // DATAMONGO-1530 + public void shouldRenderMethodReferenceMeta() { + assertThat(transform("meta('textScore')"), is("{ \"$meta\" : \"textScore\"}")); + } + + @Test // DATAMONGO-1530 + public void shouldRenderMethodReferenceNodeArrayElemAt() { + assertThat(transform("arrayElemAt(a, 10)"), is("{ \"$arrayElemAt\" : [ \"$a\" , 10]}")); + } + + @Test // DATAMONGO-1530 + public void shouldRenderMethodReferenceNodeConcatArrays() { + assertThat(transform("concatArrays(a, b, c)"), is("{ \"$concatArrays\" : [ \"$a\" , \"$b\" , \"$c\"]}")); + } + + @Test // DATAMONGO-1530 + public void shouldRenderMethodReferenceNodeFilter() { + assertThat(transform("filter(a, 'num', '$$num' > 10)"), + is("{ \"$filter\" : { \"input\" : \"$a\" , \"as\" : \"num\" , \"cond\" : { \"$gt\" : [ \"$$num\" , 10]}}}")); + } + + @Test // DATAMONGO-1530 + public void shouldRenderMethodReferenceIsArray() { + assertThat(transform("isArray(a)"), is("{ \"$isArray\" : \"$a\"}")); + } + + @Test // DATAMONGO-1530 + public void shouldRenderMethodReferenceIsSize() { + assertThat(transform("size(a)"), is("{ \"$size\" : \"$a\"}")); + } + + @Test // DATAMONGO-1530 + public void shouldRenderMethodReferenceNodeSlice() { + assertThat(transform("slice(a, 10)"), is("{ \"$slice\" : [ \"$a\" , 10]}")); + } + + @Test // DATAMONGO-1530 + public void shouldRenderMethodReferenceNodeMap() { + assertThat(transform("map(quizzes, 'grade', '$$grade' + 2)"), is( + "{ \"$map\" : { \"input\" : \"$quizzes\" , \"as\" : \"grade\" , \"in\" : { \"$add\" : [ \"$$grade\" , 2]}}}")); + } + + @Test // DATAMONGO-1530 + public void shouldRenderMethodReferenceNodeLet() { + assertThat(transform("let({low:1, high:'$$low'}, gt('$$low', '$$high'))"), is( + "{ \"$let\" : { \"vars\" : { \"low\" : 1 , \"high\" : \"$$low\"} , \"in\" : { \"$gt\" : [ \"$$low\" , \"$$high\"]}}}")); + } + + @Test // DATAMONGO-1530 + public void shouldRenderMethodReferenceLiteral() { + assertThat(transform("literal($1)"), is("{ \"$literal\" : \"$1\"}")); + } + + @Test // DATAMONGO-1530 + public void shouldRenderMethodReferenceDayOfYear() { + assertThat(transform("dayOfYear($1)"), is("{ \"$dayOfYear\" : \"$1\"}")); + } + + @Test // DATAMONGO-1530 + public void shouldRenderMethodReferenceDayOfMonth() { + assertThat(transform("dayOfMonth($1)"), is("{ \"$dayOfMonth\" : \"$1\"}")); + } + + @Test // DATAMONGO-1530 + public void shouldRenderMethodReferenceDayOfWeek() { + assertThat(transform("dayOfWeek($1)"), is("{ \"$dayOfWeek\" : \"$1\"}")); + } + + @Test // DATAMONGO-1530 + public void shouldRenderMethodReferenceYear() { + assertThat(transform("year($1)"), is("{ \"$year\" : \"$1\"}")); + } + + @Test // DATAMONGO-1530 + public void shouldRenderMethodReferenceMonth() { + assertThat(transform("month($1)"), is("{ \"$month\" : \"$1\"}")); + } + + @Test // DATAMONGO-1530 + public void shouldRenderMethodReferenceWeek() { + assertThat(transform("week($1)"), is("{ \"$week\" : \"$1\"}")); + } + + @Test // DATAMONGO-1530 + public void shouldRenderMethodReferenceHour() { + assertThat(transform("hour($1)"), is("{ \"$hour\" : \"$1\"}")); + } + + @Test // DATAMONGO-1530 + public void shouldRenderMethodReferenceMinute() { + assertThat(transform("minute($1)"), is("{ \"$minute\" : \"$1\"}")); + } + + @Test // DATAMONGO-1530 + public void shouldRenderMethodReferenceSecond() { + assertThat(transform("second($1)"), is("{ \"$second\" : \"$1\"}")); + } + + @Test // DATAMONGO-1530 + public void shouldRenderMethodReferenceMillisecond() { + assertThat(transform("millisecond($1)"), is("{ \"$millisecond\" : \"$1\"}")); + } + + @Test // DATAMONGO-1530 + public void shouldRenderMethodReferenceDateToString() { + assertThat(transform("dateToString('%Y-%m-%d', $date)"), + is("{ \"$dateToString\" : { \"format\" : \"%Y-%m-%d\" , \"date\" : \"$date\"}}")); + } + + @Test // DATAMONGO-1530 + public void shouldRenderMethodReferenceCond() { + assertThat(transform("cond(qty > 250, 30, 20)"), + is("{ \"$cond\" : { \"if\" : { \"$gt\" : [ \"$qty\" , 250]} , \"then\" : 30 , \"else\" : 20}}")); + } + + @Test // DATAMONGO-1530 + public void shouldRenderMethodReferenceNodeIfNull() { + assertThat(transform("ifNull(a, 10)"), is("{ \"$ifNull\" : [ \"$a\" , 10]}")); + } + + @Test // DATAMONGO-1530 + public void shouldRenderMethodReferenceNodeSum() { + assertThat(transform("sum(a, b)"), is("{ \"$sum\" : [ \"$a\" , \"$b\"]}")); + } + + @Test // DATAMONGO-1530 + public void shouldRenderMethodReferenceNodeAvg() { + assertThat(transform("avg(a, b)"), is("{ \"$avg\" : [ \"$a\" , \"$b\"]}")); + } + + @Test // DATAMONGO-1530 + public void shouldRenderMethodReferenceFirst() { + assertThat(transform("first($1)"), is("{ \"$first\" : \"$1\"}")); + } + + @Test // DATAMONGO-1530 + public void shouldRenderMethodReferenceLast() { + assertThat(transform("last($1)"), is("{ \"$last\" : \"$1\"}")); + } + + @Test // DATAMONGO-1530 + public void shouldRenderMethodReferenceNodeMax() { + assertThat(transform("max(a, b)"), is("{ \"$max\" : [ \"$a\" , \"$b\"]}")); + } + + @Test // DATAMONGO-1530 + public void shouldRenderMethodReferenceNodeMin() { + assertThat(transform("min(a, b)"), is("{ \"$min\" : [ \"$a\" , \"$b\"]}")); + } + + @Test // DATAMONGO-1530 + public void shouldRenderMethodReferenceNodePush() { + assertThat(transform("push({'item':'$item', 'quantity':'$qty'})"), + is("{ \"$push\" : { \"item\" : \"$item\" , \"quantity\" : \"$qty\"}}")); + } + + @Test // DATAMONGO-1530 + public void shouldRenderMethodReferenceAddToSet() { + assertThat(transform("addToSet($1)"), is("{ \"$addToSet\" : \"$1\"}")); + } + + @Test // DATAMONGO-1530 + public void shouldRenderMethodReferenceNodeStdDevPop() { + assertThat(transform("stdDevPop(scores.score)"), is("{ \"$stdDevPop\" : [ \"$scores.score\"]}")); + } + + @Test // DATAMONGO-1530 + public void shouldRenderMethodReferenceNodeStdDevSamp() { + assertThat(transform("stdDevSamp(age)"), is("{ \"$stdDevSamp\" : [ \"$age\"]}")); + } + + @Test // DATAMONGO-1530 + public void shouldRenderOperationNodeEq() { + assertThat(transform("foo == 10"), is("{ \"$eq\" : [ \"$foo\" , 10]}")); + } + + @Test // DATAMONGO-1530 + public void shouldRenderOperationNodeNe() { + assertThat(transform("foo != 10"), is("{ \"$ne\" : [ \"$foo\" , 10]}")); + } + + @Test // DATAMONGO-1530 + public void shouldRenderOperationNodeGt() { + assertThat(transform("foo > 10"), is("{ \"$gt\" : [ \"$foo\" , 10]}")); + } + + @Test // DATAMONGO-1530 + public void shouldRenderOperationNodeGte() { + assertThat(transform("foo >= 10"), is("{ \"$gte\" : [ \"$foo\" , 10]}")); + } + + @Test // DATAMONGO-1530 + public void shouldRenderOperationNodeLt() { + assertThat(transform("foo < 10"), is("{ \"$lt\" : [ \"$foo\" , 10]}")); + } + + @Test // DATAMONGO-1530 + public void shouldRenderOperationNodeLte() { + assertThat(transform("foo <= 10"), is("{ \"$lte\" : [ \"$foo\" , 10]}")); + } + + @Test // DATAMONGO-1530 + public void shouldRenderOperationNodePow() { + assertThat(transform("foo^2"), is("{ \"$pow\" : [ \"$foo\" , 2]}")); + } + + @Test // DATAMONGO-1530 + public void shouldRenderOperationNodeOr() { + assertThat(transform("true || false"), is("{ \"$or\" : [ true , false]}")); + } + + @Test // DATAMONGO-1530 + public void shouldRenderComplexOperationNodeOr() { + assertThat(transform("1+2 || concat(a, b) || true"), + is("{ \"$or\" : [ { \"$add\" : [ 1 , 2]} , { \"$concat\" : [ \"$a\" , \"$b\"]} , true]}")); + } + + @Test // DATAMONGO-1530 + public void shouldRenderOperationNodeAnd() { + assertThat(transform("true && false"), is("{ \"$and\" : [ true , false]}")); + } + + @Test // DATAMONGO-1530 + public void shouldRenderComplexOperationNodeAnd() { + assertThat(transform("1+2 && concat(a, b) && true"), + is("{ \"$and\" : [ { \"$add\" : [ 1 , 2]} , { \"$concat\" : [ \"$a\" , \"$b\"]} , true]}")); + } + + @Test // DATAMONGO-1530 + public void shouldRenderNotCorrectly() { + assertThat(transform("!true"), is("{ \"$not\" : [ true]}")); + } + + @Test // DATAMONGO-1530 + public void shouldRenderComplexNotCorrectly() { + assertThat(transform("!(foo > 10)"), is("{ \"$not\" : [ { \"$gt\" : [ \"$foo\" , 10]}]}")); + } + + @Test // DATAMONGO-1548 + public void shouldRenderMethodReferenceIndexOfBytes() { + assertThat(transform("indexOfBytes(item, 'foo')"), is("{ \"$indexOfBytes\" : [ \"$item\" , \"foo\"]}")); + } + + @Test // DATAMONGO-1548 + public void shouldRenderMethodReferenceIndexOfCP() { + assertThat(transform("indexOfCP(item, 'foo')"), is("{ \"$indexOfCP\" : [ \"$item\" , \"foo\"]}")); + } + + @Test // DATAMONGO-1548 + public void shouldRenderMethodReferenceSplit() { + assertThat(transform("split(item, ',')"), is("{ \"$split\" : [ \"$item\" , \",\"]}")); + } + + @Test // DATAMONGO-1548 + public void shouldRenderMethodReferenceStrLenBytes() { + assertThat(transform("strLenBytes(item)"), is("{ \"$strLenBytes\" : \"$item\"}")); + } + + @Test // DATAMONGO-1548 + public void shouldRenderMethodReferenceStrLenCP() { + assertThat(transform("strLenCP(item)"), is("{ \"$strLenCP\" : \"$item\"}")); + } + + @Test // DATAMONGO-1548 + public void shouldRenderMethodSubstrCP() { + assertThat(transform("substrCP(item, 0, 5)"), is("{ \"$substrCP\" : [ \"$item\" , 0 , 5]}")); + } + + @Test // DATAMONGO-1548 + public void shouldRenderMethodReferenceReverseArray() { + assertThat(transform("reverseArray(array)"), is("{ \"$reverseArray\" : \"$array\"}")); + } + + @Test // DATAMONGO-1548 + public void shouldRenderMethodReferenceReduce() { + assertThat(transform("reduce(field, '', {'$concat':new String[]{'$$value','$$this'}})"), is( + "{ \"$reduce\" : { \"input\" : \"$field\" , \"initialValue\" : \"\" , \"in\" : { \"$concat\" : [ \"$$value\" , \"$$this\"]}}}")); + } + + @Test // DATAMONGO-1548 + public void shouldRenderMethodReferenceZip() { + assertThat(transform("zip(new String[]{'$array1', '$array2'})"), + is("{ \"$zip\" : { \"inputs\" : [ \"$array1\" , \"$array2\"]}}")); + } + + @Test // DATAMONGO-1548 + public void shouldRenderMethodReferenceZipWithOptionalArgs() { + assertThat(transform("zip(new String[]{'$array1', '$array2'}, true, new int[]{1,2})"), is( + "{ \"$zip\" : { \"inputs\" : [ \"$array1\" , \"$array2\"] , \"useLongestLength\" : true , \"defaults\" : [ 1 , 2]}}")); + } + + @Test // DATAMONGO-1548 + public void shouldRenderMethodIn() { + assertThat(transform("in('item', array)"), is("{ \"$in\" : [ \"item\" , \"$array\"]}")); + } + + @Test // DATAMONGO-1548 + public void shouldRenderMethodRefereneIsoDayOfWeek() { + assertThat(transform("isoDayOfWeek(date)"), is("{ \"$isoDayOfWeek\" : \"$date\"}")); + } + + @Test // DATAMONGO-1548 + public void shouldRenderMethodRefereneIsoWeek() { + assertThat(transform("isoWeek(date)"), is("{ \"$isoWeek\" : \"$date\"}")); + } + + @Test // DATAMONGO-1548 + public void shouldRenderMethodRefereneIsoWeekYear() { + assertThat(transform("isoWeekYear(date)"), is("{ \"$isoWeekYear\" : \"$date\"}")); + } + + @Test // DATAMONGO-1548 + public void shouldRenderMethodRefereneType() { + assertThat(transform("type(a)"), is("{ \"$type\" : \"$a\"}")); } private String transform(String expression, Object... params) { diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/TypeBasedAggregationOperationContextUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/TypeBasedAggregationOperationContextUnitTests.java index b1371c00c3..76f0860712 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/TypeBasedAggregationOperationContextUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/TypeBasedAggregationOperationContextUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,7 @@ import static org.hamcrest.CoreMatchers.*; import static org.junit.Assert.*; +import static org.springframework.data.mongodb.core.DBObjectTestUtils.*; import static org.springframework.data.mongodb.core.aggregation.Aggregation.*; import static org.springframework.data.mongodb.core.aggregation.Fields.*; import static org.springframework.data.mongodb.test.util.IsBsonObject.*; @@ -36,6 +37,7 @@ import org.springframework.data.annotation.PersistenceConstructor; import org.springframework.data.domain.Sort.Direction; import org.springframework.data.mapping.model.MappingException; +import org.springframework.data.mongodb.core.aggregation.ExposedFields.DirectFieldReference; 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.CustomConversions; @@ -48,6 +50,7 @@ import com.mongodb.BasicDBObject; import com.mongodb.DBObject; +import com.mongodb.util.JSON; /** * Unit tests for {@link TypeBasedAggregationOperationContext}. @@ -83,10 +86,7 @@ public void rejectsInvalidFieldReference() { getContext(Foo.class).getReference("foo"); } - /** - * @see DATAMONGO-741 - */ - @Test + @Test // DATAMONGO-741 public void returnsReferencesToNestedFieldsCorrectly() { AggregationOperationContext context = getContext(Foo.class); @@ -98,20 +98,15 @@ public void returnsReferencesToNestedFieldsCorrectly() { assertThat(context.getReference(field), is(context.getReference("bar.name"))); } - /** - * @see DATAMONGO-806 - */ - @Test + @Test // DATAMONGO-806 public void aliasesIdFieldCorrectly() { AggregationOperationContext context = getContext(Foo.class); - assertThat(context.getReference("id"), is(new FieldReference(new ExposedField(field("id", "_id"), true)))); + assertThat(context.getReference("id"), + is((FieldReference) new DirectFieldReference(new ExposedField(field("id", "_id"), true)))); } - /** - * @see DATAMONGO-912 - */ - @Test + @Test // DATAMONGO-912 public void shouldUseCustomConversionIfPresentAndConversionIsRequiredInFirstStage() { CustomConversions customConversions = customAgeConversions(); @@ -129,10 +124,7 @@ public void shouldUseCustomConversionIfPresentAndConversionIsRequiredInFirstStag assertThat(age, is((DBObject) new BasicDBObject("v", 10))); } - /** - * @see DATAMONGO-912 - */ - @Test + @Test // DATAMONGO-912 public void shouldUseCustomConversionIfPresentAndConversionIsRequiredInLaterStage() { CustomConversions customConversions = customAgeConversions(); @@ -150,10 +142,7 @@ public void shouldUseCustomConversionIfPresentAndConversionIsRequiredInLaterStag assertThat(age, is((DBObject) new BasicDBObject("v", 10))); } - /** - * @see DATAMONGO-960 - */ - @Test + @Test // DATAMONGO-960 public void rendersAggregationOptionsInTypedAggregationContextCorrectly() { AggregationOperationContext context = getContext(FooPerson.class); @@ -173,10 +162,39 @@ public void rendersAggregationOptionsInTypedAggregationContextCorrectly() { assertThat(dbo.get("cursor"), is((Object) new BasicDBObject("foo", 1))); } - /** - * @see DATAMONGO-1133 - */ - @Test + @Test // DATAMONGO-1585 + public void rendersSortOfProjectedFieldCorrectly() { + + TypeBasedAggregationOperationContext context = getContext(MeterData.class); + TypedAggregation agg = newAggregation(MeterData.class, project().and("counterName").as("counter"), // + sort(Direction.ASC, "counter")); + + DBObject dbo = agg.toDbObject("meterData", context); + DBObject sort = getPipelineElementFromAggregationAt(dbo, 1); + + DBObject definition = (DBObject) sort.get("$sort"); + assertThat(definition.get("counter"), is(equalTo((Object) 1))); + } + + @Test // DATAMONGO-1586 + public void rendersFieldAliasingProjectionCorrectly() { + + AggregationOperationContext context = getContext(FooPerson.class); + TypedAggregation agg = newAggregation(FooPerson.class, + project() // + .and("name").as("person_name") // + .and("age.value").as("age")); + + DBObject dbo = agg.toDbObject("person", context); + + DBObject projection = getPipelineElementFromAggregationAt(dbo, 0); + assertThat(getAsDBObject(projection, "$project"), + isBsonObject() // + .containing("person_name", "$name") // + .containing("age", "$age.value")); + } + + @Test // DATAMONGO-1133 public void shouldHonorAliasedFieldsInGroupExpressions() { TypeBasedAggregationOperationContext context = getContext(MeterData.class); @@ -191,15 +209,13 @@ public void shouldHonorAliasedFieldsInGroupExpressions() { assertThat(definition.get("_id"), is(equalTo((Object) "$counter_name"))); } - /** - * @see DATAMONGO-1326 - */ - @Test + @Test // DATAMONGO-1326, DATAMONGO-1585 public void lookupShouldInheritFieldsFromInheritingAggregationOperation() { TypeBasedAggregationOperationContext context = getContext(MeterData.class); TypedAggregation agg = newAggregation(MeterData.class, - lookup("OtherCollection", "resourceId", "otherId", "lookup"), sort(Direction.ASC, "resourceId")); + lookup("OtherCollection", "resourceId", "otherId", "lookup"), // + sort(Direction.ASC, "resourceId", "counterName")); DBObject dbo = agg.toDbObject("meterData", context); DBObject sort = getPipelineElementFromAggregationAt(dbo, 1); @@ -207,12 +223,10 @@ public void lookupShouldInheritFieldsFromInheritingAggregationOperation() { DBObject definition = (DBObject) sort.get("$sort"); assertThat(definition.get("resourceId"), is(equalTo((Object) 1))); + assertThat(definition.get("counter_name"), is(equalTo((Object) 1))); } - /** - * @see DATAMONGO-1326 - */ - @Test + @Test // DATAMONGO-1326 public void groupLookupShouldInheritFieldsFromPreviousAggregationOperation() { TypeBasedAggregationOperationContext context = getContext(MeterData.class); @@ -227,10 +241,7 @@ public void groupLookupShouldInheritFieldsFromPreviousAggregationOperation() { assertThat(definition.get("foreignKey"), is(equalTo((Object) 1))); } - /** - * @see DATAMONGO-1326 - */ - @Test + @Test // DATAMONGO-1326 public void lookupGroupAggregationShouldUseCorrectGroupField() { TypeBasedAggregationOperationContext context = getContext(MeterData.class); @@ -247,10 +258,7 @@ public void lookupGroupAggregationShouldUseCorrectGroupField() { assertThat(field.get("$min"), is(equalTo((Object) "$lookup.otherkey"))); } - /** - * @see DATAMONGO-1326 - */ - @Test + @Test // DATAMONGO-1326 public void lookupGroupAggregationShouldOverwriteExposedFields() { TypeBasedAggregationOperationContext context = getContext(MeterData.class); @@ -267,10 +275,7 @@ public void lookupGroupAggregationShouldOverwriteExposedFields() { assertThat(definition.get("something_totally_different"), is(equalTo((Object) 1))); } - /** - * @see DATAMONGO-1326 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-1326 public void lookupGroupAggregationShouldFailInvalidFieldReference() { TypeBasedAggregationOperationContext context = getContext(MeterData.class); @@ -281,17 +286,15 @@ public void lookupGroupAggregationShouldFailInvalidFieldReference() { agg.toDbObject("meterData", context); } - /** - * @see DATAMONGO-861 - */ - @Test + @Test // DATAMONGO-861 public void rendersAggregationConditionalInTypedAggregationContextCorrectly() { AggregationOperationContext context = getContext(FooPerson.class); TypedAggregation agg = newAggregation(FooPerson.class, project("name") // .and("age") // - .applyCondition(conditional(Criteria.where("age.value").lt(10), new Age(0), field("age"))) // + .applyCondition( + ConditionalOperators.when(Criteria.where("age.value").lt(10)).then(new Age(0)).otherwiseValueOf("age")) // ); DBObject dbo = agg.toDbObject("person", context); @@ -307,17 +310,14 @@ public void rendersAggregationConditionalInTypedAggregationContextCorrectly() { assertThat((DBObject) getValue(age, "$cond"), isBsonObject().containing("else", "$age")); } - /** - * @see DATAMONGO-861 - */ - @Test + @Test // DATAMONGO-861, DATAMONGO-1542 public void rendersAggregationIfNullInTypedAggregationContextCorrectly() { AggregationOperationContext context = getContext(FooPerson.class); TypedAggregation agg = newAggregation(FooPerson.class, project("name") // .and("age") // - .applyCondition(ifNull("age", new Age(0))) // + .applyCondition(ConditionalOperators.ifNull("age").then(new Age(0))) // ); DBObject dbo = agg.toDbObject("person", context); @@ -328,6 +328,9 @@ public void rendersAggregationIfNullInTypedAggregationContextCorrectly() { DBObject project = getValue(projection, "$project"); DBObject age = getValue(project, "age"); + assertThat(age, is(JSON.parse( + "{ $ifNull: [ \"$age\", { \"_class\":\"org.springframework.data.mongodb.core.aggregation.TypeBasedAggregationOperationContextUnitTests$Age\", \"value\": 0} ] }"))); + assertThat(age, isBsonObject().containing("$ifNull.[0]", "$age")); assertThat(age, isBsonObject().containing("$ifNull.[1].value", 0)); assertThat(age, isBsonObject().containing("$ifNull.[1]._class", Age.class.getName())); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/UnwindOperationUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/UnwindOperationUnitTests.java index 7327c0e876..b9baa89872 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/UnwindOperationUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/UnwindOperationUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 the original author or authors. + * Copyright 2016-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,10 +32,7 @@ */ public class UnwindOperationUnitTests { - /** - * @see DATAMONGO-1391 - */ - @Test + @Test // DATAMONGO-1391 public void unwindWithPathOnlyShouldUsePreMongo32Syntax() { UnwindOperation unwindOperation = Aggregation.unwind("a"); @@ -45,10 +42,7 @@ public void unwindWithPathOnlyShouldUsePreMongo32Syntax() { assertThat(pipeline, isBsonObject().containing("$unwind", "$a")); } - /** - * @see DATAMONGO-1391 - */ - @Test + @Test // DATAMONGO-1391 public void unwindWithArrayIndexShouldUseMongo32Syntax() { UnwindOperation unwindOperation = Aggregation.unwind("a", "index"); @@ -61,10 +55,7 @@ public void unwindWithArrayIndexShouldUseMongo32Syntax() { containing("includeArrayIndex", "index")); } - /** - * @see DATAMONGO-1391 - */ - @Test + @Test // DATAMONGO-1391 public void unwindWithArrayIndexShouldExposeArrayIndex() { UnwindOperation unwindOperation = Aggregation.unwind("a", "index"); @@ -72,10 +63,7 @@ public void unwindWithArrayIndexShouldExposeArrayIndex() { assertThat(unwindOperation.getFields().getField("index"), is(not(nullValue()))); } - /** - * @see DATAMONGO-1391 - */ - @Test + @Test // DATAMONGO-1391 public void plainUnwindShouldNotExposeIndex() { UnwindOperation unwindOperation = Aggregation.unwind("a"); @@ -83,10 +71,7 @@ public void plainUnwindShouldNotExposeIndex() { assertThat(unwindOperation.getFields().exposesNoFields(), is(true)); } - /** - * @see DATAMONGO-1391 - */ - @Test + @Test // DATAMONGO-1391 public void unwindWithPreserveNullShouldUseMongo32Syntax() { UnwindOperation unwindOperation = Aggregation.unwind("a", true); @@ -99,10 +84,7 @@ public void unwindWithPreserveNullShouldUseMongo32Syntax() { notContaining("includeArrayIndex")); } - /** - * @see DATAMONGO-1391 - */ - @Test + @Test // DATAMONGO-1391 public void lookupBuilderBuildsCorrectClause() { UnwindOperation unwindOperation = UnwindOperation.newUnwind().path("$foo").noArrayIndex().skipNullAndEmptyArrays(); @@ -111,10 +93,7 @@ public void lookupBuilderBuildsCorrectClause() { assertThat(pipeline, isBsonObject().containing("$unwind", "$foo")); } - /** - * @see DATAMONGO-1391 - */ - @Test + @Test // DATAMONGO-1391 public void lookupBuilderBuildsCorrectClauseForMongo32() { UnwindOperation unwindOperation = UnwindOperation.newUnwind().path("$foo").arrayIndex("myindex") diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ZipInfo.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ZipInfo.java index 005085d25a..1dd3503a1a 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ZipInfo.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ZipInfo.java @@ -7,8 +7,8 @@ /** * Data model from mongodb reference data set * - * @see http://docs.mongodb.org/manual/tutorial/aggregation-examples/ - * @see http://media.mongodb.org/zips.json + * @see Aggregation Examples + * @see () { @@ -130,20 +124,14 @@ public void populatesConversionServiceCorrectly() { assertThat(conversionService.canConvert(String.class, Format.class), is(true)); } - /** - * @see DATAMONGO-259 - */ - @Test + @Test // DATAMONGO-259 public void doesNotConsiderTypeSimpleIfOnlyReadConverterIsRegistered() { CustomConversions conversions = new CustomConversions(Arrays.asList(StringToFormatConverter.INSTANCE)); assertThat(conversions.isSimpleType(Format.class), is(false)); } - /** - * @see DATAMONGO-298 - */ - @Test + @Test // DATAMONGO-298 public void discoversConvertersForSubtypesOfMongoTypes() { CustomConversions conversions = new CustomConversions(Arrays.asList(StringToIntegerConverter.INSTANCE)); @@ -151,10 +139,7 @@ public void discoversConvertersForSubtypesOfMongoTypes() { assertThat(conversions.hasCustomWriteTarget(String.class, Integer.class), is(true)); } - /** - * @see DATAMONGO-342 - */ - @Test + @Test // DATAMONGO-342 public void doesNotHaveConverterForStringToBigIntegerByDefault() { CustomConversions conversions = new CustomConversions(); @@ -166,39 +151,27 @@ public void doesNotHaveConverterForStringToBigIntegerByDefault() { assertThat(conversions.getCustomWriteTarget(String.class), is(nullValue())); } - /** - * @see DATAMONGO-390 - */ - @Test + @Test // DATAMONGO-390 public void considersBinaryASimpleType() { CustomConversions conversions = new CustomConversions(); assertThat(conversions.isSimpleType(Binary.class), is(true)); } - /** - * @see DATAMONGO-462 - */ - @Test + @Test // DATAMONGO-462 public void hasWriteConverterForURL() { CustomConversions conversions = new CustomConversions(); assertThat(conversions.hasCustomWriteTarget(URL.class), is(true)); } - /** - * @see DATAMONGO-462 - */ - @Test + @Test // DATAMONGO-462 public void readTargetForURL() { CustomConversions conversions = new CustomConversions(); assertThat(conversions.hasCustomReadTarget(String.class, URL.class), is(true)); } - /** - * @see DATAMONGO-795 - */ - @Test + @Test // DATAMONGO-795 @SuppressWarnings("rawtypes") public void favorsCustomConverterForIndeterminedTargetType() { @@ -206,10 +179,7 @@ public void favorsCustomConverterForIndeterminedTargetType() { assertThat(conversions.getCustomWriteTarget(DateTime.class, null), is(equalTo((Class) String.class))); } - /** - * @see DATAMONGO-881 - */ - @Test + @Test // DATAMONGO-881 public void customConverterOverridesDefault() { CustomConversions conversions = new CustomConversions(Arrays.asList(CustomDateTimeConverter.INSTANCE)); @@ -219,30 +189,21 @@ public void customConverterOverridesDefault() { assertThat(conversionService.convert(new DateTime(), Date.class), is(new Date(0))); } - /** - * @see DATAMONGO-1001 - */ - @Test + @Test // DATAMONGO-1001 public void shouldSelectPropertCustomWriteTargetForCglibProxiedType() { CustomConversions conversions = new CustomConversions(Arrays.asList(FormatToStringConverter.INSTANCE)); assertThat(conversions.getCustomWriteTarget(createProxyTypeFor(Format.class)), is(typeCompatibleWith(String.class))); } - /** - * @see DATAMONGO-1001 - */ - @Test + @Test // DATAMONGO-1001 public void shouldSelectPropertCustomReadTargetForCglibProxiedType() { CustomConversions conversions = new CustomConversions(Arrays.asList(CustomObjectToStringConverter.INSTANCE)); assertThat(conversions.hasCustomReadTarget(createProxyTypeFor(Object.class), String.class), is(true)); } - /** - * @see DATAMONGO-1131 - */ - @Test + @Test // DATAMONGO-1131 public void registersConvertersForJsr310() { CustomConversions customConversions = new CustomConversions(); @@ -250,10 +211,7 @@ public void registersConvertersForJsr310() { assertThat(customConversions.hasCustomWriteTarget(java.time.LocalDateTime.class), is(true)); } - /** - * @see DATAMONGO-1131 - */ - @Test + @Test // DATAMONGO-1131 public void registersConvertersForThreeTenBackPort() { CustomConversions customConversions = new CustomConversions(); @@ -261,10 +219,7 @@ public void registersConvertersForThreeTenBackPort() { assertThat(customConversions.hasCustomWriteTarget(LocalDateTime.class), is(true)); } - /** - * @see DATAMONGO-1302 - */ - @Test + @Test // DATAMONGO-1302 public void registersConverterFactoryCorrectly() { CustomConversions customConversions = new CustomConversions(Collections.singletonList(new FormatConverterFactory())); @@ -272,10 +227,7 @@ public void registersConverterFactoryCorrectly() { assertThat(customConversions.getCustomWriteTarget(String.class, SimpleDateFormat.class), notNullValue()); } - /** - * @see DATAMONGO-1372 - */ - @Test + @Test // DATAMONGO-1372 public void registersConvertersForCurrency() { CustomConversions customConversions = new CustomConversions(); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/CustomConvertersUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/CustomConvertersUnitTests.java index e26394f111..236c9d3f7f 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/CustomConvertersUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/CustomConvertersUnitTests.java @@ -41,7 +41,6 @@ * Test case to verify correct usage of custom {@link Converter} implementations to be used. * * @author Oliver Gierke - * @see DATADOC-101 */ @RunWith(MockitoJUnitRunner.class) public class CustomConvertersUnitTests { @@ -75,7 +74,7 @@ public void setUp() throws Exception { converter.afterPropertiesSet(); } - @Test + @Test // DATADOC-101 public void nestedToDBObjectConverterGetsInvoked() { Foo foo = new Foo(); @@ -85,7 +84,7 @@ public void nestedToDBObjectConverterGetsInvoked() { verify(barToDBObjectConverter).convert(any(Bar.class)); } - @Test + @Test // DATADOC-101 public void nestedFromDBObjectConverterGetsInvoked() { BasicDBObject dbObject = new BasicDBObject(); @@ -95,21 +94,21 @@ public void nestedFromDBObjectConverterGetsInvoked() { verify(dbObjectToBarConverter).convert(any(DBObject.class)); } - @Test + @Test // DATADOC-101 public void toDBObjectConverterGetsInvoked() { converter.write(new Bar(), new BasicDBObject()); verify(barToDBObjectConverter).convert(any(Bar.class)); } - @Test + @Test // DATADOC-101 public void fromDBObjectConverterGetsInvoked() { converter.read(Bar.class, new BasicDBObject()); verify(dbObjectToBarConverter).convert(any(DBObject.class)); } - @Test + @Test // DATADOC-101 public void foo() { DBObject dbObject = new BasicDBObject(); dbObject.put("foo", null); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DBObjectAccessorUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DBObjectAccessorUnitTests.java index 298d99c1d4..2808759e2b 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DBObjectAccessorUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DBObjectAccessorUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,9 +30,8 @@ import com.mongodb.DBObject; /** - * Unit tests for {@link DbObjectAccessor}. + * Unit tests for {@link DBObjectAccessor}. * - * @see DATAMONGO-766 * @author Oliver Gierke */ public class DBObjectAccessorUnitTests { @@ -41,7 +40,7 @@ public class DBObjectAccessorUnitTests { MongoPersistentEntity projectingTypeEntity = context.getPersistentEntity(ProjectingType.class); MongoPersistentProperty fooProperty = projectingTypeEntity.getPersistentProperty("foo"); - @Test + @Test // DATAMONGO-766 public void putsNestedFieldCorrectly() { DBObject dbObject = new BasicDBObject(); @@ -53,7 +52,7 @@ public void putsNestedFieldCorrectly() { assertThat(aDbObject.get("b"), is((Object) "FooBar")); } - @Test + @Test // DATAMONGO-766 public void getsNestedFieldCorrectly() { DBObject source = new BasicDBObject("a", new BasicDBObject("b", "FooBar")); @@ -62,27 +61,24 @@ public void getsNestedFieldCorrectly() { assertThat(accessor.get(fooProperty), is((Object) "FooBar")); } - @Test + @Test // DATAMONGO-766 public void returnsNullForNonExistingFieldPath() { DBObjectAccessor accessor = new DBObjectAccessor(new BasicDBObject()); assertThat(accessor.get(fooProperty), is(nullValue())); } - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-766 public void rejectsNonBasicDBObjects() { new DBObjectAccessor(new BasicDBList()); } - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-766 public void rejectsNullDBObject() { new DBObjectAccessor(null); } - /** - * @see DATAMONGO-1335 - */ - @Test + @Test // DATAMONGO-1335 public void writesAllNestingsCorrectly() { MongoPersistentEntity entity = context.getPersistentEntity(TypeWithTwoNestings.class); @@ -101,10 +97,7 @@ public void writesAllNestingsCorrectly() { assertThat(nestedA.get("c"), is((Object) "c")); } - /** - * @see DATAMONGO-1471 - */ - @Test + @Test // DATAMONGO-1471 public void exposesAvailabilityOfFields() { DBObjectAccessor accessor = new DBObjectAccessor(new BasicDBObject("a", new BasicDBObject("c", "d"))); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DataMongo273Tests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DataMongo273Tests.java index 5c3ae804e7..7ac9c62f78 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DataMongo273Tests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DataMongo273Tests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2012 the original author or authors. + * Copyright 2011-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -51,10 +51,7 @@ public void setupMongoConverter() { converter.afterPropertiesSet(); } - /** - * @see DATAMONGO-273 - */ - @Test + @Test // DATAMONGO-273 public void convertMapOfThings() { Plane plane = new Plane("Boeing", 4); @@ -77,10 +74,7 @@ public void convertMapOfThings() { assertTrue(mapOfThings2.get("automobile") instanceof Automobile); } - /** - * @see DATAMONGO-294 - */ - @Test + @Test // DATAMONGO-294 @SuppressWarnings({ "rawtypes", "unchecked" }) public void convertListOfThings() { Plane plane = new Plane("Boeing", 4); @@ -102,10 +96,7 @@ public void convertListOfThings() { assertTrue(listOfThings2.get(2) instanceof Automobile); } - /** - * @see DATAMONGO-294 - */ - @Test + @Test // DATAMONGO-294 @SuppressWarnings({ "rawtypes", "unchecked" }) public void convertListOfThings_NestedInMap() { diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DbRefMappingMongoConverterUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DbRefMappingMongoConverterUnitTests.java index 0018840155..240ce9760d 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DbRefMappingMongoConverterUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DbRefMappingMongoConverterUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -87,10 +87,7 @@ public void setUp() { this.converter = new MappingMongoConverter(dbRefResolver, mappingContext); } - /** - * @see DATAMONGO-347 - */ - @Test + @Test // DATAMONGO-347 public void createsSimpleDBRefCorrectly() { Person person = new Person(); @@ -101,10 +98,7 @@ public void createsSimpleDBRefCorrectly() { assertThat(dbRef.getCollectionName(), is("person")); } - /** - * @see DATAMONGO-657 - */ - @Test + @Test // DATAMONGO-657 public void convertDocumentWithMapDBRef() { DBObject mapValDBObject = new BasicDBObject(); @@ -146,10 +140,7 @@ public void convertDocumentWithMapDBRef() { assertThat(read.map.get("test").id, is(BigInteger.ONE)); } - /** - * @see DATAMONGO-347 - */ - @Test + @Test // DATAMONGO-347 public void createsDBRefWithClientSpecCorrectly() { PropertyPath path = PropertyPath.from("person", PersonClient.class); @@ -163,10 +154,7 @@ public void createsDBRefWithClientSpecCorrectly() { assertThat(dbRef.getCollectionName(), is("person")); } - /** - * @see DATAMONGO-348 - */ - @Test + @Test // DATAMONGO-348 public void lazyLoadingProxyForLazyDbRefOnInterface() { String id = "42"; @@ -187,10 +175,7 @@ public void lazyLoadingProxyForLazyDbRefOnInterface() { assertThat(result.dbRefToInterface.get(0).getValue(), is(value)); } - /** - * @see DATAMONGO-348 - */ - @Test + @Test // DATAMONGO-348 public void lazyLoadingProxyForLazyDbRefOnConcreteCollection() { String id = "42"; @@ -212,10 +197,7 @@ public void lazyLoadingProxyForLazyDbRefOnConcreteCollection() { assertThat(result.dbRefToConcreteCollection.get(0).getValue(), is(value)); } - /** - * @see DATAMONGO-348 - */ - @Test + @Test // DATAMONGO-348 public void lazyLoadingProxyForLazyDbRefOnConcreteType() { String id = "42"; @@ -236,10 +218,7 @@ public void lazyLoadingProxyForLazyDbRefOnConcreteType() { assertThat(result.dbRefToConcreteType.getValue(), is(value)); } - /** - * @see DATAMONGO-348 - */ - @Test + @Test // DATAMONGO-348 public void lazyLoadingProxyForLazyDbRefOnConcreteTypeWithPersistenceConstructor() { String id = "42"; @@ -261,10 +240,7 @@ public void lazyLoadingProxyForLazyDbRefOnConcreteTypeWithPersistenceConstructor assertThat(result.dbRefToConcreteTypeWithPersistenceConstructor.getValue(), is(value)); } - /** - * @see DATAMONGO-348 - */ - @Test + @Test // DATAMONGO-348 public void lazyLoadingProxyForLazyDbRefOnConcreteTypeWithPersistenceConstructorButWithoutDefaultConstructor() { String id = "42"; @@ -286,10 +262,7 @@ public void lazyLoadingProxyForLazyDbRefOnConcreteTypeWithPersistenceConstructor assertThat(result.dbRefToConcreteTypeWithPersistenceConstructorWithoutDefaultConstructor.getValue(), is(value)); } - /** - * @see DATAMONGO-348 - */ - @Test + @Test // DATAMONGO-348 public void lazyLoadingProxyForSerializableLazyDbRefOnConcreteType() { String id = "42"; @@ -311,10 +284,7 @@ public void lazyLoadingProxyForSerializableLazyDbRefOnConcreteType() { assertThat(deserializedResult.dbRefToSerializableTarget.getValue(), is(value)); } - /** - * @see DATAMONGO-884 - */ - @Test + @Test // DATAMONGO-884 public void lazyLoadingProxyForToStringObjectMethodOverridingDbref() { String id = "42"; @@ -335,10 +305,7 @@ public void lazyLoadingProxyForToStringObjectMethodOverridingDbref() { assertProxyIsResolved(result.dbRefToToStringObjectMethodOverride, true); } - /** - * @see DATAMONGO-884 - */ - @Test + @Test // DATAMONGO-884 public void callingToStringObjectMethodOnLazyLoadingDbrefShouldNotInitializeProxy() { String id = "42"; @@ -366,10 +333,7 @@ public void callingToStringObjectMethodOnLazyLoadingDbrefShouldNotInitializeProx assertProxyIsResolved(result.dbRefToPlainObject, true); } - /** - * @see DATAMONGO-884 - */ - @Test + @Test // DATAMONGO-884 public void equalsObjectMethodOnLazyLoadingDbrefShouldNotInitializeProxy() { String id = "42"; @@ -395,10 +359,7 @@ public void equalsObjectMethodOnLazyLoadingDbrefShouldNotInitializeProxy() { assertProxyIsResolved(result.dbRefToPlainObject, false); } - /** - * @see DATAMONGO-884 - */ - @Test + @Test // DATAMONGO-884 public void hashcodeObjectMethodOnLazyLoadingDbrefShouldNotInitializeProxy() { String id = "42"; @@ -422,10 +383,7 @@ public void hashcodeObjectMethodOnLazyLoadingDbrefShouldNotInitializeProxy() { assertProxyIsResolved(result.dbRefToPlainObject, false); } - /** - * @see DATAMONGO-884 - */ - @Test + @Test // DATAMONGO-884 public void lazyLoadingProxyForEqualsAndHashcodeObjectMethodOverridingDbref() { String id = "42"; @@ -454,10 +412,7 @@ public void lazyLoadingProxyForEqualsAndHashcodeObjectMethodOverridingDbref() { assertProxyIsResolved(result.dbRefEqualsAndHashcodeObjectMethodOverride2, true); } - /** - * @see DATAMONGO-987 - */ - @Test + @Test // DATAMONGO-987 public void shouldNotGenerateLazyLoadingProxyForNullValues() { DBObject dbo = new BasicDBObject(); @@ -475,10 +430,7 @@ public void shouldNotGenerateLazyLoadingProxyForNullValues() { assertThat(result.dbRefToConcreteTypeWithPersistenceConstructorWithoutDefaultConstructor, is(nullValue())); } - /** - * @see DATAMONGO-1005 - */ - @Test + @Test // DATAMONGO-1005 public void shouldBeAbleToStoreDirectReferencesToSelf() { DBObject dbo = new BasicDBObject(); @@ -494,10 +446,7 @@ public void shouldBeAbleToStoreDirectReferencesToSelf() { assertThat(found.reference, is(found)); } - /** - * @see DATAMONGO-1005 - */ - @Test + @Test // DATAMONGO-1005 public void shouldBeAbleToStoreNestedReferencesToSelf() { DBObject dbo = new BasicDBObject(); @@ -516,10 +465,7 @@ public void shouldBeAbleToStoreNestedReferencesToSelf() { assertThat(found.nested.reference, is(found)); } - /** - * @see DATAMONGO-1012 - */ - @Test + @Test // DATAMONGO-1012 public void shouldEagerlyResolveIdPropertyWithFieldAccess() { MongoPersistentEntity entity = mappingContext.getPersistentEntity(ClassWithLazyDbRefs.class); @@ -540,10 +486,7 @@ public void shouldEagerlyResolveIdPropertyWithFieldAccess() { assertProxyIsResolved(result.dbRefToConcreteType, false); } - /** - * @see DATAMONGO-1012 - */ - @Test + @Test // DATAMONGO-1012 public void shouldNotEagerlyResolveIdPropertyWithPropertyAccess() { MongoPersistentEntity entity = mappingContext.getPersistentEntity(ClassWithLazyDbRefs.class); @@ -561,10 +504,7 @@ public void shouldNotEagerlyResolveIdPropertyWithPropertyAccess() { assertProxyIsResolved(proxy, false); } - /** - * @see DATAMONGO-1076 - */ - @Test + @Test // DATAMONGO-1076 public void shouldNotTriggerResolvingOfLazyLoadedProxyWhenFinalizeMethodIsInvoked() throws Exception { MongoPersistentEntity entity = mappingContext.getPersistentEntity(WithObjectMethodOverrideLazyDbRefs.class); @@ -581,10 +521,7 @@ public void shouldNotTriggerResolvingOfLazyLoadedProxyWhenFinalizeMethodIsInvoke assertProxyIsResolved(result.dbRefToPlainObject, false); } - /** - * @see DATAMONGO-1194 - */ - @Test + @Test // DATAMONGO-1194 public void shouldBulkFetchListOfReferences() { String id1 = "1"; @@ -611,10 +548,7 @@ public void shouldBulkFetchListOfReferences() { verify(converterSpy, never()).readRef(Mockito.any(DBRef.class)); } - /** - * @see DATAMONGO-1194 - */ - @Test + @Test // DATAMONGO-1194 public void shouldFallbackToOneByOneFetchingWhenElementsInListOfReferencesPointToDifferentCollections() { String id1 = "1"; @@ -643,10 +577,7 @@ public void shouldFallbackToOneByOneFetchingWhenElementsInListOfReferencesPointT verify(converterSpy, never()).bulkReadRefs(anyListOf(DBRef.class)); } - /** - * @see DATAMONGO-1194 - */ - @Test + @Test // DATAMONGO-1194 public void shouldBulkFetchMapOfReferences() { MapDBRefVal val1 = new MapDBRefVal(); @@ -678,10 +609,7 @@ public void shouldBulkFetchMapOfReferences() { verify(converterSpy, never()).readRef(Mockito.any(DBRef.class)); } - /** - * @see DATAMONGO-1194 - */ - @Test + @Test // DATAMONGO-1194 public void shouldBulkFetchLazyMapOfReferences() { MapDBRefVal val1 = new MapDBRefVal(); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DefaultDbRefResolverUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DefaultDbRefResolverUnitTests.java index 8bbfdde43c..ba444fe257 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DefaultDbRefResolverUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DefaultDbRefResolverUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 the original author or authors. + * Copyright 2016-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -69,10 +69,7 @@ public void setUp() { resolver = new DefaultDbRefResolver(factoryMock); } - /** - * @see DATAMONGO-1194 - */ - @Test + @Test // DATAMONGO-1194 @SuppressWarnings("unchecked") public void bulkFetchShouldLoadDbRefsCorrectly() { @@ -91,10 +88,7 @@ public void bulkFetchShouldLoadDbRefsCorrectly() { assertThat($in, iterableWithSize(2)); } - /** - * @see DATAMONGO-1194 - */ - @Test(expected = InvalidDataAccessApiUsageException.class) + @Test(expected = InvalidDataAccessApiUsageException.class) // DATAMONGO-1194 public void bulkFetchShouldThrowExceptionWhenUsingDifferntCollectionsWithinSetOfReferences() { DBRef ref1 = new DBRef("collection-1", new ObjectId()); @@ -103,10 +97,7 @@ public void bulkFetchShouldThrowExceptionWhenUsingDifferntCollectionsWithinSetOf resolver.bulkFetch(Arrays.asList(ref1, ref2)); } - /** - * @see DATAMONGO-1194 - */ - @Test + @Test // DATAMONGO-1194 public void bulkFetchShouldReturnEarlyForEmptyLists() { resolver.bulkFetch(Collections.emptyList()); @@ -114,10 +105,7 @@ public void bulkFetchShouldReturnEarlyForEmptyLists() { verify(collectionMock, never()).find(Mockito.any(DBObject.class)); } - /** - * @see DATAMONGO-1194 - */ - @Test + @Test // DATAMONGO-1194 public void bulkFetchShouldRestoreOriginalOrder() { DBObject o1 = new BasicDBObject("_id", new ObjectId()); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DefaultMongoTypeMapperUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DefaultMongoTypeMapperUnitTests.java index 2cb4560cc2..0678922cfc 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DefaultMongoTypeMapperUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DefaultMongoTypeMapperUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2016 the original author or authors. + * Copyright 2011-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -109,10 +109,7 @@ public void readsTypeLoadingClassesForUnmappedTypesIfConfigured() { Object.class); } - /** - * @see DATAMONGO-709 - */ - @Test + @Test // DATAMONGO-709 public void writesTypeRestrictionsCorrectly() { DBObject result = new BasicDBObject(); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/GeoConvertersUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/GeoConvertersUnitTests.java index 3ff7f3e514..6181d9cfd1 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/GeoConvertersUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/GeoConvertersUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 the original author or authors. + * Copyright 2014-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -41,6 +41,7 @@ import org.springframework.data.mongodb.core.geo.Sphere; import org.springframework.data.mongodb.core.query.GeoCommand; +import com.mongodb.BasicDBObject; import com.mongodb.DBObject; /** @@ -48,14 +49,12 @@ * * @author Thomas Darimont * @author Oliver Gierke + * @author Christoph Strobl * @since 1.5 */ public class GeoConvertersUnitTests { - /** - * @see DATAMONGO-858 - */ - @Test + @Test // DATAMONGO-858 public void convertsBoxToDbObjectAndBackCorrectly() { Box box = new Box(new Point(1, 2), new Point(3, 4)); @@ -67,10 +66,7 @@ public void convertsBoxToDbObjectAndBackCorrectly() { assertThat(result.getClass().equals(Box.class), is(true)); } - /** - * @see DATAMONGO-858 - */ - @Test + @Test // DATAMONGO-858 public void convertsCircleToDbObjectAndBackCorrectlyNeutralDistance() { Circle circle = new Circle(new Point(1, 2), 3); @@ -81,10 +77,7 @@ public void convertsCircleToDbObjectAndBackCorrectlyNeutralDistance() { assertThat(result, is(circle)); } - /** - * @see DATAMONGO-858 - */ - @Test + @Test // DATAMONGO-858 public void convertsCircleToDbObjectAndBackCorrectlyMilesDistance() { Distance radius = new Distance(3, Metrics.MILES); @@ -97,10 +90,7 @@ public void convertsCircleToDbObjectAndBackCorrectlyMilesDistance() { assertThat(result.getRadius(), is(radius)); } - /** - * @see DATAMONGO-858 - */ - @Test + @Test // DATAMONGO-858 public void convertsPolygonToDbObjectAndBackCorrectly() { Polygon polygon = new Polygon(new Point(1, 2), new Point(2, 3), new Point(3, 4), new Point(5, 6)); @@ -112,10 +102,7 @@ public void convertsPolygonToDbObjectAndBackCorrectly() { assertThat(result.getClass().equals(Polygon.class), is(true)); } - /** - * @see DATAMONGO-858 - */ - @Test + @Test // DATAMONGO-858 public void convertsSphereToDbObjectAndBackCorrectlyWithNeutralDistance() { Sphere sphere = new Sphere(new Point(1, 2), 3); @@ -127,10 +114,7 @@ public void convertsSphereToDbObjectAndBackCorrectlyWithNeutralDistance() { assertThat(result.getClass().equals(Sphere.class), is(true)); } - /** - * @see DATAMONGO-858 - */ - @Test + @Test // DATAMONGO-858 public void convertsSphereToDbObjectAndBackCorrectlyWithKilometerDistance() { Distance radius = new Distance(3, Metrics.KILOMETERS); @@ -144,10 +128,7 @@ public void convertsSphereToDbObjectAndBackCorrectlyWithKilometerDistance() { assertThat(result.getClass().equals(org.springframework.data.mongodb.core.geo.Sphere.class), is(true)); } - /** - * @see DATAMONGO-858 - */ - @Test + @Test // DATAMONGO-858 public void convertsPointToListAndBackCorrectly() { Point point = new Point(1, 2); @@ -159,10 +140,7 @@ public void convertsPointToListAndBackCorrectly() { assertThat(result.getClass().equals(Point.class), is(true)); } - /** - * @see DATAMONGO-858 - */ - @Test + @Test // DATAMONGO-858 public void convertsGeoCommandToDbObjectCorrectly() { Box box = new Box(new double[] { 1, 2 }, new double[] { 3, 4 }); @@ -177,4 +155,32 @@ public void convertsGeoCommandToDbObjectCorrectly() { assertThat(boxObject, is((Object) Arrays.asList(GeoConverters.toList(box.getFirst()), GeoConverters.toList(box.getSecond())))); } + + @Test // DATAMONGO-1607 + public void convertsPointCorrectlyWhenUsingNonDoubleForCoordinates() { + + assertThat(DbObjectToPointConverter.INSTANCE.convert(new BasicDBObject().append("x", 1L).append("y", 2L)), + is(new Point(1, 2))); + } + + @Test // DATAMONGO-1607 + public void convertsCircleCorrectlyWhenUsingNonDoubleForCoordinates() { + + DBObject circle = new BasicDBObject(); + circle.put("center", new BasicDBObject().append("x", 1).append("y", 2)); + circle.put("radius", 3L); + + assertThat(DbObjectToCircleConverter.INSTANCE.convert(circle), is(new Circle(new Point(1, 2), new Distance(3)))); + } + + @Test // DATAMONGO-1607 + public void convertsSphereCorrectlyWhenUsingNonDoubleForCoordinates() { + + DBObject sphere = new BasicDBObject(); + sphere.put("center", new BasicDBObject().append("x", 1).append("y", 2)); + sphere.put("radius", 3L); + + assertThat(DbObjectToSphereConverter.INSTANCE.convert(sphere), is(new Sphere(new Point(1, 2), new Distance(3)))); + } + } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/GeoJsonConverterUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/GeoJsonConverterUnitTests.java index 935d135e83..24e98979aa 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/GeoJsonConverterUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/GeoJsonConverterUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2016 the original author or authors. + * Copyright 2015-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -185,26 +185,17 @@ public static class DbObjectToGeoJsonPolygonConverterUnitTests { DbObjectToGeoJsonPolygonConverter converter = DbObjectToGeoJsonPolygonConverter.INSTANCE; public @Rule ExpectedException expectedException = ExpectedException.none(); - /** - * @see DATAMONGO-1137 - */ - @Test + @Test // DATAMONGO-1137 public void shouldConvertDboCorrectly() { assertThat(converter.convert(POLYGON_DBO), equalTo(POLYGON)); } - /** - * @see DATAMONGO-1137 - */ - @Test + @Test // DATAMONGO-1137 public void shouldReturnNullWhenConvertIsGivenNull() { assertThat(converter.convert(null), nullValue()); } - /** - * @see DATAMONGO-1137 - */ - @Test + @Test // DATAMONGO-1137 public void shouldThrowExceptionWhenTypeDoesNotMatchPolygon() { expectedException.expect(IllegalArgumentException.class); @@ -213,10 +204,7 @@ public void shouldThrowExceptionWhenTypeDoesNotMatchPolygon() { converter.convert(new BasicDBObject("type", "YouDontKonwMe")); } - /** - * @see DATAMONGO-1399 - */ - @Test + @Test // DATAMONGO-1399 public void shouldConvertDboWithMultipleRingsCorrectly() { assertThat(converter.convert(POLYGON_WITH_2_RINGS_DBO), equalTo(POLYGON_WITH_2_RINGS)); } @@ -231,26 +219,17 @@ public static class DbObjectToGeoJsonPointConverterUnitTests { DbObjectToGeoJsonPointConverter converter = DbObjectToGeoJsonPointConverter.INSTANCE; public @Rule ExpectedException expectedException = ExpectedException.none(); - /** - * @see DATAMONGO-1137 - */ - @Test + @Test // DATAMONGO-1137 public void shouldConvertDboCorrectly() { assertThat(converter.convert(SINGLE_POINT_DBO), equalTo(SINGLE_POINT)); } - /** - * @see DATAMONGO-1137 - */ - @Test + @Test // DATAMONGO-1137 public void shouldReturnNullWhenConvertIsGivenNull() { assertThat(converter.convert(null), nullValue()); } - /** - * @see DATAMONGO-1137 - */ - @Test + @Test // DATAMONGO-1137 public void shouldThrowExceptionWhenTypeDoesNotMatchPoint() { expectedException.expect(IllegalArgumentException.class); @@ -268,26 +247,17 @@ public static class DbObjectToGeoJsonLineStringConverterUnitTests { DbObjectToGeoJsonLineStringConverter converter = DbObjectToGeoJsonLineStringConverter.INSTANCE; public @Rule ExpectedException expectedException = ExpectedException.none(); - /** - * @see DATAMONGO-1137 - */ - @Test + @Test // DATAMONGO-1137 public void shouldConvertDboCorrectly() { assertThat(converter.convert(LINE_STRING_DBO), equalTo(LINE_STRING)); } - /** - * @see DATAMONGO-1137 - */ - @Test + @Test // DATAMONGO-1137 public void shouldReturnNullWhenConvertIsGivenNull() { assertThat(converter.convert(null), nullValue()); } - /** - * @see DATAMONGO-1137 - */ - @Test + @Test // DATAMONGO-1137 public void shouldThrowExceptionWhenTypeDoesNotMatchPoint() { expectedException.expect(IllegalArgumentException.class); @@ -305,26 +275,17 @@ public static class DbObjectToGeoJsonMultiLineStringConverterUnitTests { DbObjectToGeoJsonMultiLineStringConverter converter = DbObjectToGeoJsonMultiLineStringConverter.INSTANCE; public @Rule ExpectedException expectedException = ExpectedException.none(); - /** - * @see DATAMONGO-1137 - */ - @Test + @Test // DATAMONGO-1137 public void shouldConvertDboCorrectly() { assertThat(converter.convert(MULTI_LINE_STRING_DBO), equalTo(MULTI_LINE_STRING)); } - /** - * @see DATAMONGO-1137 - */ - @Test + @Test // DATAMONGO-1137 public void shouldReturnNullWhenConvertIsGivenNull() { assertThat(converter.convert(null), nullValue()); } - /** - * @see DATAMONGO-1137 - */ - @Test + @Test // DATAMONGO-1137 public void shouldThrowExceptionWhenTypeDoesNotMatchPoint() { expectedException.expect(IllegalArgumentException.class); @@ -342,26 +303,17 @@ public static class DbObjectToGeoJsonMultiPointConverterUnitTests { DbObjectToGeoJsonMultiPointConverter converter = DbObjectToGeoJsonMultiPointConverter.INSTANCE; public @Rule ExpectedException expectedException = ExpectedException.none(); - /** - * @see DATAMONGO-1137 - */ - @Test + @Test // DATAMONGO-1137 public void shouldConvertDboCorrectly() { assertThat(converter.convert(MULTI_POINT_DBO), equalTo(MULTI_POINT)); } - /** - * @see DATAMONGO-1137 - */ - @Test + @Test // DATAMONGO-1137 public void shouldReturnNullWhenConvertIsGivenNull() { assertThat(converter.convert(null), nullValue()); } - /** - * @see DATAMONGO-1137 - */ - @Test + @Test // DATAMONGO-1137 public void shouldThrowExceptionWhenTypeDoesNotMatchPoint() { expectedException.expect(IllegalArgumentException.class); @@ -379,26 +331,17 @@ public static class DbObjectToGeoJsonMultiPolygonConverterUnitTests { DbObjectToGeoJsonMultiPolygonConverter converter = DbObjectToGeoJsonMultiPolygonConverter.INSTANCE; public @Rule ExpectedException expectedException = ExpectedException.none(); - /** - * @see DATAMONGO-1137 - */ - @Test + @Test // DATAMONGO-1137 public void shouldConvertDboCorrectly() { assertThat(converter.convert(MULTI_POLYGON_DBO), equalTo(MULTI_POLYGON)); } - /** - * @see DATAMONGO-1137 - */ - @Test + @Test // DATAMONGO-1137 public void shouldReturnNullWhenConvertIsGivenNull() { assertThat(converter.convert(null), nullValue()); } - /** - * @see DATAMONGO-1137 - */ - @Test + @Test // DATAMONGO-1137 public void shouldThrowExceptionWhenTypeDoesNotMatchPoint() { expectedException.expect(IllegalArgumentException.class); @@ -415,73 +358,47 @@ public static class GeoJsonToDbObjectConverterUnitTests { GeoJsonToDbObjectConverter converter = GeoJsonToDbObjectConverter.INSTANCE; - /** - * @see DATAMONGO-1135 - */ + // DATAMONGO-1135 public void convertShouldReturnNullWhenGivenNull() { assertThat(converter.convert(null), nullValue()); } - /** - * @see DATAMONGO-1135 - */ - @Test + @Test // DATAMONGO-1135 public void shouldConvertGeoJsonPointCorrectly() { assertThat(converter.convert(SINGLE_POINT), equalTo(SINGLE_POINT_DBO)); } - /** - * @see DATAMONGO-1135 - */ - @Test + @Test // DATAMONGO-1135 public void shouldConvertGeoJsonPolygonCorrectly() { assertThat(converter.convert(POLYGON), equalTo(POLYGON_DBO)); } - /** - * @see DATAMONGO-1137 - */ - @Test + @Test // DATAMONGO-1137 public void shouldConvertGeoJsonLineStringCorrectly() { assertThat(converter.convert(LINE_STRING), equalTo(LINE_STRING_DBO)); } - /** - * @see DATAMONGO-1137 - */ - @Test + @Test // DATAMONGO-1137 public void shouldConvertGeoJsonMultiLineStringCorrectly() { assertThat(converter.convert(MULTI_LINE_STRING), equalTo(MULTI_LINE_STRING_DBO)); } - /** - * @see DATAMONGO-1137 - */ - @Test + @Test // DATAMONGO-1137 public void shouldConvertGeoJsonMultiPointCorrectly() { assertThat(converter.convert(MULTI_POINT), equalTo(MULTI_POINT_DBO)); } - /** - * @see DATAMONGO-1137 - */ - @Test + @Test // DATAMONGO-1137 public void shouldConvertGeoJsonMultiPolygonCorrectly() { assertThat(converter.convert(MULTI_POLYGON), equalTo(MULTI_POLYGON_DBO)); } - /** - * @see DATAMONGO-1137 - */ - @Test + @Test // DATAMONGO-1137 public void shouldConvertGeometryCollectionCorrectly() { assertThat(converter.convert(GEOMETRY_COLLECTION), equalTo(GEOMETRY_COLLECTION_DBO)); } - /** - * @see DATAMONGO-1399 - */ - @Test + @Test // DATAMONGO-1399 public void shouldConvertGeoJsonPolygonWithMultipleRingsCorrectly() { assertThat(converter.convert(POLYGON_WITH_2_RINGS), equalTo(POLYGON_WITH_2_RINGS_DBO)); } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/LazyLoadingInterceptorUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/LazyLoadingInterceptorUnitTests.java index 1cdfb776bb..bec7e31ea1 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/LazyLoadingInterceptorUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/LazyLoadingInterceptorUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 the original author or authors. + * Copyright 2016-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -47,10 +47,7 @@ public class LazyLoadingInterceptorUnitTests { @Mock DBRef dbrefMock; @Mock DbRefResolverCallback callbackMock; - /** - * @see DATAMONGO-1437 - */ - @Test + @Test // DATAMONGO-1437 public void shouldPreserveCauseForNonTranslatableExceptions() throws Throwable { NullPointerException npe = new NullPointerException("Some Exception we did not think about."); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterUnitTests.java index f8d00bc8ba..c4a0dfdb38 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2015 the original author or authors. + * Copyright 2011-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -166,10 +166,7 @@ public void convertsCustomTypeOnConvertToMongoType() { converter.convertToMongoType(date); } - /** - * @see DATAMONGO-130 - */ - @Test + @Test // DATAMONGO-130 public void writesMapTypeCorrectly() { Map map = Collections.singletonMap(Locale.US, "Foo"); @@ -180,10 +177,7 @@ public void writesMapTypeCorrectly() { assertThat(dbObject.get(Locale.US.toString()).toString(), is("Foo")); } - /** - * @see DATAMONGO-130 - */ - @Test + @Test // DATAMONGO-130 public void readsMapWithCustomKeyTypeCorrectly() { DBObject mapObject = new BasicDBObject(Locale.US.toString(), "Value"); @@ -193,10 +187,7 @@ public void readsMapWithCustomKeyTypeCorrectly() { assertThat(result.map.get(Locale.US), is("Value")); } - /** - * @see DATAMONGO-128 - */ - @Test + @Test // DATAMONGO-128 public void usesDocumentsStoredTypeIfSubtypeOfRequest() { DBObject dbObject = new BasicDBObject(); @@ -206,10 +197,7 @@ public void usesDocumentsStoredTypeIfSubtypeOfRequest() { assertThat(converter.read(Contact.class, dbObject), is(instanceOf(Person.class))); } - /** - * @see DATAMONGO-128 - */ - @Test + @Test // DATAMONGO-128 public void ignoresDocumentsStoredTypeIfCompletelyDifferentTypeRequested() { DBObject dbObject = new BasicDBObject(); @@ -231,10 +219,7 @@ public void writesTypeDiscriminatorIntoRootObject() { assertThat(result.get(DefaultMongoTypeMapper.DEFAULT_TYPE_KEY).toString(), is(Person.class.getName())); } - /** - * @see DATAMONGO-136 - */ - @Test + @Test // DATAMONGO-136 public void writesEnumsCorrectly() { ClassWithEnumProperty value = new ClassWithEnumProperty(); @@ -247,10 +232,7 @@ public void writesEnumsCorrectly() { assertThat(result.get("sampleEnum").toString(), is("FIRST")); } - /** - * @see DATAMONGO-209 - */ - @Test + @Test // DATAMONGO-209 public void writesEnumCollectionCorrectly() { ClassWithEnumProperty value = new ClassWithEnumProperty(); @@ -266,10 +248,7 @@ public void writesEnumCollectionCorrectly() { assertThat((String) enums.get(0), is("FIRST")); } - /** - * @see DATAMONGO-136 - */ - @Test + @Test // DATAMONGO-136 public void readsEnumsCorrectly() { DBObject dbObject = new BasicDBObject("sampleEnum", "FIRST"); ClassWithEnumProperty result = converter.read(ClassWithEnumProperty.class, dbObject); @@ -277,10 +256,7 @@ public void readsEnumsCorrectly() { assertThat(result.sampleEnum, is(SampleEnum.FIRST)); } - /** - * @see DATAMONGO-209 - */ - @Test + @Test // DATAMONGO-209 public void readsEnumCollectionsCorrectly() { BasicDBList enums = new BasicDBList(); @@ -294,10 +270,7 @@ public void readsEnumCollectionsCorrectly() { assertThat(result.enums, hasItem(SampleEnum.FIRST)); } - /** - * @see DATAMONGO-144 - */ - @Test + @Test // DATAMONGO-144 public void considersFieldNameWhenWriting() { Person person = new Person(); @@ -310,10 +283,7 @@ public void considersFieldNameWhenWriting() { assertThat(result.containsField("firstname"), is(false)); } - /** - * @see DATAMONGO-144 - */ - @Test + @Test // DATAMONGO-144 public void considersFieldNameWhenReading() { DBObject dbObject = new BasicDBObject("foo", "Oliver"); @@ -338,10 +308,7 @@ public void resolvesNestedComplexTypeForConstructorCorrectly() { assertThat(result.addresses, is(notNullValue())); } - /** - * @see DATAMONGO-145 - */ - @Test + @Test // DATAMONGO-145 public void writesCollectionWithInterfaceCorrectly() { Person person = new Person(); @@ -361,10 +328,7 @@ public void writesCollectionWithInterfaceCorrectly() { assertThat((String) personDbObject.get(DefaultMongoTypeMapper.DEFAULT_TYPE_KEY), is(Person.class.getName())); } - /** - * @see DATAMONGO-145 - */ - @Test + @Test // DATAMONGO-145 public void readsCollectionWithInterfaceCorrectly() { BasicDBObject person = new BasicDBObject(DefaultMongoTypeMapper.DEFAULT_TYPE_KEY, Person.class.getName()); @@ -397,10 +361,7 @@ public void convertsLocalesOutOfTheBox() { assertThat(read.locale, is(Locale.US)); } - /** - * @see DATAMONGO-161 - */ - @Test + @Test // DATAMONGO-161 public void readsNestedMapsCorrectly() { Map secondLevel = new HashMap(); @@ -424,10 +385,7 @@ public void readsNestedMapsCorrectly() { assertThat(nestedMap.get("afield"), is(firstLevel)); } - /** - * @see DATACMNS-42, DATAMONGO-171 - */ - @Test + @Test // DATACMNS-42, DATAMONGO-171 public void writesClassWithBigDecimal() { BigDecimalContainer container = new BigDecimalContainer(); @@ -442,10 +400,7 @@ public void writesClassWithBigDecimal() { assertThat(((DBObject) dbObject.get("map")).get("foo"), is(instanceOf(String.class))); } - /** - * @see DATACMNS-42, DATAMONGO-171 - */ - @Test + @Test // DATACMNS-42, DATAMONGO-171 public void readsClassWithBigDecimal() { DBObject dbObject = new BasicDBObject("value", "2.5"); @@ -478,10 +433,7 @@ public void writesNestedCollectionsCorrectly() { assertThat(typedOuterString.size(), is(1)); } - /** - * @see DATAMONGO-192 - */ - @Test + @Test // DATAMONGO-192 public void readsEmptySetsCorrectly() { Person person = new Person(); @@ -507,10 +459,7 @@ public void convertsObjectIdStringsToObjectIdCorrectly() { assertThat(dbo2.get("_id"), is(instanceOf(ObjectId.class))); } - /** - * @see DATAMONGO-207 - */ - @Test + @Test // DATAMONGO-207 public void convertsCustomEmptyMapCorrectly() { DBObject map = new BasicDBObject(); @@ -522,10 +471,7 @@ public void convertsCustomEmptyMapCorrectly() { assertThat(result.map, is(instanceOf(SortedMap.class))); } - /** - * @see DATAMONGO-211 - */ - @Test + @Test // DATAMONGO-211 public void maybeConvertHandlesNullValuesCorrectly() { assertThat(converter.convertToMongoType(null), is(nullValue())); } @@ -556,10 +502,7 @@ public void readsGenericTypeCorrectly() { } - /** - * @see DATAMONGO-228 - */ - @Test + @Test // DATAMONGO-228 public void writesNullValuesForMaps() { ClassWithMapProperty foo = new ClassWithMapProperty(); @@ -591,10 +534,7 @@ public void convertsObjectsIfNecessary() { assertThat(converter.convertToMongoType(id), is((Object) id)); } - /** - * @see DATAMONGO-235 - */ - @Test + @Test // DATAMONGO-235 public void writesMapOfListsCorrectly() { ClassWithMapProperty input = new ClassWithMapProperty(); @@ -615,10 +555,7 @@ public void writesMapOfListsCorrectly() { assertThat((String) value.get(0), is("Bar")); } - /** - * @see DATAMONGO-235 - */ - @Test + @Test // DATAMONGO-235 public void readsMapListValuesCorrectly() { BasicDBList list = new BasicDBList(); @@ -629,10 +566,7 @@ public void readsMapListValuesCorrectly() { assertThat(result.mapOfLists, is(not(nullValue()))); } - /** - * @see DATAMONGO-235 - */ - @Test + @Test // DATAMONGO-235 public void writesMapsOfObjectsCorrectly() { ClassWithMapProperty input = new ClassWithMapProperty(); @@ -654,10 +588,7 @@ public void writesMapsOfObjectsCorrectly() { assertThat((String) value.get(0), is("Bar")); } - /** - * @see DATAMONGO-235 - */ - @Test + @Test // DATAMONGO-235 public void readsMapOfObjectsListValuesCorrectly() { BasicDBList list = new BasicDBList(); @@ -668,10 +599,7 @@ public void readsMapOfObjectsListValuesCorrectly() { assertThat(result.mapOfObjects, is(not(nullValue()))); } - /** - * @see DATAMONGO-245 - */ - @Test + @Test // DATAMONGO-245 public void readsMapListNestedValuesCorrectly() { BasicDBList list = new BasicDBList(); @@ -684,10 +612,7 @@ public void readsMapListNestedValuesCorrectly() { assertThat((String) ((Map) firstObjectInFoo).get("Hello"), is(equalTo("World"))); } - /** - * @see DATAMONGO-245 - */ - @Test + @Test // DATAMONGO-245 public void readsMapDoublyNestedValuesCorrectly() { BasicDBObject nested = new BasicDBObject(); @@ -704,10 +629,7 @@ public void readsMapDoublyNestedValuesCorrectly() { assertThat((String) ((Map) doublyNestedObject).get("Hello"), is(equalTo("World"))); } - /** - * @see DATAMONGO-245 - */ - @Test + @Test // DATAMONGO-245 public void readsMapListDoublyNestedValuesCorrectly() { BasicDBList list = new BasicDBList(); @@ -726,10 +648,7 @@ public void readsMapListDoublyNestedValuesCorrectly() { assertThat((String) ((Map) doublyNestedObject).get("Hello"), is(equalTo("World"))); } - /** - * @see DATAMONGO-259 - */ - @Test + @Test // DATAMONGO-259 public void writesListOfMapsCorrectly() { Map map = Collections.singletonMap("Foo", Locale.ENGLISH); @@ -750,10 +669,7 @@ public void writesListOfMapsCorrectly() { assertThat((String) dbObject.get("Foo"), is(Locale.ENGLISH.toString())); } - /** - * @see DATAMONGO-259 - */ - @Test + @Test // DATAMONGO-259 public void readsListOfMapsCorrectly() { DBObject map = new BasicDBObject("Foo", "en"); @@ -771,10 +687,7 @@ public void readsListOfMapsCorrectly() { assertThat(wrapper.listOfMaps.get(0).get("Foo"), is(Locale.ENGLISH)); } - /** - * @see DATAMONGO-259 - */ - @Test + @Test // DATAMONGO-259 public void writesPlainMapOfCollectionsCorrectly() { Map> map = Collections.singletonMap("Foo", Arrays.asList(Locale.US)); @@ -791,10 +704,7 @@ public void writesPlainMapOfCollectionsCorrectly() { assertThat(list.get(0), is((Object) Locale.US.toString())); } - /** - * @see DATAMONGO-285 - */ - @Test + @Test // DATAMONGO-285 @SuppressWarnings({ "unchecked", "rawtypes" }) public void testSaveMapWithACollectionAsValue() { @@ -819,10 +729,7 @@ public void testSaveMapWithACollectionAsValue() { assertEquals(list.get(1), listFromMongo.get(1)); } - /** - * @see DATAMONGO-309 - */ - @Test + @Test // DATAMONGO-309 @SuppressWarnings({ "unchecked" }) public void writesArraysAsMapValuesCorrectly() { @@ -845,10 +752,7 @@ public void writesArraysAsMapValuesCorrectly() { assertThat(list, hasItem((Object) "bar")); } - /** - * @see DATAMONGO-324 - */ - @Test + @Test // DATAMONGO-324 public void writesDbObjectCorrectly() { DBObject dbObject = new BasicDBObject(); @@ -862,10 +766,7 @@ public void writesDbObjectCorrectly() { assertThat(dbObject, is(result)); } - /** - * @see DATAMONGO-324 - */ - @Test + @Test // DATAMONGO-324 public void readsDbObjectCorrectly() { DBObject dbObject = new BasicDBObject(); @@ -876,10 +777,7 @@ public void readsDbObjectCorrectly() { assertThat(result, is(dbObject)); } - /** - * @see DATAMONGO-329 - */ - @Test + @Test // DATAMONGO-329 public void writesMapAsGenericFieldCorrectly() { Map> objectToSave = new HashMap>(); @@ -915,10 +813,7 @@ public void writesIntIdCorrectly() { assertThat(result.get("_id"), is((Object) 5)); } - /** - * @see DATAMONGO-368 - */ - @Test + @Test // DATAMONGO-368 @SuppressWarnings("unchecked") public void writesNullValuesForCollection() { @@ -934,10 +829,7 @@ public void writesNullValuesForCollection() { assertThat((Collection) contacts, hasItem(nullValue())); } - /** - * @see DATAMONGO-379 - */ - @Test + @Test // DATAMONGO-379 public void considersDefaultingExpressionsAtConstructorArguments() { DBObject dbObject = new BasicDBObject("foo", "bar"); @@ -947,10 +839,7 @@ public void considersDefaultingExpressionsAtConstructorArguments() { assertThat(result.bar, is(-1)); } - /** - * @see DATAMONGO-379 - */ - @Test + @Test // DATAMONGO-379 public void usesDocumentFieldIfReferencedInAtValue() { DBObject dbObject = new BasicDBObject("foo", "bar"); @@ -961,10 +850,7 @@ public void usesDocumentFieldIfReferencedInAtValue() { assertThat(result.bar, is(37)); } - /** - * @see DATAMONGO-379 - */ - @Test(expected = MappingInstantiationException.class) + @Test(expected = MappingInstantiationException.class) // DATAMONGO-379 public void rejectsNotFoundConstructorParameterForPrimitiveType() { DBObject dbObject = new BasicDBObject("foo", "bar"); @@ -972,10 +858,7 @@ public void rejectsNotFoundConstructorParameterForPrimitiveType() { converter.read(DefaultedConstructorArgument.class, dbObject); } - /** - * @see DATAMONGO-358 - */ - @Test + @Test // DATAMONGO-358 public void writesListForObjectPropertyCorrectly() { Attribute attribute = new Attribute(); @@ -1001,18 +884,12 @@ public void writesListForObjectPropertyCorrectly() { assertThat(values, hasItems("1", "2")); } - /** - * @see DATAMONGO-380 - */ - @Test(expected = MappingException.class) + @Test(expected = MappingException.class) // DATAMONGO-380 public void rejectsMapWithKeyContainingDotsByDefault() { converter.write(Collections.singletonMap("foo.bar", "foobar"), new BasicDBObject()); } - /** - * @see DATAMONGO-380 - */ - @Test + @Test // DATAMONGO-380 public void escapesDotInMapKeysIfReplacementConfigured() { converter.setMapKeyDotReplacement("~"); @@ -1024,10 +901,7 @@ public void escapesDotInMapKeysIfReplacementConfigured() { assertThat(dbObject.containsField("foo.bar"), is(false)); } - /** - * @see DATAMONGO-380 - */ - @Test + @Test // DATAMONGO-380 @SuppressWarnings("unchecked") public void unescapesDotInMapKeysIfReplacementConfigured() { @@ -1040,10 +914,7 @@ public void unescapesDotInMapKeysIfReplacementConfigured() { assertThat(result.containsKey("foobar"), is(false)); } - /** - * @see DATAMONGO-382 - */ - @Test + @Test // DATAMONGO-382 public void convertsSetToBasicDBList() { Address address = new Address(); @@ -1058,10 +929,7 @@ public void convertsSetToBasicDBList() { assertThat(readResult.iterator().next(), is(instanceOf(Address.class))); } - /** - * @see DATAMONGO-402 - */ - @Test + @Test // DATAMONGO-402 public void readsMemberClassCorrectly() { DBObject dbObject = new BasicDBObject("inner", new BasicDBObject("value", "FOO!")); @@ -1072,10 +940,7 @@ public void readsMemberClassCorrectly() { assertSyntheticFieldValueOf(outer.inner, outer); } - /** - * @see DATAMONGO-458 - */ - @Test + @Test // DATAMONGO-458 public void readEmptyCollectionIsModifiable() { DBObject dbObject = new BasicDBObject("contactsSet", new BasicDBList()); @@ -1085,10 +950,7 @@ public void readEmptyCollectionIsModifiable() { wrapper.contactsSet.add(new Contact() {}); } - /** - * @see DATAMONGO-424 - */ - @Test + @Test // DATAMONGO-424 public void readsPlainDBRefObject() { DBRef dbRef = new DBRef("foo", 2); @@ -1098,10 +960,7 @@ public void readsPlainDBRefObject() { assertThat(result.ref, is(dbRef)); } - /** - * @see DATAMONGO-424 - */ - @Test + @Test // DATAMONGO-424 public void readsCollectionOfDBRefs() { DBRef dbRef = new DBRef("foo", 2); @@ -1115,10 +974,7 @@ public void readsCollectionOfDBRefs() { assertThat(result.refs, hasItem(dbRef)); } - /** - * @see DATAMONGO-424 - */ - @Test + @Test // DATAMONGO-424 public void readsDBRefMap() { DBRef dbRef = mock(DBRef.class); @@ -1131,10 +987,7 @@ public void readsDBRefMap() { assertThat(result.refMap.values(), hasItem(dbRef)); } - /** - * @see DATAMONGO-424 - */ - @Test + @Test // DATAMONGO-424 @SuppressWarnings({ "rawtypes", "unchecked" }) public void resolvesDBRefMapValue() { @@ -1152,10 +1005,7 @@ public void resolvesDBRefMapValue() { assertThat(result.personMap.values(), hasItem(isPerson)); } - /** - * @see DATAMONGO-462 - */ - @Test + @Test // DATAMONGO-462 public void writesURLsAsStringOutOfTheBox() throws Exception { URLWrapper wrapper = new URLWrapper(); @@ -1167,20 +1017,14 @@ public void writesURLsAsStringOutOfTheBox() throws Exception { assertThat(sink.get("url"), is((Object) "http://springsource.org")); } - /** - * @see DATAMONGO-462 - */ - @Test + @Test // DATAMONGO-462 public void readsURLFromStringOutOfTheBox() throws Exception { DBObject dbObject = new BasicDBObject("url", "http://springsource.org"); URLWrapper result = converter.read(URLWrapper.class, dbObject); assertThat(result.url, is(new URL("http://springsource.org"))); } - /** - * @see DATAMONGO-485 - */ - @Test + @Test // DATAMONGO-485 public void writesComplexIdCorrectly() { ComplexId id = new ComplexId(); @@ -1198,10 +1042,7 @@ public void writesComplexIdCorrectly() { assertThat(((DBObject) idField).get("innerId"), is((Object) 4711L)); } - /** - * @see DATAMONGO-485 - */ - @Test + @Test // DATAMONGO-485 public void readsComplexIdCorrectly() { DBObject innerId = new BasicDBObject("innerId", 4711L); @@ -1213,10 +1054,7 @@ public void readsComplexIdCorrectly() { assertThat(result.complexId.innerId, is(4711L)); } - /** - * @see DATAMONGO-489 - */ - @Test + @Test // DATAMONGO-489 public void readsArraysAsMapValuesCorrectly() { BasicDBList list = new BasicDBList(); @@ -1234,10 +1072,7 @@ public void readsArraysAsMapValuesCorrectly() { assertThat(values, is(arrayWithSize(2))); } - /** - * @see DATAMONGO-497 - */ - @Test + @Test // DATAMONGO-497 public void readsEmptyCollectionIntoConstructorCorrectly() { DBObject source = new BasicDBObject("attributes", new BasicDBList()); @@ -1260,10 +1095,7 @@ private static void assertSyntheticFieldValueOf(Object target, Object expected) fail(String.format("Didn't find synthetic field on %s!", target)); } - /** - * @see DATAMGONGO-508 - */ - @Test + @Test // DATAMGONGO-508 public void eagerlyReturnsDBRefObjectIfTargetAlreadyIsOne() { DBRef dbRef = new DBRef("collection", "id"); @@ -1273,10 +1105,7 @@ public void eagerlyReturnsDBRefObjectIfTargetAlreadyIsOne() { assertThat(converter.createDBRef(dbRef, property), is(dbRef)); } - /** - * @see DATAMONGO-523 - */ - @Test + @Test // DATAMONGO-523 public void considersTypeAliasAnnotation() { Aliased aliased = new Aliased(); @@ -1290,10 +1119,7 @@ public void considersTypeAliasAnnotation() { assertThat(type.toString(), is("_")); } - /** - * @see DATAMONGO-533 - */ - @Test + @Test // DATAMONGO-533 public void marshalsThrowableCorrectly() { ThrowableWrapper wrapper = new ThrowableWrapper(); @@ -1303,10 +1129,7 @@ public void marshalsThrowableCorrectly() { converter.write(wrapper, dbObject); } - /** - * @see DATAMONGO-592 - */ - @Test + @Test // DATAMONGO-592 public void recursivelyConvertsSpELReadValue() { DBObject input = (DBObject) JSON @@ -1315,10 +1138,7 @@ public void recursivelyConvertsSpELReadValue() { converter.read(ObjectContainer.class, input); } - /** - * @see DATAMONGO-724 - */ - @Test + @Test // DATAMONGO-724 @SuppressWarnings("unchecked") public void mappingConsidersCustomConvertersNotWritingTypeInformation() { @@ -1376,20 +1196,14 @@ public Person convert(DBObject source) { assertThat(((Person) value).lastname, is("converter")); } - /** - * @see DATAMONGO-743 - */ - @Test + @Test // DATAMONGO-743 public void readsIntoStringsOutOfTheBox() { DBObject dbObject = new BasicDBObject("firstname", "Dave"); assertThat(converter.read(String.class, dbObject), is("{ \"firstname\" : \"Dave\"}")); } - /** - * @see DATAMONGO-766 - */ - @Test + @Test // DATAMONGO-766 public void writesProjectingTypeCorrectly() { NestedType nested = new NestedType(); @@ -1409,11 +1223,7 @@ public void writesProjectingTypeCorrectly() { assertThat(aValue.get("c"), is((Object) "C")); } - /** - * @see DATAMONGO-812 - * @see DATAMONGO-893 - */ - @Test + @Test // DATAMONGO-812, DATAMONGO-893 public void convertsListToBasicDBListAndRetainsTypeInformationForComplexObjects() { Address address = new Address(); @@ -1430,10 +1240,7 @@ public void convertsListToBasicDBListAndRetainsTypeInformationForComplexObjects( assertThat(getTypedValue(getAsDBObject(dbList, 0), "_class", String.class), equalTo(Address.class.getName())); } - /** - * @see DATAMONGO-812 - */ - @Test + @Test // DATAMONGO-812 public void convertsListToBasicDBListWithoutTypeInformationForSimpleTypes() { Object result = converter.convertToMongoType(Collections.singletonList("foo")); @@ -1445,10 +1252,7 @@ public void convertsListToBasicDBListWithoutTypeInformationForSimpleTypes() { assertThat(dbList.get(0), instanceOf(String.class)); } - /** - * @see DATAMONGO-812 - */ - @Test + @Test // DATAMONGO-812 public void convertsArrayToBasicDBListAndRetainsTypeInformationForComplexObjects() { Address address = new Address(); @@ -1464,10 +1268,7 @@ public void convertsArrayToBasicDBListAndRetainsTypeInformationForComplexObjects assertThat(getTypedValue(getAsDBObject(dbList, 0), "_class", String.class), equalTo(Address.class.getName())); } - /** - * @see DATAMONGO-812 - */ - @Test + @Test // DATAMONGO-812 public void convertsArrayToBasicDBListWithoutTypeInformationForSimpleTypes() { Object result = converter.convertToMongoType(new String[] { "foo" }); @@ -1479,10 +1280,7 @@ public void convertsArrayToBasicDBListWithoutTypeInformationForSimpleTypes() { assertThat(dbList.get(0), instanceOf(String.class)); } - /** - * @see DATAMONGO-833 - */ - @Test + @Test // DATAMONGO-833 public void readsEnumSetCorrectly() { BasicDBList enumSet = new BasicDBList(); @@ -1496,10 +1294,7 @@ public void readsEnumSetCorrectly() { assertThat(result.enumSet, hasItem(SampleEnum.SECOND)); } - /** - * @see DATAMONGO-833 - */ - @Test + @Test // DATAMONGO-833 public void readsEnumMapCorrectly() { BasicDBObject enumMap = new BasicDBObject("FIRST", "Dave"); @@ -1510,10 +1305,7 @@ public void readsEnumMapCorrectly() { assertThat(result.enumMap.get(SampleEnum.FIRST), is("Dave")); } - /** - * @see DATAMONGO-887 - */ - @Test + @Test // DATAMONGO-887 public void readsTreeMapCorrectly() { DBObject person = new BasicDBObject("foo", "Dave"); @@ -1527,10 +1319,7 @@ public void readsTreeMapCorrectly() { assertThat(result.treeMapOfPersons.get("key").firstname, is("Dave")); } - /** - * @see DATAMONGO-887 - */ - @Test + @Test // DATAMONGO-887 public void writesTreeMapCorrectly() { Person person = new Person(); @@ -1549,10 +1338,7 @@ public void writesTreeMapCorrectly() { assertThat(entry.get("foo"), is((Object) "Dave")); } - /** - * @DATAMONGO-858 - */ - @Test + @Test // DATAMONGO-858 public void shouldWriteEntityWithGeoBoxCorrectly() { ClassWithGeoBox object = new ClassWithGeoBox(); @@ -1571,10 +1357,7 @@ private static DBObject toDbObject(Point point) { return new BasicDBObject("x", point.getX()).append("y", point.getY()); } - /** - * @DATAMONGO-858 - */ - @Test + @Test // DATAMONGO-858 public void shouldReadEntityWithGeoBoxCorrectly() { ClassWithGeoBox object = new ClassWithGeoBox(); @@ -1589,10 +1372,7 @@ public void shouldReadEntityWithGeoBoxCorrectly() { assertThat(result.box, is(object.box)); } - /** - * @DATAMONGO-858 - */ - @Test + @Test // DATAMONGO-858 public void shouldWriteEntityWithGeoPolygonCorrectly() { ClassWithGeoPolygon object = new ClassWithGeoPolygon(); @@ -1614,10 +1394,7 @@ public void shouldWriteEntityWithGeoPolygonCorrectly() { toDbObject(object.polygon.getPoints().get(1)), toDbObject(object.polygon.getPoints().get(2)))); } - /** - * @DATAMONGO-858 - */ - @Test + @Test // DATAMONGO-858 public void shouldReadEntityWithGeoPolygonCorrectly() { ClassWithGeoPolygon object = new ClassWithGeoPolygon(); @@ -1632,10 +1409,7 @@ public void shouldReadEntityWithGeoPolygonCorrectly() { assertThat(result.polygon, is(object.polygon)); } - /** - * @DATAMONGO-858 - */ - @Test + @Test // DATAMONGO-858 public void shouldWriteEntityWithGeoCircleCorrectly() { ClassWithGeoCircle object = new ClassWithGeoCircle(); @@ -1655,10 +1429,7 @@ public void shouldWriteEntityWithGeoCircleCorrectly() { radius.getMetric().toString()))); } - /** - * @DATAMONGO-858 - */ - @Test + @Test // DATAMONGO-858 public void shouldReadEntityWithGeoCircleCorrectly() { ClassWithGeoCircle object = new ClassWithGeoCircle(); @@ -1673,10 +1444,7 @@ public void shouldReadEntityWithGeoCircleCorrectly() { assertThat(result.circle, is(result.circle)); } - /** - * @DATAMONGO-858 - */ - @Test + @Test // DATAMONGO-858 public void shouldWriteEntityWithGeoSphereCorrectly() { ClassWithGeoSphere object = new ClassWithGeoSphere(); @@ -1696,10 +1464,7 @@ public void shouldWriteEntityWithGeoSphereCorrectly() { radius.getMetric().toString()))); } - /** - * @DATAMONGO-858 - */ - @Test + @Test // DATAMONGO-858 public void shouldWriteEntityWithGeoSphereWithMetricDistanceCorrectly() { ClassWithGeoSphere object = new ClassWithGeoSphere(); @@ -1719,10 +1484,7 @@ public void shouldWriteEntityWithGeoSphereWithMetricDistanceCorrectly() { radius.getMetric().toString()))); } - /** - * @DATAMONGO-858 - */ - @Test + @Test // DATAMONGO-858 public void shouldReadEntityWithGeoSphereCorrectly() { ClassWithGeoSphere object = new ClassWithGeoSphere(); @@ -1737,10 +1499,7 @@ public void shouldReadEntityWithGeoSphereCorrectly() { assertThat(result.sphere, is(object.sphere)); } - /** - * @DATAMONGO-858 - */ - @Test + @Test // DATAMONGO-858 public void shouldWriteEntityWithGeoShapeCorrectly() { ClassWithGeoShape object = new ClassWithGeoShape(); @@ -1760,10 +1519,7 @@ public void shouldWriteEntityWithGeoShapeCorrectly() { radius.getMetric().toString()))); } - /** - * @DATAMONGO-858 - */ - @Test + @Test // DATAMONGO-858 @Ignore public void shouldReadEntityWithGeoShapeCorrectly() { @@ -1780,10 +1536,7 @@ public void shouldReadEntityWithGeoShapeCorrectly() { assertThat(result.shape, is((Shape) sphere)); } - /** - * @see DATAMONGO-976 - */ - @Test + @Test // DATAMONGO-976 public void shouldIgnoreTextScorePropertyWhenWriting() { ClassWithTextScoreProperty source = new ClassWithTextScoreProperty(); @@ -1795,10 +1548,7 @@ public void shouldIgnoreTextScorePropertyWhenWriting() { assertThat(dbo.get("score"), nullValue()); } - /** - * @see DATAMONGO-976 - */ - @Test + @Test // DATAMONGO-976 public void shouldIncludeTextScorePropertyWhenReading() { ClassWithTextScoreProperty entity = converter @@ -1806,10 +1556,7 @@ public void shouldIncludeTextScorePropertyWhenReading() { assertThat(entity.score, equalTo(5F)); } - /** - * @see DATAMONGO-1001 - */ - @Test + @Test // DATAMONGO-1001 public void shouldWriteCglibProxiedClassTypeInformationCorrectly() { ProxyFactory factory = new ProxyFactory(); @@ -1823,10 +1570,7 @@ public void shouldWriteCglibProxiedClassTypeInformationCorrectly() { assertThat(dbo.get("_class"), is((Object) GenericType.class.getName())); } - /** - * @see DATAMONGO-1001 - */ - @Test + @Test // DATAMONGO-1001 public void shouldUseTargetObjectOfLazyLoadingProxyWhenWriting() { LazyLoadingProxy mock = mock(LazyLoadingProxy.class); @@ -1837,10 +1581,7 @@ public void shouldUseTargetObjectOfLazyLoadingProxyWhenWriting() { verify(mock, times(1)).getTarget(); } - /** - * @see DATAMONGO-1034 - */ - @Test + @Test // DATAMONGO-1034 public void rejectsBasicDbListToBeConvertedIntoComplexType() { BasicDBList inner = new BasicDBList(); @@ -1860,10 +1601,7 @@ public void rejectsBasicDbListToBeConvertedIntoComplexType() { converter.read(Item.class, source); } - /** - * @see DATAMONGO-1058 - */ - @Test + @Test // DATAMONGO-1058 public void readShouldRespectExplicitFieldNameForDbRef() { BasicDBObject source = new BasicDBObject(); @@ -1875,10 +1613,7 @@ public void readShouldRespectExplicitFieldNameForDbRef() { Mockito.any(DbRefResolverCallback.class), Mockito.any(DbRefProxyHandler.class)); } - /** - * @see DATAMONGO-1050 - */ - @Test + @Test // DATAMONGO-1050 public void writeShouldUseExplicitFieldnameForIdPropertyWhenAnnotated() { RootForClassWithExplicitlyRenamedIdField source = new RootForClassWithExplicitlyRenamedIdField(); @@ -1893,10 +1628,7 @@ public void writeShouldUseExplicitFieldnameForIdPropertyWhenAnnotated() { assertThat((DBObject) sink.get("nested"), is(new BasicDBObjectBuilder().add("id", "nestedId").get())); } - /** - * @see DATAMONGO-1050 - */ - @Test + @Test // DATAMONGO-1050 public void readShouldUseExplicitFieldnameForIdPropertyWhenAnnotated() { DBObject source = new BasicDBObjectBuilder().add("_id", "rootId") @@ -1910,10 +1642,7 @@ public void readShouldUseExplicitFieldnameForIdPropertyWhenAnnotated() { assertThat(sink.nested.id, is("nestedId")); } - /** - * @see DATAMONGO-1050 - */ - @Test + @Test // DATAMONGO-1050 public void namedIdFieldShouldExtractValueFromUnderscoreIdField() { DBObject dbo = new BasicDBObjectBuilder().add("_id", "A").add("id", "B").get(); @@ -1923,10 +1652,7 @@ public void namedIdFieldShouldExtractValueFromUnderscoreIdField() { assertThat(withNamedIdField.id, is("A")); } - /** - * @see DATAMONGO-1050 - */ - @Test + @Test // DATAMONGO-1050 public void explicitlyRenamedIfFieldShouldExtractValueFromIdField() { DBObject dbo = new BasicDBObjectBuilder().add("_id", "A").add("id", "B").get(); @@ -1937,10 +1663,7 @@ public void explicitlyRenamedIfFieldShouldExtractValueFromIdField() { assertThat(withExplicitlyRenamedField.id, is("B")); } - /** - * @see DATAMONGO-1050 - */ - @Test + @Test // DATAMONGO-1050 public void annotatedIdFieldShouldExtractValueFromUnderscoreIdField() { DBObject dbo = new BasicDBObjectBuilder().add("_id", "A").add("id", "B").get(); @@ -1950,10 +1673,7 @@ public void annotatedIdFieldShouldExtractValueFromUnderscoreIdField() { assertThat(withAnnotatedIdField.key, is("A")); } - /** - * @see DATAMONGO-1102 - */ - @Test + @Test // DATAMONGO-1102 public void convertsJava8DateTimeTypesToDateAndBack() { TypeWithLocalDateTime source = new TypeWithLocalDateTime(); @@ -1966,10 +1686,7 @@ public void convertsJava8DateTimeTypesToDateAndBack() { assertThat(converter.read(TypeWithLocalDateTime.class, result).date, is(reference)); } - /** - * @see DATAMONGO-1128 - */ - @Test + @Test // DATAMONGO-1128 public void writesOptionalsCorrectly() { TypeWithOptional type = new TypeWithOptional(); @@ -1985,10 +1702,7 @@ public void writesOptionalsCorrectly() { assertThat(localDateTime.get("value"), is(instanceOf(Date.class))); } - /** - * @see DATAMONGO-1128 - */ - @Test + @Test // DATAMONGO-1128 public void readsOptionalsCorrectly() { LocalDateTime now = LocalDateTime.now(); @@ -2003,10 +1717,7 @@ public void readsOptionalsCorrectly() { assertThat(read.localDateTime, is(Optional.of(now))); } - /** - * @see DATAMONGO-1118 - */ - @Test + @Test // DATAMONGO-1118 @SuppressWarnings("unchecked") public void convertsMapKeyUsingCustomConverterForAndBackwards() { @@ -2025,10 +1736,7 @@ public void convertsMapKeyUsingCustomConverterForAndBackwards() { assertThat(converter.read(ClassWithMapUsingEnumAsKey.class, target).map, is(source.map)); } - /** - * @see DATAMONGO-1118 - */ - @Test + @Test // DATAMONGO-1118 public void writesMapKeyUsingCustomConverter() { MappingMongoConverter converter = new MappingMongoConverter(resolver, mappingContext); @@ -2049,10 +1757,7 @@ public void writesMapKeyUsingCustomConverter() { assertThat(map.containsField("bar-enum-value"), is(true)); } - /** - * @see DATAMONGO-1118 - */ - @Test + @Test // DATAMONGO-1118 public void readsMapKeyUsingCustomConverter() { MappingMongoConverter converter = new MappingMongoConverter(resolver, mappingContext); @@ -2066,18 +1771,12 @@ public void readsMapKeyUsingCustomConverter() { assertThat(target.map.get(FooBarEnum.FOO), is("spring")); } - /** - * @see DATAMONGO-1471 - */ - @Test + @Test // DATAMONGO-1471 public void readsDocumentWithPrimitiveIdButNoValue() { assertThat(converter.read(ClassWithIntId.class, new BasicDBObject()), is(notNullValue())); } - /** - * @see DATAMONGO-1497 - */ - @Test + @Test // DATAMONGO-1497 public void readsPropertyFromNestedFieldCorrectly() { DBObject source = new BasicDBObject("nested", new BasicDBObject("sample", "value")); @@ -2086,6 +1785,14 @@ public void readsPropertyFromNestedFieldCorrectly() { assertThat(result.sample, is("value")); } + @Test // DATAMONGO-1525 + public void readsEmptyEnumSet() { + + DBObject source = new BasicDBObject("enumSet", new BasicDBList()); + + assertThat(converter.read(ClassWithEnumProperty.class, source).enumSet, is(EnumSet.noneOf(SampleEnum.class))); + } + static class GenericType { T content; } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MongoConvertersUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MongoConvertersUnitTests.java index 32e70e792f..407e7081e5 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MongoConvertersUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MongoConvertersUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2016 by the original author(s). + * Copyright 2011-2017 by the original author(s). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -61,10 +61,7 @@ public void convertsBigDecimalToStringAndBackCorrectly() { assertThat(reference, is(bigDecimal)); } - /** - * @see DATAMONGO-858 - */ - @Test + @Test // DATAMONGO-858 public void convertsBoxToDbObjectAndBackCorrectly() { Box box = new Box(new Point(1, 2), new Point(3, 4)); @@ -75,10 +72,7 @@ public void convertsBoxToDbObjectAndBackCorrectly() { assertThat(shape, is((org.springframework.data.geo.Shape) box)); } - /** - * @see DATAMONGO-858 - */ - @Test + @Test // DATAMONGO-858 public void convertsCircleToDbObjectAndBackCorrectly() { Circle circle = new Circle(new Point(1, 2), 3); @@ -89,10 +83,7 @@ public void convertsCircleToDbObjectAndBackCorrectly() { assertThat(shape, is((org.springframework.data.geo.Shape) circle)); } - /** - * @see DATAMONGO-858 - */ - @Test + @Test // DATAMONGO-858 public void convertsPolygonToDbObjectAndBackCorrectly() { Polygon polygon = new Polygon(new Point(1, 2), new Point(2, 3), new Point(3, 4), new Point(5, 6)); @@ -103,10 +94,7 @@ public void convertsPolygonToDbObjectAndBackCorrectly() { assertThat(shape, is((org.springframework.data.geo.Shape) polygon)); } - /** - * @see DATAMONGO-858 - */ - @Test + @Test // DATAMONGO-858 public void convertsSphereToDbObjectAndBackCorrectly() { Sphere sphere = new Sphere(new Point(1, 2), 3); @@ -117,10 +105,7 @@ public void convertsSphereToDbObjectAndBackCorrectly() { assertThat(shape, is((org.springframework.data.geo.Shape) sphere)); } - /** - * @see DATAMONGO-858 - */ - @Test + @Test // DATAMONGO-858 public void convertsPointToListAndBackCorrectly() { Point point = new Point(1, 2); @@ -131,50 +116,32 @@ public void convertsPointToListAndBackCorrectly() { assertThat(converted, is((org.springframework.data.geo.Point) point)); } - /** - * @see DATAMONGO-1372 - */ - @Test + @Test // DATAMONGO-1372 public void convertsCurrencyToStringCorrectly() { assertThat(CurrencyToStringConverter.INSTANCE.convert(Currency.getInstance("USD")), is("USD")); } - /** - * @see DATAMONGO-1372 - */ - @Test + @Test // DATAMONGO-1372 public void convertsStringToCurrencyCorrectly() { assertThat(StringToCurrencyConverter.INSTANCE.convert("USD"), is(Currency.getInstance("USD"))); } - /** - * @see DATAMONGO-1416 - */ - @Test + @Test // DATAMONGO-1416 public void convertsAtomicLongToLongCorrectly() { assertThat(AtomicLongToLongConverter.INSTANCE.convert(new AtomicLong(100L)), is(100L)); } - /** - * @see DATAMONGO-1416 - */ - @Test + @Test // DATAMONGO-1416 public void convertsAtomicIntegerToIntegerCorrectly() { assertThat(AtomicIntegerToIntegerConverter.INSTANCE.convert(new AtomicInteger(100)), is(100)); } - /** - * @see DATAMONGO-1416 - */ - @Test + @Test // DATAMONGO-1416 public void convertsLongToAtomicLongCorrectly() { assertThat(LongToAtomicLongConverter.INSTANCE.convert(100L), is(instanceOf(AtomicLong.class))); } - /** - * @see DATAMONGO-1416 - */ - @Test + @Test // DATAMONGO-1416 public void convertsIntegerToAtomicIntegerCorrectly() { assertThat(IntegerToAtomicIntegerConverter.INSTANCE.convert(100), is(instanceOf(AtomicInteger.class))); } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MongoExampleMapperUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MongoExampleMapperUnitTests.java index db4b2a4c82..b95f77a077 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MongoExampleMapperUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MongoExampleMapperUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2016 the original author or authors. + * Copyright 2015-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -76,10 +76,7 @@ public void setUp() { this.mapper = new MongoExampleMapper(converter); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void exampleShouldBeMappedCorrectlyForFlatTypeWhenIdIsSet() { FlatDocument probe = new FlatDocument(); @@ -90,10 +87,7 @@ public void exampleShouldBeMappedCorrectlyForFlatTypeWhenIdIsSet() { assertThat(mapper.getMappedExample(of(probe), context.getPersistentEntity(FlatDocument.class)), is(expected)); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void exampleShouldBeMappedCorrectlyForFlatTypeWhenMultipleValuesSet() { FlatDocument probe = new FlatDocument(); @@ -109,10 +103,7 @@ public void exampleShouldBeMappedCorrectlyForFlatTypeWhenMultipleValuesSet() { assertThat(mapper.getMappedExample(of(probe), context.getPersistentEntity(FlatDocument.class)), is(expected)); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void exampleShouldBeMappedCorrectlyForFlatTypeWhenIdIsNotSet() { FlatDocument probe = new FlatDocument(); @@ -126,10 +117,7 @@ public void exampleShouldBeMappedCorrectlyForFlatTypeWhenIdIsNotSet() { assertThat(mapper.getMappedExample(of(probe), context.getPersistentEntity(FlatDocument.class)), is(expected)); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void exampleShouldBeMappedCorrectlyForFlatTypeWhenListHasValues() { FlatDocument probe = new FlatDocument(); @@ -144,10 +132,7 @@ public void exampleShouldBeMappedCorrectlyForFlatTypeWhenListHasValues() { assertThat(mapper.getMappedExample(of(probe), context.getPersistentEntity(FlatDocument.class)), is(expected)); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void exampleShouldBeMappedCorrectlyForFlatTypeWhenFieldNameIsCustomized() { FlatDocument probe = new FlatDocument(); @@ -158,10 +143,7 @@ public void exampleShouldBeMappedCorrectlyForFlatTypeWhenFieldNameIsCustomized() assertThat(mapper.getMappedExample(of(probe), context.getPersistentEntity(FlatDocument.class)), is(expected)); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void typedExampleShouldContainTypeRestriction() { WrapperDocument probe = new WrapperDocument(); @@ -174,10 +156,7 @@ public void typedExampleShouldContainTypeRestriction() { isBsonObject().containing("_class", new BasicDBObject("$in", new String[] { probe.getClass().getName() }))); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void exampleShouldBeMappedAsFlatMapWhenGivenNestedElementsWithLenientMatchMode() { WrapperDocument probe = new WrapperDocument(); @@ -189,10 +168,7 @@ public void exampleShouldBeMappedAsFlatMapWhenGivenNestedElementsWithLenientMatc assertThat(mapper.getMappedExample(of(probe), context.getPersistentEntity(WrapperDocument.class)), is(expected)); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void exampleShouldBeMappedAsExactObjectWhenGivenNestedElementsWithStrictMatchMode() { WrapperDocument probe = new WrapperDocument(); @@ -205,10 +181,7 @@ public void exampleShouldBeMappedAsExactObjectWhenGivenNestedElementsWithStrictM isBsonObject().containing("flatDoc.stringValue", "conflux")); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void exampleShouldBeMappedCorrectlyForFlatTypeWhenStringMatchModeIsStarting() { FlatDocument probe = new FlatDocument(); @@ -224,10 +197,7 @@ public void exampleShouldBeMappedCorrectlyForFlatTypeWhenStringMatchModeIsStarti assertThat(mapper.getMappedExample(example, context.getPersistentEntity(FlatDocument.class)), is(expected)); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void exampleShouldBeMappedCorrectlyForFlatTypeContainingDotsWhenStringMatchModeIsStarting() { FlatDocument probe = new FlatDocument(); @@ -243,10 +213,7 @@ public void exampleShouldBeMappedCorrectlyForFlatTypeContainingDotsWhenStringMat assertThat(mapper.getMappedExample(example, context.getPersistentEntity(FlatDocument.class)), is(expected)); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void exampleShouldBeMappedCorrectlyForFlatTypeWhenStringMatchModeIsEnding() { FlatDocument probe = new FlatDocument(); @@ -262,10 +229,7 @@ public void exampleShouldBeMappedCorrectlyForFlatTypeWhenStringMatchModeIsEnding assertThat(mapper.getMappedExample(example, context.getPersistentEntity(FlatDocument.class)), is(expected)); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void exampleShouldBeMappedCorrectlyForFlatTypeWhenStringMatchModeRegex() { FlatDocument probe = new FlatDocument(); @@ -281,10 +245,7 @@ public void exampleShouldBeMappedCorrectlyForFlatTypeWhenStringMatchModeRegex() assertThat(mapper.getMappedExample(example, context.getPersistentEntity(FlatDocument.class)), is(expected)); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void exampleShouldBeMappedCorrectlyForFlatTypeWhenIgnoreCaseEnabledAndMatchModeSet() { FlatDocument probe = new FlatDocument(); @@ -300,10 +261,7 @@ public void exampleShouldBeMappedCorrectlyForFlatTypeWhenIgnoreCaseEnabledAndMat assertThat(mapper.getMappedExample(example, context.getPersistentEntity(FlatDocument.class)), is(expected)); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void exampleShouldBeMappedCorrectlyForFlatTypeWhenIgnoreCaseEnabled() { FlatDocument probe = new FlatDocument(); @@ -319,10 +277,7 @@ public void exampleShouldBeMappedCorrectlyForFlatTypeWhenIgnoreCaseEnabled() { assertThat(mapper.getMappedExample(example, context.getPersistentEntity(FlatDocument.class)), is(expected)); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void exampleShouldBeMappedWhenContainingDBRef() { FlatDocument probe = new FlatDocument(); @@ -337,10 +292,7 @@ public void exampleShouldBeMappedWhenContainingDBRef() { assertThat(reference.getCollectionName(), is("refDoc")); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void exampleShouldBeMappedWhenDBRefIsNull() { FlatDocument probe = new FlatDocument(); @@ -351,10 +303,7 @@ public void exampleShouldBeMappedWhenDBRefIsNull() { assertThat(dbo, isBsonObject().containing("stringValue", "steelheart")); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void exampleShouldBeMappedCorrectlyWhenContainingLegacyPoint() { ClassWithGeoTypes probe = new ClassWithGeoTypes(); @@ -366,10 +315,7 @@ public void exampleShouldBeMappedCorrectlyWhenContainingLegacyPoint() { assertThat(dbo.get("legacyPoint.y"), Is.is(20D)); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void mappingShouldExcludeFieldWithCustomNameCorrectly() { FlatDocument probe = new FlatDocument(); @@ -386,10 +332,7 @@ public void mappingShouldExcludeFieldWithCustomNameCorrectly() { assertThat(mapper.getMappedExample(example, context.getPersistentEntity(FlatDocument.class)), is(expected)); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void mappingShouldExcludeFieldCorrectly() { FlatDocument probe = new FlatDocument(); @@ -406,10 +349,7 @@ public void mappingShouldExcludeFieldCorrectly() { assertThat(mapper.getMappedExample(example, context.getPersistentEntity(FlatDocument.class)), is(expected)); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void mappingShouldExcludeNestedFieldCorrectly() { WrapperDocument probe = new WrapperDocument(); @@ -427,10 +367,7 @@ public void mappingShouldExcludeNestedFieldCorrectly() { assertThat(mapper.getMappedExample(example, context.getPersistentEntity(WrapperDocument.class)), is(expected)); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void mappingShouldExcludeNestedFieldWithCustomNameCorrectly() { WrapperDocument probe = new WrapperDocument(); @@ -448,10 +385,7 @@ public void mappingShouldExcludeNestedFieldWithCustomNameCorrectly() { assertThat(mapper.getMappedExample(example, context.getPersistentEntity(WrapperDocument.class)), is(expected)); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void mappingShouldFavorFieldSpecificationStringMatcherOverDefaultStringMatcher() { FlatDocument probe = new FlatDocument(); @@ -467,10 +401,7 @@ public void mappingShouldFavorFieldSpecificationStringMatcherOverDefaultStringMa assertThat(mapper.getMappedExample(example, context.getPersistentEntity(FlatDocument.class)), is(expected)); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void mappingShouldIncludePropertiesFromHierarchicalDocument() { HierachicalDocument probe = new HierachicalDocument(); @@ -483,10 +414,7 @@ public void mappingShouldIncludePropertiesFromHierarchicalDocument() { assertThat(dbo, isBsonObject().containing("anotherStringValue", "calamity")); } - /** - * @see DATAMONGO-1459 - */ - @Test + @Test // DATAMONGO-1459 public void mapsAnyMatchingExampleCorrectly() { FlatDocument probe = new FlatDocument(); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/NamedMongoScriptConvertsUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/NamedMongoScriptConvertsUnitTests.java index c7acd2f0fa..1dedb52eca 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/NamedMongoScriptConvertsUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/NamedMongoScriptConvertsUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2015 the original author or authors. + * Copyright 2014-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -58,18 +58,12 @@ public static class NamedMongoScriptToDboConverterUnitTests { NamedMongoScriptToDBObjectConverter converter = NamedMongoScriptToDBObjectConverter.INSTANCE; - /** - * @see DATAMONGO-479 - */ - @Test + @Test // DATAMONGO-479 public void convertShouldReturnEmptyDboWhenScriptIsNull() { assertThat(converter.convert(null), is((DBObject) new BasicDBObject())); } - /** - * @see DATAMONGO-479 - */ - @Test + @Test // DATAMONGO-479 public void convertShouldConvertScriptNameCorreclty() { DBObject dbo = converter.convert(ECHO_SCRIPT); @@ -79,10 +73,7 @@ public void convertShouldConvertScriptNameCorreclty() { assertThat(id, is((Object) FUNCTION_NAME)); } - /** - * @see DATAMONGO-479 - */ - @Test + @Test // DATAMONGO-479 public void convertShouldConvertScriptCodeCorreclty() { DBObject dbo = converter.convert(ECHO_SCRIPT); @@ -100,18 +91,12 @@ public static class DboToNamedMongoScriptConverterUnitTests { DBObjectToNamedMongoScriptCoverter converter = DBObjectToNamedMongoScriptCoverter.INSTANCE; - /** - * @see DATAMONGO-479 - */ - @Test + @Test // DATAMONGO-479 public void convertShouldReturnNullIfSourceIsNull() { assertThat(converter.convert(null), is(nullValue())); } - /** - * @see DATAMONGO-479 - */ - @Test + @Test // DATAMONGO-479 public void convertShouldConvertIdCorreclty() { NamedMongoScript script = converter.convert(FUNCTION); @@ -119,10 +104,7 @@ public void convertShouldConvertIdCorreclty() { assertThat(script.getName(), is(FUNCTION_NAME)); } - /** - * @see DATAMONGO-479 - */ - @Test + @Test // DATAMONGO-479 public void convertShouldConvertScriptValueCorreclty() { NamedMongoScript script = converter.convert(FUNCTION); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/NumberToNumberConverterFactoryUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/NumberToNumberConverterFactoryUnitTests.java index efce959f5e..8b7c702458 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/NumberToNumberConverterFactoryUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/NumberToNumberConverterFactoryUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2016 the original author or authors. + * Copyright 2015-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -52,10 +52,7 @@ public static Collection parameters() { return Arrays. asList(longToInt, atomicIntToInt, atomicIntToDouble, atomicLongToInt, atomicLongToLong); } - /** - * @see DATAMONGO-1288 - */ - @Test + @Test // DATAMONGO-1288 public void convertsToTargetTypeCorrectly() { assertThat(NumberToNumberConverterFactory.INSTANCE.getConverter(expected.getClass()).convert(source), is(expected)); } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/QueryMapperUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/QueryMapperUnitTests.java index 585ed114bd..0b515a3a64 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/QueryMapperUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/QueryMapperUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2016 the original author or authors. + * Copyright 2011-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -128,10 +128,7 @@ public void handlesObjectIdCapableBigIntegerIdsCorrectly() { assertThat(result.get("_id"), is((Object) id)); } - /** - * @see DATAMONGO-278 - */ - @Test + @Test // DATAMONGO-278 public void translates$NeCorrectly() { Criteria criteria = where("foo").ne(new ObjectId().toString()); @@ -143,10 +140,7 @@ public void handlesObjectIdCapableBigIntegerIdsCorrectly() { assertThat(dbObject.get("$ne"), is(instanceOf(ObjectId.class))); } - /** - * @see DATAMONGO-326 - */ - @Test + @Test // DATAMONGO-326 public void handlesEnumsCorrectly() { Query query = query(where("foo").is(Enum.INSTANCE)); DBObject result = mapper.getMappedObject(query.getQueryObject(), null); @@ -186,20 +180,14 @@ public void handlesEnumsInNotEqualCorrectly() { assertThat(list.get(0).toString(), is(Enum.INSTANCE.name())); } - /** - * @see DATAMONGO-373 - */ - @Test + @Test // DATAMONGO-373 public void handlesNativelyBuiltQueryCorrectly() { DBObject query = new QueryBuilder().or(new BasicDBObject("foo", "bar")).get(); mapper.getMappedObject(query, null); } - /** - * @see DATAMONGO-369 - */ - @Test + @Test // DATAMONGO-369 public void handlesAllPropertiesIfDBObject() { DBObject query = new BasicDBObject(); @@ -210,10 +198,7 @@ public void handlesAllPropertiesIfDBObject() { assertThat(result.get("bar"), is(notNullValue())); } - /** - * @see DATAMONGO-429 - */ - @Test + @Test // DATAMONGO-429 public void transformsArraysCorrectly() { Query query = new BasicQuery("{ 'tags' : { '$all' : [ 'green', 'orange']}}"); @@ -235,10 +220,7 @@ public void doesHandleNestedFieldsWithDefaultIdNames() { assertThat(((DBObject) result.get("nested")).get("_id"), is(instanceOf(ObjectId.class))); } - /** - * @see DATAMONGO-493 - */ - @Test + @Test // DATAMONGO-493 public void doesNotTranslateNonIdPropertiesFor$NeCriteria() { ObjectId accidentallyAnObjectId = new ObjectId(); @@ -254,10 +236,7 @@ public void doesHandleNestedFieldsWithDefaultIdNames() { assertThat(publishers.get("$ne"), is(instanceOf(String.class))); } - /** - * @see DATAMONGO-494 - */ - @Test + @Test // DATAMONGO-494 public void usesEntityMetadataInOr() { Query query = query(new Criteria().orOperator(where("foo").is("bar"))); @@ -356,10 +335,7 @@ public void convertsInKeywordCorrectly() { assertThat(inClause.get(1), is(instanceOf(com.mongodb.DBRef.class))); } - /** - * @see DATAMONGO-570 - */ - @Test + @Test // DATAMONGO-570 public void correctlyConvertsNullReference() { Query query = query(where("reference").is(null)); @@ -368,10 +344,7 @@ public void correctlyConvertsNullReference() { assertThat(object.get("reference"), is(nullValue())); } - /** - * @see DATAMONGO-629 - */ - @Test + @Test // DATAMONGO-629 public void doesNotMapIdIfNoEntityMetadataAvailable() { String id = new ObjectId().toString(); @@ -384,10 +357,7 @@ public void doesNotMapIdIfNoEntityMetadataAvailable() { assertThat(object.containsField("_id"), is(false)); } - /** - * @see DATAMONGO-677 - */ - @Test + @Test // DATAMONGO-677 public void handleMapWithDBRefCorrectly() { DBObject mapDbObject = new BasicDBObject(); @@ -413,10 +383,7 @@ public void convertsUnderscoreIdValueWithoutMetadata() { assertThat(mapped.get("_id"), is(instanceOf(ObjectId.class))); } - /** - * @see DATAMONGO-705 - */ - @Test + @Test // DATAMONGO-705 public void convertsDBRefWithExistsQuery() { Query query = query(where("reference").exists(false)); @@ -429,10 +396,7 @@ public void convertsDBRefWithExistsQuery() { assertThat(reference.get("$exists"), is((Object) false)); } - /** - * @see DATAMONGO-706 - */ - @Test + @Test // DATAMONGO-706 public void convertsNestedDBRefsCorrectly() { Reference reference = new Reference(); @@ -453,10 +417,7 @@ public void convertsNestedDBRefsCorrectly() { assertThat(inClause.get(0), is(instanceOf(com.mongodb.DBRef.class))); } - /** - * @see DATAMONGO-752 - */ - @Test + @Test // DATAMONGO-752 public void mapsSimpleValuesStartingWith$Correctly() { Query query = query(where("myvalue").is("$334")); @@ -467,10 +428,7 @@ public void convertsNestedDBRefsCorrectly() { assertThat(result.get("myvalue"), is((Object) "$334")); } - /** - * @see DATAMONGO-752 - */ - @Test + @Test // DATAMONGO-752 public void mapsKeywordAsSimpleValuesCorrectly() { Query query = query(where("myvalue").is("$center")); @@ -481,10 +439,7 @@ public void mapsKeywordAsSimpleValuesCorrectly() { assertThat(result.get("myvalue"), is((Object) "$center")); } - /** - * @DATAMONGO-805 - */ - @Test + @Test // DATAMONGO-805 public void shouldExcludeDBRefAssociation() { Query query = query(where("someString").is("foo")); @@ -498,10 +453,7 @@ public void shouldExcludeDBRefAssociation() { assertThat(fieldsResult.get("reference"), is((Object) 0)); } - /** - * @see DATAMONGO-686 - */ - @Test + @Test // DATAMONGO-686 public void queryMapperShouldNotChangeStateInGivenQueryObjectWhenIdConstrainedByInList() { BasicMongoPersistentEntity persistentEntity = context.getPersistentEntity(Sample.class); @@ -515,10 +467,7 @@ public void queryMapperShouldNotChangeStateInGivenQueryObjectWhenIdConstrainedBy assertThat(idValuesAfter, is(idValuesBefore)); } - /** - * @see DATAMONGO-821 - */ - @Test + @Test // DATAMONGO-821 public void queryMapperShouldNotTryToMapDBRefListPropertyIfNestedInsideDBObjectWithinDBObject() { DBObject queryObject = query( @@ -531,10 +480,7 @@ public void queryMapperShouldNotTryToMapDBRefListPropertyIfNestedInsideDBObjectW assertThat(nestedObject, is((DBObject) new BasicDBObject("$keys", 0L))); } - /** - * @see DATAMONGO-821 - */ - @Test + @Test // DATAMONGO-821 public void queryMapperShouldNotTryToMapDBRefPropertyIfNestedInsideDBObjectWithinDBObject() { DBObject queryObject = query(where("reference").is(new BasicDBObject("$nested", new BasicDBObject("$keys", 0L)))) @@ -547,10 +493,7 @@ public void queryMapperShouldNotTryToMapDBRefPropertyIfNestedInsideDBObjectWithi assertThat(nestedObject, is((DBObject) new BasicDBObject("$keys", 0L))); } - /** - * @see DATAMONGO-821 - */ - @Test + @Test // DATAMONGO-821 public void queryMapperShouldMapDBRefPropertyIfNestedInDBObject() { Reference sample = new Reference(); @@ -566,10 +509,7 @@ public void queryMapperShouldMapDBRefPropertyIfNestedInDBObject() { assertThat(inObject.get(0), is(instanceOf(com.mongodb.DBRef.class))); } - /** - * @see DATAMONGO-773 - */ - @Test + @Test // DATAMONGO-773 public void queryMapperShouldBeAbleToProcessQueriesThatIncludeDbRefFields() { BasicMongoPersistentEntity persistentEntity = context.getPersistentEntity(WithDBRef.class); @@ -581,10 +521,7 @@ public void queryMapperShouldBeAbleToProcessQueriesThatIncludeDbRefFields() { assertThat(mappedFields, is(notNullValue())); } - /** - * @see DATAMONGO-893 - */ - @Test + @Test // DATAMONGO-893 public void classInformationShouldNotBePresentInDBObjectUsedInFinderMethods() { EmbeddedClass embedded = new EmbeddedClass(); @@ -598,10 +535,7 @@ public void classInformationShouldNotBePresentInDBObjectUsedInFinderMethods() { assertThat(dbo.toString(), equalTo("{ \"embedded\" : { \"$in\" : [ { \"_id\" : \"1\"} , { \"_id\" : \"2\"}]}}")); } - /** - * @see DATAMONGO-1406 - */ - @Test + @Test // DATAMONGO-1406 public void shouldMapQueryForNestedCustomizedPropertiesUsingConfiguredFieldNames() { EmbeddedClass embeddedClass = new EmbeddedClass(); @@ -620,10 +554,7 @@ public void shouldMapQueryForNestedCustomizedPropertiesUsingConfiguredFieldNames new BasicDbListBuilder().add(new BasicDBObject("fancy_custom_name", embeddedClass.customizedField)).get())); } - /** - * @see DATAMONGO-647 - */ - @Test + @Test // DATAMONGO-647 public void customizedFieldNameShouldBeMappedCorrectlyWhenApplyingSort() { Query query = query(where("field").is("bar")).with(new Sort(Direction.DESC, "field")); @@ -631,10 +562,7 @@ public void customizedFieldNameShouldBeMappedCorrectlyWhenApplyingSort() { assertThat(dbo, equalTo(new BasicDBObjectBuilder().add("foo", -1).get())); } - /** - * @see DATAMONGO-973 - */ - @Test + @Test // DATAMONGO-973 public void getMappedFieldsAppendsTextScoreFieldProperlyCorrectlyWhenNotPresent() { Query query = new Query(); @@ -645,10 +573,7 @@ public void getMappedFieldsAppendsTextScoreFieldProperlyCorrectlyWhenNotPresent( assertThat(dbo, equalTo(new BasicDBObjectBuilder().add("score", new BasicDBObject("$meta", "textScore")).get())); } - /** - * @see DATAMONGO-973 - */ - @Test + @Test // DATAMONGO-973 public void getMappedFieldsReplacesTextScoreFieldProperlyCorrectlyWhenPresent() { Query query = new Query(); @@ -660,10 +585,7 @@ public void getMappedFieldsReplacesTextScoreFieldProperlyCorrectlyWhenPresent() assertThat(dbo, equalTo(new BasicDBObjectBuilder().add("score", new BasicDBObject("$meta", "textScore")).get())); } - /** - * @see DATAMONGO-973 - */ - @Test + @Test // DATAMONGO-973 public void getMappedSortAppendsTextScoreProperlyWhenSortedByScore() { Query query = new Query().with(new Sort("textScore")); @@ -674,10 +596,7 @@ public void getMappedSortAppendsTextScoreProperlyWhenSortedByScore() { assertThat(dbo, equalTo(new BasicDBObjectBuilder().add("score", new BasicDBObject("$meta", "textScore")).get())); } - /** - * @see DATAMONGO-973 - */ - @Test + @Test // DATAMONGO-973 public void getMappedSortIgnoresTextScoreWhenNotSortedByScore() { Query query = new Query().with(new Sort("id")); @@ -688,10 +607,7 @@ public void getMappedSortIgnoresTextScoreWhenNotSortedByScore() { assertThat(dbo, equalTo(new BasicDBObjectBuilder().add("_id", 1).get())); } - /** - * @see DATAMONGO-1070 - */ - @Test + @Test // DATAMONGO-1070 public void mapsIdReferenceToDBRefCorrectly() { ObjectId id = new ObjectId(); @@ -704,10 +620,7 @@ public void mapsIdReferenceToDBRefCorrectly() { assertThat(reference.getId(), is(instanceOf(ObjectId.class))); } - /** - * @see DATAMONGO-1050 - */ - @Test + @Test // DATAMONGO-1050 public void shouldUseExplicitlySetFieldnameForIdPropertyCandidates() { Query query = query(where("nested.id").is("bar")); @@ -718,10 +631,7 @@ public void shouldUseExplicitlySetFieldnameForIdPropertyCandidates() { assertThat(dbo, equalTo(new BasicDBObjectBuilder().add("nested.id", "bar").get())); } - /** - * @see DATAMONGO-1050 - */ - @Test + @Test // DATAMONGO-1050 public void shouldUseExplicitlySetFieldnameForIdPropertyCandidatesUsedInSortClause() { Query query = new Query().with(new Sort("nested.id")); @@ -732,10 +642,7 @@ public void shouldUseExplicitlySetFieldnameForIdPropertyCandidatesUsedInSortClau assertThat(dbo, equalTo(new BasicDBObjectBuilder().add("nested.id", 1).get())); } - /** - * @see DATAMONGO-1135 - */ - @Test + @Test // DATAMONGO-1135 public void nearShouldUseGeoJsonRepresentationOnUnmappedProperty() { Query query = query(where("foo").near(new GeoJsonPoint(100, 50))); @@ -747,10 +654,7 @@ public void nearShouldUseGeoJsonRepresentationOnUnmappedProperty() { assertThat(dbo, isBsonObject().containing("foo.$near.$geometry.coordinates.[1]", 50D)); } - /** - * @see DATAMONGO-1135 - */ - @Test + @Test // DATAMONGO-1135 public void nearShouldUseGeoJsonRepresentationWhenMappingToGoJsonType() { Query query = query(where("geoJsonPoint").near(new GeoJsonPoint(100, 50))); @@ -760,10 +664,7 @@ public void nearShouldUseGeoJsonRepresentationWhenMappingToGoJsonType() { assertThat(dbo, isBsonObject().containing("geoJsonPoint.$near.$geometry.type", "Point")); } - /** - * @see DATAMONGO-1135 - */ - @Test + @Test // DATAMONGO-1135 public void nearSphereShouldUseGeoJsonRepresentationWhenMappingToGoJsonType() { Query query = query(where("geoJsonPoint").nearSphere(new GeoJsonPoint(100, 50))); @@ -773,10 +674,7 @@ public void nearSphereShouldUseGeoJsonRepresentationWhenMappingToGoJsonType() { assertThat(dbo, isBsonObject().containing("geoJsonPoint.$nearSphere.$geometry.type", "Point")); } - /** - * @see DATAMONGO-1135 - */ - @Test + @Test // DATAMONGO-1135 public void shouldMapNameCorrectlyForGeoJsonType() { Query query = query(where("namedGeoJsonPoint").nearSphere(new GeoJsonPoint(100, 50))); @@ -787,10 +685,7 @@ public void shouldMapNameCorrectlyForGeoJsonType() { isBsonObject().containing("geoJsonPointWithNameViaFieldAnnotation.$nearSphere.$geometry.type", "Point")); } - /** - * @see DATAMONGO-1135 - */ - @Test + @Test // DATAMONGO-1135 public void withinShouldUseGeoJsonPolygonWhenMappingPolygonOn2DSphereIndex() { Query query = query(where("geoJsonPoint") @@ -801,10 +696,7 @@ public void withinShouldUseGeoJsonPolygonWhenMappingPolygonOn2DSphereIndex() { assertThat(dbo, isBsonObject().containing("geoJsonPoint.$geoWithin.$geometry.type", "Polygon")); } - /** - * @see DATAMONGO-1134 - */ - @Test + @Test // DATAMONGO-1134 public void intersectsShouldUseGeoJsonRepresentationCorrectly() { Query query = query(where("geoJsonPoint") @@ -816,11 +708,7 @@ public void intersectsShouldUseGeoJsonRepresentationCorrectly() { assertThat(dbo, isBsonObject().containing("geoJsonPoint.$geoIntersects.$geometry.coordinates")); } - /** - * - * @see DATAMONGO-1269 - */ - @Test + @Test // DATAMONGO-1269 public void mappingShouldRetainNumericMapKey() { Query query = query(where("map.1.stringProperty").is("ba'alzamon")); @@ -831,10 +719,7 @@ public void mappingShouldRetainNumericMapKey() { assertThat(dbo.containsField("map.1.stringProperty"), is(true)); } - /** - * @see DATAMONGO-1269 - */ - @Test + @Test // DATAMONGO-1269 public void mappingShouldRetainNumericPositionInList() { Query query = query(where("list.1.stringProperty").is("ba'alzamon")); @@ -845,10 +730,7 @@ public void mappingShouldRetainNumericPositionInList() { assertThat(dbo.containsField("list.1.stringProperty"), is(true)); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void exampleShouldBeMappedCorrectly() { Foo probe = new Foo(); @@ -862,10 +744,7 @@ public void exampleShouldBeMappedCorrectly() { assertThat(dbo, isBsonObject().containing("embedded\\._id", "conflux")); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void exampleShouldBeMappedCorrectlyWhenContainingLegacyPoint() { ClassWithGeoTypes probe = new ClassWithGeoTypes(); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/ReflectiveDBRefResolverUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/ReflectiveDBRefResolverUnitTests.java index 01b105540f..7d0c65d8c4 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/ReflectiveDBRefResolverUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/ReflectiveDBRefResolverUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 the original author or authors. + * Copyright 2015-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -58,10 +58,7 @@ public void setUp() { when(collectionMock.findOne(eq("id-1"))).thenReturn(new BasicDBObject("_id", "id-1")); } - /** - * @see DATAMONGO-1193 - */ - @Test + @Test // DATAMONGO-1193 public void fetchShouldNotLookUpDbWhenUsingDriverVersion2() { assumeThat(isMongo3Driver(), is(false)); @@ -72,10 +69,7 @@ public void fetchShouldNotLookUpDbWhenUsingDriverVersion2() { verify(dbFactoryMock, never()).getDb(anyString()); } - /** - * @see DATAMONGO-1193 - */ - @Test + @Test // DATAMONGO-1193 public void fetchShouldUseDbToResolveDbRefWhenUsingDriverVersion3() { assumeThat(isMongo3Driver(), is(true)); @@ -84,10 +78,7 @@ public void fetchShouldUseDbToResolveDbRefWhenUsingDriverVersion3() { verify(dbFactoryMock, times(1)).getDb(); } - /** - * @see DATAMONGO-1193 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-1193 public void fetchShouldThrowExceptionWhenDbFactoryIsNullUsingDriverVersion3() { assumeThat(isMongo3Driver(), is(true)); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/TermToStringConverterUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/TermToStringConverterUnitTests.java index c7971154df..0ce0ca4b6b 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/TermToStringConverterUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/TermToStringConverterUnitTests.java @@ -29,18 +29,12 @@ */ public class TermToStringConverterUnitTests { - /** - * @DATAMONGO-973 - */ - @Test + @Test // DATAMONGO-973 public void shouldNotConvertNull() { assertThat(TermToStringConverter.INSTANCE.convert(null), nullValue()); } - /** - * @DATAMONGO-973 - */ - @Test + @Test // DATAMONGO-973 public void shouldUseFormattedRepresentationForConversion() { Term term = spy(new Term("foo", Type.WORD)); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/UpdateMapperUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/UpdateMapperUnitTests.java index d6942a15f3..189ec4095c 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/UpdateMapperUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/UpdateMapperUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,8 +15,7 @@ */ package org.springframework.data.mongodb.core.convert; -import static org.hamcrest.CoreMatchers.*; -import static org.hamcrest.collection.IsMapContaining.*; +import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; import static org.mockito.Mockito.*; import static org.springframework.data.mongodb.core.DBObjectTestUtils.*; @@ -42,6 +41,9 @@ import org.springframework.core.convert.converter.Converter; import org.springframework.data.annotation.Id; import org.springframework.data.convert.WritingConverter; +import org.springframework.data.domain.Sort; +import org.springframework.data.domain.Sort.Direction; +import org.springframework.data.domain.Sort.Order; import org.springframework.data.mapping.model.MappingException; import org.springframework.data.mongodb.MongoDbFactory; import org.springframework.data.mongodb.core.DBObjectTestUtils; @@ -68,6 +70,7 @@ * @author Christoph Strobl * @author Thomas Darimont * @author Mark Paluch + * @author Pavel Vodrazka */ @RunWith(MockitoJUnitRunner.class) public class UpdateMapperUnitTests { @@ -80,6 +83,7 @@ public class UpdateMapperUnitTests { private Converter writingConverterSpy; @Before + @SuppressWarnings("unchecked") public void setUp() { this.writingConverterSpy = Mockito.spy(new NestedEntityWriteConverter()); @@ -96,10 +100,7 @@ public void setUp() { this.mapper = new UpdateMapper(converter); } - /** - * @see DATAMONGO-721 - */ - @Test + @Test // DATAMONGO-721 public void updateMapperRetainsTypeInformationForCollectionField() { Update update = new Update().push("list", new ConcreteChildClass("2", "BAR")); @@ -113,10 +114,7 @@ public void updateMapperRetainsTypeInformationForCollectionField() { assertThat(list.get("_class"), is((Object) ConcreteChildClass.class.getName())); } - /** - * @see DATAMONGO-807 - */ - @Test + @Test // DATAMONGO-807 public void updateMapperShouldRetainTypeInformationForNestedEntities() { Update update = Update.update("model", new ModelImpl(1)); @@ -130,10 +128,7 @@ public void updateMapperShouldRetainTypeInformationForNestedEntities() { assertThat(modelDbObject.get("_class"), not(nullValue())); } - /** - * @see DATAMONGO-807 - */ - @Test + @Test // DATAMONGO-807 public void updateMapperShouldNotPersistTypeInformationForKnownSimpleTypes() { Update update = Update.update("model.value", 1); @@ -146,10 +141,7 @@ public void updateMapperShouldNotPersistTypeInformationForKnownSimpleTypes() { assertThat(set.get("_class"), nullValue()); } - /** - * @see DATAMONGO-807 - */ - @Test + @Test // DATAMONGO-807 public void updateMapperShouldNotPersistTypeInformationForNullValues() { Update update = Update.update("model", null); @@ -162,10 +154,7 @@ public void updateMapperShouldNotPersistTypeInformationForNullValues() { assertThat(set.get("_class"), nullValue()); } - /** - * @see DATAMONGO-407 - */ - @Test + @Test // DATAMONGO-407 public void updateMapperShouldRetainTypeInformationForNestedCollectionElements() { Update update = Update.update("list.$", new ConcreteChildClass("42", "bubu")); @@ -179,10 +168,7 @@ public void updateMapperShouldRetainTypeInformationForNestedCollectionElements() assertThat(modelDbObject.get("_class"), is((Object) ConcreteChildClass.class.getName())); } - /** - * @see DATAMONGO-407 - */ - @Test + @Test // DATAMONGO-407 public void updateMapperShouldSupportNestedCollectionElementUpdates() { Update update = Update.update("list.$.value", "foo").set("list.$.otherValue", "bar"); @@ -196,10 +182,7 @@ public void updateMapperShouldSupportNestedCollectionElementUpdates() { assertThat(set.get("aliased.$.otherValue"), is((Object) "bar")); } - /** - * @see DATAMONGO-407 - */ - @Test + @Test // DATAMONGO-407 public void updateMapperShouldWriteTypeInformationForComplexNestedCollectionElementUpdates() { Update update = Update.update("list.$.value", "foo").set("list.$.someObject", new ConcreteChildClass("42", "bubu")); @@ -217,11 +200,8 @@ public void updateMapperShouldWriteTypeInformationForComplexNestedCollectionElem assertThat(someObject.get("value"), is((Object) "bubu")); } - /** - * @see DATAMONGO-812 - */ @SuppressWarnings({ "unchecked", "rawtypes" }) - @Test + @Test // DATAMONGO-812 public void updateMapperShouldConvertPushCorrectlyWhenCalledWithEachUsingSimpleTypes() { Update update = new Update().push("values").each("spring", "data", "mongodb"); @@ -237,10 +217,7 @@ public void updateMapperShouldConvertPushCorrectlyWhenCalledWithEachUsingSimpleT assertThat(each.toMap(), (Matcher) allOf(hasValue("spring"), hasValue("data"), hasValue("mongodb"))); } - /** - * @see DATAMONGO-812 - */ - @Test + @Test // DATAMONGO-812 public void updateMapperShouldConvertPushWhithoutAddingClassInformationWhenUsedWithEvery() { Update update = new Update().push("values").each("spring", "data", "mongodb"); @@ -253,11 +230,8 @@ public void updateMapperShouldConvertPushWhithoutAddingClassInformationWhenUsedW assertThat(values.get("_class"), nullValue()); } - /** - * @see DATAMONGO-812 - */ @SuppressWarnings({ "unchecked", "rawtypes" }) - @Test + @Test // DATAMONGO-812 public void updateMapperShouldConvertPushCorrectlyWhenCalledWithEachUsingCustomTypes() { Update update = new Update().push("models").each(new ListModel("spring", "data", "mongodb")); @@ -272,10 +246,7 @@ public void updateMapperShouldConvertPushCorrectlyWhenCalledWithEachUsingCustomT assertThat(values.toMap(), (Matcher) allOf(hasValue("spring"), hasValue("data"), hasValue("mongodb"))); } - /** - * @see DATAMONGO-812 - */ - @Test + @Test // DATAMONGO-812 public void updateMapperShouldRetainClassInformationForPushCorrectlyWhenCalledWithEachUsingCustomTypes() { Update update = new Update().push("models").each(new ListModel("spring", "data", "mongodb")); @@ -289,10 +260,7 @@ public void updateMapperShouldRetainClassInformationForPushCorrectlyWhenCalledWi assertThat(((DBObject) each.get(0)).get("_class").toString(), equalTo(ListModel.class.getName())); } - /** - * @see DATAMONGO-812 - */ - @Test + @Test // DATAMONGO-812 public void testUpdateShouldAllowMultiplePushEachForDifferentFields() { Update update = new Update().push("category").each("spring", "data").push("type").each("mongodb"); @@ -303,10 +271,7 @@ public void testUpdateShouldAllowMultiplePushEachForDifferentFields() { assertThat(getAsDBObject(push, "type").containsField("$each"), is(true)); } - /** - * @see DATAMONGO-943 - */ - @Test + @Test // DATAMONGO-943 public void updatePushEachAtPositionWorksCorrectlyWhenGivenPositiveIndexParameter() { Update update = new Update().push("key").atPosition(2).each(Arrays.asList("Arya", "Arry", "Weasel")); @@ -321,10 +286,7 @@ public void updatePushEachAtPositionWorksCorrectlyWhenGivenPositiveIndexParamete assertThat(getAsDBObject(push, "key").containsField("$each"), is(true)); } - /** - * @see DATAMONGO-943 - */ - @Test + @Test // DATAMONGO-943 public void updatePushEachAtPositionWorksCorrectlyWhenGivenPositionFirst() { Update update = new Update().push("key").atPosition(Position.FIRST).each(Arrays.asList("Arya", "Arry", "Weasel")); @@ -339,10 +301,7 @@ public void updatePushEachAtPositionWorksCorrectlyWhenGivenPositionFirst() { assertThat(getAsDBObject(push, "key").containsField("$each"), is(true)); } - /** - * @see DATAMONGO-943 - */ - @Test + @Test // DATAMONGO-943 public void updatePushEachAtPositionWorksCorrectlyWhenGivenPositionLast() { Update update = new Update().push("key").atPosition(Position.LAST).each(Arrays.asList("Arya", "Arry", "Weasel")); @@ -356,10 +315,7 @@ public void updatePushEachAtPositionWorksCorrectlyWhenGivenPositionLast() { assertThat(getAsDBObject(push, "key").containsField("$each"), is(true)); } - /** - * @see DATAMONGO-943 - */ - @Test + @Test // DATAMONGO-943 public void updatePushEachAtPositionWorksCorrectlyWhenGivenPositionNull() { Update update = new Update().push("key").atPosition(null).each(Arrays.asList("Arya", "Arry", "Weasel")); @@ -373,10 +329,7 @@ public void updatePushEachAtPositionWorksCorrectlyWhenGivenPositionNull() { assertThat(getAsDBObject(push, "key").containsField("$each"), is(true)); } - /** - * @see DATAMONGO-832 - */ - @Test + @Test // DATAMONGO-832 public void updatePushEachWithSliceShouldRenderCorrectly() { Update update = new Update().push("key").slice(5).each(Arrays.asList("Arya", "Arry", "Weasel")); @@ -391,10 +344,7 @@ public void updatePushEachWithSliceShouldRenderCorrectly() { assertThat(key.containsField("$each"), is(true)); } - /** - * @see DATAMONGO-832 - */ - @Test + @Test // DATAMONGO-832 public void updatePushEachWithSliceShouldRenderWhenUsingMultiplePushCorrectly() { Update update = new Update().push("key").slice(5).each(Arrays.asList("Arya", "Arry", "Weasel")).push("key-2") @@ -416,10 +366,64 @@ public void updatePushEachWithSliceShouldRenderWhenUsingMultiplePushCorrectly() assertThat(key2.containsField("$each"), is(true)); } - /** - * @see DATAMONGO-410 - */ - @Test + @Test // DATAMONGO-1141 + public void updatePushEachWithValueSortShouldRenderCorrectly() { + + Update update = new Update().push("scores").sort(Direction.DESC).each(42, 23, 68); + + DBObject mappedObject = mapper.getMappedObject(update.getUpdateObject(), + context.getPersistentEntity(ParentClass.class)); + + DBObject push = getAsDBObject(mappedObject, "$push"); + DBObject key = getAsDBObject(push, "scores"); + + assertThat(key.containsField("$sort"), is(true)); + assertThat((Integer) key.get("$sort"), is(-1)); + assertThat(key.containsField("$each"), is(true)); + } + + @Test // DATAMONGO-1141 + public void updatePushEachWithDocumentSortShouldRenderCorrectly() { + + Update update = new Update().push("list") + .sort(new Sort(new Order(Direction.ASC, "value"), new Order(Direction.ASC, "field"))) + .each(Collections.emptyList()); + + DBObject mappedObject = mapper.getMappedObject(update.getUpdateObject(), + context.getPersistentEntity(EntityWithList.class)); + + DBObject push = getAsDBObject(mappedObject, "$push"); + DBObject key = getAsDBObject(push, "list"); + + assertThat(key.containsField("$sort"), is(true)); + assertThat((DBObject) key.get("$sort"), + equalTo(new BasicDBObjectBuilder().add("renamed-value", 1).add("field", 1).get())); + assertThat(key.containsField("$each"), is(true)); + } + + @Test // DATAMONGO-1141 + public void updatePushEachWithSortShouldRenderCorrectlyWhenUsingMultiplePush() { + + Update update = new Update().push("authors").sort(Direction.ASC).each("Harry").push("chapters") + .sort(new Sort(Direction.ASC, "order")).each(Collections.emptyList()); + + DBObject mappedObject = mapper.getMappedObject(update.getUpdateObject(), context.getPersistentEntity(Object.class)); + + DBObject push = getAsDBObject(mappedObject, "$push"); + DBObject key1 = getAsDBObject(push, "authors"); + + assertThat(key1.containsField("$sort"), is(true)); + assertThat((Integer) key1.get("$sort"), is(1)); + assertThat(key1.containsField("$each"), is(true)); + + DBObject key2 = getAsDBObject(push, "chapters"); + + assertThat(key2.containsField("$sort"), is(true)); + assertThat((DBObject) key2.get("$sort"), equalTo(new BasicDBObjectBuilder().add("order", 1).get())); + assertThat(key2.containsField("$each"), is(true)); + } + + @Test // DATAMONGO-410 public void testUpdateMapperShouldConsiderCustomWriteTarget() { List someValues = Arrays.asList(new NestedEntity("spring"), new NestedEntity("data"), @@ -432,10 +436,7 @@ public void testUpdateMapperShouldConsiderCustomWriteTarget() { verify(writingConverterSpy, times(3)).convert(Mockito.any(NestedEntity.class)); } - /** - * @see DATAMONGO-404 - */ - @Test + @Test // DATAMONGO-404 public void createsDbRefForEntityIdOnPulls() { Update update = new Update().pull("dbRefAnnotatedList.id", "2"); @@ -447,10 +448,7 @@ public void createsDbRefForEntityIdOnPulls() { assertThat(pullClause.get("dbRefAnnotatedList"), is((Object) new DBRef("entity", "2"))); } - /** - * @see DATAMONGO-404 - */ - @Test + @Test // DATAMONGO-404 public void createsDbRefForEntityOnPulls() { Entity entity = new Entity(); @@ -464,20 +462,14 @@ public void createsDbRefForEntityOnPulls() { assertThat(pullClause.get("dbRefAnnotatedList"), is((Object) new DBRef("entity", entity.id))); } - /** - * @see DATAMONGO-404 - */ - @Test(expected = MappingException.class) + @Test(expected = MappingException.class) // DATAMONGO-404 public void rejectsInvalidFieldReferenceForDbRef() { Update update = new Update().pull("dbRefAnnotatedList.name", "NAME"); mapper.getMappedObject(update.getUpdateObject(), context.getPersistentEntity(DocumentWithDBRefCollection.class)); } - /** - * @see DATAMONGO-404 - */ - @Test + @Test // DATAMONGO-404 public void rendersNestedDbRefCorrectly() { Update update = new Update().pull("nested.dbRefAnnotatedList.id", "2"); @@ -488,10 +480,7 @@ public void rendersNestedDbRefCorrectly() { assertThat(pullClause.containsField("mapped.dbRefAnnotatedList"), is(true)); } - /** - * @see DATAMONGO-468 - */ - @Test + @Test // DATAMONGO-468 public void rendersUpdateOfDbRefPropertyWithDomainObjectCorrectly() { Entity entity = new Entity(); @@ -505,10 +494,7 @@ public void rendersUpdateOfDbRefPropertyWithDomainObjectCorrectly() { assertThat(setClause.get("dbRefProperty"), is((Object) new DBRef("entity", entity.id))); } - /** - * @see DATAMONGO-862 - */ - @Test + @Test // DATAMONGO-862 public void rendersUpdateAndPreservesKeyForPathsNotPointingToProperty() { Update update = new Update().set("listOfInterface.$.value", "expected-value"); @@ -519,10 +505,7 @@ public void rendersUpdateAndPreservesKeyForPathsNotPointingToProperty() { assertThat(setClause.containsField("listOfInterface.$.value"), is(true)); } - /** - * @see DATAMONGO-863 - */ - @Test + @Test // DATAMONGO-863 public void doesNotConvertRawDbObjects() { Update update = new Update(); @@ -540,11 +523,8 @@ public void doesNotConvertRawDbObjects() { assertThat(inClause, IsIterableContainingInOrder. contains(1L, 2L)); } - /** - * @see DATAMONG0-471 - */ @SuppressWarnings({ "unchecked", "rawtypes" }) - @Test + @Test // DATAMONG0-471 public void testUpdateShouldApply$addToSetCorrectlyWhenUsedWith$each() { Update update = new Update().addToSet("values").each("spring", "data", "mongodb"); @@ -558,10 +538,7 @@ public void doesNotConvertRawDbObjects() { assertThat(each.toMap(), (Matcher) allOf(hasValue("spring"), hasValue("data"), hasValue("mongodb"))); } - /** - * @see DATAMONG0-471 - */ - @Test + @Test // DATAMONG0-471 public void testUpdateShouldRetainClassTypeInformationWhenUsing$addToSetWith$eachForCustomTypes() { Update update = new Update().addToSet("models").each(new ModelImpl(2014), new ModelImpl(1), new ModelImpl(28)); @@ -579,10 +556,7 @@ public void doesNotConvertRawDbObjects() { } } - /** - * @see DATAMONGO-897 - */ - @Test + @Test // DATAMONGO-897 public void updateOnDbrefPropertyOfInterfaceTypeWithoutExplicitGetterForIdShouldBeMappedCorrectly() { Update update = new Update().set("referencedDocument", new InterfaceDocumentDefinitionImpl("1", "Foo")); @@ -596,10 +570,7 @@ public void updateOnDbrefPropertyOfInterfaceTypeWithoutExplicitGetterForIdShould assertThat(model, allOf(instanceOf(DBRef.class), IsEqual. equalTo(expectedDBRef))); } - /** - * @see DATAMONGO-847 - */ - @Test + @Test // DATAMONGO-847 public void updateMapperConvertsNestedQueryCorrectly() { Update update = new Update().pull("list", Query.query(Criteria.where("value").in("foo", "bar"))); @@ -614,10 +585,7 @@ public void updateMapperConvertsNestedQueryCorrectly() { assertThat($in, IsIterableContainingInOrder. contains("foo", "bar")); } - /** - * @see DATAMONGO-847 - */ - @Test + @Test // DATAMONGO-847 public void updateMapperConvertsPullWithNestedQuerfyOnDBRefCorrectly() { Update update = new Update().pull("dbRefAnnotatedList", Query.query(Criteria.where("id").is("1"))); @@ -630,10 +598,7 @@ public void updateMapperConvertsPullWithNestedQuerfyOnDBRefCorrectly() { assertThat(list, equalTo(new BasicDBObjectBuilder().add("_id", "1").get())); } - /** - * @see DATAMONGO-1077 - */ - @Test + @Test // DATAMONGO-1077 public void shouldNotRemovePositionalParameter() { Update update = new Update(); @@ -647,10 +612,7 @@ public void shouldNotRemovePositionalParameter() { assertThat($unset, equalTo(new BasicDBObjectBuilder().add("dbRefAnnotatedList.$", 1).get())); } - /** - * @see DATAMONGO-1210 - */ - @Test + @Test // DATAMONGO-1210 public void mappingEachOperatorShouldNotAddTypeInfoForNonInterfaceNonAbstractTypes() { Update update = new Update().addToSet("nestedDocs").each(new NestedDocument("nested-1"), @@ -663,10 +625,7 @@ public void mappingEachOperatorShouldNotAddTypeInfoForNonInterfaceNonAbstractTyp assertThat(mappedUpdate, isBsonObject().notContaining("$addToSet.nestedDocs.$each.[1]._class")); } - /** - * @see DATAMONGO-1210 - */ - @Test + @Test // DATAMONGO-1210 public void mappingEachOperatorShouldAddTypeHintForInterfaceTypes() { Update update = new Update().addToSet("models").each(new ModelImpl(1), new ModelImpl(2)); @@ -678,10 +637,7 @@ public void mappingEachOperatorShouldAddTypeHintForInterfaceTypes() { assertThat(mappedUpdate, isBsonObject().containing("$addToSet.models.$each.[1]._class", ModelImpl.class.getName())); } - /** - * @see DATAMONGO-1210 - */ - @Test + @Test // DATAMONGO-1210 public void mappingEachOperatorShouldAddTypeHintForAbstractTypes() { Update update = new Update().addToSet("list").each(new ConcreteChildClass("foo", "one"), @@ -696,10 +652,7 @@ public void mappingEachOperatorShouldAddTypeHintForAbstractTypes() { isBsonObject().containing("$addToSet.aliased.$each.[1]._class", ConcreteChildClass.class.getName())); } - /** - * @see DATAMONGO-1210 - */ - @Test + @Test // DATAMONGO-1210 public void mappingShouldOnlyRemoveTypeHintFromTopLevelTypeInCaseOfNestedDocument() { WrapperAroundInterfaceType wait = new WrapperAroundInterfaceType(); @@ -717,10 +670,7 @@ public void mappingShouldOnlyRemoveTypeHintFromTopLevelTypeInCaseOfNestedDocumen ModelImpl.class.getName())); } - /** - * @see DATAMONGO-1210 - */ - @Test + @Test // DATAMONGO-1210 public void mappingShouldRetainTypeInformationOfNestedListWhenUpdatingConcreteyParentType() { ListModelWrapper lmw = new ListModelWrapper(); @@ -735,10 +685,7 @@ public void mappingShouldRetainTypeInformationOfNestedListWhenUpdatingConcreteyP .containing("$set.concreteTypeWithListAttributeOfInterfaceType.models.[0]._class", ModelImpl.class.getName())); } - /** - * @see DATAMONGO-1236 - */ - @Test + @Test // DATAMONGO-1236 public void mappingShouldRetainTypeInformationForObjectValues() { Update update = new Update().set("value", new NestedDocument("kaladin")); @@ -749,10 +696,7 @@ public void mappingShouldRetainTypeInformationForObjectValues() { assertThat(mappedUpdate, isBsonObject().containing("$set.value._class", NestedDocument.class.getName())); } - /** - * @see DATAMONGO-1236 - */ - @Test + @Test // DATAMONGO-1236 public void mappingShouldNotRetainTypeInformationForConcreteValues() { Update update = new Update().set("concreteValue", new NestedDocument("shallan")); @@ -763,10 +707,7 @@ public void mappingShouldNotRetainTypeInformationForConcreteValues() { assertThat(mappedUpdate, isBsonObject().notContaining("$set.concreteValue._class")); } - /** - * @see DATAMONGO-1236 - */ - @Test + @Test // DATAMONGO-1236 public void mappingShouldRetainTypeInformationForObjectValuesWithAlias() { Update update = new Update().set("value", new NestedDocument("adolin")); @@ -777,10 +718,7 @@ public void mappingShouldRetainTypeInformationForObjectValuesWithAlias() { assertThat(mappedUpdate, isBsonObject().containing("$set.renamed-value._class", NestedDocument.class.getName())); } - /** - * @see DATAMONGO-1236 - */ - @Test + @Test // DATAMONGO-1236 public void mappingShouldRetrainTypeInformationWhenValueTypeOfMapDoesNotMatchItsDeclaration() { Map map = Collections. singletonMap("szeth", new NestedDocument("son-son-vallano")); @@ -793,10 +731,7 @@ public void mappingShouldRetrainTypeInformationWhenValueTypeOfMapDoesNotMatchIts assertThat(mappedUpdate, isBsonObject().containing("$set.map.szeth._class", NestedDocument.class.getName())); } - /** - * @see DATAMONGO-1236 - */ - @Test + @Test // DATAMONGO-1236 public void mappingShouldNotContainTypeInformationWhenValueTypeOfMapMatchesDeclaration() { Map map = Collections. singletonMap("jasnah", @@ -810,10 +745,7 @@ public void mappingShouldNotContainTypeInformationWhenValueTypeOfMapMatchesDecla assertThat(mappedUpdate, isBsonObject().notContaining("$set.concreteMap.jasnah._class")); } - /** - * @see DATAMONGO-1250 - */ - @Test + @Test // DATAMONGO-1250 @SuppressWarnings("unchecked") public void mapsUpdateWithBothReadingAndWritingConverterRegistered() { @@ -837,10 +769,7 @@ public void mapsUpdateWithBothReadingAndWritingConverterRegistered() { assertThat(result, isBsonObject().containing("$set.allocation", Allocation.AVAILABLE.code)); } - /** - * @see DATAMONGO-1251 - */ - @Test + @Test // DATAMONGO-1251 public void mapsNullValueCorrectlyForSimpleTypes() { Update update = new Update().set("value", null); @@ -853,10 +782,7 @@ public void mapsNullValueCorrectlyForSimpleTypes() { assertThat($set.get("value"), nullValue()); } - /** - * @see DATAMONGO-1251 - */ - @Test + @Test // DATAMONGO-1251 public void mapsNullValueCorrectlyForJava8Date() { Update update = new Update().set("date", null); @@ -869,10 +795,7 @@ public void mapsNullValueCorrectlyForJava8Date() { assertThat($set.get("value"), nullValue()); } - /** - * @see DATAMONGO-1251 - */ - @Test + @Test // DATAMONGO-1251 public void mapsNullValueCorrectlyForCollectionTypes() { Update update = new Update().set("values", null); @@ -885,10 +808,7 @@ public void mapsNullValueCorrectlyForCollectionTypes() { assertThat($set.get("value"), nullValue()); } - /** - * @see DATAMONGO-1251 - */ - @Test + @Test // DATAMONGO-1251 public void mapsNullValueCorrectlyForPropertyOfNestedDocument() { Update update = new Update().set("concreteValue.name", null); @@ -901,10 +821,7 @@ public void mapsNullValueCorrectlyForPropertyOfNestedDocument() { assertThat($set.get("concreteValue.name"), nullValue()); } - /** - * @see DATAMONGO-1288 - */ - @Test + @Test // DATAMONGO-1288 public void mapsAtomicIntegerToIntegerCorrectly() { Update update = new Update().set("intValue", new AtomicInteger(10)); @@ -915,10 +832,7 @@ public void mapsAtomicIntegerToIntegerCorrectly() { assertThat($set.get("intValue"), Is. is(10)); } - /** - * @see DATAMONGO-1288 - */ - @Test + @Test // DATAMONGO-1288 public void mapsAtomicIntegerToPrimitiveIntegerCorrectly() { Update update = new Update().set("primIntValue", new AtomicInteger(10)); @@ -929,10 +843,7 @@ public void mapsAtomicIntegerToPrimitiveIntegerCorrectly() { assertThat($set.get("primIntValue"), Is. is(10)); } - /** - * @see DATAMONGO-1404 - */ - @Test + @Test // DATAMONGO-1404 public void mapsMinCorrectly() { Update update = new Update().min("minfield", 10); @@ -942,10 +853,7 @@ public void mapsMinCorrectly() { assertThat(mappedUpdate, isBsonObject().containing("$min", new BasicDBObject("minfield", 10))); } - /** - * @see DATAMONGO-1404 - */ - @Test + @Test // DATAMONGO-1404 public void mapsMaxCorrectly() { Update update = new Update().max("maxfield", 999); @@ -955,10 +863,7 @@ public void mapsMaxCorrectly() { assertThat(mappedUpdate, isBsonObject().containing("$max", new BasicDBObject("maxfield", 999))); } - /** - * @see DATAMONGO-1423 - */ - @Test + @Test // DATAMONGO-1423 @SuppressWarnings("unchecked") public void mappingShouldConsiderCustomConvertersForEnumMapKeys() { @@ -982,10 +887,7 @@ public void mappingShouldConsiderCustomConvertersForEnumMapKeys() { assertThat(result, isBsonObject().containing("$set.enumAsMapKey.V", 100)); } - /** - * @see DATAMONGO-1486 - */ - @Test + @Test // DATAMONGO-1486 public void mappingShouldConvertMapKeysToString() { Update update = new Update().set("map", Collections.singletonMap(25, "#StarTrek50")); @@ -1211,9 +1113,14 @@ static class EntityWithObject { NestedDocument concreteValue; } + static class EntityWithList { + List list; + } + static class EntityWithAliasedObject { @Field("renamed-value") Object value; + Object field; } static class EntityWithObjectMap { diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/AbstractGeoSpatialTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/AbstractGeoSpatialTests.java index 9b756bf01b..59ef1c4855 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/AbstractGeoSpatialTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/AbstractGeoSpatialTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2016 the original author or authors. + * Copyright 2015-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -175,10 +175,7 @@ public void nearSphere() { assertThat(venues.size(), is(11)); } - /** - * @see DATAMONGO-1360 - */ - @Test + @Test // DATAMONGO-1360 public void mapsQueryContainedInNearQuery() { Query query = query(where("openingDate").lt(LocalDate.now())); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/GeoJsonModuleUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/GeoJsonModuleUnitTests.java index 97b282317d..6950c5c601 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/GeoJsonModuleUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/GeoJsonModuleUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 the original author or authors. + * Copyright 2015-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -43,10 +43,7 @@ public void setUp() { mapper.registerModule(new GeoJsonModule()); } - /** - * @see DATAMONGO-1181 - */ - @Test + @Test // DATAMONGO-1181 public void shouldDeserializeJsonPointCorrectly() throws JsonParseException, JsonMappingException, IOException { String json = "{ \"type\": \"Point\", \"coordinates\": [10.0, 20.0] }"; @@ -54,10 +51,7 @@ public void shouldDeserializeJsonPointCorrectly() throws JsonParseException, Jso assertThat(mapper.readValue(json, GeoJsonPoint.class), is(new GeoJsonPoint(10D, 20D))); } - /** - * @see DATAMONGO-1181 - */ - @Test + @Test // DATAMONGO-1181 public void shouldDeserializeGeoJsonLineStringCorrectly() throws JsonParseException, JsonMappingException, IOException { @@ -67,10 +61,7 @@ public void shouldDeserializeGeoJsonLineStringCorrectly() throws JsonParseExcept is(new GeoJsonLineString(Arrays.asList(new Point(10, 20), new Point(30, 40), new Point(50, 60))))); } - /** - * @see DATAMONGO-1181 - */ - @Test + @Test // DATAMONGO-1181 public void shouldDeserializeGeoJsonMultiPointCorrectly() throws JsonParseException, JsonMappingException, IOException { @@ -80,10 +71,7 @@ public void shouldDeserializeGeoJsonMultiPointCorrectly() throws JsonParseExcept is(new GeoJsonMultiPoint(Arrays.asList(new Point(10, 20), new Point(30, 40), new Point(50, 60))))); } - /** - * @see DATAMONGO-1181 - */ - @Test + @Test // DATAMONGO-1181 @SuppressWarnings("unchecked") public void shouldDeserializeGeoJsonMultiLineStringCorrectly() throws JsonParseException, JsonMappingException, IOException { @@ -96,10 +84,7 @@ public void shouldDeserializeGeoJsonMultiLineStringCorrectly() throws JsonParseE 60), new Point(70, 80))))); } - /** - * @see DATAMONGO-1181 - */ - @Test + @Test // DATAMONGO-1181 public void shouldDeserializeGeoJsonPolygonCorrectly() throws JsonParseException, JsonMappingException, IOException { String json = "{ \"type\": \"Polygon\", \"coordinates\": [ [ [100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0] ] ]}"; @@ -110,10 +95,7 @@ public void shouldDeserializeGeoJsonPolygonCorrectly() throws JsonParseException new Point(100, 0))))); } - /** - * @see DATAMONGO-1181 - */ - @Test + @Test // DATAMONGO-1181 public void shouldDeserializeGeoJsonMultiPolygonCorrectly() throws JsonParseException, JsonMappingException, IOException { diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/GeoJsonTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/GeoJsonTests.java index c94172774f..9a8e79e958 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/GeoJsonTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/GeoJsonTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2016 the original author or authors. + * Copyright 2015-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -95,10 +95,7 @@ public void tearDown() { removeCollections(); } - /** - * @see DATAMONGO-1135 - */ - @Test + @Test // DATAMONGO-1135 public void geoNear() { NearQuery geoNear = NearQuery.near(new GeoJsonPoint(-73, 40), Metrics.KILOMETERS).num(10).maxDistance(150); @@ -109,10 +106,7 @@ public void geoNear() { assertThat(result.getAverageDistance().getMetric(), is((Metric) Metrics.KILOMETERS)); } - /** - * @see DATAMONGO-1135 - */ - @Test + @Test // DATAMONGO-1135 public void withinPolygon() { Point first = new Point(-73.99756, 40.73083); @@ -126,10 +120,7 @@ public void withinPolygon() { assertThat(venues.size(), is(4)); } - /** - * @see DATAMONGO-1135 - */ - @Test + @Test // DATAMONGO-1135 public void nearPoint() { GeoJsonPoint point = new GeoJsonPoint(-73.99171, 40.738868); @@ -139,10 +130,7 @@ public void nearPoint() { assertThat(venues.size(), is(1)); } - /** - * @see DATAMONGO-1135 - */ - @Test + @Test // DATAMONGO-1135 public void nearSphere() { GeoJsonPoint point = new GeoJsonPoint(-73.99171, 40.738868); @@ -153,10 +141,7 @@ public void nearSphere() { assertThat(venues.size(), is(1)); } - /** - * @see DATAMONGO-1137 - */ - @Test + @Test // DATAMONGO-1137 public void shouleSaveAndRetrieveDocumentWithGeoJsonPointTypeCorrectly() { DocumentWithPropertyUsingGeoJsonType obj = new DocumentWithPropertyUsingGeoJsonType(); @@ -171,10 +156,7 @@ public void shouleSaveAndRetrieveDocumentWithGeoJsonPointTypeCorrectly() { assertThat(result.geoJsonPoint, equalTo(obj.geoJsonPoint)); } - /** - * @see DATAMONGO-1137 - */ - @Test + @Test // DATAMONGO-1137 public void shouleSaveAndRetrieveDocumentWithGeoJsonPolygonTypeCorrectly() { DocumentWithPropertyUsingGeoJsonType obj = new DocumentWithPropertyUsingGeoJsonType(); @@ -190,10 +172,7 @@ public void shouleSaveAndRetrieveDocumentWithGeoJsonPolygonTypeCorrectly() { assertThat(result.geoJsonPolygon, equalTo(obj.geoJsonPolygon)); } - /** - * @see DATAMONGO-1137 - */ - @Test + @Test // DATAMONGO-1137 public void shouleSaveAndRetrieveDocumentWithGeoJsonLineStringTypeCorrectly() { DocumentWithPropertyUsingGeoJsonType obj = new DocumentWithPropertyUsingGeoJsonType(); @@ -208,10 +187,7 @@ public void shouleSaveAndRetrieveDocumentWithGeoJsonLineStringTypeCorrectly() { assertThat(result.geoJsonLineString, equalTo(obj.geoJsonLineString)); } - /** - * @see DATAMONGO-1137 - */ - @Test + @Test // DATAMONGO-1137 public void shouleSaveAndRetrieveDocumentWithGeoJsonMultiLineStringTypeCorrectly() { DocumentWithPropertyUsingGeoJsonType obj = new DocumentWithPropertyUsingGeoJsonType(); @@ -227,10 +203,7 @@ public void shouleSaveAndRetrieveDocumentWithGeoJsonMultiLineStringTypeCorrectly assertThat(result.geoJsonMultiLineString, equalTo(obj.geoJsonMultiLineString)); } - /** - * @see DATAMONGO-1137 - */ - @Test + @Test // DATAMONGO-1137 public void shouleSaveAndRetrieveDocumentWithGeoJsonMultiPointTypeCorrectly() { DocumentWithPropertyUsingGeoJsonType obj = new DocumentWithPropertyUsingGeoJsonType(); @@ -245,10 +218,7 @@ public void shouleSaveAndRetrieveDocumentWithGeoJsonMultiPointTypeCorrectly() { assertThat(result.geoJsonMultiPoint, equalTo(obj.geoJsonMultiPoint)); } - /** - * @see DATAMONGO-1137 - */ - @Test + @Test // DATAMONGO-1137 public void shouleSaveAndRetrieveDocumentWithGeoJsonMultiPolygonTypeCorrectly() { DocumentWithPropertyUsingGeoJsonType obj = new DocumentWithPropertyUsingGeoJsonType(); @@ -264,10 +234,7 @@ public void shouleSaveAndRetrieveDocumentWithGeoJsonMultiPolygonTypeCorrectly() assertThat(result.geoJsonMultiPolygon, equalTo(obj.geoJsonMultiPolygon)); } - /** - * @see DATAMONGO-1137 - */ - @Test + @Test // DATAMONGO-1137 public void shouleSaveAndRetrieveDocumentWithGeoJsonGeometryCollectionTypeCorrectly() { DocumentWithPropertyUsingGeoJsonType obj = new DocumentWithPropertyUsingGeoJsonType(); @@ -284,10 +251,7 @@ public void shouleSaveAndRetrieveDocumentWithGeoJsonGeometryCollectionTypeCorrec assertThat(result.geoJsonGeometryCollection, equalTo(obj.geoJsonGeometryCollection)); } - /** - * @see DATAMONGO-1110 - */ - @Test + @Test // DATAMONGO-1110 public void nearWithMinDistance() { Point point = new GeoJsonPoint(-73.99171, 40.738868); @@ -297,10 +261,7 @@ public void nearWithMinDistance() { assertThat(venues.size(), is(11)); } - /** - * @see DATAMONGO-1110 - */ - @Test + @Test // DATAMONGO-1110 public void nearSphereWithMinDistance() { Point point = new GeoJsonPoint(-73.99171, 40.738868); @@ -310,10 +271,7 @@ public void nearSphereWithMinDistance() { assertThat(venues.size(), is(11)); } - /** - * @see DATAMONGO-1135 - */ - @Test + @Test // DATAMONGO-1135 public void nearWithMinAndMaxDistance() { GeoJsonPoint point = new GeoJsonPoint(-73.99171, 40.738868); @@ -323,10 +281,7 @@ public void nearWithMinAndMaxDistance() { assertThat(venues.size(), is(2)); } - /** - * @see DATAMONGO-1453 - */ - @Test + @Test // DATAMONGO-1453 public void shouldConvertPointRepresentationCorrectlyWhenSourceCoordinatesUsesInteger() { this.template.execute(template.getCollectionName(DocumentWithPropertyUsingGeoJsonType.class), @@ -351,10 +306,7 @@ public Object doInCollection(DBCollection collection) throws MongoException, Dat DocumentWithPropertyUsingGeoJsonType.class).geoJsonPoint, is(equalTo(new GeoJsonPoint(0D, 0D)))); } - /** - * @see DATAMONGO-1453 - */ - @Test + @Test // DATAMONGO-1453 public void shouldConvertLineStringRepresentationCorrectlyWhenSourceCoordinatesUsesInteger() { this.template.execute(template.getCollectionName(DocumentWithPropertyUsingGeoJsonType.class), diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/GeoSpatial2DSphereTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/GeoSpatial2DSphereTests.java index a51bd46b97..a069b183c8 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/GeoSpatial2DSphereTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/GeoSpatial2DSphereTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2010-2015 the original author or authors. + * Copyright 2010-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -42,10 +42,7 @@ */ public class GeoSpatial2DSphereTests extends AbstractGeoSpatialTests { - /** - * @see DATAMONGO-360 - */ - @Test + @Test // DATAMONGO-360 public void indexInfoIsCorrect() { IndexOperations operations = template.indexOps(Venue.class); @@ -62,10 +59,7 @@ public void indexInfoIsCorrect() { assertThat(fields, hasItem(IndexField.geo("location"))); } - /** - * @see DATAMONGO-1110 - */ - @Test + @Test // DATAMONGO-1110 public void geoNearWithMinDistance() { NearQuery geoNear = NearQuery.near(-73, 40, Metrics.KILOMETERS).num(10).minDistance(1); @@ -76,10 +70,7 @@ public void geoNearWithMinDistance() { assertThat(result.getAverageDistance().getMetric(), is((Metric) Metrics.KILOMETERS)); } - /** - * @see DATAMONGO-1110 - */ - @Test + @Test // DATAMONGO-1110 public void nearSphereWithMinDistance() { Point point = new Point(-73.99171, 40.738868); List venues = template.find(query(where("location").nearSphere(point).minDistance(0.01)), Venue.class); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/GeoSpatial2DTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/GeoSpatial2DTests.java index e9248f63b0..896871b34f 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/GeoSpatial2DTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/GeoSpatial2DTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2010-2015 the original author or authors. + * Copyright 2010-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -50,10 +50,7 @@ public void nearPoint() { assertThat(venues.size(), is(7)); } - /** - * @see DATAMONGO-360 - */ - @Test + @Test // DATAMONGO-360 public void indexInfoIsCorrect() { IndexOperations operations = template.indexOps(Venue.class); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/GeoSpatialIndexTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/GeoSpatialIndexTests.java index 1c2f0a6832..7341317d46 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/GeoSpatialIndexTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/GeoSpatialIndexTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2014 the original author or authors. + * Copyright 2013-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -60,10 +60,7 @@ public void setUp() { template.setWriteResultChecking(WriteResultChecking.EXCEPTION); } - /** - * @see DATAMONGO-778 - */ - @Test + @Test // DATAMONGO-778 public void test2dIndex() { try { @@ -74,10 +71,7 @@ public void test2dIndex() { } } - /** - * @see DATAMONGO-778 - */ - @Test + @Test // DATAMONGO-778 public void test2dSphereIndex() { try { @@ -88,10 +82,7 @@ public void test2dSphereIndex() { } } - /** - * @see DATAMONGO-778 - */ - @Test + @Test // DATAMONGO-778 public void testHaystackIndex() { try { @@ -102,10 +93,7 @@ public void testHaystackIndex() { } } - /** - * @see DATAMONGO-827 - */ - @Test + @Test // DATAMONGO-827 public void useGeneratedNameShouldGenerateAnIndexName() { try { diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/IndexingIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/IndexingIntegrationTests.java index 2b748fc8d9..abc4d6c6b7 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/IndexingIntegrationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/IndexingIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2015 by the original author(s). + * Copyright 2011-2017 by the original author(s). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -62,10 +62,7 @@ public void tearDown() { operations.dropCollection(IndexedPerson.class); } - /** - * @see DATAMONGO-237 - */ - @Test + @Test // DATAMONGO-237 @DirtiesContext public void createsIndexWithFieldName() { @@ -74,10 +71,7 @@ public void createsIndexWithFieldName() { assertThat(hasIndex("_firstname", IndexedPerson.class), is(true)); } - /** - * @see DATAMONGO-1163 - */ - @Test + @Test // DATAMONGO-1163 @DirtiesContext public void createsIndexFromMetaAnnotation() { diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexCreatorIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexCreatorIntegrationTests.java index 529828fe41..7ac592c1b9 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexCreatorIntegrationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexCreatorIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2015 the original author or authors. + * Copyright 2012-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -83,10 +83,7 @@ public void createsIndexForConfiguredMappingContextOnly() { assertThat(indexInfo, hasSize(0)); } - /** - * @see DATAMONGO-1202 - */ - @Test + @Test // DATAMONGO-1202 public void shouldHonorIndexedPropertiesWithRecursiveMappings() { List indexInfo = templateOne.indexOps(RecursiveConcreteType.class).getIndexInfo(); @@ -95,10 +92,7 @@ public void shouldHonorIndexedPropertiesWithRecursiveMappings() { assertThat(indexInfo, Matchers. hasItem(hasProperty("name", is("firstName")))); } - /** - * @DATAMONGO-1125 - */ - @Test + @Test // DATAMONGO-1125 public void createIndexShouldThrowMeaningfulExceptionWhenIndexCreationFails() throws UnknownHostException { expectedException.expect(DataIntegrityViolationException.class); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexCreatorUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexCreatorUnitTests.java index 093286aab7..2013e49aeb 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexCreatorUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexCreatorUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2015 the original author or authors. + * Copyright 2012-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -113,10 +113,7 @@ public void doesNotCreateIndexForEntityComingFromDifferentMappingContext() { verifyZeroInteractions(collection); } - /** - * @see DATAMONGO-530 - */ - @Test + @Test // DATAMONGO-530 public void isIndexCreatorForMappingContextHandedIntoConstructor() { MongoMappingContext mappingContext = new MongoMappingContext(); @@ -127,10 +124,7 @@ public void isIndexCreatorForMappingContextHandedIntoConstructor() { assertThat(creator.isIndexCreatorFor(new MongoMappingContext()), is(false)); } - /** - * @see DATAMONGO-554 - */ - @Test + @Test // DATAMONGO-554 public void triggersBackgroundIndexingIfConfigured() { MongoMappingContext mappingContext = prepareMappingContext(AnotherPerson.class); @@ -143,10 +137,7 @@ public void triggersBackgroundIndexingIfConfigured() { assertThat(optionsCaptor.getValue().get("expireAfterSeconds"), nullValue()); } - /** - * @see DATAMONGO-544 - */ - @Test + @Test // DATAMONGO-544 public void expireAfterSecondsIfConfigured() { MongoMappingContext mappingContext = prepareMappingContext(Milk.class); @@ -157,10 +148,7 @@ public void expireAfterSecondsIfConfigured() { assertThat(optionsCaptor.getValue().get("expireAfterSeconds"), IsEqual. equalTo(60L)); } - /** - * @see DATAMONGO-899 - */ - @Test + @Test // DATAMONGO-899 public void createsNotNestedGeoSpatialIndexCorrectly() { MongoMappingContext mappingContext = prepareMappingContext(Wrapper.class); @@ -171,10 +159,7 @@ public void createsNotNestedGeoSpatialIndexCorrectly() { .add("min", -180).add("max", 180).add("bits", 26).get())); } - /** - * @see DATAMONGO-827 - */ - @Test + @Test // DATAMONGO-827 public void autoGeneratedIndexNameShouldGenerateNoName() { MongoMappingContext mappingContext = prepareMappingContext(EntityWithGeneratedIndexName.class); @@ -185,10 +170,7 @@ public void autoGeneratedIndexNameShouldGenerateNoName() { assertThat(optionsCaptor.getValue(), is(new BasicDBObjectBuilder().get())); } - /** - * @see DATAMONGO-367 - */ - @Test + @Test // DATAMONGO-367 public void indexCreationShouldNotCreateNewCollectionForNestedGeoSpatialIndexStructures() { MongoMappingContext mappingContext = prepareMappingContext(Wrapper.class); @@ -200,10 +182,7 @@ public void indexCreationShouldNotCreateNewCollectionForNestedGeoSpatialIndexStr assertThat(collectionNameCapturer.getValue(), equalTo("wrapper")); } - /** - * @see DATAMONGO-367 - */ - @Test + @Test // DATAMONGO-367 public void indexCreationShouldNotCreateNewCollectionForNestedIndexStructures() { MongoMappingContext mappingContext = prepareMappingContext(IndexedDocumentWrapper.class); @@ -215,10 +194,7 @@ public void indexCreationShouldNotCreateNewCollectionForNestedIndexStructures() assertThat(collectionNameCapturer.getValue(), equalTo("indexedDocumentWrapper")); } - /** - * @see DATAMONGO-1125 - */ - @Test(expected = DataAccessException.class) + @Test(expected = DataAccessException.class) // DATAMONGO-1125 public void createIndexShouldUsePersistenceExceptionTranslatorForNonDataIntegrityConcerns() { when(factory.getExceptionTranslator()).thenReturn(new MongoExceptionTranslator()); @@ -230,10 +206,7 @@ public void createIndexShouldUsePersistenceExceptionTranslatorForNonDataIntegrit new MongoPersistentEntityIndexCreator(mappingContext, factory); } - /** - * @see DATAMONGO-1125 - */ - @Test(expected = ClassCastException.class) + @Test(expected = ClassCastException.class) // DATAMONGO-1125 public void createIndexShouldNotConvertUnknownExceptionTypes() { when(factory.getExceptionTranslator()).thenReturn(new MongoExceptionTranslator()); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexResolverUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexResolverUnitTests.java index 97554b5582..08bb3391d8 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexResolverUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexResolverUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2016 the original author or authors. + * Copyright 2014-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -70,10 +70,7 @@ public class MongoPersistentEntityIndexResolverUnitTests { */ public static class IndexResolutionTests { - /** - * @see DATAMONGO-899 - */ - @Test + @Test // DATAMONGO-899 public void indexPathOnLevelZeroIsResolvedCorrectly() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -83,10 +80,7 @@ public void indexPathOnLevelZeroIsResolvedCorrectly() { assertIndexPathAndCollection("indexedProperty", "Zero", indexDefinitions.get(0)); } - /** - * @see DATAMONGO-899 - */ - @Test + @Test // DATAMONGO-899 public void indexPathOnLevelOneIsResolvedCorrectly() { List indexDefinitions = prepareMappingContextAndResolveIndexForType(IndexOnLevelOne.class); @@ -95,10 +89,7 @@ public void indexPathOnLevelOneIsResolvedCorrectly() { assertIndexPathAndCollection("zero.indexedProperty", "One", indexDefinitions.get(0)); } - /** - * @see DATAMONGO-899 - */ - @Test + @Test // DATAMONGO-899 public void depplyNestedIndexPathIsResolvedCorrectly() { List indexDefinitions = prepareMappingContextAndResolveIndexForType(IndexOnLevelTwo.class); @@ -107,10 +98,7 @@ public void depplyNestedIndexPathIsResolvedCorrectly() { assertIndexPathAndCollection("one.zero.indexedProperty", "Two", indexDefinitions.get(0)); } - /** - * @see DATAMONGO-899 - */ - @Test + @Test // DATAMONGO-899 public void resolvesIndexPathNameForNamedPropertiesCorrectly() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -121,10 +109,7 @@ public void resolvesIndexPathNameForNamedPropertiesCorrectly() { indexDefinitions.get(0)); } - /** - * @see DATAMONGO-899 - */ - @Test + @Test // DATAMONGO-899 public void resolvesIndexDefinitionCorrectly() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -135,10 +120,7 @@ public void resolvesIndexDefinitionCorrectly() { equalTo(new BasicDBObjectBuilder().add("name", "indexedProperty").get())); } - /** - * @see DATAMONGO-899 - */ - @Test + @Test // DATAMONGO-899 public void resolvesIndexDefinitionOptionsCorrectly() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -150,10 +132,7 @@ public void resolvesIndexDefinitionOptionsCorrectly() { .add("sparse", true).add("background", true).add("expireAfterSeconds", 10L).get())); } - /** - * @see DATAMONGO-899 - */ - @Test + @Test // DATAMONGO-899 public void resolvesIndexCollectionNameCorrectlyWhenDefinedInAnnotation() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -161,10 +140,7 @@ public void resolvesIndexCollectionNameCorrectlyWhenDefinedInAnnotation() { assertThat(indexDefinitions.get(0).getCollection(), equalTo("CollectionOverride")); } - /** - * @see DATAMONGO-1297 - */ - @Test + @Test // DATAMONGO-1297 public void resolvesIndexOnDbrefWhenDefined() { List indexDefinitions = prepareMappingContextAndResolveIndexForType(WithDbRef.class); @@ -175,10 +151,7 @@ public void resolvesIndexOnDbrefWhenDefined() { equalTo(new BasicDBObjectBuilder().add("indexedDbRef", 1).get())); } - /** - * @see DATAMONGO-1297 - */ - @Test + @Test // DATAMONGO-1297 public void resolvesIndexOnDbrefWhenDefinedOnNestedElement() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -190,10 +163,7 @@ public void resolvesIndexOnDbrefWhenDefinedOnNestedElement() { equalTo(new BasicDBObjectBuilder().add("nested.indexedDbRef", 1).get())); } - /** - * @see DATAMONGO-1163 - */ - @Test + @Test // DATAMONGO-1163 public void resolveIndexDefinitionInMetaAnnotatedFields() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -205,10 +175,7 @@ public void resolveIndexDefinitionInMetaAnnotatedFields() { equalTo(new BasicDBObjectBuilder().add("name", "_name").get())); } - /** - * @see DATAMONGO-1373 - */ - @Test + @Test // DATAMONGO-1373 public void resolveIndexDefinitionInComposedAnnotatedFields() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -223,10 +190,7 @@ public void resolveIndexDefinitionInComposedAnnotatedFields() { isBsonObject().containing("sparse", true).containing("unique", true).containing("name", "my_index_name")); } - /** - * @see DATAMONGO-1373 - */ - @Test + @Test // DATAMONGO-1373 public void resolveIndexDefinitionInCustomComposedAnnotatedFields() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -351,10 +315,7 @@ static class IndexOnMetaAnnotatedField { */ public static class GeoSpatialIndexResolutionTests { - /** - * @see DATAMONGO-899 - */ - @Test + @Test // DATAMONGO-899 public void geoSpatialIndexPathOnLevelZeroIsResolvedCorrectly() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -364,10 +325,7 @@ public void geoSpatialIndexPathOnLevelZeroIsResolvedCorrectly() { assertIndexPathAndCollection("geoIndexedProperty", "Zero", indexDefinitions.get(0)); } - /** - * @see DATAMONGO-899 - */ - @Test + @Test // DATAMONGO-899 public void geoSpatialIndexPathOnLevelOneIsResolvedCorrectly() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -377,10 +335,7 @@ public void geoSpatialIndexPathOnLevelOneIsResolvedCorrectly() { assertIndexPathAndCollection("zero.geoIndexedProperty", "One", indexDefinitions.get(0)); } - /** - * @see DATAMONGO-899 - */ - @Test + @Test // DATAMONGO-899 public void depplyNestedGeoSpatialIndexPathIsResolvedCorrectly() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -390,10 +345,7 @@ public void depplyNestedGeoSpatialIndexPathIsResolvedCorrectly() { assertIndexPathAndCollection("one.zero.geoIndexedProperty", "Two", indexDefinitions.get(0)); } - /** - * @see DATAMONGO-899 - */ - @Test + @Test // DATAMONGO-899 public void resolvesIndexDefinitionOptionsCorrectly() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -405,10 +357,7 @@ public void resolvesIndexDefinitionOptionsCorrectly() { new BasicDBObjectBuilder().add("name", "location").add("min", 1).add("max", 100).add("bits", 2).get())); } - /** - * @see DATAMONGO-1373 - */ - @Test + @Test // DATAMONGO-1373 public void resolvesComposedAnnotationIndexDefinitionOptionsCorrectly() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -479,10 +428,7 @@ static class GeoSpatialIndexedDocumentWithComposedAnnotation { */ public static class CompoundIndexResolutionTests { - /** - * @see DATAMONGO-899 - */ - @Test + @Test // DATAMONGO-899 public void compoundIndexPathOnLevelZeroIsResolvedCorrectly() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -492,10 +438,7 @@ public void compoundIndexPathOnLevelZeroIsResolvedCorrectly() { assertIndexPathAndCollection(new String[] { "foo", "bar" }, "CompoundIndexOnLevelZero", indexDefinitions.get(0)); } - /** - * @see DATAMONGO-899 - */ - @Test + @Test // DATAMONGO-899 public void compoundIndexOptionsResolvedCorrectly() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -508,10 +451,7 @@ public void compoundIndexOptionsResolvedCorrectly() { equalTo(new BasicDBObjectBuilder().add("foo", 1).add("bar", -1).get())); } - /** - * @see DATAMONGO-909 - */ - @Test + @Test // DATAMONGO-909 public void compoundIndexOnSuperClassResolvedCorrectly() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -524,10 +464,7 @@ public void compoundIndexOnSuperClassResolvedCorrectly() { equalTo(new BasicDBObjectBuilder().add("foo", 1).add("bar", -1).get())); } - /** - * @see DATAMONGO-827 - */ - @Test + @Test // DATAMONGO-827 public void compoundIndexDoesNotSpecifyNameWhenUsingGenerateName() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -540,10 +477,7 @@ public void compoundIndexDoesNotSpecifyNameWhenUsingGenerateName() { equalTo(new BasicDBObjectBuilder().add("foo", 1).add("bar", -1).get())); } - /** - * @see DATAMONGO-929 - */ - @Test + @Test // DATAMONGO-929 public void compoundIndexPathOnLevelOneIsResolvedCorrectly() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -554,10 +488,7 @@ public void compoundIndexPathOnLevelOneIsResolvedCorrectly() { indexDefinitions.get(0)); } - /** - * @see DATAMONGO-929 - */ - @Test + @Test // DATAMONGO-929 public void emptyCompoundIndexPathOnLevelOneIsResolvedCorrectly() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -568,10 +499,7 @@ public void emptyCompoundIndexPathOnLevelOneIsResolvedCorrectly() { indexDefinitions.get(0)); } - /** - * @see DATAMONGO-929 - */ - @Test + @Test // DATAMONGO-929 public void singleCompoundIndexPathOnLevelZeroIsResolvedCorrectly() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -581,10 +509,7 @@ public void singleCompoundIndexPathOnLevelZeroIsResolvedCorrectly() { assertIndexPathAndCollection(new String[] { "foo", "bar" }, "CompoundIndexOnLevelZero", indexDefinitions.get(0)); } - /** - * @see DATAMONGO-1373 - */ - @Test + @Test // DATAMONGO-1373 public void singleCompoundIndexUsingComposedAnnotationsOnTypeResolvedCorrectly() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -665,10 +590,7 @@ static class CompoundIndexDocumentWithComposedAnnotation { public static class TextIndexedResolutionTests { - /** - * @see DATAMONGO-937 - */ - @Test + @Test // DATAMONGO-937 public void shouldResolveSingleFieldTextIndexCorrectly() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -677,10 +599,7 @@ public void shouldResolveSingleFieldTextIndexCorrectly() { assertIndexPathAndCollection("bar", "textIndexOnSinglePropertyInRoot", indexDefinitions.get(0)); } - /** - * @see DATAMONGO-937 - */ - @Test + @Test // DATAMONGO-937 public void shouldResolveMultiFieldTextIndexCorrectly() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -690,10 +609,7 @@ public void shouldResolveMultiFieldTextIndexCorrectly() { indexDefinitions.get(0)); } - /** - * @see DATAMONGO-937 - */ - @Test + @Test // DATAMONGO-937 public void shouldResolveTextIndexOnElementCorrectly() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -702,10 +618,7 @@ public void shouldResolveTextIndexOnElementCorrectly() { assertIndexPathAndCollection(new String[] { "nested.foo" }, "textIndexOnNestedRoot", indexDefinitions.get(0)); } - /** - * @see DATAMONGO-937 - */ - @Test + @Test // DATAMONGO-937 public void shouldResolveTextIndexOnElementWithWeightCorrectly() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -718,10 +631,7 @@ public void shouldResolveTextIndexOnElementWithWeightCorrectly() { assertThat(weights.get("nested.foo"), is((Object) 5F)); } - /** - * @see DATAMONGO-937 - */ - @Test + @Test // DATAMONGO-937 public void shouldResolveTextIndexOnElementWithMostSpecificWeightCorrectly() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -735,10 +645,7 @@ public void shouldResolveTextIndexOnElementWithMostSpecificWeightCorrectly() { assertThat(weights.get("nested.bar"), is((Object) 10F)); } - /** - * @see DATAMONGO-937 - */ - @Test + @Test // DATAMONGO-937 public void shouldSetDefaultLanguageCorrectly() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -746,10 +653,7 @@ public void shouldSetDefaultLanguageCorrectly() { assertThat(indexDefinitions.get(0).getIndexOptions().get("default_language"), is((Object) "spanish")); } - /** - * @see DATAMONGO-937, DATAMONGO-1049 - */ - @Test + @Test // DATAMONGO-937, DATAMONGO-1049 public void shouldResolveTextIndexLanguageOverrideCorrectly() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -757,10 +661,7 @@ public void shouldResolveTextIndexLanguageOverrideCorrectly() { assertThat(indexDefinitions.get(0).getIndexOptions().get("language_override"), is((Object) "lang")); } - /** - * @see DATAMONGO-1049 - */ - @Test + @Test // DATAMONGO-1049 public void shouldIgnoreTextIndexLanguageOverrideOnNestedElements() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -768,10 +669,7 @@ public void shouldIgnoreTextIndexLanguageOverrideOnNestedElements() { assertThat(indexDefinitions.get(0).getIndexOptions().get("language_override"), is(nullValue())); } - /** - * @see DATAMONGO-1049 - */ - @Test + @Test // DATAMONGO-1049 public void shouldNotCreateIndexDefinitionWhenOnlyLanguageButNoTextIndexPresent() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -779,10 +677,7 @@ public void shouldNotCreateIndexDefinitionWhenOnlyLanguageButNoTextIndexPresent( assertThat(indexDefinitions, is(empty())); } - /** - * @see DATAMONGO-1049 - */ - @Test + @Test // DATAMONGO-1049 public void shouldNotCreateIndexDefinitionWhenOnlyAnnotatedLanguageButNoTextIndexPresent() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -790,10 +685,7 @@ public void shouldNotCreateIndexDefinitionWhenOnlyAnnotatedLanguageButNoTextInde assertThat(indexDefinitions, is(empty())); } - /** - * @see DATAMONGO-1049 - */ - @Test + @Test // DATAMONGO-1049 public void shouldPreferExplicitlyAnnotatedLanguageProperty() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -801,10 +693,7 @@ public void shouldPreferExplicitlyAnnotatedLanguageProperty() { assertThat(indexDefinitions.get(0).getIndexOptions().get("language_override"), is((Object) "lang")); } - /** - * @see DATAMONGO-1373 - */ - @Test + @Test // DATAMONGO-1373 public void shouldResolveComposedAnnotationCorrectly() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -918,10 +807,7 @@ static class TextIndexedDocumentWithComposedAnnotation { public static class MixedIndexResolutionTests { - /** - * @see DATAMONGO-899 - */ - @Test + @Test // DATAMONGO-899 public void multipleIndexesResolvedCorrectly() { List indexDefinitions = prepareMappingContextAndResolveIndexForType(MixedIndexRoot.class); @@ -931,10 +817,7 @@ public void multipleIndexesResolvedCorrectly() { assertThat(indexDefinitions.get(1).getIndexDefinition(), instanceOf(GeospatialIndex.class)); } - /** - * @see DATAMONGO-899 - */ - @Test + @Test // DATAMONGO-899 public void cyclicPropertyReferenceOverDBRefShouldNotBeTraversed() { List indexDefinitions = prepareMappingContextAndResolveIndexForType(Inner.class); @@ -943,20 +826,14 @@ public void cyclicPropertyReferenceOverDBRefShouldNotBeTraversed() { equalTo(new BasicDBObjectBuilder().add("outer", 1).get())); } - /** - * @see DATAMONGO-899 - */ - @Test + @Test // DATAMONGO-899 public void associationsShouldNotBeTraversed() { List indexDefinitions = prepareMappingContextAndResolveIndexForType(Outer.class); assertThat(indexDefinitions, empty()); } - /** - * @see DATAMONGO-926 - */ - @Test + @Test // DATAMONGO-926 public void shouldNotRunIntoStackOverflow() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -964,10 +841,7 @@ public void shouldNotRunIntoStackOverflow() { assertThat(indexDefinitions, hasSize(1)); } - /** - * @see DATAMONGO-926 - */ - @Test + @Test // DATAMONGO-926 public void indexShouldBeFoundEvenForCyclePropertyReferenceOnLevelZero() { List indexDefinitions = prepareMappingContextAndResolveIndexForType(CycleLevelZero.class); @@ -976,10 +850,7 @@ public void indexShouldBeFoundEvenForCyclePropertyReferenceOnLevelZero() { assertThat(indexDefinitions, hasSize(2)); } - /** - * @see DATAMONGO-926 - */ - @Test + @Test // DATAMONGO-926 public void indexShouldBeFoundEvenForCyclePropertyReferenceOnLevelOne() { List indexDefinitions = prepareMappingContextAndResolveIndexForType(CycleOnLevelOne.class); @@ -987,10 +858,7 @@ public void indexShouldBeFoundEvenForCyclePropertyReferenceOnLevelOne() { assertThat(indexDefinitions, hasSize(1)); } - /** - * @see DATAMONGO-926 - */ - @Test + @Test // DATAMONGO-926 public void indexBeResolvedCorrectlyWhenPropertiesOfDifferentTypesAreNamedEqually() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -1002,10 +870,7 @@ public void indexBeResolvedCorrectlyWhenPropertiesOfDifferentTypesAreNamedEquall assertThat(indexDefinitions, hasSize(3)); } - /** - * @see DATAMONGO-949 - */ - @Test + @Test // DATAMONGO-949 public void shouldNotDetectCycleInSimilarlyNamedProperties() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -1014,10 +879,7 @@ public void shouldNotDetectCycleInSimilarlyNamedProperties() { assertThat(indexDefinitions, hasSize(1)); } - /** - * @see DATAMONGO-962 - */ - @Test + @Test // DATAMONGO-962 public void shouldDetectSelfCycleViaCollectionTypeCorrectly() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -1025,10 +887,7 @@ public void shouldDetectSelfCycleViaCollectionTypeCorrectly() { assertThat(indexDefinitions, empty()); } - /** - * @see DATAMONGO-962 - */ - @Test + @Test // DATAMONGO-962 public void shouldNotDetectCycleWhenTypeIsUsedMoreThanOnce() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -1036,10 +895,7 @@ public void shouldNotDetectCycleWhenTypeIsUsedMoreThanOnce() { assertThat(indexDefinitions, empty()); } - /** - * @see DATAMONGO-962 - */ - @Test + @Test // DATAMONGO-962 @SuppressWarnings({ "rawtypes", "unchecked" }) public void shouldCatchCyclicReferenceExceptionOnRoot() { @@ -1058,10 +914,7 @@ public void shouldCatchCyclicReferenceExceptionOnRoot() { .resolveIndexForEntity(selfCyclingEntity); } - /** - * @see DATAMONGO-1025 - */ - @Test + @Test // DATAMONGO-1025 public void shouldUsePathIndexAsIndexNameForDocumentsHavingNamedNestedCompoundIndexFixedOnCollection() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -1070,10 +923,7 @@ public void shouldUsePathIndexAsIndexNameForDocumentsHavingNamedNestedCompoundIn equalTo("propertyOfTypeHavingNamedCompoundIndex.c_index")); } - /** - * @see DATAMONGO-1025 - */ - @Test + @Test // DATAMONGO-1025 public void shouldUseIndexNameForNestedTypesWithNamedCompoundIndexDefinition() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -1082,10 +932,7 @@ public void shouldUseIndexNameForNestedTypesWithNamedCompoundIndexDefinition() { equalTo("propertyOfTypeHavingNamedCompoundIndex.c_index")); } - /** - * @see DATAMONGO-1025 - */ - @Test + @Test // DATAMONGO-1025 public void shouldUsePathIndexAsIndexNameForDocumentsHavingNamedNestedIndexFixedOnCollection() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -1094,10 +941,7 @@ public void shouldUsePathIndexAsIndexNameForDocumentsHavingNamedNestedIndexFixed equalTo("propertyOfTypeHavingNamedIndex.property_index")); } - /** - * @see DATAMONGO-1025 - */ - @Test + @Test // DATAMONGO-1025 public void shouldUseIndexNameForNestedTypesWithNamedIndexDefinition() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -1106,10 +950,7 @@ public void shouldUseIndexNameForNestedTypesWithNamedIndexDefinition() { equalTo("propertyOfTypeHavingNamedIndex.property_index")); } - /** - * @see DATAMONGO-1025 - */ - @Test + @Test // DATAMONGO-1025 public void shouldUseIndexNameOnRootLevel() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -1117,10 +958,7 @@ public void shouldUseIndexNameOnRootLevel() { assertThat((String) indexDefinitions.get(0).getIndexOptions().get("name"), equalTo("property_index")); } - /** - * @see DATAMONGO-1087 - */ - @Test + @Test // DATAMONGO-1087 public void shouldAllowMultiplePropertiesOfSameTypeWithMatchingStartLettersOnRoot() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -1131,10 +969,7 @@ public void shouldAllowMultiplePropertiesOfSameTypeWithMatchingStartLettersOnRoo assertThat((String) indexDefinitions.get(1).getIndexOptions().get("name"), equalTo("nameLast.component")); } - /** - * @see DATAMONGO-1087 - */ - @Test + @Test // DATAMONGO-1087 public void shouldAllowMultiplePropertiesOfSameTypeWithMatchingStartLettersOnNestedProperty() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -1145,10 +980,7 @@ public void shouldAllowMultiplePropertiesOfSameTypeWithMatchingStartLettersOnNes assertThat((String) indexDefinitions.get(1).getIndexOptions().get("name"), equalTo("component.name")); } - /** - * @see DATAMONGO-1121 - */ - @Test + @Test // DATAMONGO-1121 public void shouldOnlyConsiderEntitiesAsPotentialCycleCandidates() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -1161,10 +993,7 @@ public void shouldOnlyConsiderEntitiesAsPotentialCycleCandidates() { } - /** - * @see DATAMONGO-1263 - */ - @Test + @Test // DATAMONGO-1263 public void shouldConsiderGenericTypeArgumentsOfCollectionElements() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/PathUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/PathUnitTests.java index 9e88c00c1b..52308193a4 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/PathUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/PathUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 the original author or authors. + * Copyright 2014-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -45,20 +45,14 @@ public void setUp() { when(entityMock.getType()).thenReturn((Class) Object.class); } - /** - * @see DATAMONGO-962 - */ - @Test + @Test // DATAMONGO-962 public void shouldIdentifyCycleForOwnerOfSameTypeAndMatchingPath() { MongoPersistentProperty property = createPersistentPropertyMock(entityMock, "foo"); assertThat(new Path(property, "foo.bar").cycles(property, "foo.bar.bar"), is(true)); } - /** - * @see DATAMONGO-962 - */ - @Test + @Test // DATAMONGO-962 @SuppressWarnings("rawtypes") public void shouldAllowMatchingPathForDifferentOwners() { @@ -71,10 +65,7 @@ public void shouldAllowMatchingPathForDifferentOwners() { assertThat(new Path(existing, "foo.bar").cycles(toBeVerified, "foo.bar.bar"), is(false)); } - /** - * @see DATAMONGO-962 - */ - @Test + @Test // DATAMONGO-962 public void shouldAllowEqaulPropertiesOnDifferentPaths() { MongoPersistentProperty property = createPersistentPropertyMock(entityMock, "foo"); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/TextIndexTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/TextIndexTests.java index 8047f0fe00..4123a2bdd7 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/TextIndexTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/TextIndexTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 the original author or authors. + * Copyright 2014-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -52,10 +52,7 @@ public void setUp() throws Exception { this.indexOps = template.indexOps(TextIndexedDocumentRoot.class); } - /** - * @see DATAMONGO-937 - */ - @Test + @Test // DATAMONGO-937 public void indexInfoShouldHaveBeenCreatedCorrectly() { List indexInfos = indexOps.getIndexInfo(); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/BasePerson.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/BasePerson.java index 8d2aec0655..d30605d605 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/BasePerson.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/BasePerson.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2015 by the original author(s). + * Copyright 2011-2017 by the original author(s). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,9 +20,9 @@ /** * {@link QuerySupertype} is necessary for Querydsl 2.2.0-beta4 to compile the query classes directly. Can be removed as - * soon as {@link https://bugs.launchpad.net/querydsl/+bug/776219} is fixed. + * soon as https://bugs.launchpad.net/querydsl/+bug/776219 is fixed. * - * @see https://bugs.launchpad.net/querydsl/+bug/776219 + * @see https://bugs.launchpad.net/querydsl/+bug/776219 * @author Jon Brisbin * @author Oliver Gierke */ diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentEntityUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentEntityUnitTests.java index e5f8c87252..cdbe8aed69 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentEntityUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentEntityUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2016 by the original author(s). + * Copyright 2011-2017 by the original author(s). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -61,10 +61,7 @@ public void evaluatesSpELExpression() { assertThat(entity.getCollection(), is("35")); } - /** - * @see DATAMONGO-65, DATAMONGO-1108 - */ - @Test + @Test // DATAMONGO-65, DATAMONGO-1108 public void collectionAllowsReferencingSpringBean() { CollectionProvider provider = new CollectionProvider(); @@ -83,10 +80,7 @@ public void collectionAllowsReferencingSpringBean() { assertThat(entity.getCollection(), is("otherReference")); } - /** - * @see DATAMONGO-937 - */ - @Test + @Test // DATAMONGO-937 public void shouldDetectLanguageCorrectly() { BasicMongoPersistentEntity entity = new BasicMongoPersistentEntity( @@ -94,11 +88,8 @@ public void shouldDetectLanguageCorrectly() { assertThat(entity.getLanguage(), is("spanish")); } - /** - * @see DATAMONGO-1053 - */ @SuppressWarnings({ "unchecked", "rawtypes" }) - @Test(expected = MappingException.class) + @Test(expected = MappingException.class) // DATAMONGO-1053 public void verifyShouldThrowExceptionForInvalidTypeOfExplicitLanguageProperty() { BasicMongoPersistentEntity entity = new BasicMongoPersistentEntity( @@ -111,11 +102,8 @@ public void verifyShouldThrowExceptionForInvalidTypeOfExplicitLanguageProperty() entity.verify(); } - /** - * @see DATAMONGO-1053 - */ @SuppressWarnings({ "unchecked", "rawtypes" }) - @Test + @Test // DATAMONGO-1053 public void verifyShouldPassForStringAsExplicitLanguageProperty() { BasicMongoPersistentEntity entity = new BasicMongoPersistentEntity( @@ -130,11 +118,8 @@ public void verifyShouldPassForStringAsExplicitLanguageProperty() { verify(propertyMock, times(1)).getActualType(); } - /** - * @see DATAMONGO-1053 - */ @SuppressWarnings({ "unchecked", "rawtypes" }) - @Test + @Test // DATAMONGO-1053 public void verifyShouldIgnoreNonExplicitLanguageProperty() { BasicMongoPersistentEntity entity = new BasicMongoPersistentEntity( @@ -149,11 +134,8 @@ public void verifyShouldIgnoreNonExplicitLanguageProperty() { verify(propertyMock, never()).getActualType(); } - /** - * @see DATAMONGO-1157 - */ @SuppressWarnings({ "unchecked", "rawtypes" }) - @Test(expected = MappingException.class) + @Test(expected = MappingException.class) // DATAMONGO-1157 public void verifyShouldThrowErrorForLazyDBRefOnFinalClass() { BasicMongoPersistentEntity entity = new BasicMongoPersistentEntity( @@ -169,10 +151,7 @@ public void verifyShouldThrowErrorForLazyDBRefOnFinalClass() { entity.verify(); } - /** - * @see DATAMONGO-1157 - */ - @Test(expected = MappingException.class) + @Test(expected = MappingException.class) // DATAMONGO-1157 public void verifyShouldThrowErrorForLazyDBRefArray() { BasicMongoPersistentEntity entity = new BasicMongoPersistentEntity( @@ -188,10 +167,7 @@ public void verifyShouldThrowErrorForLazyDBRefArray() { entity.verify(); } - /** - * @see DATAMONGO-1157 - */ - @Test + @Test // DATAMONGO-1157 @SuppressWarnings({ "unchecked", "rawtypes" }) public void verifyShouldPassForLazyDBRefOnNonArrayNonFinalClass() { @@ -210,10 +186,7 @@ public void verifyShouldPassForLazyDBRefOnNonArrayNonFinalClass() { verify(propertyMock, times(1)).isDbReference(); } - /** - * @see DATAMONGO-1157 - */ - @Test + @Test // DATAMONGO-1157 @SuppressWarnings({ "unchecked", "rawtypes" }) public void verifyShouldPassForNonLazyDBRefOnFinalClass() { @@ -232,10 +205,7 @@ public void verifyShouldPassForNonLazyDBRefOnFinalClass() { verify(dbRefMock, times(1)).lazy(); } - /** - * @see DATAMONGO-1291 - */ - @Test + @Test // DATAMONGO-1291 public void metaInformationShouldBeReadCorrectlyFromInheritedDocumentAnnotation() { BasicMongoPersistentEntity entity = new BasicMongoPersistentEntity( @@ -244,10 +214,7 @@ public void metaInformationShouldBeReadCorrectlyFromInheritedDocumentAnnotation( assertThat(entity.getCollection(), is("collection-1")); } - /** - * @see DATAMONGO-1373 - */ - @Test + @Test // DATAMONGO-1373 public void metaInformationShouldBeReadCorrectlyFromComposedDocumentAnnotation() { BasicMongoPersistentEntity entity = new BasicMongoPersistentEntity( diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentPropertyUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentPropertyUnitTests.java index 5f325e7aab..953077b882 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentPropertyUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentPropertyUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2016 by the original author(s). + * Copyright 2011-2017 by the original author(s). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -84,20 +84,14 @@ public void preventsNegativeOrder() { getPropertyFor(ReflectionUtils.findField(Person.class, "ssn")); } - /** - * @see DATAMONGO-553 - */ - @Test + @Test // DATAMONGO-553 public void usesPropertyAccessForThrowableCause() { MongoPersistentProperty property = getPropertyFor(ReflectionUtils.findField(Throwable.class, "cause")); assertThat(property.usePropertyAccess(), is(true)); } - /** - * @see DATAMONGO-607 - */ - @Test + @Test // DATAMONGO-607 public void usesCustomFieldNamingStrategyByDefault() throws Exception { Field field = ReflectionUtils.findField(Person.class, "lastname"); @@ -113,10 +107,7 @@ public void usesCustomFieldNamingStrategyByDefault() throws Exception { assertThat(property.getFieldName(), is("foo")); } - /** - * @see DATAMONGO-607 - */ - @Test + @Test // DATAMONGO-607 public void rejectsInvalidValueReturnedByFieldNamingStrategy() { Field field = ReflectionUtils.findField(Person.class, "lastname"); @@ -130,60 +121,42 @@ public void rejectsInvalidValueReturnedByFieldNamingStrategy() { property.getFieldName(); } - /** - * @see DATAMONGO-937 - */ - @Test + @Test // DATAMONGO-937 public void shouldDetectAnnotatedLanguagePropertyCorrectly() { MongoPersistentProperty property = getPropertyFor(DocumentWithLanguageProperty.class, "lang"); assertThat(property.isLanguageProperty(), is(true)); } - /** - * @see DATAMONGO-937 - */ - @Test + @Test // DATAMONGO-937 public void shouldDetectIplicitLanguagePropertyCorrectly() { MongoPersistentProperty property = getPropertyFor(DocumentWithImplicitLanguageProperty.class, "language"); assertThat(property.isLanguageProperty(), is(true)); } - /** - * @see DATAMONGO-976 - */ - @Test + @Test // DATAMONGO-976 public void shouldDetectTextScorePropertyCorrectly() { MongoPersistentProperty property = getPropertyFor(DocumentWithTextScoreProperty.class, "score"); assertThat(property.isTextScoreProperty(), is(true)); } - /** - * @see DATAMONGO-976 - */ - @Test + @Test // DATAMONGO-976 public void shouldDetectTextScoreAsReadOnlyProperty() { MongoPersistentProperty property = getPropertyFor(DocumentWithTextScoreProperty.class, "score"); assertThat(property.isWritable(), is(false)); } - /** - * @see DATAMONGO-1050 - */ - @Test + @Test // DATAMONGO-1050 public void shouldNotConsiderExplicitlyNameFieldAsIdProperty() { MongoPersistentProperty property = getPropertyFor(DocumentWithExplicitlyRenamedIdProperty.class, "id"); assertThat(property.isIdProperty(), is(false)); } - /** - * @see DATAMONGO-1050 - */ - @Test + @Test // DATAMONGO-1050 public void shouldConsiderPropertyAsIdWhenExplicitlyAnnotatedWithIdEvenWhenExplicitlyNamePresent() { MongoPersistentProperty property = getPropertyFor(DocumentWithExplicitlyRenamedIdPropertyHavingIdAnnotation.class, @@ -191,10 +164,7 @@ public void shouldConsiderPropertyAsIdWhenExplicitlyAnnotatedWithIdEvenWhenExpli assertThat(property.isIdProperty(), is(true)); } - /** - * @see DATAMONGO-1373 - */ - @Test + @Test // DATAMONGO-1373 public void shouldConsiderComposedAnnotationsForIdField() { MongoPersistentProperty property = getPropertyFor(DocumentWithComposedAnnotations.class, "myId"); @@ -202,10 +172,7 @@ public void shouldConsiderComposedAnnotationsForIdField() { assertThat(property.getFieldName(), is("_id")); } - /** - * @see DATAMONGO-1373 - */ - @Test + @Test // DATAMONGO-1373 public void shouldConsiderComposedAnnotationsForFields() { MongoPersistentProperty property = getPropertyFor(DocumentWithComposedAnnotations.class, "myField"); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/MappingTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/MappingTests.java index b101ae6ba1..b0969ea899 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/MappingTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/MappingTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2013 the original author or authors. + * Copyright 2011-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -441,10 +441,7 @@ public void testPersonWithLongDBRef() { assertEquals(12L, p2.getPersonPojoLongId().getId()); } - /** - * @see DATADOC-275 - */ - @Test + @Test // DATADOC-275 public void readsAndWritesDBRefsCorrectly() { template.dropCollection(Item.class); @@ -467,10 +464,7 @@ public void readsAndWritesDBRefsCorrectly() { assertThat(result.items.get(0).id, is(items.id)); } - /** - * @see DATAMONGO-805 - */ - @Test + @Test // DATAMONGO-805 public void supportExcludeDbRefAssociation() { template.dropCollection(Item.class); @@ -492,10 +486,7 @@ public void supportExcludeDbRefAssociation() { assertThat(result.item, is(nullValue())); } - /** - * @see DATAMONGO-805 - */ - @Test + @Test // DATAMONGO-805 public void shouldMapFieldsOfIterableEntity() { template.dropCollection(IterableItem.class); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/MongoMappingContextUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/MongoMappingContextUnitTests.java index 99e74c523e..158e13957c 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/MongoMappingContextUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/MongoMappingContextUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2014 by the original author(s). + * Copyright 2011-2017 by the original author(s). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -67,20 +67,14 @@ public void doesNotReturnPersistentEntityForMongoSimpleType() { assertThat(context.getPersistentEntity(DBRef.class), is(nullValue())); } - /** - * @see DATAMONGO-638 - */ - @Test + @Test // DATAMONGO-638 public void doesNotCreatePersistentEntityForAbstractMap() { MongoMappingContext context = new MongoMappingContext(); assertThat(context.getPersistentEntity(AbstractMap.class), is(nullValue())); } - /** - * @see DATAMONGO-607 - */ - @Test + @Test // DATAMONGO-607 public void populatesPersistentPropertyWithCustomFieldNamingStrategy() { MongoMappingContext context = new MongoMappingContext(); @@ -96,10 +90,7 @@ public String getFieldName(PersistentProperty property) { assertThat(entity.getPersistentProperty("firstname").getFieldName(), is("FIRSTNAME")); } - /** - * @see DATAMONGO-607 - */ - @Test + @Test // DATAMONGO-607 public void rejectsClassWithAmbiguousFieldMappings() { exception.expect(MappingException.class); @@ -113,10 +104,7 @@ public void rejectsClassWithAmbiguousFieldMappings() { context.getPersistentEntity(InvalidPerson.class); } - /** - * @see DATAMONGO-694 - */ - @Test + @Test // DATAMONGO-694 public void doesNotConsiderOverrridenAccessorANewField() { MongoMappingContext context = new MongoMappingContext(); @@ -124,10 +112,7 @@ public void doesNotConsiderOverrridenAccessorANewField() { context.getPersistentEntity(Child.class); } - /** - * @see DATAMONGO-688 - */ - @Test + @Test // DATAMONGO-688 public void mappingContextShouldAcceptClassWithImplicitIdProperty() { MongoMappingContext context = new MongoMappingContext(); @@ -137,10 +122,7 @@ public void mappingContextShouldAcceptClassWithImplicitIdProperty() { assertThat(pe.isIdProperty(pe.getPersistentProperty("id")), is(true)); } - /** - * @see DATAMONGO-688 - */ - @Test + @Test // DATAMONGO-688 public void mappingContextShouldAcceptClassWithExplicitIdProperty() { MongoMappingContext context = new MongoMappingContext(); @@ -150,10 +132,7 @@ public void mappingContextShouldAcceptClassWithExplicitIdProperty() { assertThat(pe.isIdProperty(pe.getPersistentProperty("myId")), is(true)); } - /** - * @see DATAMONGO-688 - */ - @Test + @Test // DATAMONGO-688 public void mappingContextShouldAcceptClassWithExplicitAndImplicitIdPropertyByGivingPrecedenceToExplicitIdProperty() { MongoMappingContext context = new MongoMappingContext(); @@ -161,30 +140,21 @@ public void mappingContextShouldAcceptClassWithExplicitAndImplicitIdPropertyByGi assertThat(pe, is(not(nullValue()))); } - /** - * @see DATAMONGO-688 - */ - @Test(expected = MappingException.class) + @Test(expected = MappingException.class) // DATAMONGO-688 public void rejectsClassWithAmbiguousExplicitIdPropertyFieldMappings() { MongoMappingContext context = new MongoMappingContext(); context.getPersistentEntity(ClassWithMultipleExplicitIds.class); } - /** - * @see DATAMONGO-688 - */ - @Test(expected = MappingException.class) + @Test(expected = MappingException.class) // DATAMONGO-688 public void rejectsClassWithAmbiguousImplicitIdPropertyFieldMappings() { MongoMappingContext context = new MongoMappingContext(); context.getPersistentEntity(ClassWithMultipleImplicitIds.class); } - /** - * @see DATAMONGO-976 - */ - @Test + @Test // DATAMONGO-976 public void shouldRejectClassWithInvalidTextScoreProperty() { exception.expect(MappingException.class); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/event/AbstractMongoEventListenerUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/event/AbstractMongoEventListenerUnitTests.java index 27847da9c0..e5a0045450 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/event/AbstractMongoEventListenerUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/event/AbstractMongoEventListenerUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2016 by the original author(s). + * Copyright 2011-2017 by the original author(s). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -64,10 +64,7 @@ public void dropsEventIfNotForCorrectDomainType() { context.close(); } - /** - * @see DATAMONGO-289 - */ - @Test + @Test // DATAMONGO-289 public void afterLoadEffectGetsHandledCorrectly() { SamplePersonEventListener listener = new SamplePersonEventListener(); @@ -75,10 +72,7 @@ public void afterLoadEffectGetsHandledCorrectly() { assertThat(listener.invokedOnAfterLoad, is(true)); } - /** - * @see DATAMONGO-289 - */ - @Test + @Test // DATAMONGO-289 public void afterLoadEventGetsFilteredForDomainType() { SamplePersonEventListener personListener = new SamplePersonEventListener(); @@ -90,10 +84,7 @@ public void afterLoadEventGetsFilteredForDomainType() { assertThat(accountListener.invokedOnAfterLoad, is(false)); } - /** - * @see DATAMONGO-289 - */ - @Test + @Test // DATAMONGO-289 public void afterLoadEventGetsFilteredForDomainTypeWorksForSubtypes() { SamplePersonEventListener personListener = new SamplePersonEventListener(); @@ -105,10 +96,7 @@ public void afterLoadEventGetsFilteredForDomainTypeWorksForSubtypes() { assertThat(contactListener.invokedOnAfterLoad, is(true)); } - /** - * @see DATAMONGO-289 - */ - @Test + @Test // DATAMONGO-289 public void afterLoadEventGetsFilteredForDomainTypeWorksForSubtypes2() { SamplePersonEventListener personListener = new SamplePersonEventListener(); @@ -120,10 +108,7 @@ public void afterLoadEventGetsFilteredForDomainTypeWorksForSubtypes2() { assertThat(contactListener.invokedOnAfterLoad, is(true)); } - /** - * @see DATAMONGO-333 - */ - @Test + @Test // DATAMONGO-333 @SuppressWarnings({ "rawtypes", "unchecked" }) public void handlesUntypedImplementations() { @@ -131,10 +116,7 @@ public void handlesUntypedImplementations() { listener.onApplicationEvent(new MongoMappingEvent(new Object(), new BasicDBObject(), "collection")); } - /** - * @see DATAMONGO-545 - */ - @Test + @Test // DATAMONGO-545 public void invokeContactCallbackForPersonEvent() { MongoMappingEvent event = new BeforeDeleteEvent(new BasicDBObject(), Person.class, @@ -145,10 +127,7 @@ public void invokeContactCallbackForPersonEvent() { assertThat(listener.invokedOnBeforeDelete, is(true)); } - /** - * @see DATAMONGO-545 - */ - @Test + @Test // DATAMONGO-545 public void invokePersonCallbackForPersonEvent() { MongoMappingEvent event = new BeforeDeleteEvent(new BasicDBObject(), Person.class, @@ -159,10 +138,7 @@ public void invokePersonCallbackForPersonEvent() { assertThat(listener.invokedOnBeforeDelete, is(true)); } - /** - * @see DATAMONGO-545 - */ - @Test + @Test // DATAMONGO-545 public void dontInvokePersonCallbackForAccountEvent() { MongoMappingEvent event = new BeforeDeleteEvent(new BasicDBObject(), Account.class, @@ -173,10 +149,7 @@ public void dontInvokePersonCallbackForAccountEvent() { assertThat(listener.invokedOnBeforeDelete, is(false)); } - /** - * @see DATAMONGO-545 - */ - @Test + @Test // DATAMONGO-545 public void donInvokePersonCallbackForUntypedEvent() { MongoMappingEvent event = new BeforeDeleteEvent(new BasicDBObject(), null, "collection-1"); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/event/ApplicationContextEventTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/event/ApplicationContextEventTests.java index c600911eef..a997613498 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/event/ApplicationContextEventTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/event/ApplicationContextEventTests.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011-2016 by the original author(s). + * Copyright (c) 2011-2017 by the original author(s). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -135,10 +135,7 @@ public void beforeSaveEvent() { comparePersonAndDbo(p, p2, dbo); } - /** - * @see DATAMONGO-1256 - */ - @Test + @Test // DATAMONGO-1256 public void loadAndConvertEvents() { PersonPojoStringId entity = new PersonPojoStringId("1", "Text"); @@ -156,10 +153,7 @@ public void loadAndConvertEvents() { assertThat(simpleMappingEventListener.onAfterConvertEvents.get(0).getCollectionName(), is(COLLECTION_NAME)); } - /** - * @see DATAMONGO-1256 - */ - @Test + @Test // DATAMONGO-1256 public void loadEventsOnAggregation() { template.insert(new PersonPojoStringId("1", "Text")); @@ -177,10 +171,7 @@ public void loadEventsOnAggregation() { assertThat(simpleMappingEventListener.onAfterConvertEvents.get(0).getCollectionName(), is(COLLECTION_NAME)); } - /** - * @see DATAMONGO-1256 - */ - @Test + @Test // DATAMONGO-1256 public void deleteEvents() { PersonPojoStringId entity = new PersonPojoStringId("1", "Text"); @@ -195,10 +186,7 @@ public void deleteEvents() { assertThat(simpleMappingEventListener.onAfterDeleteEvents.get(0).getCollectionName(), is(COLLECTION_NAME)); } - /** - * @see DATAMONGO-1271 - */ - @Test + @Test // DATAMONGO-1271 public void publishesAfterLoadAndAfterConvertEventsForDBRef() throws Exception { Related ref1 = new Related(2L, "related desc1"); @@ -226,10 +214,7 @@ public void publishesAfterLoadAndAfterConvertEventsForDBRef() throws Exception { is(equalTo(ROOT_COLLECTION_NAME))); } - /** - * @see DATAMONGO-1271 - */ - @Test + @Test // DATAMONGO-1271 public void publishesAfterLoadAndAfterConvertEventsForLazyLoadingDBRef() throws Exception { Related ref1 = new Related(2L, "related desc1"); @@ -263,10 +248,7 @@ public void publishesAfterLoadAndAfterConvertEventsForLazyLoadingDBRef() throws is(equalTo(RELATED_COLLECTION_NAME))); } - /** - * @see DATAMONGO-1271 - */ - @Test + @Test // DATAMONGO-1271 public void publishesAfterLoadAndAfterConvertEventsForListOfDBRef() throws Exception { List references = Arrays.asList(new Related(20L, "ref 1"), new Related(30L, "ref 2")); @@ -298,10 +280,7 @@ public void publishesAfterLoadAndAfterConvertEventsForListOfDBRef() throws Excep is(equalTo(ROOT_COLLECTION_NAME))); } - /** - * @see DATAMONGO-1271 - */ - @Test + @Test // DATAMONGO-1271 public void publishesAfterLoadAndAfterConvertEventsForLazyLoadingListOfDBRef() throws Exception { List references = Arrays.asList(new Related(20L, "ref 1"), new Related(30L, "ref 2")); @@ -338,10 +317,7 @@ public void publishesAfterLoadAndAfterConvertEventsForLazyLoadingListOfDBRef() t is(equalTo(RELATED_COLLECTION_NAME))); } - /** - * @see DATAMONGO-1271 - */ - @Test + @Test // DATAMONGO-1271 public void publishesAfterLoadAndAfterConvertEventsForMapOfDBRef() throws Exception { Map references = new LinkedHashMap(); @@ -375,10 +351,7 @@ public void publishesAfterLoadAndAfterConvertEventsForMapOfDBRef() throws Except is(equalTo(ROOT_COLLECTION_NAME))); } - /** - * @see DATAMONGO-1271 - */ - @Test + @Test // DATAMONGO-1271 public void publishesAfterLoadAndAfterConvertEventsForLazyLoadingMapOfDBRef() throws Exception { Map references = new LinkedHashMap(); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/event/AuditingEventListenerUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/event/AuditingEventListenerUnitTests.java index 6cc1c0043d..34e8bb43b6 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/event/AuditingEventListenerUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/event/AuditingEventListenerUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2015 the original author or authors. + * Copyright 2012-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +20,7 @@ import static org.mockito.Mockito.*; import java.util.Arrays; +import java.util.Date; import org.junit.Before; import org.junit.Test; @@ -29,7 +30,9 @@ import org.springframework.beans.BeansException; import org.springframework.beans.factory.ObjectFactory; import org.springframework.core.Ordered; +import org.springframework.data.annotation.CreatedDate; import org.springframework.data.annotation.Id; +import org.springframework.data.annotation.LastModifiedDate; import org.springframework.data.auditing.IsNewAwareAuditingHandler; import org.springframework.data.mapping.context.PersistentEntities; import org.springframework.data.mongodb.core.mapping.MongoMappingContext; @@ -65,18 +68,12 @@ public IsNewAwareAuditingHandler getObject() throws BeansException { }); } - /** - * @see DATAMONGO-577 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-577 public void rejectsNullAuditingHandler() { new AuditingEventListener(null); } - /** - * @see DATAMONGO-577 - */ - @Test + @Test // DATAMONGO-577 public void triggersCreationMarkForObjectWithEmptyId() { Sample sample = new Sample(); @@ -86,10 +83,7 @@ public void triggersCreationMarkForObjectWithEmptyId() { verify(handler, times(0)).markModified(Mockito.any(Sample.class)); } - /** - * @see DATAMONGO-577 - */ - @Test + @Test // DATAMONGO-577 public void triggersModificationMarkForObjectWithSetId() { Sample sample = new Sample(); @@ -110,5 +104,7 @@ public void hasExplicitOrder() { static class Sample { @Id String id; + @CreatedDate Date created; + @LastModifiedDate Date modified; } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/event/PersonBeforeSaveListener.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/event/PersonBeforeSaveListener.java index eda34e756f..c5b5e36d90 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/event/PersonBeforeSaveListener.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/event/PersonBeforeSaveListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2015 by the original author(s). + * Copyright 2011-2017 by the original author(s). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/event/User.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/event/User.java index c638aeadc6..e5c2a78084 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/event/User.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/event/User.java @@ -1,5 +1,5 @@ /* - * Copyright 2012 the original author or authors. + * Copyright 2012-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,7 +22,6 @@ * Class used to test JSR-303 validation * {@link org.springframework.data.mongodb.core.mapping.event.ValidatingMongoEventListener} * - * @see DATAMONGO-36 * @author Maciej Walkowiak */ public class User { diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/event/ValidatingMongoEventListenerTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/event/ValidatingMongoEventListenerTests.java index 8a8d06cd3e..47137b8052 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/event/ValidatingMongoEventListenerTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/event/ValidatingMongoEventListenerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2014 the original author or authors. + * Copyright 2012-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,7 +33,6 @@ /** * Integration test for {@link ValidatingMongoEventListener}. * - * @see DATAMONGO-36 * @author Maciej Walkowiak * @author Oliver Gierke * @author Christoph Strobl @@ -46,7 +45,7 @@ public class ValidatingMongoEventListenerTests { @Autowired MongoTemplate mongoTemplate; - @Test + @Test // DATAMONGO-36 public void shouldThrowConstraintViolationException() { User user = new User("john", 17); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapreduce/MapReduceCountsUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapreduce/MapReduceCountsUnitTests.java index a0da06bc0e..9aeda12dc8 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapreduce/MapReduceCountsUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapreduce/MapReduceCountsUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012 the original author or authors. + * Copyright 2012-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,10 +27,7 @@ */ public class MapReduceCountsUnitTests { - /** - * @see DATACMNS-378 - */ - @Test + @Test // DATACMNS-378 public void equalsForSameNumberValues() { MapReduceCounts left = new MapReduceCounts(1L, 1L, 1L); @@ -41,10 +38,7 @@ public void equalsForSameNumberValues() { assertThat(left.hashCode(), is(right.hashCode())); } - /** - * @see DATACMNS-378 - */ - @Test + @Test // DATACMNS-378 public void notEqualForDifferentNumberValues() { MapReduceCounts left = new MapReduceCounts(1L, 1L, 1L); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapreduce/MapReduceOptionsTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapreduce/MapReduceOptionsTests.java index 9cc1728de4..699802ea0d 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapreduce/MapReduceOptionsTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapreduce/MapReduceOptionsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2010-2015 the original author or authors. + * Copyright 2010-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,10 +32,7 @@ public void testFinalize() { new MapReduceOptions().finalizeFunction("code"); } - /** - * @see DATAMONGO-1334 - */ - @Test + @Test // DATAMONGO-1334 public void limitShouldBeIncludedCorrectly() { MapReduceOptions options = new MapReduceOptions(); @@ -44,10 +41,7 @@ public void limitShouldBeIncludedCorrectly() { assertThat(options.getOptionsObject(), isBsonObject().containing("limit", 10)); } - /** - * @see DATAMONGO-1334 - */ - @Test + @Test // DATAMONGO-1334 public void limitShouldNotBePresentInDboWhenNotSet() { assertThat(new MapReduceOptions().getOptionsObject(), isBsonObject().notContaining("limit")); } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapreduce/MapReduceResultsUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapreduce/MapReduceResultsUnitTests.java index 087ee786eb..a960c67c50 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapreduce/MapReduceResultsUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapreduce/MapReduceResultsUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012 the original author or authors. + * Copyright 2012-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,10 +32,7 @@ */ public class MapReduceResultsUnitTests { - /** - * @see DATAMONGO-428 - */ - @Test + @Test // DATAMONGO-428 public void resolvesOutputCollectionForPlainResult() { DBObject rawResult = new BasicDBObject("result", "FOO"); @@ -44,10 +41,7 @@ public void resolvesOutputCollectionForPlainResult() { assertThat(results.getOutputCollection(), is("FOO")); } - /** - * @see DATAMONGO-428 - */ - @Test + @Test // DATAMONGO-428 public void resolvesOutputCollectionForDBObjectResult() { DBObject rawResult = new BasicDBObject("result", new BasicDBObject("collection", "FOO")); @@ -56,10 +50,7 @@ public void resolvesOutputCollectionForDBObjectResult() { assertThat(results.getOutputCollection(), is("FOO")); } - /** - * @see DATAMONGO-378 - */ - @Test + @Test // DATAMONGO-378 public void handlesLongTotalInResult() { DBObject inner = new BasicDBObject("total", 1L); @@ -70,10 +61,7 @@ public void handlesLongTotalInResult() { new MapReduceResults(Collections.emptyList(), source); } - /** - * @see DATAMONGO-378 - */ - @Test + @Test // DATAMONGO-378 public void handlesLongResultsForCounts() { DBObject inner = new BasicDBObject("input", 1L); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapreduce/MapReduceTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapreduce/MapReduceTests.java index d971168334..76319ef437 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapreduce/MapReduceTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapreduce/MapReduceTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2014 the original author or authors. + * Copyright 2011-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -278,10 +278,7 @@ public void testMapReduceExcludeQuery() { } - /** - * @see DATAMONGO-938 - */ - @Test + @Test // DATAMONGO-938 public void mapReduceShouldUseQueryMapper() { DBCollection c = mongoTemplate.getDb().getCollection("jmrWithGeo"); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/BasicQueryUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/BasicQueryUnitTests.java index e933768b1d..da4d8ab17e 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/BasicQueryUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/BasicQueryUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2016 the original author or authors. + * Copyright 2011-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -65,10 +65,7 @@ public void overridesSortCorrectly() { assertThat(query.getSortObject(), is(sortReference)); } - /** - * @see DATAMONGO-1093 - */ - @Test + @Test // DATAMONGO-1093 public void equalsContract() { BasicQuery query1 = new BasicQuery("{ \"name\" : \"Thomas\"}", "{\"name\":1, \"age\":1}"); @@ -83,10 +80,7 @@ public void equalsContract() { .verify(); } - /** - * @see DATAMONGO-1093 - */ - @Test + @Test // DATAMONGO-1093 public void handlesEqualsAndHashCodeCorrectlyForExactCopies() { String qry = "{ \"name\" : \"Thomas\"}"; @@ -103,10 +97,7 @@ public void handlesEqualsAndHashCodeCorrectlyForExactCopies() { assertThat(query1.hashCode(), is(query2.hashCode())); } - /** - * @see DATAMONGO-1093 - */ - @Test + @Test // DATAMONGO-1093 public void handlesEqualsAndHashCodeCorrectlyWhenBasicQuerySettingsDiffer() { String qry = "{ \"name\" : \"Thomas\"}"; @@ -122,10 +113,7 @@ public void handlesEqualsAndHashCodeCorrectlyWhenBasicQuerySettingsDiffer() { assertThat(query1.hashCode(), is(not(query2.hashCode()))); } - /** - * @see DATAMONGO-1093 - */ - @Test + @Test // DATAMONGO-1093 public void handlesEqualsAndHashCodeCorrectlyWhenQuerySettingsDiffer() { String qry = "{ \"name\" : \"Thomas\"}"; @@ -141,10 +129,7 @@ public void handlesEqualsAndHashCodeCorrectlyWhenQuerySettingsDiffer() { assertThat(query1.hashCode(), is(not(query2.hashCode()))); } - /** - * @see DATAMONGO-1387 - */ - @Test + @Test // DATAMONGO-1387 public void returnsFieldsCorrectly() { String qry = "{ \"name\" : \"Thomas\"}"; @@ -155,10 +140,7 @@ public void returnsFieldsCorrectly() { assertThat(query1.getFieldsObject(), isBsonObject().containing("name").containing("age")); } - /** - * @see DATAMONGO-1387 - */ - @Test + @Test // DATAMONGO-1387 public void handlesFieldsIncludeCorrectly() { String qry = "{ \"name\" : \"Thomas\"}"; @@ -169,10 +151,7 @@ public void handlesFieldsIncludeCorrectly() { assertThat(query1.getFieldsObject(), isBsonObject().containing("name")); } - /** - * @see DATAMONGO-1387 - */ - @Test + @Test // DATAMONGO-1387 public void combinesFieldsIncludeCorrectly() { String qry = "{ \"name\" : \"Thomas\"}"; diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/CriteriaTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/CriteriaTests.java index dc8e1ce84c..f73fbfde13 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/CriteriaTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/CriteriaTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2010-2015 the original author or authors. + * Copyright 2010-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -79,10 +79,7 @@ public void equalIfCriteriaMatches() { assertThat(right, is(not(left))); } - /** - * @see DATAMONGO-507 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-507 public void shouldThrowExceptionWhenTryingToNegateAndOperation() { new Criteria() // @@ -90,10 +87,7 @@ public void shouldThrowExceptionWhenTryingToNegateAndOperation() { .andOperator(Criteria.where("delete").is(true).and("_id").is(42)); // } - /** - * @see DATAMONGO-507 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-507 public void shouldThrowExceptionWhenTryingToNegateOrOperation() { new Criteria() // @@ -101,10 +95,7 @@ public void shouldThrowExceptionWhenTryingToNegateOrOperation() { .orOperator(Criteria.where("delete").is(true).and("_id").is(42)); // } - /** - * @see DATAMONGO-507 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-507 public void shouldThrowExceptionWhenTryingToNegateNorOperation() { new Criteria() // @@ -112,10 +103,7 @@ public void shouldThrowExceptionWhenTryingToNegateNorOperation() { .norOperator(Criteria.where("delete").is(true).and("_id").is(42)); // } - /** - * @see DATAMONGO-507 - */ - @Test + @Test // DATAMONGO-507 public void shouldNegateFollowingSimpleExpression() { Criteria c = Criteria.where("age").not().gt(18).and("status").is("student"); @@ -125,10 +113,7 @@ public void shouldNegateFollowingSimpleExpression() { assertThat(co.toString(), is("{ \"age\" : { \"$not\" : { \"$gt\" : 18}} , \"status\" : \"student\"}")); } - /** - * @see DATAMONGO-1068 - */ - @Test + @Test // DATAMONGO-1068 public void getCriteriaObjectShouldReturnEmptyDBOWhenNoCriteriaSpecified() { DBObject dbo = new Criteria().getCriteriaObject(); @@ -136,10 +121,7 @@ public void getCriteriaObjectShouldReturnEmptyDBOWhenNoCriteriaSpecified() { assertThat(dbo, equalTo(new BasicDBObjectBuilder().get())); } - /** - * @see DATAMONGO-1068 - */ - @Test + @Test // DATAMONGO-1068 public void getCriteriaObjectShouldUseCritieraValuesWhenNoKeyIsPresent() { DBObject dbo = new Criteria().lt("foo").getCriteriaObject(); @@ -147,10 +129,7 @@ public void getCriteriaObjectShouldUseCritieraValuesWhenNoKeyIsPresent() { assertThat(dbo, equalTo(new BasicDBObjectBuilder().add("$lt", "foo").get())); } - /** - * @see DATAMONGO-1068 - */ - @Test + @Test // DATAMONGO-1068 public void getCriteriaObjectShouldUseCritieraValuesWhenNoKeyIsPresentButMultipleCriteriasPresent() { DBObject dbo = new Criteria().lt("foo").gt("bar").getCriteriaObject(); @@ -158,10 +137,7 @@ public void getCriteriaObjectShouldUseCritieraValuesWhenNoKeyIsPresentButMultipl assertThat(dbo, equalTo(new BasicDBObjectBuilder().add("$lt", "foo").add("$gt", "bar").get())); } - /** - * @see DATAMONGO-1068 - */ - @Test + @Test // DATAMONGO-1068 public void getCriteriaObjectShouldRespectNotWhenNoKeyPresent() { DBObject dbo = new Criteria().lt("foo").not().getCriteriaObject(); @@ -169,10 +145,7 @@ public void getCriteriaObjectShouldRespectNotWhenNoKeyPresent() { assertThat(dbo, equalTo(new BasicDBObjectBuilder().add("$not", new BasicDBObject("$lt", "foo")).get())); } - /** - * @see DATAMONGO-1135 - */ - @Test + @Test // DATAMONGO-1135 public void geoJsonTypesShouldBeWrappedInGeometry() { DBObject dbo = new Criteria("foo").near(new GeoJsonPoint(100, 200)).getCriteriaObject(); @@ -180,10 +153,7 @@ public void geoJsonTypesShouldBeWrappedInGeometry() { assertThat(dbo, isBsonObject().containing("foo.$near.$geometry", new GeoJsonPoint(100, 200))); } - /** - * @see DATAMONGO-1135 - */ - @Test + @Test // DATAMONGO-1135 public void legacyCoordinateTypesShouldNotBeWrappedInGeometry() { DBObject dbo = new Criteria("foo").near(new Point(100, 200)).getCriteriaObject(); @@ -191,10 +161,7 @@ public void legacyCoordinateTypesShouldNotBeWrappedInGeometry() { assertThat(dbo, isBsonObject().notContaining("foo.$near.$geometry")); } - /** - * @see DATAMONGO-1135 - */ - @Test + @Test // DATAMONGO-1135 public void maxDistanceShouldBeMappedInsideNearWhenUsedAlongWithGeoJsonType() { DBObject dbo = new Criteria("foo").near(new GeoJsonPoint(100, 200)).maxDistance(50D).getCriteriaObject(); @@ -202,10 +169,7 @@ public void maxDistanceShouldBeMappedInsideNearWhenUsedAlongWithGeoJsonType() { assertThat(dbo, isBsonObject().containing("foo.$near.$maxDistance", 50D)); } - /** - * @see DATAMONGO-1135 - */ - @Test + @Test // DATAMONGO-1135 public void maxDistanceShouldBeMappedInsideNearSphereWhenUsedAlongWithGeoJsonType() { DBObject dbo = new Criteria("foo").nearSphere(new GeoJsonPoint(100, 200)).maxDistance(50D).getCriteriaObject(); @@ -213,10 +177,7 @@ public void maxDistanceShouldBeMappedInsideNearSphereWhenUsedAlongWithGeoJsonTyp assertThat(dbo, isBsonObject().containing("foo.$nearSphere.$maxDistance", 50D)); } - /** - * @see DATAMONGO-1110 - */ - @Test + @Test // DATAMONGO-1110 public void minDistanceShouldBeMappedInsideNearWhenUsedAlongWithGeoJsonType() { DBObject dbo = new Criteria("foo").near(new GeoJsonPoint(100, 200)).minDistance(50D).getCriteriaObject(); @@ -224,10 +185,7 @@ public void minDistanceShouldBeMappedInsideNearWhenUsedAlongWithGeoJsonType() { assertThat(dbo, isBsonObject().containing("foo.$near.$minDistance", 50D)); } - /** - * @see DATAMONGO-1110 - */ - @Test + @Test // DATAMONGO-1110 public void minDistanceShouldBeMappedInsideNearSphereWhenUsedAlongWithGeoJsonType() { DBObject dbo = new Criteria("foo").nearSphere(new GeoJsonPoint(100, 200)).minDistance(50D).getCriteriaObject(); @@ -235,10 +193,7 @@ public void minDistanceShouldBeMappedInsideNearSphereWhenUsedAlongWithGeoJsonTyp assertThat(dbo, isBsonObject().containing("foo.$nearSphere.$minDistance", 50D)); } - /** - * @see DATAMONGO-1110 - */ - @Test + @Test // DATAMONGO-1110 public void minAndMaxDistanceShouldBeMappedInsideNearSphereWhenUsedAlongWithGeoJsonType() { DBObject dbo = new Criteria("foo").nearSphere(new GeoJsonPoint(100, 200)).minDistance(50D).maxDistance(100D) @@ -248,18 +203,12 @@ public void minAndMaxDistanceShouldBeMappedInsideNearSphereWhenUsedAlongWithGeoJ assertThat(dbo, isBsonObject().containing("foo.$nearSphere.$maxDistance", 100D)); } - /** - * @see DATAMONGO-1134 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-1134 public void intersectsShouldThrowExceptionWhenCalledWihtNullValue() { new Criteria("foo").intersects(null); } - /** - * @see DATAMONGO-1134 - */ - @Test + @Test // DATAMONGO-1134 public void intersectsShouldWrapGeoJsonTypeInGeometryCorrectly() { GeoJsonLineString lineString = new GeoJsonLineString(new Point(0, 0), new Point(10, 10)); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/IndexUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/IndexUnitTests.java index 97384eb541..b4c7013f76 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/IndexUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/IndexUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2010-2013 the original author or authors. + * Copyright 2010-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -76,10 +76,7 @@ public void testGeospatialIndex() { assertEquals("{ \"min\" : 0}", i.getIndexOptions().toString()); } - /** - * @see DATAMONGO-778 - */ - @Test + @Test // DATAMONGO-778 public void testGeospatialIndex2DSphere() { GeospatialIndex i = new GeospatialIndex("location").typed(GeoSpatialIndexType.GEO_2DSPHERE); @@ -87,10 +84,7 @@ public void testGeospatialIndex2DSphere() { assertEquals("{ }", i.getIndexOptions().toString()); } - /** - * @see DATAMONGO-778 - */ - @Test + @Test // DATAMONGO-778 public void testGeospatialIndexGeoHaystack() { GeospatialIndex i = new GeospatialIndex("location").typed(GeoSpatialIndexType.GEO_HAYSTACK) diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/NearQueryUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/NearQueryUnitTests.java index 10232f56f7..b89876ffc4 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/NearQueryUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/NearQueryUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2014 the original author or authors. + * Copyright 2011-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -83,10 +83,7 @@ public void configuresResultMetricCorrectly() { assertThat(query.getMetric(), is((Metric) Metrics.MILES)); } - /** - * @see DATAMONGO-445 - */ - @Test + @Test // DATAMONGO-445 public void shouldTakeSkipAndLimitSettingsFromGivenPageable() { Pageable pageable = new PageRequest(3, 5); @@ -96,10 +93,7 @@ public void shouldTakeSkipAndLimitSettingsFromGivenPageable() { assertThat((Integer) query.toDBObject().get("num"), is((pageable.getPageNumber() + 1) * pageable.getPageSize())); } - /** - * @see DATAMONGO-445 - */ - @Test + @Test // DATAMONGO-445 public void shouldTakeSkipAndLimitSettingsFromGivenQuery() { int limit = 10; @@ -111,10 +105,7 @@ public void shouldTakeSkipAndLimitSettingsFromGivenQuery() { assertThat((Integer) query.toDBObject().get("num"), is(limit)); } - /** - * @see DATAMONGO-445 - */ - @Test + @Test // DATAMONGO-445 public void shouldTakeSkipAndLimitSettingsFromPageableEvenIfItWasSpecifiedOnQuery() { int limit = 10; @@ -127,28 +118,19 @@ public void shouldTakeSkipAndLimitSettingsFromPageableEvenIfItWasSpecifiedOnQuer assertThat((Integer) query.toDBObject().get("num"), is((pageable.getPageNumber() + 1) * pageable.getPageSize())); } - /** - * @see DATAMONGO-829 - */ - @Test + @Test // DATAMONGO-829 public void nearQueryShouldInoreZeroLimitFromQuery() { NearQuery query = NearQuery.near(new Point(1, 2)).query(Query.query(Criteria.where("foo").is("bar"))); assertThat(query.toDBObject().get("num"), nullValue()); } - /** - * @see DATAMONOGO-829 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONOGO-829 public void nearQueryShouldThrowExceptionWhenGivenANullQuery() { NearQuery.near(new Point(1, 2)).query(null); } - /** - * @see DATAMONGO-829 - */ - @Test + @Test // DATAMONGO-829 public void numShouldNotBeAlteredByQueryWithoutPageable() { int num = 100; diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/QueryTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/QueryTests.java index d1b4b28c90..a152374e16 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/QueryTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/QueryTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2010-2013 the original author or authors. + * Copyright 2010-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -108,10 +108,7 @@ public void testQueryWithFieldsAndSlice() { Assert.assertEquals(expectedFields, q.getFieldsObject().toString()); } - /** - * @see DATAMONGO-652 - */ - @Test + @Test // DATAMONGO-652 public void testQueryWithFieldsElemMatchAndPositionalOperator() { Query query = query(where("name").gte("M").lte("T").and("age").not().gt(22)); @@ -179,10 +176,7 @@ public void testQueryWithRegexAndOption() { Assert.assertEquals(expected, q.getQueryObject().toString()); } - /** - * @see DATAMONGO-538 - */ - @Test + @Test // DATAMONGO-538 public void addsSortCorrectly() { Query query = new Query().with(new Sort(Direction.DESC, "foo")); @@ -198,10 +192,7 @@ public void rejectsOrderWithIgnoreCase() { new Query().with(new Sort(new Sort.Order("foo").ignoreCase())); } - /** - * @see DATAMONGO-709 - */ - @Test + @Test // DATAMONGO-709 @SuppressWarnings("unchecked") public void shouldReturnClassHierarchyOfRestrictedTypes() { diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/SortTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/SortTests.java index 6450ac7d62..701193bd2a 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/SortTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/SortTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2010-2013 the original author or authors. + * Copyright 2010-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -41,10 +41,7 @@ public void testWithSortDescending() { assertEquals("{ \"name\" : -1}", s.getSortObject().toString()); } - /** - * @see DATADOC-177 - */ - @Test + @Test // DATADOC-177 public void preservesOrderKeysOnMultipleSorts() { Query sort = new Query().with(new Sort(Direction.DESC, "foo").and(new Sort(Direction.DESC, "bar"))); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/TextCriteriaUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/TextCriteriaUnitTests.java index 64d96ff485..8b650f5d9f 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/TextCriteriaUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/TextCriteriaUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2016 the original author or authors. + * Copyright 2014-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,50 +33,35 @@ */ public class TextCriteriaUnitTests { - /** - * @see DATAMONGO-850 - */ - @Test + @Test // DATAMONGO-850 public void shouldNotHaveLanguageField() { TextCriteria criteria = TextCriteria.forDefaultLanguage(); assertThat(criteria.getCriteriaObject(), equalTo(searchObject("{ }"))); } - /** - * @see DATAMONGO-850 - */ - @Test + @Test // DATAMONGO-850 public void shouldNotHaveLanguageForNonDefaultLanguageField() { TextCriteria criteria = TextCriteria.forLanguage("spanish"); assertThat(criteria.getCriteriaObject(), equalTo(searchObject("{ \"$language\" : \"spanish\" }"))); } - /** - * @see DATAMONGO-850 - */ - @Test + @Test // DATAMONGO-850 public void shouldCreateSearchFieldForSingleTermCorrectly() { TextCriteria criteria = TextCriteria.forDefaultLanguage().matching("cake"); assertThat(criteria.getCriteriaObject(), equalTo(searchObject("{ \"$search\" : \"cake\" }"))); } - /** - * @see DATAMONGO-850 - */ - @Test + @Test // DATAMONGO-850 public void shouldCreateSearchFieldCorrectlyForMultipleTermsCorrectly() { TextCriteria criteria = TextCriteria.forDefaultLanguage().matchingAny("bake", "coffee", "cake"); assertThat(criteria.getCriteriaObject(), equalTo(searchObject("{ \"$search\" : \"bake coffee cake\" }"))); } - /** - * @see DATAMONGO-850 - */ - @Test + @Test // DATAMONGO-850 public void shouldCreateSearchFieldForPhraseCorrectly() { TextCriteria criteria = TextCriteria.forDefaultLanguage().matchingPhrase("coffee cake"); @@ -84,30 +69,21 @@ public void shouldCreateSearchFieldForPhraseCorrectly() { equalTo((DBObject) new BasicDBObject("$search", "\"coffee cake\""))); } - /** - * @see DATAMONGO-850 - */ - @Test + @Test // DATAMONGO-850 public void shouldCreateNotFieldCorrectly() { TextCriteria criteria = TextCriteria.forDefaultLanguage().notMatching("cake"); assertThat(criteria.getCriteriaObject(), equalTo(searchObject("{ \"$search\" : \"-cake\" }"))); } - /** - * @see DATAMONGO-850 - */ - @Test + @Test // DATAMONGO-850 public void shouldCreateSearchFieldCorrectlyForNotMultipleTermsCorrectly() { TextCriteria criteria = TextCriteria.forDefaultLanguage().notMatchingAny("bake", "coffee", "cake"); assertThat(criteria.getCriteriaObject(), equalTo(searchObject("{ \"$search\" : \"-bake -coffee -cake\" }"))); } - /** - * @see DATAMONGO-850 - */ - @Test + @Test // DATAMONGO-850 public void shouldCreateSearchFieldForNotPhraseCorrectly() { TextCriteria criteria = TextCriteria.forDefaultLanguage().notMatchingPhrase("coffee cake"); @@ -115,10 +91,7 @@ public void shouldCreateSearchFieldForNotPhraseCorrectly() { equalTo((DBObject) new BasicDBObject("$search", "-\"coffee cake\""))); } - /** - * @see DATAMONGO-1455 - */ - @Test + @Test // DATAMONGO-1455 public void caseSensitiveOperatorShouldBeSetCorrectly() { TextCriteria criteria = TextCriteria.forDefaultLanguage().matching("coffee").caseSensitive(true); @@ -126,10 +99,7 @@ public void caseSensitiveOperatorShouldBeSetCorrectly() { equalTo(new BasicDBObjectBuilder().add("$search", "coffee").add("$caseSensitive", true).get())); } - /** - * @see DATAMONGO-1456 - */ - @Test + @Test // DATAMONGO-1456 public void diacriticSensitiveOperatorShouldBeSetCorrectly() { TextCriteria criteria = TextCriteria.forDefaultLanguage().matching("coffee").diacriticSensitive(true); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/TextQueryTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/TextQueryTests.java index 94b0637ef8..c8d28c6116 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/TextQueryTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/TextQueryTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 the original author or authors. + * Copyright 2014-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -104,10 +104,7 @@ private DBObject weights() { }); } - /** - * @see DATAMONGO-850 - */ - @Test + @Test // DATAMONGO-850 public void shouldOnlyFindDocumentsMatchingAnyWordOfGivenQuery() { initWithDefaultDocuments(); @@ -117,10 +114,7 @@ public void shouldOnlyFindDocumentsMatchingAnyWordOfGivenQuery() { assertThat(result, hasItems(BAKE, COFFEE, CAKE)); } - /** - * @see DATAMONGO-850 - */ - @Test + @Test // DATAMONGO-850 public void shouldNotFindDocumentsWhenQueryDoesNotMatchAnyDocumentInIndex() { initWithDefaultDocuments(); @@ -129,10 +123,7 @@ public void shouldNotFindDocumentsWhenQueryDoesNotMatchAnyDocumentInIndex() { assertThat(result, hasSize(0)); } - /** - * @see DATAMONGO-850 - */ - @Test + @Test // DATAMONGO-850 public void shouldApplySortByScoreCorrectly() { initWithDefaultDocuments(); @@ -147,10 +138,7 @@ public void shouldApplySortByScoreCorrectly() { assertThat(result.get(3), equalTo(CAKE)); } - /** - * @see DATAMONGO-850 - */ - @Test + @Test // DATAMONGO-850 public void shouldFindTextInAnyLanguage() { initWithDefaultDocuments(); @@ -159,10 +147,7 @@ public void shouldFindTextInAnyLanguage() { assertThat(result, hasItems(SPANISH_MILK, FRENCH_MILK)); } - /** - * @see DATAMONGO-850 - */ - @Test + @Test // DATAMONGO-850 public void shouldOnlyFindTextInSpecificLanguage() { initWithDefaultDocuments(); @@ -172,10 +157,7 @@ public void shouldOnlyFindTextInSpecificLanguage() { assertThat(result.get(0), equalTo(SPANISH_MILK)); } - /** - * @see DATAMONGO-850 - */ - @Test + @Test // DATAMONGO-850 public void shouldNotFindDocumentsWithNegatedTerms() { initWithDefaultDocuments(); @@ -185,10 +167,7 @@ public void shouldNotFindDocumentsWithNegatedTerms() { assertThat(result, hasItems(BAKE, COFFEE)); } - /** - * @see DATAMONGO-976 - */ - @Test + @Test // DATAMONGO-976 public void shouldInlcudeScoreCorreclty() { initWithDefaultDocuments(); @@ -202,10 +181,7 @@ public void shouldInlcudeScoreCorreclty() { } } - /** - * @see DATAMONGO-850 - */ - @Test + @Test // DATAMONGO-850 public void shouldApplyPhraseCorrectly() { initWithDefaultDocuments(); @@ -217,10 +193,7 @@ public void shouldApplyPhraseCorrectly() { assertThat(result, contains(MILK_AND_SUGAR)); } - /** - * @see DATAMONGO-850 - */ - @Test + @Test // DATAMONGO-850 public void shouldReturnEmptyListWhenNoDocumentsMatchGivenPhrase() { initWithDefaultDocuments(); @@ -231,10 +204,7 @@ public void shouldReturnEmptyListWhenNoDocumentsMatchGivenPhrase() { assertThat(result, empty()); } - /** - * @see DATAMONGO-850 - */ - @Test + @Test // DATAMONGO-850 public void shouldApplyPaginationCorrectly() { initWithDefaultDocuments(); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/TextQueryUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/TextQueryUnitTests.java index 2a0fa9c0bc..b71b3c4ee5 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/TextQueryUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/TextQueryUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 the original author or authors. + * Copyright 2014-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,34 +32,22 @@ public class TextQueryUnitTests { private static final String QUERY = "bake coffee cake"; private static final String LANGUAGE_SPANISH = "spanish"; - /** - * @see DATAMONGO-850 - */ - @Test + @Test // DATAMONGO-850 public void shouldCreateQueryObjectCorrectly() { assertThat(new TextQuery(QUERY), isTextQuery().searchingFor(QUERY)); } - /** - * @see DATAMONGO-850 - */ - @Test + @Test // DATAMONGO-850 public void shouldIncludeLanguageInQueryObjectWhenNotNull() { assertThat(new TextQuery(QUERY, LANGUAGE_SPANISH), isTextQuery().searchingFor(QUERY).inLanguage(LANGUAGE_SPANISH)); } - /** - * @see DATAMONGO-850 - */ - @Test + @Test // DATAMONGO-850 public void shouldIncludeScoreFieldCorrectly() { assertThat(new TextQuery(QUERY).includeScore(), isTextQuery().searchingFor(QUERY).returningScore()); } - /** - * @see DATAMONGO-850 - */ - @Test + @Test // DATAMONGO-850 public void shouldNotOverrideExistingProjections() { TextQuery query = new TextQuery(TextCriteria.forDefaultLanguage().matching(QUERY)).includeScore(); @@ -68,18 +56,12 @@ public void shouldNotOverrideExistingProjections() { assertThat(query, isTextQuery().searchingFor(QUERY).returningScore().includingField("foo")); } - /** - * @see DATAMONGO-850 - */ - @Test + @Test // DATAMONGO-850 public void shouldIncludeSortingByScoreCorrectly() { assertThat(new TextQuery(QUERY).sortByScore(), isTextQuery().searchingFor(QUERY).returningScore().sortingByScore()); } - /** - * @see DATAMONGO-850 - */ - @Test + @Test // DATAMONGO-850 public void shouldNotOverrideExistingSort() { TextQuery query = new TextQuery(QUERY); @@ -90,10 +72,7 @@ public void shouldNotOverrideExistingSort() { isTextQuery().searchingFor(QUERY).returningScore().sortingByScore().sortingBy("foo", Direction.DESC)); } - /** - * @see DATAMONGO-850 - */ - @Test + @Test // DATAMONGO-850 public void shouldUseCustomFieldnameForScoring() { TextQuery query = new TextQuery(QUERY).includeScore("customFieldForScore").sortByScore(); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/UpdateTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/UpdateTests.java index 47cc8fee91..1325bb8001 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/UpdateTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/UpdateTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2010-2016 the original author or authors. + * Copyright 2010-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -104,10 +104,7 @@ public void testPushAll() { is("{ \"$pushAll\" : { \"authors\" : [ { \"name\" : \"Sven\"} , { \"name\" : \"Maria\"}]}}")); } - /** - * @see DATAMONGO-354 - */ - @Test + @Test // DATAMONGO-354 public void testMultiplePushAllShouldBePossibleWhenUsingDifferentFields() { Map m1 = Collections.singletonMap("name", "Sven"); @@ -177,78 +174,54 @@ public void testBasicUpdateIncAndSet() { is("{ \"$inc\" : { \"size\" : 1} , \"$set\" : { \"directory\" : \"/Users/Test/Desktop\"}}")); } - /** - * @see DATAMONGO-630 - */ - @Test + @Test // DATAMONGO-630 public void testSetOnInsert() { Update u = new Update().setOnInsert("size", 1); assertThat(u.getUpdateObject().toString(), is("{ \"$setOnInsert\" : { \"size\" : 1}}")); } - /** - * @see DATAMONGO-630 - */ - @Test + @Test // DATAMONGO-630 public void testSetOnInsertSetOnInsert() { Update u = new Update().setOnInsert("size", 1).setOnInsert("count", 1); assertThat(u.getUpdateObject().toString(), is("{ \"$setOnInsert\" : { \"size\" : 1 , \"count\" : 1}}")); } - /** - * @see DATAMONGO-852 - */ - @Test + @Test // DATAMONGO-852 public void testUpdateAffectsFieldShouldReturnTrueWhenMultiFieldOperationAddedForField() { Update update = new Update().set("foo", "bar"); assertThat(update.modifies("foo"), is(true)); } - /** - * @see DATAMONGO-852 - */ - @Test + @Test // DATAMONGO-852 public void testUpdateAffectsFieldShouldReturnFalseWhenMultiFieldOperationAddedForField() { Update update = new Update().set("foo", "bar"); assertThat(update.modifies("oof"), is(false)); } - /** - * @see DATAMONGO-852 - */ - @Test + @Test // DATAMONGO-852 public void testUpdateAffectsFieldShouldReturnTrueWhenSingleFieldOperationAddedForField() { Update update = new Update().pullAll("foo", new Object[] { "bar" }); assertThat(update.modifies("foo"), is(true)); } - /** - * @see DATAMONGO-852 - */ - @Test + @Test // DATAMONGO-852 public void testUpdateAffectsFieldShouldReturnFalseWhenSingleFieldOperationAddedForField() { Update update = new Update().pullAll("foo", new Object[] { "bar" }); assertThat(update.modifies("oof"), is(false)); } - /** - * @see DATAMONGO-852 - */ - @Test + @Test // DATAMONGO-852 public void testUpdateAffectsFieldShouldReturnFalseWhenCalledOnEmptyUpdate() { assertThat(new Update().modifies("foo"), is(false)); } - /** - * @see DATAMONGO-852 - */ - @Test + @Test // DATAMONGO-852 public void testUpdateAffectsFieldShouldReturnTrueWhenUpdateWithKeyCreatedFromDbObject() { Update update = new Update().set("foo", "bar"); @@ -257,10 +230,7 @@ public void testUpdateAffectsFieldShouldReturnTrueWhenUpdateWithKeyCreatedFromDb assertThat(clone.modifies("foo"), is(true)); } - /** - * @see DATAMONGO-852 - */ - @Test + @Test // DATAMONGO-852 public void testUpdateAffectsFieldShouldReturnFalseWhenUpdateWithoutKeyCreatedFromDbObject() { Update update = new Update().set("foo", "bar"); @@ -269,34 +239,22 @@ public void testUpdateAffectsFieldShouldReturnFalseWhenUpdateWithoutKeyCreatedFr assertThat(clone.modifies("oof"), is(false)); } - /** - * @see DATAMONGO-853 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-853 public void testAddingMultiFieldOperationThrowsExceptionWhenCalledWithNullKey() { new Update().addMultiFieldOperation("$op", null, "exprected to throw IllegalArgumentException."); } - /** - * @see DATAMONGO-853 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-853 public void testAddingSingleFieldOperationThrowsExceptionWhenCalledWithNullKey() { new Update().addFieldOperation("$op", null, "exprected to throw IllegalArgumentException."); } - /** - * @see DATAMONGO-853 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-853 public void testCreatingUpdateWithNullKeyThrowsException() { Update.update(null, "value"); } - /** - * @see DATAMONGO-953 - */ - @Test + @Test // DATAMONGO-953 public void testEquality() { Update actualUpdate = new Update() // @@ -321,10 +279,7 @@ public void testEquality() { assertThat(actualUpdate.hashCode(), is(equalTo(expectedUpdate.hashCode()))); } - /** - * @see DATAMONGO-953 - */ - @Test + @Test // DATAMONGO-953 public void testToString() { Update actualUpdate = new Update() // @@ -351,10 +306,7 @@ public void testToString() { + ", \"$pop\" : { \"authors\" : -1}}")); // } - /** - * @see DATAMONGO-944 - */ - @Test + @Test // DATAMONGO-944 public void getUpdateObjectShouldReturnCurrentDateCorrectlyForSingleFieldWhenUsingDate() { Update update = new Update().currentDate("foo"); @@ -362,10 +314,7 @@ public void getUpdateObjectShouldReturnCurrentDateCorrectlyForSingleFieldWhenUsi equalTo(new BasicDBObjectBuilder().add("$currentDate", new BasicDBObject("foo", true)).get())); } - /** - * @see DATAMONGO-944 - */ - @Test + @Test // DATAMONGO-944 public void getUpdateObjectShouldReturnCurrentDateCorrectlyForMultipleFieldsWhenUsingDate() { Update update = new Update().currentDate("foo").currentDate("bar"); @@ -373,10 +322,7 @@ public void getUpdateObjectShouldReturnCurrentDateCorrectlyForMultipleFieldsWhen new BasicDBObjectBuilder().add("$currentDate", new BasicDBObject("foo", true).append("bar", true)).get())); } - /** - * @see DATAMONGO-944 - */ - @Test + @Test // DATAMONGO-944 public void getUpdateObjectShouldReturnCurrentDateCorrectlyForSingleFieldWhenUsingTimestamp() { Update update = new Update().currentTimestamp("foo"); @@ -384,10 +330,7 @@ public void getUpdateObjectShouldReturnCurrentDateCorrectlyForSingleFieldWhenUsi .add("$currentDate", new BasicDBObject("foo", new BasicDBObject("$type", "timestamp"))).get())); } - /** - * @see DATAMONGO-944 - */ - @Test + @Test // DATAMONGO-944 public void getUpdateObjectShouldReturnCurrentDateCorrectlyForMultipleFieldsWhenUsingTimestamp() { Update update = new Update().currentTimestamp("foo").currentTimestamp("bar"); @@ -398,10 +341,7 @@ public void getUpdateObjectShouldReturnCurrentDateCorrectlyForMultipleFieldsWhen .get())); } - /** - * @see DATAMONGO-944 - */ - @Test + @Test // DATAMONGO-944 public void getUpdateObjectShouldReturnCurrentDateCorrectlyWhenUsingMixedDateAndTimestamp() { Update update = new Update().currentDate("foo").currentTimestamp("bar"); @@ -411,28 +351,19 @@ public void getUpdateObjectShouldReturnCurrentDateCorrectlyWhenUsingMixedDateAnd .get())); } - /** - * @see DATAMONGO-1002 - */ - @Test + @Test // DATAMONGO-1002 public void toStringWorksForUpdateWithComplexObject() { Update update = new Update().addToSet("key", new DateTime()); assertThat(update.toString(), is(notNullValue())); } - /** - * @see DATAMONGO-1097 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-1097 public void multiplyShouldThrowExceptionForNullMultiplier() { new Update().multiply("key", null); } - /** - * @see DATAMONGO-1097 - */ - @Test + @Test // DATAMONGO-1097 public void multiplyShouldAddMultiplierAsItsDoubleValue() { Update update = new Update().multiply("key", 10); @@ -441,10 +372,7 @@ public void multiplyShouldAddMultiplierAsItsDoubleValue() { equalTo(new BasicDBObjectBuilder().add("$mul", new BasicDBObject("key", 10D)).get())); } - /** - * @see DATAMONGO-1101 - */ - @Test + @Test // DATAMONGO-1101 public void getUpdateObjectShouldReturnCorrectRepresentationForBitwiseAnd() { Update update = new Update().bitwise("key").and(10L); @@ -453,10 +381,7 @@ public void getUpdateObjectShouldReturnCorrectRepresentationForBitwiseAnd() { equalTo(new BasicDBObjectBuilder().add("$bit", new BasicDBObject("key", new BasicDBObject("and", 10L))).get())); } - /** - * @see DATAMONGO-1101 - */ - @Test + @Test // DATAMONGO-1101 public void getUpdateObjectShouldReturnCorrectRepresentationForBitwiseOr() { Update update = new Update().bitwise("key").or(10L); @@ -465,10 +390,7 @@ public void getUpdateObjectShouldReturnCorrectRepresentationForBitwiseOr() { equalTo(new BasicDBObjectBuilder().add("$bit", new BasicDBObject("key", new BasicDBObject("or", 10L))).get())); } - /** - * @see DATAMONGO-1101 - */ - @Test + @Test // DATAMONGO-1101 public void getUpdateObjectShouldReturnCorrectRepresentationForBitwiseXor() { Update update = new Update().bitwise("key").xor(10L); @@ -477,18 +399,12 @@ public void getUpdateObjectShouldReturnCorrectRepresentationForBitwiseXor() { equalTo(new BasicDBObjectBuilder().add("$bit", new BasicDBObject("key", new BasicDBObject("xor", 10L))).get())); } - /** - * @see DATAMONGO-943 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-943 public void pushShouldThrowExceptionWhenGivenNegativePosition() { new Update().push("foo").atPosition(-1).each("booh"); } - /** - * @see DATAMONGO-1346 - */ - @Test + @Test // DATAMONGO-1346 public void registersMultiplePullAllClauses() { Update update = new Update(); @@ -503,26 +419,17 @@ public void registersMultiplePullAllClauses() { assertThat(pullAll.get("field2"), is(notNullValue())); } - /** - * @see DATAMONGO-1404 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-1404 public void maxShouldThrowExceptionForNullMultiplier() { new Update().max("key", null); } - /** - * @see DATAMONGO-1404 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-1404 public void minShouldThrowExceptionForNullMultiplier() { new Update().min("key", null); } - /** - * @see DATAMONGO-1404 - */ - @Test + @Test // DATAMONGO-1404 public void getUpdateObjectShouldReturnCorrectRepresentationForMax() { Update update = new Update().max("key", 10); @@ -531,10 +438,7 @@ public void getUpdateObjectShouldReturnCorrectRepresentationForMax() { equalTo(new BasicDBObjectBuilder().add("$max", new BasicDBObject("key", 10)).get())); } - /** - * @see DATAMONGO-1404 - */ - @Test + @Test // DATAMONGO-1404 public void getUpdateObjectShouldReturnCorrectRepresentationForMin() { Update update = new Update().min("key", 10); @@ -543,10 +447,7 @@ public void getUpdateObjectShouldReturnCorrectRepresentationForMin() { equalTo(new BasicDBObjectBuilder().add("$min", new BasicDBObject("key", 10)).get())); } - /** - * @see DATAMONGO-1404 - */ - @Test + @Test // DATAMONGO-1404 public void shouldSuppressPreviousValueForMax() { Update update = new Update().max("key", 10); @@ -556,10 +457,7 @@ public void shouldSuppressPreviousValueForMax() { equalTo(new BasicDBObjectBuilder().add("$max", new BasicDBObject("key", 99)).get())); } - /** - * @see DATAMONGO-1404 - */ - @Test + @Test // DATAMONGO-1404 public void shouldSuppressPreviousValueForMin() { Update update = new Update().min("key", 10); @@ -569,10 +467,7 @@ public void shouldSuppressPreviousValueForMin() { equalTo(new BasicDBObjectBuilder().add("$min", new BasicDBObject("key", 99)).get())); } - /** - * @see DATAMONGO-1404 - */ - @Test + @Test // DATAMONGO-1404 public void getUpdateObjectShouldReturnCorrectDateRepresentationForMax() { Date date = new Date(); @@ -582,10 +477,7 @@ public void getUpdateObjectShouldReturnCorrectDateRepresentationForMax() { equalTo(new BasicDBObjectBuilder().add("$max", new BasicDBObject("key", date)).get())); } - /** - * @see DATAMONGO-1404 - */ - @Test + @Test // DATAMONGO-1404 public void getUpdateObjectShouldReturnCorrectDateRepresentationForMin() { Date date = new Date(); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/script/ExecutableMongoScriptUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/script/ExecutableMongoScriptUnitTests.java index a8fda1fad1..50142e3dcf 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/script/ExecutableMongoScriptUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/script/ExecutableMongoScriptUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 the original author or authors. + * Copyright 2014-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,10 +31,7 @@ public class ExecutableMongoScriptUnitTests { public @Rule ExpectedException expectedException = ExpectedException.none(); - /** - * @see DATAMONGO-479 - */ - @Test + @Test // DATAMONGO-479 public void constructorShouldThrowExceptionWhenRawScriptIsNull() { expectException(IllegalArgumentException.class, "must not be", "null"); @@ -42,10 +39,7 @@ public void constructorShouldThrowExceptionWhenRawScriptIsNull() { new ExecutableMongoScript(null); } - /** - * @see DATAMONGO-479 - */ - @Test + @Test // DATAMONGO-479 public void constructorShouldThrowExceptionWhenRawScriptIsEmpty() { expectException(IllegalArgumentException.class, "must not be", "empty"); @@ -53,10 +47,7 @@ public void constructorShouldThrowExceptionWhenRawScriptIsEmpty() { new ExecutableMongoScript(""); } - /** - * @see DATAMONGO-479 - */ - @Test + @Test // DATAMONGO-479 public void getCodeShouldReturnCodeRepresentationOfRawScript() { String jsFunction = "function(x) { return x; }"; diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/script/NamedMongoScriptUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/script/NamedMongoScriptUnitTests.java index 32803881dc..e22918a25a 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/script/NamedMongoScriptUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/script/NamedMongoScriptUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2015 the original author or authors. + * Copyright 2014-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,34 +29,22 @@ */ public class NamedMongoScriptUnitTests { - /** - * @see DATAMONGO-479 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-479 public void shouldThrowExceptionWhenScriptNameIsNull() { new NamedMongoScript(null, "return 1;"); } - /** - * @see DATAMONGO-479 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-479 public void shouldThrowExceptionWhenScriptNameIsEmptyString() { new NamedMongoScript("", "return 1"); } - /** - * @see DATAMONGO-479 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-479 public void shouldThrowExceptionWhenRawScriptIsEmptyString() { new NamedMongoScript("foo", ""); } - /** - * @see DATAMONGO-479 - */ - @Test + @Test // DATAMONGO-479 public void getCodeShouldReturnCodeRepresentationOfRawScript() { String jsFunction = "function(x) { return x; }"; diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/spel/ExpressionNodeUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/spel/ExpressionNodeUnitTests.java index 0f6d4b9b09..fce53fb054 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/spel/ExpressionNodeUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/spel/ExpressionNodeUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,7 +36,6 @@ /** * Unit tests for {@link ExpressionNode}. * - * @see DATAMONGO-774 * @author Oliver Gierke */ @RunWith(MockitoJUnitRunner.class) @@ -56,7 +55,7 @@ public void setUp() { this.operators = Arrays.asList(minus, plus, divide, multiply); } - @Test + @Test // DATAMONGO-774 public void createsOperatorNodeForOperations() { for (SpelNode operator : operators) { diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/gridfs/GridFsTemplateIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/gridfs/GridFsTemplateIntegrationTests.java index 6803069c6e..1f2fed1d04 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/gridfs/GridFsTemplateIntegrationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/gridfs/GridFsTemplateIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2014 the original author or authors. + * Copyright 2011-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -61,10 +61,7 @@ public void setUp() { operations.delete(null); } - /** - * @see DATAMONGO-6 - */ - @Test + @Test // DATAMONGO-6 public void storesAndFindsSimpleDocument() throws IOException { GridFSFile reference = operations.store(resource.getInputStream(), "foo.xml"); @@ -74,10 +71,7 @@ public void storesAndFindsSimpleDocument() throws IOException { assertSame(result.get(0), reference); } - /** - * @see DATAMONGO-6 - */ - @Test + @Test // DATAMONGO-6 public void writesMetadataCorrectly() throws IOException { DBObject metadata = new BasicDBObject("key", "value"); @@ -88,10 +82,7 @@ public void writesMetadataCorrectly() throws IOException { assertSame(result.get(0), reference); } - /** - * @see DATAMONGO-6 - */ - @Test + @Test // DATAMONGO-6 public void marshalsComplexMetadata() throws IOException { Metadata metadata = new Metadata(); @@ -103,10 +94,7 @@ public void marshalsComplexMetadata() throws IOException { assertSame(result.get(0), reference); } - /** - * @see DATAMONGO-6 - */ - @Test + @Test // DATAMONGO-6 public void findsFilesByResourcePattern() throws IOException { GridFSFile reference = operations.store(resource.getInputStream(), "foo.xml"); @@ -118,10 +106,7 @@ public void findsFilesByResourcePattern() throws IOException { assertThat(resources[0].getContentType(), is(reference.getContentType())); } - /** - * @see DATAMONGO-6 - */ - @Test + @Test // DATAMONGO-6 public void findsFilesByResourceLocation() throws IOException { GridFSFile reference = operations.store(resource.getInputStream(), "foo.xml"); @@ -133,10 +118,7 @@ public void findsFilesByResourceLocation() throws IOException { assertThat(resources[0].getContentType(), is(reference.getContentType())); } - /** - * @see DATAMONGO-503 - */ - @Test + @Test // DATAMONGO-503 public void storesContentType() throws IOException { GridFSFile reference = operations.store(resource.getInputStream(), "foo2.xml", "application/xml"); @@ -146,10 +128,7 @@ public void storesContentType() throws IOException { assertSame(result.get(0), reference); } - /** - * @see DATAMONGO-534 - */ - @Test + @Test // DATAMONGO-534 public void considersSortWhenQueryingFiles() throws IOException { GridFSFile second = operations.store(resource.getInputStream(), "foo.xml"); @@ -165,10 +144,7 @@ public void considersSortWhenQueryingFiles() throws IOException { assertSame(result.get(2), third); } - /** - * @see DATAMONGO-534 - */ - @Test + @Test // DATAMONGO-534 public void queryingWithNullQueryReturnsAllFiles() throws IOException { GridFSFile reference = operations.store(resource.getInputStream(), "foo.xml"); @@ -179,18 +155,12 @@ public void queryingWithNullQueryReturnsAllFiles() throws IOException { assertSame(result.get(0), reference); } - /** - * @see DATAMONGO-813 - */ - @Test + @Test // DATAMONGO-813 public void getResourceShouldReturnNullForNonExistingResource() { assertThat(operations.getResource("doesnotexist"), is(nullValue())); } - /** - * @see DATAMONGO-809 - */ - @Test + @Test // DATAMONGO-809 public void storesAndFindsSimpleDocumentWithMetadataDBObject() throws IOException { DBObject metadata = new BasicDBObject("key", "value"); @@ -202,10 +172,7 @@ public void storesAndFindsSimpleDocumentWithMetadataDBObject() throws IOExceptio assertSame(result.get(0), reference); } - /** - * @see DATAMONGO-809 - */ - @Test + @Test // DATAMONGO-809 public void storesAndFindsSimpleDocumentWithMetadataObject() throws IOException { Metadata metadata = new Metadata(); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/monitor/MongoMonitorIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/monitor/MongoMonitorIntegrationTests.java index da9290058f..d9d6e66d6c 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/monitor/MongoMonitorIntegrationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/monitor/MongoMonitorIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,63 +15,57 @@ */ package org.springframework.data.mongodb.monitor; -import static org.hamcrest.Matchers.*; -import static org.junit.Assert.*; - -import java.net.UnknownHostException; - +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; + +import java.net.UnknownHostException; + import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; -import com.mongodb.Mongo; - +import com.mongodb.Mongo; + /** * This test class assumes that you are already running the MongoDB server. * * @author Mark Pollack - * @author Thomas Darimont + * @author Thomas Darimont + * @author Mark Paluch */ @RunWith(SpringJUnit4ClassRunner.class) -@ContextConfiguration("classpath:infrastructure.xml") +@ContextConfiguration("classpath:infrastructure.xml") public class MongoMonitorIntegrationTests { - @Autowired Mongo mongo; + @Autowired Mongo mongo; @Test public void serverInfo() { ServerInfo serverInfo = new ServerInfo(mongo); serverInfo.getVersion(); - Assert.isTrue(StringUtils.hasText("1.")); } - /** - * @throws UnknownHostException - * @see DATAMONGO-685 - */ - @Test - public void getHostNameShouldReturnServerNameReportedByMongo() throws UnknownHostException { - - ServerInfo serverInfo = new ServerInfo(mongo); - - String hostName = null; - try { - hostName = serverInfo.getHostName(); - } catch (UnknownHostException e) { - throw e; - } - - assertThat(hostName, is(notNullValue())); - assertThat(hostName, is("127.0.0.1")); - } - + @Test // DATAMONGO-685 + public void getHostNameShouldReturnServerNameReportedByMongo() throws UnknownHostException { + + ServerInfo serverInfo = new ServerInfo(mongo); + + String hostName = null; + try { + hostName = serverInfo.getHostName(); + } catch (UnknownHostException e) { + throw e; + } + + assertThat(hostName, is(notNullValue())); + assertThat(hostName, is("127.0.0.1")); + } + @Test public void operationCounters() { OperationCounters operationCounters = new OperationCounters(mongo); operationCounters.getInsertCount(); } -} +} diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/performance/PerformanceTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/performance/PerformanceTests.java index 9155721216..adf5325ee6 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/performance/PerformanceTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/performance/PerformanceTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2016 the original author or authors. + * Copyright 2012-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,7 +17,6 @@ import static org.springframework.data.mongodb.core.query.Criteria.*; import static org.springframework.data.mongodb.core.query.Query.*; -import static org.springframework.util.Assert.*; import java.text.DecimalFormat; import java.util.ArrayList; @@ -47,6 +46,7 @@ import org.springframework.data.mongodb.core.query.Query; import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.data.mongodb.repository.support.MongoRepositoryFactoryBean; +import org.springframework.util.Assert; import org.springframework.util.StopWatch; import org.springframework.util.StringUtils; @@ -66,6 +66,7 @@ * * @author Oliver Gierke * @author Christoph Strobl + * @author Mark Paluch */ public class PerformanceTests { @@ -97,9 +98,9 @@ public void setUp() throws Exception { this.converter = new MappingMongoConverter(new DefaultDbRefResolver(mongoDbFactory), context); this.operations = new MongoTemplate(new SimpleMongoDbFactory(this.mongo, DATABASE_NAME), converter); - MongoRepositoryFactoryBean factory = new MongoRepositoryFactoryBean(); + MongoRepositoryFactoryBean factory = new MongoRepositoryFactoryBean( + PersonRepository.class); factory.setMongoOperations(operations); - factory.setRepositoryInterface(PersonRepository.class); factory.afterPropertiesSet(); this.repository = factory.getObject(); @@ -125,8 +126,8 @@ public void doWithWriteConcern(String constantName, WriteConcern concern) { @Test public void plainConversion() throws InterruptedException { - Statistics statistics = new Statistics("Plain conversion of " + NUMBER_OF_PERSONS * 100 - + " persons - After %s iterations"); + Statistics statistics = new Statistics( + "Plain conversion of " + NUMBER_OF_PERSONS * 100 + " persons - After %s iterations"); List dbObjects = getPersonDBObjects(NUMBER_OF_PERSONS * 100); @@ -622,7 +623,7 @@ public DBObject toDBObject() { private static List pickRandomNumerOfItemsFrom(List source) { - isTrue(!source.isEmpty()); + Assert.isTrue(!source.isEmpty(), "Source must not be empty!"); Random random = new Random(); int numberOfItems = random.nextInt(source.size()); @@ -836,14 +837,14 @@ public String print(double referenceAverage, double referenceMedian) { String.format(" %s%%", DEVIATION_FORMAT.format(getMediaDeviationFrom(referenceMedian)))) + '\n'; } - /* + /* * (non-Javadoc) * @see java.lang.Object#toString() */ @Override public String toString() { - return times.isEmpty() ? "" : String.format("%s, %s: %s", api, mode, - StringUtils.collectionToCommaDelimitedString(times)) + '\n'; + return times.isEmpty() ? "" + : String.format("%s, %s: %s", api, mode, StringUtils.collectionToCommaDelimitedString(times)) + '\n'; } } @@ -895,7 +896,7 @@ public String print() { return builder.toString(); } - /* + /* * (non-Javadoc) * @see java.lang.Object#toString() */ diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java index 84bad2d14c..319f2aa47d 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2016 the original author or authors. + * Copyright 2011-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,7 +30,9 @@ import org.hamcrest.Matchers; import org.junit.Before; import org.junit.Ignore; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DuplicateKeyException; @@ -51,6 +53,7 @@ import org.springframework.data.geo.Point; import org.springframework.data.geo.Polygon; import org.springframework.data.mongodb.core.MongoOperations; +import org.springframework.data.mongodb.core.geo.GeoJsonPoint; import org.springframework.data.mongodb.core.query.BasicQuery; import org.springframework.data.mongodb.repository.Person.Sex; import org.springframework.data.mongodb.repository.SampleEvaluationContextExtension.SampleSecurityContextHolder; @@ -65,10 +68,14 @@ * @author Thomas Darimont * @author Christoph Strobl * @author Mark Paluch + * @author Fırat KÜÇÜK + * @author Edward Prentice */ @RunWith(SpringJUnit4ClassRunner.class) public abstract class AbstractPersonRepositoryIntegrationTests { + public @Rule ExpectedException expectedException = ExpectedException.none(); + @Autowired protected PersonRepository repository; @Autowired MongoOperations operations; @@ -168,6 +175,15 @@ public void findsPersonsByFirstnameLike() throws Exception { assertThat(result, hasItem(boyd)); } + @Test // DATAMONGO-1608 + public void findByFirstnameLikeWithNull() { + + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("property 'firstname'"); + + repository.findByFirstnameLike(null); + } + @Test public void findsPagedPersons() throws Exception { @@ -272,6 +288,18 @@ public void findsPeopleByLocationNear() { assertThat(result, hasItem(dave)); } + @Test // DATAMONGO-1588 + public void findsPeopleByLocationNearUsingGeoJsonType() { + + GeoJsonPoint point = new GeoJsonPoint(-73.99171, 40.738868); + dave.setLocation(point); + repository.save(dave); + + List result = repository.findByLocationNear(point); + assertThat(result.size(), is(1)); + assertThat(result, hasItem(dave)); + } + @Test public void findsPeopleByLocationWithinCircle() { Point point = new Point(-73.99171, 40.738868); @@ -325,10 +353,7 @@ public void findsPagedPeopleByPredicate() throws Exception { assertThat(page, hasItems(carter, stefan)); } - /** - * @see DATADOC-136 - */ - @Test + @Test // DATADOC-136 public void findsPeopleBySexCorrectly() { List females = repository.findBySex(Sex.FEMALE); @@ -336,10 +361,7 @@ public void findsPeopleBySexCorrectly() { assertThat(females.get(0), is(alicia)); } - /** - * @see DATAMONGO-446 - */ - @Test + @Test // DATAMONGO-446 public void findsPeopleBySexPaginated() { List males = repository.findBySex(Sex.MALE, new PageRequest(0, 2)); @@ -353,10 +375,7 @@ public void findsPeopleByNamedQuery() { assertThat(result, hasItem(dave)); } - /** - * @see DATADOC-190 - */ - @Test + @Test // DATADOC-190 public void existsWorksCorrectly() { assertThat(repository.exists(dave.getId()), is(true)); } @@ -372,10 +391,7 @@ public void rejectsDuplicateEmailAddressOnSave() { repository.save(daveSyer); } - /** - * @see DATADOC-236 - */ - @Test + @Test // DATADOC-236 public void findsPeopleByLastnameAndOrdersCorrectly() { List result = repository.findByLastnameOrderByFirstnameAsc("Matthews"); assertThat(result.size(), is(2)); @@ -383,10 +399,7 @@ public void findsPeopleByLastnameAndOrdersCorrectly() { assertThat(result.get(1), is(oliver)); } - /** - * @see DATADOC-236 - */ - @Test + @Test // DATADOC-236 public void appliesStaticAndDynamicSorting() { List result = repository.findByFirstnameLikeOrderByLastnameAsc("*e*", new Sort("age")); assertThat(result.size(), is(5)); @@ -424,10 +437,7 @@ public void executesGeoPageQueryForResultsCorrectly() { assertThat(results.getAverageDistance().getMetric(), is((Metric) Metrics.KILOMETERS)); } - /** - * @see DATAMONGO-323 - */ - @Test + @Test // DATAMONGO-323 public void considersSortForAnnotatedQuery() { List result = repository.findByAgeLessThan(60, new Sort("firstname")); @@ -442,10 +452,7 @@ public void considersSortForAnnotatedQuery() { assertThat(result.get(6), is(stefan)); } - /** - * @see DATAMONGO-347 - */ - @Test + @Test // DATAMONGO-347 public void executesQueryWithDBRefReferenceCorrectly() { operations.remove(new org.springframework.data.mongodb.core.query.Query(), User.class); @@ -463,10 +470,7 @@ public void executesQueryWithDBRefReferenceCorrectly() { assertThat(result, hasItem(dave)); } - /** - * @see DATAMONGO-425 - */ - @Test + @Test // DATAMONGO-425 public void bindsDateParameterForLessThanPredicateCorrectly() { List result = repository.findByCreatedAtLessThan(boyd.createdAt); @@ -474,10 +478,7 @@ public void bindsDateParameterForLessThanPredicateCorrectly() { assertThat(result, hasItems(dave, oliver, carter)); } - /** - * @see DATAMONGO-425 - */ - @Test + @Test // DATAMONGO-425 public void bindsDateParameterForGreaterThanPredicateCorrectly() { List result = repository.findByCreatedAtGreaterThan(carter.createdAt); @@ -485,10 +486,7 @@ public void bindsDateParameterForGreaterThanPredicateCorrectly() { assertThat(result, hasItems(boyd, stefan, leroi, alicia)); } - /** - * @see DATAMONGO-427 - */ - @Test + @Test // DATAMONGO-427 public void bindsDateParameterToBeforePredicateCorrectly() { List result = repository.findByCreatedAtBefore(boyd.createdAt); @@ -496,10 +494,7 @@ public void bindsDateParameterToBeforePredicateCorrectly() { assertThat(result, hasItems(dave, oliver, carter)); } - /** - * @see DATAMONGO-427 - */ - @Test + @Test // DATAMONGO-427 public void bindsDateParameterForAfterPredicateCorrectly() { List result = repository.findByCreatedAtAfter(carter.createdAt); @@ -507,20 +502,14 @@ public void bindsDateParameterForAfterPredicateCorrectly() { assertThat(result, hasItems(boyd, stefan, leroi, alicia)); } - /** - * @see DATAMONGO-425 - */ - @Test + @Test // DATAMONGO-425 public void bindsDateParameterForManuallyDefinedQueryCorrectly() { List result = repository.findByCreatedAtLessThanManually(boyd.createdAt); assertThat(result.isEmpty(), is(false)); } - /** - * @see DATAMONGO-472 - */ - @Test + @Test // DATAMONGO-472 public void findsPeopleUsingNotPredicate() { List result = repository.findByLastnameNot("Matthews"); @@ -528,10 +517,7 @@ public void findsPeopleUsingNotPredicate() { assertThat(result, hasSize(5)); } - /** - * @see DATAMONGO-521 - */ - @Test + @Test // DATAMONGO-521 public void executesAndQueryCorrectly() { List result = repository.findByFirstnameAndLastname("Dave", "Matthews"); @@ -545,10 +531,7 @@ public void executesAndQueryCorrectly() { assertThat(result, hasItem(oliver)); } - /** - * @see DATAMONGO-600 - */ - @Test + @Test // DATAMONGO-600 public void readsDocumentsWithNestedPolymorphismCorrectly() { UsernameAndPassword usernameAndPassword = new UsernameAndPassword(); @@ -564,34 +547,34 @@ public void readsDocumentsWithNestedPolymorphismCorrectly() { assertThat(result, hasItem(dave)); } - /** - * @see DATAMONGO-636 - */ - @Test + @Test // DATAMONGO-636 public void executesDerivedCountProjection() { assertThat(repository.countByLastname("Matthews"), is(2L)); } - /** - * @see DATAMONGO-636 - */ - @Test + @Test // DATAMONGO-636 public void executesDerivedCountProjectionToInt() { assertThat(repository.countByFirstname("Oliver August"), is(1)); } - /** - * @see DATAMONGO-636 - */ - @Test + @Test // DATAMONGO-636 public void executesAnnotatedCountProjection() { assertThat(repository.someCountQuery("Matthews"), is(2L)); } - /** - * @see DATAMONGO-701 - */ - @Test + @Test // DATAMONGO-1454 + public void executesDerivedExistsProjectionToBoolean() { + + assertThat(repository.existsByFirstname("Oliver August"), is(true)); + assertThat(repository.existsByFirstname("Hans Peter"), is(false)); + } + + @Test // DATAMONGO-1454 + public void executesAnnotatedExistProjection() { + assertThat(repository.someExistQuery("Matthews"), is(true)); + } + + @Test // DATAMONGO-701 public void executesDerivedStartsWithQueryCorrectly() { List result = repository.findByLastnameStartsWith("Matt"); @@ -599,10 +582,7 @@ public void executesDerivedStartsWithQueryCorrectly() { assertThat(result, hasItems(dave, oliver)); } - /** - * @see DATAMONGO-701 - */ - @Test + @Test // DATAMONGO-701 public void executesDerivedEndsWithQueryCorrectly() { List result = repository.findByLastnameEndsWith("thews"); @@ -610,10 +590,7 @@ public void executesDerivedEndsWithQueryCorrectly() { assertThat(result, hasItems(dave, oliver)); } - /** - * @see DATAMONGO-445 - */ - @Test + @Test // DATAMONGO-445 public void executesGeoPageQueryForWithPageRequestForPageInBetween() { Point farAway = new Point(-73.9, 40.7); @@ -638,10 +615,7 @@ public void executesGeoPageQueryForWithPageRequestForPageInBetween() { assertThat(results.getAverageDistance().getNormalizedValue(), is(0.0)); } - /** - * @see DATAMONGO-445 - */ - @Test + @Test // DATAMONGO-445 public void executesGeoPageQueryForWithPageRequestForPageAtTheEnd() { Point point = new Point(-73.99171, 40.738868); @@ -661,10 +635,7 @@ public void executesGeoPageQueryForWithPageRequestForPageAtTheEnd() { assertThat(results.getAverageDistance().getMetric(), is((Metric) Metrics.KILOMETERS)); } - /** - * @see DATAMONGO-445 - */ - @Test + @Test // DATAMONGO-445 public void executesGeoPageQueryForWithPageRequestForJustOneElement() { Point point = new Point(-73.99171, 40.738868); @@ -681,10 +652,7 @@ public void executesGeoPageQueryForWithPageRequestForJustOneElement() { assertThat(results.getAverageDistance().getMetric(), is((Metric) Metrics.KILOMETERS)); } - /** - * @see DATAMONGO-445 - */ - @Test + @Test // DATAMONGO-445 public void executesGeoPageQueryForWithPageRequestForJustOneElementEmptyPage() { dave.setLocation(new Point(-73.99171, 40.738868)); @@ -700,10 +668,16 @@ public void executesGeoPageQueryForWithPageRequestForJustOneElementEmptyPage() { assertThat(results.getAverageDistance().getMetric(), is((Metric) Metrics.KILOMETERS)); } - /** - * @see DATAMONGO-770 - */ - @Test + @Test // DATAMONGO-1608 + public void findByFirstNameIgnoreCaseWithNull() { + + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("property 'firstname'"); + + repository.findByFirstnameIgnoreCase(null); + } + + @Test // DATAMONGO-770 public void findByFirstNameIgnoreCase() { List result = repository.findByFirstnameIgnoreCase("dave"); @@ -712,10 +686,7 @@ public void findByFirstNameIgnoreCase() { assertThat(result.get(0), is(dave)); } - /** - * @see DATAMONGO-770 - */ - @Test + @Test // DATAMONGO-770 public void findByFirstnameNotIgnoreCase() { List result = repository.findByFirstnameNotIgnoreCase("dave"); @@ -724,10 +695,7 @@ public void findByFirstnameNotIgnoreCase() { assertThat(result, not(hasItem(dave))); } - /** - * @see DATAMONGO-770 - */ - @Test + @Test // DATAMONGO-770 public void findByFirstnameStartingWithIgnoreCase() { List result = repository.findByFirstnameStartingWithIgnoreCase("da"); @@ -735,10 +703,7 @@ public void findByFirstnameStartingWithIgnoreCase() { assertThat(result.get(0), is(dave)); } - /** - * @see DATAMONGO-770 - */ - @Test + @Test // DATAMONGO-770 public void findByFirstnameEndingWithIgnoreCase() { List result = repository.findByFirstnameEndingWithIgnoreCase("VE"); @@ -746,10 +711,7 @@ public void findByFirstnameEndingWithIgnoreCase() { assertThat(result.get(0), is(dave)); } - /** - * @see DATAMONGO-770 - */ - @Test + @Test // DATAMONGO-770 public void findByFirstnameContainingIgnoreCase() { List result = repository.findByFirstnameContainingIgnoreCase("AV"); @@ -757,10 +719,7 @@ public void findByFirstnameContainingIgnoreCase() { assertThat(result.get(0), is(dave)); } - /** - * @see DATAMONGO-870 - */ - @Test + @Test // DATAMONGO-870 public void findsSliceOfPersons() { Slice result = repository.findByAgeGreaterThan(40, new PageRequest(0, 2, Direction.DESC, "firstname")); @@ -768,10 +727,7 @@ public void findsSliceOfPersons() { assertThat(result.hasNext(), is(true)); } - /** - * @see DATAMONGO-871 - */ - @Test + @Test // DATAMONGO-871 public void findsPersonsByFirstnameAsArray() { Person[] result = repository.findByThePersonsFirstnameAsArray("Leroi"); @@ -780,10 +736,7 @@ public void findsPersonsByFirstnameAsArray() { assertThat(result, is(arrayContaining(leroi))); } - /** - * @see DATAMONGO-821 - */ - @Test + @Test // DATAMONGO-821 public void findUsingAnnotatedQueryOnDBRef() { operations.remove(new org.springframework.data.mongodb.core.query.Query(), User.class); @@ -801,10 +754,7 @@ public void findUsingAnnotatedQueryOnDBRef() { assertThat(result.getContent().get(0), is(alicia)); } - /** - * @see DATAMONGO-566 - */ - @Test + @Test // DATAMONGO-566 public void deleteByShouldReturnListOfDeletedElementsWhenRetunTypeIsCollectionLike() { List result = repository.deleteByLastname("Beauford"); @@ -813,10 +763,7 @@ public void deleteByShouldReturnListOfDeletedElementsWhenRetunTypeIsCollectionLi assertThat(result, hasSize(1)); } - /** - * @see DATAMONGO-566 - */ - @Test + @Test // DATAMONGO-566 public void deleteByShouldRemoveElementsMatchingDerivedQuery() { repository.deleteByLastname("Beauford"); @@ -824,34 +771,22 @@ public void deleteByShouldRemoveElementsMatchingDerivedQuery() { assertThat(operations.count(new BasicQuery("{'lastname':'Beauford'}"), Person.class), is(0L)); } - /** - * @see DATAMONGO-566 - */ - @Test + @Test // DATAMONGO-566 public void deleteByShouldReturnNumberOfDocumentsRemovedIfReturnTypeIsLong() { assertThat(repository.deletePersonByLastname("Beauford"), is(1L)); } - /** - * @see DATAMONGO-566 - */ - @Test + @Test // DATAMONGO-566 public void deleteByShouldReturnZeroInCaseNoDocumentHasBeenRemovedAndReturnTypeIsNumber() { assertThat(repository.deletePersonByLastname("dorfuaeB"), is(0L)); } - /** - * @see DATAMONGO-566 - */ - @Test + @Test // DATAMONGO-566 public void deleteByShouldReturnEmptyListInCaseNoDocumentHasBeenRemovedAndReturnTypeIsCollectionLike() { assertThat(repository.deleteByLastname("dorfuaeB"), empty()); } - /** - * @see DATAMONGO-566 - */ - @Test + @Test // DATAMONGO-566 public void deleteByUsingAnnotatedQueryShouldReturnListOfDeletedElementsWhenRetunTypeIsCollectionLike() { List result = repository.removeByLastnameUsingAnnotatedQuery("Beauford"); @@ -860,10 +795,7 @@ public void deleteByUsingAnnotatedQueryShouldReturnListOfDeletedElementsWhenRetu assertThat(result, hasSize(1)); } - /** - * @see DATAMONGO-566 - */ - @Test + @Test // DATAMONGO-566 public void deleteByUsingAnnotatedQueryShouldRemoveElementsMatchingDerivedQuery() { repository.removeByLastnameUsingAnnotatedQuery("Beauford"); @@ -871,18 +803,12 @@ public void deleteByUsingAnnotatedQueryShouldRemoveElementsMatchingDerivedQuery( assertThat(operations.count(new BasicQuery("{'lastname':'Beauford'}"), Person.class), is(0L)); } - /** - * @see DATAMONGO-566 - */ - @Test + @Test // DATAMONGO-566 public void deleteByUsingAnnotatedQueryShouldReturnNumberOfDocumentsRemovedIfReturnTypeIsLong() { assertThat(repository.removePersonByLastnameUsingAnnotatedQuery("Beauford"), is(1L)); } - /** - * @see DATAMONGO-893 - */ - @Test + @Test // DATAMONGO-893 public void findByNestedPropertyInCollectionShouldFindMatchingDocuments() { Person p = new Person("Mary", "Poppins"); @@ -896,10 +822,7 @@ public void findByNestedPropertyInCollectionShouldFindMatchingDocuments() { assertThat(result.getContent(), hasSize(1)); } - /** - * @see DATAMONGO-745 - */ - @Test + @Test // DATAMONGO-745 public void findByCustomQueryFirstnamesInListAndLastname() { repository.save(new Person("foo", "bar")); @@ -915,10 +838,7 @@ public void findByCustomQueryFirstnamesInListAndLastname() { assertThat(result.getTotalElements(), is(3L)); } - /** - * @see DATAMONGO-745 - */ - @Test + @Test // DATAMONGO-745 public void findByCustomQueryLastnameAndStreetInList() { repository.save(new Person("foo", "bar").withAddress(new Address("street1", "1", "SB"))); @@ -935,10 +855,7 @@ public void findByCustomQueryLastnameAndStreetInList() { } - /** - * @see DATAMONGO-950 - */ - @Test + @Test // DATAMONGO-950 public void shouldLimitCollectionQueryToMaxResultsWhenPresent() { repository.save(Arrays.asList(new Person("Bob-1", "Dylan"), new Person("Bob-2", "Dylan"), @@ -947,10 +864,7 @@ public void shouldLimitCollectionQueryToMaxResultsWhenPresent() { assertThat(result.size(), is(3)); } - /** - * @see DATAMONGO-950, DATAMONGO-1464 - */ - @Test + @Test // DATAMONGO-950, DATAMONGO-1464 public void shouldNotLimitPagedQueryWhenPageRequestWithinBounds() { repository.save(Arrays.asList(new Person("Bob-1", "Dylan"), new Person("Bob-2", "Dylan"), @@ -960,10 +874,7 @@ public void shouldNotLimitPagedQueryWhenPageRequestWithinBounds() { assertThat(result.getTotalElements(), is(3L)); } - /** - * @see DATAMONGO-950 - */ - @Test + @Test // DATAMONGO-950 public void shouldLimitPagedQueryWhenPageRequestExceedsUpperBoundary() { repository.save(Arrays.asList(new Person("Bob-1", "Dylan"), new Person("Bob-2", "Dylan"), @@ -972,10 +883,7 @@ public void shouldLimitPagedQueryWhenPageRequestExceedsUpperBoundary() { assertThat(result.getContent().size(), is(1)); } - /** - * @see DATAMONGO-950, DATAMONGO-1464 - */ - @Test + @Test // DATAMONGO-950, DATAMONGO-1464 public void shouldReturnEmptyWhenPageRequestedPageIsTotallyOutOfScopeForLimit() { repository.save(Arrays.asList(new Person("Bob-1", "Dylan"), new Person("Bob-2", "Dylan"), @@ -985,10 +893,7 @@ public void shouldReturnEmptyWhenPageRequestedPageIsTotallyOutOfScopeForLimit() assertThat(result.getTotalElements(), is(3L)); } - /** - * @see DATAMONGO-996, DATAMONGO-950, DATAMONGO-1464 - */ - @Test + @Test // DATAMONGO-996, DATAMONGO-950, DATAMONGO-1464 public void gettingNonFirstPageWorksWithoutLimitBeingSet() { Page slice = repository.findByLastnameLike("Matthews", new PageRequest(1, 1)); @@ -999,13 +904,8 @@ public void gettingNonFirstPageWorksWithoutLimitBeingSet() { assertThat(slice.getTotalElements(), is(2L)); } - /** - * Ignored for now as this requires Querydsl 3.4.1 to succeed. - * - * @see DATAMONGO-972 - */ - @Test - @Ignore + @Test // DATAMONGO-972 + @Ignore("Ignored for now as this requires Querydsl 3.4.1 to succeed.") public void shouldExecuteFindOnDbRefCorrectly() { operations.remove(new org.springframework.data.mongodb.core.query.Query(), User.class); @@ -1021,18 +921,12 @@ public void shouldExecuteFindOnDbRefCorrectly() { assertThat(repository.findOne(QPerson.person.creator.eq(user)), is(dave)); } - /** - * @see DATAMONGO-969 - */ - @Test + @Test // DATAMONGO-969 public void shouldFindPersonsWhenUsingQueryDslPerdicatedOnIdProperty() { assertThat(repository.findAll(person.id.in(Arrays.asList(dave.id, carter.id))), containsInAnyOrder(dave, carter)); } - /** - * @see DATAMONGO-1030 - */ - @Test + @Test // DATAMONGO-1030 public void executesSingleEntityQueryWithProjectionCorrectly() { PersonSummary result = repository.findSummaryByLastname("Beauford"); @@ -1042,10 +936,7 @@ public void executesSingleEntityQueryWithProjectionCorrectly() { assertThat(result.lastname, is("Beauford")); } - /** - * @see DATAMONGO-1057 - */ - @Test + @Test // DATAMONGO-1057 public void sliceShouldTraverseElementsWithoutSkippingOnes() { repository.deleteAll(); @@ -1065,10 +956,7 @@ public void sliceShouldTraverseElementsWithoutSkippingOnes() { assertThat(slice, contains(persons.subList(20, 40).toArray())); } - /** - * @see DATAMONGO-1072 - */ - @Test + @Test // DATAMONGO-1072 public void shouldBindPlaceholdersUsedAsKeysCorrectly() { List persons = repository.findByKeyValue("firstname", alicia.getFirstname()); @@ -1077,10 +965,7 @@ public void shouldBindPlaceholdersUsedAsKeysCorrectly() { assertThat(persons, hasItem(alicia)); } - /** - * @see DATAMONGO-1105 - */ - @Test + @Test // DATAMONGO-1105 public void returnsOrderedResultsForQuerydslOrderSpecifier() { Iterable result = repository.findAll(person.firstname.asc()); @@ -1088,10 +973,7 @@ public void returnsOrderedResultsForQuerydslOrderSpecifier() { assertThat(result, contains(alicia, boyd, carter, dave, leroi, oliver, stefan)); } - /** - * @see DATAMONGO-1085 - */ - @Test + @Test // DATAMONGO-1085 public void shouldSupportSortingByQueryDslOrderSpecifier() { repository.deleteAll(); @@ -1114,10 +996,7 @@ public void shouldSupportSortingByQueryDslOrderSpecifier() { assertThat(result.iterator().next().getFirstname(), is(persons.get(2).getFirstname())); } - /** - * @see DATAMONGO-1085 - */ - @Test + @Test // DATAMONGO-1085 public void shouldSupportSortingWithQSortByQueryDslOrderSpecifier() throws Exception { repository.deleteAll(); @@ -1139,10 +1018,7 @@ public void shouldSupportSortingWithQSortByQueryDslOrderSpecifier() throws Excep assertThat(result.iterator().next().getFirstname(), is("Siggi 2")); } - /** - * @see DATAMONGO-1085 - */ - @Test + @Test // DATAMONGO-1085 public void shouldSupportSortingWithQSort() throws Exception { repository.deleteAll(); @@ -1163,10 +1039,7 @@ public void shouldSupportSortingWithQSort() throws Exception { assertThat(result.iterator().next().getFirstname(), is("Siggi 2")); } - /** - * @see DATAMONGO-1165 - */ - @Test + @Test // DATAMONGO-1165 public void shouldAllowReturningJava8StreamInCustomQuery() throws Exception { Stream result = repository.findByCustomQueryWithStreamingCursorByFirstnames(Arrays.asList("Dave")); @@ -1178,10 +1051,7 @@ public void shouldAllowReturningJava8StreamInCustomQuery() throws Exception { } } - /** - * @see DATAMONGO-1110 - */ - @Test + @Test // DATAMONGO-1110 public void executesGeoNearQueryForResultsCorrectlyWhenGivenMinAndMaxDistance() { Point point = new Point(-73.99171, 40.738868); @@ -1194,10 +1064,7 @@ public void executesGeoNearQueryForResultsCorrectlyWhenGivenMinAndMaxDistance() assertThat(results.getContent().isEmpty(), is(false)); } - /** - * @see DATAMONGO-990 - */ - @Test + @Test // DATAMONGO-990 public void shouldFindByFirstnameForSpELExpressionWithParameterIndexOnly() { List users = repository.findWithSpelByFirstnameForSpELExpressionWithParameterIndexOnly("Dave"); @@ -1206,10 +1073,7 @@ public void shouldFindByFirstnameForSpELExpressionWithParameterIndexOnly() { assertThat(users.get(0), is(dave)); } - /** - * @see DATAMONGO-990 - */ - @Test + @Test // DATAMONGO-990 public void shouldFindByFirstnameAndCurrentUserWithCustomQuery() { SampleSecurityContextHolder.getCurrent().setPrincipal(dave); @@ -1219,10 +1083,7 @@ public void shouldFindByFirstnameAndCurrentUserWithCustomQuery() { assertThat(users.get(0), is(dave)); } - /** - * @see DATAMONGO-990 - */ - @Test + @Test // DATAMONGO-990 public void shouldFindByFirstnameForSpELExpressionWithParameterVariableOnly() { List users = repository.findWithSpelByFirstnameForSpELExpressionWithParameterVariableOnly("Dave"); @@ -1231,10 +1092,7 @@ public void shouldFindByFirstnameForSpELExpressionWithParameterVariableOnly() { assertThat(users.get(0), is(dave)); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void findByExampleShouldResolveStuffCorrectly() { Person sample = new Person(); @@ -1249,10 +1107,7 @@ public void findByExampleShouldResolveStuffCorrectly() { assertThat(result.getNumberOfElements(), is(2)); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void findAllByExampleShouldResolveStuffCorrectly() { Person sample = new Person(); @@ -1267,10 +1122,7 @@ public void findAllByExampleShouldResolveStuffCorrectly() { assertThat(result.size(), is(2)); } - /** - * @see DATAMONGO-1425 - */ - @Test + @Test // DATAMONGO-1425 public void findsPersonsByFirstnameNotContains() throws Exception { List result = repository.findByFirstnameNotContains("Boyd"); @@ -1278,10 +1130,7 @@ public void findsPersonsByFirstnameNotContains() throws Exception { assertThat(result, not(hasItem(boyd))); } - /** - * @see DATAMONGO-1425 - */ - @Test + @Test // DATAMONGO-1425 public void findBySkillsContains() throws Exception { List result = repository.findBySkillsContains(Arrays.asList("Drums")); @@ -1289,10 +1138,7 @@ public void findBySkillsContains() throws Exception { assertThat(result, hasItem(carter)); } - /** - * @see DATAMONGO-1425 - */ - @Test + @Test // DATAMONGO-1425 public void findBySkillsNotContains() throws Exception { List result = repository.findBySkillsNotContains(Arrays.asList("Drums")); @@ -1300,10 +1146,7 @@ public void findBySkillsNotContains() throws Exception { assertThat(result, not(hasItem(carter))); } - /* - * @see DATAMONGO-1424 - */ - @Test + @Test // DATAMONGO-1424 public void findsPersonsByFirstnameNotLike() throws Exception { List result = repository.findByFirstnameNotLike("Bo*"); @@ -1311,4 +1154,16 @@ public void findsPersonsByFirstnameNotLike() throws Exception { assertThat(result, not(hasItem(boyd))); } + @Test // DATAMONGO-1539 + public void countsPersonsByFirstname() { + assertThat(repository.countByThePersonsFirstname("Dave"), is(1L)); + } + + @Test // DATAMONGO-1539 + public void deletesPersonsByFirstname() { + + repository.deleteByThePersonsFirstname("Dave"); + + assertThat(repository.countByThePersonsFirstname("Dave"), is(0L)); + } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/ComplexIdRepositoryIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/ComplexIdRepositoryIntegrationTests.java index c788e4524f..f2ee84c26b 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/ComplexIdRepositoryIntegrationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/ComplexIdRepositoryIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2016 the original author or authors. + * Copyright 2014-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -82,10 +82,7 @@ public void setUp() { userWithId.id = id; } - /** - * @see DATAMONGO-1078 - */ - @Test + @Test // DATAMONGO-1078 public void annotatedFindQueryShouldWorkWhenUsingComplexId() { repo.save(userWithId); @@ -93,10 +90,7 @@ public void annotatedFindQueryShouldWorkWhenUsingComplexId() { assertThat(repo.getUserByComplexId(id), is(userWithId)); } - /** - * @see DATAMONGO-1078 - */ - @Test + @Test // DATAMONGO-1078 public void annotatedFindQueryShouldWorkWhenUsingComplexIdWithinCollection() { repo.save(userWithId); @@ -107,10 +101,7 @@ public void annotatedFindQueryShouldWorkWhenUsingComplexIdWithinCollection() { assertThat(loaded, contains(userWithId)); } - /** - * @see DATAMONGO-1078 - */ - @Test + @Test // DATAMONGO-1078 public void findOneShouldWorkWhenUsingComplexId() { repo.save(userWithId); @@ -118,10 +109,7 @@ public void findOneShouldWorkWhenUsingComplexId() { assertThat(repo.findOne(id), is(userWithId)); } - /** - * @see DATAMONGO-1078 - */ - @Test + @Test // DATAMONGO-1078 public void findAllShouldWorkWhenUsingComplexId() { repo.save(userWithId); @@ -132,10 +120,7 @@ public void findAllShouldWorkWhenUsingComplexId() { assertThat(loaded, contains(userWithId)); } - /** - * @see DATAMONGO-1373 - */ - @Test + @Test // DATAMONGO-1373 public void composedAnnotationFindQueryShouldWorkWhenUsingComplexId() { repo.save(userWithId); @@ -143,10 +128,7 @@ public void composedAnnotationFindQueryShouldWorkWhenUsingComplexId() { assertThat(repo.getUserUsingComposedAnnotationByComplexId(id), is(userWithId)); } - /** - * @see DATAMONGO-1373 - */ - @Test + @Test // DATAMONGO-1373 public void composedAnnotationFindMetaShouldWorkWhenUsingComplexId() { repo.save(userWithId); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/ContactRepositoryIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/ContactRepositoryIntegrationTests.java index 34722d7091..e504e22543 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/ContactRepositoryIntegrationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/ContactRepositoryIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2010-2016 the original author or authors. + * Copyright 2010-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -52,10 +52,7 @@ public void readsAndWritesContactCorrectly() { assertTrue(repository.findOne(result.getId().toString()) instanceof Person); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void findsContactByTypedExample() { Person person = repository.save(new Person("Oliver", "Gierke")); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/MongoRepositoryTextSearchIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/MongoRepositoryTextSearchIntegrationTests.java index 05305d9a90..b3b439f373 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/MongoRepositoryTextSearchIntegrationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/MongoRepositoryTextSearchIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 the original author or authors. + * Copyright 2014-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -88,10 +88,7 @@ public void tearDown() { template.dropCollection(FullTextDocument.class); } - /** - * @see DATAMONGO-973 - */ - @Test + @Test // DATAMONGO-973 public void findAllByTextCriteriaShouldReturnMatchingDocuments() { initRepoWithDefaultDocuments(); @@ -102,10 +99,7 @@ public void findAllByTextCriteriaShouldReturnMatchingDocuments() { assertThat(result, hasItems(PASSENGER_57, DEMOLITION_MAN)); } - /** - * @see DATAMONGO-973 - */ - @Test + @Test // DATAMONGO-973 public void derivedFinderWithTextCriteriaReturnsCorrectResult() { initRepoWithDefaultDocuments(); @@ -123,10 +117,7 @@ public void derivedFinderWithTextCriteriaReturnsCorrectResult() { assertThat(result, hasItems(blade)); } - /** - * @see DATAMONGO-973 - */ - @Test + @Test // DATAMONGO-973 public void findByWithPaginationWorksCorrectlyWhenUsingTextCriteria() { initRepoWithDefaultDocuments(); @@ -140,10 +131,7 @@ public void findByWithPaginationWorksCorrectlyWhenUsingTextCriteria() { assertThat(page.getContent().get(0), equalTo(DEMOLITION_MAN)); } - /** - * @see DATAMONGO-973 - */ - @Test + @Test // DATAMONGO-973 public void findAllByTextCriteriaWithSortWorksCorrectly() { initRepoWithDefaultDocuments(); @@ -157,10 +145,7 @@ public void findAllByTextCriteriaWithSortWorksCorrectly() { assertThat(result.get(0), equalTo(snipes)); } - /** - * @see DATAMONGO-973 - */ - @Test + @Test // DATAMONGO-973 public void findByWithSortByScoreViaPageRequestTriggersSortingCorrectly() { initRepoWithDefaultDocuments(); @@ -174,10 +159,7 @@ public void findByWithSortByScoreViaPageRequestTriggersSortingCorrectly() { assertThat(page.getContent().get(0), equalTo(snipes)); } - /** - * @see DATAMONGO-973 - */ - @Test + @Test // DATAMONGO-973 public void findByWithSortViaPageRequestIgnoresTextScoreWhenSortedByOtherProperty() { initRepoWithDefaultDocuments(); @@ -191,10 +173,7 @@ public void findByWithSortViaPageRequestIgnoresTextScoreWhenSortedByOtherPropert assertThat(page.getContent().get(0), equalTo(PASSENGER_57)); } - /** - * @see DATAMONGO-973 - */ - @Test + @Test // DATAMONGO-973 public void derivedSortForTextScorePropertyWorksCorrectly() { initRepoWithDefaultDocuments(); @@ -206,10 +185,7 @@ public void derivedSortForTextScorePropertyWorksCorrectly() { assertThat(result.get(0), equalTo(snipes)); } - /** - * @see DATAMONGO-973 - */ - @Test + @Test // DATAMONGO-973 public void derivedFinderMethodWithoutFullTextShouldNoCauseTroubleWhenHavingEntityWithTextScoreProperty() { initRepoWithDefaultDocuments(); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepository.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepository.java index f9f2f1fbb5..42beb55ed5 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepository.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepository.java @@ -1,5 +1,5 @@ /* - * Copyright 2010-2016 the original author or authors. + * Copyright 2010-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -42,6 +42,8 @@ * @author Oliver Gierke * @author Thomas Darimont * @author Christoph Strobl + * @author Fırat KÜÇÜK + * @author Mark Paluch */ public interface PersonRepository extends MongoRepository, QueryDslPredicateExecutor { @@ -75,9 +77,7 @@ public interface PersonRepository extends MongoRepository, Query @Query(value = "{ 'firstname' : ?0 }", fields = "{ 'firstname': 1, 'lastname': 1}") List findByThePersonsFirstname(String firstname); - /** - * @see DATAMONGO-871 - */ + // DATAMONGO-871 @Query(value = "{ 'firstname' : ?0 }") Person[] findByThePersonsFirstnameAsArray(String firstname); @@ -185,186 +185,144 @@ public interface PersonRepository extends MongoRepository, Query GeoResults findByLocationNear(Point point, Distance maxDistance); - /** - * @see DATAMONGO-1110 - */ + // DATAMONGO-1110 GeoResults findPersonByLocationNear(Point point, Range distance); GeoPage findByLocationNear(Point point, Distance maxDistance, Pageable pageable); List findByCreator(User user); - /** - * @see DATAMONGO-425 - */ + // DATAMONGO-425 List findByCreatedAtLessThan(Date date); - /** - * @see DATAMONGO-425 - */ + // DATAMONGO-425 List findByCreatedAtGreaterThan(Date date); - /** - * @see DATAMONGO-425 - */ + // DATAMONGO-425 @Query("{ 'createdAt' : { '$lt' : ?0 }}") List findByCreatedAtLessThanManually(Date date); - /** - * @see DATAMONGO-427 - */ + // DATAMONGO-427 List findByCreatedAtBefore(Date date); - /** - * @see DATAMONGO-427 - */ + // DATAMONGO-427 List findByCreatedAtAfter(Date date); - /** - * @see DATAMONGO-472 - * @param lastname - * @return - */ + // DATAMONGO-472 List findByLastnameNot(String lastname); - /** - * @see DATAMONGO-600 - * @param credentials - * @return - */ + // DATAMONGO-600 List findByCredentials(Credentials credentials); - /** - * @see DATAMONGO-636 - */ + // DATAMONGO-636 long countByLastname(String lastname); - /** - * @see DATAMONGO-636 - */ + // DATAMONGO-636 int countByFirstname(String firstname); - /** - * @see DATAMONGO-636 - */ + // DATAMONGO-636 @Query(value = "{ 'lastname' : ?0 }", count = true) long someCountQuery(String lastname); - /** - * @see DATAMONGO-770 - */ + // DATAMONGO-1454 + boolean existsByFirstname(String firstname); + + // DATAMONGO-1454 + @ExistsQuery(value = "{ 'lastname' : ?0 }") + boolean someExistQuery(String lastname); + + // DATAMONGO-770 List findByFirstnameIgnoreCase(String firstName); - /** - * @see DATAMONGO-770 - */ + // DATAMONGO-770 List findByFirstnameNotIgnoreCase(String firstName); - /** - * @see DATAMONGO-770 - */ + // DATAMONGO-770 List findByFirstnameStartingWithIgnoreCase(String firstName); - /** - * @see DATAMONGO-770 - */ + // DATAMONGO-770 List findByFirstnameEndingWithIgnoreCase(String firstName); - /** - * @see DATAMONGO-770 - */ + // DATAMONGO-770 List findByFirstnameContainingIgnoreCase(String firstName); - /** - * @see DATAMONGO-870 - */ + // DATAMONGO-870 Slice findByAgeGreaterThan(int age, Pageable pageable); - /** - * @see DATAMONGO-821 - */ + // DATAMONGO-821 @Query("{ creator : { $exists : true } }") Page findByHavingCreator(Pageable page); - /** - * @see DATAMONGO-566 - */ + // DATAMONGO-566 List deleteByLastname(String lastname); - /** - * @see DATAMONGO-566 - */ + // DATAMONGO-566 Long deletePersonByLastname(String lastname); - /** - * @see DATAMONGO-566 - */ + // DATAMONGO-566 @Query(value = "{ 'lastname' : ?0 }", delete = true) List removeByLastnameUsingAnnotatedQuery(String lastname); - /** - * @see DATAMONGO-566 - */ + // DATAMONGO-566 @Query(value = "{ 'lastname' : ?0 }", delete = true) Long removePersonByLastnameUsingAnnotatedQuery(String lastname); - /** - * @see DATAMONGO-893 - */ + // DATAMONGO-893 Page findByAddressIn(List
        address, Pageable page); - /** - * @see DATAMONGO-745 - */ + // DATAMONGO-745 @Query("{firstname:{$in:?0}, lastname:?1}") Page findByCustomQueryFirstnamesAndLastname(List firstnames, String lastname, Pageable page); - /** - * @see DATAMONGO-745 - */ + // DATAMONGO-745 @Query("{lastname:?0, address.street:{$in:?1}}") Page findByCustomQueryLastnameAndAddressStreetInList(String lastname, List streetNames, Pageable page); - /** - * @see DATAMONGO-950 - */ + // DATAMONGO-950 List findTop3ByLastnameStartingWith(String lastname); - /** - * @see DATAMONGO-950 - */ + // DATAMONGO-950 Page findTop3ByLastnameStartingWith(String lastname, Pageable pageRequest); - /** - * @see DATAMONGO-1030 - */ + // DATAMONGO-1030 PersonSummary findSummaryByLastname(String lastname); @Query("{ ?0 : ?1 }") List findByKeyValue(String key, String value); - /** - * @see DATAMONGO-1165 - */ + // DATAMONGO-1165 @Query("{ firstname : { $in : ?0 }}") Stream findByCustomQueryWithStreamingCursorByFirstnames(List firstnames); - /** - * @see DATAMONGO-990 - */ + // DATAMONGO-990 @Query("{ firstname : ?#{[0]}}") List findWithSpelByFirstnameForSpELExpressionWithParameterIndexOnly(String firstname); - /** - * @see DATAMONGO-990 - */ + // DATAMONGO-990 @Query("{ firstname : ?#{[0]}, email: ?#{principal.email} }") List findWithSpelByFirstnameAndCurrentUserWithCustomQuery(String firstname); - /** - * @see DATAMONGO-990 - */ + // DATAMONGO-990 @Query("{ firstname : :#{#firstname}}") List findWithSpelByFirstnameForSpELExpressionWithParameterVariableOnly(@Param("firstname") String firstname); + + /** + * Returns the count of {@link Person} with the given firstname. Uses {@link CountQuery} annotation to define the + * query to be executed. + * + * @param firstname + * @return + */ + @CountQuery("{ 'firstname' : ?0 }") // DATAMONGO-1539 + long countByThePersonsFirstname(String firstname); + + /** + * Deletes {@link Person} entities with the given firstname. Uses {@link DeleteQuery} annotation to define the query + * to be executed. + * + * @param firstname + */ + @DeleteQuery("{ 'firstname' : ?0 }") // DATAMONGO-1539 + void deleteByThePersonsFirstname(String firstname); } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepositoryLazyLoadingIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepositoryLazyLoadingIntegrationTests.java index 5bba542d85..2934f9a790 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepositoryLazyLoadingIntegrationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepositoryLazyLoadingIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2010-2013 the original author or authors. + * Copyright 2010-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -51,10 +51,7 @@ public void setUp() throws InterruptedException { operations.remove(new org.springframework.data.mongodb.core.query.Query(), User.class); } - /** - * @see DATAMONGO-348 - */ - @Test + @Test // DATAMONGO-348 public void shouldLoadAssociationWithDbRefOnInterfaceAndLazyLoadingEnabled() throws Exception { User thomas = new User(); @@ -77,10 +74,7 @@ public void shouldLoadAssociationWithDbRefOnInterfaceAndLazyLoadingEnabled() thr assertThat(user.getUsername(), is(thomas.getUsername())); } - /** - * @see DATAMONGO-348 - */ - @Test + @Test // DATAMONGO-348 public void shouldLoadAssociationWithDbRefOnConcreteCollectionAndLazyLoadingEnabled() throws Exception { User thomas = new User(); @@ -108,10 +102,7 @@ public void shouldLoadAssociationWithDbRefOnConcreteCollectionAndLazyLoadingEnab assertThat(realFan.getUsername(), is(thomas.getUsername())); } - /** - * @see DATAMONGO-348 - */ - @Test + @Test // DATAMONGO-348 public void shouldLoadAssociationWithDbRefOnConcreteDomainClassAndLazyLoadingEnabled() throws Exception { User thomas = new User(); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/RedeclaringRepositoryMethodsTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/RedeclaringRepositoryMethodsTests.java index 3447d7d6a7..aced3f4122 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/RedeclaringRepositoryMethodsTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/RedeclaringRepositoryMethodsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,10 +34,7 @@ public class RedeclaringRepositoryMethodsTests extends AbstractPersonRepositoryI @Autowired RedeclaringRepositoryMethodsRepository repository; - /** - * @see DATAMONGO-760 - */ - @Test + @Test // DATAMONGO-760 public void adjustedWellKnownPagedFindAllMethodShouldReturnOnlyTheUserWithFirstnameOliverAugust() { Page page = repository.findAll(new PageRequest(0, 2)); @@ -46,10 +43,7 @@ public void adjustedWellKnownPagedFindAllMethodShouldReturnOnlyTheUserWithFirstn assertThat(page.getContent().get(0).getFirstname(), is(oliver.getFirstname())); } - /** - * @see DATAMONGO-760 - */ - @Test + @Test // DATAMONGO-760 public void adjustedWllKnownFindAllMethodShouldReturnAnEmptyList() { List result = repository.findAll(); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/cdi/CdiExtensionIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/cdi/CdiExtensionIntegrationTests.java index eef0448479..b21ef5a197 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/cdi/CdiExtensionIntegrationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/cdi/CdiExtensionIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2014 the original author or authors. + * Copyright 2012-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -63,10 +63,7 @@ public void bootstrapsRepositoryCorrectly() { assertThat(repository.findOne(person.getId()).getId(), is(result.getId())); } - /** - * @see DATAMONGO-1017 - */ - @Test + @Test // DATAMONGO-1017 public void returnOneFromCustomImpl() { RepositoryClient repositoryConsumer = container.getInstance(RepositoryClient.class); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/cdi/SamplePersonRepository.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/cdi/SamplePersonRepository.java index 224a7ed3b4..329e231fea 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/cdi/SamplePersonRepository.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/cdi/SamplePersonRepository.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 the original author or authors. + * Copyright 2014-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +20,5 @@ /** * @author Mark Paluch - * @see DATAMONGO-1017 */ public interface SamplePersonRepository extends Repository, SamplePersonRepositoryCustom {} diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/cdi/SamplePersonRepositoryCustom.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/cdi/SamplePersonRepositoryCustom.java index a545d35919..d792f58a06 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/cdi/SamplePersonRepositoryCustom.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/cdi/SamplePersonRepositoryCustom.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 the original author or authors. + * Copyright 2014-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,7 +17,6 @@ package org.springframework.data.mongodb.repository.cdi; /** - * @see DATAMONGO-1017 * @author Mark Paluch */ interface SamplePersonRepositoryCustom { diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/cdi/SamplePersonRepositoryImpl.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/cdi/SamplePersonRepositoryImpl.java index 46a22cb6e8..aaa77b6701 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/cdi/SamplePersonRepositoryImpl.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/cdi/SamplePersonRepositoryImpl.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 the original author or authors. + * Copyright 2014-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,7 +17,6 @@ package org.springframework.data.mongodb.repository.cdi; /** - * @see DATAMONGO-1017 * @author Mark Paluch */ class SamplePersonRepositoryImpl implements SamplePersonRepositoryCustom { diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/config/MongoNamespaceIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/config/MongoNamespaceIntegrationTests.java index 8847e0abd0..6481c5c18b 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/config/MongoNamespaceIntegrationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/config/MongoNamespaceIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012 the original author or authors. + * Copyright 2012-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -63,10 +63,7 @@ public void assertDefaultMappingContextIsWired() { assertThat(definition, is(notNullValue())); } - /** - * @see DATAMONGO-581 - */ - @Test + @Test // DATAMONGO-581 public void exposesPersistentEntity() { Repositories repositories = new Repositories(context); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/config/MongoRepositoriesRegistrarIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/config/MongoRepositoriesRegistrarIntegrationTests.java index 32d49c7b8a..3d1a0f11d7 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/config/MongoRepositoriesRegistrarIntegrationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/config/MongoRepositoriesRegistrarIntegrationTests.java @@ -15,11 +15,6 @@ */ package org.springframework.data.mongodb.repository.config; -import static org.hamcrest.CoreMatchers.*; -import static org.junit.Assert.*; - -import java.util.Arrays; - import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; @@ -59,15 +54,4 @@ public MongoOperations mongoTemplate() throws Exception { @Test public void testConfiguration() {} - - /** - * @see DATAMONGO-901 - */ - @Test - public void registersTypePredictingPostProcessor() { - - Iterable beanNames = Arrays.asList(context.getBeanDefinitionNames()); - - assertThat(beanNames, hasItem(containsString("RepositoryFactoryBeanSupport_Predictor"))); - } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/config/MongoRepositoryConfigurationExtensionUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/config/MongoRepositoryConfigurationExtensionUnitTests.java index eb6fd4f40f..b75489b6ad 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/config/MongoRepositoryConfigurationExtensionUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/config/MongoRepositoryConfigurationExtensionUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 the original author or authors. + * Copyright 2014-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -46,30 +46,21 @@ public class MongoRepositoryConfigurationExtensionUnitTests { RepositoryConfigurationSource configurationSource = new AnnotationRepositoryConfigurationSource(metadata, EnableMongoRepositories.class, loader, environment); - /** - * @see DATAMONGO-1009 - */ - @Test + @Test // DATAMONGO-1009 public void isStrictMatchIfDomainTypeIsAnnotatedWithDocument() { MongoRepositoryConfigurationExtension extension = new MongoRepositoryConfigurationExtension(); assertHasRepo(SampleRepository.class, extension.getRepositoryConfigurations(configurationSource, loader, true)); } - /** - * @see DATAMONGO-1009 - */ - @Test + @Test // DATAMONGO-1009 public void isStrictMatchIfRepositoryExtendsStoreSpecificBase() { MongoRepositoryConfigurationExtension extension = new MongoRepositoryConfigurationExtension(); assertHasRepo(StoreRepository.class, extension.getRepositoryConfigurations(configurationSource, loader, true)); } - /** - * @see DATAMONGO-1009 - */ - @Test + @Test // DATAMONGO-1009 public void isNotStrictMatchIfDomainTypeIsNotAnnotatedWithDocument() { MongoRepositoryConfigurationExtension extension = new MongoRepositoryConfigurationExtension(); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/config/lazy/AllowNestedMongoRepositoriesRepositoryConfigTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/config/lazy/AllowNestedMongoRepositoriesRepositoryConfigTests.java index bb4f61d8e9..315779c70f 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/config/lazy/AllowNestedMongoRepositoriesRepositoryConfigTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/config/lazy/AllowNestedMongoRepositoriesRepositoryConfigTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,10 +36,7 @@ public class AllowNestedMongoRepositoriesRepositoryConfigTests { @Autowired NestedUserRepository fooRepository; - /** - * @see DATAMONGO-780 - */ - @Test + @Test // DATAMONGO-780 public void shouldFindNestedRepository() { assertThat(fooRepository, is(notNullValue())); } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/config/lazy/ClassWithNestedRepository.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/config/lazy/ClassWithNestedRepository.java index fe9b547783..9e87ef701c 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/config/lazy/ClassWithNestedRepository.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/config/lazy/ClassWithNestedRepository.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,7 +19,6 @@ import org.springframework.data.mongodb.repository.User; /** - * @see DATAMONGO-780 * @author Thomas Darimont */ public class ClassWithNestedRepository { diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/config/lazy/NestedMongoRepositoriesJavaConfigTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/config/lazy/NestedMongoRepositoriesJavaConfigTests.java index e9713a3839..dbea6bad20 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/config/lazy/NestedMongoRepositoriesJavaConfigTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/config/lazy/NestedMongoRepositoriesJavaConfigTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -45,10 +45,7 @@ static class Config {} @Autowired NestedUserRepository nestedUserRepository; - /** - * @see DATAMONGO-780 - */ - @Test + @Test // DATAMONGO-780 public void shouldSupportNestedRepositories() { assertThat(nestedUserRepository, is(notNullValue())); } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/custom/CustomRepositoryImplementationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/custom/CustomRepositoryImplementationTests.java index d375bd0e14..951b781e03 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/custom/CustomRepositoryImplementationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/custom/CustomRepositoryImplementationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -46,10 +46,7 @@ static class Config {} @Autowired CustomMongoRepository customMongoRepository; - /** - * @see DATAMONGO-804 - */ - @Test + @Test // DATAMONGO-804 public void shouldExecuteMethodOnCustomRepositoryImplementation() { String username = "bubu"; diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/AbstractMongoQueryUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/AbstractMongoQueryUnitTests.java index 7406e63341..3b2515d781 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/AbstractMongoQueryUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/AbstractMongoQueryUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2016 the original author or authors. + * Copyright 2014-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -91,11 +91,8 @@ public void setUp() { doReturn(converter).when(mongoOperationsMock).getConverter(); } - /** - * @see DATAMONGO-566 - */ @SuppressWarnings("unchecked") - @Test + @Test // DATAMONGO-566 public void testDeleteExecutionCallsRemoveCorreclty() { createQueryForMethod("deletePersonByLastname", String.class).setDeleteQuery(true).execute(new Object[] { "booh" }); @@ -105,12 +102,8 @@ public void testDeleteExecutionCallsRemoveCorreclty() { Matchers.anyString()); } - /** - * @see DATAMONGO-566 - * @see DATAMONGO-1040 - */ @SuppressWarnings("unchecked") - @Test + @Test // DATAMONGO-566, DATAMONGO-1040 public void testDeleteExecutionLoadsListOfRemovedDocumentsWhenReturnTypeIsCollectionLike() { when(mongoOperationsMock.find(Matchers.any(Query.class), Matchers.any(Class.class), Matchers.anyString())) @@ -121,10 +114,7 @@ public void testDeleteExecutionLoadsListOfRemovedDocumentsWhenReturnTypeIsCollec verify(mongoOperationsMock, times(1)).findAllAndRemove(Matchers.any(Query.class), eq(Person.class), eq("persons")); } - /** - * @see DATAMONGO-566 - */ - @Test + @Test // DATAMONGO-566 public void testDeleteExecutionReturnsZeroWhenWriteResultIsNull() { MongoQueryFake query = createQueryForMethod("deletePersonByLastname", String.class); @@ -133,11 +123,7 @@ public void testDeleteExecutionReturnsZeroWhenWriteResultIsNull() { assertThat(query.execute(new Object[] { "fake" }), Is. is(0L)); } - /** - * @see DATAMONGO-566 - * @see DATAMONGO-978 - */ - @Test + @Test // DATAMONGO-566, DATAMONGO-978 public void testDeleteExecutionReturnsNrDocumentsDeletedFromWriteResult() { when(writeResultMock.getN()).thenReturn(100); @@ -151,10 +137,7 @@ public void testDeleteExecutionReturnsNrDocumentsDeletedFromWriteResult() { verify(mongoOperationsMock, times(1)).remove(Matchers.any(Query.class), eq(Person.class), eq("persons")); } - /** - * @see DATAMONGO-957 - */ - @Test + @Test // DATAMONGO-957 public void metadataShouldNotBeAddedToQueryWhenNotPresent() { MongoQueryFake query = createQueryForMethod("findByFirstname", String.class); @@ -167,10 +150,7 @@ public void metadataShouldNotBeAddedToQueryWhenNotPresent() { assertThat(captor.getValue().getMeta().getComment(), nullValue()); } - /** - * @see DATAMONGO-957 - */ - @Test + @Test // DATAMONGO-957 public void metadataShouldBeAddedToQueryCorrectly() { MongoQueryFake query = createQueryForMethod("findByFirstname", String.class, Pageable.class); @@ -182,10 +162,7 @@ public void metadataShouldBeAddedToQueryCorrectly() { assertThat(captor.getValue().getMeta().getComment(), is("comment")); } - /** - * @see DATAMONGO-957 - */ - @Test + @Test // DATAMONGO-957 public void metadataShouldBeAddedToCountQueryCorrectly() { MongoQueryFake query = createQueryForMethod("findByFirstname", String.class, Pageable.class); @@ -197,10 +174,7 @@ public void metadataShouldBeAddedToCountQueryCorrectly() { assertThat(captor.getValue().getMeta().getComment(), is("comment")); } - /** - * @see DATAMONGO-957 - */ - @Test + @Test // DATAMONGO-957 public void metadataShouldBeAddedToStringBasedQueryCorrectly() { MongoQueryFake query = createQueryForMethod("findByAnnotatedQuery", String.class, Pageable.class); @@ -212,10 +186,7 @@ public void metadataShouldBeAddedToStringBasedQueryCorrectly() { assertThat(captor.getValue().getMeta().getComment(), is("comment")); } - /** - * @see DATAMONGO-1057 - */ - @Test + @Test // DATAMONGO-1057 public void slicedExecutionShouldRetainNrOfElementsToSkip() { MongoQueryFake query = createQueryForMethod("findByLastname", String.class, Pageable.class); @@ -233,10 +204,7 @@ public void slicedExecutionShouldRetainNrOfElementsToSkip() { assertThat(captor.getAllValues().get(1).getSkip(), is(10)); } - /** - * @see DATAMONGO-1057 - */ - @Test + @Test // DATAMONGO-1057 public void slicedExecutionShouldIncrementLimitByOne() { MongoQueryFake query = createQueryForMethod("findByLastname", String.class, Pageable.class); @@ -254,10 +222,7 @@ public void slicedExecutionShouldIncrementLimitByOne() { assertThat(captor.getAllValues().get(1).getLimit(), is(11)); } - /** - * @see DATAMONGO-1057 - */ - @Test + @Test // DATAMONGO-1057 public void slicedExecutionShouldRetainSort() { MongoQueryFake query = createQueryForMethod("findByLastname", String.class, Pageable.class); @@ -276,10 +241,7 @@ public void slicedExecutionShouldRetainSort() { assertThat(captor.getAllValues().get(1).getSortObject(), is(expectedSortObject)); } - /** - * @see DATAMONGO-1080 - */ - @Test + @Test // DATAMONGO-1080 public void doesNotTryToPostProcessQueryResultIntoWrapperType() { Person reference = new Person(); @@ -312,6 +274,7 @@ private MongoQueryFake createQueryForMethod(String methodName, Class... param private static class MongoQueryFake extends AbstractMongoQuery { private boolean isCountQuery; + private boolean isExistsQuery; private boolean isDeleteQuery; public MongoQueryFake(MongoQueryMethod method, MongoOperations operations) { @@ -328,6 +291,11 @@ protected boolean isCountQuery() { return isCountQuery; } + @Override + protected boolean isExistsQuery() { + return false; + } + @Override protected boolean isDeleteQuery() { return isDeleteQuery; @@ -354,7 +322,7 @@ private interface Repo extends MongoRepository { @org.springframework.data.mongodb.repository.Query("{}") Page findByAnnotatedQuery(String firstnanme, Pageable pageable); - /** @see DATAMONGO-1057 */ + // DATAMONGO-1057 Slice findByLastname(String lastname, Pageable page); Optional findByLastname(String lastname); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/ConvertingParameterAccessorUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/ConvertingParameterAccessorUnitTests.java index 4692b0ef25..4601c0c006 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/ConvertingParameterAccessorUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/ConvertingParameterAccessorUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2015 the original author or authors. + * Copyright 2011-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -93,10 +93,7 @@ public void convertsCollectionUponAccess() { assertThat(result, is((Object) reference)); } - /** - * @see DATAMONGO-505 - */ - @Test + @Test // DATAMONGO-505 public void convertsAssociationsToDBRef() { Property property = new Property(); @@ -110,10 +107,7 @@ public void convertsAssociationsToDBRef() { assertThat(dbRef.getId(), is((Object) 5L)); } - /** - * @see DATAMONGO-505 - */ - @Test + @Test // DATAMONGO-505 public void convertsAssociationsToDBRefForCollections() { Property property = new Property(); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MappingMongoEntityInformationUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MappingMongoEntityInformationUnitTests.java index 821aab557c..5cc0a4d731 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MappingMongoEntityInformationUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MappingMongoEntityInformationUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2014 by the original author(s). + * Copyright 2011-2017 by the original author(s). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -45,20 +45,14 @@ public void setUp() { when(info.getCollection()).thenReturn("Person"); } - /** - * @see DATAMONGO-248 - */ - @Test + @Test // DATAMONGO-248 public void usesEntityCollectionIfNoCustomOneGiven() { MongoEntityInformation information = new MappingMongoEntityInformation(info); assertThat(information.getCollectionName(), is("Person")); } - /** - * @see DATAMONGO-248 - */ - @Test + @Test // DATAMONGO-248 public void usesCustomCollectionIfGiven() { MongoEntityInformation information = new MappingMongoEntityInformation(info, "foobar"); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoParametersParameterAccessorUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoParametersParameterAccessorUnitTests.java index df6dd2715a..bae0d460ea 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoParametersParameterAccessorUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoParametersParameterAccessorUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2015 the original author or authors. + * Copyright 2011-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -71,10 +71,7 @@ public void returnsDistanceIfAvailable() throws NoSuchMethodException, SecurityE assertThat(accessor.getDistanceRange().getUpperBound(), is(DISTANCE)); } - /** - * @see DATAMONGO-973 - */ - @Test + @Test // DATAMONGO-973 public void shouldReturnAsFullTextStringWhenNoneDefinedForMethod() throws NoSuchMethodException, SecurityException { Method method = PersonRepository.class.getMethod("findByLocationNear", Point.class, Distance.class); @@ -85,10 +82,7 @@ public void shouldReturnAsFullTextStringWhenNoneDefinedForMethod() throws NoSuch assertThat(accessor.getFullText(), IsNull.nullValue()); } - /** - * @see DATAMONGO-973 - */ - @Test + @Test // DATAMONGO-973 public void shouldProperlyConvertTextCriteria() throws NoSuchMethodException, SecurityException { Method method = PersonRepository.class.getMethod("findByFirstname", String.class, TextCriteria.class); @@ -100,10 +94,7 @@ public void shouldProperlyConvertTextCriteria() throws NoSuchMethodException, Se equalTo("{ \"$text\" : { \"$search\" : \"data\"}}")); } - /** - * @see DATAMONGO-1110 - */ - @Test + @Test // DATAMONGO-1110 public void shouldDetectMinAndMaxDistance() throws NoSuchMethodException, SecurityException { Method method = PersonRepository.class.getMethod("findByLocationNear", Point.class, Range.class); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoParametersUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoParametersUnitTests.java index e7468d4a10..56c63b0d3a 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoParametersUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoParametersUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2015 the original author or authors. + * Copyright 2011-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -100,10 +100,7 @@ public void findsAnnotatedDoubleArrayForGeoNearQuery() throws Exception { assertThat(parameters.getNearIndex(), is(1)); } - /** - * @see DATAMONGO-973 - */ - @Test + @Test // DATAMONGO-973 public void shouldFindTextCriteriaAtItsIndex() throws SecurityException, NoSuchMethodException { Method method = PersonRepository.class.getMethod("findByNameAndText", String.class, TextCriteria.class); @@ -111,10 +108,7 @@ public void shouldFindTextCriteriaAtItsIndex() throws SecurityException, NoSuchM assertThat(parameters.getFullTextParameterIndex(), is(1)); } - /** - * @see DATAMONGO-973 - */ - @Test + @Test // DATAMONGO-973 public void shouldTreatTextCriteriaParameterAsSpecialParameter() throws SecurityException, NoSuchMethodException { Method method = PersonRepository.class.getMethod("findByNameAndText", String.class, TextCriteria.class); @@ -122,10 +116,7 @@ public void shouldTreatTextCriteriaParameterAsSpecialParameter() throws Security assertThat(parameters.getParameter(parameters.getFullTextParameterIndex()).isSpecialParameter(), is(true)); } - /** - * @see DATAMONGO-1110 - */ - @Test + @Test // DATAMONGO-1110 public void shouldFindMinAndMaxDistanceParameters() throws NoSuchMethodException, SecurityException { Method method = PersonRepository.class.getMethod("findByLocationNear", Point.class, Range.class); @@ -135,10 +126,7 @@ public void shouldFindMinAndMaxDistanceParameters() throws NoSuchMethodException assertThat(parameters.getMaxDistanceIndex(), is(-1)); } - /** - * @see DATAMONGO-1110 - */ - @Test + @Test // DATAMONGO-1110 public void shouldNotHaveMinDistanceIfOnlyOneDistanceParameterPresent() throws NoSuchMethodException, SecurityException { diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoQueryCreatorUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoQueryCreatorUnitTests.java index 71dc7316b2..567d5b9b1f 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoQueryCreatorUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoQueryCreatorUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2016 the original author or authors. + * Copyright 2011-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -44,6 +44,8 @@ import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver; import org.springframework.data.mongodb.core.convert.MappingMongoConverter; import org.springframework.data.mongodb.core.convert.MongoConverter; +import org.springframework.data.mongodb.core.geo.GeoJsonLineString; +import org.springframework.data.mongodb.core.geo.GeoJsonPoint; import org.springframework.data.mongodb.core.index.GeoSpatialIndexType; import org.springframework.data.mongodb.core.index.GeoSpatialIndexed; import org.springframework.data.mongodb.core.mapping.DBRef; @@ -69,8 +71,6 @@ */ public class MongoQueryCreatorUnitTests { - Method findByFirstname, findByFirstnameAndFriend, findByFirstnameNotNull; - MappingContext, MongoPersistentProperty> context; MongoConverter converter; @@ -95,10 +95,7 @@ public void createsQueryCorrectly() throws Exception { assertThat(query, is(query(where("firstName").is("Oliver")))); } - /** - * @see DATAMONGO-469 - */ - @Test + @Test // DATAMONGO-469 public void createsAndQueryCorrectly() { Person person = new Person(); @@ -169,10 +166,7 @@ public void createsGreaterThanEqualQueryCorrectly() throws Exception { assertThat(creator.createQuery(), is(reference)); } - /** - * @see DATAMONGO-338 - */ - @Test + @Test // DATAMONGO-338 public void createsExistsClauseCorrectly() { PartTree tree = new PartTree("findByAgeExists", Person.class); @@ -181,10 +175,7 @@ public void createsExistsClauseCorrectly() { assertThat(creator.createQuery(), is(query)); } - /** - * @see DATAMONGO-338 - */ - @Test + @Test // DATAMONGO-338 public void createsRegexClauseCorrectly() { PartTree tree = new PartTree("findByFirstNameRegex", Person.class); @@ -193,10 +184,7 @@ public void createsRegexClauseCorrectly() { assertThat(creator.createQuery(), is(query)); } - /** - * @see DATAMONGO-338 - */ - @Test + @Test // DATAMONGO-338 public void createsTrueClauseCorrectly() { PartTree tree = new PartTree("findByActiveTrue", Person.class); @@ -205,10 +193,7 @@ public void createsTrueClauseCorrectly() { assertThat(creator.createQuery(), is(query)); } - /** - * @see DATAMONGO-338 - */ - @Test + @Test // DATAMONGO-338 public void createsFalseClauseCorrectly() { PartTree tree = new PartTree("findByActiveFalse", Person.class); @@ -217,10 +202,7 @@ public void createsFalseClauseCorrectly() { assertThat(creator.createQuery(), is(query)); } - /** - * @see DATAMONGO-413 - */ - @Test + @Test // DATAMONGO-413 public void createsOrQueryCorrectly() { PartTree tree = new PartTree("findByFirstNameOrAge", Person.class); @@ -230,10 +212,7 @@ public void createsOrQueryCorrectly() { assertThat(query, is(query(new Criteria().orOperator(where("firstName").is("Dave"), where("age").is(42))))); } - /** - * @see DATAMONGO-347 - */ - @Test + @Test // DATAMONGO-347 public void createsQueryReferencingADBRefCorrectly() { User user = new User(); @@ -246,10 +225,7 @@ public void createsQueryReferencingADBRefCorrectly() { assertThat(queryObject.get("creator"), is((Object) user)); } - /** - * @see DATAMONGO-418 - */ - @Test + @Test // DATAMONGO-418 public void createsQueryWithStartingWithPredicateCorrectly() { PartTree tree = new PartTree("findByUsernameStartingWith", User.class); @@ -259,10 +235,7 @@ public void createsQueryWithStartingWithPredicateCorrectly() { assertThat(query, is(query(where("username").regex("^Matt")))); } - /** - * @see DATAMONGO-418 - */ - @Test + @Test // DATAMONGO-418 public void createsQueryWithEndingWithPredicateCorrectly() { PartTree tree = new PartTree("findByUsernameEndingWith", User.class); @@ -272,10 +245,7 @@ public void createsQueryWithEndingWithPredicateCorrectly() { assertThat(query, is(query(where("username").regex("ews$")))); } - /** - * @see DATAMONGO-418 - */ - @Test + @Test // DATAMONGO-418 public void createsQueryWithContainingPredicateCorrectly() { PartTree tree = new PartTree("findByUsernameContaining", User.class); @@ -301,10 +271,7 @@ private void assertBindsDistanceToQuery(Point point, Distance distance, Query re assertThat(query, is(query)); } - /** - * @see DATAMONGO-770 - */ - @Test + @Test // DATAMONGO-770 public void createsQueryWithFindByIgnoreCaseCorrectly() { PartTree tree = new PartTree("findByfirstNameIgnoreCase", Person.class); @@ -314,10 +281,7 @@ public void createsQueryWithFindByIgnoreCaseCorrectly() { assertThat(query, is(query(where("firstName").regex("^dave$", "i")))); } - /** - * @see DATAMONGO-770 - */ - @Test + @Test // DATAMONGO-770 public void createsQueryWithFindByNotIgnoreCaseCorrectly() { PartTree tree = new PartTree("findByFirstNameNotIgnoreCase", Person.class); @@ -327,10 +291,7 @@ public void createsQueryWithFindByNotIgnoreCaseCorrectly() { assertThat(query.toString(), is(query(where("firstName").not().regex("^dave$", "i")).toString())); } - /** - * @see DATAMONGO-770 - */ - @Test + @Test // DATAMONGO-770 public void createsQueryWithFindByStartingWithIgnoreCaseCorrectly() { PartTree tree = new PartTree("findByFirstNameStartingWithIgnoreCase", Person.class); @@ -340,10 +301,7 @@ public void createsQueryWithFindByStartingWithIgnoreCaseCorrectly() { assertThat(query, is(query(where("firstName").regex("^dave", "i")))); } - /** - * @see DATAMONGO-770 - */ - @Test + @Test // DATAMONGO-770 public void createsQueryWithFindByEndingWithIgnoreCaseCorrectly() { PartTree tree = new PartTree("findByFirstNameEndingWithIgnoreCase", Person.class); @@ -353,10 +311,7 @@ public void createsQueryWithFindByEndingWithIgnoreCaseCorrectly() { assertThat(query, is(query(where("firstName").regex("dave$", "i")))); } - /** - * @see DATAMONGO-770 - */ - @Test + @Test // DATAMONGO-770 public void createsQueryWithFindByContainingIgnoreCaseCorrectly() { PartTree tree = new PartTree("findByFirstNameContainingIgnoreCase", Person.class); @@ -366,10 +321,7 @@ public void createsQueryWithFindByContainingIgnoreCaseCorrectly() { assertThat(query, is(query(where("firstName").regex(".*dave.*", "i")))); } - /** - * @see DATAMONGO-770 - */ - @Test + @Test // DATAMONGO-770 public void shouldThrowExceptionForQueryWithFindByIgnoreCaseOnNonStringProperty() { expection.expect(IllegalArgumentException.class); @@ -381,10 +333,7 @@ public void shouldThrowExceptionForQueryWithFindByIgnoreCaseOnNonStringProperty( creator.createQuery(); } - /** - * @see DATAMONGO-770 - */ - @Test + @Test // DATAMONGO-770 public void shouldOnlyGenerateLikeExpressionsForStringPropertiesIfAllIgnoreCase() { PartTree tree = new PartTree("findByFirstNameAndAgeAllIgnoreCase", Person.class); @@ -394,10 +343,7 @@ public void shouldOnlyGenerateLikeExpressionsForStringPropertiesIfAllIgnoreCase( assertThat(query, is(query(where("firstName").regex("^dave$", "i").and("age").is(42)))); } - /** - * @see DATAMONGO-566 - */ - @Test + @Test // DATAMONGO-566 public void shouldCreateDeleteByQueryCorrectly() { PartTree tree = new PartTree("deleteByFirstName", Person.class); @@ -409,10 +355,7 @@ public void shouldCreateDeleteByQueryCorrectly() { assertThat(query, is(query(where("firstName").is("dave")))); } - /** - * @see DATAMONGO-566 - */ - @Test + @Test // DATAMONGO-566 public void shouldCreateDeleteByQueryCorrectlyForMultipleCriteriaAndCaseExpressions() { PartTree tree = new PartTree("deleteByFirstNameAndAgeAllIgnoreCase", Person.class); @@ -424,10 +367,7 @@ public void shouldCreateDeleteByQueryCorrectlyForMultipleCriteriaAndCaseExpressi assertThat(query, is(query(where("firstName").regex("^dave$", "i").and("age").is(42)))); } - /** - * @see DATAMONGO-1075 - */ - @Test + @Test // DATAMONGO-1075 public void shouldCreateInClauseWhenUsingContainsOnCollectionLikeProperty() { PartTree tree = new PartTree("findByEmailAddressesContaining", User.class); @@ -438,10 +378,7 @@ public void shouldCreateInClauseWhenUsingContainsOnCollectionLikeProperty() { assertThat(query, is(query(where("emailAddresses").in("dave")))); } - /** - * @see DATAMONGO-1075 - */ - @Test + @Test // DATAMONGO-1075 public void shouldCreateInClauseWhenUsingNotContainsOnCollectionLikeProperty() { PartTree tree = new PartTree("findByEmailAddressesNotContaining", User.class); @@ -452,11 +389,7 @@ public void shouldCreateInClauseWhenUsingNotContainsOnCollectionLikeProperty() { assertThat(query, is(query(where("emailAddresses").not().in("dave")))); } - /** - * @see DATAMONGO-1075 - * @see DATAMONGO-1425 - */ - @Test + @Test // DATAMONGO-1075, DATAMONGO-1425 public void shouldCreateRegexWhenUsingNotContainsOnStringProperty() { PartTree tree = new PartTree("findByUsernameNotContaining", User.class); @@ -466,11 +399,8 @@ public void shouldCreateRegexWhenUsingNotContainsOnStringProperty() { assertThat(query.getQueryObject(), is(query(where("username").not().regex(".*thew.*")).getQueryObject())); } - /** - * @see DATAMONGO-1139 - */ - @Test - public void createsNonShericalNearForDistanceWithDefaultMetric() { + @Test // DATAMONGO-1139 + public void createsNonSphericalNearForDistanceWithDefaultMetric() { Point point = new Point(1.0, 1.0); Distance distance = new Distance(1.0); @@ -482,10 +412,7 @@ public void createsNonShericalNearForDistanceWithDefaultMetric() { assertThat(query, is(query(where("location").near(point).maxDistance(1.0)))); } - /** - * @see DATAMONGO-1136 - */ - @Test + @Test // DATAMONGO-1136 public void shouldCreateWithinQueryCorrectly() { Point first = new Point(1, 1); @@ -500,10 +427,7 @@ public void shouldCreateWithinQueryCorrectly() { assertThat(query, is(query(where("address.geo").within(shape)))); } - /** - * @see DATAMONGO-1110 - */ - @Test + @Test // DATAMONGO-1110 public void shouldCreateNearSphereQueryForSphericalProperty() { Point point = new Point(10, 20); @@ -515,10 +439,7 @@ public void shouldCreateNearSphereQueryForSphericalProperty() { assertThat(query, is(query(where("address2dSphere.geo").nearSphere(point)))); } - /** - * @see DATAMONGO-1110 - */ - @Test + @Test // DATAMONGO-1110 public void shouldCreateNearSphereQueryForSphericalPropertyHavingDistanceWithDefaultMetric() { Point point = new Point(1.0, 1.0); @@ -531,10 +452,7 @@ public void shouldCreateNearSphereQueryForSphericalPropertyHavingDistanceWithDef assertThat(query, is(query(where("address2dSphere.geo").nearSphere(point).maxDistance(1.0)))); } - /** - * @see DATAMONGO-1110 - */ - @Test + @Test // DATAMONGO-1110 public void shouldCreateNearQueryForMinMaxDistance() { Point point = new Point(10, 20); @@ -547,10 +465,7 @@ public void shouldCreateNearQueryForMinMaxDistance() { assertThat(query, is(query(where("address.geo").near(point).minDistance(10D).maxDistance(20D)))); } - /** - * @see DATAMONGO-1229 - */ - @Test + @Test // DATAMONGO-1229 public void appliesIgnoreCaseToLeafProperty() { PartTree tree = new PartTree("findByAddressStreetIgnoreCase", User.class); @@ -559,10 +474,7 @@ public void appliesIgnoreCaseToLeafProperty() { assertThat(new MongoQueryCreator(tree, accessor, context).createQuery(), is(notNullValue())); } - /** - * @see DATAMONGO-1232 - */ - @Test + @Test // DATAMONGO-1232 public void ignoreCaseShouldEscapeSource() { PartTree tree = new PartTree("findByUsernameIgnoreCase", User.class); @@ -573,10 +485,7 @@ public void ignoreCaseShouldEscapeSource() { assertThat(query, is(query(where("username").regex("^\\Qcon.flux+\\E$", "i")))); } - /** - * @see DATAMONGO-1232 - */ - @Test + @Test // DATAMONGO-1232 public void ignoreCaseShouldEscapeSourceWhenUsedForStartingWith() { PartTree tree = new PartTree("findByUsernameStartingWithIgnoreCase", User.class); @@ -587,10 +496,7 @@ public void ignoreCaseShouldEscapeSourceWhenUsedForStartingWith() { assertThat(query, is(query(where("username").regex("^\\Qdawns.light+\\E", "i")))); } - /** - * @see DATAMONGO-1232 - */ - @Test + @Test // DATAMONGO-1232 public void ignoreCaseShouldEscapeSourceWhenUsedForEndingWith() { PartTree tree = new PartTree("findByUsernameEndingWithIgnoreCase", User.class); @@ -601,10 +507,7 @@ public void ignoreCaseShouldEscapeSourceWhenUsedForEndingWith() { assertThat(query, is(query(where("username").regex("\\Qnew.ton+\\E$", "i")))); } - /** - * @see DATAMONGO-1232 - */ - @Test + @Test // DATAMONGO-1232 public void likeShouldEscapeSourceWhenUsedWithLeadingAndTrailingWildcard() { PartTree tree = new PartTree("findByUsernameLike", User.class); @@ -615,10 +518,7 @@ public void likeShouldEscapeSourceWhenUsedWithLeadingAndTrailingWildcard() { assertThat(query, is(query(where("username").regex(".*\\Qfire.fight+\\E.*")))); } - /** - * @see DATAMONGO-1232 - */ - @Test + @Test // DATAMONGO-1232 public void likeShouldEscapeSourceWhenUsedWithLeadingWildcard() { PartTree tree = new PartTree("findByUsernameLike", User.class); @@ -629,10 +529,7 @@ public void likeShouldEscapeSourceWhenUsedWithLeadingWildcard() { assertThat(query, is(query(where("username").regex(".*\\Qsteel.heart+\\E")))); } - /** - * @see DATAMONGO-1232 - */ - @Test + @Test // DATAMONGO-1232 public void likeShouldEscapeSourceWhenUsedWithTrailingWildcard() { PartTree tree = new PartTree("findByUsernameLike", User.class); @@ -642,10 +539,7 @@ public void likeShouldEscapeSourceWhenUsedWithTrailingWildcard() { assertThat(query, is(query(where("username").regex("\\Qcala.mity+\\E.*")))); } - /** - * @see DATAMONGO-1232 - */ - @Test + @Test // DATAMONGO-1232 public void likeShouldBeTreatedCorrectlyWhenUsedWithWildcardOnly() { PartTree tree = new PartTree("findByUsernameLike", User.class); @@ -655,10 +549,7 @@ public void likeShouldBeTreatedCorrectlyWhenUsedWithWildcardOnly() { assertThat(query, is(query(where("username").regex(".*")))); } - /** - * @see DATAMONGO-1342 - */ - @Test + @Test // DATAMONGO-1342 public void bindsNullValueToContainsClause() { PartTree partTree = new PartTree("emailAddressesContains", User.class); @@ -669,10 +560,7 @@ public void bindsNullValueToContainsClause() { assertThat(query, is(query(where("emailAddresses").in((Object) null)))); } - /** - * @see DATAMONGO-1424 - */ - @Test + @Test // DATAMONGO-1424 public void notLikeShouldEscapeSourceWhenUsedWithLeadingAndTrailingWildcard() { PartTree tree = new PartTree("findByUsernameNotLike", User.class); @@ -684,10 +572,7 @@ public void notLikeShouldEscapeSourceWhenUsedWithLeadingAndTrailingWildcard() { is(query(where("username").not().regex(".*\\Qfire.fight+\\E.*")).getQueryObject())); } - /** - * @see DATAMONGO-1424 - */ - @Test + @Test // DATAMONGO-1424 public void notLikeShouldEscapeSourceWhenUsedWithLeadingWildcard() { PartTree tree = new PartTree("findByUsernameNotLike", User.class); @@ -699,10 +584,7 @@ public void notLikeShouldEscapeSourceWhenUsedWithLeadingWildcard() { is(query(where("username").not().regex(".*\\Qsteel.heart+\\E")).getQueryObject())); } - /** - * @see DATAMONGO-1424 - */ - @Test + @Test // DATAMONGO-1424 public void notLikeShouldEscapeSourceWhenUsedWithTrailingWildcard() { PartTree tree = new PartTree("findByUsernameNotLike", User.class); @@ -712,10 +594,7 @@ public void notLikeShouldEscapeSourceWhenUsedWithTrailingWildcard() { assertThat(query.getQueryObject(), is(query(where("username").not().regex("\\Qcala.mity+\\E.*")).getQueryObject())); } - /** - * @see DATAMONGO-1424 - */ - @Test + @Test // DATAMONGO-1424 public void notLikeShouldBeTreatedCorrectlyWhenUsedWithWildcardOnly() { PartTree tree = new PartTree("findByUsernameNotLike", User.class); @@ -725,6 +604,29 @@ public void notLikeShouldBeTreatedCorrectlyWhenUsedWithWildcardOnly() { assertThat(query.getQueryObject(), is(query(where("username").not().regex(".*")).getQueryObject())); } + @Test // DATAMONGO-1588 + public void queryShouldAcceptSubclassOfDeclaredArgument() { + + PartTree tree = new PartTree("findByLocationNear", User.class); + ConvertingParameterAccessor accessor = getAccessor(converter, new GeoJsonPoint(-74.044502D, 40.689247D)); + + Query query = new MongoQueryCreator(tree, accessor, context).createQuery(); + assertThat(query.getQueryObject().containsField("location"), is(true)); + } + + @Test // DATAMONGO-1588 + public void queryShouldThrowExceptionWhenArgumentDoesNotMatchDeclaration() { + + expection.expect(IllegalArgumentException.class); + expection.expectMessage("Expected parameter type of " + Point.class); + + PartTree tree = new PartTree("findByLocationNear", User.class); + ConvertingParameterAccessor accessor = getAccessor(converter, + new GeoJsonLineString(new Point(-74.044502D, 40.689247D), new Point(-73.997330D, 40.730824D))); + + new MongoQueryCreator(tree, accessor, context).createQuery(); + } + interface PersonRepository extends Repository { List findByLocationNearAndFirstname(Point location, Distance maxDistance, String firstname); @@ -743,16 +645,21 @@ class User { Address address; Address2dSphere address2dSphere; + + Point location; } static class Address { String street; + Point geo; } static class Address2dSphere { + String street; + @GeoSpatialIndexed(type = GeoSpatialIndexType.GEO_2DSPHERE) Point geo; } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoQueryExecutionUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoQueryExecutionUnitTests.java index 133e910dd6..7ccac637b1 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoQueryExecutionUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoQueryExecutionUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 the original author or authors. + * Copyright 2016-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -82,10 +82,7 @@ public void setUp() throws Exception { when(mongoOperationsMock.getConverter()).thenReturn(converter); } - /** - * @see DATAMONGO-1464 - */ - @Test + @Test // DATAMONGO-1464 public void pagedExecutionShouldNotGenerateCountQueryIfQueryReportedNoResults() { when(mongoOperationsMock.find(any(Query.class), eq(Person.class), eq("person"))) @@ -98,10 +95,7 @@ public void pagedExecutionShouldNotGenerateCountQueryIfQueryReportedNoResults() verify(mongoOperationsMock, never()).count(any(Query.class), eq(Person.class), eq("person")); } - /** - * @see DATAMONGO-1464 - */ - @Test + @Test // DATAMONGO-1464 public void pagedExecutionShouldUseCountFromResultWithOffsetAndResultsWithinPageSize() { when(mongoOperationsMock.find(any(Query.class), eq(Person.class), eq("person"))) @@ -114,10 +108,7 @@ public void pagedExecutionShouldUseCountFromResultWithOffsetAndResultsWithinPage verify(mongoOperationsMock, never()).count(any(Query.class), eq(Person.class), eq("person")); } - /** - * @see DATAMONGO-1464 - */ - @Test + @Test // DATAMONGO-1464 public void pagedExecutionRetrievesObjectsForPageableOutOfRange() throws Exception { when(mongoOperationsMock.find(any(Query.class), eq(Person.class), eq("person"))) @@ -130,10 +121,7 @@ public void pagedExecutionRetrievesObjectsForPageableOutOfRange() throws Excepti verify(mongoOperationsMock).count(any(Query.class), eq(Person.class), eq("person")); } - /** - * @see DATAMONGO-1464 - */ - @Test + @Test // DATAMONGO-1464 public void pagingGeoExecutionShouldUseCountFromResultWithOffsetAndResultsWithinPageSize() throws Exception { MongoParameterAccessor accessor = new MongoParametersParameterAccessor(queryMethod, @@ -153,10 +141,7 @@ public void pagingGeoExecutionShouldUseCountFromResultWithOffsetAndResultsWithin verify(mongoOperationsMock, never()).count(any(Query.class), eq("person")); } - /** - * @see DATAMONGO-1464 - */ - @Test + @Test // DATAMONGO-1464 public void pagingGeoExecutionRetrievesObjectsForPageableOutOfRange() throws Exception { MongoParameterAccessor accessor = new MongoParametersParameterAccessor(queryMethod, diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoQueryMethodUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoQueryMethodUnitTests.java index bba8388d0c..0955a30788 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoQueryMethodUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoQueryMethodUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2016 the original author or authors. + * Copyright 2011-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -128,10 +128,7 @@ public void createsMongoQueryMethodObjectForMethodReturningAnInterface() throws queryMethod(SampleRepository2.class, "methodReturningAnInterface"); } - /** - * @see DATAMONGO-957 - */ - @Test + @Test // DATAMONGO-957 public void createsMongoQueryMethodWithEmptyMetaCorrectly() throws Exception { MongoQueryMethod method = queryMethod(PersonRepository.class, "emptyMetaAnnotation"); @@ -140,10 +137,7 @@ public void createsMongoQueryMethodWithEmptyMetaCorrectly() throws Exception { assertThat(method.getQueryMetaAttributes().hasValues(), is(false)); } - /** - * @see DATAMONGO-957 - */ - @Test + @Test // DATAMONGO-957 public void createsMongoQueryMethodWithMaxExecutionTimeCorrectly() throws Exception { MongoQueryMethod method = queryMethod(PersonRepository.class, "metaWithMaxExecutionTime"); @@ -152,10 +146,7 @@ public void createsMongoQueryMethodWithMaxExecutionTimeCorrectly() throws Except assertThat(method.getQueryMetaAttributes().getMaxTimeMsec(), is(100L)); } - /** - * @see DATAMONGO-1403 - */ - @Test + @Test // DATAMONGO-1403 public void createsMongoQueryMethodWithSpellFixedMaxExecutionTimeCorrectly() throws Exception { MongoQueryMethod method = queryMethod(PersonRepository.class, "metaWithSpellFixedMaxExecutionTime"); @@ -164,10 +155,7 @@ public void createsMongoQueryMethodWithSpellFixedMaxExecutionTimeCorrectly() thr assertThat(method.getQueryMetaAttributes().getMaxTimeMsec(), is(100L)); } - /** - * @see DATAMONGO-957 - */ - @Test + @Test // DATAMONGO-957 public void createsMongoQueryMethodWithMaxScanCorrectly() throws Exception { MongoQueryMethod method = queryMethod(PersonRepository.class, "metaWithMaxScan"); @@ -176,10 +164,7 @@ public void createsMongoQueryMethodWithMaxScanCorrectly() throws Exception { assertThat(method.getQueryMetaAttributes().getMaxScan(), is(10L)); } - /** - * @see DATAMONGO-957 - */ - @Test + @Test // DATAMONGO-957 public void createsMongoQueryMethodWithCommentCorrectly() throws Exception { MongoQueryMethod method = queryMethod(PersonRepository.class, "metaWithComment"); @@ -188,10 +173,7 @@ public void createsMongoQueryMethodWithCommentCorrectly() throws Exception { assertThat(method.getQueryMetaAttributes().getComment(), is("foo bar")); } - /** - * @see DATAMONGO-957 - */ - @Test + @Test // DATAMONGO-957 public void createsMongoQueryMethodWithSnapshotCorrectly() throws Exception { MongoQueryMethod method = queryMethod(PersonRepository.class, "metaWithSnapshotUsage"); @@ -200,10 +182,7 @@ public void createsMongoQueryMethodWithSnapshotCorrectly() throws Exception { assertThat(method.getQueryMetaAttributes().getSnapshot(), is(true)); } - /** - * @see DATAMONGO-1480 - */ - @Test + @Test // DATAMONGO-1480 public void createsMongoQueryMethodWithNoCursorTimeoutCorrectly() throws Exception { MongoQueryMethod method = queryMethod(PersonRepository.class, "metaWithNoCursorTimeout"); @@ -213,10 +192,7 @@ public void createsMongoQueryMethodWithNoCursorTimeoutCorrectly() throws Excepti containsInAnyOrder(org.springframework.data.mongodb.core.query.Meta.CursorOption.NO_TIMEOUT)); } - /** - * @see DATAMONGO-1480 - */ - @Test + @Test // DATAMONGO-1480 public void createsMongoQueryMethodWithMultipleFlagsCorrectly() throws Exception { MongoQueryMethod method = queryMethod(PersonRepository.class, "metaWithMultipleFlags"); @@ -226,10 +202,7 @@ public void createsMongoQueryMethodWithMultipleFlagsCorrectly() throws Exception containsInAnyOrder(org.springframework.data.mongodb.core.query.Meta.CursorOption.NO_TIMEOUT, org.springframework.data.mongodb.core.query.Meta.CursorOption.SLAVE_OK)); } - /** - * @see DATAMONGO-1266 - */ - @Test + @Test // DATAMONGO-1266 public void fallsBackToRepositoryDomainTypeIfMethodDoesNotReturnADomainType() throws Exception { MongoQueryMethod method = queryMethod(PersonRepository.class, "deleteByUserName", String.class); @@ -281,9 +254,7 @@ interface PersonRepository extends Repository { @Meta(flags = { org.springframework.data.mongodb.core.query.Meta.CursorOption.NO_TIMEOUT, org.springframework.data.mongodb.core.query.Meta.CursorOption.SLAVE_OK }) List metaWithMultipleFlags(); - /** - * @see DATAMONGO-1266 - */ + // DATAMONGO-1266 void deleteByUserName(String userName); } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/PartTreeMongoQueryUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/PartTreeMongoQueryUnitTests.java index 9e578c9dfe..45912de686 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/PartTreeMongoQueryUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/PartTreeMongoQueryUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2016 the original author or authors. + * Copyright 2014-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -77,10 +77,7 @@ public void setUp() { when(mongoOperationsMock.getConverter()).thenReturn(converter); } - /** - * @see DATAMOGO-952 - */ - @Test + @Test // DATAMOGO-952 public void rejectsInvalidFieldSpecification() { exception.expect(IllegalStateException.class); @@ -89,10 +86,7 @@ public void rejectsInvalidFieldSpecification() { deriveQueryFromMethod("findByLastname", new Object[] { "foo" }); } - /** - * @see DATAMOGO-952 - */ - @Test + @Test // DATAMOGO-952 public void singleFieldJsonIncludeRestrictionShouldBeConsidered() { org.springframework.data.mongodb.core.query.Query query = deriveQueryFromMethod("findByFirstname", @@ -101,10 +95,7 @@ public void singleFieldJsonIncludeRestrictionShouldBeConsidered() { assertThat(query.getFieldsObject(), is(new BasicDBObjectBuilder().add("firstname", 1).get())); } - /** - * @see DATAMOGO-952 - */ - @Test + @Test // DATAMOGO-952 public void multiFieldJsonIncludeRestrictionShouldBeConsidered() { org.springframework.data.mongodb.core.query.Query query = deriveQueryFromMethod("findByFirstnameAndLastname", @@ -113,10 +104,7 @@ public void multiFieldJsonIncludeRestrictionShouldBeConsidered() { assertThat(query.getFieldsObject(), is(new BasicDBObjectBuilder().add("firstname", 1).add("lastname", 1).get())); } - /** - * @see DATAMOGO-952 - */ - @Test + @Test // DATAMOGO-952 public void multiFieldJsonExcludeRestrictionShouldBeConsidered() { org.springframework.data.mongodb.core.query.Query query = deriveQueryFromMethod("findPersonByFirstnameAndLastname", @@ -125,10 +113,7 @@ public void multiFieldJsonExcludeRestrictionShouldBeConsidered() { assertThat(query.getFieldsObject(), is(new BasicDBObjectBuilder().add("firstname", 0).add("lastname", 0).get())); } - /** - * @see DATAMOGO-973 - */ - @Test + @Test // DATAMOGO-973 public void shouldAddFullTextParamCorrectlyToDerivedQuery() { org.springframework.data.mongodb.core.query.Query query = deriveQueryFromMethod("findPersonByFirstname", @@ -137,10 +122,7 @@ public void shouldAddFullTextParamCorrectlyToDerivedQuery() { assertThat(query, isTextQuery().searchingFor("search").where(new Criteria("firstname").is("text"))); } - /** - * @see DATAMONGO-1180 - */ - @Test + @Test // DATAMONGO-1180 public void propagatesRootExceptionForInvalidQuery() { exception.expect(IllegalStateException.class); @@ -149,18 +131,12 @@ public void propagatesRootExceptionForInvalidQuery() { deriveQueryFromMethod("findByAge", new Object[] { 1 }); } - /** - * @see DATAMONGO-1345 - */ - @Test + @Test // DATAMONGO-1345 public void doesNotDeriveFieldSpecForNormalDomainType() { assertThat(deriveQueryFromMethod("findPersonBy", new Object[0]).getFieldsObject(), is(nullValue())); } - /** - * @see DATAMONGO-1345 - */ - @Test + @Test // DATAMONGO-1345 public void restrictsQueryToFieldsRequiredForProjection() { DBObject fieldsObject = deriveQueryFromMethod("findPersonProjectedBy", new Object[0]).getFieldsObject(); @@ -169,10 +145,7 @@ public void restrictsQueryToFieldsRequiredForProjection() { assertThat(fieldsObject.get("lastname"), is((Object) 1)); } - /** - * @see DATAMONGO-1345 - */ - @Test + @Test // DATAMONGO-1345 public void restrictsQueryToFieldsRequiredForDto() { DBObject fieldsObject = deriveQueryFromMethod("findPersonDtoByAge", new Object[] { 42 }).getFieldsObject(); @@ -181,10 +154,7 @@ public void restrictsQueryToFieldsRequiredForDto() { assertThat(fieldsObject.get("lastname"), is((Object) 1)); } - /** - * @see DATAMONGO-1345 - */ - @Test + @Test // DATAMONGO-1345 public void usesDynamicProjection() { DBObject fields = deriveQueryFromMethod("findDynamicallyProjectedBy", ExtendedProjection.class).getFieldsObject(); @@ -194,10 +164,7 @@ public void usesDynamicProjection() { assertThat(fields.get("age"), is((Object) 1)); } - /** - * @see DATAMONGO-1500 - */ - @Test + @Test // DATAMONGO-1500 public void shouldLeaveParameterConversionToQueryMapper() { org.springframework.data.mongodb.core.query.Query query = deriveQueryFromMethod("findBySex", Sex.FEMALE); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQueryUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQueryUnitTests.java index 41e0c6ef8c..8d9237bcbe 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQueryUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQueryUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2015 the original author or authors. + * Copyright 2011-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,6 +23,7 @@ import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.regex.Pattern; import javax.xml.bind.DatatypeConverter; @@ -50,10 +51,12 @@ import org.springframework.data.repository.query.DefaultEvaluationContextProvider; import org.springframework.expression.spel.standard.SpelExpressionParser; +import com.mongodb.BasicDBList; import com.mongodb.BasicDBObject; import com.mongodb.BasicDBObjectBuilder; import com.mongodb.DBObject; import com.mongodb.DBRef; +import com.mongodb.util.JSON; /** * Unit tests for {@link StringBasedMongoQuery}. @@ -61,6 +64,7 @@ * @author Oliver Gierke * @author Christoph Strobl * @author Thomas Darimont + * @author Mark Paluch */ @RunWith(MockitoJUnitRunner.class) public class StringBasedMongoQueryUnitTests { @@ -84,9 +88,9 @@ public void setUp() { public void bindsSimplePropertyCorrectly() throws Exception { StringBasedMongoQuery mongoQuery = createQueryForMethod("findByLastname", String.class); - ConvertingParameterAccessor accesor = StubParameterAccessor.getAccessor(converter, "Matthews"); + ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, "Matthews"); - org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accesor); + org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor); org.springframework.data.mongodb.core.query.Query reference = new BasicQuery("{'lastname' : 'Matthews'}"); assertThat(query.getQueryObject(), is(reference.getQueryObject())); @@ -98,13 +102,13 @@ public void bindsComplexPropertyCorrectly() throws Exception { StringBasedMongoQuery mongoQuery = createQueryForMethod("findByAddress", Address.class); Address address = new Address("Foo", "0123", "Bar"); - ConvertingParameterAccessor accesor = StubParameterAccessor.getAccessor(converter, address); + ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, address); DBObject dbObject = new BasicDBObject(); converter.write(address, dbObject); dbObject.removeField(DefaultMongoTypeMapper.DEFAULT_TYPE_KEY); - org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accesor); + org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor); BasicDBObject queryObject = new BasicDBObject("address", dbObject); org.springframework.data.mongodb.core.query.Query reference = new BasicQuery(queryObject); @@ -117,7 +121,7 @@ public void bindsMultipleParametersCorrectly() throws Exception { StringBasedMongoQuery mongoQuery = createQueryForMethod("findByLastnameAndAddress", String.class, Address.class); Address address = new Address("Foo", "0123", "Bar"); - ConvertingParameterAccessor accesor = StubParameterAccessor.getAccessor(converter, "Matthews", address); + ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, "Matthews", address); DBObject addressDbObject = new BasicDBObject(); converter.write(address, addressDbObject); @@ -126,7 +130,7 @@ public void bindsMultipleParametersCorrectly() throws Exception { DBObject reference = new BasicDBObject("address", addressDbObject); reference.put("lastname", "Matthews"); - org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accesor); + org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor); assertThat(query.getQueryObject(), is(reference)); } @@ -141,45 +145,33 @@ public void bindsNullParametersCorrectly() throws Exception { assertThat(query.getQueryObject().get("address"), is(nullValue())); } - /** - * @see DATAMONGO-821 - */ - @Test + @Test // DATAMONGO-821 public void bindsDbrefCorrectly() throws Exception { StringBasedMongoQuery mongoQuery = createQueryForMethod("findByHavingSizeFansNotZero"); - ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, new Object[] {}); + ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter); org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor); assertThat(query.getQueryObject(), is(new BasicQuery("{ fans : { $not : { $size : 0 } } }").getQueryObject())); } - /** - * @see DATAMONGO-566 - */ - @Test + @Test // DATAMONGO-566 public void constructsDeleteQueryCorrectly() throws Exception { StringBasedMongoQuery mongoQuery = createQueryForMethod("removeByLastname", String.class); assertThat(mongoQuery.isDeleteQuery(), is(true)); } - /** - * @see DATAMONGO-566 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-566 public void preventsDeleteAndCountFlagAtTheSameTime() throws Exception { createQueryForMethod("invalidMethod", String.class); } - /** - * @see DATAMONGO-420 - */ - @Test + @Test // DATAMONGO-420 public void shouldSupportFindByParameterizedCriteriaAndFields() throws Exception { - ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, new Object[] { - new BasicDBObject("firstname", "first").append("lastname", "last"), Collections.singletonMap("lastname", 1) }); + ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, + new BasicDBObject("firstname", "first").append("lastname", "last"), Collections.singletonMap("lastname", 1)); StringBasedMongoQuery mongoQuery = createQueryForMethod("findByParameterizedCriteriaAndFields", DBObject.class, Map.class); @@ -190,13 +182,10 @@ public void shouldSupportFindByParameterizedCriteriaAndFields() throws Exception assertThat(query.getFieldsObject(), is(new BasicQuery(null, "{ \"lastname\": 1}").getFieldsObject())); } - /** - * @see DATAMONGO-420 - */ - @Test + @Test // DATAMONGO-420 public void shouldSupportRespectExistingQuotingInFindByTitleBeginsWithExplicitQuoting() throws Exception { - ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, new Object[] { "fun" }); + ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, "fun"); StringBasedMongoQuery mongoQuery = createQueryForMethod("findByTitleBeginsWithExplicitQuoting", String.class); org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor); @@ -204,13 +193,10 @@ public void shouldSupportRespectExistingQuotingInFindByTitleBeginsWithExplicitQu assertThat(query.getQueryObject(), is(new BasicQuery("{title: {$regex: '^fun', $options: 'i'}}").getQueryObject())); } - /** - * @see DATAMONGO-995, DATAMONGO-420 - */ - @Test + @Test // DATAMONGO-995, DATAMONGO-420 public void shouldParseQueryWithParametersInExpression() throws Exception { - ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, new Object[] { 1, 2, 3, 4 }); + ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, 1, 2, 3, 4); StringBasedMongoQuery mongoQuery = createQueryForMethod("findByQueryWithParametersInExpression", int.class, int.class, int.class, int.class); @@ -221,55 +207,43 @@ public void shouldParseQueryWithParametersInExpression() throws Exception { .getQueryObject())); } - /** - * @see DATAMONGO-995, DATAMONGO-420 - */ - @Test + @Test // DATAMONGO-995, DATAMONGO-420 public void bindsSimplePropertyAlreadyQuotedCorrectly() throws Exception { - ConvertingParameterAccessor accesor = StubParameterAccessor.getAccessor(converter, "Matthews"); + ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, "Matthews"); StringBasedMongoQuery mongoQuery = createQueryForMethod("findByLastnameQuoted", String.class); - org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accesor); + org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor); org.springframework.data.mongodb.core.query.Query reference = new BasicQuery("{'lastname' : 'Matthews'}"); assertThat(query.getQueryObject(), is(reference.getQueryObject())); } - /** - * @see DATAMONGO-995, DATAMONGO-420 - */ - @Test + @Test // DATAMONGO-995, DATAMONGO-420 public void bindsSimplePropertyAlreadyQuotedWithRegexCorrectly() throws Exception { - ConvertingParameterAccessor accesor = StubParameterAccessor.getAccessor(converter, "^Mat.*"); + ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, "^Mat.*"); StringBasedMongoQuery mongoQuery = createQueryForMethod("findByLastnameQuoted", String.class); - org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accesor); + org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor); org.springframework.data.mongodb.core.query.Query reference = new BasicQuery("{'lastname' : '^Mat.*'}"); assertThat(query.getQueryObject(), is(reference.getQueryObject())); } - /** - * @see DATAMONGO-995, DATAMONGO-420 - */ - @Test + @Test // DATAMONGO-995, DATAMONGO-420 public void bindsSimplePropertyWithRegexCorrectly() throws Exception { StringBasedMongoQuery mongoQuery = createQueryForMethod("findByLastname", String.class); - ConvertingParameterAccessor accesor = StubParameterAccessor.getAccessor(converter, "^Mat.*"); + ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, "^Mat.*"); - org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accesor); + org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor); org.springframework.data.mongodb.core.query.Query reference = new BasicQuery("{'lastname' : '^Mat.*'}"); assertThat(query.getQueryObject(), is(reference.getQueryObject())); } - /** - * @see DATAMONGO-1070 - */ - @Test + @Test // DATAMONGO-1070 public void parsesDbRefDeclarationsCorrectly() throws Exception { StringBasedMongoQuery mongoQuery = createQueryForMethod("methodWithManuallyDefinedDbRef", String.class); @@ -282,10 +256,7 @@ public void parsesDbRefDeclarationsCorrectly() throws Exception { assertThat(dbRef.getCollectionName(), is("reference")); } - /** - * @see DATAMONGO-1072 - */ - @Test + @Test // DATAMONGO-1072 public void shouldParseJsonKeyReplacementCorrectly() throws Exception { StringBasedMongoQuery mongoQuery = createQueryForMethod("methodWithPlaceholderInKeyOfJsonStructure", String.class, @@ -297,71 +268,175 @@ public void shouldParseJsonKeyReplacementCorrectly() throws Exception { assertThat(query.getQueryObject(), is(new BasicDBObjectBuilder().add("key", "value").get())); } - /** - * @see DATAMONGO-990 - */ - @Test + @Test // DATAMONGO-990 public void shouldSupportExpressionsInCustomQueries() throws Exception { - ConvertingParameterAccessor accesor = StubParameterAccessor.getAccessor(converter, "Matthews"); + ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, "Matthews"); StringBasedMongoQuery mongoQuery = createQueryForMethod("findByQueryWithExpression", String.class); - org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accesor); + org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor); org.springframework.data.mongodb.core.query.Query reference = new BasicQuery("{'lastname' : 'Matthews'}"); assertThat(query.getQueryObject(), is(reference.getQueryObject())); } - /** - * @see DATAMONGO-1244 - */ - @Test + @Test // DATAMONGO-1244 public void shouldSupportExpressionsInCustomQueriesWithNestedObject() throws Exception { - ConvertingParameterAccessor accesor = StubParameterAccessor.getAccessor(converter, true, "param1", "param2"); + ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, true, "param1", "param2"); StringBasedMongoQuery mongoQuery = createQueryForMethod("findByQueryWithExpressionAndNestedObject", boolean.class, String.class); - org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accesor); + org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor); org.springframework.data.mongodb.core.query.Query reference = new BasicQuery("{ \"id\" : { \"$exists\" : true}}"); assertThat(query.getQueryObject(), is(reference.getQueryObject())); } - /** - * @see DATAMONGO-1244 - */ - @Test + @Test // DATAMONGO-1244 public void shouldSupportExpressionsInCustomQueriesWithMultipleNestedObjects() throws Exception { - ConvertingParameterAccessor accesor = StubParameterAccessor.getAccessor(converter, true, "param1", "param2"); + ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, true, "param1", "param2"); StringBasedMongoQuery mongoQuery = createQueryForMethod("findByQueryWithExpressionAndMultipleNestedObjects", boolean.class, String.class, String.class); - org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accesor); + org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor); org.springframework.data.mongodb.core.query.Query reference = new BasicQuery( "{ \"id\" : { \"$exists\" : true} , \"foo\" : 42 , \"bar\" : { \"$exists\" : false}}"); assertThat(query.getQueryObject(), is(reference.getQueryObject())); } - /** - * @see DATAMONGO-1290 - */ - @Test + @Test // DATAMONGO-1290 public void shouldSupportNonQuotedBinaryDataReplacement() throws Exception { byte[] binaryData = "Matthews".getBytes("UTF-8"); - ConvertingParameterAccessor accesor = StubParameterAccessor.getAccessor(converter, binaryData); + ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, binaryData); StringBasedMongoQuery mongoQuery = createQueryForMethod("findByLastnameAsBinary", byte[].class); - org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accesor); + org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor); org.springframework.data.mongodb.core.query.Query reference = new BasicQuery("{'lastname' : { '$binary' : '" + DatatypeConverter.printBase64Binary(binaryData) + "', '$type' : " + BSON.B_GENERAL + "}}"); assertThat(query.getQueryObject(), is(reference.getQueryObject())); } + @Test // DATAMONGO-1454 + public void shouldSupportExistsProjection() throws Exception { + + StringBasedMongoQuery mongoQuery = createQueryForMethod("existsByLastname", String.class); + + assertThat(mongoQuery.isExistsQuery(), is(true)); + } + + @Test // DATAMONGO-1565 + public void bindsPropertyReferenceMultipleTimesCorrectly() throws Exception { + + StringBasedMongoQuery mongoQuery = createQueryForMethod("findByAgeQuotedAndUnquoted", Integer.TYPE); + + ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, 3); + + org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor); + BasicDBList or = new BasicDBList(); + or.add(new BasicDBObject("age", 3)); + or.add(new BasicDBObject("displayAge", "3")); + BasicDBObject queryObject = new BasicDBObject("$or", or); + org.springframework.data.mongodb.core.query.Query reference = new BasicQuery(queryObject); + + assertThat(query.getQueryObject(), is(reference.getQueryObject())); + } + + @Test // DATAMONGO-1565 + public void shouldIgnorePlaceholderPatternInReplacementValue() throws Exception { + + ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, "argWith?1andText", + "nothing-special"); + + StringBasedMongoQuery mongoQuery = createQueryForMethod("findByStringWithWildcardChar", String.class, String.class); + + org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor); + assertThat(query.getQueryObject(), + is(JSON.parse("{ \"arg0\" : \"argWith?1andText\" , \"arg1\" : \"nothing-special\"}"))); + } + + @Test // DATAMONGO-1565 + public void shouldQuoteStringReplacementCorrectly() throws Exception { + + StringBasedMongoQuery mongoQuery = createQueryForMethod("findByLastnameQuoted", String.class); + ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, "Matthews', password: 'foo"); + + org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor); + assertThat(query.getQueryObject(), + is(not(new BasicDBObjectBuilder().add("lastname", "Matthews").add("password", "foo").get()))); + assertThat(query.getQueryObject(), is((DBObject) new BasicDBObject("lastname", "Matthews', password: 'foo"))); + } + + @Test // DATAMONGO-1565 + public void shouldQuoteStringReplacementContainingQuotesCorrectly() throws Exception { + + StringBasedMongoQuery mongoQuery = createQueryForMethod("findByLastnameQuoted", String.class); + ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, "Matthews\", password: \"foo"); + + org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor); + assertThat(query.getQueryObject(), + is(not(new BasicDBObjectBuilder().add("lastname", "Matthews").add("password", "foo").get()))); + assertThat(query.getQueryObject(), is((DBObject) new BasicDBObject("lastname", "Matthews\", password: \"foo"))); + } + + @Test // DATAMONGO-1565 + public void shouldQuoteStringReplacementWithQuotationsCorrectly() throws Exception { + + StringBasedMongoQuery mongoQuery = createQueryForMethod("findByLastnameQuoted", String.class); + ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, + "\"Dave Matthews\", password: 'foo"); + + org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor); + assertThat(query.getQueryObject(), + is((DBObject) new BasicDBObject("lastname", "\"Dave Matthews\", password: 'foo"))); + } + + @Test // DATAMONGO-1565, DATAMONGO-1575 + public void shouldQuoteComplexQueryStringCorrectly() throws Exception { + + StringBasedMongoQuery mongoQuery = createQueryForMethod("findByLastnameQuoted", String.class); + ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, "{ $ne : \"calamity\" }"); + + org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor); + assertThat(query.getQueryObject(), is((DBObject) new BasicDBObject("lastname", "{ $ne : \"calamity\" }"))); + } + + @Test // DATAMONGO-1565, DATAMONGO-1575 + public void shouldQuotationInQuotedComplexQueryString() throws Exception { + + StringBasedMongoQuery mongoQuery = createQueryForMethod("findByLastnameQuoted", String.class); + ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, + "{ $ne : \"\\\"calamity\\\"\" }"); + + org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor); + assertThat(query.getQueryObject(), is((DBObject) new BasicDBObject("lastname", "{ $ne : \"\\\"calamity\\\"\" }"))); + } + + @Test // DATAMONGO-1575 + public void shouldTakeBsonParameterAsIs() throws Exception { + + StringBasedMongoQuery mongoQuery = createQueryForMethod("findByWithBsonArgument", DBObject.class); + ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, + new BasicDBObject("$regex", "^calamity$")); + + org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor); + assertThat(query.getQueryObject(), is((DBObject) new BasicDBObject("arg0", Pattern.compile("^calamity$")))); + } + + @Test // DATAMONGO-1575 + public void shouldReplaceParametersInInQuotedExpressionOfNestedQueryOperator() throws Exception { + + StringBasedMongoQuery mongoQuery = createQueryForMethod("findByLastnameRegex", String.class); + ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, "calamity"); + + org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor); + assertThat(query.getQueryObject(), is((DBObject) new BasicDBObject("lastname", Pattern.compile("^(calamity)")))); + } + private StringBasedMongoQuery createQueryForMethod(String name, Class... parameters) throws Exception { Method method = SampleRepository.class.getMethod(name, parameters); @@ -382,6 +457,9 @@ private interface SampleRepository extends Repository { @Query("{ 'lastname' : '?0' }") Person findByLastnameQuoted(String lastname); + @Query("{ 'lastname' : { '$regex' : '^(?0)'} }") + Person findByLastnameRegex(String lastname); + @Query("{ 'address' : ?0 }") Person findByAddress(Address address); @@ -420,5 +498,17 @@ private interface SampleRepository extends Repository { @Query("{'id':?#{ [0] ? { $exists :true} : [1] }, 'foo':42, 'bar': ?#{ [0] ? { $exists :false} : [1] }}") List findByQueryWithExpressionAndMultipleNestedObjects(boolean param0, String param1, String param2); + + @Query(value = "{ $or : [{'age' : ?0 }, {'displayAge' : '?0'}] }") + boolean findByAgeQuotedAndUnquoted(int age); + + @Query(value = "{ 'lastname' : ?0 }", exists = true) + boolean existsByLastname(String lastname); + + @Query("{ 'arg0' : ?0, 'arg1' : ?1 }") + List findByStringWithWildcardChar(String arg0, String arg1); + + @Query("{ 'arg0' : ?0 }") + List findByWithBsonArgument(DBObject arg0); } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/MongoRepositoryFactoryBeanUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/MongoRepositoryFactoryBeanUnitTests.java index 0d0365e557..76f8185183 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/MongoRepositoryFactoryBeanUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/MongoRepositoryFactoryBeanUnitTests.java @@ -48,7 +48,7 @@ public class MongoRepositoryFactoryBeanUnitTests { @SuppressWarnings("rawtypes") public void addsIndexEnsuringQueryCreationListenerIfConfigured() { - MongoRepositoryFactoryBean factory = new MongoRepositoryFactoryBean(); + MongoRepositoryFactoryBean factory = new MongoRepositoryFactoryBean(ContactRepository.class); factory.setCreateIndexesForQueryMethods(true); List listeners = getListenersFromFactory(factory); @@ -60,7 +60,7 @@ public void addsIndexEnsuringQueryCreationListenerIfConfigured() { @SuppressWarnings("rawtypes") public void doesNotAddIndexEnsuringQueryCreationListenerByDefault() { - List listeners = getListenersFromFactory(new MongoRepositoryFactoryBean()); + List listeners = getListenersFromFactory(new MongoRepositoryFactoryBean(ContactRepository.class)); assertThat(listeners.size(), is(1)); } @@ -72,7 +72,6 @@ private List getListenersFromFactory(MongoRepositoryFactoryBean factoryB factoryBean.setLazyInit(true); factoryBean.setMongoOperations(operations); - factoryBean.setRepositoryInterface(ContactRepository.class); factoryBean.afterPropertiesSet(); RepositoryFactorySupport factory = factoryBean.createRepositoryFactory(); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/MongoRepositoryFactoryUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/MongoRepositoryFactoryUnitTests.java index be030e07ad..68282293b2 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/MongoRepositoryFactoryUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/MongoRepositoryFactoryUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011 the original author or authors. + * Copyright 2011-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -75,10 +75,7 @@ public void usesMappingMongoEntityInformationIfMappingContextSet() { assertTrue(entityInformation instanceof MappingMongoEntityInformation); } - /** - * @see DATAMONGO-385 - */ - @Test + @Test // DATAMONGO-385 @SuppressWarnings("unchecked") public void createsRepositoryWithIdTypeLong() { diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/PersistableMappingMongoEntityInformationUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/PersistableMappingMongoEntityInformationUnitTests.java new file mode 100644 index 0000000000..c5ebcb86fd --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/PersistableMappingMongoEntityInformationUnitTests.java @@ -0,0 +1,65 @@ +/* + * Copyright 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.mongodb.repository.support; + +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +import lombok.Value; + +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.domain.Persistable; +import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity; + +/** + * Tests for {@link PersistableMongoEntityInformation}. + * + * @author Christoph Strobl + * @author Oliver Gierke + */ +@RunWith(MockitoJUnitRunner.class) +public class PersistableMappingMongoEntityInformationUnitTests { + + @Mock MongoPersistentEntity persistableImplementingEntityTypeInfo; + + @Before + public void setUp() { + when(persistableImplementingEntityTypeInfo.getType()).thenReturn(TypeImplementingPersistable.class); + } + + @Test // DATAMONGO-1590 + public void considersPersistableIsNew() { + + PersistableMongoEntityInformation information = new PersistableMongoEntityInformation( + new MappingMongoEntityInformation(persistableImplementingEntityTypeInfo)); + + assertThat(information.isNew(new TypeImplementingPersistable(100L, false)), is(false)); + } + + @Value + static class TypeImplementingPersistable implements Persistable { + + private static final long serialVersionUID = -1619090149320971099L; + + Long id; + boolean isNew; + } +} diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/QueryDslMongoRepositoryIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/QueryDslMongoRepositoryIntegrationTests.java index 419683ccc3..de30bf44df 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/QueryDslMongoRepositoryIntegrationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/QueryDslMongoRepositoryIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 the original author or authors. + * Copyright 2015-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -70,10 +70,7 @@ public void setup() { repository.save(Arrays.asList(oliver, dave, carter)); } - /** - * @see DATAMONGO-1146 - */ - @Test + @Test // DATAMONGO-1146 public void shouldSupportExistsWithPredicate() throws Exception { assertThat(repository.exists(person.firstname.eq("Dave")), is(true)); @@ -81,10 +78,7 @@ public void shouldSupportExistsWithPredicate() throws Exception { assertThat(repository.exists((Predicate) null), is(true)); } - /** - * @see DATAMONGO-1167 - */ - @Test + @Test // DATAMONGO-1167 public void shouldSupportFindAllWithPredicateAndSort() { List users = repository.findAll(person.lastname.isNotNull(), new Sort(Direction.ASC, "firstname")); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/QuerydslRepositorySupportTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/QuerydslRepositorySupportTests.java index 814a1be19f..e4b6ba694c 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/QuerydslRepositorySupportTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/QuerydslRepositorySupportTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2016 the original author or authors. + * Copyright 2011-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -65,10 +65,7 @@ public void providesMongoQuery() { assertThat(query.fetchOne(), is(person)); } - /** - * @see DATAMONGO-1063 - */ - @Test + @Test // DATAMONGO-1063 public void shouldAllowAny() { person.setSkills(Arrays.asList("vocalist", "songwriter", "guitarist")); @@ -82,10 +79,7 @@ public void shouldAllowAny() { assertThat(query.fetchOne(), is(person)); } - /** - * @see DATAMONGO-1394 - */ - @Test + @Test // DATAMONGO-1394 public void shouldAllowDbRefAgainstIdProperty() { User bart = new User(); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/SimpleMongoRepositoryTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/SimpleMongoRepositoryTests.java index 81f3661f7d..10aaa8564b 100755 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/SimpleMongoRepositoryTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/SimpleMongoRepositoryTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2010-2016 the original author or authors. + * Copyright 2010-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -113,10 +113,7 @@ public void deleteByIdFromCustomCollectionName() { assertThat(result, not(hasItem(dave))); } - /** - * @see DATAMONGO-1054 - */ - @Test + @Test // DATAMONGO-1054 public void shouldInsertSingle() { String randomId = UUID.randomUUID().toString(); @@ -129,10 +126,7 @@ public void shouldInsertSingle() { assertThat(saved, is(equalTo(person1))); } - /** - * @see DATAMONGO-1054 - */ - @Test + @Test // DATAMONGO-1054 public void shouldInsertMultipleFromList() { String randomId = UUID.randomUUID().toString(); @@ -151,10 +145,7 @@ public void shouldInsertMultipleFromList() { assertThatAllReferencePersonsWereStoredCorrectly(idToPerson, saved); } - /** - * @see DATAMONGO-1054 - */ - @Test + @Test // DATAMONGO-1054 public void shouldInsertMutlipleFromSet() { String randomId = UUID.randomUUID().toString(); @@ -173,10 +164,7 @@ public void shouldInsertMutlipleFromSet() { assertThatAllReferencePersonsWereStoredCorrectly(idToPerson, saved); } - /** - * @see DATAMONGO-1245, DATAMONGO-1464 - */ - @Test + @Test // DATAMONGO-1245, DATAMONGO-1464 public void findByExampleShouldLookUpEntriesCorrectly() { Person sample = new Person(); @@ -190,10 +178,7 @@ public void findByExampleShouldLookUpEntriesCorrectly() { assertThat(result.getTotalPages(), is(1)); } - /** - * @see DATAMONGO-1464 - */ - @Test + @Test // DATAMONGO-1464 public void findByExampleMultiplePagesShouldLookUpEntriesCorrectly() { Person sample = new Person(); @@ -206,10 +191,7 @@ public void findByExampleMultiplePagesShouldLookUpEntriesCorrectly() { assertThat(result.getTotalPages(), is(2)); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void findAllByExampleShouldLookUpEntriesCorrectly() { Person sample = new Person(); @@ -222,10 +204,7 @@ public void findAllByExampleShouldLookUpEntriesCorrectly() { assertThat(result, hasSize(2)); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void findAllByExampleShouldLookUpEntriesCorrectlyWhenUsingNestedObject() { dave.setAddress(new Address("1600 Pennsylvania Ave NW", "20500", "Washington")); @@ -244,10 +223,7 @@ public void findAllByExampleShouldLookUpEntriesCorrectlyWhenUsingNestedObject() assertThat(result, hasSize(1)); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void findAllByExampleShouldLookUpEntriesCorrectlyWhenUsingPartialNestedObject() { dave.setAddress(new Address("1600 Pennsylvania Ave NW", "20500", "Washington")); @@ -266,10 +242,7 @@ public void findAllByExampleShouldLookUpEntriesCorrectlyWhenUsingPartialNestedOb assertThat(result, hasSize(2)); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void findAllByExampleShouldNotFindEntriesWhenUsingPartialNestedObjectInStrictMode() { dave.setAddress(new Address("1600 Pennsylvania Ave NW", "20500", "Washington")); @@ -285,10 +258,7 @@ public void findAllByExampleShouldNotFindEntriesWhenUsingPartialNestedObjectInSt assertThat(result, empty()); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void findAllByExampleShouldLookUpEntriesCorrectlyWhenUsingNestedObjectInStrictMode() { dave.setAddress(new Address("1600 Pennsylvania Ave NW", "20500", "Washington")); @@ -305,10 +275,7 @@ public void findAllByExampleShouldLookUpEntriesCorrectlyWhenUsingNestedObjectInS assertThat(result, hasSize(1)); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void findAllByExampleShouldRespectStringMatchMode() { Person sample = new Person(); @@ -322,10 +289,7 @@ public void findAllByExampleShouldRespectStringMatchMode() { assertThat(result, hasSize(2)); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void findAllByExampleShouldResolveDbRefCorrectly() { User user = new User(); @@ -348,10 +312,7 @@ public void findAllByExampleShouldResolveDbRefCorrectly() { assertThat(result, hasSize(1)); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void findAllByExampleShouldResolveLegacyCoordinatesCorrectly() { Person megan = new Person("megan", "tarash"); @@ -369,10 +330,7 @@ public void findAllByExampleShouldResolveLegacyCoordinatesCorrectly() { assertThat(result, hasSize(1)); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void findAllByExampleShouldResolveGeoJsonCoordinatesCorrectly() { Person megan = new Person("megan", "tarash"); @@ -390,10 +348,7 @@ public void findAllByExampleShouldResolveGeoJsonCoordinatesCorrectly() { assertThat(result, hasSize(1)); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void findAllByExampleShouldProcessInheritanceCorrectly() { PersonExtended reference = new PersonExtended(); @@ -412,10 +367,7 @@ public void findAllByExampleShouldProcessInheritanceCorrectly() { assertThat(result, hasItem(reference)); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void findOneByExampleShouldLookUpEntriesCorrectly() { Person sample = new Person(); @@ -428,10 +380,7 @@ public void findOneByExampleShouldLookUpEntriesCorrectly() { assertThat(result, is(equalTo(dave))); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void existsByExampleShouldLookUpEntriesCorrectly() { Person sample = new Person(); @@ -444,10 +393,7 @@ public void existsByExampleShouldLookUpEntriesCorrectly() { assertThat(result, is(true)); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void countByExampleShouldLookUpEntriesCorrectly() { Person sample = new Person(); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/SpringDataMongodbSerializerUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/SpringDataMongodbSerializerUnitTests.java index 986a29b4ba..bf20640b53 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/SpringDataMongodbSerializerUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/SpringDataMongodbSerializerUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2016 the original author or authors. + * Copyright 2011-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -103,20 +103,14 @@ public void convertsComplexObjectOnSerializing() { assertThat(value, is(reference)); } - /** - * @see DATAMONGO-376 - */ - @Test + @Test // DATAMONGO-376 public void returnsEmptyStringIfNoPathExpressionIsGiven() { QAddress address = QPerson.person.shippingAddresses.any(); assertThat(serializer.getKeyForPath(address, address.getMetadata()), is("")); } - /** - * @see DATAMONGO-467 - */ - @Test + @Test // DATAMONGO-467 public void convertsIdPropertyCorrectly() { ObjectId id = new ObjectId(); @@ -130,10 +124,7 @@ public void convertsIdPropertyCorrectly() { assertThat(result.get("_id"), is((Object) id)); } - /** - * @see DATAMONGO-761 - */ - @Test + @Test // DATAMONGO-761 public void looksUpKeyForNonPropertyPath() { PathBuilder
        builder = new PathBuilder
        (Address.class, "address"); @@ -143,10 +134,7 @@ public void looksUpKeyForNonPropertyPath() { assertThat(path, is("0")); } - /** - * @see DATAMONGO-969 - */ - @Test + @Test // DATAMONGO-969 public void shouldConvertObjectIdEvenWhenNestedInOperatorDbObject() { ObjectId value = new ObjectId("53bb9fd14438765b29c2d56e"); @@ -157,10 +145,7 @@ public void shouldConvertObjectIdEvenWhenNestedInOperatorDbObject() { assertThat($ne, is(value)); } - /** - * @see DATAMONGO-969 - */ - @Test + @Test // DATAMONGO-969 public void shouldConvertCollectionOfObjectIdEvenWhenNestedInOperatorDbObject() { ObjectId firstId = new ObjectId("53bb9fd14438765b29c2d56e"); @@ -178,10 +163,7 @@ public void shouldConvertCollectionOfObjectIdEvenWhenNestedInOperatorDbObject() assertThat($in, Matchers. arrayContaining(firstId, secondId)); } - /** - * @see DATAMONGO-1485 - */ - @Test + @Test // DATAMONGO-1485 public void takesCustomConversionForEnumsIntoAccount() { MongoMappingContext context = new MongoMappingContext(); diff --git a/spring-data-mongodb/src/test/resources/org/springframework/data/mongodb/repository/PersonRepositoryIntegrationTests-context.xml b/spring-data-mongodb/src/test/resources/org/springframework/data/mongodb/repository/PersonRepositoryIntegrationTests-context.xml index 7e7271d855..119940c359 100644 --- a/spring-data-mongodb/src/test/resources/org/springframework/data/mongodb/repository/PersonRepositoryIntegrationTests-context.xml +++ b/spring-data-mongodb/src/test/resources/org/springframework/data/mongodb/repository/PersonRepositoryIntegrationTests-context.xml @@ -17,8 +17,8 @@ + - @@ -34,5 +34,5 @@ - + diff --git a/src/main/asciidoc/index.adoc b/src/main/asciidoc/index.adoc index aee79f8aed..74b274a81d 100644 --- a/src/main/asciidoc/index.adoc +++ b/src/main/asciidoc/index.adoc @@ -6,7 +6,7 @@ Mark Pollack; Thomas Risberg; Oliver Gierke; Costin Leau; Jon Brisbin; Thomas Da :toc-placement!: :spring-data-commons-docs: ../../../../spring-data-commons/src/main/asciidoc -(C) 2008-2015 The original authors. +(C) 2008-2017 The original authors. NOTE: _Copies of this document may be made for your own use and for distribution to others, provided that you do not charge any fee for such copies and further provided that each copy contains this Copyright Notice, whether distributed in print or electronically._ diff --git a/src/main/asciidoc/new-features.adoc b/src/main/asciidoc/new-features.adoc index 8066c6a086..1c771b996e 100644 --- a/src/main/asciidoc/new-features.adoc +++ b/src/main/asciidoc/new-features.adoc @@ -3,8 +3,15 @@ [[new-features.1-10-0]] == What's new in Spring Data MongoDB 1.10 -* Support for `$min`, `$max` and `$slice` operators via `Update`. -* Support for `$cond` and `$ifNull` operators via `Aggregation`. +* Compatible with MongoDB Server 3.4 and the MongoDB Java Driver 3.4. +* New annotations for `@CountQuery`, `@DeleteQuery` and `@ExistsQuery`. +* Extended support for MongoDB 3.2 and MongoDB 3.4 aggregation operators (see <>). +* Support partial filter expression when creating indexes. +* Publish lifecycle events when loading/converting ``DBRef``s. +* Added any-match mode for Query By Example. +* Support for `$caseSensitive` and `$diacriticSensitive` text search. +* Support for GeoJSON Polygon with hole. +* Performance improvements by bulk fetching ``DBRef``s. [[new-features.1-9-0]] == What's new in Spring Data MongoDB 1.9 diff --git a/src/main/asciidoc/preface.adoc b/src/main/asciidoc/preface.adoc index 8efc6f2d27..e8dd0f5b56 100644 --- a/src/main/asciidoc/preface.adoc +++ b/src/main/asciidoc/preface.adoc @@ -5,19 +5,19 @@ The Spring Data MongoDB project applies core Spring concepts to the development This document is the reference guide for Spring Data - Document Support. It explains Document module concepts and semantics and the syntax for various store namespaces. -This section provides some basic introduction to Spring and Document database. The rest of the document refers only to Spring Data Document features and assumes the user is familiar with document databases such as MongoDB and CouchDB as well as Spring concepts. +This section provides some basic introduction to Spring and Document databases. The rest of the document refers only to Spring Data MongoDB features and assumes the user is familiar with MongoDB and Spring concepts. [[get-started:first-steps:spring]] == Knowing Spring -Spring Data uses Spring framework's http://docs.spring.io/spring/docs/4.2.x/spring-framework-reference/html/spring-core.html[core] functionality, such as the http://docs.spring.io/spring/docs/{springVersion}/spring-framework-reference/html/beans.html[IoC] container, http://docs.spring.io/spring/docs/{springVersion}/spring-framework-reference/html/validation.html#core-convert[type conversion system], http://docs.spring.io/spring/docs/{springVersion}/spring-framework-reference/html/expressions.html[expression language], http://docs.spring.io/spring/docs/{springVersion}/spring-framework-reference/html/jmx.html[JMX integration], and portable http://docs.spring.io/spring/docs/{springVersion}/spring-framework-reference/html/dao.html#dao-exceptions[DAO exception hierarchy]. While it is not important to know the Spring APIs, understanding the concepts behind them is. At a minimum, the idea behind IoC should be familiar for whatever IoC container you choose to use. +Spring Data uses Spring framework's http://docs.spring.io/spring/docs/{springVersion}/spring-framework-reference/html/spring-core.html[core] functionality, such as the http://docs.spring.io/spring/docs/{springVersion}/spring-framework-reference/html/beans.html[IoC] container, http://docs.spring.io/spring/docs/{springVersion}/spring-framework-reference/html/validation.html#core-convert[type conversion system], http://docs.spring.io/spring/docs/{springVersion}/spring-framework-reference/html/expressions.html[expression language], http://docs.spring.io/spring/docs/{springVersion}/spring-framework-reference/html/jmx.html[JMX integration], and portable http://docs.spring.io/spring/docs/{springVersion}/spring-framework-reference/html/dao.html#dao-exceptions[DAO exception hierarchy]. While it is not important to know the Spring APIs, understanding the concepts behind them is. At a minimum, the idea behind IoC should be familiar for whatever IoC container you choose to use. -The core functionality of the MongoDB and CouchDB support can be used directly, with no need to invoke the IoC services of the Spring Container. This is much like `JdbcTemplate` which can be used 'standalone' without any other services of the Spring container. To leverage all the features of Spring Data document, such as the repository support, you will need to configure some parts of the library using Spring. +The core functionality of the MongoDB support can be used directly, with no need to invoke the IoC services of the Spring Container. This is much like `JdbcTemplate` which can be used 'standalone' without any other services of the Spring container. To leverage all the features of Spring Data MongoDB, such as the repository support, you will need to configure some parts of the library using Spring. -To learn more about Spring, you can refer to the comprehensive (and sometimes disarming) documentation that explains in detail the Spring Framework. There are a lot of articles, blog entries and books on the matter - take a look at the Spring framework http://spring.io/docs[home page ] for more information. +To learn more about Spring, you can refer to the comprehensive (and sometimes disarming) documentation that explains in detail the Spring Framework. There are a lot of articles, blog entries and books on the matter - take a look at the Spring framework http://spring.io/docs[home page] for more information. [[get-started:first-steps:nosql]] == Knowing NoSQL and Document databases -NoSQL stores have taken the storage world by storm. It is a vast domain with a plethora of solutions, terms and patterns (to make things worse even the term itself has multiple http://www.google.com/search?q=nosoql+acronym[meanings]). While some of the principles are common, it is crucial that the user is familiar to some degree with the stores supported by DATADOC. The best way to get acquainted to this solutions is to read their documentation and follow their examples - it usually doesn't take more then 5-10 minutes to go through them and if you are coming from an RDMBS-only background many times these exercises can be an eye opener. +NoSQL stores have taken the storage world by storm. It is a vast domain with a plethora of solutions, terms and patterns (to make things worse even the term itself has multiple http://www.google.com/search?q=nosoql+acronym[meanings]). While some of the principles are common, it is crucial that the user is familiar to some degree with MongoDB. The best way to get acquainted to this solutions is to read their documentation and follow their examples - it usually doesn't take more then 5-10 minutes to go through them and if you are coming from an RDMBS-only background many times these exercises can be an eye opener. The jumping off ground for learning about MongoDB is http://www.mongodb.org/[www.mongodb.org]. Here is a list of other useful resources: @@ -36,7 +36,7 @@ In terms of document stores, http://www.mongodb.org/[MongoDB] at least 2.6. == Additional Help Resources -Learning a new framework is not always straight forward. In this section, we try to provide what we think is an easy to follow guide for starting with Spring Data Document module. However, if you encounter issues or you are just looking for an advice, feel free to use one of the links below: +Learning a new framework is not always straight forward. In this section, we try to provide what we think is an easy to follow guide for starting with Spring Data MongoDB module. However, if you encounter issues or you are just looking for an advice, feel free to use one of the links below: [[get-started:help]] === Support diff --git a/src/main/asciidoc/reference/cross-store.adoc b/src/main/asciidoc/reference/cross-store.adoc index 01a5991616..fda6aa1244 100644 --- a/src/main/asciidoc/reference/cross-store.adoc +++ b/src/main/asciidoc/reference/cross-store.adoc @@ -157,7 +157,7 @@ Finally, you need to configure your project to use MongoDB and also configure th [[mongodb_cross-store-application]] == Writing the Cross Store Application -We are assuming that you have a working JPA application so we will only cover the additional steps needed to persist part of your Entity in your Mongo database. First you need to identify the field you want persisted. It should be a domain class and follow the general rules for the Mongo mapping support covered in previous chapters. The field you want persisted in MongoDB should be annotated using the `@RelatedDocument` annotation. That is really all you need to do!. The cross-store aspects take care of the rest. This includes marking the field with `@Transient` so it won't be persisted using JPA, keeping track of any changes made to the field value and writing them to the database on successful transaction completion, loading the document from MongoDB the first time the value is used in your application. Here is an example of a simple Entity that has a field annotated with `@RelatedEntity`. +We are assuming that you have a working JPA application so we will only cover the additional steps needed to persist part of your Entity in your Mongo database. First you need to identify the field you want persisted. It should be a domain class and follow the general rules for the Mongo mapping support covered in previous chapters. The field you want persisted in MongoDB should be annotated using the `@RelatedDocument` annotation. That is really all you need to do!. The cross-store aspects take care of the rest. This includes marking the field with `@Transient` so it won't be persisted using JPA, keeping track of any changes made to the field value and writing them to the database on successful transaction completion, loading the document from MongoDB the first time the value is used in your application. Here is an example of a simple Entity that has a field annotated with `@RelatedDocument`. .Example of Entity with @RelatedDocument ==== diff --git a/src/main/asciidoc/reference/introduction.adoc b/src/main/asciidoc/reference/introduction.adoc index d224c29d37..ff14c7a69c 100644 --- a/src/main/asciidoc/reference/introduction.adoc +++ b/src/main/asciidoc/reference/introduction.adoc @@ -3,7 +3,7 @@ == Document Structure -This part of the reference documentation explains the core functionality offered by Spring Data Document. +This part of the reference documentation explains the core functionality offered by Spring Data MongoDB. <> introduces the MongoDB module feature set. diff --git a/src/main/asciidoc/reference/logging.adoc b/src/main/asciidoc/reference/logging.adoc index 5cd3c5d401..d1cdbe05ae 100644 --- a/src/main/asciidoc/reference/logging.adoc +++ b/src/main/asciidoc/reference/logging.adoc @@ -10,17 +10,17 @@ Here is an example configuration [source] ---- -log4j.rootCategory=INFO, stdout - -log4j.appender.stdout=org.springframework.data.document.mongodb.log4j.MongoLog4jAppender -log4j.appender.stdout.layout=org.apache.log4j.PatternLayout -log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - <%m>%n -log4j.appender.stdout.host = localhost -log4j.appender.stdout.port = 27017 -log4j.appender.stdout.database = logs -log4j.appender.stdout.collectionPattern = %X{year}%X{month} -log4j.appender.stdout.applicationId = my.application -log4j.appender.stdout.warnOrHigherWriteConcern = FSYNC_SAFE +log4j.rootCategory=INFO, mongo + +log4j.appender.mongo=org.springframework.data.document.mongodb.log4j.MongoLog4jAppender +log4j.appender.mongo.layout=org.apache.log4j.PatternLayout +log4j.appender.mongo.layout.ConversionPattern=%d %p [%c] - <%m>%n +log4j.appender.mongo.host = localhost +log4j.appender.mongo.port = 27017 +log4j.appender.mongo.database = logs +log4j.appender.mongo.collectionPattern = %X{year}%X{month} +log4j.appender.mongo.applicationId = my.application +log4j.appender.mongo.warnOrHigherWriteConcern = FSYNC_SAFE log4j.category.org.apache.activemq=ERROR log4j.category.org.springframework.batch=DEBUG @@ -28,6 +28,23 @@ log4j.category.org.springframework.data.document.mongodb=DEBUG log4j.category.org.springframework.transaction=INFO ---- -The important configuration to look at aside from host and port is the database and collectionPattern. The variables year, month, day and hour are available for you to use in forming a collection name. This is to support the common convention of grouping log information in a collection that corresponds to a specific time period, for example a collection per day. +The important configuration to look at aside from host and port is the database and `collectionPattern`. The variables `year`, `month`, `day` and `hour` are available for you to use in forming a collection name. This is to support the common convention of grouping log information in a collection that corresponds to a specific time period, for example a collection per day. -There is also an applicationId which is put into the stored message. The document stored from logging as the following keys: level, name, applicationId, timestamp, properties, traceback, and message. +There is also an `applicationId` which is put into the stored message. The document stored from logging as the following keys: `level`, `name`, `applicationId`, `timestamp`, `properties`, `traceback`, and `message`. + +[[mongodb:logging-configuration:authentication]] +=== Using authentication + +The MongoDB Log4j appender can be configured to use username/password authentication. +Authentication is performed using the specified database. A different `authenticationDatabase` can be specified to override the default behavior. + +[source] +---- +# ... +log4j.appender.mongo.username = admin +log4j.appender.mongo.password = test +log4j.appender.mongo.authenticationDatabase = logs +# ... +---- + +NOTE: Authentication failures lead to exceptions during logging and are propagated to the caller of the logging method. diff --git a/src/main/asciidoc/reference/mapping.adoc b/src/main/asciidoc/reference/mapping.adoc index e3912b4772..af758f77c5 100644 --- a/src/main/asciidoc/reference/mapping.adoc +++ b/src/main/asciidoc/reference/mapping.adoc @@ -1,16 +1,16 @@ [[mapping-chapter]] = Mapping -Rich mapping support is provided by the `MongoMappingConverter`. `MongoMappingConverter` has a rich metadata model that provides a full feature set of functionality to map domain objects to MongoDB documents.The mapping metadata model is populated using annotations on your domain objects. However, the infrastructure is not limited to using annotations as the only source of metadata information. The `MongoMappingConverter` also allows you to map objects to documents without providing any additional metadata, by following a set of conventions. +Rich mapping support is provided by the `MappingMongoConverter`. `MappingMongoConverter` has a rich metadata model that provides a full feature set of functionality to map domain objects to MongoDB documents.The mapping metadata model is populated using annotations on your domain objects. However, the infrastructure is not limited to using annotations as the only source of metadata information. The `MappingMongoConverter` also allows you to map objects to documents without providing any additional metadata, by following a set of conventions. -In this section we will describe the features of the `MongoMappingConverter`. How to use conventions for mapping objects to documents and how to override those conventions with annotation based mapping metadata. +In this section we will describe the features of the `MappingMongoConverter`. How to use conventions for mapping objects to documents and how to override those conventions with annotation based mapping metadata. NOTE: `SimpleMongoConverter` has been deprecated in Spring Data MongoDB M3 as all of its functionality has been subsumed into `MappingMongoConverter`. [[mapping-conventions]] == Convention based Mapping -`MongoMappingConverter` has a few conventions for mapping objects to documents when no additional mapping metadata is provided. The conventions are: +`MappingMongoConverter` has a few conventions for mapping objects to documents when no additional mapping metadata is provided. The conventions are: * The short Java class name is mapped to the collection name in the following manner. The class `com.bigbank.SavingsAccount` maps to `savingsAccount` collection name. * All nested objects are stored as nested objects in the document and *not* as DBRefs @@ -21,7 +21,7 @@ NOTE: `SimpleMongoConverter` has been deprecated in Spring Data MongoDB M3 as al [[mapping.conventions.id-field]] === How the `_id` field is handled in the mapping layer -MongoDB requires that you have an `_id` field for all documents. If you don't provide one the driver will assign a ObjectId with a generated value. The "_id" field can be of any type the, other than arrays, so long as it is unique. The driver naturally supports all primitive types and Dates. When using the `MongoMappingConverter` there are certain rules that govern how properties from the Java class is mapped to this `_id` field. +MongoDB requires that you have an `_id` field for all documents. If you don't provide one the driver will assign a ObjectId with a generated value. The "_id" field can be of any type the, other than arrays, so long as it is unique. The driver naturally supports all primitive types and Dates. When using the `MappingMongoConverter` there are certain rules that govern how properties from the Java class is mapped to this `_id` field. The following outlines what field will be mapped to the `_id` document field: @@ -126,6 +126,10 @@ In addition to these types, Spring Data MongoDB provides a set of built-in conve | native | `{"value" : { … }}` +| `Decimal128` +| native +| `{"value" : NumberDecimal(…)}` + | `AtomicInteger` + calling `get()` before the actual conversion | converter + @@ -246,9 +250,9 @@ calling `get()` before the actual conversion [[mapping-configuration]] == Mapping Configuration -Unless explicitly configured, an instance of `MongoMappingConverter` is created by default when creating a `MongoTemplate`. You can create your own instance of the `MappingMongoConverter` so as to tell it where to scan the classpath at startup your domain classes in order to extract metadata and construct indexes. Also, by creating your own instance you can register Spring converters to use for mapping specific classes to and from the database. +Unless explicitly configured, an instance of `MappingMongoConverter` is created by default when creating a `MongoTemplate`. You can create your own instance of the `MappingMongoConverter` so as to tell it where to scan the classpath at startup your domain classes in order to extract metadata and construct indexes. Also, by creating your own instance you can register Spring converters to use for mapping specific classes to and from the database. -You can configure the `MongoMappingConverter` as well as `com.mongodb.Mongo` and MongoTemplate either using Java or XML based metadata. Here is an example using Spring's Java based configuration +You can configure the `MappingMongoConverter` as well as `com.mongodb.Mongo` and MongoTemplate either using Java or XML based metadata. Here is an example using Spring's Java based configuration .@Configuration class to configure MongoDB mapping support ==== diff --git a/src/main/asciidoc/reference/mongo-3.adoc b/src/main/asciidoc/reference/mongo-3.adoc index c0aee3c949..41d37542b9 100644 --- a/src/main/asciidoc/reference/mongo-3.adoc +++ b/src/main/asciidoc/reference/mongo-3.adoc @@ -88,7 +88,7 @@ This section covers additional things to keep in mind when using the 3.0 driver. * `IndexOperations.resetIndexCache()` is no longer supported. * Any `MapReduceOptions.extraOption` is silently ignored. -* `WriteResult` does not longer hold error informations but throws an Exception. +* `WriteResult` does not longer hold error information but throws an Exception. * `MongoOperations.executeInSession(…)` no longer calls `requestStart` / `requestDone`. * Index name generation has become a driver internal operations, still we use the 2.x schema to generate names. * Some Exception messages differ between the generation 2 and 3 servers as well as between _MMap.v1_ and _WiredTiger_ storage engine. diff --git a/src/main/asciidoc/reference/mongo-repositories.adoc b/src/main/asciidoc/reference/mongo-repositories.adoc index eb053f8926..40d12e2ac6 100644 --- a/src/main/asciidoc/reference/mongo-repositories.adoc +++ b/src/main/asciidoc/reference/mongo-repositories.adoc @@ -366,7 +366,9 @@ public interface PersonRepository extends MongoRepository } ---- -The placeholder ?0 lets you substitute the value from the method arguments into the JSON query string. +The placeholder `?0` lets you substitute the value from the method arguments into the JSON query string. + +NOTE: `String` parameter values are escaped during the binding process, which means that it is not possible to add MongoDB specific operators via the argument. You can also use the filter property to restrict the set of properties that will be mapped into the Java object. For example, @@ -382,6 +384,62 @@ public interface PersonRepository extends MongoRepository This will return only the firstname, lastname and Id properties of the Person objects. The age property, a java.lang.Integer, will not be set and its value will therefore be null. +[[mongodb.repositories.queries.json-spel]] +=== JSON based queries with SpEL expressions + +Query strings and field definitions can be used together with SpEL expressions to create dynamic queries at runtime. +SpEL expressions can provide predicate values and can be used to extend predicates with subdocuments. + +Expressions expose method arguments through an array that contains all arguments. The the following query uses `[0]` +to declare the predicate value for `lastname` that is equivalent to the `?0` parameter binding. + +[source,java] +---- +public interface PersonRepository extends MongoRepository + + @Query("{'lastname': ?#{[0]} }") + List findByQueryWithExpression(String param0); +} +---- + +Expressions can be used to invoke functions, evaluate conditionals and construct values. SpEL expressions +reveal in conjunction with JSON a side-effect as Map-like declarations inside of SpEL read like JSON. + +[source,java] +---- +public interface PersonRepository extends MongoRepository + + @Query("{'id': ?#{ [0] ? {$exists :true} : [1] }}") + List findByQueryWithExpressionAndNestedObject(boolean param0, String param1); +} +---- + +SpEL in query strings can be a powerful way to enhance queries and can accept a broad range of unwanted arguments. +You should make sure to sanitize strings before passing these to the query to avoid unwanted changes to your query. + +Expression support is extensible through the Query SPI `org.springframework.data.repository.query.spi.EvaluationContextExtension` +than can contribute properties, functions and customize the root object. Extensions are retrieved from the application context +at the time of SpEL evaluation when the query is build. + +[source,java] +---- +public class SampleEvaluationContextExtension extends EvaluationContextExtensionSupport { + + @Override + public String getExtensionId() { + return "security"; + } + + @Override + public Map getProperties() { + return Collections.singletonMap("principal", SecurityContextHolder.getCurrent().getPrincipal()); + } +} +---- + +NOTE: Bootstrapping `MongoRepositoryFactory` yourself is not application context-aware and requires further configuration +to pick up Query SPI extensions. + [[mongodb.repositories.queries.type-safe]] === Type-safe Query methods diff --git a/src/main/asciidoc/reference/mongodb.adoc b/src/main/asciidoc/reference/mongodb.adoc index 6e4c4e7b02..342ff17f86 100644 --- a/src/main/asciidoc/reference/mongodb.adoc +++ b/src/main/asciidoc/reference/mongodb.adoc @@ -387,6 +387,20 @@ You can also provide the host and port for the underlying `com.mongodb.Mongo` in password="secret"/> ---- +If your MongoDB authentication database differs from the target database, use the `authentication-dbname` attribute, as shown below. + +[source,xml] +---- + +---- + If you need to configure additional options on the `com.mongodb.Mongo` instance that is used to create a `SimpleMongoDbFactory` you can refer to an existing bean using the `mongo-ref` attribute as shown below. To show another common usage pattern, this listing shows the use of a property placeholder to parametrise the configuration and creating `MongoTemplate`. [source,xml] @@ -422,15 +436,15 @@ The class `MongoTemplate`, located in the package `org.springframework.data.mong NOTE: Once configured, `MongoTemplate` is thread-safe and can be reused across multiple instances. -The mapping between MongoDB documents and domain classes is done by delegating to an implementation of the interface `MongoConverter`. Spring provides two implementations, `SimpleMappingConverter` and `MongoMappingConverter`, but you can also write your own converter. Please refer to the section on MongoConverters for more detailed information. +The mapping between MongoDB documents and domain classes is done by delegating to an implementation of the interface `MongoConverter`. Spring provides two implementations, `SimpleMappingConverter` and `MappingMongoConverter`, but you can also write your own converter. Please refer to the section on MongoConverters for more detailed information. The `MongoTemplate` class implements the interface `MongoOperations`. In as much as possible, the methods on `MongoOperations` are named after methods available on the MongoDB driver `Collection` object to make the API familiar to existing MongoDB developers who are used to the driver API. For example, you will find methods such as "find", "findAndModify", "findOne", "insert", "remove", "save", "update" and "updateMulti". The design goal was to make it as easy as possible to transition between the use of the base MongoDB driver and `MongoOperations`. A major difference in between the two APIs is that MongoOperations can be passed domain objects instead of `DBObject` and there are fluent APIs for `Query`, `Criteria`, and `Update` operations instead of populating a `DBObject` to specify the parameters for those operations. NOTE: The preferred way to reference the operations on `MongoTemplate` instance is via its interface `MongoOperations`. -The default converter implementation used by `MongoTemplate` is MongoMappingConverter. While the `MongoMappingConverter` can make use of additional metadata to specify the mapping of objects to documents it is also capable of converting objects that contain no additional metadata by using some conventions for the mapping of IDs and collection names. These conventions as well as the use of mapping annotations is explained in the <>. +The default converter implementation used by `MongoTemplate` is MappingMongoConverter. While the `MappingMongoConverter` can make use of additional metadata to specify the mapping of objects to documents it is also capable of converting objects that contain no additional metadata by using some conventions for the mapping of IDs and collection names. These conventions as well as the use of mapping annotations is explained in the <>. -NOTE: In the M2 release `SimpleMappingConverter`, was the default and this class is now deprecated as its functionality has been subsumed by the `MongoMappingConverter`. +NOTE: In the M2 release `SimpleMappingConverter`, was the default and this class is now deprecated as its functionality has been subsumed by the `MappingMongoConverter`. Another central feature of MongoTemplate is exception translation of exceptions thrown in the MongoDB Java driver into Spring's portable Data Access Exception hierarchy. Refer to the section on <> for more information. @@ -645,7 +659,7 @@ The query syntax used in the example is explained in more detail in the section [[mongo-template.id-handling]] === How the `_id` field is handled in the mapping layer -MongoDB requires that you have an `_id` field for all documents. If you don't provide one the driver will assign a `ObjectId` with a generated value. When using the `MongoMappingConverter` there are certain rules that govern how properties from the Java class is mapped to this `_id` field. +MongoDB requires that you have an `_id` field for all documents. If you don't provide one the driver will assign a `ObjectId` with a generated value. When using the `MappingMongoConverter` there are certain rules that govern how properties from the Java class is mapped to this `_id` field. The following outlines what property will be mapped to the `_id` document field: @@ -1674,22 +1688,40 @@ At the time of this writing we provide support for the following Aggregation Ope [cols="2*"] |=== | Pipeline Aggregation Operators -| project, skip, limit, lookup, unwind, group, sort, geoNear +| bucket, bucketAuto, count, facet, geoNear, graphLookup, group, limit, lookup, match, project, replaceRoot, skip, sort, unwind + +| Set Aggregation Operators +| setEquals, setIntersection, setUnion, setDifference, setIsSubset, anyElementTrue, allElementsTrue | Group Aggregation Operators -| addToSet, first, last, max, min, avg, push, sum, (*count) +| addToSet, first, last, max, min, avg, push, sum, (*count), stdDevPop, stdDevSamp | Arithmetic Aggregation Operators -| add (*via plus), subtract (*via minus), multiply, divide, mod +| abs, add (*via plus), ceil, divide, exp, floor, ln, log, log10, mod, multiply, pow, sqrt, subtract (*via minus), trunc + +| String Aggregation Operators +| concat, substr, toLower, toUpper, stcasecmp, indexOfBytes, indexOfCP, split, strLenBytes, strLenCP, substrCP, | Comparison Aggregation Operators | eq (*via: is), gt, gte, lt, lte, ne | Array Aggregation Operators -| size, slice +| arrayElementAt, concatArrays, filter, in, indexOfArray, isArray, range, reverseArray, reduce, size, slice, zip + +| Literal Operators +| literal + +| Date Aggregation Operators +| dayOfYear, dayOfMonth, dayOfWeek, year, month, week, hour, minute, second, millisecond, dateToString, isoDayOfWeek, isoWeek, isoWeekYear + +| Variable Operators +| map | Conditional Aggregation Operators -| cond, ifNull +| cond, ifNull, switch + +| Type Aggregation Operators +| type |=== @@ -1700,27 +1732,123 @@ Note that the aggregation operations not listed here are currently not supported [[mongo.aggregation.projection]] === Projection Expressions -Projection expressions are used to define the fields that are the outcome of a particular aggregation step. Projection expressions can be defined via the `project` method of the `Aggregate` class either by passing a list of `String` 's or an aggregation framework `Fields` object. The projection can be extended with additional fields through a fluent API via the `and(String)` method and aliased via the `as(String)` method. -Note that one can also define fields with aliases via the static factory method `Fields.field` of the aggregation framework that can then be used to construct a new `Fields` instance. +Projection expressions are used to define the fields that are the outcome of a particular aggregation step. Projection expressions can be defined via the `project` method of the `Aggregate` class either by passing a list of ``String``'s or an aggregation framework `Fields` object. The projection can be extended with additional fields through a fluent API via the `and(String)` method and aliased via the `as(String)` method. +Note that one can also define fields with aliases via the static factory method `Fields.field` of the aggregation framework that can then be used to construct a new `Fields` instance. References to projected fields in later aggregation stages are only valid by using the field name of included fields or their alias of aliased or newly defined fields. Fields not included in the projection cannot be referenced in later aggregation stages. .Projection expression examples ==== [source,java] ---- -project("name", "netPrice") // will generate {$project: {name: 1, netPrice: 1}} -project().and("foo").as("bar") // will generate {$project: {bar: $foo}} -project("a","b").and("foo").as("bar") // will generate {$project: {a: 1, b: 1, bar: $foo}} +// will generate {$project: {name: 1, netPrice: 1}} +project("name", "netPrice") + +// will generate {$project: {bar: $foo}} +project().and("foo").as("bar") + +// will generate {$project: {a: 1, b: 1, bar: $foo}} +project("a","b").and("foo").as("bar") +---- +==== + +.Multi-Stage Aggregation using Projection and Sorting +==== +[source,java] +---- +// will generate {$project: {name: 1, netPrice: 1}}, {$sort: {name: 1}} +project("name", "netPrice"), sort(ASC, "name") + +// will generate {$project: {bar: $foo}}, {$sort: {bar: 1}} +project().and("foo").as("bar"), sort(ASC, "bar") + +// this will not work +project().and("foo").as("bar"), sort(ASC, "foo") ---- ==== -Note that more examples for project operations can be found in the `AggregationTests` class. +More examples for project operations can be found in the `AggregationTests` class. Note that further details regarding the projection expressions can be found in the http://docs.mongodb.org/manual/reference/operator/aggregation/project/#pipe._S_project[corresponding section] of the MongoDB Aggregation Framework reference documentation. + +[[mongo.aggregation.facet]] +=== Faceted classification + +MongoDB supports as of Version 3.4 faceted classification using the Aggregation Framework. A faceted classification uses semantic categories, either general or subject-specific, that are combined to create the full classification entry. Documents flowing through the aggregation pipeline are classificated into buckets. A multi-faceted classification enables various aggregations on the same set of input documents, without needing to retrieve the input documents multiple times. + +==== Buckets + +Bucket operations categorize incoming documents into groups, called buckets, based on a specified expression and bucket boundaries. Bucket operations require a grouping field or grouping expression. They can be defined via the `bucket()`/`bucketAuto()` methods of the `Aggregate` class. `BucketOperation` and `BucketAutoOperation` can expose accumulations based on aggregation expressions for input documents. The bucket operation can be extended with additional parameters through a fluent API via the `with…()` methods, the `andOutput(String)` method and aliased via the `as(String)` method. Each bucket is represented as a document in the output. + +`BucketOperation` takes a defined set of boundaries to group incoming documents into these categories. Boundaries are required to be sorted. + +.Bucket operation examples +==== +[source,java] +---- +// will generate {$bucket: {groupBy: $price, boundaries: [0, 100, 400]}} +bucket("price").withBoundaries(0, 100, 400); + +// will generate {$bucket: {groupBy: $price, default: "Other" boundaries: [0, 100]}} +bucket("price").withBoundaries(0, 100).withDefault("Other"); -Note that further details regarding the projection expressions can be found in the http://docs.mongodb.org/manual/reference/operator/aggregation/project/#pipe._S_project[corresponding section] of the MongoDB Aggregation Framework reference documentation. +// will generate {$bucket: {groupBy: $price, boundaries: [0, 100], output: { count: { $sum: 1}}}} +bucket("price").withBoundaries(0, 100).andOutputCount().as("count"); + +// will generate {$bucket: {groupBy: $price, boundaries: [0, 100], 5, output: { titles: { $push: "$title"}}} +bucket("price").withBoundaries(0, 100).andOutput("title").push().as("titles"); +---- +==== + +`BucketAutoOperation` determines boundaries itself in an attempt to evenly distribute documents into a specified number of buckets. `BucketAutoOperation` optionally takes a granularity specifies the https://en.wikipedia.org/wiki/Preferred_number[preferred number] series to use to ensure that the calculated boundary edges end on preferred round numbers or their powers of 10. + +.Bucket operation examples +==== +[source,java] +---- +// will generate {$bucketAuto: {groupBy: $price, buckets: 5}} +bucketAuto("price", 5) + +// will generate {$bucketAuto: {groupBy: $price, buckets: 5, granularity: "E24"}} +bucketAuto("price", 5).withGranularity(Granularities.E24).withDefault("Other"); + +// will generate {$bucketAuto: {groupBy: $price, buckets: 5, output: { titles: { $push: "$title"}}} +bucketAuto("price", 5).andOutput("title").push().as("titles"); +---- +==== + +Bucket operations can use `AggregationExpression` via `andOutput()` and <> via `andOutputExpression()` to create output fields in buckets. + +Note that further details regarding bucket expressions can be found in the http://docs.mongodb.org/manual/reference/operator/aggregation/bucket/[`$bucket` section] and +http://docs.mongodb.org/manual/reference/operator/aggregation/bucketAuto/[`$bucketAuto` section] of the MongoDB Aggregation Framework reference documentation. + +==== Multi-faceted aggregation + +Multiple aggregation pipelines can be used to create multi-faceted aggregations which characterize data across multiple dimensions, or facets, within a single aggregation stage. Multi-faceted aggregations provide multiple filters and categorizations to guide data browsing and analysis. A common implementation of faceting is how many online retailers provide ways to narrow down search results by applying filters on product price, manufacturer, size, etc. + +A `FacetOperation` can be defined via the `facet()` method of the `Aggregation` class. It can be customized with multiple aggregation pipelines via the `and()` method. Each sub-pipeline has its own field in the output document where its results are stored as an array of documents. + +Sub-pipelines can project and filter input documents prior grouping. Common cases are extraction of date parts or calculations before categorization. + +.Facet operation examples +==== +[source,java] +---- +// will generate {$facet: {categorizedByPrice: [ { $match: { price: {$exists : true}}}, { $bucketAuto: {groupBy: $price, buckets: 5}}]}} +facet(match(Criteria.where("price").exists(true)), bucketAuto("price", 5)).as("categorizedByPrice")) + +// will generate {$facet: {categorizedByYear: [ +// { $project: { title: 1, publicationYear: { $year: "publicationDate"}}}, +// { $bucketAuto: {groupBy: $price, buckets: 5, output: { titles: {$push:"$title"}}} +// ]}} +facet(project("title").and("publicationDate").extractYear().as("publicationYear"), + bucketAuto("publicationYear", 5).andOutput("title").push().as("titles")) + .as("categorizedByYear")) +---- +==== + +Note that further details regarding facet operation can be found in the http://docs.mongodb.org/manual/reference/operator/aggregation/facet/[`$facet` section] of the MongoDB Aggregation Framework reference documentation. [[mongo.aggregation.projection.expressions]] ==== Spring Expression Support in Projection Expressions -As of Version 1.4.0 we support the use of SpEL expression in projection expressions via the `andExpression` method of the `ProjectionOperation` class. This allows you to define the desired expression as a SpEL expression which is translated into a corresponding MongoDB projection expression part on query execution. This makes it much easier to express complex calculations. +We support the use of SpEL expression in projection expressions via the `andExpression` method of the `ProjectionOperation` and `BucketOperation` classes. This allows you to define the desired expression as a SpEL expression which is translated into a corresponding MongoDB projection expression part on query execution. This makes it much easier to express complex calculations. ===== Complex calculations with SpEL expressions @@ -1745,6 +1873,49 @@ will be translated into the following projection expression part: Have a look at an example in more context in <> and <>. You can find more usage examples for supported SpEL expression constructs in `SpelExpressionTransformerUnitTests`. +.Supported SpEL transformations +[cols="2"] +|=== +| a == b +| { $eq : [$a, $b] } +| a != b +| { $ne : [$a , $b] } +| a > b +| { $gt : [$a, $b] } +| a >= b +| { $gte : [$a, $b] } +| a < b +| { $lt : [$a, $b] } +| a <= b +| { $lte : [$a, $b] } +| a + b +| { $add : [$a, $b] } +| a - b +| { $subtract : [$a, $b] } +| a * b +| { $multiply : [$a, $b] } +| a / b +| { $divide : [$a, $b] } +| a^b +| { $pow : [$a, $b] } +| a % b +| { $mod : [$a, $b] } +| a && b +| { $and : [$a, $b] } +| a \|\| b +| { $or : [$a, $b] } +| !a +| { $not : [$a] } +|=== + +Next to the transformations shown in <> it is possible to use standard SpEL operations like `new` to eg. create arrays and reference expressions via their name followed by the arguments to use in brackets. + +[source,java] +---- +// { $setEquals : [$a, [5, 8, 13] ] } +.andExpression("setEquals(a, new int[]{5, 8, 13})"); +---- + [[mongo.aggregation.examples]] ==== Aggregation Framework Examples @@ -1845,7 +2016,7 @@ ZipInfoStats firstZipInfoStats = result.getMappedResults().get(0); * The class `ZipInfo` maps the structure of the given input-collection. The class `ZipInfoStats` defines the structure in the desired output format. * As a first step we use the `group` operation to define a group from the input-collection. The grouping criteria is the combination of the fields `"state"` and `"city"` which forms the id structure of the group. We aggregate the value of the `"population"` property from the grouped elements with by using the `sum` operator saving the result in the field `"pop"`. -* In a second step we use the `sort` operation to sort the intermediate-result by the fields `"pop"`, `"state"` and `"city"` in ascending order, such that the smallest city is at the top and the biggest city is at the bottom of the result. Note that the sorting on "state" and `"city"` is implicitly performed against the group id fields which Spring Data MongoDB took care of. +* In a second step we use the `sort` operation to sort the intermediate-result by the fields `"pop"`, `"state"` and `"city"` in ascending order, such that the smallest city is at the top and the biggest city is at the bottom of the result. Note that the sorting on `"state"` and `"city"` is implicitly performed against the group id fields which Spring Data MongoDB took care of. * In the third step we use a `group` operation again to group the intermediate result by `"state"`. Note that `"state"` again implicitly references an group-id field. We select the name and the population count of the biggest and smallest city with calls to the `last(…)` and `first(...)` operator respectively via the `project` operation. * As the forth step we select the `"state"` field from the previous `group` operation. Note that `"state"` again implicitly references an group-id field. As we do not want an implicitly generated id to appear, we exclude the id from the previous operation via `and(previousOperation()).exclude()`. As we want to populate the nested `City` structures in our output-class accordingly we have to emit appropriate sub-documents with the nested method. * Finally as the fifth step we sort the resulting list of `StateStats` by their state name in ascending order via the `sort` operation. @@ -2271,6 +2442,8 @@ The list of callback methods that are present in AbstractMappingEventListener ar * `onAfterLoad` - called in MongoTemplate find, findAndRemove, findOne and getCollection methods after the DBObject is retrieved from the database. * `onAfterConvert` - called in MongoTemplate find, findAndRemove, findOne and getCollection methods after the DBObject retrieved from the database was converted to a POJO. +NOTE: Lifecycle events are only emitted for root level types. Complex types used as properties within a document root are not subject of event publication unless they are document references annotated with `@DBRef`. + [[mongo.exception]] == Exception Translation diff --git a/src/main/resources/changelog.txt b/src/main/resources/changelog.txt index 6ca345936c..5512f0b26a 100644 --- a/src/main/resources/changelog.txt +++ b/src/main/resources/changelog.txt @@ -1,6 +1,98 @@ Spring Data MongoDB Changelog ============================= +Changes in version 1.10.0.RELEASE (2017-01-26) +---------------------------------------------- +* DATAMONGO-1596 - Reference to wrong annotation in cross-store reference documentation. +* DATAMONGO-1594 - Update "what’s new" section in reference documentation. +* DATAMONGO-1590 - Entity new detection doesn't consider Persistable.isNew(). +* DATAMONGO-1589 - Update project documentation with the CLA tool integration. +* DATAMONGO-1588 - Repository will not accept Point subclass in spatial query. +* DATAMONGO-1587 - Migrate ticket references in test code to Spring Framework style. +* DATAMONGO-1586 - TypeBasedAggregationOperationContext.getReferenceFor(…) exposes fields with their leaf property name. +* DATAMONGO-1585 - Aggregation sort references target field of projected and aliased fields. +* DATAMONGO-1578 - Add missing @Test annotation to ProjectionOperationUnitTests. +* DATAMONGO-1577 - Fix Reference and JavaDoc spelling issues. +* DATAMONGO-1576 - AbstractMongoEventListener methods not called when working with member fields. +* DATAMONGO-1575 - Treat String replacement values in StringBased queries as such unless they are SpEL expressions. +* DATAMONGO-1574 - Release 1.10 GA (Ingalls). +* DATAMONGO-1508 - Documentation lacking for db-factory authentication-dbname. + + +Changes in version 1.9.6.RELEASE (2016-12-21) +--------------------------------------------- +* DATAMONGO-1565 - Placeholders in manually defined queries not escaped properly. +* DATAMONGO-1534 - Type hint is missing when using BulkOperations.insert. +* DATAMONGO-1525 - Reading empty EnumSet fails. +* DATAMONGO-1522 - Release 1.9.6 (Hopper SR6). + + +Changes in version 1.10.0.RC1 (2016-12-21) +------------------------------------------ +* DATAMONGO-1567 - Upgrade to a newer JDK version on TravisCI. +* DATAMONGO-1566 - Adapt API in RepositoryFactoryBeanSupport implementation. +* DATAMONGO-1565 - Placeholders in manually defined queries not escaped properly. +* DATAMONGO-1564 - Split up AggregationExpressions. +* DATAMONGO-1558 - Upgrade travis-ci profile to MongoDB 3.4. +* DATAMONGO-1552 - Add $facet, $bucket and $bucketAuto aggregation stages. +* DATAMONGO-1551 - Add $graphLookup aggregation stage. +* DATAMONGO-1550 - Add $replaceRoot aggregation stage. +* DATAMONGO-1549 - Add $count aggregation stage. +* DATAMONGO-1548 - Add new MongoDB 3.4 aggregation operators. +* DATAMONGO-1547 - Register repository factory in spring.factories for multi-store support. +* DATAMONGO-1546 - Switch to new way of registering custom Jackson modules. +* DATAMONGO-1542 - Refactor CondOperator and IfNullOperator to children of AggregationExpressions. +* DATAMONGO-1540 - Add support for $map to aggregation. +* DATAMONGO-1539 - Add dedicated annotations for manually declared count and delete queries. +* DATAMONGO-1538 - Add support for $let to aggregation. +* DATAMONGO-1536 - Add missing aggregation operators. +* DATAMONGO-1534 - Type hint is missing when using BulkOperations.insert. +* DATAMONGO-1533 - Add support for SpEL in GroupOperations (aggregation). +* DATAMONGO-1530 - Support missing aggregation pipeline operators in expression support. +* DATAMONGO-1525 - Reading empty EnumSet fails. +* DATAMONGO-1521 - Aggregation.skip(...) expects int but new SkipOperation(...) supports long. +* DATAMONGO-1520 - Aggregation.match should accept CriteriaDefinition. +* DATAMONGO-1514 - SpringDataMongodbQuery should be public. +* DATAMONGO-1513 - Non-ObjectId identifiers generated by event listeners are not populated if documents are inserted as batch. +* DATAMONGO-1504 - Assert compatibility with MongoDB 3.4 server and driver. +* DATAMONGO-1500 - RuntimeException for query methods with fields declaration and Pageable parameters. +* DATAMONGO-1498 - MongoMappingContext doesn't know about types usually auto-detected (JodaTime, JDK 8 date time types). +* DATAMONGO-1493 - Typos in reference documentation. +* DATAMONGO-1492 - Interface AggregationExpression in package org.springframework.data.mongodb.core.aggregation should be public. +* DATAMONGO-1491 - Add support for $filter to aggregation. +* DATAMONGO-1490 - Change the XML data type of boolean flags to String. +* DATAMONGO-1486 - Changes to MappingMongoConverter Result in Class Cast Exception. +* DATAMONGO-1485 - Querydsl MongodbSerializer does not take registered converters for Enums into account. +* DATAMONGO-1480 - Add support for noCursorTimeout in Query. +* DATAMONGO-1479 - MappingMongoConverter.convertToMongoType causes StackOverflowError for parameterized map value types. +* DATAMONGO-1476 - New stream method only partially makes use of collection name. +* DATAMONGO-1471 - MappingMongoConverter attempts to set null value on potentially primitive identifier. +* DATAMONGO-1470 - AbstractMongoConfiguraton should allow multiple base package for @Document scanning. +* DATAMONGO-1469 - Release 1.10 RC1 (Ingalls). +* DATAMONGO-1467 - Support partial filter expressions for indexing introduced in MongoDB 3.2. +* DATAMONGO-1465 - String arguments passed to DefaultScriptOperations.execute() appear quoted in script. +* DATAMONGO-1454 - Add support for exists projection in repository query derivation. +* DATAMONGO-1406 - Query mapper does not use @Field field name when querying nested fields in combination with nested keywords. +* DATAMONGO-1328 - Add support for mongodb 3.2 specific arithmetic operators to aggregation. +* DATAMONGO-1327 - Add support for $stdDevSamp and $stdDevPop to aggregation ($group stage). +* DATAMONGO-1299 - Add support for date aggregations. +* DATAMONGO-1141 - Add support for $push $sort in Update. +* DATAMONGO-861 - Add support for $cond and $ifNull operators in aggregation operation. +* DATAMONGO-784 - Add support for $cmp in group or project aggregation. + + +Changes in version 2.0.0.M1 (2016-11-23) +---------------------------------------- +* DATAMONGO-1527 - Release 2.0 M1 (Kay). +* DATAMONGO-1509 - Inconsistent type alias placement in list of classes. +* DATAMONGO-1461 - Upgrade Hibernate/JPA dependencies to match Spring 5 baseline. +* DATAMONGO-1448 - Set up 2.0 development. +* DATAMONGO-1444 - Reactive support in Spring Data MongoDB. +* DATAMONGO-1176 - Use org.bson types instead of com.mongodb. +* DATAMONGO-563 - Upgrade to MongoDB driver 2.9.2 as it fixes a serious regression introduced in 2.9.0. +* DATAMONGO-562 - Cannot create entity with OptimisticLocking (@Version) and initial id. + + Changes in version 1.9.5.RELEASE (2016-11-03) --------------------------------------------- * DATAMONGO-1521 - Aggregation.skip(...) expects int but new SkipOperation(...) supports long. diff --git a/src/main/resources/notice.txt b/src/main/resources/notice.txt index 5bc8d4cc27..0e749a8efa 100644 --- a/src/main/resources/notice.txt +++ b/src/main/resources/notice.txt @@ -1,4 +1,4 @@ -Spring Data MongoDB 1.10 M1 +Spring Data MongoDB 1.10 GA Copyright (c) [2010-2015] Pivotal Software, Inc. This product is licensed to you under the Apache License, Version 2.0 (the "License").