Compare commits
59 Commits
2.1.0.M3
...
1.10.0.RC1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
737f7b4f30 | ||
|
|
408c5d8684 | ||
|
|
982adf317e | ||
|
|
7914e8a630 | ||
|
|
dc4a30a7f8 | ||
|
|
29e405b800 | ||
|
|
1a105333aa | ||
|
|
14e326dc09 | ||
|
|
f026ab419d | ||
|
|
75139042e0 | ||
|
|
6236384c1d | ||
|
|
6993054d6a | ||
|
|
35bfb92ace | ||
|
|
89a02bb822 | ||
|
|
c9c5fe62ca | ||
|
|
d250f88c38 | ||
|
|
bcb63b2732 | ||
|
|
2c4377c9a6 | ||
|
|
e992d813fb | ||
|
|
aa1e91c761 | ||
|
|
9737464f9a | ||
|
|
204a0515c4 | ||
|
|
c6dae3c444 | ||
|
|
c1c7daf0ed | ||
|
|
14678ce7a9 | ||
|
|
4e56d9c575 | ||
|
|
cab35759db | ||
|
|
7b49b120e3 | ||
|
|
dc57b66adf | ||
|
|
0449719a16 | ||
|
|
3d8b6868c7 | ||
|
|
68db0d4cb0 | ||
|
|
c9dfeea0c7 | ||
|
|
1a11877ae9 | ||
|
|
ea4782c421 | ||
|
|
a0ac3510a0 | ||
|
|
192399413d | ||
|
|
a0be890437 | ||
|
|
438dbc4b33 | ||
|
|
407affb458 | ||
|
|
c6a4e7166c | ||
|
|
7f39c42eb7 | ||
|
|
40da4701de | ||
|
|
3ae6aebebb | ||
|
|
bbfa0f7b83 | ||
|
|
63d6234446 | ||
|
|
9ff86feb4f | ||
|
|
8c838e8350 | ||
|
|
a79930145d | ||
|
|
9059a77712 | ||
|
|
a741400e9b | ||
|
|
b786b8220a | ||
|
|
710770e88d | ||
|
|
e631e2d7c5 | ||
|
|
3dc1e9355a | ||
|
|
2985b4ca3d | ||
|
|
578441ee9f | ||
|
|
36838ffe31 | ||
|
|
5bd0e21173 |
@@ -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
|
||||
|
||||
|
||||
21
pom.xml
21
pom.xml
@@ -5,7 +5,7 @@
|
||||
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-mongodb-parent</artifactId>
|
||||
<version>1.10.0.BUILD-SNAPSHOT</version>
|
||||
<version>1.10.0.RC1</version>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<name>Spring Data MongoDB</name>
|
||||
@@ -15,7 +15,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.data.build</groupId>
|
||||
<artifactId>spring-data-parent</artifactId>
|
||||
<version>1.9.0.BUILD-SNAPSHOT</version>
|
||||
<version>1.9.0.RC1</version>
|
||||
</parent>
|
||||
|
||||
<modules>
|
||||
@@ -28,7 +28,7 @@
|
||||
<properties>
|
||||
<project.type>multi</project.type>
|
||||
<dist.id>spring-data-mongodb</dist.id>
|
||||
<springdata.commons>1.13.0.BUILD-SNAPSHOT</springdata.commons>
|
||||
<springdata.commons>1.13.0.RC1</springdata.commons>
|
||||
<mongo>2.14.3</mongo>
|
||||
<mongo.osgi>2.13.0</mongo.osgi>
|
||||
</properties>
|
||||
@@ -178,11 +178,20 @@
|
||||
|
||||
</profile>
|
||||
|
||||
<profile>
|
||||
|
||||
<id>mongo34</id>
|
||||
<properties>
|
||||
<mongo>3.4.0</mongo>
|
||||
</properties>
|
||||
|
||||
</profile>
|
||||
|
||||
<profile>
|
||||
|
||||
<id>mongo34-next</id>
|
||||
<properties>
|
||||
<mongo>3.4.0-SNAPSHOT</mongo>
|
||||
<mongo>3.4.1-SNAPSHOT</mongo>
|
||||
</properties>
|
||||
|
||||
<repositories>
|
||||
@@ -220,8 +229,8 @@
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>spring-libs-snapshot</id>
|
||||
<url>https://repo.spring.io/libs-snapshot</url>
|
||||
<id>spring-libs-milestone</id>
|
||||
<url>https://repo.spring.io/libs-milestone</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-mongodb-parent</artifactId>
|
||||
<version>1.10.0.BUILD-SNAPSHOT</version>
|
||||
<version>1.10.0.RC1</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
<dependency>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-mongodb</artifactId>
|
||||
<version>1.10.0.BUILD-SNAPSHOT</version>
|
||||
<version>1.10.0.RC1</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-mongodb-parent</artifactId>
|
||||
<version>1.10.0.BUILD-SNAPSHOT</version>
|
||||
<version>1.10.0.RC1</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-mongodb-parent</artifactId>
|
||||
<version>1.10.0.BUILD-SNAPSHOT</version>
|
||||
<version>1.10.0.RC1</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
||||
@@ -18,6 +18,8 @@ package org.springframework.data.mongodb.log4j;
|
||||
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.BasicDBObject;
|
||||
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 class MongoLog4jAppender extends AppenderSkeleton {
|
||||
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 class MongoLog4jAppender extends AppenderSkeleton {
|
||||
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 class MongoLog4jAppender extends AppenderSkeleton {
|
||||
}
|
||||
|
||||
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<MongoCredential> credentials = Collections.singletonList(mongoCredential);
|
||||
return new MongoClient(serverAddress, credentials);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.apache.log4j.AppenderSkeleton#append(org.apache.log4j.spi.LoggingEvent)
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -20,8 +20,10 @@ import static org.junit.Assert.*;
|
||||
|
||||
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.BasicDBObject;
|
||||
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 class MongoLog4jAppenderIntegrationTests {
|
||||
|
||||
@Test
|
||||
public void testProperties() {
|
||||
|
||||
MDC.put("property", "one");
|
||||
log.debug("DEBUG message");
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-mongodb-parent</artifactId>
|
||||
<version>1.10.0.BUILD-SNAPSHOT</version>
|
||||
<version>1.10.0.RC1</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ import org.springframework.data.mongodb.core.query.Update;
|
||||
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 @@ import com.mongodb.WriteConcern;
|
||||
*
|
||||
* @author Tobias Trelle
|
||||
* @author Oliver Gierke
|
||||
* @author Christoph Strobl
|
||||
* @since 1.9
|
||||
*/
|
||||
class DefaultBulkOperations implements BulkOperations {
|
||||
@@ -117,7 +119,15 @@ class DefaultBulkOperations implements BulkOperations {
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
@@ -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 @@ import com.mongodb.MongoException;
|
||||
*/
|
||||
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<String> 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 class DefaultIndexOperations implements IndexOperations {
|
||||
* @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<Object>() {
|
||||
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 class DefaultIndexOperations implements IndexOperations {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private MongoPersistentEntity<?> lookupPersistentEntity(Class<?> entityType, String collection) {
|
||||
|
||||
if (entityType != null) {
|
||||
return mongoOperations.getConverter().getMappingContext().getPersistentEntity(entityType);
|
||||
}
|
||||
|
||||
Collection<? extends MongoPersistentEntity<?>> entities = mongoOperations.getConverter().getMappingContext()
|
||||
.getPersistentEntities();
|
||||
|
||||
for (MongoPersistentEntity<?> entity : entities) {
|
||||
if (entity.getCollection().equals(collection)) {
|
||||
return entity;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -126,7 +167,9 @@ public class DefaultIndexOperations implements IndexOperations {
|
||||
public List<IndexInfo> getIndexInfo() {
|
||||
|
||||
return mongoOperations.execute(collectionName, new CollectionCallback<List<IndexInfo>>() {
|
||||
|
||||
public List<IndexInfo> doInCollection(DBCollection collection) throws MongoException, DataAccessException {
|
||||
|
||||
List<DBObject> dbObjectList = collection.getIndexInfo();
|
||||
return getIndexData(dbObjectList);
|
||||
}
|
||||
@@ -136,44 +179,7 @@ public class DefaultIndexOperations implements IndexOperations {
|
||||
List<IndexInfo> indexInfoList = new ArrayList<IndexInfo>();
|
||||
|
||||
for (DBObject ix : dbObjectList) {
|
||||
|
||||
DBObject keyDbObject = (DBObject) ix.get("key");
|
||||
int numberOfElements = keyDbObject.keySet().size();
|
||||
|
||||
List<IndexField> indexFields = new ArrayList<IndexField>(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;
|
||||
|
||||
@@ -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 @@ package org.springframework.data.mongodb.core;
|
||||
|
||||
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() {
|
||||
|
||||
@@ -557,7 +557,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
|
||||
}
|
||||
|
||||
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) {
|
||||
|
||||
@@ -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<Object> arguments = (List<Object>) value;
|
||||
List<Object> args = new ArrayList<Object>(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<String, Object> entry : ((java.util.Map<String, Object>) 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<Field> 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<Object> sourceList = (List<Object>) value;
|
||||
List<Object> mappedList = new ArrayList<Object>(sourceList.size());
|
||||
|
||||
for (Object item : sourceList) {
|
||||
mappedList.add(unpack(item, context));
|
||||
}
|
||||
return mappedList;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
protected List<Object> append(Object value) {
|
||||
|
||||
if (this.value instanceof List) {
|
||||
|
||||
List<Object> clone = new ArrayList<Object>((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<String, Object> append(String key, Object value) {
|
||||
|
||||
if (!(this.value instanceof java.util.Map)) {
|
||||
throw new IllegalArgumentException("o_O");
|
||||
}
|
||||
java.util.Map<String, Object> clone = new LinkedHashMap<String, Object>((java.util.Map<String, Object>) this.value);
|
||||
clone.put(key, value);
|
||||
return clone;
|
||||
|
||||
}
|
||||
|
||||
protected List<Object> values() {
|
||||
|
||||
if (value instanceof List) {
|
||||
return new ArrayList<Object>((List) value);
|
||||
}
|
||||
if (value instanceof java.util.Map) {
|
||||
return new ArrayList<Object>(((java.util.Map) value).values());
|
||||
}
|
||||
return new ArrayList<Object>(Collections.singletonList(value));
|
||||
}
|
||||
|
||||
protected abstract String getMongoMethod();
|
||||
}
|
||||
@@ -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. <br />
|
||||
* <strong>NOTE:</strong> 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. <br />
|
||||
* <strong>NOTE:</strong> 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<Object>) 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. <br />
|
||||
* <strong>NOTE:</strong> 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. <br />
|
||||
* <strong>NOTE:</strong> 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<Object>) 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. <br />
|
||||
* <strong>NOTE:</strong> 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. <br />
|
||||
* <strong>NOTE:</strong> 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<Object>) 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. <br />
|
||||
* <strong>NOTE:</strong> 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. <br />
|
||||
* <strong>NOTE:</strong> 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<Object>) 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. <br/>
|
||||
* <strong>NOTE:</strong> 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. <br />
|
||||
* <strong>NOTE:</strong> 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<Object>) 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. <br />
|
||||
* <strong>NOTE:</strong> 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. <br />
|
||||
* <strong>NOTE:</strong> 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<Object>) value).iterator().next(), context);
|
||||
}
|
||||
}
|
||||
|
||||
return super.toDbObject(value, context);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,11 +23,18 @@ import java.util.List;
|
||||
|
||||
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<AggregationOperation> operations;
|
||||
@@ -196,7 +203,7 @@ public class Aggregation {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 class Aggregation {
|
||||
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 class Aggregation {
|
||||
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 class Aggregation {
|
||||
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.
|
||||
@@ -355,6 +419,73 @@ public class Aggregation {
|
||||
return new OutOperation(outCollectionName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link BucketOperation} given {@literal groupByField}.
|
||||
*
|
||||
* @param groupByField must not be {@literal null} or empty.
|
||||
* @return
|
||||
* @since 1.10
|
||||
*/
|
||||
public static BucketOperation bucket(String groupByField) {
|
||||
return new BucketOperation(field(groupByField));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link BucketOperation} given {@link AggregationExpression group-by expression}.
|
||||
*
|
||||
* @param groupByExpression must not be {@literal null}.
|
||||
* @return
|
||||
* @since 1.10
|
||||
*/
|
||||
public static BucketOperation bucket(AggregationExpression groupByExpression) {
|
||||
return new BucketOperation(groupByExpression);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link BucketAutoOperation} given {@literal groupByField}.
|
||||
*
|
||||
* @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 BucketAutoOperation bucketAuto(String groupByField, int buckets) {
|
||||
return new BucketAutoOperation(field(groupByField), buckets);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link BucketAutoOperation} given {@link AggregationExpression group-by expression}.
|
||||
*
|
||||
* @param groupByExpression must not be {@literal null}.
|
||||
* @param buckets number of buckets, must be a positive integer.
|
||||
* @return
|
||||
* @since 1.10
|
||||
*/
|
||||
public static BucketAutoOperation bucketAuto(AggregationExpression groupByExpression, int buckets) {
|
||||
return new BucketAutoOperation(groupByExpression, buckets);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link FacetOperation}.
|
||||
*
|
||||
* @return
|
||||
* @since 1.10
|
||||
*/
|
||||
public static FacetOperation facet() {
|
||||
return FacetOperation.EMPTY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link FacetOperationBuilder} given {@link Aggregation}.
|
||||
*
|
||||
* @param aggregationOperations the sub-pipeline, must not be {@literal null}.
|
||||
* @return
|
||||
* @since 1.10
|
||||
*/
|
||||
public static FacetOperationBuilder facet(AggregationOperation... aggregationOperations) {
|
||||
return facet().and(aggregationOperations);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link LookupOperation}.
|
||||
*
|
||||
@@ -384,65 +515,13 @@ public class Aggregation {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link IfNullOperator} for the given {@code field} and {@code replacement} value.
|
||||
* Creates a new {@link CountOperationBuilder}.
|
||||
*
|
||||
* @param field must not be {@literal null}.
|
||||
* @param replacement must not be {@literal null}.
|
||||
* @return never {@literal null}.
|
||||
* @since 1.10
|
||||
*/
|
||||
public static IfNullOperator ifNull(String field, Object replacement) {
|
||||
return IfNullOperator.newBuilder().ifNull(field).thenReplaceWith(replacement);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link IfNullOperator} for the given {@link Field} and {@link Field} to obtain a value from.
|
||||
*
|
||||
* @param field must not be {@literal null}.
|
||||
* @param replacement must not be {@literal null}.
|
||||
* @return never {@literal null}.
|
||||
* @since 1.10
|
||||
*/
|
||||
public static IfNullOperator ifNull(Field field, Field replacement) {
|
||||
return IfNullOperator.newBuilder().ifNull(field).thenReplaceWith(replacement);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link IfNullOperator} for the given {@link Field} and {@code replacement} value.
|
||||
*
|
||||
* @param field must not be {@literal null}.
|
||||
* @param replacement must not be {@literal null}.
|
||||
* @return never {@literal null}.
|
||||
* @since 1.10
|
||||
*/
|
||||
public static IfNullOperator ifNull(Field field, Object replacement) {
|
||||
return IfNullOperator.newBuilder().ifNull(field).thenReplaceWith(replacement);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link ConditionalOperator} for the given {@link Field} that holds a {@literal boolean} value.
|
||||
*
|
||||
* @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}.
|
||||
* @since 1.10
|
||||
*/
|
||||
public static ConditionalOperator conditional(Field booleanField, Object then, Object otherwise) {
|
||||
return ConditionalOperator.newBuilder().when(booleanField).then(then).otherwise(otherwise);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link ConditionalOperator} for the given {@link Criteria}.
|
||||
*
|
||||
* @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 class Aggregation {
|
||||
*/
|
||||
public DBObject toDbObject(String inputCollectionName, AggregationOperationContext rootContext) {
|
||||
|
||||
AggregationOperationContext context = rootContext;
|
||||
List<DBObject> operationDocuments = new ArrayList<DBObject>(operations.size());
|
||||
|
||||
for (AggregationOperation operation : operations) {
|
||||
|
||||
operationDocuments.add(operation.toDBObject(context));
|
||||
|
||||
if (operation instanceof FieldsExposingAggregationOperation) {
|
||||
|
||||
FieldsExposingAggregationOperation exposedFieldsOperation = (FieldsExposingAggregationOperation) operation;
|
||||
|
||||
if (operation instanceof InheritsFieldsAggregationOperation) {
|
||||
context = new InheritingExposedFieldsAggregationOperationContext(exposedFieldsOperation.getFields(), context);
|
||||
} else {
|
||||
context = new ExposedFieldsAggregationOperationContext(exposedFieldsOperation.getFields(), context);
|
||||
}
|
||||
}
|
||||
}
|
||||
List<DBObject> operationDocuments = AggregationOperationRenderer.toDBObject(operations, rootContext);
|
||||
|
||||
DBObject command = new BasicDBObject("aggregate", inputCollectionName);
|
||||
command.put("pipeline", operationDocuments);
|
||||
@@ -531,43 +593,7 @@ public class Aggregation {
|
||||
*/
|
||||
@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));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -607,7 +633,7 @@ public class Aggregation {
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Enum#toString()
|
||||
*/
|
||||
|
||||
@@ -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 @@ import com.mongodb.DBObject;
|
||||
*
|
||||
* @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 enum AggregationFunctionExpressions {
|
||||
*
|
||||
* @author Thomas Darimont
|
||||
* @author Oliver Gierke
|
||||
* @since 1.10
|
||||
* @since 1.7
|
||||
*/
|
||||
static class FunctionExpression implements AggregationExpression {
|
||||
|
||||
|
||||
@@ -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<DBObject> toDBObject(List<AggregationOperation> operations, AggregationOperationContext rootContext) {
|
||||
|
||||
List<DBObject> operationDocuments = new ArrayList<DBObject>(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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
* <a href="http://docs.spring.io/spring/docs/current/spring-framework-reference/html/expressions.html">SpEL
|
||||
* expression</a>. <br />
|
||||
* <br />
|
||||
* <strong>Samples:</strong> <br />
|
||||
* <code>
|
||||
* <pre>
|
||||
* // { $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')");
|
||||
* </pre>
|
||||
* </code>
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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. <br />
|
||||
* 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. <br />
|
||||
* We recommend to use the static factory method {@link Aggregation#bucketAuto(String, int)} instead of creating
|
||||
* instances of this class directly.
|
||||
*
|
||||
* @see <a href=
|
||||
* "http://docs.mongodb.org/manual/reference/aggregation/bucketAuto/">http://docs.mongodb.org/manual/reference/aggregation/bucketAuto/</a>
|
||||
* @see BucketOperationSupport
|
||||
* @author Mark Paluch
|
||||
* @author Christoph Strobl
|
||||
* @since 1.10
|
||||
*/
|
||||
public class BucketAutoOperation extends BucketOperationSupport<BucketAutoOperation, BucketAutoOperationOutputBuilder>
|
||||
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}. <br />
|
||||
* 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<BucketAutoOperationOutputBuilder, BucketAutoOperation> {
|
||||
|
||||
/**
|
||||
* 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<BucketAutoOperationOutputBuilder, BucketAutoOperation> {
|
||||
|
||||
/**
|
||||
* 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 <a
|
||||
* href="https://docs.mongodb.com/manual/reference/operator/aggregation/bucketAuto/#granularity>https://docs.mongodb.com/manual/reference/operator/aggregation/bucketAuto/#granularity</a>
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public enum Granularities implements Granularity {
|
||||
|
||||
R5, R10, R20, R40, R80, //
|
||||
|
||||
SERIES_1_2_5("1-2-5"), //
|
||||
|
||||
E6, E12, E24, E48, E96, E192, //
|
||||
|
||||
POWERSOF2;
|
||||
|
||||
private final String granularity;
|
||||
|
||||
Granularities() {
|
||||
this.granularity = name();
|
||||
}
|
||||
|
||||
Granularities(String granularity) {
|
||||
this.granularity = granularity;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.aggregation.GranularitytoMongoGranularity()
|
||||
*/
|
||||
@Override
|
||||
public String getMongoRepresentation() {
|
||||
return granularity;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,227 @@
|
||||
/*
|
||||
* 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.mongodb.core.aggregation.BucketOperation.BucketOperationOutputBuilder;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.mongodb.BasicDBObject;
|
||||
import com.mongodb.DBObject;
|
||||
|
||||
/**
|
||||
* Encapsulates the aggregation framework {@code $bucket}-operation. <br />
|
||||
*
|
||||
* 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. <br />
|
||||
*
|
||||
* We recommend to use the static factory method {@link Aggregation#bucket(String)} instead of creating instances of
|
||||
* this class directly.
|
||||
*
|
||||
* @see <a href="http://docs.mongodb.org/manual/reference/aggregation/bucket/">http://docs.mongodb.org/manual/reference/aggregation/bucket/</a>
|
||||
* @see BucketOperationSupport
|
||||
* @author Mark Paluch
|
||||
* @since 1.10
|
||||
*/
|
||||
public class BucketOperation extends BucketOperationSupport<BucketOperation, BucketOperationOutputBuilder>
|
||||
implements FieldsExposingAggregationOperation {
|
||||
|
||||
private final List<Object> 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<Object> boundaries, Object defaultBucket) {
|
||||
|
||||
super(bucketOperation);
|
||||
|
||||
this.boundaries = new ArrayList<Object>(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<Object> newBoundaries = new ArrayList<Object>(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<BucketOperationOutputBuilder, BucketOperation> {
|
||||
|
||||
/**
|
||||
* 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<BucketOperationOutputBuilder, BucketOperation> {
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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. <br />
|
||||
* Bucket stages collect documents into buckets and can contribute output fields. <br />
|
||||
* Implementing classes are required to provide an {@link OutputBuilder}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Christoph Strobl
|
||||
* @since 1.10
|
||||
*/
|
||||
public abstract class BucketOperationSupport<T extends BucketOperationSupport<T, B>, B extends OutputBuilder<B, T>>
|
||||
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<B, T> 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<B extends OutputBuilder<B, T>, T extends BucketOperationSupport<T, B>>
|
||||
extends OutputBuilder<B, T> {
|
||||
|
||||
/**
|
||||
* 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<B extends OutputBuilder<B, T>, T extends BucketOperationSupport<T, B>> {
|
||||
|
||||
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. <br />
|
||||
* 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<Object> objects = new ArrayList<Object>(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<Output> outputs;
|
||||
|
||||
/**
|
||||
* Creates a new, empty {@link Outputs}.
|
||||
*/
|
||||
private Outputs() {
|
||||
this.outputs = new ArrayList<Output>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link Outputs} containing all given {@link Output}s.
|
||||
*
|
||||
* @param current
|
||||
* @param output
|
||||
*/
|
||||
private Outputs(Collection<Output> current, Output output) {
|
||||
|
||||
this.outputs = new ArrayList<Output>(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. <br />
|
||||
* 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. <br />
|
||||
* {@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<Object> 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<? extends Object> 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<Object>(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<Object> operationArguments = getOperationArguments(context);
|
||||
return new BasicDBObject(operation,
|
||||
operationArguments.size() == 1 ? operationArguments.get(0) : operationArguments);
|
||||
}
|
||||
|
||||
protected List<Object> getOperationArguments(AggregationOperationContext context) {
|
||||
|
||||
List<Object> result = new ArrayList<Object>(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<Object> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<Object> clauses = new ArrayList<Object>();
|
||||
|
||||
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<Object> getClauses(AggregationOperationContext context, DBObject mappedObject) {
|
||||
|
||||
List<Object> clauses = new ArrayList<Object>();
|
||||
|
||||
for (String key : mappedObject.keySet()) {
|
||||
|
||||
Object predicate = mappedObject.get(key);
|
||||
clauses.addAll(getClauses(context, key, predicate));
|
||||
}
|
||||
|
||||
return clauses;
|
||||
}
|
||||
|
||||
private List<Object> getClauses(AggregationOperationContext context, String key, Object predicate) {
|
||||
|
||||
List<Object> clauses = new ArrayList<Object>();
|
||||
|
||||
if (predicate instanceof List) {
|
||||
|
||||
List<Object> args = new ArrayList<Object>();
|
||||
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<Object> args = new ArrayList<Object>();
|
||||
args.add("$" + key);
|
||||
args.add(nested.get(s));
|
||||
clauses.add(new BasicDBObject(s, args));
|
||||
}
|
||||
|
||||
} else if (!isKeyword(key)) {
|
||||
|
||||
List<Object> args = new ArrayList<Object>();
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<CaseOperator> 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 <a href=
|
||||
* "http://docs.mongodb.com/manual/reference/operator/aggregation/ifNull/">http://docs.mongodb.com/manual/reference/operator/aggregation/ifNull/</a>
|
||||
* @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<Object> list = new ArrayList<Object>();
|
||||
|
||||
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<String, Object> 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<CaseOperator> conditions) {
|
||||
|
||||
Assert.notNull(conditions, "Conditions must not be null!");
|
||||
return new Switch(Collections.<String, Object> singletonMap("branches", new ArrayList<CaseOperator>(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 <a href=
|
||||
* "http://docs.mongodb.com/manual/reference/operator/aggregation/cond/">http://docs.mongodb.com/manual/reference/operator/aggregation/cond/</a>
|
||||
* @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<Object> clauses = new ArrayList<Object>();
|
||||
|
||||
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<Object> getClauses(AggregationOperationContext context, DBObject mappedObject) {
|
||||
|
||||
List<Object> clauses = new ArrayList<Object>();
|
||||
|
||||
for (String key : mappedObject.keySet()) {
|
||||
|
||||
Object predicate = mappedObject.get(key);
|
||||
clauses.addAll(getClauses(context, key, predicate));
|
||||
}
|
||||
|
||||
return clauses;
|
||||
}
|
||||
|
||||
private List<Object> getClauses(AggregationOperationContext context, String key, Object predicate) {
|
||||
|
||||
List<Object> clauses = new ArrayList<Object>();
|
||||
|
||||
if (predicate instanceof List) {
|
||||
|
||||
List<Object> args = new ArrayList<Object>();
|
||||
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<Object> args = new ArrayList<Object>();
|
||||
args.add("$" + key);
|
||||
args.add(nested.get(s));
|
||||
clauses.add(new BasicDBObject(s, args));
|
||||
}
|
||||
|
||||
} else if (!isKeyword(key)) {
|
||||
|
||||
List<Object> args = new ArrayList<Object>();
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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. <br />
|
||||
* We recommend to use the static factory method {@link Aggregation#count()} instead of creating instances of this class
|
||||
* directly.
|
||||
*
|
||||
* @see <a href=
|
||||
* "https://docs.mongodb.com/manual/reference/operator/aggregation/count/#pipe._S_count">https://docs.mongodb.com/manual/reference/operator/aggregation/count/</a>
|
||||
* @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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<String, Object> argumentMap(Object date, String format) {
|
||||
|
||||
java.util.Map<String, Object> args = new LinkedHashMap<String, Object>(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,8 +22,10 @@ import java.util.Iterator;
|
||||
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 @@ public final class ExposedFields implements Iterable<ExposedField> {
|
||||
result.add(new ExposedField(field, synthetic));
|
||||
}
|
||||
|
||||
return ExposedFields.from(result);
|
||||
return from(result);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -336,12 +338,36 @@ public final class ExposedFields implements Iterable<ExposedField> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 @@ public final class ExposedFields implements Iterable<ExposedField> {
|
||||
*
|
||||
* @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 final class ExposedFields implements Iterable<ExposedField> {
|
||||
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 final class ExposedFields implements Iterable<ExposedField> {
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
|
||||
if(getRaw().startsWith("$")) {
|
||||
return getRaw();
|
||||
}
|
||||
|
||||
return String.format("$%s", getRaw());
|
||||
}
|
||||
|
||||
@@ -398,11 +426,11 @@ public final class ExposedFields implements Iterable<ExposedField> {
|
||||
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 final class ExposedFields implements Iterable<ExposedField> {
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 @@ class ExposedFieldsAggregationOperationContext implements AggregationOperationCo
|
||||
|
||||
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 @@ class ExposedFieldsAggregationOperationContext implements AggregationOperationCo
|
||||
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;
|
||||
|
||||
@@ -0,0 +1,228 @@
|
||||
/*
|
||||
* 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.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. <br />
|
||||
* 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. <br />
|
||||
* As of MongoDB 3.4, {@link FacetOperation} cannot be used with nested pipelines containing {@link GeoNearOperation},
|
||||
* {@link OutOperation} and {@link FacetOperation}. <br />
|
||||
* We recommend to use the static factory method {@link Aggregation#facet()} instead of creating instances of this class
|
||||
* directly.
|
||||
*
|
||||
* @see http://docs.mongodb.org/manual/reference/aggregation/facet/
|
||||
* @author Mark Paluch
|
||||
* @author Christoph Strobl
|
||||
* @since 1.10
|
||||
*/
|
||||
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}. <br />
|
||||
* {@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<AggregationOperation> operations;
|
||||
|
||||
private FacetOperationBuilder(Facets current, List<AggregationOperation> 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.<Facet> emptyList());
|
||||
|
||||
private List<Facet> facets;
|
||||
|
||||
/**
|
||||
* Creates a new {@link Facets} given {@link List} of {@link Facet}.
|
||||
*
|
||||
* @param facets
|
||||
*/
|
||||
private Facets(List<Facet> 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<AggregationOperation> operations) {
|
||||
|
||||
Assert.hasText(fieldName, "FieldName must not be null or empty!");
|
||||
Assert.notNull(operations, "AggregationOperations must not be null!");
|
||||
|
||||
List<Facet> facets = new ArrayList<Facet>(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<AggregationOperation> 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<AggregationOperation> 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<DBObject> toDBObjects(AggregationOperationContext context) {
|
||||
return AggregationOperationRenderer.toDBObject(operations, context);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@ package org.springframework.data.mongodb.core.aggregation;
|
||||
|
||||
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 final class Fields implements Iterable<Field> {
|
||||
return fields.iterator();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return
|
||||
* @since 1.10
|
||||
*/
|
||||
public List<Field> asList() {
|
||||
return Collections.unmodifiableList(fields);
|
||||
}
|
||||
|
||||
/**
|
||||
* Value object to encapsulate a field in an aggregation operation.
|
||||
*
|
||||
@@ -192,6 +201,7 @@ public final class Fields implements Iterable<Field> {
|
||||
*/
|
||||
static class AggregationField implements Field {
|
||||
|
||||
private final String raw;
|
||||
private final String name;
|
||||
private final String target;
|
||||
|
||||
@@ -216,6 +226,7 @@ public final class Fields implements Iterable<Field> {
|
||||
*/
|
||||
public AggregationField(String name, String target) {
|
||||
|
||||
raw = name;
|
||||
String nameToSet = cleanUp(name);
|
||||
String targetToSet = cleanUp(target);
|
||||
|
||||
@@ -257,6 +268,11 @@ public final class Fields implements Iterable<Field> {
|
||||
* @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 final class Fields implements Iterable<Field> {
|
||||
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()
|
||||
|
||||
@@ -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. <br />
|
||||
* Performs a recursive search on a collection, with options for restricting the search by recursion depth and query
|
||||
* filter. <br />
|
||||
* We recommend to use the static factory method {@link Aggregation#graphLookup(String)} instead of creating instances
|
||||
* of this class directly.
|
||||
*
|
||||
* @see <a href=
|
||||
* "http://docs.mongodb.org/manual/reference/aggregation/graphLookup/">http://docs.mongodb.org/manual/reference/aggregation/graphLookup/</a>
|
||||
* @author Mark Paluch
|
||||
* @author Christoph Strobl
|
||||
* @since 1.10
|
||||
*/
|
||||
public class GraphLookupOperation implements InheritsFieldsAggregationOperation {
|
||||
|
||||
private static final Set<Class<?>> ALLOWED_START_TYPES = new HashSet<Class<?>>(
|
||||
Arrays.<Class<?>> asList(AggregationExpression.class, String.class, Field.class, DBObject.class));
|
||||
|
||||
private final String from;
|
||||
private final List<Object> 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<Object> 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<Object> mappedStartWith = new ArrayList<Object>(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<? extends Object> 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<Object> fields = new ArrayList<Object>(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<Object> verifyAndPotentiallyTransformStartsWithTypes(Object... expressions) {
|
||||
|
||||
List<Object> expressionsToUse = new ArrayList<Object>(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<Object> startWith;
|
||||
private final Field connectFrom;
|
||||
private final Field connectTo;
|
||||
private Long maxDepth;
|
||||
private Field depthField;
|
||||
private CriteriaDefinition restrictSearchWithMatch;
|
||||
|
||||
protected GraphLookupOperationBuilder(String from, List<? extends Object> startWith, String connectFrom,
|
||||
String connectTo) {
|
||||
|
||||
this.from = from;
|
||||
this.startWith = new ArrayList<Object>(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2015 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.
|
||||
@@ -39,6 +39,8 @@ import com.mongodb.DBObject;
|
||||
* @author Sebastian Herold
|
||||
* @author Thomas Darimont
|
||||
* @author Oliver Gierke
|
||||
* @author Gustavo de Geus
|
||||
* @author Christoph Strobl
|
||||
* @since 1.3
|
||||
*/
|
||||
public class GroupOperation implements FieldsExposingAggregationOperation {
|
||||
@@ -307,6 +309,51 @@ public class GroupOperation implements FieldsExposingAggregationOperation {
|
||||
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 @@ public class GroupOperation implements FieldsExposingAggregationOperation {
|
||||
|
||||
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");
|
||||
|
||||
private String mongoOperator;
|
||||
|
||||
GroupOps(String mongoOperator) {
|
||||
this.mongoOperator = mongoOperator;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
|
||||
String[] parts = name().split("_");
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
for (String part : parts) {
|
||||
String lowerCase = part.toLowerCase(Locale.US);
|
||||
builder.append(builder.length() == 0 ? lowerCase : StringUtils.capitalize(lowerCase));
|
||||
}
|
||||
|
||||
return "$" + builder.toString();
|
||||
return mongoOperator;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<Object> list = new ArrayList<Object>();
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 @@ class InheritingExposedFieldsAggregationOperationContext extends ExposedFieldsAg
|
||||
AggregationOperationContext previousContext) {
|
||||
|
||||
super(exposedFields, previousContext);
|
||||
Assert.notNull(previousContext, "PreviousContext must not be null!");
|
||||
|
||||
this.previousContext = previousContext;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -17,9 +17,13 @@ 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.VariableOperators.Let.ExpressionVariable;
|
||||
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;
|
||||
@@ -243,22 +247,22 @@ public class ProjectionOperation implements FieldsExposingAggregationOperation {
|
||||
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 class ProjectionOperation implements FieldsExposingAggregationOperation {
|
||||
* @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 class ProjectionOperation implements FieldsExposingAggregationOperation {
|
||||
* @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 class ProjectionOperation implements FieldsExposingAggregationOperation {
|
||||
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 class ProjectionOperation implements FieldsExposingAggregationOperation {
|
||||
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 class ProjectionOperation implements FieldsExposingAggregationOperation {
|
||||
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 class ProjectionOperation implements FieldsExposingAggregationOperation {
|
||||
/**
|
||||
* 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 class ProjectionOperation implements FieldsExposingAggregationOperation {
|
||||
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. <br />
|
||||
*
|
||||
@@ -616,6 +676,85 @@ public class ProjectionOperation implements FieldsExposingAggregationOperation {
|
||||
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. <br />
|
||||
* If {@literal n} is positive, $slice returns up to the first n elements in the array. <br />
|
||||
@@ -641,7 +780,452 @@ public class ProjectionOperation implements FieldsExposingAggregationOperation {
|
||||
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<ExpressionVariable> 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)
|
||||
*/
|
||||
@@ -825,7 +1409,18 @@ public class ProjectionOperation implements FieldsExposingAggregationOperation {
|
||||
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;
|
||||
|
||||
@@ -0,0 +1,574 @@
|
||||
/*
|
||||
* 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.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. <br />
|
||||
* We recommend to use the static factory method {@link Aggregation#replaceRoot(String)} instead of creating instances
|
||||
* of this class directly.
|
||||
*
|
||||
* @see <a href=
|
||||
* "https://docs.mongodb.com/manual/reference/operator/aggregation/replaceRoot/">https://docs.mongodb.com/manual/reference/operator/aggregation/replaceRoot/</a>
|
||||
* @author Mark Paluch
|
||||
* @author Christoph Strobl
|
||||
* @since 1.10
|
||||
*/
|
||||
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. <br />
|
||||
* {@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}. <br />
|
||||
* {@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. <br />
|
||||
* 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<ReplacementContributor> replacements;
|
||||
|
||||
/**
|
||||
* Creates an empty {@link ReplacementDocument}.
|
||||
*/
|
||||
protected ReplacementDocument() {
|
||||
replacements = new ArrayList<ReplacementContributor>();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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<ReplacementContributor> 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<ReplacementContributor> replacements = new ArrayList<ReplacementContributor>(
|
||||
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}. <br />
|
||||
* 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.ExpressionNode;
|
||||
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 @@ import com.mongodb.DBObject;
|
||||
* 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 @@ class SpelExpressionTransformer implements AggregationExpressionTransformer {
|
||||
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 @@ class SpelExpressionTransformer implements AggregationExpressionTransformer {
|
||||
* @author Thomas Darimont
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
private static abstract class ExpressionNodeConversion<T extends ExpressionNode> implements
|
||||
AggregationExpressionTransformer {
|
||||
private static abstract class ExpressionNodeConversion<T extends ExpressionNode>
|
||||
implements AggregationExpressionTransformer {
|
||||
|
||||
private final AggregationExpressionTransformer transformer;
|
||||
private final Class<? extends ExpressionNode> nodeType;
|
||||
@@ -235,8 +245,17 @@ class SpelExpressionTransformer implements AggregationExpressionTransformer {
|
||||
protected Object convert(AggregationExpressionTransformationContext<OperatorNode> 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 @@ class SpelExpressionTransformer implements AggregationExpressionTransformer {
|
||||
return nextDbObject;
|
||||
}
|
||||
|
||||
private Object convertUnaryMinusOp(ExpressionTransformationContextSupport<OperatorNode> context, Object leftResult) {
|
||||
private Object convertUnaryMinusOp(ExpressionTransformationContextSupport<OperatorNode> context,
|
||||
Object leftResult) {
|
||||
|
||||
Object result = leftResult instanceof Number ? leftResult
|
||||
: new BasicDBObject("$multiply", dbList(-1, leftResult));
|
||||
@@ -289,7 +309,7 @@ class SpelExpressionTransformer implements AggregationExpressionTransformer {
|
||||
*/
|
||||
@Override
|
||||
protected boolean supports(ExpressionNode node) {
|
||||
return node.isMathematicalOperation();
|
||||
return node.isMathematicalOperation() || node.isLogicalOperator();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -462,13 +482,33 @@ class SpelExpressionTransformer implements AggregationExpressionTransformer {
|
||||
protected Object convert(AggregationExpressionTransformationContext<MethodReferenceNode> context) {
|
||||
|
||||
MethodReferenceNode node = context.getCurrentNode();
|
||||
List<Object> args = new ArrayList<Object>();
|
||||
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<Object> argList = new ArrayList<Object>();
|
||||
|
||||
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 @@ class SpelExpressionTransformer implements AggregationExpressionTransformer {
|
||||
return node.isOfType(CompoundExpression.class);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
* @since 1.10
|
||||
*/
|
||||
static class NotOperatorNodeConversion extends ExpressionNodeConversion<NotOperatorNode> {
|
||||
|
||||
/**
|
||||
* 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<NotOperatorNode> context) {
|
||||
|
||||
NotOperatorNode node = context.getCurrentNode();
|
||||
List<Object> args = new ArrayList<Object>();
|
||||
|
||||
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<ExpressionNode> {
|
||||
|
||||
/**
|
||||
* 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<ExpressionNode> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -22,6 +22,7 @@ import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.mapping.context.PersistentPropertyPath;
|
||||
import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExposedField;
|
||||
import org.springframework.data.mongodb.core.aggregation.ExposedFields.FieldReference;
|
||||
import org.springframework.data.mongodb.core.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;
|
||||
@@ -98,6 +99,6 @@ public class TypeBasedAggregationOperationContext implements AggregationOperatio
|
||||
Field mappedField = field(propertyPath.getLeafProperty().getName(),
|
||||
propertyPath.toDotPath(MongoPersistentProperty.PropertyToFieldNameConverter.INSTANCE));
|
||||
|
||||
return new FieldReference(new ExposedField(mappedField, true));
|
||||
return new DirectFieldReference(new ExposedField(mappedField, true));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<ExpressionVariable> 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<ExpressionVariable> vars;
|
||||
private final AggregationExpression expression;
|
||||
|
||||
private Let(List<ExpressionVariable> 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<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(new ArrayList<ExpressionVariable>(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -892,10 +892,6 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
|
||||
Class<?> collectionType = targetType.getType();
|
||||
|
||||
if (sourceValue.isEmpty()) {
|
||||
return getPotentiallyConvertedSimpleRead(new HashSet<Object>(), collectionType);
|
||||
}
|
||||
|
||||
TypeInformation<?> componentType = targetType.getComponentType();
|
||||
Class<?> rawComponentType = componentType == null ? null : componentType.getType();
|
||||
|
||||
@@ -903,6 +899,10 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
Collection<Object> items = targetType.getType().isArray() ? new ArrayList<Object>()
|
||||
: CollectionFactory.createCollection(collectionType, rawComponentType, sourceValue.size());
|
||||
|
||||
if (sourceValue.isEmpty()) {
|
||||
return getPotentiallyConvertedSimpleRead(items, collectionType);
|
||||
}
|
||||
|
||||
if (!DBRef.class.equals(rawComponentType) && isCollectionOfDbRefWhereBulkFetchIsPossible(sourceValue)) {
|
||||
return bulkReadAndConvertDBRefs((List<DBRef>) (List) (sourceValue), componentType, path, rawComponentType);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2015 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.
|
||||
@@ -18,6 +18,8 @@ package org.springframework.data.mongodb.core.convert;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.domain.Sort.Order;
|
||||
import org.springframework.data.mapping.Association;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
|
||||
@@ -37,6 +39,7 @@ import com.mongodb.DBObject;
|
||||
* @author Thomas Darimont
|
||||
* @author Oliver Gierke
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class UpdateMapper extends QueryMapper {
|
||||
|
||||
@@ -130,11 +133,23 @@ public class UpdateMapper extends QueryMapper {
|
||||
}
|
||||
|
||||
private DBObject getMappedValue(Field field, Modifier modifier) {
|
||||
return new BasicDBObject(modifier.getKey(), getMappedModifier(field, modifier));
|
||||
}
|
||||
|
||||
private Object getMappedModifier(Field field, Modifier modifier) {
|
||||
|
||||
Object value = modifier.getValue();
|
||||
|
||||
if (value instanceof Sort) {
|
||||
|
||||
DBObject sortObject = getSortObject((Sort) value);
|
||||
return field == null || field.getPropertyEntity() == null ? sortObject
|
||||
: getMappedSort(sortObject, field.getPropertyEntity());
|
||||
}
|
||||
|
||||
TypeInformation<?> 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 @@ public class UpdateMapper extends QueryMapper {
|
||||
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)
|
||||
|
||||
@@ -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 class GeospatialIndex implements IndexDefinition {
|
||||
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 <a href=
|
||||
* "https://docs.mongodb.com/manual/core/index-partial/">https://docs.mongodb.com/manual/core/index-partial/</a>
|
||||
* @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 class GeospatialIndex implements IndexDefinition {
|
||||
break;
|
||||
}
|
||||
|
||||
if (filter != null) {
|
||||
dbo.put("partialFilterExpression", filter.getFilterObject());
|
||||
}
|
||||
|
||||
return dbo;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2010-2015 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.
|
||||
@@ -63,6 +63,8 @@ public class Index implements IndexDefinition {
|
||||
|
||||
private long expire = -1;
|
||||
|
||||
private IndexFilter filter;
|
||||
|
||||
public Index() {}
|
||||
|
||||
public Index(String key, Direction direction) {
|
||||
@@ -176,6 +178,21 @@ public class Index implements IndexDefinition {
|
||||
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 <a href=
|
||||
* "https://docs.mongodb.com/manual/core/index-partial/">https://docs.mongodb.com/manual/core/index-partial/</a>
|
||||
* @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 class Index implements IndexDefinition {
|
||||
dbo.put("expireAfterSeconds", expire);
|
||||
}
|
||||
|
||||
if (filter != null) {
|
||||
dbo.put("partialFilterExpression", filter.getFilterObject());
|
||||
}
|
||||
return dbo;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
* <a href="https://docs.mongodb.com/manual/core/index-partial/">Partial Indexes</a>.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 1.10
|
||||
*/
|
||||
public interface IndexFilter {
|
||||
|
||||
/**
|
||||
* Get the raw (unmapped) filter expression.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
DBObject getFilterObject();
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2014 the original author or authors.
|
||||
* Copyright 2002-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,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,6 +26,8 @@ import java.util.List;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
import com.mongodb.DBObject;
|
||||
|
||||
/**
|
||||
* @author Mark Pollack
|
||||
* @author Oliver Gierke
|
||||
@@ -30,6 +35,10 @@ import org.springframework.util.ObjectUtils;
|
||||
*/
|
||||
public class IndexInfo {
|
||||
|
||||
private static final Double ONE = Double.valueOf(1);
|
||||
private static final Double MINUS_ONE = Double.valueOf(-1);
|
||||
private static final Collection<String> TWO_D_IDENTIFIERS = Arrays.asList("2d", "2dsphere");
|
||||
|
||||
private final List<IndexField> indexFields;
|
||||
|
||||
private final String name;
|
||||
@@ -37,6 +46,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 +72,64 @@ public class IndexInfo {
|
||||
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<IndexField> indexFields = new ArrayList<IndexField>(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.
|
||||
*
|
||||
@@ -113,10 +181,19 @@ public class IndexInfo {
|
||||
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 +207,7 @@ public class IndexInfo {
|
||||
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 +249,9 @@ public class IndexInfo {
|
||||
if (!ObjectUtils.nullSafeEquals(language, other.language)) {
|
||||
return false;
|
||||
}
|
||||
if (!ObjectUtils.nullSafeEquals(partialFilterExpression, other.partialFilterExpression)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014 the original author or authors.
|
||||
* Copyright 2014-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 TextIndexDefinition implements IndexDefinition {
|
||||
private Set<TextIndexedFieldSpec> fieldSpecs;
|
||||
private String defaultLanguage;
|
||||
private String languageOverride;
|
||||
private IndexFilter filter;
|
||||
|
||||
TextIndexDefinition() {
|
||||
fieldSpecs = new LinkedHashSet<TextIndexedFieldSpec>();
|
||||
@@ -129,6 +130,10 @@ public class TextIndexDefinition implements IndexDefinition {
|
||||
options.put("language_override", languageOverride);
|
||||
}
|
||||
|
||||
if (filter != null) {
|
||||
options.put("partialFilterExpression", filter.getFilterObject());
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
@@ -288,8 +293,8 @@ public class TextIndexDefinition implements IndexDefinition {
|
||||
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));
|
||||
@@ -318,15 +323,30 @@ public class TextIndexDefinition implements IndexDefinition {
|
||||
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 <a href=
|
||||
* "https://docs.mongodb.com/manual/core/index-partial/">https://docs.mongodb.com/manual/core/index-partial/</a>
|
||||
* @since 1.10
|
||||
*/
|
||||
public TextIndexDefinitionBuilder partial(IndexFilter filter) {
|
||||
|
||||
this.instance.filter = filter;
|
||||
return this;
|
||||
}
|
||||
|
||||
public TextIndexDefinition build() {
|
||||
return this.instance;
|
||||
}
|
||||
|
||||
@@ -27,6 +27,9 @@ import java.util.Map;
|
||||
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 @@ import com.mongodb.DBObject;
|
||||
* @author Thomas Darimont
|
||||
* @author Alexey Plotnik
|
||||
* @author Mark Paluch
|
||||
* @author Pavel Vodrazka
|
||||
*/
|
||||
public class Update {
|
||||
|
||||
@@ -312,7 +316,7 @@ public class Update {
|
||||
*/
|
||||
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;
|
||||
}
|
||||
@@ -329,7 +333,7 @@ public class Update {
|
||||
*/
|
||||
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;
|
||||
}
|
||||
@@ -346,7 +350,7 @@ public class Update {
|
||||
*/
|
||||
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 class Update {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 class Update {
|
||||
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}.
|
||||
*
|
||||
|
||||
@@ -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.
|
||||
@@ -23,12 +23,14 @@ import org.springframework.expression.spel.SpelNode;
|
||||
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
|
||||
*/
|
||||
public class ExpressionNode implements Iterable<ExpressionNode> {
|
||||
|
||||
@@ -79,6 +81,10 @@ public class ExpressionNode implements Iterable<ExpressionNode> {
|
||||
return new LiteralNode((Literal) node, state);
|
||||
}
|
||||
|
||||
if (node instanceof OperatorNot) {
|
||||
return new NotOperatorNode((OperatorNot) node, state);
|
||||
}
|
||||
|
||||
return new ExpressionNode(node, state);
|
||||
}
|
||||
|
||||
@@ -122,6 +128,16 @@ public class ExpressionNode implements Iterable<ExpressionNode> {
|
||||
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.
|
||||
*
|
||||
|
||||
@@ -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 @@ import org.springframework.expression.spel.ast.StringLiteral;
|
||||
|
||||
/**
|
||||
* A node representing a literal in an expression.
|
||||
*
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public class LiteralNode extends ExpressionNode {
|
||||
|
||||
private static final Set<Class<?>> SUPPORTED_LITERAL_TYPES;
|
||||
private final Literal literal;
|
||||
|
||||
static {
|
||||
|
||||
Set<Class<?>> supportedTypes = new HashSet<Class<?>>(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 class LiteralNode extends ExpressionNode {
|
||||
*/
|
||||
@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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<String, String> FUNCTIONS;
|
||||
private static final Map<String, AggregationMethodReference> FUNCTIONS;
|
||||
|
||||
static {
|
||||
|
||||
Map<String, String> map = new HashMap<String, String>();
|
||||
Map<String, AggregationMethodReference> map = new HashMap<String, AggregationMethodReference>();
|
||||
|
||||
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.
|
||||
// BOOLEAN OPERATORS
|
||||
map.put("and", arrayArgumentAggregationMethodReference().forOperator("$and"));
|
||||
map.put("or", arrayArgumentAggregationMethodReference().forOperator("$or"));
|
||||
map.put("not", arrayArgumentAggregationMethodReference().forOperator("$not"));
|
||||
|
||||
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
|
||||
// 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. <br />
|
||||
* <strong>NOTE:</strong> 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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
@@ -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 @@ package org.springframework.data.mongodb.core.spel;
|
||||
|
||||
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<String, String> OPERATORS;
|
||||
private static final Set<Class> SUPPORTED_MATH_OPERATORS;
|
||||
|
||||
static {
|
||||
|
||||
Map<String, String> map = new HashMap<String, String>(6);
|
||||
Map<String, String> map = new HashMap<String, String>(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<Class> set = new HashSet<Class>(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 class OperatorNode extends ExpressionNode {
|
||||
* @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());
|
||||
}
|
||||
|
||||
|
||||
@@ -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 "";
|
||||
}
|
||||
@@ -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 "";
|
||||
}
|
||||
@@ -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 "";
|
||||
}
|
||||
@@ -62,6 +62,14 @@ public @interface Query {
|
||||
*/
|
||||
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.
|
||||
*
|
||||
|
||||
@@ -20,7 +20,9 @@ import org.springframework.data.convert.EntityInstantiators;
|
||||
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 @@ import org.springframework.util.Assert;
|
||||
* @author Oliver Gierke
|
||||
* @author Thomas Darimont
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public abstract class AbstractMongoQuery implements RepositoryQuery {
|
||||
|
||||
@@ -123,8 +126,12 @@ public abstract class AbstractMongoQuery implements RepositoryQuery {
|
||||
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 @@ public abstract class AbstractMongoQuery implements RepositoryQuery {
|
||||
*/
|
||||
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.
|
||||
*
|
||||
|
||||
@@ -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.
|
||||
@@ -15,8 +15,15 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.repository.query;
|
||||
|
||||
import java.util.Collections;
|
||||
import lombok.Value;
|
||||
|
||||
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;
|
||||
|
||||
@@ -39,6 +46,7 @@ import com.mongodb.util.JSON;
|
||||
* @author Christoph Strobl
|
||||
* @author Thomas Darimont
|
||||
* @author Oliver Gierke
|
||||
* @author Mark Paluch
|
||||
* @since 1.9
|
||||
*/
|
||||
class ExpressionEvaluatingParameterBinder {
|
||||
@@ -85,7 +93,7 @@ class ExpressionEvaluatingParameterBinder {
|
||||
*
|
||||
* @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 +102,72 @@ class ExpressionEvaluatingParameterBinder {
|
||||
return input;
|
||||
}
|
||||
|
||||
boolean isCompletlyParameterizedQuery = input.matches("^\\?\\d+$");
|
||||
StringBuilder result = new StringBuilder(input);
|
||||
|
||||
for (ParameterBinding binding : bindingContext.getBindings()) {
|
||||
|
||||
String parameter = binding.getParameter();
|
||||
int idx = result.indexOf(parameter);
|
||||
|
||||
if (idx == -1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String valueForBinding = getParameterValueForBinding(accessor, bindingContext.getParameters(), binding);
|
||||
|
||||
int start = idx;
|
||||
int end = idx + parameter.length();
|
||||
|
||||
// If the value to bind is an object literal we need to remove the quoting around the expression insertion point.
|
||||
if (valueForBinding.startsWith("{") && !isCompletlyParameterizedQuery) {
|
||||
|
||||
// Is the insertion point actually surrounded by quotes?
|
||||
char beforeStart = result.charAt(start - 1);
|
||||
char afterEnd = result.charAt(end);
|
||||
|
||||
if ((beforeStart == '\'' || beforeStart == '"') && (afterEnd == '\'' || afterEnd == '"')) {
|
||||
|
||||
// Skip preceding and following quote
|
||||
start -= 1;
|
||||
end += 1;
|
||||
}
|
||||
}
|
||||
|
||||
result.replace(start, end, valueForBinding);
|
||||
if (input.matches("^\\?\\d+$")) {
|
||||
return getParameterValueForBinding(accessor, bindingContext.getParameters(),
|
||||
bindingContext.getBindings().iterator().next());
|
||||
}
|
||||
|
||||
return result.toString();
|
||||
Matcher matcher = createReplacementPattern(bindingContext.getBindings()).matcher(input);
|
||||
StringBuffer buffer = new StringBuffer();
|
||||
|
||||
while (matcher.find()) {
|
||||
|
||||
ParameterBinding binding = bindingContext.getBindingFor(extractPlaceholder(matcher.group()));
|
||||
String valueForBinding = getParameterValueForBinding(accessor, bindingContext.getParameters(), binding);
|
||||
|
||||
// appendReplacement does not like unescaped $ sign and others, so we need to quote that stuff first
|
||||
matcher.appendReplacement(buffer, Matcher.quoteReplacement(valueForBinding));
|
||||
|
||||
if (binding.isQuoted()) {
|
||||
postProcessQuotedBinding(buffer, valueForBinding);
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
*/
|
||||
private void postProcessQuotedBinding(StringBuffer buffer, String valueForBinding) {
|
||||
|
||||
int quotationMarkIndex = buffer.length() - valueForBinding.length() - 1;
|
||||
char quotationMark = buffer.charAt(quotationMarkIndex);
|
||||
|
||||
while (quotationMark != '\'' && quotationMark != '"') {
|
||||
|
||||
quotationMarkIndex--;
|
||||
|
||||
if (quotationMarkIndex < 0) {
|
||||
throw new IllegalArgumentException("Could not find opening quotes for quoted parameter");
|
||||
}
|
||||
|
||||
quotationMark = buffer.charAt(quotationMarkIndex);
|
||||
}
|
||||
|
||||
if (valueForBinding.startsWith("{")) { // remove quotation char before the complex object string
|
||||
|
||||
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 +181,7 @@ class ExpressionEvaluatingParameterBinder {
|
||||
: accessor.getBindableValue(binding.getParameterIndex());
|
||||
|
||||
if (value instanceof String && binding.isQuoted()) {
|
||||
return (String) value;
|
||||
return ((String) value).startsWith("{") ? (String) value : ((String) value).replace("\"", "\\\"");
|
||||
}
|
||||
|
||||
if (value instanceof byte[]) {
|
||||
@@ -167,7 +200,7 @@ class ExpressionEvaluatingParameterBinder {
|
||||
|
||||
/**
|
||||
* 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 +214,61 @@ class ExpressionEvaluatingParameterBinder {
|
||||
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<ParameterBinding> bindings) {
|
||||
|
||||
StringBuilder regex = new StringBuilder();
|
||||
|
||||
for (ParameterBinding binding : bindings) {
|
||||
|
||||
regex.append("|");
|
||||
regex.append(Pattern.quote(binding.getParameter()));
|
||||
regex.append("['\"]?"); // 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 groupName The actual {@link Matcher#group() group}.
|
||||
* @return
|
||||
*/
|
||||
private Placeholder extractPlaceholder(String groupName) {
|
||||
|
||||
return !groupName.endsWith("'") && !groupName.endsWith("\"") ? //
|
||||
Placeholder.of(groupName, false) : //
|
||||
Placeholder.of(groupName.substring(0, groupName.length() - 1), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
* @since 1.9
|
||||
*/
|
||||
static class BindingContext {
|
||||
|
||||
final MongoParameters parameters;
|
||||
final List<ParameterBinding> bindings;
|
||||
final Map<Placeholder, ParameterBinding> bindings;
|
||||
|
||||
/**
|
||||
* Creates new {@link BindingContext}.
|
||||
*
|
||||
*
|
||||
* @param parameters
|
||||
* @param bindings
|
||||
*/
|
||||
public BindingContext(MongoParameters parameters, List<ParameterBinding> bindings) {
|
||||
|
||||
this.parameters = parameters;
|
||||
this.bindings = bindings;
|
||||
this.bindings = mapBindings(bindings);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -211,21 +280,70 @@ class ExpressionEvaluatingParameterBinder {
|
||||
|
||||
/**
|
||||
* Get unmodifiable list of {@link ParameterBinding}s.
|
||||
*
|
||||
*
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
public List<ParameterBinding> getBindings() {
|
||||
return Collections.unmodifiableList(bindings);
|
||||
return new ArrayList<ParameterBinding>(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<Placeholder, ParameterBinding> mapBindings(List<ParameterBinding> bindings) {
|
||||
|
||||
Map<Placeholder, ParameterBinding> map = new LinkedHashMap<Placeholder, ParameterBinding>(bindings.size(), 1);
|
||||
|
||||
for (ParameterBinding binding : bindings) {
|
||||
map.put(Placeholder.of(binding.getParameter(), binding.isQuoted()), binding);
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encapsulates a quoted/unquoted parameter placeholder.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 1.9
|
||||
*/
|
||||
@Value(staticConstructor = "of")
|
||||
static class Placeholder {
|
||||
|
||||
private final String parameter;
|
||||
private final boolean quoted;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return quoted ? String.format("'%s'", parameter) : parameter;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,7 +163,6 @@ interface MongoQueryExecution {
|
||||
static final class SingleEntityExecution implements MongoQueryExecution {
|
||||
|
||||
private final MongoOperations operations;
|
||||
private final boolean countProjection;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
@@ -171,7 +170,50 @@ interface 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@ import com.mongodb.util.JSONParseException;
|
||||
* @author Oliver Gierke
|
||||
* @author Christoph Strobl
|
||||
* @author Thomas Darimont
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class PartTreeMongoQuery extends AbstractMongoQuery {
|
||||
|
||||
@@ -141,6 +142,15 @@ public class PartTreeMongoQuery extends AbstractMongoQuery {
|
||||
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()
|
||||
|
||||
@@ -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 @@ import com.mongodb.util.JSON;
|
||||
* @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<ParameterBinding> queryParameterBindings;
|
||||
private final List<ParameterBinding> fieldSpecParameterBindings;
|
||||
@@ -95,14 +97,27 @@ public class StringBasedMongoQuery extends AbstractMongoQuery {
|
||||
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;
|
||||
|
||||
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 @@ public class StringBasedMongoQuery extends AbstractMongoQuery {
|
||||
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 @@ public class StringBasedMongoQuery extends AbstractMongoQuery {
|
||||
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 @@ public class StringBasedMongoQuery extends AbstractMongoQuery {
|
||||
|
||||
} else if (value instanceof Pattern) {
|
||||
|
||||
String string = ((Pattern) value).toString().trim();
|
||||
String string = value.toString().trim();
|
||||
Matcher valueMatcher = PARSEABLE_BINDING_PATTERN.matcher(string);
|
||||
|
||||
while (valueMatcher.find()) {
|
||||
|
||||
@@ -30,13 +30,22 @@ import org.springframework.util.Assert;
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class MongoRepositoryFactoryBean<T extends Repository<S, ID>, S, ID extends Serializable> extends
|
||||
RepositoryFactoryBeanSupport<T, S, ID> {
|
||||
public class MongoRepositoryFactoryBean<T extends Repository<S, ID>, S, ID extends Serializable>
|
||||
extends RepositoryFactoryBeanSupport<T, S, ID> {
|
||||
|
||||
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<? extends T> repositoryInterface) {
|
||||
super(repositoryInterface);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the {@link MongoOperations} to be used.
|
||||
*
|
||||
|
||||
@@ -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
|
||||
@@ -18,6 +18,8 @@ package org.springframework.data.mongodb.core;
|
||||
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 abstract class DBObjectTestUtils {
|
||||
return getTypedValue(source, key, BasicDBList.class);
|
||||
}
|
||||
|
||||
public static List<Object> 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.
|
||||
*
|
||||
|
||||
@@ -46,6 +46,7 @@ import com.mongodb.WriteConcern;
|
||||
*
|
||||
* @author Tobias Trelle
|
||||
* @author Oliver Gierke
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration("classpath:infrastructure.xml")
|
||||
@@ -270,6 +271,25 @@ public class DefaultBulkOperationsIntegrationTests {
|
||||
assertThat(result.getRemovedCount(), is(1));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1534
|
||||
*/
|
||||
@Test
|
||||
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);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2015 the original author or authors.
|
||||
* Copyright 2014-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,18 +17,28 @@ package org.springframework.data.mongodb.core;
|
||||
|
||||
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 @@ import com.mongodb.DBObject;
|
||||
@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,6 +73,14 @@ public class DefaultIndexOperationsIntegrationTests {
|
||||
this.indexOps = new DefaultIndexOperations(template, collectionName);
|
||||
}
|
||||
|
||||
private void queryMongoVersionIfNecessary() {
|
||||
|
||||
if (mongoVersion == null) {
|
||||
CommandResult result = template.executeCommand("{ buildInfo: 1 }");
|
||||
mongoVersion = Version.parse(result.get("version").toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1008
|
||||
*/
|
||||
@@ -71,6 +93,78 @@ public class DefaultIndexOperationsIntegrationTests {
|
||||
assertThat(info.getIndexFields().get(0).isGeo(), is(true));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1467
|
||||
*/
|
||||
@Test
|
||||
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}}")));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1467
|
||||
*/
|
||||
@Test
|
||||
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}}")));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1467
|
||||
*/
|
||||
@Test
|
||||
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}}")));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1467
|
||||
*/
|
||||
@Test
|
||||
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 +183,15 @@ public class DefaultIndexOperationsIntegrationTests {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,11 +24,14 @@ 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.*;
|
||||
|
||||
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.CollectionCallback;
|
||||
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.data.util.Version;
|
||||
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;
|
||||
@@ -92,6 +99,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,6 +143,10 @@ public class AggregationTests {
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -519,7 +531,7 @@ public class AggregationTests {
|
||||
TypedAggregation<InventoryItem> 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)));
|
||||
|
||||
@@ -567,7 +579,7 @@ public class AggregationTests {
|
||||
|
||||
TypedAggregation<InventoryItem> aggregation = newAggregation(InventoryItem.class, //
|
||||
project("item") //
|
||||
.and(ifNull("description", "Unspecified")) //
|
||||
.and(ConditionalOperators.ifNull("description").then("Unspecified")) //
|
||||
.as("description")//
|
||||
);
|
||||
|
||||
@@ -594,7 +606,7 @@ public class AggregationTests {
|
||||
TypedAggregation<ZipInfo> 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"));
|
||||
@@ -619,9 +631,9 @@ public class AggregationTests {
|
||||
TypedAggregation<ZipInfo> 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"));
|
||||
|
||||
@@ -648,7 +660,7 @@ public class AggregationTests {
|
||||
TypedAggregation<LineItem> 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()));
|
||||
@@ -675,7 +687,7 @@ public class AggregationTests {
|
||||
TypedAggregation<LineItem> 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()));
|
||||
@@ -711,12 +723,16 @@ public class AggregationTests {
|
||||
TypedAggregation<CarPerson> 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<DBObject> result = mongoTemplate.aggregate(agg, DBObject.class);
|
||||
@@ -1042,6 +1058,32 @@ public class AggregationTests {
|
||||
assertThat((Integer) dbo.get("millisecond"), is(789));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1550
|
||||
*/
|
||||
@Test
|
||||
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<Data> agg = newAggregation(Data.class, project("item"), //
|
||||
replaceRoot("item"), //
|
||||
project().and("primitiveIntValue").as("my_primitiveIntValue"));
|
||||
|
||||
AggregationResults<DBObject> 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));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-788
|
||||
*/
|
||||
@@ -1284,6 +1326,30 @@ public class AggregationTests {
|
||||
assertThat(result.getMappedResults(), hasSize(2));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1549
|
||||
*/
|
||||
@Test
|
||||
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<DBObject> 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));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-975
|
||||
*/
|
||||
@@ -1500,6 +1566,231 @@ public class AggregationTests {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1491
|
||||
*/
|
||||
@Test
|
||||
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<Sales> 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.<Item> emptyList()).build()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1538
|
||||
*/
|
||||
@Test
|
||||
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<Sales2> 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<DBObject> 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()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1551
|
||||
*/
|
||||
@Test
|
||||
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<Employee> 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<DBObject> 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));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1552
|
||||
*/
|
||||
@Test
|
||||
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<Art> 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<DBObject> 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<String>) bound100.get("titles"),
|
||||
hasItems("The Pillars of Society", "The Great Wave off Kanagawa"));
|
||||
assertThat((Double) bound100.get("sum"), is(closeTo(3672.9, 0.1)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1552
|
||||
*/
|
||||
@Test
|
||||
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<Art> 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<DBObject> 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<String>) bound1.get("titles"), hasItems("The Great Wave off Kanagawa"));
|
||||
assertThat((Double) bound1.get("sum"), is(closeTo(1673.0, 0.1)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1552
|
||||
*/
|
||||
@Test
|
||||
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<Art> aggregation = newAggregation(Art.class, //
|
||||
project("title", "artist", "year", "price"), //
|
||||
facet(bucketPrice).as("categorizeByPrice") //
|
||||
.and(bucketAuto("year", 3)).as("categorizeByYear"));
|
||||
|
||||
AggregationResults<DBObject> 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);
|
||||
@@ -1740,4 +2031,67 @@ public class AggregationTests {
|
||||
this.qty = qty;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1491
|
||||
*/
|
||||
@lombok.Data
|
||||
@Builder
|
||||
static class Sales {
|
||||
|
||||
@Id String id;
|
||||
List<Item> items;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1491
|
||||
*/
|
||||
@lombok.Data
|
||||
@Builder
|
||||
static class Item {
|
||||
|
||||
@org.springframework.data.mongodb.core.mapping.Field("item_id") //
|
||||
String itemId;
|
||||
Integer quantity;
|
||||
Long price;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1538
|
||||
*/
|
||||
@lombok.Data
|
||||
@Builder
|
||||
static class Sales2 {
|
||||
|
||||
String id;
|
||||
Integer price;
|
||||
Float tax;
|
||||
boolean applyDiscount;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1551
|
||||
*/
|
||||
@lombok.Data
|
||||
@Builder
|
||||
static class Employee {
|
||||
|
||||
int id;
|
||||
String name;
|
||||
String reportsTo;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1552
|
||||
*/
|
||||
@lombok.Data
|
||||
@Builder
|
||||
static class Art {
|
||||
|
||||
int id;
|
||||
String title;
|
||||
String artist;
|
||||
Integer year;
|
||||
double price;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ 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.core.query.Criteria.*;
|
||||
import static org.springframework.data.mongodb.test.util.IsBsonObject.*;
|
||||
|
||||
@@ -36,6 +35,7 @@ import org.springframework.data.mongodb.core.query.Criteria;
|
||||
import com.mongodb.BasicDBObject;
|
||||
import com.mongodb.BasicDBObjectBuilder;
|
||||
import com.mongodb.DBObject;
|
||||
import org.springframework.data.mongodb.test.util.BasicDbListBuilder;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link Aggregation}.
|
||||
@@ -157,6 +157,21 @@ public class AggregationUnitTests {
|
||||
isBsonObject().notContaining("includeArrayIndex").containing("preserveNullAndEmptyArrays", true));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1550
|
||||
*/
|
||||
@Test
|
||||
public void replaceRootOperationShouldBuildCorrectClause() {
|
||||
|
||||
DBObject agg = newAggregation( //
|
||||
replaceRoot().withDocument().andValue("value").as("field")) //
|
||||
.toDbObject("foo", Aggregation.DEFAULT_CONTEXT);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
DBObject unwind = ((List<DBObject>) agg.get("pipeline")).get(0);
|
||||
assertThat(unwind, isBsonObject().containing("$replaceRoot.newRoot", new BasicDBObject("field", "value")));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-753
|
||||
*/
|
||||
@@ -391,7 +406,9 @@ public class AggregationUnitTests {
|
||||
|
||||
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")
|
||||
@@ -409,7 +426,7 @@ public class AggregationUnitTests {
|
||||
public void shouldRenderProjectionConditionalExpressionCorrectly() {
|
||||
|
||||
DBObject agg = Aggregation.newAggregation(//
|
||||
project().and(ConditionalOperator.newBuilder() //
|
||||
project().and(ConditionalOperators.Cond.newBuilder() //
|
||||
.when("isYellow") //
|
||||
.then("bright") //
|
||||
.otherwise("dark")).as("color"))
|
||||
@@ -432,7 +449,7 @@ public class AggregationUnitTests {
|
||||
|
||||
DBObject agg = Aggregation.newAggregation(//
|
||||
project().and("color")
|
||||
.applyCondition(ConditionalOperator.newBuilder() //
|
||||
.applyCondition(ConditionalOperators.Cond.newBuilder() //
|
||||
.when("isYellow") //
|
||||
.then("bright") //
|
||||
.otherwise("dark")))
|
||||
@@ -456,7 +473,8 @@ public class AggregationUnitTests {
|
||||
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");
|
||||
@@ -478,7 +496,10 @@ public class AggregationUnitTests {
|
||||
.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");
|
||||
@@ -500,7 +521,10 @@ public class AggregationUnitTests {
|
||||
.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");
|
||||
@@ -522,7 +546,9 @@ public class AggregationUnitTests {
|
||||
.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");
|
||||
@@ -541,7 +567,8 @@ public class AggregationUnitTests {
|
||||
.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 +577,44 @@ public class AggregationUnitTests {
|
||||
isBsonObject().containing("$ifNull", Arrays.asList("$chroma", "$fallback")));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1552
|
||||
*/
|
||||
@Test
|
||||
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));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1533
|
||||
*/
|
||||
@Test
|
||||
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<DBObject>) 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<DBObject> pipeline = (List<DBObject>) agg.get("pipeline");
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
* 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.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 {
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1552
|
||||
*/
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void rejectsNullFields() {
|
||||
new BucketAutoOperation((Field) null, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1552
|
||||
*/
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void rejectsNonPositiveIntegerNullFields() {
|
||||
new BucketAutoOperation(Fields.field("field"), 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1552
|
||||
*/
|
||||
@Test
|
||||
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\" } }}")));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1552
|
||||
*/
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void shouldRenderEmptyAggregationExpression() {
|
||||
bucket("groupby").andOutput("field").as("alias");
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1552
|
||||
*/
|
||||
@Test
|
||||
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 } }")));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1552
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderCorrectly() {
|
||||
|
||||
DBObject agg = bucketAuto("field", 1).withBuckets(5).toDBObject(Aggregation.DEFAULT_CONTEXT);
|
||||
|
||||
assertThat(agg, is(JSON.parse("{ $bucketAuto: { groupBy: \"$field\", buckets: 5 } }")));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1552
|
||||
*/
|
||||
@Test
|
||||
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\" } }")));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1552
|
||||
*/
|
||||
@Test
|
||||
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\" } }")));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1552
|
||||
*/
|
||||
@Test
|
||||
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");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,267 @@
|
||||
/*
|
||||
* 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.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 {
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1552
|
||||
*/
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void rejectsNullFields() {
|
||||
new BucketOperation((Field) null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1552
|
||||
*/
|
||||
@Test
|
||||
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\" } }}")));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1552
|
||||
*/
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void shouldRenderEmptyAggregationExpression() {
|
||||
bucket("groupby").andOutput("field").as("alias");
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1552
|
||||
*/
|
||||
@Test
|
||||
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 } }")));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1552
|
||||
*/
|
||||
@Test
|
||||
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\"} } } }")));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1552
|
||||
*/
|
||||
@Test
|
||||
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\" } }")));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1552
|
||||
*/
|
||||
@Test
|
||||
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\" } }")));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1552
|
||||
*/
|
||||
@Test
|
||||
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\" } }")));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1552
|
||||
*/
|
||||
@Test
|
||||
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 } }")));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1552
|
||||
*/
|
||||
@Test
|
||||
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\" } }")));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1552
|
||||
*/
|
||||
@Test
|
||||
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\" } }")));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1552
|
||||
*/
|
||||
@Test
|
||||
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\" } }")));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1552
|
||||
*/
|
||||
@Test
|
||||
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\" } }")));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1552
|
||||
*/
|
||||
@Test
|
||||
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\" } }")));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1552
|
||||
*/
|
||||
@Test
|
||||
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\" } }")));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1552
|
||||
*/
|
||||
@Test
|
||||
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\"]} } }")));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1552
|
||||
*/
|
||||
@Test
|
||||
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] } }")));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1552
|
||||
*/
|
||||
@Test
|
||||
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");
|
||||
}
|
||||
}
|
||||
@@ -16,48 +16,25 @@
|
||||
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, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-861
|
||||
*/
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void shouldRejectOtherwiseValue() {
|
||||
new ConditionalOperator(Fields.field("field"), "", null);
|
||||
}
|
||||
public class CondExpressionUnitTests {
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-861
|
||||
@@ -92,12 +69,12 @@ public class ConditionalOperatorUnitTests {
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-861
|
||||
* @see DATAMONGO-861, DATAMONGO-1542
|
||||
*/
|
||||
@Test
|
||||
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() //
|
||||
@@ -109,12 +86,12 @@ public class ConditionalOperatorUnitTests {
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-861
|
||||
* @see DATAMONGO-861, DATAMONGO-1542
|
||||
*/
|
||||
@Test
|
||||
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);
|
||||
|
||||
@@ -132,11 +109,10 @@ public class ConditionalOperatorUnitTests {
|
||||
@Test
|
||||
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 +123,20 @@ public class ConditionalOperatorUnitTests {
|
||||
DBObject expectedCondition = new BasicDBObject() //
|
||||
.append("if", Arrays.<Object> 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
|
||||
* @see DATAMONGO-861, DATAMONGO-1542
|
||||
*/
|
||||
@Test
|
||||
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);
|
||||
|
||||
@@ -176,14 +152,13 @@ public class ConditionalOperatorUnitTests {
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-861
|
||||
* @see DATAMONGO-861, DATAMONGO-1542
|
||||
*/
|
||||
@Test
|
||||
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")) //
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* 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.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 {
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1549
|
||||
*/
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void rejectsEmptyFieldName() {
|
||||
new CountOperation("");
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1549
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderCorrectly() {
|
||||
|
||||
CountOperation countOperation = new CountOperation("field");
|
||||
assertThat(countOperation.toDBObject(Aggregation.DEFAULT_CONTEXT), is(JSON.parse("{$count : \"field\" }")));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1549
|
||||
*/
|
||||
@Test
|
||||
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());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* 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.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 {
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1552
|
||||
*/
|
||||
@Test
|
||||
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\" } } ] } }")));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1552
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderEmpty() throws Exception {
|
||||
|
||||
FacetOperation facetOperation = facet();
|
||||
|
||||
DBObject dbObject = facetOperation.toDBObject(Aggregation.DEFAULT_CONTEXT);
|
||||
|
||||
assertThat(dbObject, is(JSON.parse("{ $facet: { } }")));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1552
|
||||
*/
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
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\" } } ] } }")));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1552
|
||||
*/
|
||||
@Test
|
||||
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\" } } } } ] } }")));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
* 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)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1491
|
||||
*/
|
||||
@Test
|
||||
public void shouldConstructFilterExpressionCorrectly() {
|
||||
|
||||
TypedAggregation<Sales> 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<Object> 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));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1491
|
||||
*/
|
||||
@Test
|
||||
public void shouldConstructFilterExpressionCorrectlyWhenUsingFilterOnProjectionBuilder() {
|
||||
|
||||
TypedAggregation<Sales> 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<Object> 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));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1491
|
||||
*/
|
||||
@Test
|
||||
public void shouldConstructFilterExpressionCorrectlyWhenInputMapToArray() {
|
||||
|
||||
TypedAggregation<Sales> agg = Aggregation.newAggregation(Sales.class,
|
||||
Aggregation.project().and(filter(Arrays.<Object> 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<Object> 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<Object> items;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
/*
|
||||
* 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.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 {
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1551
|
||||
*/
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void rejectsNullFromCollection() {
|
||||
GraphLookupOperation.builder().from(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1551
|
||||
*/
|
||||
@Test
|
||||
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));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1551
|
||||
*/
|
||||
@Test
|
||||
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")));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1551
|
||||
*/
|
||||
@Test
|
||||
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\" } }")));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1551
|
||||
*/
|
||||
@Test
|
||||
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\" } }")));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1551
|
||||
*/
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void shouldRejectUnknownTypeInMixedArrayOfStartsWithCorrectly() {
|
||||
|
||||
GraphLookupOperation graphLookupOperation = GraphLookupOperation.builder() //
|
||||
.from("employees") //
|
||||
.startWith("reportsTo", new Person()) //
|
||||
.connectFrom("reportsTo") //
|
||||
.connectTo("name") //
|
||||
.as("reportingHierarchy");
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1551
|
||||
*/
|
||||
@Test
|
||||
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\" } }")));
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2015 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.
|
||||
@@ -33,6 +33,7 @@ import com.mongodb.DBObject;
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Thomas Darimont
|
||||
* @author Gustavo de Geus
|
||||
*/
|
||||
public class GroupOperationUnitTests {
|
||||
|
||||
@@ -204,6 +205,34 @@ public class GroupOperationUnitTests {
|
||||
assertThat(tagsCount.get("$first"), is((Object) new BasicDBObject("$size", Arrays.asList("$tags"))));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1327
|
||||
*/
|
||||
@Test
|
||||
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")));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1327
|
||||
*/
|
||||
@Test
|
||||
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");
|
||||
|
||||
@@ -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.<Object> 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.<Object> asList("$optional", "$never-null")));
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
* 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.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 {
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1550
|
||||
*/
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void rejectsNullField() {
|
||||
new ReplaceRootOperation((Field) null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1550
|
||||
*/
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void rejectsNullExpression() {
|
||||
new ReplaceRootOperation((AggregationExpression) null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1550
|
||||
*/
|
||||
@Test
|
||||
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\" } } }")));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1550
|
||||
*/
|
||||
@Test
|
||||
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]} } " + "} } }")));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1550
|
||||
*/
|
||||
@Test
|
||||
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]} } } }")));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1550
|
||||
*/
|
||||
@Test
|
||||
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\"} } } }")));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1550
|
||||
*/
|
||||
@Test
|
||||
public void shouldNotExposeFields() {
|
||||
|
||||
ReplaceRootOperation operation = new ReplaceRootOperation(Fields.field("field"));
|
||||
|
||||
assertThat(operation.getFields().exposesNoFields(), is(true));
|
||||
assertThat(operation.getFields().exposesSingleFieldOnly(), is(false));
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
@@ -29,6 +29,7 @@ import org.springframework.data.mongodb.core.Person;
|
||||
* @see DATAMONGO-774
|
||||
* @author Thomas Darimont
|
||||
* @author Oliver Gierke
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public class SpelExpressionTransformerUnitTests {
|
||||
|
||||
@@ -69,7 +70,7 @@ public class SpelExpressionTransformerUnitTests {
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void shouldThrowExceptionOnUnknownOperand() {
|
||||
transform("a ^ 1");
|
||||
transform("a++");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -80,17 +81,15 @@ public class SpelExpressionTransformerUnitTests {
|
||||
@Test
|
||||
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
|
||||
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
|
||||
@@ -136,9 +135,8 @@ public class SpelExpressionTransformerUnitTests {
|
||||
@Test
|
||||
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
|
||||
@@ -195,15 +193,818 @@ public class SpelExpressionTransformerUnitTests {
|
||||
assertThat(transform("a.b + a.c"), is("{ \"$add\" : [ \"$a.b\" , \"$a.c\"]}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1530
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderStringFunctions() {
|
||||
public void shouldRenderMethodReferenceNodeAnd() {
|
||||
assertThat(transform("and(a, b)"), is("{ \"$and\" : [ \"$a\" , \"$b\"]}"));
|
||||
}
|
||||
|
||||
assertThat(transform("concat(a, b)"), is("{ \"$concat\" : [ \"$a\" , \"$b\"]}"));
|
||||
assertThat(transform("substr(a, 1, 2)"), is("{ \"$substr\" : [ \"$a\" , 1 , 2]}"));
|
||||
/**
|
||||
* @see DATAMONGO-1530
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderMethodReferenceNodeOr() {
|
||||
assertThat(transform("or(a, b)"), is("{ \"$or\" : [ \"$a\" , \"$b\"]}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1530
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderMethodReferenceNodeNot() {
|
||||
assertThat(transform("not(a)"), is("{ \"$not\" : [ \"$a\"]}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1530
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderMethodReferenceNodeSetEquals() {
|
||||
assertThat(transform("setEquals(a, b)"), is("{ \"$setEquals\" : [ \"$a\" , \"$b\"]}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1530
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderMethodReferenceNodeSetEqualsForArrays() {
|
||||
assertThat(transform("setEquals(new int[]{1,2,3}, new int[]{4,5,6})"),
|
||||
is("{ \"$setEquals\" : [ [ 1 , 2 , 3] , [ 4 , 5 , 6]]}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1530
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderMethodReferenceNodeSetEqualsMixedArrays() {
|
||||
assertThat(transform("setEquals(a, new int[]{4,5,6})"), is("{ \"$setEquals\" : [ \"$a\" , [ 4 , 5 , 6]]}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1530
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderMethodReferenceSetIntersection() {
|
||||
assertThat(transform("setIntersection(a, new int[]{4,5,6})"),
|
||||
is("{ \"$setIntersection\" : [ \"$a\" , [ 4 , 5 , 6]]}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1530
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderMethodReferenceSetUnion() {
|
||||
assertThat(transform("setUnion(a, new int[]{4,5,6})"), is("{ \"$setUnion\" : [ \"$a\" , [ 4 , 5 , 6]]}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1530
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderMethodReferenceSeDifference() {
|
||||
assertThat(transform("setDifference(a, new int[]{4,5,6})"), is("{ \"$setDifference\" : [ \"$a\" , [ 4 , 5 , 6]]}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1530
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderMethodReferenceSetIsSubset() {
|
||||
assertThat(transform("setIsSubset(a, new int[]{4,5,6})"), is("{ \"$setIsSubset\" : [ \"$a\" , [ 4 , 5 , 6]]}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1530
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderMethodReferenceAnyElementTrue() {
|
||||
assertThat(transform("anyElementTrue(a)"), is("{ \"$anyElementTrue\" : [ \"$a\"]}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1530
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderMethodReferenceAllElementsTrue() {
|
||||
assertThat(transform("allElementsTrue(a, new int[]{4,5,6})"),
|
||||
is("{ \"$allElementsTrue\" : [ \"$a\" , [ 4 , 5 , 6]]}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1530
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderMethodReferenceCmp() {
|
||||
assertThat(transform("cmp(a, 250)"), is("{ \"$cmp\" : [ \"$a\" , 250]}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1530
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderMethodReferenceEq() {
|
||||
assertThat(transform("eq(a, 250)"), is("{ \"$eq\" : [ \"$a\" , 250]}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1530
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderMethodReferenceGt() {
|
||||
assertThat(transform("gt(a, 250)"), is("{ \"$gt\" : [ \"$a\" , 250]}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1530
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderMethodReferenceGte() {
|
||||
assertThat(transform("gte(a, 250)"), is("{ \"$gte\" : [ \"$a\" , 250]}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1530
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderMethodReferenceLt() {
|
||||
assertThat(transform("lt(a, 250)"), is("{ \"$lt\" : [ \"$a\" , 250]}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1530
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderMethodReferenceLte() {
|
||||
assertThat(transform("lte(a, 250)"), is("{ \"$lte\" : [ \"$a\" , 250]}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1530
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderMethodReferenceNe() {
|
||||
assertThat(transform("ne(a, 250)"), is("{ \"$ne\" : [ \"$a\" , 250]}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1530
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderMethodReferenceAbs() {
|
||||
assertThat(transform("abs(1)"), is("{ \"$abs\" : 1}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1530
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderMethodReferenceAdd() {
|
||||
assertThat(transform("add(a, 250)"), is("{ \"$add\" : [ \"$a\" , 250]}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1530
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderMethodReferenceCeil() {
|
||||
assertThat(transform("ceil(7.8)"), is("{ \"$ceil\" : 7.8}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1530
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderMethodReferenceDivide() {
|
||||
assertThat(transform("divide(a, 250)"), is("{ \"$divide\" : [ \"$a\" , 250]}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1530
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderMethodReferenceExp() {
|
||||
assertThat(transform("exp(2)"), is("{ \"$exp\" : 2}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1530
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderMethodReferenceFloor() {
|
||||
assertThat(transform("floor(2)"), is("{ \"$floor\" : 2}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1530
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderMethodReferenceLn() {
|
||||
assertThat(transform("ln(2)"), is("{ \"$ln\" : 2}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1530
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderMethodReferenceLog() {
|
||||
assertThat(transform("log(100, 10)"), is("{ \"$log\" : [ 100 , 10]}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1530
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderMethodReferenceLog10() {
|
||||
assertThat(transform("log10(100)"), is("{ \"$log10\" : 100}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1530
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderMethodReferenceNodeMod() {
|
||||
assertThat(transform("mod(a, b)"), is("{ \"$mod\" : [ \"$a\" , \"$b\"]}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1530
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderMethodReferenceNodeMultiply() {
|
||||
assertThat(transform("multiply(a, b)"), is("{ \"$multiply\" : [ \"$a\" , \"$b\"]}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1530
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderMethodReferenceNodePow() {
|
||||
assertThat(transform("pow(a, 2)"), is("{ \"$pow\" : [ \"$a\" , 2]}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1530
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderMethodReferenceSqrt() {
|
||||
assertThat(transform("sqrt(2)"), is("{ \"$sqrt\" : 2}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1530
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderMethodReferenceNodeSubtract() {
|
||||
assertThat(transform("subtract(a, b)"), is("{ \"$subtract\" : [ \"$a\" , \"$b\"]}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1530
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderMethodReferenceTrunc() {
|
||||
assertThat(transform("trunc(2.1)"), is("{ \"$trunc\" : 2.1}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1530
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderMethodReferenceNodeConcat() {
|
||||
assertThat(transform("concat(a, b, 'c')"), is("{ \"$concat\" : [ \"$a\" , \"$b\" , \"c\"]}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1530
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderMethodReferenceNodeSubstrc() {
|
||||
assertThat(transform("substr(a, 0, 1)"), is("{ \"$substr\" : [ \"$a\" , 0 , 1]}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1530
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderMethodReferenceToLower() {
|
||||
assertThat(transform("toLower(a)"), is("{ \"$toLower\" : \"$a\"}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1530
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderMethodReferenceToUpper() {
|
||||
assertThat(transform("toUpper(a)"), is("{ \"$toUpper\" : \"$a\"}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1530
|
||||
*/
|
||||
@Test
|
||||
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\"]}]}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1530
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderMethodReferenceMeta() {
|
||||
assertThat(transform("meta('textScore')"), is("{ \"$meta\" : \"textScore\"}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1530
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderMethodReferenceNodeArrayElemAt() {
|
||||
assertThat(transform("arrayElemAt(a, 10)"), is("{ \"$arrayElemAt\" : [ \"$a\" , 10]}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1530
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderMethodReferenceNodeConcatArrays() {
|
||||
assertThat(transform("concatArrays(a, b, c)"), is("{ \"$concatArrays\" : [ \"$a\" , \"$b\" , \"$c\"]}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1530
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderMethodReferenceNodeFilter() {
|
||||
assertThat(transform("filter(a, 'num', '$$num' > 10)"),
|
||||
is("{ \"$filter\" : { \"input\" : \"$a\" , \"as\" : \"num\" , \"cond\" : { \"$gt\" : [ \"$$num\" , 10]}}}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1530
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderMethodReferenceIsArray() {
|
||||
assertThat(transform("isArray(a)"), is("{ \"$isArray\" : \"$a\"}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1530
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderMethodReferenceIsSize() {
|
||||
assertThat(transform("size(a)"), is("{ \"$size\" : \"$a\"}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1530
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderMethodReferenceNodeSlice() {
|
||||
assertThat(transform("slice(a, 10)"), is("{ \"$slice\" : [ \"$a\" , 10]}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1530
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderMethodReferenceNodeMap() {
|
||||
assertThat(transform("map(quizzes, 'grade', '$$grade' + 2)"), is(
|
||||
"{ \"$map\" : { \"input\" : \"$quizzes\" , \"as\" : \"grade\" , \"in\" : { \"$add\" : [ \"$$grade\" , 2]}}}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1530
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderMethodReferenceNodeLet() {
|
||||
assertThat(transform("let({low:1, high:'$$low'}, gt('$$low', '$$high'))"), is(
|
||||
"{ \"$let\" : { \"vars\" : { \"low\" : 1 , \"high\" : \"$$low\"} , \"in\" : { \"$gt\" : [ \"$$low\" , \"$$high\"]}}}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1530
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderMethodReferenceLiteral() {
|
||||
assertThat(transform("literal($1)"), is("{ \"$literal\" : \"$1\"}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1530
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderMethodReferenceDayOfYear() {
|
||||
assertThat(transform("dayOfYear($1)"), is("{ \"$dayOfYear\" : \"$1\"}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1530
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderMethodReferenceDayOfMonth() {
|
||||
assertThat(transform("dayOfMonth($1)"), is("{ \"$dayOfMonth\" : \"$1\"}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1530
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderMethodReferenceDayOfWeek() {
|
||||
assertThat(transform("dayOfWeek($1)"), is("{ \"$dayOfWeek\" : \"$1\"}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1530
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderMethodReferenceYear() {
|
||||
assertThat(transform("year($1)"), is("{ \"$year\" : \"$1\"}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1530
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderMethodReferenceMonth() {
|
||||
assertThat(transform("month($1)"), is("{ \"$month\" : \"$1\"}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1530
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderMethodReferenceWeek() {
|
||||
assertThat(transform("week($1)"), is("{ \"$week\" : \"$1\"}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1530
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderMethodReferenceHour() {
|
||||
assertThat(transform("hour($1)"), is("{ \"$hour\" : \"$1\"}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1530
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderMethodReferenceMinute() {
|
||||
assertThat(transform("minute($1)"), is("{ \"$minute\" : \"$1\"}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1530
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderMethodReferenceSecond() {
|
||||
assertThat(transform("second($1)"), is("{ \"$second\" : \"$1\"}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1530
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderMethodReferenceMillisecond() {
|
||||
assertThat(transform("millisecond($1)"), is("{ \"$millisecond\" : \"$1\"}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1530
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderMethodReferenceDateToString() {
|
||||
assertThat(transform("dateToString('%Y-%m-%d', $date)"),
|
||||
is("{ \"$dateToString\" : { \"format\" : \"%Y-%m-%d\" , \"date\" : \"$date\"}}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1530
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderMethodReferenceCond() {
|
||||
assertThat(transform("cond(qty > 250, 30, 20)"),
|
||||
is("{ \"$cond\" : { \"if\" : { \"$gt\" : [ \"$qty\" , 250]} , \"then\" : 30 , \"else\" : 20}}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1530
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderMethodReferenceNodeIfNull() {
|
||||
assertThat(transform("ifNull(a, 10)"), is("{ \"$ifNull\" : [ \"$a\" , 10]}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1530
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderMethodReferenceNodeSum() {
|
||||
assertThat(transform("sum(a, b)"), is("{ \"$sum\" : [ \"$a\" , \"$b\"]}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1530
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderMethodReferenceNodeAvg() {
|
||||
assertThat(transform("avg(a, b)"), is("{ \"$avg\" : [ \"$a\" , \"$b\"]}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1530
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderMethodReferenceFirst() {
|
||||
assertThat(transform("first($1)"), is("{ \"$first\" : \"$1\"}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1530
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderMethodReferenceLast() {
|
||||
assertThat(transform("last($1)"), is("{ \"$last\" : \"$1\"}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1530
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderMethodReferenceNodeMax() {
|
||||
assertThat(transform("max(a, b)"), is("{ \"$max\" : [ \"$a\" , \"$b\"]}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1530
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderMethodReferenceNodeMin() {
|
||||
assertThat(transform("min(a, b)"), is("{ \"$min\" : [ \"$a\" , \"$b\"]}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1530
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderMethodReferenceNodePush() {
|
||||
assertThat(transform("push({'item':'$item', 'quantity':'$qty'})"),
|
||||
is("{ \"$push\" : { \"item\" : \"$item\" , \"quantity\" : \"$qty\"}}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1530
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderMethodReferenceAddToSet() {
|
||||
assertThat(transform("addToSet($1)"), is("{ \"$addToSet\" : \"$1\"}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1530
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderMethodReferenceNodeStdDevPop() {
|
||||
assertThat(transform("stdDevPop(scores.score)"), is("{ \"$stdDevPop\" : [ \"$scores.score\"]}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1530
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderMethodReferenceNodeStdDevSamp() {
|
||||
assertThat(transform("stdDevSamp(age)"), is("{ \"$stdDevSamp\" : [ \"$age\"]}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1530
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderOperationNodeEq() {
|
||||
assertThat(transform("foo == 10"), is("{ \"$eq\" : [ \"$foo\" , 10]}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1530
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderOperationNodeNe() {
|
||||
assertThat(transform("foo != 10"), is("{ \"$ne\" : [ \"$foo\" , 10]}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1530
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderOperationNodeGt() {
|
||||
assertThat(transform("foo > 10"), is("{ \"$gt\" : [ \"$foo\" , 10]}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1530
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderOperationNodeGte() {
|
||||
assertThat(transform("foo >= 10"), is("{ \"$gte\" : [ \"$foo\" , 10]}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1530
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderOperationNodeLt() {
|
||||
assertThat(transform("foo < 10"), is("{ \"$lt\" : [ \"$foo\" , 10]}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1530
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderOperationNodeLte() {
|
||||
assertThat(transform("foo <= 10"), is("{ \"$lte\" : [ \"$foo\" , 10]}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1530
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderOperationNodePow() {
|
||||
assertThat(transform("foo^2"), is("{ \"$pow\" : [ \"$foo\" , 2]}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1530
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderOperationNodeOr() {
|
||||
assertThat(transform("true || false"), is("{ \"$or\" : [ true , false]}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1530
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderComplexOperationNodeOr() {
|
||||
assertThat(transform("1+2 || concat(a, b) || true"),
|
||||
is("{ \"$or\" : [ { \"$add\" : [ 1 , 2]} , { \"$concat\" : [ \"$a\" , \"$b\"]} , true]}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1530
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderOperationNodeAnd() {
|
||||
assertThat(transform("true && false"), is("{ \"$and\" : [ true , false]}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1530
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderComplexOperationNodeAnd() {
|
||||
assertThat(transform("1+2 && concat(a, b) && true"),
|
||||
is("{ \"$and\" : [ { \"$add\" : [ 1 , 2]} , { \"$concat\" : [ \"$a\" , \"$b\"]} , true]}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1530
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderNotCorrectly() {
|
||||
assertThat(transform("!true"), is("{ \"$not\" : [ true]}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1530
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderComplexNotCorrectly() {
|
||||
assertThat(transform("!(foo > 10)"), is("{ \"$not\" : [ { \"$gt\" : [ \"$foo\" , 10]}]}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1548
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderMethodReferenceIndexOfBytes() {
|
||||
assertThat(transform("indexOfBytes(item, 'foo')"), is("{ \"$indexOfBytes\" : [ \"$item\" , \"foo\"]}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1548
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderMethodReferenceIndexOfCP() {
|
||||
assertThat(transform("indexOfCP(item, 'foo')"), is("{ \"$indexOfCP\" : [ \"$item\" , \"foo\"]}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1548
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderMethodReferenceSplit() {
|
||||
assertThat(transform("split(item, ',')"), is("{ \"$split\" : [ \"$item\" , \",\"]}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1548
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderMethodReferenceStrLenBytes() {
|
||||
assertThat(transform("strLenBytes(item)"), is("{ \"$strLenBytes\" : \"$item\"}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1548
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderMethodReferenceStrLenCP() {
|
||||
assertThat(transform("strLenCP(item)"), is("{ \"$strLenCP\" : \"$item\"}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1548
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderMethodSubstrCP() {
|
||||
assertThat(transform("substrCP(item, 0, 5)"), is("{ \"$substrCP\" : [ \"$item\" , 0 , 5]}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1548
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderMethodReferenceReverseArray() {
|
||||
assertThat(transform("reverseArray(array)"), is("{ \"$reverseArray\" : \"$array\"}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1548
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderMethodReferenceReduce() {
|
||||
assertThat(transform("reduce(field, '', {'$concat':new String[]{'$$value','$$this'}})"), is(
|
||||
"{ \"$reduce\" : { \"input\" : \"$field\" , \"initialValue\" : \"\" , \"in\" : { \"$concat\" : [ \"$$value\" , \"$$this\"]}}}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1548
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderMethodReferenceZip() {
|
||||
assertThat(transform("zip(new String[]{'$array1', '$array2'})"),
|
||||
is("{ \"$zip\" : { \"inputs\" : [ \"$array1\" , \"$array2\"]}}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1548
|
||||
*/
|
||||
@Test
|
||||
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]}}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1548
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderMethodIn() {
|
||||
assertThat(transform("in('item', array)"), is("{ \"$in\" : [ \"item\" , \"$array\"]}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1548
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderMethodRefereneIsoDayOfWeek() {
|
||||
assertThat(transform("isoDayOfWeek(date)"), is("{ \"$isoDayOfWeek\" : \"$date\"}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1548
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderMethodRefereneIsoWeek() {
|
||||
assertThat(transform("isoWeek(date)"), is("{ \"$isoWeek\" : \"$date\"}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1548
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderMethodRefereneIsoWeekYear() {
|
||||
assertThat(transform("isoWeekYear(date)"), is("{ \"$isoWeekYear\" : \"$date\"}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1548
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderMethodRefereneType() {
|
||||
assertThat(transform("type(a)"), is("{ \"$type\" : \"$a\"}"));
|
||||
}
|
||||
|
||||
private String transform(String expression, Object... params) {
|
||||
|
||||
@@ -36,6 +36,7 @@ import org.springframework.data.annotation.Id;
|
||||
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 +49,7 @@ 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 TypeBasedAggregationOperationContext}.
|
||||
@@ -105,7 +107,7 @@ public class TypeBasedAggregationOperationContextUnitTests {
|
||||
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))));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -291,7 +293,8 @@ public class TypeBasedAggregationOperationContextUnitTests {
|
||||
TypedAggregation<FooPerson> 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);
|
||||
@@ -308,7 +311,7 @@ public class TypeBasedAggregationOperationContextUnitTests {
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-861
|
||||
* @see DATAMONGO-861, DATAMONGO-1542
|
||||
*/
|
||||
@Test
|
||||
public void rendersAggregationIfNullInTypedAggregationContextCorrectly() {
|
||||
@@ -317,7 +320,7 @@ public class TypeBasedAggregationOperationContextUnitTests {
|
||||
TypedAggregation<FooPerson> 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 +331,9 @@ public class TypeBasedAggregationOperationContextUnitTests {
|
||||
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()));
|
||||
|
||||
@@ -2086,6 +2086,17 @@ public class MappingMongoConverterUnitTests {
|
||||
assertThat(result.sample, is("value"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1525
|
||||
*/
|
||||
@Test
|
||||
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> {
|
||||
T content;
|
||||
}
|
||||
|
||||
@@ -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.mockito.runners.MockitoJUnitRunner;
|
||||
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 @@ import com.mongodb.DBRef;
|
||||
* @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<NestedEntity, DBObject> writingConverterSpy;
|
||||
|
||||
@Before
|
||||
@SuppressWarnings("unchecked")
|
||||
public void setUp() {
|
||||
|
||||
this.writingConverterSpy = Mockito.spy(new NestedEntityWriteConverter());
|
||||
@@ -416,6 +420,72 @@ public class UpdateMapperUnitTests {
|
||||
assertThat(key2.containsField("$each"), is(true));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1141
|
||||
*/
|
||||
@Test
|
||||
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));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1141
|
||||
*/
|
||||
@Test
|
||||
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));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1141
|
||||
*/
|
||||
@Test
|
||||
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));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-410
|
||||
*/
|
||||
@@ -1211,9 +1281,14 @@ public class UpdateMapperUnitTests {
|
||||
NestedDocument concreteValue;
|
||||
}
|
||||
|
||||
static class EntityWithList {
|
||||
List<EntityWithAliasedObject> list;
|
||||
}
|
||||
|
||||
static class EntityWithAliasedObject {
|
||||
|
||||
@Field("renamed-value") Object value;
|
||||
Object field;
|
||||
}
|
||||
|
||||
static class EntityWithObjectMap {
|
||||
|
||||
@@ -97,9 +97,9 @@ public class PerformanceTests {
|
||||
this.converter = new MappingMongoConverter(new DefaultDbRefResolver(mongoDbFactory), context);
|
||||
this.operations = new MongoTemplate(new SimpleMongoDbFactory(this.mongo, DATABASE_NAME), converter);
|
||||
|
||||
MongoRepositoryFactoryBean<PersonRepository, Person, ObjectId> factory = new MongoRepositoryFactoryBean<PersonRepository, Person, ObjectId>();
|
||||
MongoRepositoryFactoryBean<PersonRepository, Person, ObjectId> factory = new MongoRepositoryFactoryBean<PersonRepository, Person, ObjectId>(
|
||||
PersonRepository.class);
|
||||
factory.setMongoOperations(operations);
|
||||
factory.setRepositoryInterface(PersonRepository.class);
|
||||
factory.afterPropertiesSet();
|
||||
|
||||
this.repository = factory.getObject();
|
||||
@@ -125,8 +125,8 @@ public class PerformanceTests {
|
||||
@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<DBObject> dbObjects = getPersonDBObjects(NUMBER_OF_PERSONS * 100);
|
||||
|
||||
@@ -842,8 +842,8 @@ public class PerformanceTests {
|
||||
*/
|
||||
@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';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -65,6 +65,7 @@ import org.springframework.test.util.ReflectionTestUtils;
|
||||
* @author Thomas Darimont
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
* @author Fırat KÜÇÜK
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
public abstract class AbstractPersonRepositoryIntegrationTests {
|
||||
@@ -588,6 +589,24 @@ public abstract class AbstractPersonRepositoryIntegrationTests {
|
||||
assertThat(repository.someCountQuery("Matthews"), is(2L));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1454
|
||||
*/
|
||||
@Test
|
||||
public void executesDerivedExistsProjectionToBoolean() {
|
||||
|
||||
assertThat(repository.existsByFirstname("Oliver August"), is(true));
|
||||
assertThat(repository.existsByFirstname("Hans Peter"), is(false));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1454
|
||||
*/
|
||||
@Test
|
||||
public void executesAnnotatedExistProjection() {
|
||||
assertThat(repository.someExistQuery("Matthews"), is(true));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-701
|
||||
*/
|
||||
@@ -1311,4 +1330,22 @@ public abstract class AbstractPersonRepositoryIntegrationTests {
|
||||
assertThat(result, not(hasItem(boyd)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1539
|
||||
*/
|
||||
@Test
|
||||
public void countsPersonsByFirstname() {
|
||||
assertThat(repository.countByThePersonsFirstname("Dave"), is(1L));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1539
|
||||
*/
|
||||
@Test
|
||||
public void deletesPersonsByFirstname() {
|
||||
|
||||
repository.deleteByThePersonsFirstname("Dave");
|
||||
|
||||
assertThat(repository.countByThePersonsFirstname("Dave"), is(0L));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +42,8 @@ import org.springframework.data.repository.query.Param;
|
||||
* @author Oliver Gierke
|
||||
* @author Thomas Darimont
|
||||
* @author Christoph Strobl
|
||||
* @author Fırat KÜÇÜK
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public interface PersonRepository extends MongoRepository<Person, String>, QueryDslPredicateExecutor<Person> {
|
||||
|
||||
@@ -250,6 +252,17 @@ public interface PersonRepository extends MongoRepository<Person, String>, Query
|
||||
@Query(value = "{ 'lastname' : ?0 }", count = true)
|
||||
long someCountQuery(String lastname);
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1454
|
||||
*/
|
||||
boolean existsByFirstname(String firstname);
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1454
|
||||
*/
|
||||
@ExistsQuery(value = "{ 'lastname' : ?0 }")
|
||||
boolean someExistQuery(String lastname);
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-770
|
||||
*/
|
||||
@@ -367,4 +380,25 @@ public interface PersonRepository extends MongoRepository<Person, String>, Query
|
||||
*/
|
||||
@Query("{ firstname : :#{#firstname}}")
|
||||
List<Person> 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.
|
||||
*
|
||||
* @see DATAMONGO-1539
|
||||
* @param firstname
|
||||
* @return
|
||||
*/
|
||||
@CountQuery("{ 'firstname' : ?0 }")
|
||||
long countByThePersonsFirstname(String firstname);
|
||||
|
||||
/**
|
||||
* Deletes {@link Person} entities with the given firstname. Uses {@link DeleteQuery} annotation to define the query
|
||||
* to be executed.
|
||||
*
|
||||
* @see DATAMONGO-1539
|
||||
* @param firstname
|
||||
*/
|
||||
@DeleteQuery("{ 'firstname' : ?0 }")
|
||||
void deleteByThePersonsFirstname(String firstname);
|
||||
}
|
||||
|
||||
@@ -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 class MongoRepositoriesRegistrarIntegrationTests {
|
||||
|
||||
@Test
|
||||
public void testConfiguration() {}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-901
|
||||
*/
|
||||
@Test
|
||||
public void registersTypePredictingPostProcessor() {
|
||||
|
||||
Iterable<String> beanNames = Arrays.asList(context.getBeanDefinitionNames());
|
||||
|
||||
assertThat(beanNames, hasItem(containsString("RepositoryFactoryBeanSupport_Predictor")));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -312,6 +312,7 @@ public class AbstractMongoQueryUnitTests {
|
||||
private static class MongoQueryFake extends AbstractMongoQuery {
|
||||
|
||||
private boolean isCountQuery;
|
||||
private boolean isExistsQuery;
|
||||
private boolean isDeleteQuery;
|
||||
|
||||
public MongoQueryFake(MongoQueryMethod method, MongoOperations operations) {
|
||||
@@ -328,6 +329,11 @@ public class AbstractMongoQueryUnitTests {
|
||||
return isCountQuery;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isExistsQuery() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isDeleteQuery() {
|
||||
return isDeleteQuery;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user