Compare commits

...

59 Commits

Author SHA1 Message Date
Oliver Gierke
737f7b4f30 DATAMONGO-1469 - Release version 1.10 RC1 (Ingalls). 2016-12-21 16:16:51 +01:00
Oliver Gierke
408c5d8684 DATAMONGO-1469 - Prepare 1.10 RC1 (Ingalls). 2016-12-21 16:15:47 +01:00
Oliver Gierke
982adf317e DATAMONGO-1469 - Updated changelog. 2016-12-21 16:15:39 +01:00
Oliver Gierke
7914e8a630 DATAMONGO-1467 - Polishing.
Original pull request: #431.
2016-12-19 19:44:47 +01:00
Christoph Strobl
dc4a30a7f8 DATAMONGO-1467 - Add support for MongoDB 3.2 partialFilterExpression for index creation.
We now support partial filter expression on indexes via Index.partial(…). This allows to create partial indexes that only index the documents in a collection that meet a specified filter expression. 

new Index().named("idx").on("k3y", ASC).partial(filter(where("age").gte(10)))

The filter expression can be set via a plain DBObject or a CriteriaDefinition and is mapped against the associated domain type.

Original pull request: #431.
2016-12-19 19:44:47 +01:00
Oliver Gierke
29e405b800 DATAMONGO-1565 - Polishing.
Formatting in ExpressionEvaluatingParameterBinder and StringBasedMongoQueryUnitTests. Turned Placeholder into value object.
2016-12-19 14:37:26 +01:00
Mark Paluch
1a105333aa DATAMONGO-1565 - Polishing.
Consider quoted/unquoted parameter use with the same parameter reference. Extend date range in license headers.
2016-12-19 14:37:26 +01:00
Christoph Strobl
14e326dc09 DATAMONGO-1565 - Ignore placeholder pattern in replacement values for annotated queries.
We now make sure to quote single and double ticks in the replacement values before actually appending them to the query. We also replace single ticks around parameters in the actual raw annotated query by double quotes to make sure they are treated as a single string parameter.
2016-12-19 14:36:44 +01:00
Mark Paluch
f026ab419d DATAMONGO-1564 - Polishing.
Fix JavaDoc references and minor a import formatting issue.

Original pull request: #429.
2016-12-16 14:11:18 +01:00
Christoph Strobl
75139042e0 DATAMONGO-1564 - Split up AggregationExpressions.
Refactored to multiple smaller Aggregation Operator classes reflecting the grouping (array operators, string operators,…) predefined by MongoDB.

Original pull request: #429.
2016-12-16 14:11:18 +01:00
Mark Paluch
6236384c1d DATAMONGO-1567 - Use newer Java 8 on Travis CI. 2016-12-16 10:47:02 +01:00
Mark Paluch
6993054d6a DATAMONGO-1533 - Polishing.
Enhance JavaDoc. Minor reformatting.

Original pull request: #428.
2016-12-16 09:25:00 +01:00
Christoph Strobl
35bfb92ace DATAMONGO-1533 - Add AggregationExpression derived from SpEL AST.
We added an AggregationExpression that renders a MongoDB Aggregation Framework expression from the AST of a SpEL expression. This allows usage with various stages (eg. $project, $group) throughout the aggregation support.

  // { $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')");

Original pull request: #428.
2016-12-16 09:02:15 +01:00
Oliver Gierke
89a02bb822 DATAMONGO-1566 - Adapt API in MongoRepositoryFactoryBean.
Related tickets: DATACMNS-891.
2016-12-15 14:57:26 +01:00
Christoph Strobl
c9c5fe62ca DATAMONGO-1552 - Polishing.
Updated doc, removed whitespaces, minor method wording changes.

Original Pull Request: #426
2016-12-14 12:05:26 +01:00
Mark Paluch
d250f88c38 DATAMONGO-1552 - Update Documentation.
Original Pull Request: #426
2016-12-14 11:09:46 +01:00
Mark Paluch
bcb63b2732 DATAMONGO-1552 - Add $facet aggregation stage.
Original Pull Request: #426
2016-12-14 11:09:01 +01:00
Mark Paluch
2c4377c9a6 DATAMONGO-1552 - Add $bucketAuto aggregation stage.
Original Pull Request: #426
2016-12-14 11:08:26 +01:00
Mark Paluch
e992d813fb DATAMONGO-1552 - Add $bucket aggregation stage.
Original Pull Request: #426
2016-12-14 11:07:40 +01:00
Mark Paluch
aa1e91c761 DATAMONGO-442 - Polishing.
Reformat code according to Spring Data style. Add test for authenticated use. Add JavaDoc to newly introduced methods. Allow configuration of an authentication database. Update reference documentation.

Original pull request: #419.
2016-12-13 16:50:37 +01:00
Ricardo Espirito Santo
9737464f9a DATAMONGO-442 - Support username/password authentication with MongoLog4jAppender.
Added optional username and password for authentication support on Log4jAppender.

Original pull request: #419.
2016-12-13 15:56:56 +01:00
Christoph Strobl
204a0515c4 DATAMONGO-1551 - Polishing.
Add startWith overload allowing to mix expressions, removed white spaces, updated doc.

Original Pull Request: #424
2016-12-13 15:15:55 +01:00
Mark Paluch
c6dae3c444 DATAMONGO-1551 - Add $graphLookup aggregation stage.
We now support the $graphLookup aggregation pipeline stage via Aggregation to perform recursive lookup adding the lookup result as array to documents entering $graphLookup.

TypedAggregation<Employee> agg = Aggregation.newAggregation(Employee.class,
		graphLookup("employee")
			.startWith("reportsTo")
			.connectFrom("reportsTo")
			.connectTo("name")
			.depthField("depth")
			.maxDepth(5)
			.as("reportingHierarchy"));

Original Pull Request: #424
2016-12-13 14:23:39 +01:00
Christoph Strobl
c1c7daf0ed DATAMONGO-1550 - Polishing $replaceRoot (aggregation stage).
Original Pull Request: #422.
2016-12-13 08:22:20 +01:00
Mark Paluch
14678ce7a9 DATAMONGO-1550 - Add $replaceRoot aggregation stage.
We now support the $replaceRoot stage in aggregation pipelines. $replaceRoot can reference either a field, an aggregation expression or it can be used to compose a replacement document.

newAggregation(
	replaceRoot().withDocument()
		.andValue("value").as("field")
		.and(MULTIPLY.of(field("total"), field("discounted")))
);

newAggregation(
	replaceRoot("item")));

Original Pull Request: #422
2016-12-13 08:22:20 +01:00
Christoph Strobl
4e56d9c575 DATAMONGO-1549 - Polishing $count (aggregation stage).
Original Pull Request: #422
2016-12-13 08:22:10 +01:00
Mark Paluch
cab35759db DATAMONGO-1549 - Add $count aggregation stage.
We now support the $count stage in aggregation pipelines.

newAggregation(
	match(where("hotelCode").is("0360")),
	count().as("documents"));

Original Pull Request: #422
2016-12-13 07:58:58 +01:00
Christoph Strobl
7b49b120e3 DATAMONGO-1558 - Upgrade MongoDB server version to, and add build profile for MongoDB 3.4.
Added MongoDB 3.4 profile to pom.xml and upgraded to MongoDB 3.4 on travis-ci.
2016-12-13 07:58:37 +01:00
Mark Paluch
dc57b66adf DATAMONGO-1548 - Polishing.
Enhance JavaDoc. Minor formatting. Fix typos.

Original pull request: #423.
2016-12-12 12:05:32 +01:00
Christoph Strobl
0449719a16 DATAMONGO-1548 - Add support for MongoDB 3.4 aggregation operators.
We now support the following MongoDB 3.4 aggregation operators:

$indexOfBytes, $indexOfCP, $split, $strLenBytes, $strLenCP, $substrCP, $indexOfArray, $range, $reverseArray, $reduce, $zip, $in, $isoDayOfWeek, $isoWeek, $isoWeekYear, $switch and $type.

Original pull request: #423.
2016-12-12 12:05:09 +01:00
Mark Paluch
3d8b6868c7 DATAMONGO-1538 - Polishing.
Use InheritingExposedFieldsAggregationOperationContext instead of anonymous context class for condition mapping. Drop aggregation input collections before tests. Minor reformatting.

Original pull request: #417.
2016-12-07 09:35:08 +01:00
Christoph Strobl
68db0d4cb0 DATAMONGO-1538 - Add support for $let to aggregation.
We now support $let in aggregation $project stage.

ExpressionVariable total = newExpressionVariable("total").forExpression(ADD.of(field("price"), field("tax")));
ExpressionVariable discounted = newExpressionVariable("discounted").forExpression(Cond.when("applyDiscount").then(0.9D).otherwise(1.0D));

newAggregation(Sales.class,
	project()
		.and(define(total, discounted)
			.andApply(MULTIPLY.of(field("total"), field("discounted"))))
		.as("finalTotal"));

Original pull request: #417.
2016-12-07 09:33:58 +01:00
Christoph Strobl
c9dfeea0c7 DATAMONGO-1542 - Polishing.
Added some static entry points for better readability.

Original Pull Request: #421
2016-12-06 10:13:32 +01:00
Mark Paluch
1a11877ae9 DATAMONGO-1542 - Refactor CondOperator and IfNullOperator to children of AggregationExpressions.
Renamed CondOperator to Cond and IfNullOperator to IfNull. Both conditional operations are now available from ConditionalOperators.when and ConditionalOperators.ifNull and accept AggregationExpressions for conditions and values.

Original Pull Request: #421
2016-12-06 09:19:28 +01:00
Christoph Strobl
ea4782c421 DATAMONGO-1520 - Add overload for aggregation $match accepting CriteriaDefinition.
We now also accept CriteriaDefinition next to Criteria for Aggregation.match. The existing match(Criteria) method remains to preserve binary compatibility.
2016-12-06 09:05:55 +01:00
Mark Paluch
a0ac3510a0 DATAMONGO-1540 - Polishing.
Reduce Map aggregation expression builder entrypoint. Fix JavaDoc.

Original pull request: #420.
2016-12-05 16:47:20 +01:00
Christoph Strobl
192399413d DATAMONGO-1540 - Add support for $map (aggregation).
We now support $map operator in aggregation.

Original pull request: #420.
2016-12-05 16:47:17 +01:00
Oliver Gierke
a0be890437 DATAMONGO-1547 - Register MongoRepositoryFactory in spring.factories.
This is required for the switch in support for multi-store detection.

Related ticket: DATACMNS-952.
2016-12-05 14:27:24 +01:00
Oliver Gierke
438dbc4b33 DATAMONGO-1546 - Register GeoJsonConfiguration via spring.factories.
Related tickets: DATACMNS-952.
2016-12-05 14:22:09 +01:00
Oliver Gierke
407affb458 DATAMONGO-1141 - Polishing.
Aligned assertion messages for consistency. Fixed imports in  UpdateMapperUnitTests.

Original pull request: #405.
2016-12-02 18:13:22 +01:00
Mark Paluch
c6a4e7166c DATAMONGO-1141 - Polishing.
Add property to field name mapping for Sort orders by moving Sort mapping to UpdateMapper. Fix typo. Add JavaDoc. Reformat code. Remove trailing whitespaces.

Original pull request: #405.
2016-12-02 18:13:18 +01:00
Pavel Vodrážka
7f39c42eb7 DATAMONGO-1141 - Add support for $push $sort in Update.
Sorting update modifier added. Supports sorting arrays by document fields and element values.

Original pull request: #405.
2016-12-02 18:13:02 +01:00
Oliver Gierke
40da4701de DATAMONGO-1454 - Polishing.
Formatting in test case.

Original pull request: #381.
2016-12-02 16:33:13 +01:00
Mark Paluch
3ae6aebebb DATAMONGO-1454 - Add support for exists projection in repository query methods.
We now support exists projections for query methods in query methods for derived and string queries.

public PersonRepository extends Repository<Person, String> {

  boolean existsByFirstname(String firstname);

  @ExistsQuery(value = "{ 'lastname' : ?0 }")
  boolean someExistQuery(String lastname);

  @Query(value = "{ 'lastname' : ?0 }", exists = true)
  boolean anotherExistQuery(String lastname);
}

Original pull request: #381.
2016-12-02 16:32:58 +01:00
Mark Paluch
bbfa0f7b83 DATAMONGO-1536 - Polishing.
Add JavaDoc. Change visibility of AbstractAggregationExpression.getMongoMethod() to protected.

Original pull request: #418.
2016-12-02 15:32:16 +01:00
Christoph Strobl
63d6234446 DATAMONGO-1536 - Add aggregation operators for array, arithmetic, date and set operations.
We now support the following aggregation framework operators:

- setEquals, setIntersection, setUnion, setDifference, setIsSubset, anyElementTrue, allElementsTrue
- stdDevPop, stdDevSamp
- abs, ceil, exp, floor, ln, log, log10, pow, sqrt, trunc
- arrayElementAt, concatArrays, isArray
- literal
- dayOfYear, dayOfMonth, dayOfWeek, year, month, week, hour, minute, second, millisecond, dateToString

Original pull request: #418.
2016-12-02 15:30:01 +01:00
Oliver Gierke
9ff86feb4f DATAMONGO-1539 - Polishing.
Renamed @Count and @Delete to @CountQuery and @DeleteQuery. Minor polishing in test cases and test repository methods. JavaDoc, formatting.

Original pull request: #416.
2016-12-02 11:55:14 +01:00
Fırat KÜÇÜK
8c838e8350 DATAMONGO-1539 - Introduce @CountQuery and @DeleteQuery.
Introducing dedicated annotations for manually defined count and delete queries to avoid misconfiguration and generally simplifying the declaration.

Original pull request: 416.
2016-12-02 11:55:07 +01:00
Oliver Gierke
a79930145d DATAMONGO-1525 - Improved creation of empty collections, esp. EnumSet.
We now use more type information to create a better empty collection in the first place. The previous algorithm always used an empty HashSet plus a subsequent conversion using the raw collection type. Especially the latter caused problems for EnumSets as the conversion into one requires the presence of component type information.

We now use Spring's collection factory and more available type information to create a proper collection in the first place and only rely on a subsequent conversion for arrays.
2016-12-01 20:10:41 +01:00
Christoph Strobl
9059a77712 DATAMONGO-1534 - Fix bulk operations missing to write type info.
We now correctly convert entities into their MongoDB representation including type information via _class property.

Original pull request: #415.
2016-11-28 09:06:13 +01:00
Christoph Strobl
a741400e9b DATAMONGO-1530 - Polishing.
Add missing transformations for ConstructorReference, OperatorNot, OpNE, OpEQ, OpGT, OpGE, OpLT, OpLE, OperatorPower, OpOr and OpAnd. This allows usage of logical operators &, || and ! as part of the expression, while ConstructorReference allows instantiating eg. arrays via an expression `new int[]{4,5,6}`. This can be useful eg. comparing arrays using $setEquals.

More complex aggregation operators like $filter can be created by defining the variable references as string inside the expression like filter(a, 'num', '$$num' > 10).
Commands like $let requires usage of InlineMap to pass in required arguments like eg. let({low:1, high:'$$low'}, gt('$$low', '$$high')).

Original Pull Request: #410
2016-11-25 15:45:14 +01:00
Sebastien Gerard
b786b8220a DATAMONGO-1530 - Add support for missing MongoDB 3.2 aggregation pipeline operators.
Original Pull Request: #410
2016-11-25 15:43:43 +01:00
Mark Paluch
710770e88d DATAMONGO-784 - Polishing.
Add JavaDoc for compareValue.

Original pull request: #414.
2016-11-24 13:56:27 +01:00
Christoph Strobl
e631e2d7c5 DATAMONGO-784 - Add support for comparison aggregation operators to group & project.
We now directly support comparison aggregation operators ($cmp, $eq, $gt, $gte, $lt, $lte and $ne) on both group and project stages.

Original pull request: #414.
2016-11-24 13:56:27 +01:00
Mark Paluch
3dc1e9355a DATAMONGO-1491 - Polishing.
Remove variable before returning value. Add generics for list creation.

Original pull request: #412.
2016-11-24 12:48:15 +01:00
Christoph Strobl
2985b4ca3d DATAMONGO-1491 - Add support for $filter (aggregation).
We new support $filter in aggregation pipeline.

Aggregation.newAggregation(Sales.class,
	Aggregation.project()
		.and(filter("items").as("item").by(GTE.of(field("item.price"), 100)))
		.as("items"))

Original pull request: #412.
2016-11-24 12:47:07 +01:00
Christoph Strobl
578441ee9f DATAMONGO-1327 - Polishing.
Just added overloads for stdDevSamp and stdDevPop taking AggregationExpression and updated the doc.
Also replaced String operation based MongoDB operation building by using operators directly.

Original Pull Request: #360
2016-11-23 15:39:01 +01:00
gustavodegeus
36838ffe31 DATAMONGO-1327 - Added support for $stdDevSamp and $stdDevPop to aggregation $group stage.
Original Pull Request: #360
CLA: 171720160409030719 (Gustavo de Geus)
2016-11-23 15:38:35 +01:00
Oliver Gierke
5bd0e21173 DATAMONGO-1527 - Updated changelog. 2016-11-23 13:52:43 +01:00
108 changed files with 19223 additions and 1222 deletions

View File

@@ -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
View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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)

View File

@@ -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");
}
}

View File

@@ -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");
}

View File

@@ -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

View File

@@ -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

View File

@@ -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>

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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() {

View File

@@ -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) {

View File

@@ -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();
}

View File

@@ -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);
}
}
}

View File

@@ -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()
*/

View File

@@ -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 {

View File

@@ -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));
}
}
}

View File

@@ -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);
}
}

View File

@@ -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));
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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));
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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));
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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;

View File

@@ -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);
}
}
}

View File

@@ -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()

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}

View File

@@ -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);
}
}
}

View File

@@ -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));
}
}

View File

@@ -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 Eulers
* 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;

View File

@@ -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));
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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));
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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);
}

View File

@@ -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)

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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();
}

View File

@@ -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;
}
}

View File

@@ -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()));
}
}

View File

@@ -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;
}

View File

@@ -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}.
*

View File

@@ -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.
*

View File

@@ -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());
}
}

View File

@@ -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
}
}
}

View File

@@ -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";
}
}

View File

@@ -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());
}

View File

@@ -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 "";
}

View File

@@ -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 "";
}

View File

@@ -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 "";
}

View File

@@ -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.
*

View File

@@ -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.
*

View File

@@ -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;
}
}
}

View File

@@ -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);
}
}

View File

@@ -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()

View File

@@ -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()) {

View File

@@ -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.
*

View File

@@ -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

View File

@@ -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.
*

View File

@@ -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);

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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");

View File

@@ -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");
}
}

View File

@@ -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");
}
}

View File

@@ -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")) //

View File

@@ -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());
}
}

View File

@@ -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\" } } } } ] } }")));
}
}

View File

@@ -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;
}
}

View File

@@ -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\" } }")));
}
}

View File

@@ -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");

View File

@@ -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")));
}
}

View File

@@ -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));
}
}

View File

@@ -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) {

View File

@@ -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()));

View File

@@ -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;
}

View File

@@ -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 {

View File

@@ -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';
}
}

View File

@@ -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));
}
}

View File

@@ -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);
}

View File

@@ -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")));
}
}

View File

@@ -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