Compare commits
201 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
32ae2e9ddc | ||
|
|
6fa0625167 | ||
|
|
e88b88ef36 | ||
|
|
2ca879f534 | ||
|
|
8d969ef41d | ||
|
|
bbd02107b2 | ||
|
|
4a1a9a7b0c | ||
|
|
adf16bb31f | ||
|
|
a834f29f32 | ||
|
|
d8721c9930 | ||
|
|
8fe734deb1 | ||
|
|
9246d3311d | ||
|
|
6e3e3e4a01 | ||
|
|
37b9eb3580 | ||
|
|
da1384b84d | ||
|
|
6c0dd2c80e | ||
|
|
28d96f5fe2 | ||
|
|
5372a5c8c1 | ||
|
|
8672dff5bc | ||
|
|
e010bee286 | ||
|
|
07b8e8278e | ||
|
|
8f4db5540d | ||
|
|
9a0dad1723 | ||
|
|
a4c5682f4d | ||
|
|
b2d2c12347 | ||
|
|
b50b645d1f | ||
|
|
4c6f793870 | ||
|
|
5d39191f0b | ||
|
|
75b6dc7a0e | ||
|
|
0c8f176e70 | ||
|
|
c900ceb0d9 | ||
|
|
9072e0a703 | ||
|
|
67991568f3 | ||
|
|
bc981be9f3 | ||
|
|
398eafe615 | ||
|
|
4673525462 | ||
|
|
5c009e393f | ||
|
|
e8e24632e4 | ||
|
|
5c54663adf | ||
|
|
f04e67edf0 | ||
|
|
74269aa6c0 | ||
|
|
bb743f494a | ||
|
|
a502ffabc3 | ||
|
|
ffe4e9b914 | ||
|
|
914bdd9434 | ||
|
|
3cd9542483 | ||
|
|
586bf858f9 | ||
|
|
3478fd5ab3 | ||
|
|
fa5f523c92 | ||
|
|
2191ab3bba | ||
|
|
a79142931f | ||
|
|
1ba210366d | ||
|
|
16aa611007 | ||
|
|
13e29eb81f | ||
|
|
fe90950880 | ||
|
|
492dec8ecf | ||
|
|
a1ac2f7c1d | ||
|
|
04e53316c6 | ||
|
|
a991b96518 | ||
|
|
d53c5cf5c4 | ||
|
|
90779bbb27 | ||
|
|
892cc2e69a | ||
|
|
a69f1b4d51 | ||
|
|
7859ee1013 | ||
|
|
a58562ba69 | ||
|
|
779b0da358 | ||
|
|
ff1703f7c9 | ||
|
|
7b23f8eee2 | ||
|
|
cc97c5a961 | ||
|
|
08a57e58fd | ||
|
|
9d27d2ff8e | ||
|
|
3eba7de073 | ||
|
|
3dc6cab132 | ||
|
|
799fa6c87e | ||
|
|
c58032cf37 | ||
|
|
67c3f02dcc | ||
|
|
208bd6ae52 | ||
|
|
64419751c0 | ||
|
|
cd089d4a54 | ||
|
|
e484337dcf | ||
|
|
e4da45baed | ||
|
|
03246f04b8 | ||
|
|
50070dfc64 | ||
|
|
029d50e526 | ||
|
|
9764ce0147 | ||
|
|
c00f461d06 | ||
|
|
4205516446 | ||
|
|
beced8184f | ||
|
|
64dc3dbb1d | ||
|
|
7b67ad4f6c | ||
|
|
c2373d05fe | ||
|
|
da63788a52 | ||
|
|
016892085c | ||
|
|
4f9c0fa6b3 | ||
|
|
e1393847be | ||
|
|
ff6f5d9ef3 | ||
|
|
d4f351a37c | ||
|
|
67281916c2 | ||
|
|
f8b2781ec8 | ||
|
|
58116dfd63 | ||
|
|
5f2c411501 | ||
|
|
ac84c7bf57 | ||
|
|
db2c05e8fc | ||
|
|
5a735138fc | ||
|
|
7f28aaf60d | ||
|
|
8b8eb3cfe5 | ||
|
|
7f9352f9b8 | ||
|
|
9d1471bb28 | ||
|
|
088928c64a | ||
|
|
648bfdfc67 | ||
|
|
390b00d5fe | ||
|
|
98433250c8 | ||
|
|
323b0a8479 | ||
|
|
d1b1dfbae9 | ||
|
|
d1dea13c32 | ||
|
|
ba2ab183ed | ||
|
|
1eab66aff4 | ||
|
|
8cc4ef3c3f | ||
|
|
1e49c95e41 | ||
|
|
7d06f2b040 | ||
|
|
b7755e71f6 | ||
|
|
0ec82e1f2e | ||
|
|
e18f506edd | ||
|
|
46ed58b465 | ||
|
|
fb8084c9f7 | ||
|
|
5a0171203d | ||
|
|
c1d840d87d | ||
|
|
ed1f2c7833 | ||
|
|
c545c855b9 | ||
|
|
1b7678a6af | ||
|
|
0d06e141a3 | ||
|
|
2d36fc3050 | ||
|
|
78c2ab290d | ||
|
|
88150eca54 | ||
|
|
30b86e7612 | ||
|
|
d3976f5199 | ||
|
|
f587e1f42a | ||
|
|
bd5815dbcb | ||
|
|
ac89ce1b2c | ||
|
|
fa880f1c5c | ||
|
|
56e61a2965 | ||
|
|
fcb8647c59 | ||
|
|
07e0e78aec | ||
|
|
0c0f47f76f | ||
|
|
18313db8fb | ||
|
|
05f325687c | ||
|
|
8145b84dbe | ||
|
|
794b026f73 | ||
|
|
8f11916014 | ||
|
|
c5129aca45 | ||
|
|
dfede781fb | ||
|
|
fe43ba470b | ||
|
|
06622bed35 | ||
|
|
2bac54c70f | ||
|
|
daae696c78 | ||
|
|
9f77aba8bb | ||
|
|
2d9232ca04 | ||
|
|
e90c5bad2c | ||
|
|
bec79e6d7b | ||
|
|
619d344f57 | ||
|
|
6f1b9d3fa4 | ||
|
|
24417c4c99 | ||
|
|
eec36b791a | ||
|
|
512fa036bb | ||
|
|
ea8df26eee | ||
|
|
43d821aab0 | ||
|
|
9bb8211ed7 | ||
|
|
5a24e04226 | ||
|
|
521d28ff3f | ||
|
|
e38e7d89f4 | ||
|
|
fff69b9ed7 | ||
|
|
e2e9e92563 | ||
|
|
3e040d283b | ||
|
|
a66b87118e | ||
|
|
f296a499e5 | ||
|
|
1acf00b039 | ||
|
|
e23c861a39 | ||
|
|
85aef4836d | ||
|
|
5470486d8d | ||
|
|
42c02c9b70 | ||
|
|
ee9bca4856 | ||
|
|
0d823df7f3 | ||
|
|
4cd2935087 | ||
|
|
6fbb7cec22 | ||
|
|
44ea579b69 | ||
|
|
30af34f80a | ||
|
|
247f30143b | ||
|
|
364f266a3a | ||
|
|
f92bd20384 | ||
|
|
304e1c607f | ||
|
|
449780573e | ||
|
|
e424573f0d | ||
|
|
31630c0dcc | ||
|
|
7cdc3d00c1 | ||
|
|
a9f5d7bd3d | ||
|
|
b5f18468db | ||
|
|
ef872d2527 | ||
|
|
c2516946e9 | ||
|
|
857add7349 | ||
|
|
2ec3f219c8 | ||
|
|
5c8701f79c |
37
.travis.yml
37
.travis.yml
@@ -3,44 +3,33 @@ language: java
|
||||
jdk:
|
||||
- oraclejdk8
|
||||
|
||||
before_script:
|
||||
- mongod --version
|
||||
before_install:
|
||||
- mkdir -p downloads
|
||||
- mkdir -p var/db var/log
|
||||
- if [[ ! -d downloads/mongodb-linux-x86_64-ubuntu1604-${MONGO_VERSION} ]] ; then cd downloads && wget https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-ubuntu1604-${MONGO_VERSION}.tgz && tar xzf mongodb-linux-x86_64-ubuntu1604-${MONGO_VERSION}.tgz && cd ..; fi
|
||||
- downloads/mongodb-linux-x86_64-ubuntu1604-${MONGO_VERSION}/bin/mongod --version
|
||||
- downloads/mongodb-linux-x86_64-ubuntu1604-${MONGO_VERSION}/bin/mongod --dbpath var/db --replSet rs0 --fork --logpath var/log/mongod.log
|
||||
- sleep 10
|
||||
- |-
|
||||
echo "replication:
|
||||
replSetName: rs0" | sudo tee -a /etc/mongod.conf
|
||||
- sudo service mongod restart
|
||||
- sleep 20
|
||||
- |-
|
||||
mongo --eval "rs.initiate({_id: 'rs0', members:[{_id: 0, host: '127.0.0.1:27017'}]});"
|
||||
- sleep 15
|
||||
|
||||
services:
|
||||
- mongodb
|
||||
downloads/mongodb-linux-x86_64-ubuntu1604-${MONGO_VERSION}/bin/mongo --eval "rs.initiate({_id: 'rs0', members:[{_id: 0, host: '127.0.0.1:27017'}]});"
|
||||
sleep 15
|
||||
|
||||
env:
|
||||
matrix:
|
||||
- PROFILE=ci
|
||||
- PROFILE=mongo36-next
|
||||
global:
|
||||
- MONGO_VERSION=4.0.0
|
||||
|
||||
# Current MongoDB version is 2.4.2 as of 2016-04, see https://github.com/travis-ci/travis-ci/issues/3694
|
||||
# apt-get starts a MongoDB instance so it's not started using before_script
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
- mongodb-3.4-precise
|
||||
packages:
|
||||
- mongodb-org-server
|
||||
- mongodb-org-shell
|
||||
- oracle-java8-installer
|
||||
- oracle-java8-installer
|
||||
|
||||
sudo: false
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.m2
|
||||
|
||||
install:
|
||||
- |-
|
||||
mongo admin --eval "db.adminCommand({setFeatureCompatibilityVersion: '3.4'});"
|
||||
- downloads
|
||||
|
||||
script: "mvn clean dependency:list test -P${PROFILE} -Dsort"
|
||||
|
||||
36
README.md
36
README.md
@@ -138,6 +138,42 @@ public class MyService {
|
||||
}
|
||||
```
|
||||
|
||||
### MongoDB 4.0 Transactions
|
||||
|
||||
As of version 4 MongoDB supports [Transactions](https://www.mongodb.com/transactions). Transactions are built on top of
|
||||
`ClientSessions` and therefore require an active session.
|
||||
|
||||
`MongoTransactionManager` is the gateway to the well known Spring transaction support. It allows applications to use
|
||||
[managed transaction features of Spring](http://docs.spring.io/spring/docs/current/spring-framework-reference/html/transaction.html).
|
||||
The `MongoTransactionManager` binds a `ClientSession` to the thread. `MongoTemplate` automatically detects those and operates on them accordingly.
|
||||
|
||||
```java
|
||||
@Configuration
|
||||
static class Config extends AbstractMongoConfiguration {
|
||||
|
||||
@Bean
|
||||
MongoTransactionManager transactionManager(MongoDbFactory dbFactory) {
|
||||
return new MongoTransactionManager(dbFactory);
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
@Component
|
||||
public class StateService {
|
||||
|
||||
@Transactional
|
||||
void someBusinessFunction(Step step) {
|
||||
|
||||
template.insert(step);
|
||||
|
||||
process(step);
|
||||
|
||||
template.update(Step.class).apply(Update.set("state", // ...
|
||||
};
|
||||
});
|
||||
```
|
||||
|
||||
## Contributing to Spring Data
|
||||
|
||||
Here are some ways for you to get involved in the community:
|
||||
|
||||
43
pom.xml
43
pom.xml
@@ -5,7 +5,7 @@
|
||||
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-mongodb-parent</artifactId>
|
||||
<version>2.1.0.M2</version>
|
||||
<version>2.2.0.M1</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>2.1.0.M2</version>
|
||||
<version>2.2.0.M1</version>
|
||||
</parent>
|
||||
|
||||
<modules>
|
||||
@@ -27,9 +27,9 @@
|
||||
<properties>
|
||||
<project.type>multi</project.type>
|
||||
<dist.id>spring-data-mongodb</dist.id>
|
||||
<springdata.commons>2.1.0.M2</springdata.commons>
|
||||
<mongo>3.6.3</mongo>
|
||||
<mongo.reactivestreams>1.7.1</mongo.reactivestreams>
|
||||
<springdata.commons>2.2.0.M1</springdata.commons>
|
||||
<mongo>3.8.2</mongo>
|
||||
<mongo.reactivestreams>1.9.2</mongo.reactivestreams>
|
||||
<jmh.version>1.19</jmh.version>
|
||||
</properties>
|
||||
|
||||
@@ -115,38 +115,6 @@
|
||||
|
||||
<profiles>
|
||||
|
||||
<!-- not-yet available profile>
|
||||
|
||||
<id>mongo35-next</id>
|
||||
<properties>
|
||||
<mongo>3.5.1-SNAPSHOT</mongo>
|
||||
</properties>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>mongo-snapshots</id>
|
||||
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
</profile -->
|
||||
|
||||
<profile>
|
||||
|
||||
<id>mongo36-next</id>
|
||||
<properties>
|
||||
<mongo>3.6.0-SNAPSHOT</mongo>
|
||||
</properties>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>mongo-snapshots</id>
|
||||
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
</profile>
|
||||
|
||||
<profile>
|
||||
<id>release</id>
|
||||
<build>
|
||||
@@ -169,7 +137,6 @@
|
||||
<module>spring-data-mongodb-benchmarks</module>
|
||||
</modules>
|
||||
</profile>
|
||||
|
||||
</profiles>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-mongodb-parent</artifactId>
|
||||
<version>2.1.0.M2</version>
|
||||
<version>2.2.0.M1</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-mongodb-parent</artifactId>
|
||||
<version>2.1.0.M2</version>
|
||||
<version>2.2.0.M1</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
<dependency>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-mongodb</artifactId>
|
||||
<version>2.1.0.M2</version>
|
||||
<version>2.2.0.M1</version>
|
||||
</dependency>
|
||||
|
||||
<!-- reactive -->
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -13,7 +14,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-mongodb-parent</artifactId>
|
||||
<version>2.1.0.M2</version>
|
||||
<version>2.2.0.M1</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -35,8 +36,15 @@
|
||||
<plugin>
|
||||
<groupId>org.asciidoctor</groupId>
|
||||
<artifactId>asciidoctor-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<attributes>
|
||||
<mongo-reactivestreams>${mongo.reactivestreams}</mongo-reactivestreams>
|
||||
<reactor>${reactor}</reactor>
|
||||
</attributes>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
|
||||
</build>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-mongodb-parent</artifactId>
|
||||
<version>2.1.0.M2</version>
|
||||
<version>2.2.0.M1</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -253,6 +253,13 @@
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>javax.transaction</groupId>
|
||||
<artifactId>jta</artifactId>
|
||||
<version>1.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Kotlin extension -->
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
|
||||
@@ -0,0 +1,240 @@
|
||||
/*
|
||||
* Copyright 2018 the original author 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;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.transaction.support.ResourceHolderSynchronization;
|
||||
import org.springframework.transaction.support.TransactionSynchronization;
|
||||
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.mongodb.ClientSessionOptions;
|
||||
import com.mongodb.client.ClientSession;
|
||||
import com.mongodb.client.MongoDatabase;
|
||||
|
||||
/**
|
||||
* Helper class for managing a {@link MongoDatabase} instances via {@link MongoDbFactory}. Used for obtaining
|
||||
* {@link ClientSession session bound} resources, such as {@link MongoDatabase} and
|
||||
* {@link com.mongodb.client.MongoCollection} suitable for transactional usage.
|
||||
* <p />
|
||||
* <strong>Note:</strong> Intended for internal usage only.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
* @currentRead Shadow's Edge - Brent Weeks
|
||||
* @since 2.1
|
||||
*/
|
||||
public class MongoDatabaseUtils {
|
||||
|
||||
/**
|
||||
* Obtain the default {@link MongoDatabase database} form the given {@link MongoDbFactory factory} using
|
||||
* {@link SessionSynchronization#ON_ACTUAL_TRANSACTION native session synchronization}.
|
||||
* <p />
|
||||
* Registers a {@link MongoSessionSynchronization MongoDB specific transaction synchronization} within the current
|
||||
* {@link Thread} if {@link TransactionSynchronizationManager#isSynchronizationActive() synchronization is active}.
|
||||
*
|
||||
* @param factory the {@link MongoDbFactory} to get the {@link MongoDatabase} from.
|
||||
* @return the {@link MongoDatabase} that is potentially associated with a transactional {@link ClientSession}.
|
||||
*/
|
||||
public static MongoDatabase getDatabase(MongoDbFactory factory) {
|
||||
return doGetMongoDatabase(null, factory, SessionSynchronization.ON_ACTUAL_TRANSACTION);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain the default {@link MongoDatabase database} form the given {@link MongoDbFactory factory}.
|
||||
* <p />
|
||||
* Registers a {@link MongoSessionSynchronization MongoDB specific transaction synchronization} within the current
|
||||
* {@link Thread} if {@link TransactionSynchronizationManager#isSynchronizationActive() synchronization is active}.
|
||||
*
|
||||
* @param factory the {@link MongoDbFactory} to get the {@link MongoDatabase} from.
|
||||
* @param sessionSynchronization the synchronization to use. Must not be {@literal null}.
|
||||
* @return the {@link MongoDatabase} that is potentially associated with a transactional {@link ClientSession}.
|
||||
*/
|
||||
public static MongoDatabase getDatabase(MongoDbFactory factory, SessionSynchronization sessionSynchronization) {
|
||||
return doGetMongoDatabase(null, factory, sessionSynchronization);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain the {@link MongoDatabase database} with given name form the given {@link MongoDbFactory factory} using
|
||||
* {@link SessionSynchronization#ON_ACTUAL_TRANSACTION native session synchronization}.
|
||||
* <p />
|
||||
* Registers a {@link MongoSessionSynchronization MongoDB specific transaction synchronization} within the current
|
||||
* {@link Thread} if {@link TransactionSynchronizationManager#isSynchronizationActive() synchronization is active}.
|
||||
*
|
||||
* @param dbName the name of the {@link MongoDatabase} to get.
|
||||
* @param factory the {@link MongoDbFactory} to get the {@link MongoDatabase} from.
|
||||
* @return the {@link MongoDatabase} that is potentially associated with a transactional {@link ClientSession}.
|
||||
*/
|
||||
public static MongoDatabase getDatabase(String dbName, MongoDbFactory factory) {
|
||||
return doGetMongoDatabase(dbName, factory, SessionSynchronization.ON_ACTUAL_TRANSACTION);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain the {@link MongoDatabase database} with given name form the given {@link MongoDbFactory factory}.
|
||||
* <p />
|
||||
* Registers a {@link MongoSessionSynchronization MongoDB specific transaction synchronization} within the current
|
||||
* {@link Thread} if {@link TransactionSynchronizationManager#isSynchronizationActive() synchronization is active}.
|
||||
*
|
||||
* @param dbName the name of the {@link MongoDatabase} to get.
|
||||
* @param factory the {@link MongoDbFactory} to get the {@link MongoDatabase} from.
|
||||
* @param sessionSynchronization the synchronization to use. Must not be {@literal null}.
|
||||
* @return the {@link MongoDatabase} that is potentially associated with a transactional {@link ClientSession}.
|
||||
*/
|
||||
public static MongoDatabase getDatabase(String dbName, MongoDbFactory factory,
|
||||
SessionSynchronization sessionSynchronization) {
|
||||
return doGetMongoDatabase(dbName, factory, sessionSynchronization);
|
||||
}
|
||||
|
||||
private static MongoDatabase doGetMongoDatabase(@Nullable String dbName, MongoDbFactory factory,
|
||||
SessionSynchronization sessionSynchronization) {
|
||||
|
||||
Assert.notNull(factory, "Factory must not be null!");
|
||||
|
||||
if (!TransactionSynchronizationManager.isSynchronizationActive()) {
|
||||
return StringUtils.hasText(dbName) ? factory.getDb(dbName) : factory.getDb();
|
||||
}
|
||||
|
||||
ClientSession session = doGetSession(factory, sessionSynchronization);
|
||||
|
||||
if (session == null) {
|
||||
return StringUtils.hasText(dbName) ? factory.getDb(dbName) : factory.getDb();
|
||||
}
|
||||
|
||||
MongoDbFactory factoryToUse = factory.withSession(session);
|
||||
return StringUtils.hasText(dbName) ? factoryToUse.getDb(dbName) : factoryToUse.getDb();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the {@link MongoDbFactory} is actually bound to a {@link ClientSession} that has an active transaction, or
|
||||
* if a {@link TransactionSynchronization} has been registered for the {@link MongoDbFactory resource} and if the
|
||||
* associated {@link ClientSession} has an {@link ClientSession#hasActiveTransaction() active transaction}.
|
||||
*
|
||||
* @param dbFactory the resource to check transactions for. Must not be {@literal null}.
|
||||
* @return {@literal true} if the factory has an ongoing transaction.
|
||||
* @since 2.1.3
|
||||
*/
|
||||
public static boolean isTransactionActive(MongoDbFactory dbFactory) {
|
||||
|
||||
if (dbFactory.isTransactionActive()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
MongoResourceHolder resourceHolder = (MongoResourceHolder) TransactionSynchronizationManager.getResource(dbFactory);
|
||||
return resourceHolder != null && resourceHolder.hasActiveTransaction();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static ClientSession doGetSession(MongoDbFactory dbFactory, SessionSynchronization sessionSynchronization) {
|
||||
|
||||
MongoResourceHolder resourceHolder = (MongoResourceHolder) TransactionSynchronizationManager.getResource(dbFactory);
|
||||
|
||||
// check for native MongoDB transaction
|
||||
if (resourceHolder != null && (resourceHolder.hasSession() || resourceHolder.isSynchronizedWithTransaction())) {
|
||||
|
||||
if (!resourceHolder.hasSession()) {
|
||||
resourceHolder.setSession(createClientSession(dbFactory));
|
||||
}
|
||||
|
||||
return resourceHolder.getSession();
|
||||
}
|
||||
|
||||
if (SessionSynchronization.ON_ACTUAL_TRANSACTION.equals(sessionSynchronization)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// init a non native MongoDB transaction by registering a MongoSessionSynchronization
|
||||
|
||||
resourceHolder = new MongoResourceHolder(createClientSession(dbFactory), dbFactory);
|
||||
resourceHolder.getRequiredSession().startTransaction();
|
||||
|
||||
TransactionSynchronizationManager
|
||||
.registerSynchronization(new MongoSessionSynchronization(resourceHolder, dbFactory));
|
||||
resourceHolder.setSynchronizedWithTransaction(true);
|
||||
TransactionSynchronizationManager.bindResource(dbFactory, resourceHolder);
|
||||
|
||||
return resourceHolder.getSession();
|
||||
}
|
||||
|
||||
private static ClientSession createClientSession(MongoDbFactory dbFactory) {
|
||||
return dbFactory.getSession(ClientSessionOptions.builder().causallyConsistent(true).build());
|
||||
}
|
||||
|
||||
/**
|
||||
* MongoDB specific {@link ResourceHolderSynchronization} for resource cleanup at the end of a transaction when
|
||||
* participating in a non-native MongoDB transaction, such as a Jta or JDBC transaction.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.1
|
||||
*/
|
||||
private static class MongoSessionSynchronization extends ResourceHolderSynchronization<MongoResourceHolder, Object> {
|
||||
|
||||
private final MongoResourceHolder resourceHolder;
|
||||
|
||||
MongoSessionSynchronization(MongoResourceHolder resourceHolder, MongoDbFactory dbFactory) {
|
||||
|
||||
super(resourceHolder, dbFactory);
|
||||
this.resourceHolder = resourceHolder;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.transaction.support.ResourceHolderSynchronization#shouldReleaseBeforeCompletion()
|
||||
*/
|
||||
@Override
|
||||
protected boolean shouldReleaseBeforeCompletion() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.transaction.support.ResourceHolderSynchronization#processResourceAfterCommit(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
protected void processResourceAfterCommit(MongoResourceHolder resourceHolder) {
|
||||
|
||||
if (resourceHolder.hasActiveTransaction()) {
|
||||
resourceHolder.getRequiredSession().commitTransaction();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.transaction.support.ResourceHolderSynchronization#afterCompletion(int)
|
||||
*/
|
||||
@Override
|
||||
public void afterCompletion(int status) {
|
||||
|
||||
if (status == TransactionSynchronization.STATUS_ROLLED_BACK && this.resourceHolder.hasActiveTransaction()) {
|
||||
resourceHolder.getRequiredSession().abortTransaction();
|
||||
}
|
||||
|
||||
super.afterCompletion(status);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.transaction.support.ResourceHolderSynchronization#releaseResource(java.lang.Object, java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
protected void releaseResource(MongoResourceHolder resourceHolder, Object resourceKey) {
|
||||
|
||||
if (resourceHolder.hasActiveSession()) {
|
||||
resourceHolder.getRequiredSession().close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,8 +22,8 @@ import org.springframework.data.mongodb.core.MongoExceptionTranslator;
|
||||
|
||||
import com.mongodb.ClientSessionOptions;
|
||||
import com.mongodb.DB;
|
||||
import com.mongodb.client.ClientSession;
|
||||
import com.mongodb.client.MongoDatabase;
|
||||
import com.mongodb.session.ClientSession;
|
||||
|
||||
/**
|
||||
* Interface for factories creating {@link MongoDatabase} instances.
|
||||
@@ -32,7 +32,7 @@ import com.mongodb.session.ClientSession;
|
||||
* @author Thomas Darimont
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public interface MongoDbFactory extends CodecRegistryProvider {
|
||||
public interface MongoDbFactory extends CodecRegistryProvider, MongoSessionProvider {
|
||||
|
||||
/**
|
||||
* Creates a default {@link MongoDatabase} instance.
|
||||
@@ -58,6 +58,14 @@ public interface MongoDbFactory extends CodecRegistryProvider {
|
||||
*/
|
||||
PersistenceExceptionTranslator getExceptionTranslator();
|
||||
|
||||
/**
|
||||
* Get the legacy database entry point. Please consider {@link #getDb()} instead.
|
||||
*
|
||||
* @return
|
||||
* @deprecated since 2.1, use {@link #getDb()}. This method will be removed with a future version as it works only
|
||||
* with the legacy MongoDB driver.
|
||||
*/
|
||||
@Deprecated
|
||||
DB getLegacyDb();
|
||||
|
||||
/**
|
||||
@@ -100,4 +108,15 @@ public interface MongoDbFactory extends CodecRegistryProvider {
|
||||
* @since 2.1
|
||||
*/
|
||||
MongoDbFactory withSession(ClientSession session);
|
||||
|
||||
/**
|
||||
* Returns if the given {@link MongoDbFactory} is bound to a {@link ClientSession} that has an
|
||||
* {@link ClientSession#hasActiveTransaction() active transaction}.
|
||||
*
|
||||
* @return {@literal true} if there's an active transaction, {@literal false} otherwise.
|
||||
* @since 2.1.3
|
||||
*/
|
||||
default boolean isTransactionActive() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
/*
|
||||
* Copyright 2018 the original author 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;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.transaction.TransactionDefinition;
|
||||
import org.springframework.transaction.support.ResourceHolderSupport;
|
||||
|
||||
import com.mongodb.client.ClientSession;
|
||||
|
||||
/**
|
||||
* MongoDB specific {@link ResourceHolderSupport resource holder}, wrapping a {@link ClientSession}.
|
||||
* {@link MongoTransactionManager} binds instances of this class to the thread.
|
||||
* <p />
|
||||
* <strong>Note:</strong> Intended for internal usage only.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
* @since 2.1
|
||||
* @see MongoTransactionManager
|
||||
* @see org.springframework.data.mongodb.core.MongoTemplate
|
||||
*/
|
||||
class MongoResourceHolder extends ResourceHolderSupport {
|
||||
|
||||
private @Nullable ClientSession session;
|
||||
private MongoDbFactory dbFactory;
|
||||
|
||||
/**
|
||||
* Create a new {@link MongoResourceHolder} for a given {@link ClientSession session}.
|
||||
*
|
||||
* @param session the associated {@link ClientSession}. Can be {@literal null}.
|
||||
* @param dbFactory the associated {@link MongoDbFactory}. must not be {@literal null}.
|
||||
*/
|
||||
MongoResourceHolder(@Nullable ClientSession session, MongoDbFactory dbFactory) {
|
||||
|
||||
this.session = session;
|
||||
this.dbFactory = dbFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the associated {@link ClientSession}. Can be {@literal null}.
|
||||
*/
|
||||
@Nullable
|
||||
ClientSession getSession() {
|
||||
return session;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the required associated {@link ClientSession}.
|
||||
* @throws IllegalStateException if no {@link ClientSession} is associated with this {@link MongoResourceHolder}.
|
||||
* @since 2.1.3
|
||||
*/
|
||||
ClientSession getRequiredSession() {
|
||||
|
||||
ClientSession session = getSession();
|
||||
|
||||
if (session == null) {
|
||||
throw new IllegalStateException("No session available!");
|
||||
}
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the associated {@link MongoDbFactory}.
|
||||
*/
|
||||
public MongoDbFactory getDbFactory() {
|
||||
return dbFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link ClientSession} to guard.
|
||||
*
|
||||
* @param session can be {@literal null}.
|
||||
*/
|
||||
public void setSession(@Nullable ClientSession session) {
|
||||
this.session = session;
|
||||
}
|
||||
|
||||
/**
|
||||
* Only set the timeout if it does not match the {@link TransactionDefinition#TIMEOUT_DEFAULT default timeout}.
|
||||
*
|
||||
* @param seconds
|
||||
*/
|
||||
void setTimeoutIfNotDefaulted(int seconds) {
|
||||
|
||||
if (seconds != TransactionDefinition.TIMEOUT_DEFAULT) {
|
||||
setTimeoutInSeconds(seconds);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@literal true} if session is not {@literal null}.
|
||||
*/
|
||||
boolean hasSession() {
|
||||
return session != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@literal true} if the session is active and has not been closed.
|
||||
*/
|
||||
boolean hasActiveSession() {
|
||||
|
||||
if (!hasSession()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return hasServerSession() && !getRequiredSession().getServerSession().isClosed();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@literal true} if the session has an active transaction.
|
||||
* @since 2.1.3
|
||||
* @see #hasActiveSession()
|
||||
*/
|
||||
boolean hasActiveTransaction() {
|
||||
|
||||
if (!hasActiveSession()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return getRequiredSession().hasActiveTransaction();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@literal true} if the {@link ClientSession} has a {@link com.mongodb.session.ServerSession} associated
|
||||
* that is accessible via {@link ClientSession#getServerSession()}.
|
||||
*/
|
||||
boolean hasServerSession() {
|
||||
|
||||
try {
|
||||
return getRequiredSession().getServerSession() != null;
|
||||
} catch (IllegalStateException serverSessionClosed) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2018 the original author 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;
|
||||
|
||||
import com.mongodb.ClientSessionOptions;
|
||||
import com.mongodb.client.ClientSession;
|
||||
|
||||
/**
|
||||
* A simple interface for obtaining a {@link ClientSession} to be consumed by
|
||||
* {@link org.springframework.data.mongodb.core.MongoOperations} and MongoDB native operations that support causal
|
||||
* consistency and transactions.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @currentRead Shadow's Edge - Brent Weeks
|
||||
* @since 2.1
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface MongoSessionProvider {
|
||||
|
||||
/**
|
||||
* Obtain a {@link ClientSession} with with given options.
|
||||
*
|
||||
* @param options must not be {@literal null}.
|
||||
* @return never {@literal null}.
|
||||
* @throws org.springframework.dao.DataAccessException
|
||||
*/
|
||||
ClientSession getSession(ClientSessionOptions options);
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2018 the original author 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;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* A specific {@link ClientSessionException} related to issues with a transaction such as aborted or non existing
|
||||
* transactions.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.1
|
||||
*/
|
||||
public class MongoTransactionException extends ClientSessionException {
|
||||
|
||||
/**
|
||||
* Constructor for {@link MongoTransactionException}.
|
||||
*
|
||||
* @param msg the detail message. Must not be {@literal null}.
|
||||
*/
|
||||
public MongoTransactionException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for {@link ClientSessionException}.
|
||||
*
|
||||
* @param msg the detail message. Can be {@literal null}.
|
||||
* @param cause the root cause. Can be {@literal null}.
|
||||
*/
|
||||
public MongoTransactionException(@Nullable String msg, @Nullable Throwable cause) {
|
||||
super(msg, cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,526 @@
|
||||
/*
|
||||
* Copyright 2018 the original author 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;
|
||||
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.transaction.TransactionDefinition;
|
||||
import org.springframework.transaction.TransactionException;
|
||||
import org.springframework.transaction.TransactionSystemException;
|
||||
import org.springframework.transaction.support.AbstractPlatformTransactionManager;
|
||||
import org.springframework.transaction.support.DefaultTransactionStatus;
|
||||
import org.springframework.transaction.support.ResourceTransactionManager;
|
||||
import org.springframework.transaction.support.SmartTransactionObject;
|
||||
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
||||
import org.springframework.transaction.support.TransactionSynchronizationUtils;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
import com.mongodb.ClientSessionOptions;
|
||||
import com.mongodb.MongoException;
|
||||
import com.mongodb.TransactionOptions;
|
||||
import com.mongodb.client.ClientSession;
|
||||
|
||||
/**
|
||||
* A {@link org.springframework.transaction.PlatformTransactionManager} implementation that manages
|
||||
* {@link ClientSession} based transactions for a single {@link MongoDbFactory}.
|
||||
* <p />
|
||||
* Binds a {@link ClientSession} from the specified {@link MongoDbFactory} to the thread.
|
||||
* <p />
|
||||
* {@link TransactionDefinition#isReadOnly() Readonly} transactions operate on a {@link ClientSession} and enable causal
|
||||
* consistency, and also {@link ClientSession#startTransaction() start}, {@link ClientSession#commitTransaction()
|
||||
* commit} or {@link ClientSession#abortTransaction() abort} a transaction.
|
||||
* <p />
|
||||
* Application code is required to retrieve the {@link com.mongodb.client.MongoDatabase} via
|
||||
* {@link MongoDatabaseUtils#getDatabase(MongoDbFactory)} instead of a standard {@link MongoDbFactory#getDb()} call.
|
||||
* Spring classes such as {@link org.springframework.data.mongodb.core.MongoTemplate} use this strategy implicitly.
|
||||
* <p />
|
||||
* By default failure of a {@literal commit} operation raises a {@link TransactionSystemException}. One may override
|
||||
* {@link #doCommit(MongoTransactionObject)} to implement the
|
||||
* <a href="https://docs.mongodb.com/manual/core/transactions/#retry-commit-operation">Retry Commit Operation</a>
|
||||
* behavior as outlined in the MongoDB reference manual.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
* @currentRead Shadow's Edge - Brent Weeks
|
||||
* @since 2.1
|
||||
* @see <a href="https://www.mongodb.com/transactions">MongoDB Transaction Documentation</a>
|
||||
* @see MongoDatabaseUtils#getDatabase(MongoDbFactory, SessionSynchronization)
|
||||
*/
|
||||
public class MongoTransactionManager extends AbstractPlatformTransactionManager
|
||||
implements ResourceTransactionManager, InitializingBean {
|
||||
|
||||
private @Nullable MongoDbFactory dbFactory;
|
||||
private @Nullable TransactionOptions options;
|
||||
|
||||
/**
|
||||
* Create a new {@link MongoTransactionManager} for bean-style usage.
|
||||
* <p />
|
||||
* <strong>Note:</strong>The {@link MongoDbFactory db factory} has to be {@link #setDbFactory(MongoDbFactory) set}
|
||||
* before using the instance. Use this constructor to prepare a {@link MongoTransactionManager} via a
|
||||
* {@link org.springframework.beans.factory.BeanFactory}.
|
||||
* <p />
|
||||
* Optionally it is possible to set default {@link TransactionOptions transaction options} defining
|
||||
* {@link com.mongodb.ReadConcern} and {@link com.mongodb.WriteConcern}.
|
||||
*
|
||||
* @see #setDbFactory(MongoDbFactory)
|
||||
* @see #setTransactionSynchronization(int)
|
||||
*/
|
||||
public MongoTransactionManager() {}
|
||||
|
||||
/**
|
||||
* Create a new {@link MongoTransactionManager} obtaining sessions from the given {@link MongoDbFactory}.
|
||||
*
|
||||
* @param dbFactory must not be {@literal null}.
|
||||
*/
|
||||
public MongoTransactionManager(MongoDbFactory dbFactory) {
|
||||
this(dbFactory, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link MongoTransactionManager} obtaining sessions from the given {@link MongoDbFactory} applying the
|
||||
* given {@link TransactionOptions options}, if present, when starting a new transaction.
|
||||
*
|
||||
* @param dbFactory must not be {@literal null}.
|
||||
* @param options can be {@literal null}.
|
||||
*/
|
||||
public MongoTransactionManager(MongoDbFactory dbFactory, @Nullable TransactionOptions options) {
|
||||
|
||||
Assert.notNull(dbFactory, "DbFactory must not be null!");
|
||||
|
||||
this.dbFactory = dbFactory;
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* org.springframework.transaction.support.AbstractPlatformTransactionManager#doGetTransaction()
|
||||
*/
|
||||
@Override
|
||||
protected Object doGetTransaction() throws TransactionException {
|
||||
|
||||
MongoResourceHolder resourceHolder = (MongoResourceHolder) TransactionSynchronizationManager
|
||||
.getResource(getRequiredDbFactory());
|
||||
return new MongoTransactionObject(resourceHolder);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* org.springframework.transaction.support.AbstractPlatformTransactionManager#isExistingTransaction(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
protected boolean isExistingTransaction(Object transaction) throws TransactionException {
|
||||
return extractMongoTransaction(transaction).hasResourceHolder();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* org.springframework.transaction.support.AbstractPlatformTransactionManager#doBegin(java.lang.Object, org.springframework.transaction.TransactionDefinition)
|
||||
*/
|
||||
@Override
|
||||
protected void doBegin(Object transaction, TransactionDefinition definition) throws TransactionException {
|
||||
|
||||
MongoTransactionObject mongoTransactionObject = extractMongoTransaction(transaction);
|
||||
|
||||
MongoResourceHolder resourceHolder = newResourceHolder(definition,
|
||||
ClientSessionOptions.builder().causallyConsistent(true).build());
|
||||
mongoTransactionObject.setResourceHolder(resourceHolder);
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger
|
||||
.debug(String.format("About to start transaction for session %s.", debugString(resourceHolder.getSession())));
|
||||
}
|
||||
|
||||
try {
|
||||
mongoTransactionObject.startTransaction(options);
|
||||
} catch (MongoException ex) {
|
||||
throw new TransactionSystemException(String.format("Could not start Mongo transaction for session %s.",
|
||||
debugString(mongoTransactionObject.getSession())), ex);
|
||||
}
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug(String.format("Started transaction for session %s.", debugString(resourceHolder.getSession())));
|
||||
}
|
||||
|
||||
resourceHolder.setSynchronizedWithTransaction(true);
|
||||
TransactionSynchronizationManager.bindResource(getRequiredDbFactory(), resourceHolder);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* org.springframework.transaction.support.AbstractPlatformTransactionManager#doSuspend(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
protected Object doSuspend(Object transaction) throws TransactionException {
|
||||
|
||||
MongoTransactionObject mongoTransactionObject = extractMongoTransaction(transaction);
|
||||
mongoTransactionObject.setResourceHolder(null);
|
||||
|
||||
return TransactionSynchronizationManager.unbindResource(getRequiredDbFactory());
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* org.springframework.transaction.support.AbstractPlatformTransactionManager#doResume(java.lang.Object, java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
protected void doResume(@Nullable Object transaction, Object suspendedResources) {
|
||||
TransactionSynchronizationManager.bindResource(getRequiredDbFactory(), suspendedResources);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* org.springframework.transaction.support.AbstractPlatformTransactionManager#doCommit(org.springframework.transaction.support.DefaultTransactionStatus)
|
||||
*/
|
||||
@Override
|
||||
protected final void doCommit(DefaultTransactionStatus status) throws TransactionException {
|
||||
|
||||
MongoTransactionObject mongoTransactionObject = extractMongoTransaction(status);
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug(String.format("About to commit transaction for session %s.",
|
||||
debugString(mongoTransactionObject.getSession())));
|
||||
}
|
||||
|
||||
try {
|
||||
doCommit(mongoTransactionObject);
|
||||
} catch (Exception ex) {
|
||||
|
||||
throw new TransactionSystemException(String.format("Could not commit Mongo transaction for session %s.",
|
||||
debugString(mongoTransactionObject.getSession())), ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Customization hook to perform an actual commit of the given transaction.<br />
|
||||
* If a commit operation encounters an error, the MongoDB driver throws a {@link MongoException} holding
|
||||
* {@literal error labels}. <br />
|
||||
* By default those labels are ignored, nevertheless one might check for
|
||||
* {@link MongoException#UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL transient commit errors labels} and retry the the
|
||||
* commit. <br />
|
||||
* <code>
|
||||
* <pre>
|
||||
* int retries = 3;
|
||||
* do {
|
||||
* try {
|
||||
* transactionObject.commitTransaction();
|
||||
* break;
|
||||
* } catch (MongoException ex) {
|
||||
* if (!ex.hasErrorLabel(MongoException.UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL)) {
|
||||
* throw ex;
|
||||
* }
|
||||
* }
|
||||
* Thread.sleep(500);
|
||||
* } while (--retries > 0);
|
||||
* </pre>
|
||||
* </code>
|
||||
*
|
||||
* @param transactionObject never {@literal null}.
|
||||
* @throws Exception in case of transaction errors.
|
||||
*/
|
||||
protected void doCommit(MongoTransactionObject transactionObject) throws Exception {
|
||||
transactionObject.commitTransaction();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* org.springframework.transaction.support.AbstractPlatformTransactionManager#doRollback(org.springframework.transaction.support.DefaultTransactionStatus)
|
||||
*/
|
||||
@Override
|
||||
protected void doRollback(DefaultTransactionStatus status) throws TransactionException {
|
||||
|
||||
MongoTransactionObject mongoTransactionObject = extractMongoTransaction(status);
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug(String.format("About to abort transaction for session %s.",
|
||||
debugString(mongoTransactionObject.getSession())));
|
||||
}
|
||||
|
||||
try {
|
||||
mongoTransactionObject.abortTransaction();
|
||||
} catch (MongoException ex) {
|
||||
|
||||
throw new TransactionSystemException(String.format("Could not abort Mongo transaction for session %s.",
|
||||
debugString(mongoTransactionObject.getSession())), ex);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* org.springframework.transaction.support.AbstractPlatformTransactionManager#doSetRollbackOnly(org.springframework.transaction.support.DefaultTransactionStatus)
|
||||
*/
|
||||
@Override
|
||||
protected void doSetRollbackOnly(DefaultTransactionStatus status) throws TransactionException {
|
||||
|
||||
MongoTransactionObject transactionObject = extractMongoTransaction(status);
|
||||
transactionObject.getRequiredResourceHolder().setRollbackOnly();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* org.springframework.transaction.support.AbstractPlatformTransactionManager#doCleanupAfterCompletion(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
protected void doCleanupAfterCompletion(Object transaction) {
|
||||
|
||||
Assert.isInstanceOf(MongoTransactionObject.class, transaction,
|
||||
() -> String.format("Expected to find a %s but it turned out to be %s.", MongoTransactionObject.class,
|
||||
transaction.getClass()));
|
||||
|
||||
MongoTransactionObject mongoTransactionObject = (MongoTransactionObject) transaction;
|
||||
|
||||
// Remove the connection holder from the thread.
|
||||
TransactionSynchronizationManager.unbindResource(getRequiredDbFactory());
|
||||
mongoTransactionObject.getRequiredResourceHolder().clear();
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug(String.format("About to release Session %s after transaction.",
|
||||
debugString(mongoTransactionObject.getSession())));
|
||||
}
|
||||
|
||||
mongoTransactionObject.closeSession();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link MongoDbFactory} that this instance should manage transactions for.
|
||||
*
|
||||
* @param dbFactory must not be {@literal null}.
|
||||
*/
|
||||
public void setDbFactory(MongoDbFactory dbFactory) {
|
||||
|
||||
Assert.notNull(dbFactory, "DbFactory must not be null!");
|
||||
this.dbFactory = dbFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link TransactionOptions} to be applied when starting transactions.
|
||||
*
|
||||
* @param options can be {@literal null}.
|
||||
*/
|
||||
public void setOptions(@Nullable TransactionOptions options) {
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link MongoDbFactory} that this instance manages transactions for.
|
||||
*
|
||||
* @return can be {@literal null}.
|
||||
*/
|
||||
@Nullable
|
||||
public MongoDbFactory getDbFactory() {
|
||||
return dbFactory;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.transaction.support.ResourceTransactionManager#getResourceFactory()
|
||||
*/
|
||||
@Override
|
||||
public MongoDbFactory getResourceFactory() {
|
||||
return getRequiredDbFactory();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
|
||||
*/
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
getRequiredDbFactory();
|
||||
}
|
||||
|
||||
private MongoResourceHolder newResourceHolder(TransactionDefinition definition, ClientSessionOptions options) {
|
||||
|
||||
MongoDbFactory dbFactory = getResourceFactory();
|
||||
|
||||
MongoResourceHolder resourceHolder = new MongoResourceHolder(dbFactory.getSession(options), dbFactory);
|
||||
resourceHolder.setTimeoutIfNotDefaulted(determineTimeout(definition));
|
||||
|
||||
return resourceHolder;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws IllegalStateException if {@link #dbFactory} is {@literal null}.
|
||||
*/
|
||||
private MongoDbFactory getRequiredDbFactory() {
|
||||
|
||||
Assert.state(dbFactory != null,
|
||||
"MongoTransactionManager operates upon a MongoDbFactory. Did you forget to provide one? It's required.");
|
||||
|
||||
return dbFactory;
|
||||
}
|
||||
|
||||
private static MongoTransactionObject extractMongoTransaction(Object transaction) {
|
||||
|
||||
Assert.isInstanceOf(MongoTransactionObject.class, transaction,
|
||||
() -> String.format("Expected to find a %s but it turned out to be %s.", MongoTransactionObject.class,
|
||||
transaction.getClass()));
|
||||
|
||||
return (MongoTransactionObject) transaction;
|
||||
}
|
||||
|
||||
private static MongoTransactionObject extractMongoTransaction(DefaultTransactionStatus status) {
|
||||
|
||||
Assert.isInstanceOf(MongoTransactionObject.class, status.getTransaction(),
|
||||
() -> String.format("Expected to find a %s but it turned out to be %s.", MongoTransactionObject.class,
|
||||
status.getTransaction().getClass()));
|
||||
|
||||
return (MongoTransactionObject) status.getTransaction();
|
||||
}
|
||||
|
||||
private static String debugString(@Nullable ClientSession session) {
|
||||
|
||||
if (session == null) {
|
||||
return "null";
|
||||
}
|
||||
|
||||
String debugString = String.format("[%s@%s ", ClassUtils.getShortName(session.getClass()),
|
||||
Integer.toHexString(session.hashCode()));
|
||||
|
||||
try {
|
||||
if (session.getServerSession() != null) {
|
||||
debugString += String.format("id = %s, ", session.getServerSession().getIdentifier());
|
||||
debugString += String.format("causallyConsistent = %s, ", session.isCausallyConsistent());
|
||||
debugString += String.format("txActive = %s, ", session.hasActiveTransaction());
|
||||
debugString += String.format("txNumber = %d, ", session.getServerSession().getTransactionNumber());
|
||||
debugString += String.format("closed = %d, ", session.getServerSession().isClosed());
|
||||
debugString += String.format("clusterTime = %s", session.getClusterTime());
|
||||
} else {
|
||||
debugString += "id = n/a";
|
||||
debugString += String.format("causallyConsistent = %s, ", session.isCausallyConsistent());
|
||||
debugString += String.format("txActive = %s, ", session.hasActiveTransaction());
|
||||
debugString += String.format("clusterTime = %s", session.getClusterTime());
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
debugString += String.format("error = %s", e.getMessage());
|
||||
}
|
||||
|
||||
debugString += "]";
|
||||
|
||||
return debugString;
|
||||
}
|
||||
|
||||
/**
|
||||
* MongoDB specific transaction object, representing a {@link MongoResourceHolder}. Used as transaction object by
|
||||
* {@link MongoTransactionManager}.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
* @since 2.1
|
||||
* @see MongoResourceHolder
|
||||
*/
|
||||
protected static class MongoTransactionObject implements SmartTransactionObject {
|
||||
|
||||
private @Nullable MongoResourceHolder resourceHolder;
|
||||
|
||||
MongoTransactionObject(@Nullable MongoResourceHolder resourceHolder) {
|
||||
this.resourceHolder = resourceHolder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link MongoResourceHolder}.
|
||||
*
|
||||
* @param resourceHolder can be {@literal null}.
|
||||
*/
|
||||
void setResourceHolder(@Nullable MongoResourceHolder resourceHolder) {
|
||||
this.resourceHolder = resourceHolder;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@literal true} if a {@link MongoResourceHolder} is set.
|
||||
*/
|
||||
final boolean hasResourceHolder() {
|
||||
return resourceHolder != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a MongoDB transaction optionally given {@link TransactionOptions}.
|
||||
*
|
||||
* @param options can be {@literal null}
|
||||
*/
|
||||
void startTransaction(@Nullable TransactionOptions options) {
|
||||
|
||||
ClientSession session = getRequiredSession();
|
||||
if (options != null) {
|
||||
session.startTransaction(options);
|
||||
} else {
|
||||
session.startTransaction();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Commit the transaction.
|
||||
*/
|
||||
public void commitTransaction() {
|
||||
getRequiredSession().commitTransaction();
|
||||
}
|
||||
|
||||
/**
|
||||
* Rollback (abort) the transaction.
|
||||
*/
|
||||
public void abortTransaction() {
|
||||
getRequiredSession().abortTransaction();
|
||||
}
|
||||
|
||||
/**
|
||||
* Close a {@link ClientSession} without regard to its transactional state.
|
||||
*/
|
||||
void closeSession() {
|
||||
|
||||
ClientSession session = getRequiredSession();
|
||||
if (session.getServerSession() != null && !session.getServerSession().isClosed()) {
|
||||
session.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public ClientSession getSession() {
|
||||
return resourceHolder != null ? resourceHolder.getSession() : null;
|
||||
}
|
||||
|
||||
private MongoResourceHolder getRequiredResourceHolder() {
|
||||
|
||||
Assert.state(resourceHolder != null, "MongoResourceHolder is required but not present. o_O");
|
||||
return resourceHolder;
|
||||
}
|
||||
|
||||
private ClientSession getRequiredSession() {
|
||||
|
||||
ClientSession session = getSession();
|
||||
Assert.state(session != null, "A Session is required but it turned out to be null.");
|
||||
return session;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.transaction.support.SmartTransactionObject#isRollbackOnly()
|
||||
*/
|
||||
@Override
|
||||
public boolean isRollbackOnly() {
|
||||
return this.resourceHolder != null && this.resourceHolder.isRollbackOnly();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.transaction.support.SmartTransactionObject#flush()
|
||||
*/
|
||||
@Override
|
||||
public void flush() {
|
||||
TransactionSynchronizationUtils.triggerFlush();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -24,8 +24,8 @@ import org.springframework.dao.support.PersistenceExceptionTranslator;
|
||||
import org.springframework.data.mongodb.core.MongoExceptionTranslator;
|
||||
|
||||
import com.mongodb.ClientSessionOptions;
|
||||
import com.mongodb.reactivestreams.client.ClientSession;
|
||||
import com.mongodb.reactivestreams.client.MongoDatabase;
|
||||
import com.mongodb.session.ClientSession;
|
||||
|
||||
/**
|
||||
* Interface for factories creating reactive {@link MongoDatabase} instances.
|
||||
|
||||
@@ -57,6 +57,7 @@ public class SessionAwareMethodInterceptor<D, C> implements MethodInterceptor {
|
||||
private final Class<?> targetType;
|
||||
private final Class<?> collectionType;
|
||||
private final Class<?> databaseType;
|
||||
private final Class<? extends ClientSession> sessionType;
|
||||
|
||||
/**
|
||||
* Create a new SessionAwareMethodInterceptor for given target.
|
||||
@@ -71,12 +72,13 @@ public class SessionAwareMethodInterceptor<D, C> implements MethodInterceptor {
|
||||
* {@code MongoCollection}.
|
||||
* @param <T> target object type.
|
||||
*/
|
||||
public <T> SessionAwareMethodInterceptor(ClientSession session, T target, Class<D> databaseType,
|
||||
ClientSessionOperator<D> databaseDecorator, Class<C> collectionType,
|
||||
public <T> SessionAwareMethodInterceptor(ClientSession session, T target, Class<? extends ClientSession> sessionType,
|
||||
Class<D> databaseType, ClientSessionOperator<D> databaseDecorator, Class<C> collectionType,
|
||||
ClientSessionOperator<C> collectionDecorator) {
|
||||
|
||||
Assert.notNull(session, "ClientSession must not be null!");
|
||||
Assert.notNull(target, "Target must not be null!");
|
||||
Assert.notNull(sessionType, "SessionType must not be null!");
|
||||
Assert.notNull(databaseType, "Database type must not be null!");
|
||||
Assert.notNull(databaseDecorator, "Database ClientSessionOperator must not be null!");
|
||||
Assert.notNull(collectionType, "Collection type must not be null!");
|
||||
@@ -90,6 +92,7 @@ public class SessionAwareMethodInterceptor<D, C> implements MethodInterceptor {
|
||||
this.databaseDecorator = databaseDecorator;
|
||||
|
||||
this.targetType = ClassUtils.isAssignable(databaseType, target.getClass()) ? databaseType : collectionType;
|
||||
this.sessionType = sessionType;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -114,7 +117,7 @@ public class SessionAwareMethodInterceptor<D, C> implements MethodInterceptor {
|
||||
return methodInvocation.proceed();
|
||||
}
|
||||
|
||||
Optional<Method> targetMethod = METHOD_CACHE.lookup(methodInvocation.getMethod(), targetType);
|
||||
Optional<Method> targetMethod = METHOD_CACHE.lookup(methodInvocation.getMethod(), targetType, sessionType);
|
||||
|
||||
return !targetMethod.isPresent() ? methodInvocation.proceed()
|
||||
: ReflectionUtils.invokeMethod(targetMethod.get(), target,
|
||||
@@ -171,18 +174,19 @@ public class SessionAwareMethodInterceptor<D, C> implements MethodInterceptor {
|
||||
* @param targetClass
|
||||
* @return
|
||||
*/
|
||||
Optional<Method> lookup(Method method, Class<?> targetClass) {
|
||||
Optional<Method> lookup(Method method, Class<?> targetClass, Class<? extends ClientSession> sessionType) {
|
||||
|
||||
return cache.computeIfAbsent(new MethodClassKey(method, targetClass),
|
||||
val -> Optional.ofNullable(findTargetWithSession(method, targetClass)));
|
||||
val -> Optional.ofNullable(findTargetWithSession(method, targetClass, sessionType)));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Method findTargetWithSession(Method sourceMethod, Class<?> targetType) {
|
||||
private Method findTargetWithSession(Method sourceMethod, Class<?> targetType,
|
||||
Class<? extends ClientSession> sessionType) {
|
||||
|
||||
Class<?>[] argTypes = sourceMethod.getParameterTypes();
|
||||
Class<?>[] args = new Class<?>[argTypes.length + 1];
|
||||
args[0] = ClientSession.class;
|
||||
args[0] = sessionType;
|
||||
System.arraycopy(argTypes, 0, args, 1, argTypes.length);
|
||||
|
||||
return ReflectionUtils.findMethod(targetType, sourceMethod.getName(), args);
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2018 the original author 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;
|
||||
|
||||
/**
|
||||
* {@link SessionSynchronization} is used along with {@link org.springframework.data.mongodb.core.MongoTemplate} to
|
||||
* define in which type of transactions to participate if any.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
* @since 2.1
|
||||
*/
|
||||
public enum SessionSynchronization {
|
||||
|
||||
/**
|
||||
* Synchronize with any transaction even with empty transactions and initiate a MongoDB transaction when doing so by
|
||||
* registering a MongoDB specific {@link org.springframework.transaction.support.ResourceHolderSynchronization}.
|
||||
*/
|
||||
ALWAYS,
|
||||
|
||||
/**
|
||||
* Synchronize with native MongoDB transactions initiated via {@link MongoTransactionManager}.
|
||||
*/
|
||||
ON_ACTUAL_TRANSACTION;
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* Copyright 2018 the original author 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.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.mongodb.MongoDbFactory;
|
||||
import org.springframework.data.mongodb.core.MongoTemplate;
|
||||
import org.springframework.data.mongodb.core.SimpleMongoClientDbFactory;
|
||||
import org.springframework.data.mongodb.core.SimpleMongoDbFactory;
|
||||
import org.springframework.data.mongodb.core.convert.DbRefResolver;
|
||||
import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver;
|
||||
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
|
||||
import org.springframework.data.mongodb.core.mapping.Document;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
import com.mongodb.client.MongoClient;
|
||||
|
||||
/**
|
||||
* Base class for Spring Data MongoDB configuration using JavaConfig with {@link com.mongodb.client.MongoClient}.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.1
|
||||
* @see MongoConfigurationSupport
|
||||
* @see AbstractMongoConfiguration
|
||||
*/
|
||||
@Configuration
|
||||
public abstract class AbstractMongoClientConfiguration extends MongoConfigurationSupport {
|
||||
|
||||
/**
|
||||
* Return the {@link MongoClient} instance to connect to. Annotate with {@link Bean} in case you want to expose a
|
||||
* {@link MongoClient} instance to the {@link org.springframework.context.ApplicationContext}.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public abstract MongoClient mongoClient();
|
||||
|
||||
/**
|
||||
* Creates a {@link MongoTemplate}.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Bean
|
||||
public MongoTemplate mongoTemplate() throws Exception {
|
||||
return new MongoTemplate(mongoDbFactory(), mappingMongoConverter());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link SimpleMongoDbFactory} to be used by the {@link MongoTemplate}. Will use the {@link MongoClient}
|
||||
* instance configured in {@link #mongoClient()}.
|
||||
*
|
||||
* @see #mongoClient()
|
||||
* @see #mongoTemplate()
|
||||
* @return
|
||||
*/
|
||||
@Bean
|
||||
public MongoDbFactory mongoDbFactory() {
|
||||
return new SimpleMongoClientDbFactory(mongoClient(), getDatabaseName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the base package to scan for mapped {@link Document}s. Will return the package name of the configuration
|
||||
* class' (the concrete class, not this one here) by default. So if you have a {@code com.acme.AppConfig} extending
|
||||
* {@link AbstractMongoClientConfiguration} the base package will be considered {@code com.acme} unless the method is
|
||||
* overridden to implement alternate behavior.
|
||||
*
|
||||
* @return the base package to scan for mapped {@link Document} classes or {@literal null} to not enable scanning for
|
||||
* entities.
|
||||
* @deprecated use {@link #getMappingBasePackages()} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
@Nullable
|
||||
protected String getMappingBasePackage() {
|
||||
|
||||
Package mappingBasePackage = getClass().getPackage();
|
||||
return mappingBasePackage == null ? null : mappingBasePackage.getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link MappingMongoConverter} using the configured {@link #mongoDbFactory()} and
|
||||
* {@link #mongoMappingContext()}. Will get {@link #customConversions()} applied.
|
||||
*
|
||||
* @see #customConversions()
|
||||
* @see #mongoMappingContext()
|
||||
* @see #mongoDbFactory()
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
@Bean
|
||||
public MappingMongoConverter mappingMongoConverter() throws Exception {
|
||||
|
||||
DbRefResolver dbRefResolver = new DefaultDbRefResolver(mongoDbFactory());
|
||||
MappingMongoConverter converter = new MappingMongoConverter(dbRefResolver, mongoMappingContext());
|
||||
converter.setCustomConversions(customConversions());
|
||||
|
||||
return converter;
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,10 @@ import org.springframework.lang.Nullable;
|
||||
import com.mongodb.MongoClient;
|
||||
|
||||
/**
|
||||
* Base class for Spring Data MongoDB configuration using JavaConfig.
|
||||
* Base class for Spring Data MongoDB configuration using JavaConfig with {@link com.mongodb.MongoClient}.
|
||||
* <p />
|
||||
* <strong>INFO:</strong>In case you want to use {@link com.mongodb.client.MongoClients} for configuration please refer
|
||||
* to {@link AbstractMongoClientConfiguration}.
|
||||
*
|
||||
* @author Mark Pollack
|
||||
* @author Oliver Gierke
|
||||
@@ -38,10 +41,10 @@ import com.mongodb.MongoClient;
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
* @see MongoConfigurationSupport
|
||||
* @see AbstractMongoClientConfiguration
|
||||
*/
|
||||
@Configuration
|
||||
public abstract class
|
||||
AbstractMongoConfiguration extends MongoConfigurationSupport {
|
||||
public abstract class AbstractMongoConfiguration extends MongoConfigurationSupport {
|
||||
|
||||
/**
|
||||
* Return the {@link MongoClient} instance to connect to. Annotate with {@link Bean} in case you want to expose a
|
||||
@@ -63,7 +66,7 @@ AbstractMongoConfiguration extends MongoConfigurationSupport {
|
||||
|
||||
/**
|
||||
* Creates a {@link SimpleMongoDbFactory} to be used by the {@link MongoTemplate}. Will use the {@link MongoClient}
|
||||
* instance configured in {@link #mongo()}.
|
||||
* instance configured in {@link #mongoClient()}.
|
||||
*
|
||||
* @see #mongoClient()
|
||||
* @see #mongoTemplate()
|
||||
@@ -111,4 +114,5 @@ AbstractMongoConfiguration extends MongoConfigurationSupport {
|
||||
|
||||
return converter;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import org.springframework.data.mongodb.core.ReactiveMongoOperations;
|
||||
import org.springframework.data.mongodb.core.ReactiveMongoTemplate;
|
||||
import org.springframework.data.mongodb.core.SimpleReactiveMongoDatabaseFactory;
|
||||
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
|
||||
import org.springframework.data.mongodb.core.convert.NoOpDbRefResolver;
|
||||
|
||||
import com.mongodb.reactivestreams.client.MongoClient;
|
||||
|
||||
@@ -80,8 +81,7 @@ public abstract class AbstractReactiveMongoConfiguration extends MongoConfigurat
|
||||
@Bean
|
||||
public MappingMongoConverter mappingMongoConverter() throws Exception {
|
||||
|
||||
MappingMongoConverter converter = new MappingMongoConverter(ReactiveMongoTemplate.NO_OP_REF_RESOLVER,
|
||||
mongoMappingContext());
|
||||
MappingMongoConverter converter = new MappingMongoConverter(NoOpDbRefResolver.INSTANCE, mongoMappingContext());
|
||||
converter.setCustomConversions(customConversions());
|
||||
|
||||
return converter;
|
||||
|
||||
@@ -60,6 +60,7 @@ import org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexCre
|
||||
import org.springframework.data.mongodb.core.mapping.Document;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
|
||||
import org.springframework.data.mongodb.core.mapping.event.ValidatingMongoEventListener;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
@@ -75,6 +76,7 @@ import org.w3c.dom.Element;
|
||||
* @author Thomas Darimont
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
* @author Zied Yaich
|
||||
*/
|
||||
public class MappingMongoConverterParser implements BeanDefinitionParser {
|
||||
|
||||
@@ -159,6 +161,7 @@ public class MappingMongoConverterParser implements BeanDefinitionParser {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private BeanDefinition potentiallyCreateValidatingMongoEventListener(Element element, ParserContext parserContext) {
|
||||
|
||||
String disableValidation = element.getAttribute("disable-validation");
|
||||
@@ -180,6 +183,7 @@ public class MappingMongoConverterParser implements BeanDefinitionParser {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private RuntimeBeanReference getValidator(Object source, ParserContext parserContext) {
|
||||
|
||||
if (!JSR_303_PRESENT) {
|
||||
@@ -197,7 +201,7 @@ public class MappingMongoConverterParser implements BeanDefinitionParser {
|
||||
}
|
||||
|
||||
public static String potentiallyCreateMappingContext(Element element, ParserContext parserContext,
|
||||
BeanDefinition conversionsDefinition, String converterId) {
|
||||
@Nullable BeanDefinition conversionsDefinition, @Nullable String converterId) {
|
||||
|
||||
String ctxRef = element.getAttribute("mapping-context-ref");
|
||||
|
||||
@@ -211,7 +215,7 @@ public class MappingMongoConverterParser implements BeanDefinitionParser {
|
||||
BeanDefinitionBuilder mappingContextBuilder = BeanDefinitionBuilder
|
||||
.genericBeanDefinition(MongoMappingContext.class);
|
||||
|
||||
Set<String> classesToAdd = getInititalEntityClasses(element);
|
||||
Set<String> classesToAdd = getInitialEntityClasses(element);
|
||||
|
||||
if (classesToAdd != null) {
|
||||
mappingContextBuilder.addPropertyValue("initialEntitySet", classesToAdd);
|
||||
@@ -262,6 +266,7 @@ public class MappingMongoConverterParser implements BeanDefinitionParser {
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private BeanDefinition getCustomConversions(Element element, ParserContext parserContext) {
|
||||
|
||||
List<Element> customConvertersElements = DomUtils.getChildElementsByTagName(element, "custom-converters");
|
||||
@@ -269,7 +274,7 @@ public class MappingMongoConverterParser implements BeanDefinitionParser {
|
||||
if (customConvertersElements.size() == 1) {
|
||||
|
||||
Element customerConvertersElement = customConvertersElements.get(0);
|
||||
ManagedList<BeanMetadataElement> converterBeans = new ManagedList<BeanMetadataElement>();
|
||||
ManagedList<BeanMetadataElement> converterBeans = new ManagedList<>();
|
||||
List<Element> converterElements = DomUtils.getChildElementsByTagName(customerConvertersElement, "converter");
|
||||
|
||||
if (converterElements != null) {
|
||||
@@ -285,9 +290,7 @@ public class MappingMongoConverterParser implements BeanDefinitionParser {
|
||||
provider.addExcludeFilter(new NegatingFilter(new AssignableTypeFilter(Converter.class),
|
||||
new AssignableTypeFilter(GenericConverter.class)));
|
||||
|
||||
for (BeanDefinition candidate : provider.findCandidateComponents(packageToScan)) {
|
||||
converterBeans.add(candidate);
|
||||
}
|
||||
converterBeans.addAll(provider.findCandidateComponents(packageToScan));
|
||||
}
|
||||
|
||||
BeanDefinitionBuilder conversionsBuilder = BeanDefinitionBuilder.rootBeanDefinition(MongoCustomConversions.class);
|
||||
@@ -304,7 +307,8 @@ public class MappingMongoConverterParser implements BeanDefinitionParser {
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Set<String> getInititalEntityClasses(Element element) {
|
||||
@Nullable
|
||||
private static Set<String> getInitialEntityClasses(Element element) {
|
||||
|
||||
String basePackage = element.getAttribute(BASE_PACKAGE);
|
||||
|
||||
@@ -317,7 +321,7 @@ public class MappingMongoConverterParser implements BeanDefinitionParser {
|
||||
componentProvider.addIncludeFilter(new AnnotationTypeFilter(Document.class));
|
||||
componentProvider.addIncludeFilter(new AnnotationTypeFilter(Persistent.class));
|
||||
|
||||
Set<String> classes = new ManagedSet<String>();
|
||||
Set<String> classes = new ManagedSet<>();
|
||||
for (BeanDefinition candidate : componentProvider.findCandidateComponents(basePackage)) {
|
||||
classes.add(candidate.getBeanClassName());
|
||||
}
|
||||
@@ -325,6 +329,7 @@ public class MappingMongoConverterParser implements BeanDefinitionParser {
|
||||
return classes;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public BeanMetadataElement parseConverter(Element element, ParserContext parserContext) {
|
||||
|
||||
String converterRef = element.getAttribute("ref");
|
||||
@@ -375,7 +380,7 @@ public class MappingMongoConverterParser implements BeanDefinitionParser {
|
||||
|
||||
Assert.notNull(filters, "TypeFilters must not be null");
|
||||
|
||||
this.delegates = new HashSet<TypeFilter>(Arrays.asList(filters));
|
||||
this.delegates = new HashSet<>(Arrays.asList(filters));
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -35,6 +35,8 @@ import com.mongodb.MongoCredential;
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author Oliver Gierke
|
||||
* @author Stephen Tyler Conrad
|
||||
* @author Mark Paluch
|
||||
* @since 1.7
|
||||
*/
|
||||
public class MongoCredentialPropertyEditor extends PropertyEditorSupport {
|
||||
@@ -98,6 +100,12 @@ public class MongoCredentialPropertyEditor extends PropertyEditorSupport {
|
||||
verifyDatabasePresent(database);
|
||||
credentials.add(MongoCredential.createScramSha1Credential(userNameAndPassword[0], database,
|
||||
userNameAndPassword[1].toCharArray()));
|
||||
} else if (MongoCredential.SCRAM_SHA_256_MECHANISM.equals(authMechanism)) {
|
||||
|
||||
verifyUsernameAndPasswordPresent(userNameAndPassword);
|
||||
verifyDatabasePresent(database);
|
||||
credentials.add(MongoCredential.createScramSha256Credential(userNameAndPassword[0], database,
|
||||
userNameAndPassword[1].toCharArray()));
|
||||
} else {
|
||||
throw new IllegalArgumentException(
|
||||
String.format("Cannot create MongoCredentials for unknown auth mechanism '%s'!", authMechanism));
|
||||
@@ -164,7 +172,7 @@ public class MongoCredentialPropertyEditor extends PropertyEditorSupport {
|
||||
private static Properties extractOptions(String text) {
|
||||
|
||||
int optionsSeparationIndex = text.lastIndexOf(OPTIONS_DELIMITER);
|
||||
int dbSeparationIndex = text.lastIndexOf(OPTIONS_DELIMITER);
|
||||
int dbSeparationIndex = text.lastIndexOf(DATABASE_DELIMITER);
|
||||
|
||||
if (optionsSeparationIndex == -1 || dbSeparationIndex > optionsSeparationIndex) {
|
||||
return new Properties();
|
||||
@@ -173,7 +181,13 @@ public class MongoCredentialPropertyEditor extends PropertyEditorSupport {
|
||||
Properties properties = new Properties();
|
||||
|
||||
for (String option : text.substring(optionsSeparationIndex + 1).split(OPTION_VALUE_DELIMITER)) {
|
||||
|
||||
String[] optionArgs = option.split("=");
|
||||
|
||||
if (optionArgs.length == 1) {
|
||||
throw new IllegalArgumentException(String.format("Query parameter '%s' has no value!", optionArgs[0]));
|
||||
}
|
||||
|
||||
properties.put(optionArgs[0], optionArgs[1]);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
/*
|
||||
* Copyright 2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.mongodb.core;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.bson.Document;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.mongodb.core.aggregation.Aggregation;
|
||||
import org.springframework.data.mongodb.core.aggregation.AggregationOperation;
|
||||
import org.springframework.data.mongodb.core.aggregation.AggregationOperationContext;
|
||||
import org.springframework.data.mongodb.core.aggregation.AggregationOptions;
|
||||
import org.springframework.data.mongodb.core.aggregation.CountOperation;
|
||||
import org.springframework.data.mongodb.core.aggregation.TypeBasedAggregationOperationContext;
|
||||
import org.springframework.data.mongodb.core.aggregation.TypedAggregation;
|
||||
import org.springframework.data.mongodb.core.convert.QueryMapper;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
|
||||
import org.springframework.data.mongodb.core.query.CriteriaDefinition;
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
/**
|
||||
* Utility methods to map {@link org.springframework.data.mongodb.core.aggregation.Aggregation} pipeline definitions and
|
||||
* create type-bound {@link AggregationOperationContext}.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
* @since 2.1
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
class AggregationUtil {
|
||||
|
||||
QueryMapper queryMapper;
|
||||
MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext;
|
||||
|
||||
/**
|
||||
* Prepare the {@link AggregationOperationContext} for a given aggregation by either returning the context itself it
|
||||
* is not {@literal null}, create a {@link TypeBasedAggregationOperationContext} if the aggregation contains type
|
||||
* information (is a {@link TypedAggregation}) or use the {@link Aggregation#DEFAULT_CONTEXT}.
|
||||
*
|
||||
* @param aggregation must not be {@literal null}.
|
||||
* @param context can be {@literal null}.
|
||||
* @return the root {@link AggregationOperationContext} to use.
|
||||
*/
|
||||
AggregationOperationContext prepareAggregationContext(Aggregation aggregation,
|
||||
@Nullable AggregationOperationContext context) {
|
||||
|
||||
if (context != null) {
|
||||
return context;
|
||||
}
|
||||
|
||||
if (aggregation instanceof TypedAggregation) {
|
||||
return new TypeBasedAggregationOperationContext(((TypedAggregation) aggregation).getInputType(), mappingContext,
|
||||
queryMapper);
|
||||
}
|
||||
|
||||
return Aggregation.DEFAULT_CONTEXT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract and map the aggregation pipeline into a {@link List} of {@link Document}.
|
||||
*
|
||||
* @param aggregation
|
||||
* @param context
|
||||
* @return
|
||||
*/
|
||||
List<Document> createPipeline(Aggregation aggregation, AggregationOperationContext context) {
|
||||
|
||||
if (!ObjectUtils.nullSafeEquals(context, Aggregation.DEFAULT_CONTEXT)) {
|
||||
return aggregation.toPipeline(context);
|
||||
}
|
||||
|
||||
return mapAggregationPipeline(aggregation.toPipeline(context));
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the command and map the aggregation pipeline.
|
||||
*
|
||||
* @param aggregation
|
||||
* @param context
|
||||
* @return
|
||||
*/
|
||||
Document createCommand(String collection, Aggregation aggregation, AggregationOperationContext context) {
|
||||
|
||||
Document command = aggregation.toDocument(collection, context);
|
||||
|
||||
if (!ObjectUtils.nullSafeEquals(context, Aggregation.DEFAULT_CONTEXT)) {
|
||||
return command;
|
||||
}
|
||||
|
||||
command.put("pipeline", mapAggregationPipeline(command.get("pipeline", List.class)));
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@code $count} aggregation for {@link Query} and optionally a {@link Class entity class}.
|
||||
*
|
||||
* @param query must not be {@literal null}.
|
||||
* @param entityClass can be {@literal null} if the {@link Query} object is empty.
|
||||
* @return the {@link Aggregation} pipeline definition to run a {@code $count} aggregation.
|
||||
*/
|
||||
Aggregation createCountAggregation(Query query, @Nullable Class<?> entityClass) {
|
||||
|
||||
List<AggregationOperation> pipeline = computeCountAggregationPipeline(query, entityClass);
|
||||
|
||||
Aggregation aggregation = entityClass != null ? Aggregation.newAggregation(entityClass, pipeline)
|
||||
: Aggregation.newAggregation(pipeline);
|
||||
aggregation.withOptions(AggregationOptions.builder().collation(query.getCollation().orElse(null)).build());
|
||||
|
||||
return aggregation;
|
||||
}
|
||||
|
||||
private List<AggregationOperation> computeCountAggregationPipeline(Query query, @Nullable Class<?> entityType) {
|
||||
|
||||
CountOperation count = Aggregation.count().as("totalEntityCount");
|
||||
if (query.getQueryObject().isEmpty()) {
|
||||
return Collections.singletonList(count);
|
||||
}
|
||||
|
||||
Assert.notNull(entityType, "Entity type must not be null!");
|
||||
|
||||
Document mappedQuery = queryMapper.getMappedObject(query.getQueryObject(),
|
||||
mappingContext.getPersistentEntity(entityType));
|
||||
|
||||
CriteriaDefinition criteria = new CriteriaDefinition() {
|
||||
|
||||
@Override
|
||||
public Document getCriteriaObject() {
|
||||
return mappedQuery;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String getKey() {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
return Arrays.asList(Aggregation.match(criteria), count);
|
||||
}
|
||||
|
||||
private List<Document> mapAggregationPipeline(List<Document> pipeline) {
|
||||
|
||||
return pipeline.stream().map(val -> queryMapper.getMappedObject(val, Optional.empty()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
@@ -17,8 +17,11 @@ package org.springframework.data.mongodb.core;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.time.Instant;
|
||||
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
|
||||
|
||||
import org.bson.BsonTimestamp;
|
||||
import org.bson.BsonValue;
|
||||
import org.bson.Document;
|
||||
import org.springframework.data.mongodb.core.convert.MongoConverter;
|
||||
import org.springframework.data.mongodb.core.messaging.Message;
|
||||
@@ -26,6 +29,7 @@ import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
import com.mongodb.client.model.changestream.ChangeStreamDocument;
|
||||
import com.mongodb.client.model.changestream.OperationType;
|
||||
|
||||
/**
|
||||
* {@link Message} implementation specific to MongoDB <a href="https://docs.mongodb.com/manual/changeStreams/">Change
|
||||
@@ -38,11 +42,17 @@ import com.mongodb.client.model.changestream.ChangeStreamDocument;
|
||||
@EqualsAndHashCode
|
||||
public class ChangeStreamEvent<T> {
|
||||
|
||||
@SuppressWarnings("rawtypes") //
|
||||
private static final AtomicReferenceFieldUpdater<ChangeStreamEvent, Object> CONVERTED_UPDATER = AtomicReferenceFieldUpdater
|
||||
.newUpdater(ChangeStreamEvent.class, Object.class, "converted");
|
||||
|
||||
private final @Nullable ChangeStreamDocument<Document> raw;
|
||||
|
||||
private final Class<T> targetType;
|
||||
private final MongoConverter converter;
|
||||
private final AtomicReference<T> converted = new AtomicReference<>();
|
||||
|
||||
// accessed through CONVERTED_UPDATER.
|
||||
private volatile @Nullable T converted;
|
||||
|
||||
/**
|
||||
* @param raw can be {@literal null}.
|
||||
@@ -67,6 +77,69 @@ public class ChangeStreamEvent<T> {
|
||||
return raw;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link ChangeStreamDocument#getClusterTime() cluster time} as {@link Instant} the event was emitted at.
|
||||
*
|
||||
* @return can be {@literal null}.
|
||||
*/
|
||||
@Nullable
|
||||
public Instant getTimestamp() {
|
||||
|
||||
return getBsonTimestamp() != null ? converter.getConversionService().convert(raw.getClusterTime(), Instant.class)
|
||||
: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link ChangeStreamDocument#getClusterTime() cluster time}.
|
||||
*
|
||||
* @return can be {@literal null}.
|
||||
* @since 2.2
|
||||
*/
|
||||
@Nullable
|
||||
public BsonTimestamp getBsonTimestamp() {
|
||||
return raw != null ? raw.getClusterTime() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link ChangeStreamDocument#getResumeToken() resume token} for this event.
|
||||
*
|
||||
* @return can be {@literal null}.
|
||||
*/
|
||||
@Nullable
|
||||
public BsonValue getResumeToken() {
|
||||
return raw != null ? raw.getResumeToken() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link ChangeStreamDocument#getOperationType() operation type} for this event.
|
||||
*
|
||||
* @return can be {@literal null}.
|
||||
*/
|
||||
@Nullable
|
||||
public OperationType getOperationType() {
|
||||
return raw != null ? raw.getOperationType() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the database name the event was originated at.
|
||||
*
|
||||
* @return can be {@literal null}.
|
||||
*/
|
||||
@Nullable
|
||||
public String getDatabaseName() {
|
||||
return raw != null ? raw.getNamespace().getDatabaseName() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the collection name the event was originated at.
|
||||
*
|
||||
* @return can be {@literal null}.
|
||||
*/
|
||||
@Nullable
|
||||
public String getCollectionName() {
|
||||
return raw != null ? raw.getNamespace().getCollectionName() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the potentially converted {@link ChangeStreamDocument#getFullDocument()}.
|
||||
*
|
||||
@@ -80,36 +153,48 @@ public class ChangeStreamEvent<T> {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (raw.getFullDocument() == null) {
|
||||
return targetType.cast(raw.getFullDocument());
|
||||
Document fullDocument = raw.getFullDocument();
|
||||
|
||||
if (fullDocument == null) {
|
||||
return targetType.cast(fullDocument);
|
||||
}
|
||||
|
||||
return getConverted();
|
||||
return getConverted(fullDocument);
|
||||
}
|
||||
|
||||
private T getConverted() {
|
||||
@SuppressWarnings("unchecked")
|
||||
private T getConverted(Document fullDocument) {
|
||||
return (T) doGetConverted(fullDocument);
|
||||
}
|
||||
|
||||
private Object doGetConverted(Document fullDocument) {
|
||||
|
||||
Object result = CONVERTED_UPDATER.get(this);
|
||||
|
||||
T result = converted.get();
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (ClassUtils.isAssignable(Document.class, raw.getFullDocument().getClass())) {
|
||||
if (ClassUtils.isAssignable(Document.class, fullDocument.getClass())) {
|
||||
|
||||
result = converter.read(targetType, raw.getFullDocument());
|
||||
return converted.compareAndSet(null, result) ? result : converted.get();
|
||||
result = converter.read(targetType, fullDocument);
|
||||
return CONVERTED_UPDATER.compareAndSet(this, null, result) ? result : CONVERTED_UPDATER.get(this);
|
||||
}
|
||||
|
||||
if (converter.getConversionService().canConvert(raw.getFullDocument().getClass(), targetType)) {
|
||||
if (converter.getConversionService().canConvert(fullDocument.getClass(), targetType)) {
|
||||
|
||||
result = converter.getConversionService().convert(raw.getFullDocument(), targetType);
|
||||
return converted.compareAndSet(null, result) ? result : converted.get();
|
||||
result = converter.getConversionService().convert(fullDocument, targetType);
|
||||
return CONVERTED_UPDATER.compareAndSet(this, null, result) ? result : CONVERTED_UPDATER.get(this);
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException(String.format("No converter found capable of converting %s to %s",
|
||||
raw.getFullDocument().getClass(), targetType));
|
||||
fullDocument.getClass(), targetType));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ChangeStreamEvent {" + "raw=" + raw + ", targetType=" + targetType + '}';
|
||||
|
||||
@@ -17,15 +17,19 @@ package org.springframework.data.mongodb.core;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Arrays;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.bson.BsonTimestamp;
|
||||
import org.bson.BsonValue;
|
||||
import org.bson.Document;
|
||||
import org.springframework.data.mongodb.core.aggregation.Aggregation;
|
||||
import org.springframework.data.mongodb.core.query.Collation;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
import com.mongodb.client.model.changestream.ChangeStreamDocument;
|
||||
import com.mongodb.client.model.changestream.FullDocument;
|
||||
@@ -46,6 +50,7 @@ public class ChangeStreamOptions {
|
||||
private @Nullable BsonValue resumeToken;
|
||||
private @Nullable FullDocument fullDocumentLookup;
|
||||
private @Nullable Collation collation;
|
||||
private @Nullable Object resumeTimestamp;
|
||||
|
||||
protected ChangeStreamOptions() {}
|
||||
|
||||
@@ -77,6 +82,21 @@ public class ChangeStreamOptions {
|
||||
return Optional.ofNullable(collation);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@link Optional#empty()} if not set.
|
||||
*/
|
||||
public Optional<Instant> getResumeTimestamp() {
|
||||
return Optional.ofNullable(resumeTimestamp).map(timestamp -> asTimestampOfType(timestamp, Instant.class));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@link Optional#empty()} if not set.
|
||||
* @since 2.2
|
||||
*/
|
||||
public Optional<BsonTimestamp> getResumeBsonTimestamp() {
|
||||
return Optional.ofNullable(resumeTimestamp).map(timestamp -> asTimestampOfType(timestamp, BsonTimestamp.class));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return empty {@link ChangeStreamOptions}.
|
||||
*/
|
||||
@@ -94,6 +114,29 @@ public class ChangeStreamOptions {
|
||||
return new ChangeStreamOptionsBuilder();
|
||||
}
|
||||
|
||||
private static <T> T asTimestampOfType(Object timestamp, Class<T> targetType) {
|
||||
return targetType.cast(doGetTimestamp(timestamp, targetType));
|
||||
}
|
||||
|
||||
private static <T> Object doGetTimestamp(Object timestamp, Class<T> targetType) {
|
||||
|
||||
if (ClassUtils.isAssignableValue(targetType, timestamp)) {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
if (timestamp instanceof Instant) {
|
||||
return new BsonTimestamp((int) ((Instant) timestamp).getEpochSecond(), 0);
|
||||
}
|
||||
|
||||
if (timestamp instanceof BsonTimestamp) {
|
||||
return Instant.ofEpochSecond(((BsonTimestamp) timestamp).getTime());
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException(
|
||||
"o_O that should actually not happen. The timestamp should be an Instant or a BsonTimestamp but was "
|
||||
+ ObjectUtils.nullSafeClassName(timestamp));
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder for creating {@link ChangeStreamOptions}.
|
||||
*
|
||||
@@ -106,6 +149,7 @@ public class ChangeStreamOptions {
|
||||
private @Nullable BsonValue resumeToken;
|
||||
private @Nullable FullDocument fullDocumentLookup;
|
||||
private @Nullable Collation collation;
|
||||
private @Nullable Object resumeTimestamp;
|
||||
|
||||
private ChangeStreamOptionsBuilder() {}
|
||||
|
||||
@@ -200,6 +244,35 @@ public class ChangeStreamOptions {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the cluster time to resume from.
|
||||
*
|
||||
* @param resumeTimestamp must not be {@literal null}.
|
||||
* @return this.
|
||||
*/
|
||||
public ChangeStreamOptionsBuilder resumeAt(Instant resumeTimestamp) {
|
||||
|
||||
Assert.notNull(resumeTimestamp, "ResumeTimestamp must not be null!");
|
||||
|
||||
this.resumeTimestamp = resumeTimestamp;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the cluster time to resume from.
|
||||
*
|
||||
* @param resumeTimestamp must not be {@literal null}.
|
||||
* @return this.
|
||||
* @since 2.2
|
||||
*/
|
||||
public ChangeStreamOptionsBuilder resumeAt(BsonTimestamp resumeTimestamp) {
|
||||
|
||||
Assert.notNull(resumeTimestamp, "ResumeTimestamp must not be null!");
|
||||
|
||||
this.resumeTimestamp = resumeTimestamp;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the built {@link ChangeStreamOptions}
|
||||
*/
|
||||
@@ -211,6 +284,7 @@ public class ChangeStreamOptions {
|
||||
options.resumeToken = resumeToken;
|
||||
options.fullDocumentLookup = fullDocumentLookup;
|
||||
options.collation = collation;
|
||||
options.resumeTimestamp = resumeTimestamp;
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,683 @@
|
||||
/*
|
||||
* Copyright 2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.mongodb.core;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
import org.bson.Document;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
import org.springframework.data.mapping.IdentifierAccessor;
|
||||
import org.springframework.data.mapping.MappingException;
|
||||
import org.springframework.data.mapping.PersistentPropertyAccessor;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
|
||||
import org.springframework.data.mongodb.core.convert.MongoWriter;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoSimpleTypes;
|
||||
import org.springframework.data.mongodb.core.query.Criteria;
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
|
||||
import com.mongodb.util.JSONParseException;
|
||||
|
||||
/**
|
||||
* Common operations performed on an entity in the context of it's mapping metadata.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Mark Paluch
|
||||
* @since 2.1
|
||||
* @see MongoTemplate
|
||||
* @see ReactiveMongoTemplate
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
class EntityOperations {
|
||||
|
||||
private static final String ID_FIELD = "_id";
|
||||
|
||||
private final @NonNull MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> context;
|
||||
|
||||
/**
|
||||
* Creates a new {@link Entity} for the given bean.
|
||||
*
|
||||
* @param entity must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
public <T> Entity<T> forEntity(T entity) {
|
||||
|
||||
Assert.notNull(entity, "Bean must not be null!");
|
||||
|
||||
if (entity instanceof String) {
|
||||
return new UnmappedEntity(parse(entity.toString()));
|
||||
}
|
||||
|
||||
if (entity instanceof Map) {
|
||||
return new SimpleMappedEntity((Map<String, Object>) entity);
|
||||
}
|
||||
|
||||
return MappedEntity.of(entity, context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link AdaptibleEntity} for the given bean and {@link ConversionService}.
|
||||
*
|
||||
* @param entity must not be {@literal null}.
|
||||
* @param conversionService must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
public <T> AdaptibleEntity<T> forEntity(T entity, ConversionService conversionService) {
|
||||
|
||||
Assert.notNull(entity, "Bean must not be null!");
|
||||
Assert.notNull(conversionService, "ConversionService must not be null!");
|
||||
|
||||
if (entity instanceof String) {
|
||||
return new UnmappedEntity(parse(entity.toString()));
|
||||
}
|
||||
|
||||
if (entity instanceof Map) {
|
||||
return new SimpleMappedEntity((Map<String, Object>) entity);
|
||||
}
|
||||
|
||||
return AdaptibleMappedEntity.of(entity, context, conversionService);
|
||||
}
|
||||
|
||||
public String determineCollectionName(@Nullable Class<?> entityClass) {
|
||||
|
||||
if (entityClass == null) {
|
||||
throw new InvalidDataAccessApiUsageException(
|
||||
"No class parameter provided, entity collection can't be determined!");
|
||||
}
|
||||
|
||||
return context.getRequiredPersistentEntity(entityClass).getCollection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the collection name to be used for the given entity.
|
||||
*
|
||||
* @param obj can be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
@Nullable
|
||||
public String determineEntityCollectionName(@Nullable Object obj) {
|
||||
return null == obj ? null : determineCollectionName(obj.getClass());
|
||||
}
|
||||
|
||||
public Query getByIdInQuery(Collection<?> entities) {
|
||||
|
||||
MultiValueMap<String, Object> byIds = new LinkedMultiValueMap<>();
|
||||
|
||||
entities.stream() //
|
||||
.map(this::forEntity) //
|
||||
.forEach(it -> byIds.add(it.getIdFieldName(), it.getId()));
|
||||
|
||||
Criteria[] criterias = byIds.entrySet().stream() //
|
||||
.map(it -> Criteria.where(it.getKey()).in(it.getValue())) //
|
||||
.toArray(Criteria[]::new);
|
||||
|
||||
return new Query(criterias.length == 1 ? criterias[0] : new Criteria().orOperator(criterias));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the identifier property. Considers mapping information but falls back to the MongoDB default of
|
||||
* {@code _id} if no identifier property can be found.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public String getIdPropertyName(Class<?> type) {
|
||||
|
||||
Assert.notNull(type, "Type must not be null!");
|
||||
|
||||
MongoPersistentEntity<?> persistentEntity = context.getPersistentEntity(type);
|
||||
|
||||
if (persistentEntity != null && persistentEntity.getIdProperty() != null) {
|
||||
return persistentEntity.getRequiredIdProperty().getName();
|
||||
}
|
||||
|
||||
return ID_FIELD;
|
||||
}
|
||||
|
||||
private static Document parse(String source) {
|
||||
|
||||
try {
|
||||
return Document.parse(source);
|
||||
} catch (JSONParseException | org.bson.json.JsonParseException o_O) {
|
||||
throw new MappingException("Could not parse given String to save into a JSON document!", o_O);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A representation of information about an entity.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @since 2.1
|
||||
*/
|
||||
interface Entity<T> {
|
||||
|
||||
/**
|
||||
* Returns the field name of the identifier of the entity.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
String getIdFieldName();
|
||||
|
||||
/**
|
||||
* Returns the identifier of the entity.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
Object getId();
|
||||
|
||||
/**
|
||||
* Returns the {@link Query} to find the entity by its identifier.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
Query getByIdQuery();
|
||||
|
||||
/**
|
||||
* Returns the {@link Query} to find the entity in its current version.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
Query getQueryForVersion();
|
||||
|
||||
/**
|
||||
* Maps the backing entity into a {@link MappedDocument} using the given {@link MongoWriter}.
|
||||
*
|
||||
* @param writer must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
MappedDocument toMappedDocument(MongoWriter<? super T> writer);
|
||||
|
||||
/**
|
||||
* Asserts that the identifier type is updatable in case its not already set.
|
||||
*/
|
||||
default void assertUpdateableIdIfNotSet() {}
|
||||
|
||||
/**
|
||||
* Returns whether the entity is versioned, i.e. if it contains a version property.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
default boolean isVersionedEntity() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the version if the entity has a version property, {@literal null} otherwise.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Nullable
|
||||
Object getVersion();
|
||||
|
||||
/**
|
||||
* Returns the underlying bean.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
T getBean();
|
||||
|
||||
/**
|
||||
* Returns whether the entity is considered to be new.
|
||||
*
|
||||
* @return
|
||||
* @since 2.1.2
|
||||
*/
|
||||
boolean isNew();
|
||||
}
|
||||
|
||||
/**
|
||||
* Information and commands on an entity.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @since 2.1
|
||||
*/
|
||||
interface AdaptibleEntity<T> extends Entity<T> {
|
||||
|
||||
/**
|
||||
* Populates the identifier of the backing entity if it has an identifier property and there's no identifier
|
||||
* currently present.
|
||||
*
|
||||
* @param id must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
@Nullable
|
||||
T populateIdIfNecessary(@Nullable Object id);
|
||||
|
||||
/**
|
||||
* Initializes the version property of the of the current entity if available.
|
||||
*
|
||||
* @return the entity with the version property updated if available.
|
||||
*/
|
||||
T initializeVersionProperty();
|
||||
|
||||
/**
|
||||
* Increments the value of the version property if available.
|
||||
*
|
||||
* @return the entity with the version property incremented if available.
|
||||
*/
|
||||
T incrementVersion();
|
||||
|
||||
/**
|
||||
* Returns the current version value if the entity has a version property.
|
||||
*
|
||||
* @return the current version or {@literal null} in case it's uninitialized or the entity doesn't expose a version
|
||||
* property.
|
||||
*/
|
||||
@Nullable
|
||||
Number getVersion();
|
||||
}
|
||||
|
||||
@RequiredArgsConstructor
|
||||
private static class UnmappedEntity<T extends Map<String, Object>> implements AdaptibleEntity<T> {
|
||||
|
||||
private final T map;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.EntityOperations.PersistableSource#getIdPropertyName()
|
||||
*/
|
||||
@Override
|
||||
public String getIdFieldName() {
|
||||
return ID_FIELD;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.EntityOperations.PersistableSource#getId()
|
||||
*/
|
||||
@Override
|
||||
public Object getId() {
|
||||
return map.get(ID_FIELD);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.EntityOperations.PersistableSource#getByIdQuery()
|
||||
*/
|
||||
@Override
|
||||
public Query getByIdQuery() {
|
||||
return Query.query(Criteria.where(ID_FIELD).is(map.get(ID_FIELD)));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.EntityOperations.MutablePersistableSource#populateIdIfNecessary(java.lang.Object)
|
||||
*/
|
||||
@Nullable
|
||||
@Override
|
||||
public T populateIdIfNecessary(@Nullable Object id) {
|
||||
|
||||
map.put(ID_FIELD, id);
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.EntityOperations.PersistableSource#getQueryForVersion()
|
||||
*/
|
||||
@Override
|
||||
public Query getQueryForVersion() {
|
||||
throw new MappingException("Cannot query for version on plain Documents!");
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.EntityOperations.PersistableSource#toMappedDocument(org.springframework.data.mongodb.core.convert.MongoWriter)
|
||||
*/
|
||||
@Override
|
||||
public MappedDocument toMappedDocument(MongoWriter<? super T> writer) {
|
||||
return MappedDocument.of(map instanceof Document //
|
||||
? (Document) map //
|
||||
: new Document(map));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.EntityOperations.MutablePersistableSource#initializeVersionProperty()
|
||||
*/
|
||||
@Override
|
||||
public T initializeVersionProperty() {
|
||||
return map;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.EntityOperations.MutablePersistableSource#getVersion()
|
||||
*/
|
||||
@Override
|
||||
@Nullable
|
||||
public Number getVersion() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.EntityOperations.MutablePersistableSource#incrementVersion()
|
||||
*/
|
||||
@Override
|
||||
public T incrementVersion() {
|
||||
return map;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.EntityOperations.PersistableSource#getBean()
|
||||
*/
|
||||
@Override
|
||||
public T getBean() {
|
||||
return map;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.EntityOperations.Entity#isNew()
|
||||
*/
|
||||
@Override
|
||||
public boolean isNew() {
|
||||
return map.get(ID_FIELD) != null;
|
||||
}
|
||||
}
|
||||
|
||||
private static class SimpleMappedEntity<T extends Map<String, Object>> extends UnmappedEntity<T> {
|
||||
|
||||
SimpleMappedEntity(T map) {
|
||||
super(map);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.EntityOperations.PersistableSource#toMappedDocument(org.springframework.data.mongodb.core.convert.MongoWriter)
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public MappedDocument toMappedDocument(MongoWriter<? super T> writer) {
|
||||
|
||||
T bean = getBean();
|
||||
bean = (T) (bean instanceof Document //
|
||||
? (Document) bean //
|
||||
: new Document(bean));
|
||||
Document document = new Document();
|
||||
writer.write(bean, document);
|
||||
|
||||
return MappedDocument.of(document);
|
||||
}
|
||||
}
|
||||
|
||||
@RequiredArgsConstructor(access = AccessLevel.PROTECTED)
|
||||
private static class MappedEntity<T> implements Entity<T> {
|
||||
|
||||
private final @NonNull MongoPersistentEntity<?> entity;
|
||||
private final @NonNull IdentifierAccessor idAccessor;
|
||||
private final @NonNull PersistentPropertyAccessor<T> propertyAccessor;
|
||||
|
||||
private static <T> MappedEntity<T> of(T bean,
|
||||
MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> context) {
|
||||
|
||||
MongoPersistentEntity<?> entity = context.getRequiredPersistentEntity(bean.getClass());
|
||||
IdentifierAccessor identifierAccessor = entity.getIdentifierAccessor(bean);
|
||||
PersistentPropertyAccessor<T> propertyAccessor = entity.getPropertyAccessor(bean);
|
||||
|
||||
return new MappedEntity<>(entity, identifierAccessor, propertyAccessor);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.EntityOperations.PersistableSource#getIdPropertyName()
|
||||
*/
|
||||
@Override
|
||||
public String getIdFieldName() {
|
||||
return entity.getRequiredIdProperty().getFieldName();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.EntityOperations.PersistableSource#getId()
|
||||
*/
|
||||
@Override
|
||||
public Object getId() {
|
||||
return idAccessor.getRequiredIdentifier();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.EntityOperations.PersistableSource#getByIdQuery()
|
||||
*/
|
||||
@Override
|
||||
public Query getByIdQuery() {
|
||||
|
||||
if (!entity.hasIdProperty()) {
|
||||
throw new MappingException("No id property found for object of type " + entity.getType() + "!");
|
||||
}
|
||||
|
||||
MongoPersistentProperty idProperty = entity.getRequiredIdProperty();
|
||||
|
||||
return Query.query(Criteria.where(idProperty.getName()).is(getId()));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.EntityOperations.PersistableSource#getQueryForVersion(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public Query getQueryForVersion() {
|
||||
|
||||
MongoPersistentProperty idProperty = entity.getRequiredIdProperty();
|
||||
MongoPersistentProperty property = entity.getRequiredVersionProperty();
|
||||
|
||||
return new Query(Criteria.where(idProperty.getName()).is(getId())//
|
||||
.and(property.getName()).is(getVersion()));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.EntityOperations.PersistableSource#toMappedDocument(org.springframework.data.mongodb.core.convert.MongoWriter)
|
||||
*/
|
||||
@Override
|
||||
public MappedDocument toMappedDocument(MongoWriter<? super T> writer) {
|
||||
|
||||
T bean = propertyAccessor.getBean();
|
||||
|
||||
Document document = new Document();
|
||||
writer.write(bean, document);
|
||||
|
||||
if (document.containsKey(ID_FIELD) && document.get(ID_FIELD) == null) {
|
||||
document.remove(ID_FIELD);
|
||||
}
|
||||
|
||||
return MappedDocument.of(document);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.EntityOperations.Entity#assertUpdateableIdIfNotSet()
|
||||
*/
|
||||
public void assertUpdateableIdIfNotSet() {
|
||||
|
||||
if (!entity.hasIdProperty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
MongoPersistentProperty property = entity.getRequiredIdProperty();
|
||||
Object propertyValue = idAccessor.getIdentifier();
|
||||
|
||||
if (propertyValue != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!MongoSimpleTypes.AUTOGENERATED_ID_TYPES.contains(property.getType())) {
|
||||
throw new InvalidDataAccessApiUsageException(
|
||||
String.format("Cannot autogenerate id of type %s for entity of type %s!", property.getType().getName(),
|
||||
entity.getType().getName()));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.EntityOperations.PersistableSource#isVersionedEntity()
|
||||
*/
|
||||
@Override
|
||||
public boolean isVersionedEntity() {
|
||||
return entity.hasVersionProperty();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.EntityOperations.PersistableSource#getVersion()
|
||||
*/
|
||||
@Override
|
||||
@Nullable
|
||||
public Object getVersion() {
|
||||
return propertyAccessor.getProperty(entity.getRequiredVersionProperty());
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.EntityOperations.PersistableSource#getBean()
|
||||
*/
|
||||
@Override
|
||||
public T getBean() {
|
||||
return propertyAccessor.getBean();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.EntityOperations.Entity#isNew()
|
||||
*/
|
||||
@Override
|
||||
public boolean isNew() {
|
||||
return entity.isNew(propertyAccessor.getBean());
|
||||
}
|
||||
}
|
||||
|
||||
private static class AdaptibleMappedEntity<T> extends MappedEntity<T> implements AdaptibleEntity<T> {
|
||||
|
||||
private final MongoPersistentEntity<?> entity;
|
||||
private final ConvertingPropertyAccessor<T> propertyAccessor;
|
||||
private final IdentifierAccessor identifierAccessor;
|
||||
|
||||
private AdaptibleMappedEntity(MongoPersistentEntity<?> entity, IdentifierAccessor identifierAccessor,
|
||||
ConvertingPropertyAccessor<T> propertyAccessor) {
|
||||
|
||||
super(entity, identifierAccessor, propertyAccessor);
|
||||
|
||||
this.entity = entity;
|
||||
this.propertyAccessor = propertyAccessor;
|
||||
this.identifierAccessor = identifierAccessor;
|
||||
}
|
||||
|
||||
private static <T> AdaptibleEntity<T> of(T bean,
|
||||
MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> context,
|
||||
ConversionService conversionService) {
|
||||
|
||||
MongoPersistentEntity<?> entity = context.getRequiredPersistentEntity(bean.getClass());
|
||||
IdentifierAccessor identifierAccessor = entity.getIdentifierAccessor(bean);
|
||||
PersistentPropertyAccessor<T> propertyAccessor = entity.getPropertyAccessor(bean);
|
||||
|
||||
return new AdaptibleMappedEntity<>(entity, identifierAccessor,
|
||||
new ConvertingPropertyAccessor<>(propertyAccessor, conversionService));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.EntityOperations.AdaptibleEntity#populateIdIfNecessary(java.lang.Object)
|
||||
*/
|
||||
@Nullable
|
||||
@Override
|
||||
public T populateIdIfNecessary(@Nullable Object id) {
|
||||
|
||||
if (id == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
T bean = propertyAccessor.getBean();
|
||||
MongoPersistentProperty idProperty = entity.getIdProperty();
|
||||
|
||||
if (idProperty == null) {
|
||||
return bean;
|
||||
}
|
||||
|
||||
if (identifierAccessor.getIdentifier() != null) {
|
||||
return bean;
|
||||
}
|
||||
|
||||
propertyAccessor.setProperty(idProperty, id);
|
||||
|
||||
return propertyAccessor.getBean();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.EntityOperations.MappedEntity#getVersion()
|
||||
*/
|
||||
@Override
|
||||
@Nullable
|
||||
public Number getVersion() {
|
||||
|
||||
MongoPersistentProperty versionProperty = entity.getRequiredVersionProperty();
|
||||
|
||||
return propertyAccessor.getProperty(versionProperty, Number.class);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.EntityOperations.AdaptibleEntity#initializeVersionProperty()
|
||||
*/
|
||||
@Override
|
||||
public T initializeVersionProperty() {
|
||||
|
||||
if (!entity.hasVersionProperty()) {
|
||||
return propertyAccessor.getBean();
|
||||
}
|
||||
|
||||
MongoPersistentProperty versionProperty = entity.getRequiredVersionProperty();
|
||||
|
||||
propertyAccessor.setProperty(versionProperty, versionProperty.getType().isPrimitive() ? 1 : 0);
|
||||
|
||||
return propertyAccessor.getBean();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.EntityOperations.AdaptibleEntity#incrementVersion()
|
||||
*/
|
||||
@Override
|
||||
public T incrementVersion() {
|
||||
|
||||
MongoPersistentProperty versionProperty = entity.getRequiredVersionProperty();
|
||||
Number version = getVersion();
|
||||
Number nextVersion = version == null ? 0 : version.longValue() + 1;
|
||||
|
||||
propertyAccessor.setProperty(versionProperty, nextVersion);
|
||||
|
||||
return propertyAccessor.getBean();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -35,22 +35,10 @@ import org.springframework.util.StringUtils;
|
||||
* @author Mark Paluch
|
||||
* @since 2.0
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
class ExecutableAggregationOperationSupport implements ExecutableAggregationOperation {
|
||||
|
||||
private final MongoTemplate template;
|
||||
|
||||
/**
|
||||
* Create new instance of {@link ExecutableAggregationOperationSupport}.
|
||||
*
|
||||
* @param template must not be {@literal null}.
|
||||
* @throws IllegalArgumentException if template is {@literal null}.
|
||||
*/
|
||||
ExecutableAggregationOperationSupport(MongoTemplate template) {
|
||||
|
||||
Assert.notNull(template, "Template must not be null!");
|
||||
|
||||
this.template = template;
|
||||
}
|
||||
private final @NonNull MongoTemplate template;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
@@ -131,11 +119,11 @@ class ExecutableAggregationOperationSupport implements ExecutableAggregationOper
|
||||
TypedAggregation<?> typedAggregation = (TypedAggregation<?>) aggregation;
|
||||
|
||||
if (typedAggregation.getInputType() != null) {
|
||||
return template.determineCollectionName(typedAggregation.getInputType());
|
||||
return template.getCollectionName(typedAggregation.getInputType());
|
||||
}
|
||||
}
|
||||
|
||||
return template.determineCollectionName(domainType);
|
||||
return template.getCollectionName(domainType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,24 +45,12 @@ import com.mongodb.client.FindIterable;
|
||||
* @author Mark Paluch
|
||||
* @since 2.0
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
class ExecutableFindOperationSupport implements ExecutableFindOperation {
|
||||
|
||||
private static final Query ALL_QUERY = new Query();
|
||||
|
||||
private final MongoTemplate template;
|
||||
|
||||
/**
|
||||
* Create new {@link ExecutableFindOperationSupport}.
|
||||
*
|
||||
* @param template must not be {@literal null}.
|
||||
* @throws IllegalArgumentException if template is {@literal null}.
|
||||
*/
|
||||
ExecutableFindOperationSupport(MongoTemplate template) {
|
||||
|
||||
Assert.notNull(template, "Template must not be null!");
|
||||
|
||||
this.template = template;
|
||||
}
|
||||
private final @NonNull MongoTemplate template;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
@@ -242,7 +230,7 @@ class ExecutableFindOperationSupport implements ExecutableFindOperation {
|
||||
}
|
||||
|
||||
private String getCollectionName() {
|
||||
return StringUtils.hasText(collection) ? collection : template.determineCollectionName(domainType);
|
||||
return StringUtils.hasText(collection) ? collection : template.getCollectionName(domainType);
|
||||
}
|
||||
|
||||
private String asString() {
|
||||
|
||||
@@ -63,17 +63,19 @@ public interface ExecutableInsertOperation {
|
||||
* Insert exactly one object.
|
||||
*
|
||||
* @param object must not be {@literal null}.
|
||||
* @return the inserted object.
|
||||
* @throws IllegalArgumentException if object is {@literal null}.
|
||||
*/
|
||||
void one(T object);
|
||||
T one(T object);
|
||||
|
||||
/**
|
||||
* Insert a collection of objects.
|
||||
*
|
||||
* @param objects must not be {@literal null}.
|
||||
* @return the inserted objects.
|
||||
* @throws IllegalArgumentException if objects is {@literal null}.
|
||||
*/
|
||||
void all(Collection<? extends T> objects);
|
||||
Collection<? extends T> all(Collection<? extends T> objects);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -37,22 +37,10 @@ import com.mongodb.bulk.BulkWriteResult;
|
||||
* @author Mark Paluch
|
||||
* @since 2.0
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
class ExecutableInsertOperationSupport implements ExecutableInsertOperation {
|
||||
|
||||
private final MongoTemplate template;
|
||||
|
||||
/**
|
||||
* Create new {@link ExecutableInsertOperationSupport}.
|
||||
*
|
||||
* @param template must not be {@literal null}.
|
||||
* @throws IllegalArgumentException if template is {@literal null}.
|
||||
*/
|
||||
ExecutableInsertOperationSupport(MongoTemplate template) {
|
||||
|
||||
Assert.notNull(template, "Template must not be null!");
|
||||
|
||||
this.template = template;
|
||||
}
|
||||
private final @NonNull MongoTemplate template;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
@@ -84,11 +72,11 @@ class ExecutableInsertOperationSupport implements ExecutableInsertOperation {
|
||||
* @see org.springframework.data.mongodb.core.ExecutableInsertOperation.TerminatingInsert#insert(java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public void one(T object) {
|
||||
public T one(T object) {
|
||||
|
||||
Assert.notNull(object, "Object must not be null!");
|
||||
|
||||
template.insert(object, getCollectionName());
|
||||
return template.insert(object, getCollectionName());
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -96,11 +84,11 @@ class ExecutableInsertOperationSupport implements ExecutableInsertOperation {
|
||||
* @see org.springframework.data.mongodb.core.ExecutableInsertOperation.TerminatingInsert#all(java.util.Collection)
|
||||
*/
|
||||
@Override
|
||||
public void all(Collection<? extends T> objects) {
|
||||
public Collection<T> all(Collection<? extends T> objects) {
|
||||
|
||||
Assert.notNull(objects, "Objects must not be null!");
|
||||
|
||||
template.insert(objects, getCollectionName());
|
||||
return template.insert(objects, getCollectionName());
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -141,7 +129,7 @@ class ExecutableInsertOperationSupport implements ExecutableInsertOperation {
|
||||
}
|
||||
|
||||
private String getCollectionName() {
|
||||
return StringUtils.hasText(collection) ? collection : template.determineCollectionName(domainType);
|
||||
return StringUtils.hasText(collection) ? collection : template.getCollectionName(domainType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,199 @@
|
||||
/*
|
||||
* Copyright 2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.mongodb.core;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.mongodb.core.ExecutableFindOperation.ExecutableFind;
|
||||
import org.springframework.data.mongodb.core.mapreduce.MapReduceOptions;
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
|
||||
/**
|
||||
* {@link ExecutableMapReduceOperation} allows creation and execution of MongoDB mapReduce operations in a fluent API
|
||||
* style. The starting {@literal domainType} is used for mapping an optional {@link Query} provided via {@code matching}
|
||||
* into the MongoDB specific representation. By default, the originating {@literal domainType} is also used for mapping
|
||||
* back the results from the {@link org.bson.Document}. However, it is possible to define an different
|
||||
* {@literal returnType} via {@code as} to mapping the result.<br />
|
||||
* The collection to operate on is by default derived from the initial {@literal domainType} and can be defined there
|
||||
* via {@link org.springframework.data.mongodb.core.mapping.Document}. Using {@code inCollection} allows to override the
|
||||
* collection name for the execution.
|
||||
*
|
||||
* <pre>
|
||||
* <code>
|
||||
* mapReduce(Human.class)
|
||||
* .map("function() { emit(this.id, this.firstname) }")
|
||||
* .reduce("function(id, name) { return sum(id, name); }")
|
||||
* .inCollection("star-wars")
|
||||
* .as(Jedi.class)
|
||||
* .matching(query(where("lastname").is("skywalker")))
|
||||
* .all();
|
||||
* </code>
|
||||
* </pre>
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.1
|
||||
*/
|
||||
public interface ExecutableMapReduceOperation {
|
||||
|
||||
/**
|
||||
* Start creating a mapReduce operation for the given {@literal domainType}.
|
||||
*
|
||||
* @param domainType must not be {@literal null}.
|
||||
* @return new instance of {@link ExecutableFind}.
|
||||
* @throws IllegalArgumentException if domainType is {@literal null}.
|
||||
*/
|
||||
<T> MapReduceWithMapFunction<T> mapReduce(Class<T> domainType);
|
||||
|
||||
/**
|
||||
* Trigger mapReduce execution by calling one of the terminating methods.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.1
|
||||
*/
|
||||
interface TerminatingMapReduce<T> {
|
||||
|
||||
/**
|
||||
* Get the mapReduce results.
|
||||
*
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
List<T> all();
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide the Javascript {@code function()} used to map matching documents.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.1
|
||||
*/
|
||||
interface MapReduceWithMapFunction<T> {
|
||||
|
||||
/**
|
||||
* Set the Javascript map {@code function()}.
|
||||
*
|
||||
* @param mapFunction must not be {@literal null} nor empty.
|
||||
* @return new instance of {@link MapReduceWithReduceFunction}.
|
||||
* @throws IllegalArgumentException if {@literal mapFunction} is {@literal null} or empty.
|
||||
*/
|
||||
MapReduceWithReduceFunction<T> map(String mapFunction);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide the Javascript {@code function()} used to reduce matching documents.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.1
|
||||
*/
|
||||
interface MapReduceWithReduceFunction<T> {
|
||||
|
||||
/**
|
||||
* Set the Javascript map {@code function()}.
|
||||
*
|
||||
* @param reduceFunction must not be {@literal null} nor empty.
|
||||
* @return new instance of {@link ExecutableMapReduce}.
|
||||
* @throws IllegalArgumentException if {@literal reduceFunction} is {@literal null} or empty.
|
||||
*/
|
||||
ExecutableMapReduce<T> reduce(String reduceFunction);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Collection override (Optional).
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.1
|
||||
*/
|
||||
interface MapReduceWithCollection<T> extends MapReduceWithQuery<T> {
|
||||
|
||||
/**
|
||||
* Explicitly set the name of the collection to perform the mapReduce operation on. <br />
|
||||
* Skip this step to use the default collection derived from the domain type.
|
||||
*
|
||||
* @param collection must not be {@literal null} nor {@literal empty}.
|
||||
* @return new instance of {@link MapReduceWithProjection}.
|
||||
* @throws IllegalArgumentException if collection is {@literal null}.
|
||||
*/
|
||||
MapReduceWithProjection<T> inCollection(String collection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Input document filter query (Optional).
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.1
|
||||
*/
|
||||
interface MapReduceWithQuery<T> extends TerminatingMapReduce<T> {
|
||||
|
||||
/**
|
||||
* Set the filter query to be used.
|
||||
*
|
||||
* @param query must not be {@literal null}.
|
||||
* @return new instance of {@link TerminatingMapReduce}.
|
||||
* @throws IllegalArgumentException if query is {@literal null}.
|
||||
*/
|
||||
TerminatingMapReduce<T> matching(Query query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Result type override (Optional).
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.1
|
||||
*/
|
||||
interface MapReduceWithProjection<T> extends MapReduceWithQuery<T> {
|
||||
|
||||
/**
|
||||
* Define the target type fields should be mapped to. <br />
|
||||
* Skip this step if you are anyway only interested in the original domain type.
|
||||
*
|
||||
* @param resultType must not be {@literal null}.
|
||||
* @param <R> result type.
|
||||
* @return new instance of {@link TerminatingMapReduce}.
|
||||
* @throws IllegalArgumentException if resultType is {@literal null}.
|
||||
*/
|
||||
<R> MapReduceWithQuery<R> as(Class<R> resultType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Additional mapReduce options (Optional).
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.1
|
||||
*/
|
||||
interface MapReduceWithOptions<T> {
|
||||
|
||||
/**
|
||||
* Set additional options to apply to the mapReduce operation.
|
||||
*
|
||||
* @param options must not be {@literal null}.
|
||||
* @return new instance of {@link ExecutableMapReduce}.
|
||||
* @throws IllegalArgumentException if options is {@literal null}.
|
||||
*/
|
||||
ExecutableMapReduce<T> with(MapReduceOptions options);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link ExecutableMapReduce} provides methods for constructing mapReduce operations in a fluent way.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.1
|
||||
*/
|
||||
interface ExecutableMapReduce<T> extends MapReduceWithMapFunction<T>, MapReduceWithReduceFunction<T>,
|
||||
MapReduceWithCollection<T>, MapReduceWithProjection<T>, MapReduceWithOptions<T> {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
/*
|
||||
* Copyright 2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.mongodb.core;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.mongodb.core.mapreduce.MapReduceOptions;
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Implementation of {@link ExecutableMapReduceOperation}.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.1
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
class ExecutableMapReduceOperationSupport implements ExecutableMapReduceOperation {
|
||||
|
||||
private static final Query ALL_QUERY = new Query();
|
||||
|
||||
private final @NonNull MongoTemplate template;
|
||||
|
||||
/*
|
||||
* (non-Javascript)
|
||||
* @see in org.springframework.data.mongodb.core.ExecutableMapReduceOperation#mapReduce(java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public <T> ExecutableMapReduceSupport<T> mapReduce(Class<T> domainType) {
|
||||
|
||||
Assert.notNull(domainType, "DomainType must not be null!");
|
||||
|
||||
return new ExecutableMapReduceSupport<>(template, domainType, domainType, null, ALL_QUERY, null, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
* @since 2.1
|
||||
*/
|
||||
static class ExecutableMapReduceSupport<T>
|
||||
implements ExecutableMapReduce<T>, MapReduceWithOptions<T>, MapReduceWithCollection<T>,
|
||||
MapReduceWithProjection<T>, MapReduceWithQuery<T>, MapReduceWithReduceFunction<T>, MapReduceWithMapFunction<T> {
|
||||
|
||||
private final MongoTemplate template;
|
||||
private final Class<?> domainType;
|
||||
private final Class<T> returnType;
|
||||
private final @Nullable String collection;
|
||||
private final Query query;
|
||||
private final @Nullable String mapFunction;
|
||||
private final @Nullable String reduceFunction;
|
||||
private final @Nullable MapReduceOptions options;
|
||||
|
||||
ExecutableMapReduceSupport(MongoTemplate template, Class<?> domainType, Class<T> returnType,
|
||||
@Nullable String collection, Query query, @Nullable String mapFunction, @Nullable String reduceFunction,
|
||||
@Nullable MapReduceOptions options) {
|
||||
|
||||
this.template = template;
|
||||
this.domainType = domainType;
|
||||
this.returnType = returnType;
|
||||
this.collection = collection;
|
||||
this.query = query;
|
||||
this.mapFunction = mapFunction;
|
||||
this.reduceFunction = reduceFunction;
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javascript)
|
||||
* @see in org.springframework.data.mongodb.core.ExecutableMapReduceOperation.TerminatingMapReduce#all()
|
||||
*/
|
||||
@Override
|
||||
public List<T> all() {
|
||||
return template.mapReduce(query, domainType, getCollectionName(), mapFunction, reduceFunction, options,
|
||||
returnType);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javascript)
|
||||
* @see in org.springframework.data.mongodb.core.ExecutableMapReduceOperation.MapReduceWithCollection#inCollection(java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public MapReduceWithProjection<T> inCollection(String collection) {
|
||||
|
||||
Assert.hasText(collection, "Collection name must not be null nor empty!");
|
||||
|
||||
return new ExecutableMapReduceSupport<>(template, domainType, returnType, collection, query, mapFunction,
|
||||
reduceFunction, options);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javascript)
|
||||
* @see in org.springframework.data.mongodb.core.ExecutableMapReduceOperation.MapReduceWithQuery#query(org.springframework.data.mongodb.core.query.Query)
|
||||
*/
|
||||
@Override
|
||||
public TerminatingMapReduce<T> matching(Query query) {
|
||||
|
||||
Assert.notNull(query, "Query must not be null!");
|
||||
|
||||
return new ExecutableMapReduceSupport<>(template, domainType, returnType, collection, query, mapFunction,
|
||||
reduceFunction, options);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javascript)
|
||||
* @see in org.springframework.data.mongodb.core.ExecutableMapReduceOperation.MapReduceWithProjection#as(java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public <R> MapReduceWithQuery<R> as(Class<R> resultType) {
|
||||
|
||||
Assert.notNull(resultType, "ResultType must not be null!");
|
||||
|
||||
return new ExecutableMapReduceSupport<>(template, domainType, resultType, collection, query, mapFunction,
|
||||
reduceFunction, options);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javascript)
|
||||
* @see in org.springframework.data.mongodb.core.ExecutableMapReduceOperation.MapReduceWithOptions#with(org.springframework.data.mongodb.core.mapreduce.MapReduceOptions)
|
||||
*/
|
||||
@Override
|
||||
public ExecutableMapReduce<T> with(MapReduceOptions options) {
|
||||
|
||||
Assert.notNull(options, "Options must not be null! Please consider empty MapReduceOptions#options() instead.");
|
||||
|
||||
return new ExecutableMapReduceSupport<>(template, domainType, returnType, collection, query, mapFunction,
|
||||
reduceFunction, options);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javascript)
|
||||
* @see in org.springframework.data.mongodb.core.ExecutableMapReduceOperation.MapReduceWithMapFunction#map(java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public MapReduceWithReduceFunction<T> map(String mapFunction) {
|
||||
|
||||
Assert.hasText(mapFunction, "MapFunction name must not be null nor empty!");
|
||||
|
||||
return new ExecutableMapReduceSupport<>(template, domainType, returnType, collection, query, mapFunction,
|
||||
reduceFunction, options);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javascript)
|
||||
* @see in org.springframework.data.mongodb.core.ExecutableMapReduceOperation.MapReduceWithReduceFunction#reduce(java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public ExecutableMapReduce<T> reduce(String reduceFunction) {
|
||||
|
||||
Assert.hasText(reduceFunction, "ReduceFunction name must not be null nor empty!");
|
||||
|
||||
return new ExecutableMapReduceSupport<>(template, domainType, returnType, collection, query, mapFunction,
|
||||
reduceFunction, options);
|
||||
}
|
||||
|
||||
private String getCollectionName() {
|
||||
return StringUtils.hasText(collection) ? collection : template.getCollectionName(domainType);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -53,6 +53,37 @@ public interface ExecutableRemoveOperation {
|
||||
*/
|
||||
<T> ExecutableRemove<T> remove(Class<T> domainType);
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
* @since 2.0
|
||||
*/
|
||||
interface TerminatingRemove<T> {
|
||||
|
||||
/**
|
||||
* Remove all documents matching.
|
||||
*
|
||||
* @return the {@link DeleteResult}. Never {@literal null}.
|
||||
*/
|
||||
DeleteResult all();
|
||||
|
||||
/**
|
||||
* Remove the first matching document.
|
||||
*
|
||||
* @return the {@link DeleteResult}. Never {@literal null}.
|
||||
*/
|
||||
DeleteResult one();
|
||||
|
||||
/**
|
||||
* Remove and return all matching documents. <br/>
|
||||
* <strong>NOTE</strong> The entire list of documents will be fetched before sending the actual delete commands.
|
||||
* Also, {@link org.springframework.context.ApplicationEvent}s will be published for each and every delete
|
||||
* operation.
|
||||
*
|
||||
* @return empty {@link List} if no match found. Never {@literal null}.
|
||||
*/
|
||||
List<T> findAndRemove();
|
||||
}
|
||||
|
||||
/**
|
||||
* Collection override (optional).
|
||||
*
|
||||
@@ -73,29 +104,6 @@ public interface ExecutableRemoveOperation {
|
||||
RemoveWithQuery<T> inCollection(String collection);
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
* @since 2.0
|
||||
*/
|
||||
interface TerminatingRemove<T> {
|
||||
|
||||
/**
|
||||
* Remove all documents matching.
|
||||
*
|
||||
* @return the {@link DeleteResult}. Never {@literal null}.
|
||||
*/
|
||||
DeleteResult all();
|
||||
|
||||
/**
|
||||
* Remove and return all matching documents. <br/>
|
||||
* <strong>NOTE</strong> The entire list of documents will be fetched before sending the actual delete commands.
|
||||
* Also, {@link org.springframework.context.ApplicationEvent}s will be published for each and every delete
|
||||
* operation.
|
||||
*
|
||||
* @return empty {@link List} if no match found. Never {@literal null}.
|
||||
*/
|
||||
List<T> findAndRemove();
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
|
||||
@@ -36,24 +36,12 @@ import com.mongodb.client.result.DeleteResult;
|
||||
* @author Mark Paluch
|
||||
* @since 2.0
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
class ExecutableRemoveOperationSupport implements ExecutableRemoveOperation {
|
||||
|
||||
private static final Query ALL_QUERY = new Query();
|
||||
|
||||
private final MongoTemplate tempate;
|
||||
|
||||
/**
|
||||
* Create new {@link ExecutableRemoveOperationSupport}.
|
||||
*
|
||||
* @param template must not be {@literal null}.
|
||||
* @throws IllegalArgumentException if template is {@literal null}.
|
||||
*/
|
||||
ExecutableRemoveOperationSupport(MongoTemplate template) {
|
||||
|
||||
Assert.notNull(template, "Template must not be null!");
|
||||
|
||||
this.tempate = template;
|
||||
}
|
||||
private final @NonNull MongoTemplate tempate;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
@@ -110,10 +98,16 @@ class ExecutableRemoveOperationSupport implements ExecutableRemoveOperation {
|
||||
*/
|
||||
@Override
|
||||
public DeleteResult all() {
|
||||
return template.doRemove(getCollectionName(), query, domainType, true);
|
||||
}
|
||||
|
||||
String collectionName = getCollectionName();
|
||||
|
||||
return template.doRemove(collectionName, query, domainType);
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.ExecutableRemoveOperation.TerminatingRemove#one()
|
||||
*/
|
||||
@Override
|
||||
public DeleteResult one() {
|
||||
return template.doRemove(getCollectionName(), query, domainType, false);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -129,7 +123,7 @@ class ExecutableRemoveOperationSupport implements ExecutableRemoveOperation {
|
||||
}
|
||||
|
||||
private String getCollectionName() {
|
||||
return StringUtils.hasText(collection) ? collection : template.determineCollectionName(domainType);
|
||||
return StringUtils.hasText(collection) ? collection : template.getCollectionName(domainType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,13 +19,13 @@ import java.util.Optional;
|
||||
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
import org.springframework.data.mongodb.core.query.Update;
|
||||
|
||||
import com.mongodb.client.result.UpdateResult;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
import com.mongodb.client.result.UpdateResult;
|
||||
|
||||
/**
|
||||
* {@link ExecutableUpdateOperation} allows creation and execution of MongoDB update / findAndModify operations in a
|
||||
* fluent API style. <br />
|
||||
* {@link ExecutableUpdateOperation} allows creation and execution of MongoDB update / findAndModify / findAndReplace
|
||||
* operations in a fluent API style. <br />
|
||||
* The starting {@literal domainType} is used for mapping the {@link Query} provided via {@code matching}, as well as
|
||||
* the {@link Update} via {@code apply} into the MongoDB specific representations. The collection to operate on is by
|
||||
* default derived from the initial {@literal domainType} and can be defined there via
|
||||
@@ -57,6 +57,91 @@ public interface ExecutableUpdateOperation {
|
||||
*/
|
||||
<T> ExecutableUpdate<T> update(Class<T> domainType);
|
||||
|
||||
/**
|
||||
* Trigger findAndModify execution by calling one of the terminating methods.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
* @since 2.0
|
||||
*/
|
||||
interface TerminatingFindAndModify<T> {
|
||||
|
||||
/**
|
||||
* Find, modify and return the first matching document.
|
||||
*
|
||||
* @return {@link Optional#empty()} if nothing found.
|
||||
*/
|
||||
default Optional<T> findAndModify() {
|
||||
return Optional.ofNullable(findAndModifyValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* Find, modify and return the first matching document.
|
||||
*
|
||||
* @return {@literal null} if nothing found.
|
||||
*/
|
||||
@Nullable
|
||||
T findAndModifyValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger
|
||||
* <a href="https://docs.mongodb.com/manual/reference/method/db.collection.findOneAndReplace/">findOneAndReplace<a/>
|
||||
* execution by calling one of the terminating methods.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 2.1
|
||||
*/
|
||||
interface TerminatingFindAndReplace<T> {
|
||||
|
||||
/**
|
||||
* Find, replace and return the first matching document.
|
||||
*
|
||||
* @return {@link Optional#empty()} if nothing found.
|
||||
*/
|
||||
default Optional<T> findAndReplace() {
|
||||
return Optional.ofNullable(findAndReplaceValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* Find, replace and return the first matching document.
|
||||
*
|
||||
* @return {@literal null} if nothing found.
|
||||
*/
|
||||
@Nullable
|
||||
T findAndReplaceValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger update execution by calling one of the terminating methods.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.0
|
||||
*/
|
||||
interface TerminatingUpdate<T> extends TerminatingFindAndModify<T>, FindAndModifyWithOptions<T> {
|
||||
|
||||
/**
|
||||
* Update all matching documents in the collection.
|
||||
*
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
UpdateResult all();
|
||||
|
||||
/**
|
||||
* Update the first document in the collection.
|
||||
*
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
UpdateResult first();
|
||||
|
||||
/**
|
||||
* Creates a new document if no documents match the filter query or updates the matching ones.
|
||||
*
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
UpdateResult upsert();
|
||||
}
|
||||
|
||||
/**
|
||||
* Declare the {@link Update} to apply.
|
||||
*
|
||||
@@ -73,6 +158,16 @@ public interface ExecutableUpdateOperation {
|
||||
* @throws IllegalArgumentException if update is {@literal null}.
|
||||
*/
|
||||
TerminatingUpdate<T> apply(Update update);
|
||||
|
||||
/**
|
||||
* Specify {@code replacement} object.
|
||||
*
|
||||
* @param replacement must not be {@literal null}.
|
||||
* @return new instance of {@link FindAndReplaceOptions}.
|
||||
* @throws IllegalArgumentException if options is {@literal null}.
|
||||
* @since 2.1
|
||||
*/
|
||||
FindAndReplaceWithProjection<T> replaceWith(T replacement);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -131,56 +226,43 @@ public interface ExecutableUpdateOperation {
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger findAndModify execution by calling one of the terminating methods.
|
||||
* Define {@link FindAndReplaceOptions}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Christoph Strobl
|
||||
* @since 2.1
|
||||
*/
|
||||
interface TerminatingFindAndModify<T> {
|
||||
interface FindAndReplaceWithOptions<T> extends TerminatingFindAndReplace<T> {
|
||||
|
||||
/**
|
||||
* Find, modify and return the first matching document.
|
||||
* Explicitly define {@link FindAndReplaceOptions} for the {@link Update}.
|
||||
*
|
||||
* @return {@link Optional#empty()} if nothing found.
|
||||
* @param options must not be {@literal null}.
|
||||
* @return new instance of {@link FindAndReplaceOptions}.
|
||||
* @throws IllegalArgumentException if options is {@literal null}.
|
||||
*/
|
||||
default Optional<T> findAndModify() {
|
||||
return Optional.ofNullable(findAndModifyValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* Find, modify and return the first matching document.
|
||||
*
|
||||
* @return {@literal null} if nothing found.
|
||||
*/
|
||||
@Nullable
|
||||
T findAndModifyValue();
|
||||
FindAndReplaceWithProjection<T> withOptions(FindAndReplaceOptions options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger update execution by calling one of the terminating methods.
|
||||
* Result type override (Optional).
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.0
|
||||
* @since 2.1
|
||||
*/
|
||||
interface TerminatingUpdate<T> extends TerminatingFindAndModify<T>, FindAndModifyWithOptions<T> {
|
||||
interface FindAndReplaceWithProjection<T> extends FindAndReplaceWithOptions<T> {
|
||||
|
||||
/**
|
||||
* Update all matching documents in the collection.
|
||||
* Define the target type fields should be mapped to. <br />
|
||||
* Skip this step if you are anyway only interested in the original domain type.
|
||||
*
|
||||
* @return never {@literal null}.
|
||||
* @param resultType must not be {@literal null}.
|
||||
* @param <R> result type.
|
||||
* @return new instance of {@link FindAndReplaceWithProjection}.
|
||||
* @throws IllegalArgumentException if resultType is {@literal null}.
|
||||
*/
|
||||
UpdateResult all();
|
||||
<R> FindAndReplaceWithOptions<R> as(Class<R> resultType);
|
||||
|
||||
/**
|
||||
* Update the first document in the collection.
|
||||
*
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
UpdateResult first();
|
||||
|
||||
/**
|
||||
* Creates a new document if no documents match the filter query or updates the matching ones.
|
||||
*
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
UpdateResult upsert();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -35,23 +35,12 @@ import com.mongodb.client.result.UpdateResult;
|
||||
* @author Mark Paluch
|
||||
* @since 2.0
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
class ExecutableUpdateOperationSupport implements ExecutableUpdateOperation {
|
||||
|
||||
private static final Query ALL_QUERY = new Query();
|
||||
|
||||
private final MongoTemplate template;
|
||||
|
||||
/**
|
||||
* Creates new {@link ExecutableUpdateOperationSupport}.
|
||||
*
|
||||
* @param template must not be {@literal null}.
|
||||
*/
|
||||
ExecutableUpdateOperationSupport(MongoTemplate template) {
|
||||
|
||||
Assert.notNull(template, "Template must not be null!");
|
||||
|
||||
this.template = template;
|
||||
}
|
||||
private final @NonNull MongoTemplate template;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
@@ -62,7 +51,7 @@ class ExecutableUpdateOperationSupport implements ExecutableUpdateOperation {
|
||||
|
||||
Assert.notNull(domainType, "DomainType must not be null!");
|
||||
|
||||
return new ExecutableUpdateSupport<>(template, domainType, ALL_QUERY, null, null, null);
|
||||
return new ExecutableUpdateSupport<>(template, domainType, ALL_QUERY, null, null, null, null, null, domainType);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -72,14 +61,18 @@ class ExecutableUpdateOperationSupport implements ExecutableUpdateOperation {
|
||||
@RequiredArgsConstructor
|
||||
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
|
||||
static class ExecutableUpdateSupport<T>
|
||||
implements ExecutableUpdate<T>, UpdateWithCollection<T>, UpdateWithQuery<T>, TerminatingUpdate<T> {
|
||||
implements ExecutableUpdate<T>, UpdateWithCollection<T>, UpdateWithQuery<T>, TerminatingUpdate<T>,
|
||||
FindAndReplaceWithOptions<T>, TerminatingFindAndReplace<T>, FindAndReplaceWithProjection<T> {
|
||||
|
||||
@NonNull MongoTemplate template;
|
||||
@NonNull Class<T> domainType;
|
||||
@NonNull Class domainType;
|
||||
Query query;
|
||||
@Nullable Update update;
|
||||
@Nullable String collection;
|
||||
@Nullable FindAndModifyOptions options;
|
||||
@Nullable FindAndModifyOptions findAndModifyOptions;
|
||||
@Nullable FindAndReplaceOptions findAndReplaceOptions;
|
||||
@Nullable Object replacement;
|
||||
@NonNull Class<T> targetType;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
@@ -90,7 +83,8 @@ class ExecutableUpdateOperationSupport implements ExecutableUpdateOperation {
|
||||
|
||||
Assert.notNull(update, "Update must not be null!");
|
||||
|
||||
return new ExecutableUpdateSupport<>(template, domainType, query, update, collection, options);
|
||||
return new ExecutableUpdateSupport<>(template, domainType, query, update, collection, findAndModifyOptions,
|
||||
findAndReplaceOptions, replacement, targetType);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -102,7 +96,8 @@ class ExecutableUpdateOperationSupport implements ExecutableUpdateOperation {
|
||||
|
||||
Assert.hasText(collection, "Collection must not be null nor empty!");
|
||||
|
||||
return new ExecutableUpdateSupport<>(template, domainType, query, update, collection, options);
|
||||
return new ExecutableUpdateSupport<>(template, domainType, query, update, collection, findAndModifyOptions,
|
||||
findAndReplaceOptions, replacement, targetType);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -114,7 +109,34 @@ class ExecutableUpdateOperationSupport implements ExecutableUpdateOperation {
|
||||
|
||||
Assert.notNull(options, "Options must not be null!");
|
||||
|
||||
return new ExecutableUpdateSupport<>(template, domainType, query, update, collection, options);
|
||||
return new ExecutableUpdateSupport<>(template, domainType, query, update, collection, options,
|
||||
findAndReplaceOptions, replacement, targetType);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.ExecutableUpdateOperation.UpdateWithUpdate#replaceWith(Object)
|
||||
*/
|
||||
@Override
|
||||
public FindAndReplaceWithProjection<T> replaceWith(T replacement) {
|
||||
|
||||
Assert.notNull(replacement, "Replacement must not be null!");
|
||||
|
||||
return new ExecutableUpdateSupport<>(template, domainType, query, update, collection, findAndModifyOptions,
|
||||
findAndReplaceOptions, replacement, targetType);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.ExecutableUpdateOperation.FindAndReplaceWithOptions#withOptions(org.springframework.data.mongodb.core.FindAndReplaceOptions)
|
||||
*/
|
||||
@Override
|
||||
public FindAndReplaceWithProjection<T> withOptions(FindAndReplaceOptions options) {
|
||||
|
||||
Assert.notNull(options, "Options must not be null!");
|
||||
|
||||
return new ExecutableUpdateSupport<>(template, domainType, query, update, collection, findAndModifyOptions,
|
||||
options, replacement, targetType);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -126,7 +148,21 @@ class ExecutableUpdateOperationSupport implements ExecutableUpdateOperation {
|
||||
|
||||
Assert.notNull(query, "Query must not be null!");
|
||||
|
||||
return new ExecutableUpdateSupport<>(template, domainType, query, update, collection, options);
|
||||
return new ExecutableUpdateSupport<>(template, domainType, query, update, collection, findAndModifyOptions,
|
||||
findAndReplaceOptions, replacement, targetType);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.ReactiveUpdateOperation.FindAndReplaceWithProjection#as(java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public <R> FindAndReplaceWithOptions<R> as(Class<R> resultType) {
|
||||
|
||||
Assert.notNull(resultType, "ResultType must not be null!");
|
||||
|
||||
return new ExecutableUpdateSupport<>(template, domainType, query, update, collection, findAndModifyOptions,
|
||||
findAndReplaceOptions, replacement, resultType);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -162,7 +198,22 @@ class ExecutableUpdateOperationSupport implements ExecutableUpdateOperation {
|
||||
*/
|
||||
@Override
|
||||
public @Nullable T findAndModifyValue() {
|
||||
return template.findAndModify(query, update, options != null ? options : new FindAndModifyOptions(), domainType, getCollectionName());
|
||||
|
||||
return template.findAndModify(query, update,
|
||||
findAndModifyOptions != null ? findAndModifyOptions : new FindAndModifyOptions(), targetType,
|
||||
getCollectionName());
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.ExecutableUpdateOperation.TerminatingFindAndReplace#findAndReplaceValue()
|
||||
*/
|
||||
@Override
|
||||
public @Nullable T findAndReplaceValue() {
|
||||
|
||||
return (T) template.findAndReplace(query, replacement,
|
||||
findAndReplaceOptions != null ? findAndReplaceOptions : FindAndReplaceOptions.empty(), domainType,
|
||||
getCollectionName(), targetType);
|
||||
}
|
||||
|
||||
private UpdateResult doUpdate(boolean multi, boolean upsert) {
|
||||
@@ -170,7 +221,7 @@ class ExecutableUpdateOperationSupport implements ExecutableUpdateOperation {
|
||||
}
|
||||
|
||||
private String getCollectionName() {
|
||||
return StringUtils.hasText(collection) ? collection : template.determineCollectionName(domainType);
|
||||
return StringUtils.hasText(collection) ? collection : template.getCollectionName(domainType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,15 +36,17 @@ public class FindAndModifyOptions {
|
||||
/**
|
||||
* Static factory method to create a FindAndModifyOptions instance
|
||||
*
|
||||
* @return a new instance
|
||||
* @return new instance of {@link FindAndModifyOptions}.
|
||||
*/
|
||||
public static FindAndModifyOptions options() {
|
||||
return new FindAndModifyOptions();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param options
|
||||
* @return
|
||||
* Create new {@link FindAndModifyOptions} based on option of given {@litearl source}.
|
||||
*
|
||||
* @param source can be {@literal null}.
|
||||
* @return new instance of {@link FindAndModifyOptions}.
|
||||
* @since 2.0
|
||||
*/
|
||||
public static FindAndModifyOptions of(@Nullable FindAndModifyOptions source) {
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Copyright 2018 the original author 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;
|
||||
|
||||
/**
|
||||
* Options for
|
||||
* <a href="https://docs.mongodb.com/manual/reference/method/db.collection.findOneAndReplace/">findOneAndReplace<a/>.
|
||||
* <br />
|
||||
* Defaults to
|
||||
* <dl>
|
||||
* <dt>returnNew</dt>
|
||||
* <dd>false</dd>
|
||||
* <dt>upsert</dt>
|
||||
* <dd>false</dd>
|
||||
* </dl>
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Christoph Strobl
|
||||
* @since 2.1
|
||||
*/
|
||||
public class FindAndReplaceOptions {
|
||||
|
||||
private boolean returnNew;
|
||||
private boolean upsert;
|
||||
|
||||
/**
|
||||
* Static factory method to create a {@link FindAndReplaceOptions} instance.
|
||||
* <dl>
|
||||
* <dt>returnNew</dt>
|
||||
* <dd>false</dd>
|
||||
* <dt>upsert</dt>
|
||||
* <dd>false</dd>
|
||||
* </dl>
|
||||
*
|
||||
* @return new instance of {@link FindAndReplaceOptions}.
|
||||
*/
|
||||
public static FindAndReplaceOptions options() {
|
||||
return new FindAndReplaceOptions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Static factory method to create a {@link FindAndReplaceOptions} instance with
|
||||
* <dl>
|
||||
* <dt>returnNew</dt>
|
||||
* <dd>false</dd>
|
||||
* <dt>upsert</dt>
|
||||
* <dd>false</dd>
|
||||
* </dl>
|
||||
*
|
||||
* @return new instance of {@link FindAndReplaceOptions}.
|
||||
*/
|
||||
public static FindAndReplaceOptions empty() {
|
||||
return new FindAndReplaceOptions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the replacement document.
|
||||
*
|
||||
* @return this.
|
||||
*/
|
||||
public FindAndReplaceOptions returnNew() {
|
||||
|
||||
this.returnNew = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert a new document if not exists.
|
||||
*
|
||||
* @return this.
|
||||
*/
|
||||
public FindAndReplaceOptions upsert() {
|
||||
|
||||
this.upsert = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the bit indicating to return the replacement document.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean isReturnNew() {
|
||||
return returnNew;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the bit indicating if to create a new document if not exists.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean isUpsert() {
|
||||
return upsert;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -22,4 +22,4 @@ package org.springframework.data.mongodb.core;
|
||||
* @since 2.0
|
||||
*/
|
||||
public interface FluentMongoOperations extends ExecutableFindOperation, ExecutableInsertOperation,
|
||||
ExecutableUpdateOperation, ExecutableRemoveOperation, ExecutableAggregationOperation {}
|
||||
ExecutableUpdateOperation, ExecutableRemoveOperation, ExecutableAggregationOperation, ExecutableMapReduceOperation {}
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright 2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.mongodb.core;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.bson.Document;
|
||||
import org.bson.conversions.Bson;
|
||||
import org.springframework.data.mongodb.core.query.Update;
|
||||
import org.springframework.data.util.StreamUtils;
|
||||
|
||||
import com.mongodb.client.model.Filters;
|
||||
|
||||
/**
|
||||
* A MongoDB document in its mapped state. I.e. after a source document has been mapped using mapping information of the
|
||||
* entity the source document was supposed to represent.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @since 2.1
|
||||
*/
|
||||
@RequiredArgsConstructor(staticName = "of")
|
||||
public class MappedDocument {
|
||||
|
||||
private static final String ID_FIELD = "_id";
|
||||
private static final Document ID_ONLY_PROJECTION = new Document(ID_FIELD, 1);
|
||||
|
||||
private final @Getter Document document;
|
||||
|
||||
public static Document getIdOnlyProjection() {
|
||||
return ID_ONLY_PROJECTION;
|
||||
}
|
||||
|
||||
public static Document getIdIn(Collection<?> ids) {
|
||||
return new Document(ID_FIELD, new Document("$in", ids));
|
||||
}
|
||||
|
||||
public static List<Object> toIds(Collection<Document> documents) {
|
||||
|
||||
return documents.stream()//
|
||||
.map(it -> it.get(ID_FIELD))//
|
||||
.collect(StreamUtils.toUnmodifiableList());
|
||||
}
|
||||
|
||||
public boolean hasId() {
|
||||
return document.containsKey(ID_FIELD);
|
||||
}
|
||||
|
||||
public boolean hasNonNullId() {
|
||||
return hasId() && document.get(ID_FIELD) != null;
|
||||
}
|
||||
|
||||
public Object getId() {
|
||||
return document.get(ID_FIELD);
|
||||
}
|
||||
|
||||
public <T> T getId(Class<T> type) {
|
||||
return document.get(ID_FIELD, type);
|
||||
}
|
||||
|
||||
public boolean isIdPresent(Class<?> type) {
|
||||
return type.isInstance(getId());
|
||||
}
|
||||
|
||||
public Bson getIdFilter() {
|
||||
return Filters.eq(ID_FIELD, document.get(ID_FIELD));
|
||||
}
|
||||
|
||||
public Update updateWithoutId() {
|
||||
return Update.fromDocument(document, ID_FIELD);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,272 @@
|
||||
/*
|
||||
* Copyright 2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.mongodb.core;
|
||||
|
||||
import lombok.Value;
|
||||
|
||||
import org.springframework.aop.framework.ProxyFactory;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.dao.support.PersistenceExceptionTranslator;
|
||||
import org.springframework.data.mongodb.MongoDbFactory;
|
||||
import org.springframework.data.mongodb.SessionAwareMethodInterceptor;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.mongodb.ClientSessionOptions;
|
||||
import com.mongodb.DB;
|
||||
import com.mongodb.WriteConcern;
|
||||
import com.mongodb.client.ClientSession;
|
||||
import com.mongodb.client.MongoCollection;
|
||||
import com.mongodb.client.MongoDatabase;
|
||||
|
||||
/**
|
||||
* Common base class for usage with both {@link com.mongodb.client.MongoClients} and {@link com.mongodb.MongoClient}
|
||||
* defining common properties such as database name and exception translator.
|
||||
* <p/>
|
||||
* Not intended to be used directly.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
* @param <C> Client type.
|
||||
* @since 2.1
|
||||
* @see SimpleMongoDbFactory
|
||||
* @see SimpleMongoClientDbFactory
|
||||
*/
|
||||
public abstract class MongoDbFactorySupport<C> implements MongoDbFactory {
|
||||
|
||||
private final C mongoClient;
|
||||
private final String databaseName;
|
||||
private final boolean mongoInstanceCreated;
|
||||
private final PersistenceExceptionTranslator exceptionTranslator;
|
||||
|
||||
private @Nullable WriteConcern writeConcern;
|
||||
|
||||
/**
|
||||
* Create a new {@link MongoDbFactorySupport} object given {@code mongoClient}, {@code databaseName},
|
||||
* {@code mongoInstanceCreated} and {@link PersistenceExceptionTranslator}.
|
||||
*
|
||||
* @param mongoClient must not be {@literal null}.
|
||||
* @param databaseName must not be {@literal null} or empty.
|
||||
* @param mongoInstanceCreated {@literal true} if the client instance was created by a subclass of
|
||||
* {@link MongoDbFactorySupport} to close the client on {@link #destroy()}.
|
||||
* @param exceptionTranslator must not be {@literal null}.
|
||||
*/
|
||||
protected MongoDbFactorySupport(C mongoClient, String databaseName, boolean mongoInstanceCreated,
|
||||
PersistenceExceptionTranslator exceptionTranslator) {
|
||||
|
||||
Assert.notNull(mongoClient, "MongoClient must not be null!");
|
||||
Assert.hasText(databaseName, "Database name must not be empty!");
|
||||
Assert.isTrue(databaseName.matches("[^/\\\\.$\"\\s]+"),
|
||||
"Database name must not contain slashes, dots, spaces, quotes, or dollar signs!");
|
||||
|
||||
this.mongoClient = mongoClient;
|
||||
this.databaseName = databaseName;
|
||||
this.mongoInstanceCreated = mongoInstanceCreated;
|
||||
this.exceptionTranslator = exceptionTranslator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the {@link WriteConcern} to be used on the {@link MongoDatabase} instance being created.
|
||||
*
|
||||
* @param writeConcern the writeConcern to set
|
||||
*/
|
||||
public void setWriteConcern(WriteConcern writeConcern) {
|
||||
this.writeConcern = writeConcern;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.MongoDbFactory#getDb()
|
||||
*/
|
||||
public MongoDatabase getDb() throws DataAccessException {
|
||||
return getDb(databaseName);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.MongoDbFactory#getDb(java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public MongoDatabase getDb(String dbName) throws DataAccessException {
|
||||
|
||||
Assert.hasText(dbName, "Database name must not be empty!");
|
||||
|
||||
MongoDatabase db = doGetMongoDatabase(dbName);
|
||||
|
||||
if (writeConcern == null) {
|
||||
return db;
|
||||
}
|
||||
|
||||
return db.withWriteConcern(writeConcern);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the actual {@link MongoDatabase} from the client.
|
||||
*
|
||||
* @param dbName must not be {@literal null} or empty.
|
||||
* @return
|
||||
*/
|
||||
protected abstract MongoDatabase doGetMongoDatabase(String dbName);
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.beans.factory.DisposableBean#destroy()
|
||||
*/
|
||||
public void destroy() throws Exception {
|
||||
if (mongoInstanceCreated) {
|
||||
closeClient();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.MongoDbFactory#getExceptionTranslator()
|
||||
*/
|
||||
public PersistenceExceptionTranslator getExceptionTranslator() {
|
||||
return this.exceptionTranslator;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.MongoDbFactory#withSession(com.mongodb.session.Session)
|
||||
*/
|
||||
public MongoDbFactory withSession(ClientSession session) {
|
||||
return new MongoDbFactorySupport.ClientSessionBoundMongoDbFactory(session, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the client instance.
|
||||
*/
|
||||
protected abstract void closeClient();
|
||||
|
||||
/**
|
||||
* @return the Mongo client object.
|
||||
*/
|
||||
protected C getMongoClient() {
|
||||
return mongoClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the database name.
|
||||
*/
|
||||
protected String getDefaultDatabaseName() {
|
||||
return databaseName;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link ClientSession} bound {@link MongoDbFactory} decorating the database with a
|
||||
* {@link SessionAwareMethodInterceptor}.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.1
|
||||
*/
|
||||
@Value
|
||||
static class ClientSessionBoundMongoDbFactory implements MongoDbFactory {
|
||||
|
||||
ClientSession session;
|
||||
MongoDbFactory delegate;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.MongoDbFactory#getDb()
|
||||
*/
|
||||
@Override
|
||||
public MongoDatabase getDb() throws DataAccessException {
|
||||
return proxyMongoDatabase(delegate.getDb());
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.MongoDbFactory#getDb(java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public MongoDatabase getDb(String dbName) throws DataAccessException {
|
||||
return proxyMongoDatabase(delegate.getDb(dbName));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.MongoDbFactory#getExceptionTranslator()
|
||||
*/
|
||||
@Override
|
||||
public PersistenceExceptionTranslator getExceptionTranslator() {
|
||||
return delegate.getExceptionTranslator();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.MongoDbFactory#getLegacyDb()
|
||||
*/
|
||||
@Override
|
||||
public DB getLegacyDb() {
|
||||
return delegate.getLegacyDb();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.MongoDbFactory#getSession(com.mongodb.ClientSessionOptions)
|
||||
*/
|
||||
@Override
|
||||
public ClientSession getSession(ClientSessionOptions options) {
|
||||
return delegate.getSession(options);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.MongoDbFactory#withSession(com.mongodb.session.ClientSession)
|
||||
*/
|
||||
@Override
|
||||
public MongoDbFactory withSession(ClientSession session) {
|
||||
return delegate.withSession(session);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.MongoDbFactory#isTransactionActive()
|
||||
*/
|
||||
@Override
|
||||
public boolean isTransactionActive() {
|
||||
return session != null && session.hasActiveTransaction();
|
||||
}
|
||||
|
||||
private MongoDatabase proxyMongoDatabase(MongoDatabase database) {
|
||||
return createProxyInstance(session, database, MongoDatabase.class);
|
||||
}
|
||||
|
||||
private MongoDatabase proxyDatabase(com.mongodb.session.ClientSession session, MongoDatabase database) {
|
||||
return createProxyInstance(session, database, MongoDatabase.class);
|
||||
}
|
||||
|
||||
private MongoCollection<?> proxyCollection(com.mongodb.session.ClientSession session,
|
||||
MongoCollection<?> collection) {
|
||||
return createProxyInstance(session, collection, MongoCollection.class);
|
||||
}
|
||||
|
||||
private <T> T createProxyInstance(com.mongodb.session.ClientSession session, T target, Class<T> targetType) {
|
||||
|
||||
ProxyFactory factory = new ProxyFactory();
|
||||
factory.setTarget(target);
|
||||
factory.setInterfaces(targetType);
|
||||
factory.setOpaque(true);
|
||||
|
||||
factory.addAdvice(new SessionAwareMethodInterceptor<>(session, target, ClientSession.class, MongoDatabase.class,
|
||||
this::proxyDatabase, MongoCollection.class, this::proxyCollection));
|
||||
|
||||
return targetType.cast(factory.getProxy());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -16,6 +16,7 @@
|
||||
package org.springframework.data.mongodb.core;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
@@ -30,6 +31,7 @@ import org.springframework.dao.PermissionDeniedDataAccessException;
|
||||
import org.springframework.dao.support.PersistenceExceptionTranslator;
|
||||
import org.springframework.data.mongodb.BulkOperationException;
|
||||
import org.springframework.data.mongodb.ClientSessionException;
|
||||
import org.springframework.data.mongodb.MongoTransactionException;
|
||||
import org.springframework.data.mongodb.UncategorizedMongoDbException;
|
||||
import org.springframework.data.mongodb.util.MongoDbErrorCodes;
|
||||
import org.springframework.lang.Nullable;
|
||||
@@ -52,17 +54,17 @@ import com.mongodb.bulk.BulkWriteError;
|
||||
*/
|
||||
public class MongoExceptionTranslator implements PersistenceExceptionTranslator {
|
||||
|
||||
private static final Set<String> DULICATE_KEY_EXCEPTIONS = new HashSet<String>(
|
||||
private static final Set<String> DUPLICATE_KEY_EXCEPTIONS = new HashSet<>(
|
||||
Arrays.asList("MongoException.DuplicateKey", "DuplicateKeyException"));
|
||||
|
||||
private static final Set<String> RESOURCE_FAILURE_EXCEPTIONS = new HashSet<String>(
|
||||
private static final Set<String> RESOURCE_FAILURE_EXCEPTIONS = new HashSet<>(
|
||||
Arrays.asList("MongoException.Network", "MongoSocketException", "MongoException.CursorNotFound",
|
||||
"MongoCursorNotFoundException", "MongoServerSelectionException", "MongoTimeoutException"));
|
||||
|
||||
private static final Set<String> RESOURCE_USAGE_EXCEPTIONS = new HashSet<String>(
|
||||
Arrays.asList("MongoInternalException"));
|
||||
private static final Set<String> RESOURCE_USAGE_EXCEPTIONS = new HashSet<>(
|
||||
Collections.singletonList("MongoInternalException"));
|
||||
|
||||
private static final Set<String> DATA_INTEGRETY_EXCEPTIONS = new HashSet<String>(
|
||||
private static final Set<String> DATA_INTEGRITY_EXCEPTIONS = new HashSet<>(
|
||||
Arrays.asList("WriteConcernException", "MongoWriteException", "MongoBulkWriteException"));
|
||||
|
||||
/*
|
||||
@@ -80,7 +82,7 @@ public class MongoExceptionTranslator implements PersistenceExceptionTranslator
|
||||
|
||||
String exception = ClassUtils.getShortName(ClassUtils.getUserClass(ex.getClass()));
|
||||
|
||||
if (DULICATE_KEY_EXCEPTIONS.contains(exception)) {
|
||||
if (DUPLICATE_KEY_EXCEPTIONS.contains(exception)) {
|
||||
return new DuplicateKeyException(ex.getMessage(), ex);
|
||||
}
|
||||
|
||||
@@ -92,7 +94,7 @@ public class MongoExceptionTranslator implements PersistenceExceptionTranslator
|
||||
return new InvalidDataAccessResourceUsageException(ex.getMessage(), ex);
|
||||
}
|
||||
|
||||
if (DATA_INTEGRETY_EXCEPTIONS.contains(exception)) {
|
||||
if (DATA_INTEGRITY_EXCEPTIONS.contains(exception)) {
|
||||
|
||||
if (ex instanceof MongoServerException) {
|
||||
if (((MongoServerException) ex).getCode() == 11000) {
|
||||
@@ -128,6 +130,10 @@ public class MongoExceptionTranslator implements PersistenceExceptionTranslator
|
||||
return new InvalidDataAccessApiUsageException(ex.getMessage(), ex);
|
||||
} else if (MongoDbErrorCodes.isPermissionDeniedCode(code)) {
|
||||
return new PermissionDeniedDataAccessException(ex.getMessage(), ex);
|
||||
} else if (MongoDbErrorCodes.isClientSessionFailureCode(code)) {
|
||||
return new ClientSessionException(ex.getMessage(), ex);
|
||||
} else if (MongoDbErrorCodes.isTransactionFailureCode(code)) {
|
||||
return new MongoTransactionException(ex.getMessage(), ex);
|
||||
}
|
||||
return new UncategorizedMongoDbException(ex.getMessage(), ex);
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ import org.springframework.data.mongodb.core.aggregation.Aggregation;
|
||||
import org.springframework.data.mongodb.core.aggregation.AggregationOptions;
|
||||
import org.springframework.data.mongodb.core.aggregation.AggregationResults;
|
||||
import org.springframework.data.mongodb.core.aggregation.TypedAggregation;
|
||||
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
|
||||
import org.springframework.data.mongodb.core.convert.MongoConverter;
|
||||
import org.springframework.data.mongodb.core.index.IndexOperations;
|
||||
import org.springframework.data.mongodb.core.mapreduce.GroupBy;
|
||||
@@ -42,14 +43,15 @@ import org.springframework.data.mongodb.core.query.Update;
|
||||
import org.springframework.data.util.CloseableIterator;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
import com.mongodb.ClientSessionOptions;
|
||||
import com.mongodb.Cursor;
|
||||
import com.mongodb.ReadPreference;
|
||||
import com.mongodb.client.ClientSession;
|
||||
import com.mongodb.client.MongoCollection;
|
||||
import com.mongodb.client.result.DeleteResult;
|
||||
import com.mongodb.client.result.UpdateResult;
|
||||
import com.mongodb.session.ClientSession;
|
||||
|
||||
/**
|
||||
* Interface that specifies a basic set of MongoDB operations. Implemented by {@link MongoTemplate}. Not often used but
|
||||
@@ -381,7 +383,7 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
* Returns a new {@link BulkOperations} for the given entity type and collection name.
|
||||
*
|
||||
* @param mode the {@link BulkMode} to use for bulk operations, must not be {@literal null}.
|
||||
* @param entityClass the name of the entity class. Can be {@literal null}.
|
||||
* @param entityType the name of the entity class. Can be {@literal null}.
|
||||
* @param collectionName the name of the collection to work on, must not be {@literal null} or empty.
|
||||
* @return {@link BulkOperations} on the named collection associated with the given entity class.
|
||||
*/
|
||||
@@ -420,8 +422,6 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
* Execute a group operation over the entire collection. The group operation entity class should match the 'shape' of
|
||||
* the returned object that takes int account the initial document structure as well as any finalize functions.
|
||||
*
|
||||
* @param criteria The criteria that restricts the row that are considered for grouping. If not specified all rows are
|
||||
* considered.
|
||||
* @param inputCollectionName the collection where the group operation will read from
|
||||
* @param groupBy the conditions under which the group operation will be performed, e.g. keys, initial document,
|
||||
* reduce function.
|
||||
@@ -894,6 +894,167 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
<T> T findAndModify(Query query, Update update, FindAndModifyOptions options, Class<T> entityClass,
|
||||
String collectionName);
|
||||
|
||||
/**
|
||||
* Triggers
|
||||
* <a href="https://docs.mongodb.com/manual/reference/method/db.collection.findOneAndReplace/">findOneAndReplace<a/>
|
||||
* to replace a single document matching {@link Criteria} of given {@link Query} with the {@code replacement}
|
||||
* document. <br />
|
||||
* The collection name is derived from the {@literal replacement} type. <br />
|
||||
* Options are defaulted to {@link FindAndReplaceOptions#empty()}. <br />
|
||||
* <strong>NOTE:</strong> The replacement entity must not hold an {@literal id}.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional
|
||||
* fields specification. Must not be {@literal null}.
|
||||
* @param replacement the replacement document. Must not be {@literal null}.
|
||||
* @return the converted object that was updated or {@literal null}, if not found.
|
||||
* @since 2.1
|
||||
*/
|
||||
@Nullable
|
||||
default <T> T findAndReplace(Query query, T replacement) {
|
||||
return findAndReplace(query, replacement, FindAndReplaceOptions.empty());
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers
|
||||
* <a href="https://docs.mongodb.com/manual/reference/method/db.collection.findOneAndReplace/">findOneAndReplace<a/>
|
||||
* to replace a single document matching {@link Criteria} of given {@link Query} with the {@code replacement}
|
||||
* document.<br />
|
||||
* Options are defaulted to {@link FindAndReplaceOptions#empty()}. <br />
|
||||
* <strong>NOTE:</strong> The replacement entity must not hold an {@literal id}.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional
|
||||
* fields specification. Must not be {@literal null}.
|
||||
* @param replacement the replacement document. Must not be {@literal null}.
|
||||
* @param collectionName the collection to query. Must not be {@literal null}.
|
||||
* @return the converted object that was updated or {@literal null}, if not found.
|
||||
* @since 2.1
|
||||
*/
|
||||
@Nullable
|
||||
default <T> T findAndReplace(Query query, T replacement, String collectionName) {
|
||||
return findAndReplace(query, replacement, FindAndReplaceOptions.empty(), collectionName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers
|
||||
* <a href="https://docs.mongodb.com/manual/reference/method/db.collection.findOneAndReplace/">findOneAndReplace<a/>
|
||||
* to replace a single document matching {@link Criteria} of given {@link Query} with the {@code replacement} document
|
||||
* taking {@link FindAndReplaceOptions} into account.<br />
|
||||
* <strong>NOTE:</strong> The replacement entity must not hold an {@literal id}.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional
|
||||
* fields specification. Must not be {@literal null}.
|
||||
* @param replacement the replacement document. Must not be {@literal null}.
|
||||
* @param options the {@link FindAndModifyOptions} holding additional information. Must not be {@literal null}.
|
||||
* @return the converted object that was updated or {@literal null}, if not found. Depending on the value of
|
||||
* {@link FindAndReplaceOptions#isReturnNew()} this will either be the object as it was before the update or
|
||||
* as it is after the update.
|
||||
* @since 2.1
|
||||
*/
|
||||
@Nullable
|
||||
default <T> T findAndReplace(Query query, T replacement, FindAndReplaceOptions options) {
|
||||
return findAndReplace(query, replacement, options, getCollectionName(ClassUtils.getUserClass(replacement)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers
|
||||
* <a href="https://docs.mongodb.com/manual/reference/method/db.collection.findOneAndReplace/">findOneAndReplace<a/>
|
||||
* to replace a single document matching {@link Criteria} of given {@link Query} with the {@code replacement} document
|
||||
* taking {@link FindAndReplaceOptions} into account.<br />
|
||||
* <strong>NOTE:</strong> The replacement entity must not hold an {@literal id}.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional
|
||||
* fields specification. Must not be {@literal null}.
|
||||
* @param replacement the replacement document. Must not be {@literal null}.
|
||||
* @param options the {@link FindAndModifyOptions} holding additional information. Must not be {@literal null}.
|
||||
* @return the converted object that was updated or {@literal null}, if not found. Depending on the value of
|
||||
* {@link FindAndReplaceOptions#isReturnNew()} this will either be the object as it was before the update or
|
||||
* as it is after the update.
|
||||
* @since 2.1
|
||||
*/
|
||||
@Nullable
|
||||
default <T> T findAndReplace(Query query, T replacement, FindAndReplaceOptions options, String collectionName) {
|
||||
|
||||
Assert.notNull(replacement, "Replacement must not be null!");
|
||||
return findAndReplace(query, replacement, options, (Class<T>) ClassUtils.getUserClass(replacement), collectionName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers
|
||||
* <a href="https://docs.mongodb.com/manual/reference/method/db.collection.findOneAndReplace/">findOneAndReplace<a/>
|
||||
* to replace a single document matching {@link Criteria} of given {@link Query} with the {@code replacement} document
|
||||
* taking {@link FindAndReplaceOptions} into account.<br />
|
||||
* <strong>NOTE:</strong> The replacement entity must not hold an {@literal id}.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional
|
||||
* fields specification. Must not be {@literal null}.
|
||||
* @param replacement the replacement document. Must not be {@literal null}.
|
||||
* @param options the {@link FindAndModifyOptions} holding additional information. Must not be {@literal null}.
|
||||
* @param entityType the parametrized type. Must not be {@literal null}.
|
||||
* @param collectionName the collection to query. Must not be {@literal null}.
|
||||
* @return the converted object that was updated or {@literal null}, if not found. Depending on the value of
|
||||
* {@link FindAndReplaceOptions#isReturnNew()} this will either be the object as it was before the update or
|
||||
* as it is after the update.
|
||||
* @since 2.1
|
||||
*/
|
||||
@Nullable
|
||||
default <T> T findAndReplace(Query query, T replacement, FindAndReplaceOptions options, Class<T> entityType,
|
||||
String collectionName) {
|
||||
|
||||
return findAndReplace(query, replacement, options, entityType, collectionName, entityType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers
|
||||
* <a href="https://docs.mongodb.com/manual/reference/method/db.collection.findOneAndReplace/">findOneAndReplace<a/>
|
||||
* to replace a single document matching {@link Criteria} of given {@link Query} with the {@code replacement} document
|
||||
* taking {@link FindAndReplaceOptions} into account.<br />
|
||||
* <strong>NOTE:</strong> The replacement entity must not hold an {@literal id}.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional
|
||||
* fields specification. Must not be {@literal null}.
|
||||
* @param replacement the replacement document. Must not be {@literal null}.
|
||||
* @param options the {@link FindAndModifyOptions} holding additional information. Must not be {@literal null}.
|
||||
* @param entityType the type used for mapping the {@link Query} to domain type fields and deriving the collection
|
||||
* from. Must not be {@literal null}.
|
||||
* @param resultType the parametrized type projection return type. Must not be {@literal null}, use the domain type of
|
||||
* {@code Object.class} instead.
|
||||
* @return the converted object that was updated or {@literal null}, if not found. Depending on the value of
|
||||
* {@link FindAndReplaceOptions#isReturnNew()} this will either be the object as it was before the update or
|
||||
* as it is after the update.
|
||||
* @since 2.1
|
||||
*/
|
||||
@Nullable
|
||||
default <S, T> T findAndReplace(Query query, S replacement, FindAndReplaceOptions options, Class<S> entityType,
|
||||
Class<T> resultType) {
|
||||
|
||||
return findAndReplace(query, replacement, options, entityType,
|
||||
getCollectionName(ClassUtils.getUserClass(entityType)), resultType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers
|
||||
* <a href="https://docs.mongodb.com/manual/reference/method/db.collection.findOneAndReplace/">findOneAndReplace<a/>
|
||||
* to replace a single document matching {@link Criteria} of given {@link Query} with the {@code replacement} document
|
||||
* taking {@link FindAndReplaceOptions} into account.<br />
|
||||
* <strong>NOTE:</strong> The replacement entity must not hold an {@literal id}.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional
|
||||
* fields specification. Must not be {@literal null}.
|
||||
* @param replacement the replacement document. Must not be {@literal null}.
|
||||
* @param options the {@link FindAndModifyOptions} holding additional information. Must not be {@literal null}.
|
||||
* @param entityType the type used for mapping the {@link Query} to domain type fields. Must not be {@literal null}.
|
||||
* @param collectionName the collection to query. Must not be {@literal null}.
|
||||
* @param resultType the parametrized type projection return type. Must not be {@literal null}, use the domain type of
|
||||
* {@code Object.class} instead.
|
||||
* @return the converted object that was updated or {@literal null}, if not found. Depending on the value of
|
||||
* {@link FindAndReplaceOptions#isReturnNew()} this will either be the object as it was before the update or
|
||||
* as it is after the update.
|
||||
* @since 2.1
|
||||
*/
|
||||
@Nullable
|
||||
<S, T> T findAndReplace(Query query, S replacement, FindAndReplaceOptions options, Class<S> entityType,
|
||||
String collectionName, Class<T> resultType);
|
||||
|
||||
/**
|
||||
* Map the results of an ad-hoc query on the collection for the entity type to a single instance of an object of the
|
||||
* specified type. The first document that matches the query is returned and also removed from the collection in the
|
||||
@@ -980,8 +1141,9 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
* Insert is used to initially store the object into the database. To update an existing object use the save method.
|
||||
*
|
||||
* @param objectToSave the object to store in the collection. Must not be {@literal null}.
|
||||
* @return the inserted object.
|
||||
*/
|
||||
void insert(Object objectToSave);
|
||||
<T> T insert(T objectToSave);
|
||||
|
||||
/**
|
||||
* Insert the object into the specified collection.
|
||||
@@ -993,32 +1155,36 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
*
|
||||
* @param objectToSave the object to store in the collection. Must not be {@literal null}.
|
||||
* @param collectionName name of the collection to store the object in. Must not be {@literal null}.
|
||||
* @return the inserted object.
|
||||
*/
|
||||
void insert(Object objectToSave, String collectionName);
|
||||
<T> T insert(T objectToSave, String collectionName);
|
||||
|
||||
/**
|
||||
* Insert a Collection of objects into a collection in a single batch write to the database.
|
||||
*
|
||||
* @param batchToSave the batch of objects to save. Must not be {@literal null}.
|
||||
* @param entityClass class that determines the collection to use. Must not be {@literal null}.
|
||||
* @return the inserted objects that.
|
||||
*/
|
||||
void insert(Collection<? extends Object> batchToSave, Class<?> entityClass);
|
||||
<T> Collection<T> insert(Collection<? extends T> batchToSave, Class<?> entityClass);
|
||||
|
||||
/**
|
||||
* Insert a batch of objects into the specified collection in a single batch write to the database.
|
||||
*
|
||||
* @param batchToSave the list of objects to save. Must not be {@literal null}.
|
||||
* @param collectionName name of the collection to store the object in. Must not be {@literal null}.
|
||||
* @return the inserted objects that.
|
||||
*/
|
||||
void insert(Collection<? extends Object> batchToSave, String collectionName);
|
||||
<T> Collection<T> insert(Collection<? extends T> batchToSave, String collectionName);
|
||||
|
||||
/**
|
||||
* Insert a mixed Collection of objects into a database collection determining the collection name to use based on the
|
||||
* class.
|
||||
*
|
||||
* @param collectionToSave the list of objects to save. Must not be {@literal null}.
|
||||
* @param objectsToSave the list of objects to save. Must not be {@literal null}.
|
||||
* @return the inserted objects.
|
||||
*/
|
||||
void insertAll(Collection<? extends Object> objectsToSave);
|
||||
<T> Collection<T> insertAll(Collection<? extends T> objectsToSave);
|
||||
|
||||
/**
|
||||
* Save the object to the collection for the entity type of the object to save. This will perform an insert if the
|
||||
@@ -1034,8 +1200,9 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
* Conversion"</a> for more details.
|
||||
*
|
||||
* @param objectToSave the object to store in the collection. Must not be {@literal null}.
|
||||
* @return the saved object.
|
||||
*/
|
||||
void save(Object objectToSave);
|
||||
<T> T save(T objectToSave);
|
||||
|
||||
/**
|
||||
* Save the object to the specified collection. This will perform an insert if the object is not already present, that
|
||||
@@ -1052,8 +1219,9 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
*
|
||||
* @param objectToSave the object to store in the collection. Must not be {@literal null}.
|
||||
* @param collectionName name of the collection to store the object in. Must not be {@literal null}.
|
||||
* @return the saved object.
|
||||
*/
|
||||
void save(Object objectToSave, String collectionName);
|
||||
<T> T save(T objectToSave, String collectionName);
|
||||
|
||||
/**
|
||||
* Performs an upsert. If no document is found that matches the query, a new document is created and inserted by
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright 2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.mongodb.core;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import org.bson.Document;
|
||||
import org.springframework.data.mapping.SimplePropertyHandler;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
|
||||
import org.springframework.data.projection.ProjectionFactory;
|
||||
import org.springframework.data.projection.ProjectionInformation;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* Common operations performed on properties of an entity like extracting fields information for projection creation.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.1
|
||||
*/
|
||||
@RequiredArgsConstructor(access = AccessLevel.PACKAGE)
|
||||
class PropertyOperations {
|
||||
|
||||
private final MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext;
|
||||
|
||||
/**
|
||||
* For cases where {@code fields} is {@link Document#isEmpty() empty} include only fields that are required for
|
||||
* creating the projection (target) type if the {@code targetType} is a {@literal DTO projection} or a
|
||||
* {@literal closed interface projection}.
|
||||
*
|
||||
* @param projectionFactory must not be {@literal null}.
|
||||
* @param fields must not be {@literal null}.
|
||||
* @param domainType must not be {@literal null}.
|
||||
* @param targetType must not be {@literal null}.
|
||||
* @return {@link Document} with fields to be included.
|
||||
*/
|
||||
Document computeFieldsForProjection(ProjectionFactory projectionFactory, Document fields, Class<?> domainType,
|
||||
Class<?> targetType) {
|
||||
|
||||
if (!fields.isEmpty() || ClassUtils.isAssignable(domainType, targetType)) {
|
||||
return fields;
|
||||
}
|
||||
|
||||
Document projectedFields = new Document();
|
||||
|
||||
if (targetType.isInterface()) {
|
||||
|
||||
ProjectionInformation projectionInformation = projectionFactory.getProjectionInformation(targetType);
|
||||
|
||||
if (projectionInformation.isClosed()) {
|
||||
projectionInformation.getInputProperties().forEach(it -> projectedFields.append(it.getName(), 1));
|
||||
}
|
||||
} else {
|
||||
mappingContext.getRequiredPersistentEntity(targetType).doWithProperties(
|
||||
(SimplePropertyHandler) persistentProperty -> projectedFields.append(persistentProperty.getName(), 1));
|
||||
}
|
||||
|
||||
return projectedFields;
|
||||
}
|
||||
}
|
||||
@@ -30,5 +30,4 @@ import com.mongodb.reactivestreams.client.MongoCollection;
|
||||
public interface ReactiveCollectionCallback<T> {
|
||||
|
||||
Publisher<T> doInCollection(MongoCollection<Document> collection) throws MongoException, DataAccessException;
|
||||
|
||||
}
|
||||
|
||||
@@ -85,6 +85,23 @@ public interface ReactiveFindOperation {
|
||||
*/
|
||||
Flux<T> all();
|
||||
|
||||
/**
|
||||
* Get all matching elements using a {@link com.mongodb.CursorType#TailableAwait tailable cursor}. The stream will
|
||||
* not be completed unless the {@link org.reactivestreams.Subscription} is
|
||||
* {@link org.reactivestreams.Subscription#cancel() canceled}.
|
||||
* <p />
|
||||
* However, the stream may become dead, or invalid, if either the query returns no match or the cursor returns the
|
||||
* document at the "end" of the collection and then the application deletes that document.
|
||||
* <p />
|
||||
* A stream that is no longer in use must be {@link reactor.core.Disposable#dispose()} disposed} otherwise the
|
||||
* streams will linger and exhaust resources. <br/>
|
||||
* <strong>NOTE:</strong> Requires a capped collection.
|
||||
*
|
||||
* @return the {@link Flux} emitting converted objects.
|
||||
* @since 2.1
|
||||
*/
|
||||
Flux<T> tail();
|
||||
|
||||
/**
|
||||
* Get the number of matching elements.
|
||||
*
|
||||
|
||||
@@ -169,6 +169,15 @@ class ReactiveFindOperationSupport implements ReactiveFindOperation {
|
||||
return doFind(null);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.ReactiveFindOperation.TerminatingFind#tail()
|
||||
*/
|
||||
@Override
|
||||
public Flux<T> tail() {
|
||||
return doFind(template.new TailingQueryFindPublisherPreparer(query, domainType));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.ReactiveFindOperation.FindWithQuery#near(org.springframework.data.mongodb.core.query.NearQuery)
|
||||
|
||||
@@ -19,7 +19,8 @@ package org.springframework.data.mongodb.core;
|
||||
* Stripped down interface providing access to a fluent API that specifies a basic set of reactive MongoDB operations.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Christoph Strobl
|
||||
* @since 2.0
|
||||
*/
|
||||
public interface ReactiveFluentMongoOperations extends ReactiveFindOperation, ReactiveInsertOperation,
|
||||
ReactiveUpdateOperation, ReactiveRemoveOperation, ReactiveAggregationOperation {}
|
||||
ReactiveUpdateOperation, ReactiveRemoveOperation, ReactiveAggregationOperation, ReactiveMapReduceOperation {}
|
||||
|
||||
@@ -0,0 +1,199 @@
|
||||
/*
|
||||
* Copyright 2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.mongodb.core;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import org.springframework.data.mongodb.core.ExecutableFindOperation.ExecutableFind;
|
||||
import org.springframework.data.mongodb.core.mapreduce.MapReduceOptions;
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
|
||||
/**
|
||||
* {@link ReactiveMapReduceOperation} allows creation and execution of MongoDB mapReduce operations in a fluent API
|
||||
* style. The starting {@literal domainType} is used for mapping an optional {@link Query} provided via {@code matching}
|
||||
* into the MongoDB specific representation. By default, the originating {@literal domainType} is also used for mapping
|
||||
* back the results from the {@link org.bson.Document}. However, it is possible to define an different
|
||||
* {@literal returnType} via {@code as} to mapping the result.<br />
|
||||
* The collection to operate on is by default derived from the initial {@literal domainType} and can be defined there
|
||||
* via {@link org.springframework.data.mongodb.core.mapping.Document}. Using {@code inCollection} allows to override the
|
||||
* collection name for the execution.
|
||||
*
|
||||
* <pre>
|
||||
* <code>
|
||||
* mapReduce(Human.class)
|
||||
* .map("function() { emit(this.id, this.firstname) }")
|
||||
* .reduce("function(id, name) { return sum(id, name); }")
|
||||
* .inCollection("star-wars")
|
||||
* .as(Jedi.class)
|
||||
* .matching(query(where("lastname").is("skywalker")))
|
||||
* .all();
|
||||
* </code>
|
||||
* </pre>
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.1
|
||||
*/
|
||||
public interface ReactiveMapReduceOperation {
|
||||
|
||||
/**
|
||||
* Start creating a mapReduce operation for the given {@literal domainType}.
|
||||
*
|
||||
* @param domainType must not be {@literal null}.
|
||||
* @return new instance of {@link ExecutableFind}.
|
||||
* @throws IllegalArgumentException if domainType is {@literal null}.
|
||||
*/
|
||||
<T> MapReduceWithMapFunction<T> mapReduce(Class<T> domainType);
|
||||
|
||||
/**
|
||||
* Trigger mapReduce execution by calling one of the terminating methods.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.1
|
||||
*/
|
||||
interface TerminatingMapReduce<T> {
|
||||
|
||||
/**
|
||||
* Get the {@link Flux} emitting mapReduce results.
|
||||
*
|
||||
* @return a {@link Flux} emitting the already mapped operation results.
|
||||
*/
|
||||
Flux<T> all();
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide the Javascript {@code function()} used to map matching documents.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.1
|
||||
*/
|
||||
interface MapReduceWithMapFunction<T> {
|
||||
|
||||
/**
|
||||
* Set the Javascript map {@code function()}.
|
||||
*
|
||||
* @param mapFunction must not be {@literal null} nor empty.
|
||||
* @return new instance of {@link MapReduceWithReduceFunction}.
|
||||
* @throws IllegalArgumentException if {@literal mapFunction} is {@literal null} or empty.
|
||||
*/
|
||||
MapReduceWithReduceFunction<T> map(String mapFunction);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide the Javascript {@code function()} used to reduce matching documents.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.1
|
||||
*/
|
||||
interface MapReduceWithReduceFunction<T> {
|
||||
|
||||
/**
|
||||
* Set the Javascript map {@code function()}.
|
||||
*
|
||||
* @param reduceFunction must not be {@literal null} nor empty.
|
||||
* @return new instance of {@link ReactiveMapReduce}.
|
||||
* @throws IllegalArgumentException if {@literal reduceFunction} is {@literal null} or empty.
|
||||
*/
|
||||
ReactiveMapReduce<T> reduce(String reduceFunction);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Collection override (Optional).
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.1
|
||||
*/
|
||||
interface MapReduceWithCollection<T> extends MapReduceWithQuery<T> {
|
||||
|
||||
/**
|
||||
* Explicitly set the name of the collection to perform the mapReduce operation on. <br />
|
||||
* Skip this step to use the default collection derived from the domain type.
|
||||
*
|
||||
* @param collection must not be {@literal null} nor {@literal empty}.
|
||||
* @return new instance of {@link MapReduceWithProjection}.
|
||||
* @throws IllegalArgumentException if collection is {@literal null}.
|
||||
*/
|
||||
MapReduceWithProjection<T> inCollection(String collection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Input document filter query (Optional).
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.1
|
||||
*/
|
||||
interface MapReduceWithQuery<T> extends TerminatingMapReduce<T> {
|
||||
|
||||
/**
|
||||
* Set the filter query to be used.
|
||||
*
|
||||
* @param query must not be {@literal null}.
|
||||
* @return new instance of {@link TerminatingMapReduce}.
|
||||
* @throws IllegalArgumentException if query is {@literal null}.
|
||||
*/
|
||||
TerminatingMapReduce<T> matching(Query query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Result type override (Optional).
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.1
|
||||
*/
|
||||
interface MapReduceWithProjection<T> extends MapReduceWithQuery<T> {
|
||||
|
||||
/**
|
||||
* Define the target type fields should be mapped to. <br />
|
||||
* Skip this step if you are anyway only interested in the original domain type.
|
||||
*
|
||||
* @param resultType must not be {@literal null}.
|
||||
* @param <R> result type.
|
||||
* @return new instance of {@link TerminatingMapReduce}.
|
||||
* @throws IllegalArgumentException if resultType is {@literal null}.
|
||||
*/
|
||||
<R> MapReduceWithQuery<R> as(Class<R> resultType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Additional mapReduce options (Optional).
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.1
|
||||
*/
|
||||
interface MapReduceWithOptions<T> {
|
||||
|
||||
/**
|
||||
* Set additional options to apply to the mapReduce operation.
|
||||
*
|
||||
* @param options must not be {@literal null}.
|
||||
* @return new instance of {@link ReactiveMapReduce}.
|
||||
* @throws IllegalArgumentException if options is {@literal null}.
|
||||
*/
|
||||
ReactiveMapReduce<T> with(MapReduceOptions options);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link ReactiveMapReduce} provides methods for constructing reactive mapReduce operations in a fluent way.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.1
|
||||
*/
|
||||
interface ReactiveMapReduce<T> extends MapReduceWithMapFunction<T>, MapReduceWithReduceFunction<T>,
|
||||
MapReduceWithCollection<T>, MapReduceWithProjection<T>, MapReduceWithOptions<T> {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
/*
|
||||
* Copyright 2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.mongodb.core;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import org.springframework.data.mongodb.core.mapreduce.MapReduceOptions;
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Implementation of {@link ReactiveMapReduceOperation}.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.1
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
class ReactiveMapReduceOperationSupport implements ReactiveMapReduceOperation {
|
||||
|
||||
private static final Query ALL_QUERY = new Query();
|
||||
|
||||
private final @NonNull ReactiveMongoTemplate template;
|
||||
|
||||
/*
|
||||
* (non-Javascript)
|
||||
* @see in org.springframework.data.mongodb.core.ExecutableMapReduceOperation#mapReduce(java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public <T> ReactiveMapReduceSupport<T> mapReduce(Class<T> domainType) {
|
||||
|
||||
Assert.notNull(domainType, "DomainType must not be null!");
|
||||
|
||||
return new ReactiveMapReduceSupport<>(template, domainType, domainType, null, ALL_QUERY, null, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
* @since 2.1
|
||||
*/
|
||||
static class ReactiveMapReduceSupport<T>
|
||||
implements ReactiveMapReduce<T>, MapReduceWithOptions<T>, MapReduceWithCollection<T>, MapReduceWithProjection<T>,
|
||||
MapReduceWithQuery<T>, MapReduceWithReduceFunction<T>, MapReduceWithMapFunction<T> {
|
||||
|
||||
private final ReactiveMongoTemplate template;
|
||||
private final Class<?> domainType;
|
||||
private final Class<T> returnType;
|
||||
private final @Nullable String collection;
|
||||
private final Query query;
|
||||
private final @Nullable String mapFunction;
|
||||
private final @Nullable String reduceFunction;
|
||||
private final @Nullable MapReduceOptions options;
|
||||
|
||||
ReactiveMapReduceSupport(ReactiveMongoTemplate template, Class<?> domainType, Class<T> returnType,
|
||||
@Nullable String collection, Query query, @Nullable String mapFunction, @Nullable String reduceFunction,
|
||||
@Nullable MapReduceOptions options) {
|
||||
|
||||
this.template = template;
|
||||
this.domainType = domainType;
|
||||
this.returnType = returnType;
|
||||
this.collection = collection;
|
||||
this.query = query;
|
||||
this.mapFunction = mapFunction;
|
||||
this.reduceFunction = reduceFunction;
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javascript)
|
||||
* @see in org.springframework.data.mongodb.core.ExecutableMapReduceOperation.TerminatingMapReduce#all()
|
||||
*/
|
||||
@Override
|
||||
public Flux<T> all() {
|
||||
|
||||
return template.mapReduce(query, domainType, getCollectionName(), returnType, mapFunction, reduceFunction,
|
||||
options);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javascript)
|
||||
* @see in org.springframework.data.mongodb.core.ReactiveMapReduceOperation.MapReduceWithCollection#inCollection(java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public MapReduceWithProjection<T> inCollection(String collection) {
|
||||
|
||||
Assert.hasText(collection, "Collection name must not be null nor empty!");
|
||||
|
||||
return new ReactiveMapReduceSupport<>(template, domainType, returnType, collection, query, mapFunction,
|
||||
reduceFunction, options);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javascript)
|
||||
* @see in org.springframework.data.mongodb.core.ReactiveMapReduceOperation.MapReduceWithQuery#query(org.springframework.data.mongodb.core.query.Query)
|
||||
*/
|
||||
@Override
|
||||
public TerminatingMapReduce<T> matching(Query query) {
|
||||
|
||||
Assert.notNull(query, "Query must not be null!");
|
||||
|
||||
return new ReactiveMapReduceSupport<>(template, domainType, returnType, collection, query, mapFunction,
|
||||
reduceFunction, options);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javascript)
|
||||
* @see in org.springframework.data.mongodb.core.ReactiveMapReduceOperation.MapReduceWithProjection#as(java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public <R> MapReduceWithQuery<R> as(Class<R> resultType) {
|
||||
|
||||
Assert.notNull(resultType, "ResultType must not be null!");
|
||||
|
||||
return new ReactiveMapReduceSupport<>(template, domainType, resultType, collection, query, mapFunction,
|
||||
reduceFunction, options);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javascript)
|
||||
* @see in org.springframework.data.mongodb.core.ReactiveMapReduceOperation.MapReduceWithOptions#with(org.springframework.data.mongodb.core.mapreduce.MapReduceOptions)
|
||||
*/
|
||||
@Override
|
||||
public ReactiveMapReduce<T> with(MapReduceOptions options) {
|
||||
|
||||
Assert.notNull(options, "Options must not be null! Please consider empty MapReduceOptions#options() instead.");
|
||||
|
||||
return new ReactiveMapReduceSupport<>(template, domainType, returnType, collection, query, mapFunction,
|
||||
reduceFunction, options);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javascript)
|
||||
* @see in org.springframework.data.mongodb.core.ReactiveMapReduceOperation.MapReduceWithMapFunction#map(java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public MapReduceWithReduceFunction<T> map(String mapFunction) {
|
||||
|
||||
Assert.hasText(mapFunction, "MapFunction name must not be null nor empty!");
|
||||
|
||||
return new ReactiveMapReduceSupport<>(template, domainType, returnType, collection, query, mapFunction,
|
||||
reduceFunction, options);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javascript)
|
||||
* @see in org.springframework.data.mongodb.core.ReactiveMapReduceOperation.MapReduceWithReduceFunction#reduce(java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public ReactiveMapReduce<T> reduce(String reduceFunction) {
|
||||
|
||||
Assert.hasText(reduceFunction, "ReduceFunction name must not be null nor empty!");
|
||||
|
||||
return new ReactiveMapReduceSupport<>(template, domainType, returnType, collection, query, mapFunction,
|
||||
reduceFunction, options);
|
||||
}
|
||||
|
||||
private String getCollectionName() {
|
||||
return StringUtils.hasText(collection) ? collection : template.determineCollectionName(domainType);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright 2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.mongodb.core;
|
||||
|
||||
import org.reactivestreams.Publisher;
|
||||
import org.springframework.util.Assert;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.util.context.Context;
|
||||
|
||||
import com.mongodb.reactivestreams.client.ClientSession;
|
||||
|
||||
/**
|
||||
* {@link ReactiveMongoContext} utilizes and enriches the Reactor {@link Context} with information potentially required
|
||||
* for e.g. {@link ClientSession} handling and transactions.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
* @since 2.1
|
||||
* @see Mono#subscriberContext()
|
||||
* @see Context
|
||||
*/
|
||||
public class ReactiveMongoContext {
|
||||
|
||||
private static final Class<?> SESSION_KEY = ClientSession.class;
|
||||
|
||||
/**
|
||||
* Gets the {@code Mono<ClientSession>} from Reactor {@link reactor.util.context.Context}. The resulting {@link Mono}
|
||||
* emits the {@link ClientSession} if a session is associated with the current {@link reactor.util.context.Context
|
||||
* subscriber context}. If the context does not contain a session, the resulting {@link Mono} terminates empty (i.e.
|
||||
* without emitting a value).
|
||||
*
|
||||
* @return the {@link Mono} emitting the client session if present; otherwise the {@link Mono} terminates empty.
|
||||
*/
|
||||
public static Mono<ClientSession> getSession() {
|
||||
|
||||
return Mono.subscriberContext().filter(ctx -> ctx.hasKey(SESSION_KEY))
|
||||
.flatMap(ctx -> ctx.<Mono<ClientSession>> get(SESSION_KEY));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link ClientSession} into the Reactor {@link reactor.util.context.Context}.
|
||||
*
|
||||
* @param context must not be {@literal null}.
|
||||
* @param session must not be {@literal null}.
|
||||
* @return a new {@link Context}.
|
||||
* @see Context#put(Object, Object)
|
||||
*/
|
||||
public static Context setSession(Context context, Publisher<ClientSession> session) {
|
||||
|
||||
Assert.notNull(context, "Context must not be null!");
|
||||
Assert.notNull(session, "Session publisher must not be null!");
|
||||
|
||||
return context.put(SESSION_KEY, Mono.from(session));
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,6 @@ import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
@@ -27,12 +26,14 @@ import org.bson.Document;
|
||||
import org.reactivestreams.Publisher;
|
||||
import org.reactivestreams.Subscription;
|
||||
import org.springframework.data.geo.GeoResult;
|
||||
import org.springframework.data.mongodb.ReactiveMongoDatabaseFactory;
|
||||
import org.springframework.data.mongodb.core.aggregation.Aggregation;
|
||||
import org.springframework.data.mongodb.core.aggregation.AggregationOptions;
|
||||
import org.springframework.data.mongodb.core.aggregation.TypedAggregation;
|
||||
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
|
||||
import org.springframework.data.mongodb.core.convert.MongoConverter;
|
||||
import org.springframework.data.mongodb.core.index.ReactiveIndexOperations;
|
||||
import org.springframework.data.mongodb.core.mapreduce.MapReduceOptions;
|
||||
import org.springframework.data.mongodb.core.query.BasicQuery;
|
||||
import org.springframework.data.mongodb.core.query.Criteria;
|
||||
import org.springframework.data.mongodb.core.query.NearQuery;
|
||||
@@ -40,13 +41,14 @@ import org.springframework.data.mongodb.core.query.Query;
|
||||
import org.springframework.data.mongodb.core.query.Update;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
import com.mongodb.ClientSessionOptions;
|
||||
import com.mongodb.ReadPreference;
|
||||
import com.mongodb.client.result.DeleteResult;
|
||||
import com.mongodb.client.result.UpdateResult;
|
||||
import com.mongodb.reactivestreams.client.ClientSession;
|
||||
import com.mongodb.reactivestreams.client.MongoCollection;
|
||||
import com.mongodb.session.ClientSession;
|
||||
|
||||
/**
|
||||
* Interface that specifies a basic set of MongoDB operations executed in a reactive way.
|
||||
@@ -155,7 +157,7 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
* {@link ClientSession} when done.
|
||||
*
|
||||
* @param sessionProvider must not be {@literal null}.
|
||||
* @return new instance of {@link SessionScoped}. Never {@literal null}.
|
||||
* @return new instance of {@link ReactiveSessionScoped}. Never {@literal null}.
|
||||
* @since 2.1
|
||||
*/
|
||||
default ReactiveSessionScoped withSession(Supplier<ClientSession> sessionProvider) {
|
||||
@@ -168,40 +170,30 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
/**
|
||||
* Obtain a {@link ClientSession session} bound instance of {@link SessionScoped} binding a new {@link ClientSession}
|
||||
* with given {@literal sessionOptions} to each and every command issued against MongoDB.
|
||||
* <p />
|
||||
* <strong>Note:</strong> It is up to the caller to manage the {@link ClientSession} lifecycle. Use
|
||||
* {@link ReactiveSessionScoped#execute(ReactiveSessionCallback, Consumer)} to provide a hook for processing the
|
||||
* {@link ClientSession} when done.
|
||||
*
|
||||
* @param sessionOptions must not be {@literal null}.
|
||||
* @return new instance of {@link SessionScoped}. Never {@literal null}.
|
||||
* @return new instance of {@link ReactiveSessionScoped}. Never {@literal null}.
|
||||
* @since 2.1
|
||||
*/
|
||||
ReactiveSessionScoped withSession(ClientSessionOptions sessionOptions);
|
||||
|
||||
/**
|
||||
* Obtain a {@link ClientSession session} bound instance of {@link SessionScoped} binding the {@link ClientSession}
|
||||
* provided by the given {@link Supplier} to each and every command issued against MongoDB.
|
||||
* Obtain a {@link ClientSession session} bound instance of {@link ReactiveSessionScoped} binding the
|
||||
* {@link ClientSession} provided by the given {@link Publisher} to each and every command issued against MongoDB.
|
||||
* <p />
|
||||
* <strong>Note:</strong> It is up to the caller to manage the {@link ClientSession} lifecycle. Use the
|
||||
* {@literal onComplete} hook to potentially close the {@link ClientSession}.
|
||||
* <strong>Note:</strong> It is up to the caller to manage the {@link ClientSession} lifecycle. Use
|
||||
* {@link ReactiveSessionScoped#execute(ReactiveSessionCallback, Consumer)} to provide a hook for processing the
|
||||
* {@link ClientSession} when done.
|
||||
*
|
||||
* @param sessionProvider must not be {@literal null}.
|
||||
* @return new instance of {@link SessionScoped}. Never {@literal null}.
|
||||
* @return new instance of {@link ReactiveSessionScoped}. Never {@literal null}.
|
||||
* @since 2.1
|
||||
*/
|
||||
default ReactiveSessionScoped withSession(Publisher<ClientSession> sessionProvider) {
|
||||
|
||||
return new ReactiveSessionScoped() {
|
||||
|
||||
private final Mono<ClientSession> cachedSession = Mono.from(sessionProvider).cache();
|
||||
|
||||
@Override
|
||||
public <T> Flux<T> execute(ReactiveSessionCallback<T> action, Consumer<ClientSession> doFinally) {
|
||||
|
||||
return cachedSession.flatMapMany(session -> {
|
||||
return Flux.from(action.doInSession(ReactiveMongoOperations.this.withSession(session)))
|
||||
.doFinally((signalType) -> doFinally.accept(session));
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
ReactiveSessionScoped withSession(Publisher<ClientSession> sessionProvider);
|
||||
|
||||
/**
|
||||
* Obtain a {@link ClientSession} bound instance of {@link ReactiveMongoOperations}.
|
||||
@@ -214,6 +206,34 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
*/
|
||||
ReactiveMongoOperations withSession(ClientSession session);
|
||||
|
||||
/**
|
||||
* Initiate a new {@link ClientSession} and obtain a {@link ClientSession session} bound instance of
|
||||
* {@link ReactiveSessionScoped}. Starts the transaction and adds the {@link ClientSession} to each and every command
|
||||
* issued against MongoDB.
|
||||
* <p/>
|
||||
* Each {@link ReactiveSessionScoped#execute(ReactiveSessionCallback) execution} initiates a new managed transaction
|
||||
* that is {@link ClientSession#commitTransaction() committed} on success. Transactions are
|
||||
* {@link ClientSession#abortTransaction() rolled back} upon errors.
|
||||
*
|
||||
* @return new instance of {@link ReactiveSessionScoped}. Never {@literal null}.
|
||||
*/
|
||||
ReactiveSessionScoped inTransaction();
|
||||
|
||||
/**
|
||||
* Obtain a {@link ClientSession session} bound instance of {@link ReactiveSessionScoped}, start the transaction and
|
||||
* bind the {@link ClientSession} provided by the given {@link Publisher} to each and every command issued against
|
||||
* MongoDB.
|
||||
* <p/>
|
||||
* Each {@link ReactiveSessionScoped#execute(ReactiveSessionCallback) execution} initiates a new managed transaction
|
||||
* that is {@link ClientSession#commitTransaction() committed} on success. Transactions are
|
||||
* {@link ClientSession#abortTransaction() rolled back} upon errors.
|
||||
*
|
||||
* @param sessionProvider must not be {@literal null}.
|
||||
* @return new instance of {@link ReactiveSessionScoped}. Never {@literal null}.
|
||||
* @since 2.1
|
||||
*/
|
||||
ReactiveSessionScoped inTransaction(Publisher<ClientSession> sessionProvider);
|
||||
|
||||
/**
|
||||
* Create an uncapped collection with a name based on the provided entity class.
|
||||
*
|
||||
@@ -669,6 +689,160 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
<T> Mono<T> findAndModify(Query query, Update update, FindAndModifyOptions options, Class<T> entityClass,
|
||||
String collectionName);
|
||||
|
||||
/**
|
||||
* Triggers
|
||||
* <a href="https://docs.mongodb.com/manual/reference/method/db.collection.findOneAndReplace/">findOneAndReplace<a/>
|
||||
* to replace a single document matching {@link Criteria} of given {@link Query} with the {@code replacement}
|
||||
* document. <br />
|
||||
* Options are defaulted to {@link FindAndReplaceOptions#empty()}. <br />
|
||||
* <strong>NOTE:</strong> The replacement entity must not hold an {@literal id}.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional
|
||||
* fields specification. Must not be {@literal null}.
|
||||
* @param replacement the replacement document. Must not be {@literal null}.
|
||||
* @return the converted object that was updated or {@link Mono#empty()}, if not found.
|
||||
* @since 2.1
|
||||
*/
|
||||
default <T> Mono<T> findAndReplace(Query query, T replacement) {
|
||||
return findAndReplace(query, replacement, FindAndReplaceOptions.empty());
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers
|
||||
* <a href="https://docs.mongodb.com/manual/reference/method/db.collection.findOneAndReplace/">findOneAndReplace<a/>
|
||||
* to replace a single document matching {@link Criteria} of given {@link Query} with the {@code replacement}
|
||||
* document. <br />
|
||||
* Options are defaulted to {@link FindAndReplaceOptions#empty()}. <br />
|
||||
* <strong>NOTE:</strong> The replacement entity must not hold an {@literal id}.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional
|
||||
* fields specification. Must not be {@literal null}.
|
||||
* @param replacement the replacement document. Must not be {@literal null}.
|
||||
* @param collectionName the collection to query. Must not be {@literal null}.
|
||||
* @return the converted object that was updated or {@link Mono#empty()}, if not found.
|
||||
* @since 2.1
|
||||
*/
|
||||
default <T> Mono<T> findAndReplace(Query query, T replacement, String collectionName) {
|
||||
return findAndReplace(query, replacement, FindAndReplaceOptions.empty(), collectionName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers
|
||||
* <a href="https://docs.mongodb.com/manual/reference/method/db.collection.findOneAndReplace/">findOneAndReplace<a/>
|
||||
* to replace a single document matching {@link Criteria} of given {@link Query} with the {@code replacement} document
|
||||
* taking {@link FindAndReplaceOptions} into account. <br />
|
||||
* <strong>NOTE:</strong> The replacement entity must not hold an {@literal id}.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional
|
||||
* fields specification. Must not be {@literal null}.
|
||||
* @param replacement the replacement document. Must not be {@literal null}.
|
||||
* @param options the {@link FindAndModifyOptions} holding additional information. Must not be {@literal null}.
|
||||
* @return the converted object that was updated or {@link Mono#empty()}, if not found. Depending on the value of
|
||||
* {@link FindAndReplaceOptions#isReturnNew()} this will either be the object as it was before the update or
|
||||
* as it is after the update.
|
||||
* @since 2.1
|
||||
*/
|
||||
default <T> Mono<T> findAndReplace(Query query, T replacement, FindAndReplaceOptions options) {
|
||||
return findAndReplace(query, replacement, options, getCollectionName(ClassUtils.getUserClass(replacement)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers
|
||||
* <a href="https://docs.mongodb.com/manual/reference/method/db.collection.findOneAndReplace/">findOneAndReplace<a/>
|
||||
* to replace a single document matching {@link Criteria} of given {@link Query} with the {@code replacement} document
|
||||
* taking {@link FindAndReplaceOptions} into account. <br />
|
||||
* <strong>NOTE:</strong> The replacement entity must not hold an {@literal id}.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional
|
||||
* fields specification. Must not be {@literal null}.
|
||||
* @param replacement the replacement document. Must not be {@literal null}.
|
||||
* @param options the {@link FindAndModifyOptions} holding additional information. Must not be {@literal null}.
|
||||
* @return the converted object that was updated or {@link Mono#empty()}, if not found. Depending on the value of
|
||||
* {@link FindAndReplaceOptions#isReturnNew()} this will either be the object as it was before the update or
|
||||
* as it is after the update.
|
||||
* @since 2.1
|
||||
*/
|
||||
default <T> Mono<T> findAndReplace(Query query, T replacement, FindAndReplaceOptions options, String collectionName) {
|
||||
|
||||
Assert.notNull(replacement, "Replacement must not be null!");
|
||||
return findAndReplace(query, replacement, options, (Class<T>) ClassUtils.getUserClass(replacement), collectionName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers
|
||||
* <a href="https://docs.mongodb.com/manual/reference/method/db.collection.findOneAndReplace/">findOneAndReplace<a/>
|
||||
* to replace a single document matching {@link Criteria} of given {@link Query} with the {@code replacement} document
|
||||
* taking {@link FindAndReplaceOptions} into account. <br />
|
||||
* <strong>NOTE:</strong> The replacement entity must not hold an {@literal id}.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional
|
||||
* fields specification. Must not be {@literal null}.
|
||||
* @param replacement the replacement document. Must not be {@literal null}.
|
||||
* @param options the {@link FindAndModifyOptions} holding additional information. Must not be {@literal null}.
|
||||
* @param entityType the parametrized type. Must not be {@literal null}.
|
||||
* @param collectionName the collection to query. Must not be {@literal null}.
|
||||
* @return the converted object that was updated or {@link Mono#empty()}, if not found. Depending on the value of
|
||||
* {@link FindAndReplaceOptions#isReturnNew()} this will either be the object as it was before the update or
|
||||
* as it is after the update.
|
||||
* @since 2.1
|
||||
*/
|
||||
default <T> Mono<T> findAndReplace(Query query, T replacement, FindAndReplaceOptions options, Class<T> entityType,
|
||||
String collectionName) {
|
||||
|
||||
return findAndReplace(query, replacement, options, entityType, collectionName, entityType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers
|
||||
* <a href="https://docs.mongodb.com/manual/reference/method/db.collection.findOneAndReplace/">findOneAndReplace<a/>
|
||||
* to replace a single document matching {@link Criteria} of given {@link Query} with the {@code replacement} document
|
||||
* taking {@link FindAndReplaceOptions} into account. <br />
|
||||
* <strong>NOTE:</strong> The replacement entity must not hold an {@literal id}.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional
|
||||
* fields specification. Must not be {@literal null}.
|
||||
* @param replacement the replacement document. Must not be {@literal null}.
|
||||
* @param options the {@link FindAndModifyOptions} holding additional information. Must not be {@literal null}.
|
||||
* @param entityType the type used for mapping the {@link Query} to domain type fields and deriving the collection
|
||||
* from. Must not be {@literal null}.
|
||||
* @param resultType the parametrized type projection return type. Must not be {@literal null}, use the domain type of
|
||||
* {@code Object.class} instead.
|
||||
* @return the converted object that was updated or {@link Mono#empty()}, if not found. Depending on the value of
|
||||
* {@link FindAndReplaceOptions#isReturnNew()} this will either be the object as it was before the update or
|
||||
* as it is after the update.
|
||||
* @since 2.1
|
||||
*/
|
||||
default <S, T> Mono<T> findAndReplace(Query query, S replacement, FindAndReplaceOptions options, Class<S> entityType,
|
||||
Class<T> resultType) {
|
||||
|
||||
return findAndReplace(query, replacement, options, entityType,
|
||||
getCollectionName(ClassUtils.getUserClass(entityType)), resultType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers
|
||||
* <a href="https://docs.mongodb.com/manual/reference/method/db.collection.findOneAndReplace/">findOneAndReplace<a/>
|
||||
* to replace a single document matching {@link Criteria} of given {@link Query} with the {@code replacement} document
|
||||
* taking {@link FindAndReplaceOptions} into account. <br />
|
||||
* <strong>NOTE:</strong> The replacement entity must not hold an {@literal id}.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional
|
||||
* fields specification. Must not be {@literal null}.
|
||||
* @param replacement the replacement document. Must not be {@literal null}.
|
||||
* @param options the {@link FindAndModifyOptions} holding additional information. Must not be {@literal null}.
|
||||
* @param entityType the type used for mapping the {@link Query} to domain type fields and deriving the collection
|
||||
* from. Must not be {@literal null}.
|
||||
* @param collectionName the collection to query. Must not be {@literal null}.
|
||||
* @param resultType resultType the parametrized type projection return type. Must not be {@literal null}, use the
|
||||
* domain type of {@code Object.class} instead.
|
||||
* @return the converted object that was updated or {@link Mono#empty()}, if not found. Depending on the value of
|
||||
* {@link FindAndReplaceOptions#isReturnNew()} this will either be the object as it was before the update or
|
||||
* as it is after the update.
|
||||
* @since 2.1
|
||||
*/
|
||||
<S, T> Mono<T> findAndReplace(Query query, S replacement, FindAndReplaceOptions options, Class<S> entityType,
|
||||
String collectionName, Class<T> resultType);
|
||||
|
||||
/**
|
||||
* Map the results of an ad-hoc query on the collection for the entity type to a single instance of an object of the
|
||||
* specified type. The first document that matches the query is returned and also removed from the collection in the
|
||||
@@ -753,7 +927,7 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
* Insert is used to initially store the object into the database. To update an existing object use the save method.
|
||||
*
|
||||
* @param objectToSave the object to store in the collection. Must not be {@literal null}.
|
||||
* @return the saved object.
|
||||
* @return the inserted object.
|
||||
*/
|
||||
<T> Mono<T> insert(T objectToSave);
|
||||
|
||||
@@ -767,7 +941,7 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
*
|
||||
* @param objectToSave the object to store in the collection. Must not be {@literal null}.
|
||||
* @param collectionName name of the collection to store the object in. Must not be {@literal null}.
|
||||
* @return the saved object.
|
||||
* @return the inserted object.
|
||||
*/
|
||||
<T> Mono<T> insert(T objectToSave, String collectionName);
|
||||
|
||||
@@ -776,7 +950,7 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
*
|
||||
* @param batchToSave the batch of objects to save. Must not be {@literal null}.
|
||||
* @param entityClass class that determines the collection to use. Must not be {@literal null}.
|
||||
* @return the saved objects.
|
||||
* @return the inserted objects .
|
||||
*/
|
||||
<T> Flux<T> insert(Collection<? extends T> batchToSave, Class<?> entityClass);
|
||||
|
||||
@@ -785,7 +959,7 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
*
|
||||
* @param batchToSave the list of objects to save. Must not be {@literal null}.
|
||||
* @param collectionName name of the collection to store the object in. Must not be {@literal null}.
|
||||
* @return the saved objects.
|
||||
* @return the inserted objects.
|
||||
*/
|
||||
<T> Flux<T> insert(Collection<? extends T> batchToSave, String collectionName);
|
||||
|
||||
@@ -813,7 +987,7 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
* Insert is used to initially store the object into the database. To update an existing object use the save method.
|
||||
*
|
||||
* @param objectToSave the object to store in the collection. Must not be {@literal null}.
|
||||
* @return the saved object.
|
||||
* @return the inserted objects.
|
||||
*/
|
||||
<T> Mono<T> insert(Mono<? extends T> objectToSave);
|
||||
|
||||
@@ -822,7 +996,7 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
*
|
||||
* @param batchToSave the publisher which provides objects to save. Must not be {@literal null}.
|
||||
* @param entityClass class that determines the collection to use. Must not be {@literal null}.
|
||||
* @return the saved objects.
|
||||
* @return the inserted objects.
|
||||
*/
|
||||
<T> Flux<T> insertAll(Mono<? extends Collection<? extends T>> batchToSave, Class<?> entityClass);
|
||||
|
||||
@@ -831,7 +1005,7 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
*
|
||||
* @param batchToSave the publisher which provides objects to save. Must not be {@literal null}.
|
||||
* @param collectionName name of the collection to store the object in. Must not be {@literal null}.
|
||||
* @return the saved objects.
|
||||
* @return the inserted objects.
|
||||
*/
|
||||
<T> Flux<T> insertAll(Mono<? extends Collection<? extends T>> batchToSave, String collectionName);
|
||||
|
||||
@@ -840,7 +1014,7 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
* class.
|
||||
*
|
||||
* @param objectsToSave the publisher which provides objects to save. Must not be {@literal null}.
|
||||
* @return the saved objects.
|
||||
* @return the inserted objects.
|
||||
*/
|
||||
<T> Flux<T> insertAll(Mono<? extends Collection<? extends T>> objectsToSave);
|
||||
|
||||
@@ -1183,7 +1357,57 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
<T> Flux<T> tail(Query query, Class<T> entityClass, String collectionName);
|
||||
|
||||
/**
|
||||
* Subscribe to a MongoDB <a href="https://docs.mongodb.com/manual/changeStreams/">Change Streams</a> via the reactive
|
||||
* Subscribe to a MongoDB <a href="https://docs.mongodb.com/manual/changeStreams/">Change Stream</a> for all events in
|
||||
* the configured default database via the reactive infrastructure. Use the optional provided {@link Aggregation} to
|
||||
* filter events. The stream will not be completed unless the {@link org.reactivestreams.Subscription} is
|
||||
* {@link Subscription#cancel() canceled}.
|
||||
* <p />
|
||||
* The {@link ChangeStreamEvent#getBody()} is mapped to the {@literal resultType} while the
|
||||
* {@link ChangeStreamEvent#getRaw()} contains the unmodified payload.
|
||||
* <p />
|
||||
* Use {@link ChangeStreamOptions} to set arguments like {@link ChangeStreamOptions#getResumeToken() the resumseToken}
|
||||
* for resuming change streams.
|
||||
*
|
||||
* @param options must not be {@literal null}. Use {@link ChangeStreamOptions#empty()}.
|
||||
* @param targetType the result type to use.
|
||||
* @param <T>
|
||||
* @return the {@link Flux} emitting {@link ChangeStreamEvent events} as they arrive.
|
||||
* @since 2.1
|
||||
* @see ReactiveMongoDatabaseFactory#getMongoDatabase()
|
||||
* @see ChangeStreamOptions#getFilter()
|
||||
*/
|
||||
default <T> Flux<ChangeStreamEvent<T>> changeStream(ChangeStreamOptions options, Class<T> targetType) {
|
||||
return changeStream(null, options, targetType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribe to a MongoDB <a href="https://docs.mongodb.com/manual/changeStreams/">Change Stream</a> for all events in
|
||||
* the given collection via the reactive infrastructure. Use the optional provided {@link Aggregation} to filter
|
||||
* events. The stream will not be completed unless the {@link org.reactivestreams.Subscription} is
|
||||
* {@link Subscription#cancel() canceled}.
|
||||
* <p />
|
||||
* The {@link ChangeStreamEvent#getBody()} is mapped to the {@literal resultType} while the
|
||||
* {@link ChangeStreamEvent#getRaw()} contains the unmodified payload.
|
||||
* <p />
|
||||
* Use {@link ChangeStreamOptions} to set arguments like {@link ChangeStreamOptions#getResumeToken() the resumseToken}
|
||||
* for resuming change streams.
|
||||
*
|
||||
* @param collectionName the collection to watch. Can be {@literal null} to watch all collections.
|
||||
* @param options must not be {@literal null}. Use {@link ChangeStreamOptions#empty()}.
|
||||
* @param targetType the result type to use.
|
||||
* @param <T>
|
||||
* @return the {@link Flux} emitting {@link ChangeStreamEvent events} as they arrive.
|
||||
* @since 2.1
|
||||
* @see ChangeStreamOptions#getFilter()
|
||||
*/
|
||||
default <T> Flux<ChangeStreamEvent<T>> changeStream(@Nullable String collectionName, ChangeStreamOptions options,
|
||||
Class<T> targetType) {
|
||||
|
||||
return changeStream(null, collectionName, options, targetType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribe to a MongoDB <a href="https://docs.mongodb.com/manual/changeStreams/">Change Stream</a> via the reactive
|
||||
* infrastructure. Use the optional provided {@link Aggregation} to filter events. The stream will not be completed
|
||||
* unless the {@link org.reactivestreams.Subscription} is {@link Subscription#cancel() canceled}.
|
||||
* <p />
|
||||
@@ -1193,38 +1417,53 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
* Use {@link ChangeStreamOptions} to set arguments like {@link ChangeStreamOptions#getResumeToken() the resumseToken}
|
||||
* for resuming change streams.
|
||||
*
|
||||
* @param filter can be {@literal null}.
|
||||
* @param resultType must not be {@literal null}.
|
||||
* @param options must not be {@literal null}.
|
||||
* @param collectionName must not be {@literal null} nor empty.
|
||||
* @param database the database to watch. Can be {@literal null}, uses configured default if so.
|
||||
* @param collectionName the collection to watch. Can be {@literal null}, watches all collections if so.
|
||||
* @param options must not be {@literal null}. Use {@link ChangeStreamOptions#empty()}.
|
||||
* @param targetType the result type to use.
|
||||
* @param <T>
|
||||
* @return
|
||||
* @return the {@link Flux} emitting {@link ChangeStreamEvent events} as they arrive.
|
||||
* @since 2.1
|
||||
* @see ChangeStreamOptions#getFilter()
|
||||
*/
|
||||
<T> Flux<ChangeStreamEvent<T>> changeStream(@Nullable Aggregation filter, Class<T> resultType,
|
||||
ChangeStreamOptions options, String collectionName);
|
||||
<T> Flux<ChangeStreamEvent<T>> changeStream(@Nullable String database, @Nullable String collectionName,
|
||||
ChangeStreamOptions options, Class<T> targetType);
|
||||
|
||||
/**
|
||||
* Subscribe to a MongoDB <a href="https://docs.mongodb.com/manual/changeStreams/">Change Streams</a> via the reactive
|
||||
* infrastructure. Use the optional provided aggregation chain to filter events. The stream will not be completed
|
||||
* unless the {@link org.reactivestreams.Subscription} is {@link Subscription#cancel() canceled}.
|
||||
* <p />
|
||||
* The {@link ChangeStreamEvent#getBody()} is mapped to the {@literal resultType} while the
|
||||
* {@link ChangeStreamEvent#getRaw()} contains the unmodified payload.
|
||||
* <p />
|
||||
* Use {@link ChangeStreamOptions} to set arguments like {@link ChangeStreamOptions#getResumeToken() the resumeToken}
|
||||
* for resuming change streams.
|
||||
* Execute a map-reduce operation. Use {@link MapReduceOptions} to optionally specify an output collection and other
|
||||
* args.
|
||||
*
|
||||
* @param filter can be empty, must not be {@literal null}.
|
||||
* @param resultType must not be {@literal null}.
|
||||
* @param options must not be {@literal null}.
|
||||
* @param collectionName must not be {@literal null} nor empty.
|
||||
* @param <T>
|
||||
* @return
|
||||
* @param filterQuery the selection criteria for the documents going input to the map function. Must not be
|
||||
* {@literal null}.
|
||||
* @param domainType source type used to determine the input collection name and map the filter {@link Query} against.
|
||||
* Must not be {@literal null}.
|
||||
* @param resultType the mapping target of the operations result documents. Must not be {@literal null}.
|
||||
* @param mapFunction the JavaScript map function. Must not be {@literal null}.
|
||||
* @param reduceFunction the JavaScript reduce function. Must not be {@literal null}.
|
||||
* @param options additional options like output collection. Must not be {@literal null}.
|
||||
* @return a {@link Flux} emitting the result document sequence. Never {@literal null}.
|
||||
* @since 2.1
|
||||
*/
|
||||
<T> Flux<ChangeStreamEvent<T>> changeStream(List<Document> filter, Class<T> resultType, ChangeStreamOptions options,
|
||||
String collectionName);
|
||||
<T> Flux<T> mapReduce(Query filterQuery, Class<?> domainType, Class<T> resultType, String mapFunction,
|
||||
String reduceFunction, MapReduceOptions options);
|
||||
|
||||
/**
|
||||
* Execute a map-reduce operation. Use {@link MapReduceOptions} to optionally specify an output collection and other
|
||||
* args.
|
||||
*
|
||||
* @param filterQuery the selection criteria for the documents going input to the map function. Must not be
|
||||
* {@literal null}.
|
||||
* @param domainType source type used to map the filter {@link Query} against. Must not be {@literal null}.
|
||||
* @param inputCollectionName the input collection.
|
||||
* @param resultType the mapping target of the operations result documents. Must not be {@literal null}.
|
||||
* @param mapFunction the JavaScript map function. Must not be {@literal null}.
|
||||
* @param reduceFunction the JavaScript reduce function. Must not be {@literal null}.
|
||||
* @param options additional options like output collection. Must not be {@literal null}.
|
||||
* @return a {@link Flux} emitting the result document sequence. Never {@literal null}.
|
||||
* @since 2.1
|
||||
*/
|
||||
<T> Flux<T> mapReduce(Query filterQuery, Class<?> domainType, String inputCollectionName, Class<T> resultType,
|
||||
String mapFunction, String reduceFunction, MapReduceOptions options);
|
||||
|
||||
/**
|
||||
* Returns the underlying {@link MongoConverter}.
|
||||
@@ -1233,4 +1472,13 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
*/
|
||||
MongoConverter getConverter();
|
||||
|
||||
/**
|
||||
* The collection name used for the specified class by this template.
|
||||
*
|
||||
* @param entityClass must not be {@literal null}.
|
||||
* @return
|
||||
* @since 2.1
|
||||
*/
|
||||
String getCollectionName(Class<?> entityClass);
|
||||
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -19,12 +19,12 @@ import org.reactivestreams.Publisher;
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
|
||||
/**
|
||||
* Callback interface for executing operations within a {@link com.mongodb.session.ClientSession} using reactive
|
||||
* infrastructure.
|
||||
* Callback interface for executing operations within a {@link com.mongodb.reactivestreams.client.ClientSession} using
|
||||
* reactive infrastructure.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.1
|
||||
* @see com.mongodb.session.ClientSession
|
||||
* @see com.mongodb.reactivestreams.client.ClientSession
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface ReactiveSessionCallback<T> {
|
||||
|
||||
@@ -19,7 +19,7 @@ import reactor.core.publisher.Flux;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import com.mongodb.session.ClientSession;
|
||||
import com.mongodb.reactivestreams.client.ClientSession;
|
||||
|
||||
/**
|
||||
* Gateway interface to execute {@link ClientSession} bound operations against MongoDB via a
|
||||
@@ -39,7 +39,7 @@ public interface ReactiveSessionScoped {
|
||||
*
|
||||
* @param action callback object that specifies the MongoDB action the callback action. Must not be {@literal null}.
|
||||
* @param <T> return type.
|
||||
* @return a result object returned by the action. Can be {@literal null}.
|
||||
* @return a result object returned by the action, can be {@link Flux#empty()}.
|
||||
*/
|
||||
default <T> Flux<T> execute(ReactiveSessionCallback<T> action) {
|
||||
return execute(action, (session) -> {});
|
||||
@@ -56,7 +56,7 @@ public interface ReactiveSessionScoped {
|
||||
* This {@link Consumer} is guaranteed to be notified in any case (successful and exceptional outcome of
|
||||
* {@link ReactiveSessionCallback}).
|
||||
* @param <T> return type.
|
||||
* @return a result object returned by the action. Can be {@literal null}.
|
||||
* @return a result object returned by the action, can be {@link Flux#empty()}.
|
||||
*/
|
||||
<T> Flux<T> execute(ReactiveSessionCallback<T> action, Consumer<ClientSession> doFinally);
|
||||
}
|
||||
|
||||
@@ -18,12 +18,13 @@ package org.springframework.data.mongodb.core;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
import org.springframework.data.mongodb.core.query.Update;
|
||||
|
||||
import com.mongodb.client.result.UpdateResult;
|
||||
|
||||
/**
|
||||
* {@link ReactiveUpdateOperation} allows creation and execution of reactive MongoDB update / findAndModify operations
|
||||
* in a fluent API style. <br />
|
||||
* {@link ReactiveUpdateOperation} allows creation and execution of reactive MongoDB update / findAndModify /
|
||||
* findAndReplace operations in a fluent API style. <br />
|
||||
* The starting {@literal domainType} is used for mapping the {@link Query} provided via {@code matching}, as well as
|
||||
* the {@link org.springframework.data.mongodb.core.query.Update} via {@code apply} into the MongoDB specific
|
||||
* representations. The collection to operate on is by default derived from the initial {@literal domainType} and can be
|
||||
@@ -68,6 +69,22 @@ public interface ReactiveUpdateOperation {
|
||||
Mono<T> findAndModify();
|
||||
}
|
||||
|
||||
/**
|
||||
* Compose findAndReplace execution by calling one of the terminating methods.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 2.1
|
||||
*/
|
||||
interface TerminatingFindAndReplace<T> {
|
||||
|
||||
/**
|
||||
* Find, replace and return the first matching document.
|
||||
*
|
||||
* @return {@link Mono#empty()} if nothing found. Never {@literal null}.
|
||||
*/
|
||||
Mono<T> findAndReplace();
|
||||
}
|
||||
|
||||
/**
|
||||
* Compose update execution by calling one of the terminating methods.
|
||||
*/
|
||||
@@ -108,6 +125,16 @@ public interface ReactiveUpdateOperation {
|
||||
* @throws IllegalArgumentException if update is {@literal null}.
|
||||
*/
|
||||
TerminatingUpdate<T> apply(org.springframework.data.mongodb.core.query.Update update);
|
||||
|
||||
/**
|
||||
* Specify {@code replacement} object.
|
||||
*
|
||||
* @param replacement must not be {@literal null}.
|
||||
* @return new instance of {@link FindAndReplaceOptions}.
|
||||
* @throws IllegalArgumentException if options is {@literal null}.
|
||||
* @since 2.1
|
||||
*/
|
||||
FindAndReplaceWithProjection<T> replaceWith(T replacement);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -157,5 +184,45 @@ public interface ReactiveUpdateOperation {
|
||||
TerminatingFindAndModify<T> withOptions(FindAndModifyOptions options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Define {@link FindAndReplaceOptions}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Christoph Strobl
|
||||
* @since 2.1
|
||||
*/
|
||||
interface FindAndReplaceWithOptions<T> extends TerminatingFindAndReplace<T> {
|
||||
|
||||
/**
|
||||
* Explicitly define {@link FindAndReplaceOptions} for the {@link Update}.
|
||||
*
|
||||
* @param options must not be {@literal null}.
|
||||
* @return new instance of {@link FindAndReplaceOptions}.
|
||||
* @throws IllegalArgumentException if options is {@literal null}.
|
||||
*/
|
||||
FindAndReplaceWithProjection<T> withOptions(FindAndReplaceOptions options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Result type override (Optional).
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.1
|
||||
*/
|
||||
interface FindAndReplaceWithProjection<T> extends FindAndReplaceWithOptions<T> {
|
||||
|
||||
/**
|
||||
* Define the target type fields should be mapped to. <br />
|
||||
* Skip this step if you are anyway only interested in the original domain type.
|
||||
*
|
||||
* @param resultType must not be {@literal null}.
|
||||
* @param <R> result type.
|
||||
* @return new instance of {@link FindAndReplaceWithProjection}.
|
||||
* @throws IllegalArgumentException if resultType is {@literal null}.
|
||||
*/
|
||||
<R> FindAndReplaceWithOptions<R> as(Class<R> resultType);
|
||||
|
||||
}
|
||||
|
||||
interface ReactiveUpdate<T> extends UpdateWithCollection<T>, UpdateWithQuery<T>, UpdateWithUpdate<T> {}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import lombok.experimental.FieldDefaults;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
@@ -50,20 +51,24 @@ class ReactiveUpdateOperationSupport implements ReactiveUpdateOperation {
|
||||
|
||||
Assert.notNull(domainType, "DomainType must not be null!");
|
||||
|
||||
return new ReactiveUpdateSupport<>(template, domainType, ALL_QUERY, null, null, null);
|
||||
return new ReactiveUpdateSupport<>(template, domainType, ALL_QUERY, null, null, null, null, null, domainType);
|
||||
}
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
|
||||
static class ReactiveUpdateSupport<T>
|
||||
implements ReactiveUpdate<T>, UpdateWithCollection<T>, UpdateWithQuery<T>, TerminatingUpdate<T> {
|
||||
implements ReactiveUpdate<T>, UpdateWithCollection<T>, UpdateWithQuery<T>, TerminatingUpdate<T>,
|
||||
FindAndReplaceWithOptions<T>, FindAndReplaceWithProjection<T>, TerminatingFindAndReplace<T> {
|
||||
|
||||
@NonNull ReactiveMongoTemplate template;
|
||||
@NonNull Class<T> domainType;
|
||||
@NonNull Class<?> domainType;
|
||||
Query query;
|
||||
org.springframework.data.mongodb.core.query.Update update;
|
||||
String collection;
|
||||
FindAndModifyOptions options;
|
||||
@Nullable String collection;
|
||||
@Nullable FindAndModifyOptions findAndModifyOptions;
|
||||
@Nullable FindAndReplaceOptions findAndReplaceOptions;
|
||||
@Nullable Object replacement;
|
||||
@NonNull Class<T> targetType;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
@@ -74,7 +79,8 @@ class ReactiveUpdateOperationSupport implements ReactiveUpdateOperation {
|
||||
|
||||
Assert.notNull(update, "Update must not be null!");
|
||||
|
||||
return new ReactiveUpdateSupport<>(template, domainType, query, update, collection, options);
|
||||
return new ReactiveUpdateSupport<>(template, domainType, query, update, collection, findAndModifyOptions,
|
||||
findAndReplaceOptions, replacement, targetType);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -86,7 +92,8 @@ class ReactiveUpdateOperationSupport implements ReactiveUpdateOperation {
|
||||
|
||||
Assert.hasText(collection, "Collection must not be null nor empty!");
|
||||
|
||||
return new ReactiveUpdateSupport<>(template, domainType, query, update, collection, options);
|
||||
return new ReactiveUpdateSupport<>(template, domainType, query, update, collection, findAndModifyOptions,
|
||||
findAndReplaceOptions, replacement, targetType);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -116,7 +123,18 @@ class ReactiveUpdateOperationSupport implements ReactiveUpdateOperation {
|
||||
|
||||
String collectionName = getCollectionName();
|
||||
|
||||
return template.findAndModify(query, update, options, domainType, collectionName);
|
||||
return template.findAndModify(query, update, findAndModifyOptions, targetType, collectionName);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.ReactiveUpdateOperation.TerminatingFindAndReplace#findAndReplace()
|
||||
*/
|
||||
@Override
|
||||
public Mono<T> findAndReplace() {
|
||||
return template.findAndReplace(query, replacement,
|
||||
findAndReplaceOptions != null ? findAndReplaceOptions : new FindAndReplaceOptions(), (Class) domainType,
|
||||
getCollectionName(), targetType);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -128,7 +146,8 @@ class ReactiveUpdateOperationSupport implements ReactiveUpdateOperation {
|
||||
|
||||
Assert.notNull(query, "Query must not be null!");
|
||||
|
||||
return new ReactiveUpdateSupport<>(template, domainType, query, update, collection, options);
|
||||
return new ReactiveUpdateSupport<>(template, domainType, query, update, collection, findAndModifyOptions,
|
||||
findAndReplaceOptions, replacement, targetType);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -149,7 +168,47 @@ class ReactiveUpdateOperationSupport implements ReactiveUpdateOperation {
|
||||
|
||||
Assert.notNull(options, "Options must not be null!");
|
||||
|
||||
return new ReactiveUpdateSupport<>(template, domainType, query, update, collection, options);
|
||||
return new ReactiveUpdateSupport<>(template, domainType, query, update, collection, options,
|
||||
findAndReplaceOptions, replacement, targetType);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.ReactiveUpdateOperation.UpdateWithUpdate#replaceWith(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public FindAndReplaceWithProjection<T> replaceWith(T replacement) {
|
||||
|
||||
Assert.notNull(replacement, "Replacement must not be null!");
|
||||
|
||||
return new ReactiveUpdateSupport<>(template, domainType, query, update, collection, findAndModifyOptions,
|
||||
findAndReplaceOptions, replacement, targetType);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.ReactiveUpdateOperation.FindAndReplaceWithOptions#withOptions(org.springframework.data.mongodb.core.FindAndReplaceOptions)
|
||||
*/
|
||||
@Override
|
||||
public FindAndReplaceWithProjection<T> withOptions(FindAndReplaceOptions options) {
|
||||
|
||||
Assert.notNull(options, "Options must not be null!");
|
||||
|
||||
return new ReactiveUpdateSupport<>(template, domainType, query, update, collection, findAndModifyOptions, options,
|
||||
replacement, targetType);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.ReactiveUpdateOperation.FindAndReplaceWithProjection#as(java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public <R> FindAndReplaceWithOptions<R> as(Class<R> resultType) {
|
||||
|
||||
Assert.notNull(resultType, "ResultType must not be null!");
|
||||
|
||||
return new ReactiveUpdateSupport<>(template, domainType, query, update, collection, findAndModifyOptions,
|
||||
findAndReplaceOptions, replacement, resultType);
|
||||
}
|
||||
|
||||
private Mono<UpdateResult> doUpdate(boolean multi, boolean upsert) {
|
||||
|
||||
@@ -25,6 +25,7 @@ import org.springframework.lang.Nullable;
|
||||
* @since 2.1
|
||||
* @see com.mongodb.session.ClientSession
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface SessionCallback<T> {
|
||||
|
||||
/**
|
||||
|
||||
@@ -19,7 +19,7 @@ import java.util.function.Consumer;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
import com.mongodb.session.ClientSession;
|
||||
import com.mongodb.client.ClientSession;
|
||||
|
||||
/**
|
||||
* Gateway interface to execute {@link ClientSession} bound operations against MongoDB via a {@link SessionCallback}.
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* Copyright 2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.mongodb.core;
|
||||
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
|
||||
import com.mongodb.ClientSessionOptions;
|
||||
import com.mongodb.ConnectionString;
|
||||
import com.mongodb.DB;
|
||||
import com.mongodb.client.ClientSession;
|
||||
import com.mongodb.client.MongoClient;
|
||||
import com.mongodb.client.MongoClients;
|
||||
import com.mongodb.client.MongoDatabase;
|
||||
|
||||
/**
|
||||
* Factory to create {@link MongoDatabase} instances from a {@link MongoClient} instance.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.1
|
||||
*/
|
||||
public class SimpleMongoClientDbFactory extends MongoDbFactorySupport<MongoClient> implements DisposableBean {
|
||||
|
||||
/**
|
||||
* Creates a new {@link SimpleMongoClientDbFactory} instance for the given {@code connectionString}.
|
||||
*
|
||||
* @param connectionString connection coordinates for a database connection. Must contain a database name and must not
|
||||
* be {@literal null} or empty.
|
||||
* @see <a href="https://docs.mongodb.com/manual/reference/connection-string/">MongoDB Connection String reference</a>
|
||||
*/
|
||||
public SimpleMongoClientDbFactory(String connectionString) {
|
||||
this(new ConnectionString(connectionString));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link SimpleMongoClientDbFactory} instance from the given {@link MongoClient}.
|
||||
*
|
||||
* @param connectionString connection coordinates for a database connection. Must contain also a database name and not
|
||||
* be {@literal null}.
|
||||
*/
|
||||
public SimpleMongoClientDbFactory(ConnectionString connectionString) {
|
||||
this(MongoClients.create(connectionString), connectionString.getDatabase(), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link SimpleMongoClientDbFactory} instance from the given {@link MongoClient}.
|
||||
*
|
||||
* @param mongoClient must not be {@literal null}.
|
||||
* @param databaseName must not be {@literal null} or empty.
|
||||
*/
|
||||
public SimpleMongoClientDbFactory(MongoClient mongoClient, String databaseName) {
|
||||
this(mongoClient, databaseName, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link SimpleMongoClientDbFactory} instance from the given {@link MongoClient}.
|
||||
*
|
||||
* @param mongoClient must not be {@literal null}.
|
||||
* @param databaseName must not be {@literal null} or empty.
|
||||
* @param mongoInstanceCreated
|
||||
*/
|
||||
private SimpleMongoClientDbFactory(MongoClient mongoClient, String databaseName, boolean mongoInstanceCreated) {
|
||||
super(mongoClient, databaseName, mongoInstanceCreated, new MongoExceptionTranslator());
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.MongoDbFactory#getLegacyDb()
|
||||
*/
|
||||
@Override
|
||||
public DB getLegacyDb() {
|
||||
|
||||
throw new UnsupportedOperationException(String.format(
|
||||
"%s does not support legacy DBObject API! Please consider using SimpleMongoDbFactory for that purpose.",
|
||||
MongoClient.class));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.MongoDbFactory#getSession(com.mongodb.ClientSessionOptions)
|
||||
*/
|
||||
@Override
|
||||
public ClientSession getSession(ClientSessionOptions options) {
|
||||
return getMongoClient().startSession(options);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.MongoDbFactoryBase#closeClient()
|
||||
*/
|
||||
@Override
|
||||
protected void closeClient() {
|
||||
getMongoClient().close();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.MongoDbFactoryBase#doGetMongoDatabase(java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
protected MongoDatabase doGetMongoDatabase(String dbName) {
|
||||
return getMongoClient().getDatabase(dbName);
|
||||
}
|
||||
}
|
||||
@@ -15,25 +15,16 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core;
|
||||
|
||||
import lombok.Value;
|
||||
|
||||
import org.springframework.aop.framework.ProxyFactory;
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.dao.support.PersistenceExceptionTranslator;
|
||||
import org.springframework.data.mongodb.MongoDbFactory;
|
||||
import org.springframework.data.mongodb.SessionAwareMethodInterceptor;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.mongodb.ClientSessionOptions;
|
||||
import com.mongodb.DB;
|
||||
import com.mongodb.MongoClient;
|
||||
import com.mongodb.MongoClientURI;
|
||||
import com.mongodb.WriteConcern;
|
||||
import com.mongodb.client.ClientSession;
|
||||
import com.mongodb.client.MongoCollection;
|
||||
import com.mongodb.client.MongoDatabase;
|
||||
import com.mongodb.session.ClientSession;
|
||||
|
||||
/**
|
||||
* Factory to create {@link MongoDatabase} instances from a {@link MongoClient} instance.
|
||||
@@ -45,19 +36,12 @@ import com.mongodb.session.ClientSession;
|
||||
* @author George Moraitis
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class SimpleMongoDbFactory implements DisposableBean, MongoDbFactory {
|
||||
|
||||
private final MongoClient mongoClient;
|
||||
private final String databaseName;
|
||||
private final boolean mongoInstanceCreated;
|
||||
private final PersistenceExceptionTranslator exceptionTranslator;
|
||||
|
||||
private @Nullable WriteConcern writeConcern;
|
||||
public class SimpleMongoDbFactory extends MongoDbFactorySupport<MongoClient> implements DisposableBean {
|
||||
|
||||
/**
|
||||
* Creates a new {@link SimpleMongoDbFactory} instance from the given {@link MongoClientURI}.
|
||||
*
|
||||
* @param uri must not be {@literal null}.
|
||||
* @param uri coordinates for a database connection. Must contain a database name and must not be {@literal null}.
|
||||
* @since 1.7
|
||||
*/
|
||||
public SimpleMongoDbFactory(MongoClientURI uri) {
|
||||
@@ -68,7 +52,7 @@ public class SimpleMongoDbFactory implements DisposableBean, MongoDbFactory {
|
||||
* Creates a new {@link SimpleMongoDbFactory} instance from the given {@link MongoClient}.
|
||||
*
|
||||
* @param mongoClient must not be {@literal null}.
|
||||
* @param databaseName must not be {@literal null}.
|
||||
* @param databaseName must not be {@literal null} or empty.
|
||||
* @since 1.7
|
||||
*/
|
||||
public SimpleMongoDbFactory(MongoClient mongoClient, String databaseName) {
|
||||
@@ -82,76 +66,16 @@ public class SimpleMongoDbFactory implements DisposableBean, MongoDbFactory {
|
||||
* @since 1.7
|
||||
*/
|
||||
private SimpleMongoDbFactory(MongoClient mongoClient, String databaseName, boolean mongoInstanceCreated) {
|
||||
|
||||
Assert.notNull(mongoClient, "MongoClient must not be null!");
|
||||
Assert.hasText(databaseName, "Database name must not be empty!");
|
||||
Assert.isTrue(databaseName.matches("[^/\\\\.$\"\\s]+"),
|
||||
"Database name must not contain slashes, dots, spaces, quotes, or dollar signs!");
|
||||
|
||||
this.mongoClient = mongoClient;
|
||||
this.databaseName = databaseName;
|
||||
this.mongoInstanceCreated = mongoInstanceCreated;
|
||||
this.exceptionTranslator = new MongoExceptionTranslator();
|
||||
super(mongoClient, databaseName, mongoInstanceCreated, new MongoExceptionTranslator());
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the {@link WriteConcern} to be used on the {@link DB} instance being created.
|
||||
*
|
||||
* @param writeConcern the writeConcern to set
|
||||
*/
|
||||
public void setWriteConcern(WriteConcern writeConcern) {
|
||||
this.writeConcern = writeConcern;
|
||||
}
|
||||
|
||||
/*
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.MongoDbFactory#getDb()
|
||||
* @see org.springframework.data.mongodb.MongoDbFactory#getLegacyDb()
|
||||
*/
|
||||
public MongoDatabase getDb() throws DataAccessException {
|
||||
return getDb(databaseName);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.MongoDbFactory#getDb(java.lang.String)
|
||||
*/
|
||||
public MongoDatabase getDb(String dbName) throws DataAccessException {
|
||||
|
||||
Assert.hasText(dbName, "Database name must not be empty.");
|
||||
|
||||
MongoDatabase db = mongoClient.getDatabase(dbName);
|
||||
|
||||
if (writeConcern == null) {
|
||||
return db;
|
||||
}
|
||||
|
||||
return db.withWriteConcern(writeConcern);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up the Mongo instance if it was created by the factory itself.
|
||||
*
|
||||
* @see DisposableBean#destroy()
|
||||
*/
|
||||
public void destroy() throws Exception {
|
||||
if (mongoInstanceCreated) {
|
||||
mongoClient.close();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.MongoDbFactory#getExceptionTranslator()
|
||||
*/
|
||||
@Override
|
||||
public PersistenceExceptionTranslator getExceptionTranslator() {
|
||||
return this.exceptionTranslator;
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public DB getLegacyDb() {
|
||||
return mongoClient.getDB(databaseName);
|
||||
return getMongoClient().getDB(getDefaultDatabaseName());
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -160,108 +84,24 @@ public class SimpleMongoDbFactory implements DisposableBean, MongoDbFactory {
|
||||
*/
|
||||
@Override
|
||||
public ClientSession getSession(ClientSessionOptions options) {
|
||||
return mongoClient.startSession(options);
|
||||
return getMongoClient().startSession(options);
|
||||
}
|
||||
|
||||
/*
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.MongoDbFactory#withSession(com.mongodb.session.ClientSession)
|
||||
* @see org.springframework.data.mongodb.core.MongoDbFactoryBase#closeClient()
|
||||
*/
|
||||
@Override
|
||||
public MongoDbFactory withSession(ClientSession session) {
|
||||
return new ClientSessionBoundMongoDbFactory(session, this);
|
||||
protected void closeClient() {
|
||||
getMongoClient().close();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link ClientSession} bound {@link MongoDbFactory} decorating the database with a
|
||||
* {@link SessionAwareMethodInterceptor}.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.1
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.MongoDbFactoryBase#doGetMongoDatabase(java.lang.String)
|
||||
*/
|
||||
@Value
|
||||
static class ClientSessionBoundMongoDbFactory implements MongoDbFactory {
|
||||
|
||||
ClientSession session;
|
||||
MongoDbFactory delegate;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.MongoDbFactory#getDb()
|
||||
*/
|
||||
@Override
|
||||
public MongoDatabase getDb() throws DataAccessException {
|
||||
return proxyMongoDatabase(delegate.getDb());
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.MongoDbFactory#getDb(java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public MongoDatabase getDb(String dbName) throws DataAccessException {
|
||||
return proxyMongoDatabase(delegate.getDb(dbName));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.MongoDbFactory#getExceptionTranslator()
|
||||
*/
|
||||
@Override
|
||||
public PersistenceExceptionTranslator getExceptionTranslator() {
|
||||
return delegate.getExceptionTranslator();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.MongoDbFactory#getLegacyDb()
|
||||
*/
|
||||
@Override
|
||||
public DB getLegacyDb() {
|
||||
return delegate.getLegacyDb();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.MongoDbFactory#getSession(com.mongodb.ClientSessionOptions)
|
||||
*/
|
||||
@Override
|
||||
public ClientSession getSession(ClientSessionOptions options) {
|
||||
return delegate.getSession(options);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.MongoDbFactory#withSession(com.mongodb.session.ClientSession)
|
||||
*/
|
||||
@Override
|
||||
public MongoDbFactory withSession(ClientSession session) {
|
||||
return delegate.withSession(session);
|
||||
}
|
||||
|
||||
private MongoDatabase proxyMongoDatabase(MongoDatabase database) {
|
||||
return createProxyInstance(session, database, MongoDatabase.class);
|
||||
}
|
||||
|
||||
private MongoDatabase proxyDatabase(ClientSession session, MongoDatabase database) {
|
||||
return createProxyInstance(session, database, MongoDatabase.class);
|
||||
}
|
||||
|
||||
private MongoCollection proxyCollection(ClientSession session, MongoCollection collection) {
|
||||
return createProxyInstance(session, collection, MongoCollection.class);
|
||||
}
|
||||
|
||||
private <T> T createProxyInstance(ClientSession session, T target, Class<T> targetType) {
|
||||
|
||||
ProxyFactory factory = new ProxyFactory();
|
||||
factory.setTarget(target);
|
||||
factory.setInterfaces(targetType);
|
||||
factory.setOpaque(true);
|
||||
|
||||
factory.addAdvice(new SessionAwareMethodInterceptor<>(session, target, MongoDatabase.class, this::proxyDatabase,
|
||||
MongoCollection.class, this::proxyCollection));
|
||||
|
||||
return targetType.cast(factory.getProxy());
|
||||
}
|
||||
@Override
|
||||
protected MongoDatabase doGetMongoDatabase(String dbName) {
|
||||
return getMongoClient().getDatabase(dbName);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,11 +30,11 @@ import org.springframework.util.Assert;
|
||||
import com.mongodb.ClientSessionOptions;
|
||||
import com.mongodb.ConnectionString;
|
||||
import com.mongodb.WriteConcern;
|
||||
import com.mongodb.reactivestreams.client.ClientSession;
|
||||
import com.mongodb.reactivestreams.client.MongoClient;
|
||||
import com.mongodb.reactivestreams.client.MongoClients;
|
||||
import com.mongodb.reactivestreams.client.MongoCollection;
|
||||
import com.mongodb.reactivestreams.client.MongoDatabase;
|
||||
import com.mongodb.session.ClientSession;
|
||||
|
||||
/**
|
||||
* Factory to create {@link MongoDatabase} instances from a {@link MongoClient} instance.
|
||||
@@ -215,23 +215,23 @@ public class SimpleReactiveMongoDatabaseFactory implements DisposableBean, React
|
||||
return createProxyInstance(session, database, MongoDatabase.class);
|
||||
}
|
||||
|
||||
private MongoDatabase proxyDatabase(ClientSession session, MongoDatabase database) {
|
||||
private MongoDatabase proxyDatabase(com.mongodb.session.ClientSession session, MongoDatabase database) {
|
||||
return createProxyInstance(session, database, MongoDatabase.class);
|
||||
}
|
||||
|
||||
private MongoCollection proxyCollection(ClientSession session, MongoCollection collection) {
|
||||
private MongoCollection proxyCollection(com.mongodb.session.ClientSession session, MongoCollection collection) {
|
||||
return createProxyInstance(session, collection, MongoCollection.class);
|
||||
}
|
||||
|
||||
private <T> T createProxyInstance(ClientSession session, T target, Class<T> targetType) {
|
||||
private <T> T createProxyInstance(com.mongodb.session.ClientSession session, T target, Class<T> targetType) {
|
||||
|
||||
ProxyFactory factory = new ProxyFactory();
|
||||
factory.setTarget(target);
|
||||
factory.setInterfaces(targetType);
|
||||
factory.setOpaque(true);
|
||||
|
||||
factory.addAdvice(new SessionAwareMethodInterceptor<>(session, target, MongoDatabase.class, this::proxyDatabase,
|
||||
MongoCollection.class, this::proxyCollection));
|
||||
factory.addAdvice(new SessionAwareMethodInterceptor<>(session, target, ClientSession.class, MongoDatabase.class,
|
||||
this::proxyDatabase, MongoCollection.class, this::proxyCollection));
|
||||
|
||||
return targetType.cast(factory.getProxy());
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.bson.Document;
|
||||
import org.springframework.data.mongodb.core.aggregation.Aggregation.SystemVariable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
@@ -92,6 +93,10 @@ abstract class AbstractAggregationExpression implements AggregationExpression {
|
||||
return targetDocument;
|
||||
}
|
||||
|
||||
if (value instanceof SystemVariable) {
|
||||
return value.toString();
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ class AggregationOperationRenderer {
|
||||
* {@link Document} representation.
|
||||
*
|
||||
* @param operations must not be {@literal null}.
|
||||
* @param context must not be {@literal null}.
|
||||
* @param rootContext must not be {@literal null}.
|
||||
* @return the {@link List} of {@link Document}.
|
||||
*/
|
||||
static List<Document> toDocument(List<AggregationOperation> operations, AggregationOperationContext rootContext) {
|
||||
|
||||
@@ -267,6 +267,19 @@ public class ArrayOperators {
|
||||
return (usesFieldRef() ? In.arrayOf(fieldReference) : In.arrayOf(expression)).containsValue(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link AggregationExpression} that converts the associated expression into an object.
|
||||
* <strong>NOTE:</strong> Requires MongoDB 3.6 or later.
|
||||
*
|
||||
* @return new instance of {@link ArrayToObject}.
|
||||
* @since 2.1
|
||||
*/
|
||||
public ArrayToObject toObject() {
|
||||
|
||||
return usesFieldRef() ? ArrayToObject.arrayValueOfToObject(fieldReference)
|
||||
: ArrayToObject.arrayValueOfToObject(expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
@@ -1497,4 +1510,59 @@ public class ArrayOperators {
|
||||
In containsValue(Object value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link AggregationExpression} for {@code $arrayToObject} that transforms an array into a single document. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 3.6 or later.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @see <a href=
|
||||
* "https://docs.mongodb.com/manual/reference/operator/aggregation/arrayToObject/">https://docs.mongodb.com/manual/reference/operator/aggregation/arrayToObject/</a>
|
||||
* @since 2.1
|
||||
*/
|
||||
public static class ArrayToObject extends AbstractAggregationExpression {
|
||||
|
||||
private ArrayToObject(Object value) {
|
||||
super(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the given array (e.g. an array of two-element arrays, a field reference to an array,...) to an object.
|
||||
*
|
||||
* @param array must not be {@literal null}.
|
||||
* @return new instance of {@link ArrayToObject}.
|
||||
*/
|
||||
public static ArrayToObject arrayToObject(Object array) {
|
||||
return new ArrayToObject(array);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the array pointed to by the given {@link Field field reference} to an object.
|
||||
*
|
||||
* @param fieldReference must not be {@literal null}.
|
||||
* @return new instance of {@link ArrayToObject}.
|
||||
*/
|
||||
public static ArrayToObject arrayValueOfToObject(String fieldReference) {
|
||||
return new ArrayToObject(Fields.field(fieldReference));
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the result array of the given {@link AggregationExpression expression} to an object.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return new instance of {@link ArrayToObject}.
|
||||
*/
|
||||
public static ArrayToObject arrayValueOfToObject(AggregationExpression expression) {
|
||||
return new ArrayToObject(expression);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.aggregation.AbstractAggregationExpression#getMongoMethod()
|
||||
*/
|
||||
@Override
|
||||
protected String getMongoMethod() {
|
||||
return "$arrayToObject";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,695 @@
|
||||
/*
|
||||
* Copyright 2018 the original author 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 org.springframework.data.mongodb.core.schema.JsonSchemaObject.Type;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Gateway to {@literal convert} aggregation operations.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.1
|
||||
*/
|
||||
public class ConvertOperators {
|
||||
|
||||
/**
|
||||
* Take the field referenced by given {@literal fieldReference}.
|
||||
*
|
||||
* @param fieldReference must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public static ConvertOperatorFactory valueOf(String fieldReference) {
|
||||
return new ConvertOperatorFactory(fieldReference);
|
||||
}
|
||||
|
||||
/**
|
||||
* Take the value resulting from the given {@link AggregationExpression}.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public static ConvertOperatorFactory valueOf(AggregationExpression expression) {
|
||||
return new ConvertOperatorFactory(expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public static class ConvertOperatorFactory {
|
||||
|
||||
private final @Nullable String fieldReference;
|
||||
private final @Nullable AggregationExpression expression;
|
||||
|
||||
/**
|
||||
* Creates new {@link ConvertOperatorFactory} for given {@literal fieldReference}.
|
||||
*
|
||||
* @param fieldReference must not be {@literal null}.
|
||||
*/
|
||||
public ConvertOperatorFactory(String fieldReference) {
|
||||
|
||||
Assert.notNull(fieldReference, "FieldReference must not be null!");
|
||||
|
||||
this.fieldReference = fieldReference;
|
||||
this.expression = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link ConvertOperatorFactory} for given {@link AggregationExpression}.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
*/
|
||||
public ConvertOperatorFactory(AggregationExpression expression) {
|
||||
|
||||
Assert.notNull(expression, "Expression must not be null!");
|
||||
|
||||
this.fieldReference = null;
|
||||
this.expression = expression;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link Convert aggregation expression} that takes the associated value and converts it into the type
|
||||
* specified by the given {@code stringTypeIdentifier}. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @param stringTypeIdentifier must not be {@literal null}.
|
||||
* @return new instance of {@link Convert}.
|
||||
*/
|
||||
public Convert convertTo(String stringTypeIdentifier) {
|
||||
return createConvert().to(stringTypeIdentifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link Convert aggregation expression} that takes the associated value and converts it into the type
|
||||
* specified by the given {@code numericTypeIdentifier}. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @param numericTypeIdentifier must not be {@literal null}.
|
||||
* @return new instance of {@link Convert}.
|
||||
*/
|
||||
public Convert convertTo(int numericTypeIdentifier) {
|
||||
return createConvert().to(numericTypeIdentifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link Convert aggregation expression} that takes the associated value and converts it into the type
|
||||
* specified by the given {@link Type}. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
* @return new instance of {@link Convert}.
|
||||
*/
|
||||
public Convert convertTo(Type type) {
|
||||
return createConvert().to(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link Convert aggregation expression} that takes the associated value and converts it into the type
|
||||
* specified by the value of the given {@link Field field reference}. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @param fieldReference must not be {@literal null}.
|
||||
* @return new instance of {@link Convert}.
|
||||
*/
|
||||
public Convert convertToTypeOf(String fieldReference) {
|
||||
return createConvert().toTypeOf(fieldReference);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link Convert aggregation expression} that takes the associated value and converts it into the type
|
||||
* specified by the given {@link AggregationExpression expression}. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return new instance of {@link Convert}.
|
||||
*/
|
||||
public Convert convertToTypeOf(AggregationExpression expression) {
|
||||
return createConvert().toTypeOf(expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link ToBool aggregation expression} for {@code $toBool} that converts a value to boolean. Shorthand
|
||||
* for {@link #convertTo(String) #convertTo("bool")}. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @return new instance of {@link ToBool}.
|
||||
*/
|
||||
public ToBool convertToBoolean() {
|
||||
return ToBool.toBoolean(valueObject());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link ToDate aggregation expression} for {@code $toDate} that converts a value to a date. Shorthand
|
||||
* for {@link #convertTo(String) #convertTo("date")}. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @return new instance of {@link ToDate}.
|
||||
*/
|
||||
public ToDate convertToDate() {
|
||||
return ToDate.toDate(valueObject());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link ToDecimal aggregation expression} for {@code $toDecimal} that converts a value to a decimal.
|
||||
* Shorthand for {@link #convertTo(String) #convertTo("decimal")}. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @return new instance of {@link ToDecimal}.
|
||||
*/
|
||||
public ToDecimal convertToDecimal() {
|
||||
return ToDecimal.toDecimal(valueObject());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link ToDouble aggregation expression} for {@code $toDouble} that converts a value to a decimal.
|
||||
* Shorthand for {@link #convertTo(String) #convertTo("double")}. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @return new instance of {@link ToDouble}.
|
||||
*/
|
||||
public ToDouble convertToDouble() {
|
||||
return ToDouble.toDouble(valueObject());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link ToInt aggregation expression} for {@code $toInt} that converts a value to an int. Shorthand
|
||||
* for {@link #convertTo(String) #convertTo("int")}. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @return new instance of {@link ToInt}.
|
||||
*/
|
||||
public ToInt convertToInt() {
|
||||
return ToInt.toInt(valueObject());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link ToInt aggregation expression} for {@code $toLong} that converts a value to a long. Shorthand
|
||||
* for {@link #convertTo(String) #convertTo("long")}. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @return new instance of {@link ToInt}.
|
||||
*/
|
||||
public ToLong convertToLong() {
|
||||
return ToLong.toLong(valueObject());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link ToInt aggregation expression} for {@code $toObjectId} that converts a value to a objectId. Shorthand
|
||||
* for {@link #convertTo(String) #convertTo("objectId")}. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @return new instance of {@link ToInt}.
|
||||
*/
|
||||
public ToObjectId convertToObjectId() {
|
||||
return ToObjectId.toObjectId(valueObject());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link ToInt aggregation expression} for {@code $toString} that converts a value to a string. Shorthand
|
||||
* for {@link #convertTo(String) #convertTo("string")}. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @return new instance of {@link ToInt}.
|
||||
*/
|
||||
public ToString convertToString() {
|
||||
return ToString.toString(valueObject());
|
||||
}
|
||||
|
||||
private Convert createConvert() {
|
||||
return usesFieldRef() ? Convert.convertValueOf(fieldReference) : Convert.convertValueOf(expression);
|
||||
}
|
||||
|
||||
private Object valueObject() {
|
||||
return usesFieldRef() ? Fields.field(fieldReference) : expression;
|
||||
}
|
||||
|
||||
private boolean usesFieldRef() {
|
||||
return fieldReference != null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link AggregationExpression} for {@code $convert} that converts a value to a specified type. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @see <a href=
|
||||
* "https://docs.mongodb.com/manual/reference/operator/aggregation/convert/">https://docs.mongodb.com/manual/reference/operator/aggregation/convert/</a>
|
||||
* @since 2.1
|
||||
*/
|
||||
public static class Convert extends AbstractAggregationExpression {
|
||||
|
||||
private Convert(Object value) {
|
||||
super(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link Convert} using the given value for the {@literal input} attribute.
|
||||
*
|
||||
* @param value must not be {@literal null}.
|
||||
* @return new instance of {@link Convert}.
|
||||
*/
|
||||
public static Convert convertValue(Object value) {
|
||||
return new Convert(Collections.singletonMap("input", value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link Convert} using the value of the provided {@link Field fieldReference} as {@literal input}
|
||||
* value.
|
||||
*
|
||||
* @param fieldReference must not be {@literal null}.
|
||||
* @return new instance of {@link Convert}.
|
||||
*/
|
||||
public static Convert convertValueOf(String fieldReference) {
|
||||
return convertValue(Fields.field(fieldReference));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link Convert} using the result of the provided {@link AggregationExpression expression} as
|
||||
* {@literal input} value.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return new instance of {@link Convert}.
|
||||
*/
|
||||
public static Convert convertValueOf(AggregationExpression expression) {
|
||||
return convertValue(expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the conversion target type via its {@link String} representation.
|
||||
* <ul>
|
||||
* <li>double</li>
|
||||
* <li>string</li>
|
||||
* <li>objectId</li>
|
||||
* <li>bool</li>
|
||||
* <li>date</li>
|
||||
* <li>int</li>
|
||||
* <li>long</li>
|
||||
* <li>decimal</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param stringTypeIdentifier must not be {@literal null}.
|
||||
* @return new instance of {@link Convert}.
|
||||
*/
|
||||
public Convert to(String stringTypeIdentifier) {
|
||||
return new Convert(append("to", stringTypeIdentifier));
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the conversion target type via its numeric representation.
|
||||
* <dl>
|
||||
* <dt>1</dt>
|
||||
* <dd>double</dd>
|
||||
* <dt>2</dt>
|
||||
* <dd>string</li>
|
||||
* <dt>7</dt>
|
||||
* <dd>objectId</li>
|
||||
* <dt>8</dt>
|
||||
* <dd>bool</dd>
|
||||
* <dt>9</dt>
|
||||
* <dd>date</dd>
|
||||
* <dt>16</dt>
|
||||
* <dd>int</dd>
|
||||
* <dt>18</dt>
|
||||
* <dd>long</dd>
|
||||
* <dt>19</dt>
|
||||
* <dd>decimal</dd>
|
||||
* </dl>
|
||||
*
|
||||
* @param numericTypeIdentifier must not be {@literal null}.
|
||||
* @return new instance of {@link Convert}.
|
||||
*/
|
||||
public Convert to(int numericTypeIdentifier) {
|
||||
return new Convert(append("to", numericTypeIdentifier));
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the conversion target type.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
* @return new instance of {@link Convert}.
|
||||
*/
|
||||
public Convert to(Type type) {
|
||||
|
||||
String typeString = Type.BOOLEAN.equals(type) ? "bool" : type.value().toString();
|
||||
return to(typeString);
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the conversion target type via the value of the given field.
|
||||
*
|
||||
* @param fieldReference must not be {@literal null}.
|
||||
* @return new instance of {@link Convert}.
|
||||
*/
|
||||
public Convert toTypeOf(String fieldReference) {
|
||||
return new Convert(append("to", Fields.field(fieldReference)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the conversion target type via the value of the given {@link AggregationExpression expression}.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return new instance of {@link Convert}.
|
||||
*/
|
||||
public Convert toTypeOf(AggregationExpression expression) {
|
||||
return new Convert(append("to", expression));
|
||||
}
|
||||
|
||||
/**
|
||||
* Optionally specify the value to return on encountering an error during conversion.
|
||||
*
|
||||
* @param value must not be {@literal null}.
|
||||
* @return new instance of {@link Convert}.
|
||||
*/
|
||||
public Convert onErrorReturn(Object value) {
|
||||
return new Convert(append("onError", value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Optionally specify the field holding the value to return on encountering an error during conversion.
|
||||
*
|
||||
* @param fieldReference must not be {@literal null}.
|
||||
* @return new instance of {@link Convert}.
|
||||
*/
|
||||
public Convert onErrorReturnValueOf(String fieldReference) {
|
||||
return onErrorReturn(Fields.field(fieldReference));
|
||||
}
|
||||
|
||||
/**
|
||||
* Optionally specify the expression to evaluate and return on encountering an error during conversion.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return new instance of {@link Convert}.
|
||||
*/
|
||||
public Convert onErrorReturnValueOf(AggregationExpression expression) {
|
||||
return onErrorReturn(expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* Optionally specify the value to return when the input is {@literal null} or missing.
|
||||
*
|
||||
* @param value must not be {@literal null}.
|
||||
* @return new instance of {@link Convert}.
|
||||
*/
|
||||
public Convert onNullReturn(Object value) {
|
||||
return new Convert(append("onNull", value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Optionally specify the field holding the value to return when the input is {@literal null} or missing.
|
||||
*
|
||||
* @param fieldReference must not be {@literal null}.
|
||||
* @return new instance of {@link Convert}.
|
||||
*/
|
||||
public Convert onNullReturnValueOf(String fieldReference) {
|
||||
return onNullReturn(Fields.field(fieldReference));
|
||||
}
|
||||
|
||||
/**
|
||||
* Optionally specify the expression to evaluate and return when the input is {@literal null} or missing.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return new instance of {@link Convert}.
|
||||
*/
|
||||
public Convert onNullReturnValueOf(AggregationExpression expression) {
|
||||
return onNullReturn(expression);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getMongoMethod() {
|
||||
return "$convert";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link AggregationExpression} for {@code $toBool} that converts a value to {@literal boolean}. Shorthand for
|
||||
* {@link Convert#to(String) Convert#to("bool")}. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @see <a href=
|
||||
* "https://docs.mongodb.com/manual/reference/operator/aggregation/toBool/">https://docs.mongodb.com/manual/reference/operator/aggregation/toBool/</a>
|
||||
* @since 2.1
|
||||
*/
|
||||
public static class ToBool extends AbstractAggregationExpression {
|
||||
|
||||
private ToBool(Object value) {
|
||||
super(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link ToBool} using the given value as input.
|
||||
*
|
||||
* @param value must not be {@literal null}.
|
||||
* @return new instance of {@link ToBool}.
|
||||
*/
|
||||
public static ToBool toBoolean(Object value) {
|
||||
return new ToBool(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getMongoMethod() {
|
||||
return "$toBool";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link AggregationExpression} for {@code $toDate} that converts a value to {@literal date}. Shorthand for
|
||||
* {@link Convert#to(String) Convert#to("date")}. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @see <a href=
|
||||
* "https://docs.mongodb.com/manual/reference/operator/aggregation/toDate/">https://docs.mongodb.com/manual/reference/operator/aggregation/toDate/</a>
|
||||
* @since 2.1
|
||||
*/
|
||||
public static class ToDate extends AbstractAggregationExpression {
|
||||
|
||||
private ToDate(Object value) {
|
||||
super(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link ToDate} using the given value as input.
|
||||
*
|
||||
* @param value must not be {@literal null}.
|
||||
* @return new instance of {@link ToDate}.
|
||||
*/
|
||||
public static ToDate toDate(Object value) {
|
||||
return new ToDate(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getMongoMethod() {
|
||||
return "$toDate";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link AggregationExpression} for {@code $toDecimal} that converts a value to {@literal decimal}. Shorthand for
|
||||
* {@link Convert#to(String) Convert#to("decimal")}. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @see <a href=
|
||||
* "https://docs.mongodb.com/manual/reference/operator/aggregation/toDecimal/">https://docs.mongodb.com/manual/reference/operator/aggregation/toDecimal/</a>
|
||||
* @since 2.1
|
||||
*/
|
||||
public static class ToDecimal extends AbstractAggregationExpression {
|
||||
|
||||
private ToDecimal(Object value) {
|
||||
super(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link ToDecimal} using the given value as input.
|
||||
*
|
||||
* @param value must not be {@literal null}.
|
||||
* @return new instance of {@link ToDecimal}.
|
||||
*/
|
||||
public static ToDecimal toDecimal(Object value) {
|
||||
return new ToDecimal(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getMongoMethod() {
|
||||
return "$toDecimal";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link AggregationExpression} for {@code $toDouble} that converts a value to {@literal double}. Shorthand for
|
||||
* {@link Convert#to(String) Convert#to("double")}. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @see <a href=
|
||||
* "https://docs.mongodb.com/manual/reference/operator/aggregation/toDouble/">https://docs.mongodb.com/manual/reference/operator/aggregation/toDouble/</a>
|
||||
* @since 2.1
|
||||
*/
|
||||
public static class ToDouble extends AbstractAggregationExpression {
|
||||
|
||||
private ToDouble(Object value) {
|
||||
super(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link ToDouble} using the given value as input.
|
||||
*
|
||||
* @param value must not be {@literal null}.
|
||||
* @return new instance of {@link ToDouble}.
|
||||
*/
|
||||
public static ToDouble toDouble(Object value) {
|
||||
return new ToDouble(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getMongoMethod() {
|
||||
return "$toDouble";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link AggregationExpression} for {@code $toInt} that converts a value to {@literal integer}. Shorthand for
|
||||
* {@link Convert#to(String) Convert#to("int")}. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @see <a href=
|
||||
* "https://docs.mongodb.com/manual/reference/operator/aggregation/toInt/">https://docs.mongodb.com/manual/reference/operator/aggregation/toInt/</a>
|
||||
* @since 2.1
|
||||
*/
|
||||
public static class ToInt extends AbstractAggregationExpression {
|
||||
|
||||
private ToInt(Object value) {
|
||||
super(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link ToInt} using the given value as input.
|
||||
*
|
||||
* @param value must not be {@literal null}.
|
||||
* @return new instance of {@link ToInt}.
|
||||
*/
|
||||
public static ToInt toInt(Object value) {
|
||||
return new ToInt(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getMongoMethod() {
|
||||
return "$toInt";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link AggregationExpression} for {@code $toLong} that converts a value to {@literal long}. Shorthand for
|
||||
* {@link Convert#to(String) Convert#to("long")}. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @see <a href=
|
||||
* "https://docs.mongodb.com/manual/reference/operator/aggregation/toLong/">https://docs.mongodb.com/manual/reference/operator/aggregation/toLong/</a>
|
||||
* @since 2.1
|
||||
*/
|
||||
public static class ToLong extends AbstractAggregationExpression {
|
||||
|
||||
private ToLong(Object value) {
|
||||
super(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link ToLong} using the given value as input.
|
||||
*
|
||||
* @param value must not be {@literal null}.
|
||||
* @return new instance of {@link ToLong}.
|
||||
*/
|
||||
public static ToLong toLong(Object value) {
|
||||
return new ToLong(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getMongoMethod() {
|
||||
return "$toLong";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link AggregationExpression} for {@code $toObjectId} that converts a value to {@literal objectId}. Shorthand for
|
||||
* {@link Convert#to(String) Convert#to("objectId")}. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @see <a href=
|
||||
* "https://docs.mongodb.com/manual/reference/operator/aggregation/toObjectId/">https://docs.mongodb.com/manual/reference/operator/aggregation/toObjectId/</a>
|
||||
* @since 2.1
|
||||
*/
|
||||
public static class ToObjectId extends AbstractAggregationExpression {
|
||||
|
||||
private ToObjectId(Object value) {
|
||||
super(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link ToObjectId} using the given value as input.
|
||||
*
|
||||
* @param value must not be {@literal null}.
|
||||
* @return new instance of {@link ToObjectId}.
|
||||
*/
|
||||
public static ToObjectId toObjectId(Object value) {
|
||||
return new ToObjectId(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getMongoMethod() {
|
||||
return "$toObjectId";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link AggregationExpression} for {@code $toString} that converts a value to {@literal string}. Shorthand for
|
||||
* {@link Convert#to(String) Convert#to("string")}. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @see <a href=
|
||||
* "https://docs.mongodb.com/manual/reference/operator/aggregation/toString/">https://docs.mongodb.com/manual/reference/operator/aggregation/toString/</a>
|
||||
* @since 2.1
|
||||
*/
|
||||
public static class ToString extends AbstractAggregationExpression {
|
||||
|
||||
private ToString(Object value) {
|
||||
super(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link ToString} using the given value as input.
|
||||
*
|
||||
* @param value must not be {@literal null}.
|
||||
* @return new instance of {@link ToString}.
|
||||
*/
|
||||
public static ToString toString(Object value) {
|
||||
return new ToString(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getMongoMethod() {
|
||||
return "$toString";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,7 @@ import java.util.Map;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Gateway to {@literal Date} aggregation operations.
|
||||
@@ -178,7 +179,7 @@ public class DateOperators {
|
||||
* Create a {@link Timezone} for the {@link AggregationExpression} resulting in the Olson Timezone Identifier or UTC
|
||||
* Offset.
|
||||
*
|
||||
* @param value the {@link AggregationExpression} resulting in the timezone.
|
||||
* @param expression the {@link AggregationExpression} resulting in the timezone.
|
||||
* @return new instance of {@link Timezone}.
|
||||
*/
|
||||
public static Timezone ofExpression(AggregationExpression expression) {
|
||||
@@ -380,6 +381,17 @@ public class DateOperators {
|
||||
return applyTimezone(DateToString.dateToString(dateReference()).toString(format), timezone);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link AggregationExpression} that converts a date object to a string according to the server default
|
||||
* format.
|
||||
*
|
||||
* @return new instance of {@link DateToString}.
|
||||
* @since 2.1
|
||||
*/
|
||||
public DateToString toStringWithDefaultFormat() {
|
||||
return applyTimezone(DateToString.dateToString(dateReference()).defaultFormat(), timezone);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link AggregationExpression} that returns the weekday number in ISO 8601-2018 format, ranging from 1
|
||||
* (for Monday) to 7 (for Sunday).
|
||||
@@ -1352,6 +1364,11 @@ public class DateOperators {
|
||||
Assert.notNull(format, "Format must not be null!");
|
||||
return new DateToString(argumentMap(value, format, Timezone.none()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public DateToString defaultFormat() {
|
||||
return new DateToString(argumentMap(value, null, Timezone.none()));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1392,7 +1409,43 @@ public class DateOperators {
|
||||
public DateToString withTimezone(Timezone timezone) {
|
||||
|
||||
Assert.notNull(timezone, "Timezone must not be null.");
|
||||
return new DateToString(argumentMap(get("date"), get("format"), timezone));
|
||||
return new DateToString(append("timezone", timezone));
|
||||
}
|
||||
|
||||
/**
|
||||
* Optionally specify the value to return when the date is {@literal null} or missing. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @param value must not be {@literal null}.
|
||||
* @return new instance of {@link DateToString}.
|
||||
* @since 2.1
|
||||
*/
|
||||
public DateToString onNullReturn(Object value) {
|
||||
return new DateToString(append("onNull", value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Optionally specify the field holding the value to return when the date is {@literal null} or missing. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @param fieldReference must not be {@literal null}.
|
||||
* @return new instance of {@link DateToString}.
|
||||
* @since 2.1
|
||||
*/
|
||||
public DateToString onNullReturnValueOf(String fieldReference) {
|
||||
return onNullReturn(Fields.field(fieldReference));
|
||||
}
|
||||
|
||||
/**
|
||||
* Optionally specify the expression to evaluate and return when the date is {@literal null} or missing. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return new instance of {@link DateToString}.
|
||||
* @since 2.1
|
||||
*/
|
||||
public DateToString onNullReturnValueOf(AggregationExpression expression) {
|
||||
return onNullReturn(expression);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1400,10 +1453,14 @@ public class DateOperators {
|
||||
return "$dateToString";
|
||||
}
|
||||
|
||||
private static java.util.Map<String, Object> argumentMap(Object date, String format, Timezone timezone) {
|
||||
private static java.util.Map<String, Object> argumentMap(Object date, @Nullable String format, Timezone timezone) {
|
||||
|
||||
java.util.Map<String, Object> args = new LinkedHashMap<>(2);
|
||||
|
||||
if (StringUtils.hasText(format)) {
|
||||
args.put("format", format);
|
||||
}
|
||||
|
||||
java.util.Map<String, Object> args = new LinkedHashMap<String, Object>(2);
|
||||
args.put("format", format);
|
||||
args.put("date", date);
|
||||
|
||||
if (!ObjectUtils.nullSafeEquals(timezone, Timezone.none())) {
|
||||
@@ -1412,6 +1469,25 @@ public class DateOperators {
|
||||
return args;
|
||||
}
|
||||
|
||||
protected java.util.Map<String, Object> append(String key, Object value) {
|
||||
|
||||
java.util.Map<String, Object> clone = new LinkedHashMap<>(argumentMap());
|
||||
|
||||
if (value instanceof Timezone) {
|
||||
|
||||
if (ObjectUtils.nullSafeEquals(value, Timezone.none())) {
|
||||
clone.remove("timezone");
|
||||
} else {
|
||||
clone.put("timezone", ((Timezone) value).value);
|
||||
}
|
||||
|
||||
} else {
|
||||
clone.put(key, value);
|
||||
}
|
||||
|
||||
return clone;
|
||||
}
|
||||
|
||||
public interface FormatBuilder {
|
||||
|
||||
/**
|
||||
@@ -1421,6 +1497,16 @@ public class DateOperators {
|
||||
* @return
|
||||
*/
|
||||
DateToString toString(String format);
|
||||
|
||||
/**
|
||||
* Creates new {@link DateToString} using the server default string format ({@code %Y-%m-%dT%H:%M:%S.%LZ}) for
|
||||
* dates. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @return new instance of {@link DateToString}.
|
||||
* @since 2.1
|
||||
*/
|
||||
DateToString defaultFormat();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2269,6 +2355,20 @@ public class DateOperators {
|
||||
return new DateFromString(appendTimezone(argumentMap(), timezone));
|
||||
}
|
||||
|
||||
/**
|
||||
* Optionally set the date format to use. If not specified {@code %Y-%m-%dT%H:%M:%S.%LZ} is used.<br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @param format must not be {@literal null}.
|
||||
* @return new instance of {@link DateFromString}.
|
||||
* @throws IllegalArgumentException if given {@literal format} is {@literal null}.
|
||||
*/
|
||||
public DateFromString withFormat(String format) {
|
||||
|
||||
Assert.notNull(format, "Format must not be null!");
|
||||
return new DateFromString(append("format", format));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getMongoMethod() {
|
||||
return "$dateFromString";
|
||||
|
||||
@@ -17,7 +17,9 @@ package org.springframework.data.mongodb.core.aggregation;
|
||||
|
||||
import org.bson.Document;
|
||||
import org.springframework.data.mongodb.core.query.NearQuery;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Represents a {@code geoNear} aggregation operation.
|
||||
@@ -28,26 +30,55 @@ import org.springframework.util.Assert;
|
||||
* @author Thomas Darimont
|
||||
* @author Christoph Strobl
|
||||
* @since 1.3
|
||||
* @see <a href="https://docs.mongodb.com/manual/reference/operator/aggregation/geoNear/">MongoDB Aggregation Framework:
|
||||
* $geoNear</a>
|
||||
*/
|
||||
public class GeoNearOperation implements AggregationOperation {
|
||||
|
||||
private final NearQuery nearQuery;
|
||||
private final String distanceField;
|
||||
private final @Nullable String indexKey;
|
||||
|
||||
/**
|
||||
* Creates a new {@link GeoNearOperation} from the given {@link NearQuery} and the given distance field. The
|
||||
* {@code distanceField} defines output field that contains the calculated distance.
|
||||
*
|
||||
* @param query must not be {@literal null}.
|
||||
* @param nearQuery must not be {@literal null}.
|
||||
* @param distanceField must not be {@literal null}.
|
||||
*/
|
||||
public GeoNearOperation(NearQuery nearQuery, String distanceField) {
|
||||
this(nearQuery, distanceField, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link GeoNearOperation} from the given {@link NearQuery} and the given distance field. The
|
||||
* {@code distanceField} defines output field that contains the calculated distance.
|
||||
*
|
||||
* @param nearQuery must not be {@literal null}.
|
||||
* @param distanceField must not be {@literal null}.
|
||||
* @param indexKey can be {@literal null};
|
||||
* @since 2.1
|
||||
*/
|
||||
private GeoNearOperation(NearQuery nearQuery, String distanceField, @Nullable String indexKey) {
|
||||
|
||||
Assert.notNull(nearQuery, "NearQuery must not be null.");
|
||||
Assert.hasLength(distanceField, "Distance field must not be null or empty.");
|
||||
|
||||
this.nearQuery = nearQuery;
|
||||
this.distanceField = distanceField;
|
||||
this.indexKey = indexKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Optionally specify the geospatial index to use via the field to use in the calculation. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @param key the geospatial index field to use when calculating the distance.
|
||||
* @return new instance of {@link GeoNearOperation}.
|
||||
* @since 2.1
|
||||
*/
|
||||
public GeoNearOperation useIndex(String key) {
|
||||
return new GeoNearOperation(nearQuery, distanceField, key);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -60,6 +91,10 @@ public class GeoNearOperation implements AggregationOperation {
|
||||
Document command = context.getMappedObject(nearQuery.toDocument());
|
||||
command.put("distanceField", distanceField);
|
||||
|
||||
if (StringUtils.hasText(indexKey)) {
|
||||
command.put("key", indexKey);
|
||||
}
|
||||
|
||||
return new Document("$geoNear", command);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,8 +103,8 @@ public class GraphLookupOperation implements InheritsFieldsAggregationOperation
|
||||
|
||||
graphLookup.put("startWith", mappedStartWith.size() == 1 ? mappedStartWith.iterator().next() : mappedStartWith);
|
||||
|
||||
graphLookup.put("connectFromField", connectFrom.getName());
|
||||
graphLookup.put("connectToField", connectTo.getName());
|
||||
graphLookup.put("connectFromField", connectFrom.getTarget());
|
||||
graphLookup.put("connectToField", connectTo.getTarget());
|
||||
graphLookup.put("as", as.getName());
|
||||
|
||||
if (maxDepth != null) {
|
||||
@@ -112,7 +112,7 @@ public class GraphLookupOperation implements InheritsFieldsAggregationOperation
|
||||
}
|
||||
|
||||
if (depthField != null) {
|
||||
graphLookup.put("depthField", depthField.getName());
|
||||
graphLookup.put("depthField", depthField.getTarget());
|
||||
}
|
||||
|
||||
if (restrictSearchWithMatch != null) {
|
||||
|
||||
@@ -0,0 +1,298 @@
|
||||
/*
|
||||
* Copyright 2018 the original author 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.Collection;
|
||||
|
||||
import org.bson.Document;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Gateway for
|
||||
* <a href="https://docs.mongodb.com/manual/meta/aggregation-quick-reference/#object-expression-operators">object
|
||||
* expression operators</a>.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.1
|
||||
*/
|
||||
public class ObjectOperators {
|
||||
|
||||
/**
|
||||
* Take the value referenced by given {@literal fieldReference}.
|
||||
*
|
||||
* @param fieldReference must not be {@literal null}.
|
||||
* @return new instance of {@link ObjectOperatorFactory}.
|
||||
*/
|
||||
public static ObjectOperatorFactory valueOf(String fieldReference) {
|
||||
return new ObjectOperatorFactory(Fields.field(fieldReference));
|
||||
}
|
||||
|
||||
/**
|
||||
* Take the value provided by the given {@link AggregationExpression}.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return new instance of {@link ObjectOperatorFactory}.
|
||||
*/
|
||||
public static ObjectOperatorFactory valueOf(AggregationExpression expression) {
|
||||
return new ObjectOperatorFactory(expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public static class ObjectOperatorFactory {
|
||||
|
||||
private final Object value;
|
||||
|
||||
/**
|
||||
* Creates new {@link ObjectOperatorFactory} for given {@literal value}.
|
||||
*
|
||||
* @param value must not be {@literal null}.
|
||||
*/
|
||||
public ObjectOperatorFactory(Object value) {
|
||||
|
||||
Assert.notNull(value, "Value must not be null!");
|
||||
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link MergeObjects aggregation expression} that takes the associated value and uses
|
||||
* {@literal $mergeObjects} as an accumulator within the {@literal $group} stage. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @return new instance of {@link MergeObjects}.
|
||||
*/
|
||||
public MergeObjects merge() {
|
||||
return MergeObjects.merge(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link MergeObjects aggregation expression} that takes the associated value and combines it with the
|
||||
* given values (documents or mapped objects) into a single document. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @return new instance of {@link MergeObjects}.
|
||||
*/
|
||||
public MergeObjects mergeWith(Object... values) {
|
||||
return merge().mergeWith(values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link MergeObjects aggregation expression} that takes the associated value and combines it with the
|
||||
* values of the given {@link Field field references} into a single document. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @return new instance of {@link MergeObjects}.
|
||||
*/
|
||||
public MergeObjects mergeWithValuesOf(String... fieldReferences) {
|
||||
return merge().mergeWithValuesOf(fieldReferences);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link MergeObjects aggregation expression} that takes the associated value and combines it with the
|
||||
* result values of the given {@link Aggregation expressions} into a single document. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @return new instance of {@link MergeObjects}.
|
||||
*/
|
||||
public MergeObjects mergeWithValuesOf(AggregationExpression... expression) {
|
||||
return merge().mergeWithValuesOf(expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link ObjectToArray aggregation expression} that takes the associated value and converts it to an
|
||||
* array of {@link Document documents} that contain two fields {@literal k} and {@literal v} each. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 3.6 or later.
|
||||
*
|
||||
* @since 2.1
|
||||
*/
|
||||
public ObjectToArray toArray() {
|
||||
return ObjectToArray.toArray(value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link AggregationExpression} for {@code $mergeObjects} that combines multiple documents into a single document.
|
||||
* <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @see <a href=
|
||||
* "https://docs.mongodb.com/manual/reference/operator/aggregation/mergeObjects/">https://docs.mongodb.com/manual/reference/operator/aggregation/mergeObjects/</a>
|
||||
* @since 2.1
|
||||
*/
|
||||
public static class MergeObjects extends AbstractAggregationExpression {
|
||||
|
||||
private MergeObjects(Object value) {
|
||||
super(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link MergeObjects aggregation expression} that takes given values and combines them into a single
|
||||
* document. <br />
|
||||
*
|
||||
* @param values must not be {@literal null}.
|
||||
* @return new instance of {@link MergeObjects}.
|
||||
*/
|
||||
public static MergeObjects merge(Object... values) {
|
||||
return new MergeObjects(Arrays.asList(values));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link MergeObjects aggregation expression} that takes the given {@link Field field references} and
|
||||
* combines them into a single document.
|
||||
*
|
||||
* @param fieldReferences must not be {@literal null}.
|
||||
* @return new instance of {@link MergeObjects}.
|
||||
*/
|
||||
public static MergeObjects mergeValuesOf(String... fieldReferences) {
|
||||
return merge(Arrays.stream(fieldReferences).map(Fields::field).toArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link MergeObjects aggregation expression} that takes the result of the given {@link Aggregation
|
||||
* expressions} and combines them into a single document.
|
||||
*
|
||||
* @param expressions must not be {@literal null}.
|
||||
* @return new instance of {@link MergeObjects}.
|
||||
*/
|
||||
public static MergeObjects mergeValuesOf(AggregationExpression... expressions) {
|
||||
return merge(expressions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link MergeObjects aggregation expression} by adding the given {@link Field field references}.
|
||||
*
|
||||
* @param fieldReferences must not be {@literal null}.
|
||||
* @return new instance of {@link MergeObjects}.
|
||||
*/
|
||||
public MergeObjects mergeWithValuesOf(String... fieldReferences) {
|
||||
return mergeWith(Arrays.stream(fieldReferences).map(Fields::field).toArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link MergeObjects aggregation expression} by adding the given {@link AggregationExpression
|
||||
* expressions}.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return new instance of {@link MergeObjects}.
|
||||
*/
|
||||
public MergeObjects mergeWithValuesOf(AggregationExpression... expression) {
|
||||
return mergeWith(expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link MergeObjects aggregation expression} by adding the given values (documents or mapped objects).
|
||||
*
|
||||
* @param values must not be {@literal null}.
|
||||
* @return new instance of {@link MergeObjects}.
|
||||
*/
|
||||
public MergeObjects mergeWith(Object... values) {
|
||||
return new MergeObjects(append(Arrays.asList(values)));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.aggregation.AbstractAggregationExpression#toDocument(java.lang.Object, org.springframework.data.mongodb.core.aggregation.AggregationOperationContext)
|
||||
*/
|
||||
@Override
|
||||
public Document toDocument(Object value, AggregationOperationContext context) {
|
||||
return super.toDocument(potentiallyExtractSingleValue(value), context);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Object potentiallyExtractSingleValue(Object value) {
|
||||
|
||||
if (value instanceof Collection) {
|
||||
|
||||
Collection<Object> collection = ((Collection<Object>) value);
|
||||
if (collection.size() == 1) {
|
||||
return collection.iterator().next();
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.aggregation.AbstractAggregationExpression#getMongoMethod()
|
||||
*/
|
||||
@Override
|
||||
protected String getMongoMethod() {
|
||||
return "$mergeObjects";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link AggregationExpression} for {@code $objectToArray} that converts a document to an array of {@link Document
|
||||
* documents} that each contains two fields {@literal k} and {@literal v}. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 3.6 or later.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @see <a href=
|
||||
* "https://docs.mongodb.com/manual/reference/operator/aggregation/objectToArray/">https://docs.mongodb.com/manual/reference/operator/aggregation/objectToArray/</a>
|
||||
* @since 2.1
|
||||
*/
|
||||
public static class ObjectToArray extends AbstractAggregationExpression {
|
||||
|
||||
private ObjectToArray(Object value) {
|
||||
super(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link ObjectToArray aggregation expression} that takes the value pointed to by given {@link Field
|
||||
* fieldReference} and converts it to an array.
|
||||
*
|
||||
* @param fieldReference must not be {@literal null}.
|
||||
* @return new instance of {@link ObjectToArray}.
|
||||
*/
|
||||
public static ObjectToArray valueOfToArray(String fieldReference) {
|
||||
return toArray(Fields.field(fieldReference));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link ObjectToArray aggregation expression} that takes the result value of the given
|
||||
* {@link AggregationExpression expression} and converts it to an array.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return new instance of {@link ObjectToArray}.
|
||||
*/
|
||||
public static ObjectToArray valueOfToArray(AggregationExpression expression) {
|
||||
return toArray(expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link ObjectToArray aggregation expression} that takes the given value and converts it to an array.
|
||||
*
|
||||
* @param value must not be {@literal null}.
|
||||
* @return new instance of {@link ObjectToArray}.
|
||||
*/
|
||||
public static ObjectToArray toArray(Object value) {
|
||||
return new ObjectToArray(value);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.aggregation.AbstractAggregationExpression#getMongoMethod()
|
||||
*/
|
||||
@Override
|
||||
protected String getMongoMethod() {
|
||||
return "$objectToArray";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -102,7 +102,7 @@ public class PrefixingDelegatingAggregationOperationContext implements Aggregati
|
||||
}
|
||||
|
||||
private String prefixKey(String key) {
|
||||
return (key.startsWith("$") || blacklist.contains(key)) ? key : (prefix + "." + key);
|
||||
return (key.startsWith("$") || isBlacklisted(key)) ? key : (prefix + "." + key);
|
||||
}
|
||||
|
||||
private Object prefixCollection(Collection<Object> sourceCollection) {
|
||||
@@ -119,4 +119,23 @@ public class PrefixingDelegatingAggregationOperationContext implements Aggregati
|
||||
|
||||
return prefixed;
|
||||
}
|
||||
|
||||
private boolean isBlacklisted(String key) {
|
||||
|
||||
if (blacklist.contains(key)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!key.contains(".")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (String blacklisted : blacklist) {
|
||||
if (key.startsWith(blacklisted + ".")) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1204,6 +1204,18 @@ public class ProjectionOperation implements FieldsExposingAggregationOperation {
|
||||
return this.operation.and(DateOperators.DateToString.dateOf(getRequiredName()).toString(format));
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a {@code $dateToString} expression that takes the date representation of the previously mentioned field
|
||||
* using the server default format. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @return
|
||||
* @since 2.1
|
||||
*/
|
||||
public ProjectionOperationBuilder dateAsFormattedString() {
|
||||
return this.operation.and(DateOperators.DateToString.dateOf(getRequiredName()).defaultFormat());
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a {@code $let} expression that binds variables for use in the specified expression, and returns the
|
||||
* result of the expression.
|
||||
|
||||
@@ -350,8 +350,7 @@ public class StringOperators {
|
||||
* @return
|
||||
*/
|
||||
public StrLenBytes length() {
|
||||
return usesFieldRef() ? StrLenBytes.stringLengthOf(fieldReference)
|
||||
: StrLenBytes.stringLengthOf(expression);
|
||||
return usesFieldRef() ? StrLenBytes.stringLengthOf(fieldReference) : StrLenBytes.stringLengthOf(expression);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -391,6 +390,132 @@ public class StringOperators {
|
||||
return usesFieldRef() ? SubstrCP.valueOf(fieldReference) : SubstrCP.valueOf(expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link AggregationExpression} that takes the associated string representation and trims whitespaces
|
||||
* from the beginning and end. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @return new instance of {@link Trim}.
|
||||
* @since 2.1
|
||||
*/
|
||||
public Trim trim() {
|
||||
return createTrim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link AggregationExpression} that takes the associated string representation and trims the given
|
||||
* character sequence from the beginning and end. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @param chars must not be {@literal null}.
|
||||
* @return new instance of {@link Trim}.
|
||||
* @since 2.1
|
||||
*/
|
||||
public Trim trim(String chars) {
|
||||
return trim().chars(chars);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link AggregationExpression} that takes the associated string representation and trims the character
|
||||
* sequence resulting from the given {@link AggregationExpression} from the beginning and end. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return new instance of {@link Trim}.
|
||||
* @since 2.1
|
||||
*/
|
||||
public Trim trim(AggregationExpression expression) {
|
||||
return trim().charsOf(expression);
|
||||
}
|
||||
|
||||
private Trim createTrim() {
|
||||
return usesFieldRef() ? Trim.valueOf(fieldReference) : Trim.valueOf(expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link AggregationExpression} that takes the associated string representation and trims whitespaces
|
||||
* from the beginning. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @return new instance of {@link LTrim}.
|
||||
* @since 2.1
|
||||
*/
|
||||
public LTrim ltrim() {
|
||||
return createLTrim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link AggregationExpression} that takes the associated string representation and trims the given
|
||||
* character sequence from the beginning. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @param chars must not be {@literal null}.
|
||||
* @return new instance of {@link LTrim}.
|
||||
* @since 2.1
|
||||
*/
|
||||
public LTrim ltrim(String chars) {
|
||||
return ltrim().chars(chars);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link AggregationExpression} that takes the associated string representation and trims the character
|
||||
* sequence resulting from the given {@link AggregationExpression} from the beginning. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return new instance of {@link LTrim}.
|
||||
* @since 2.1
|
||||
*/
|
||||
public LTrim ltrim(AggregationExpression expression) {
|
||||
return ltrim().charsOf(expression);
|
||||
}
|
||||
|
||||
private LTrim createLTrim() {
|
||||
return usesFieldRef() ? LTrim.valueOf(fieldReference) : LTrim.valueOf(expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link AggregationExpression} that takes the associated string representation and trims whitespaces
|
||||
* from the end. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @return new instance of {@link RTrim}.
|
||||
* @since 2.1
|
||||
*/
|
||||
public RTrim rtrim() {
|
||||
return createRTrim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link AggregationExpression} that takes the associated string representation and trims the given
|
||||
* character sequence from the end. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @param chars must not be {@literal null}.
|
||||
* @return new instance of {@link RTrim}.
|
||||
* @since 2.1
|
||||
*/
|
||||
public RTrim rtrim(String chars) {
|
||||
return rtrim().chars(chars);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link AggregationExpression} that takes the associated string representation and trims the character
|
||||
* sequence resulting from the given {@link AggregationExpression} from the end. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return new instance of {@link RTrim}.
|
||||
* @since 2.1
|
||||
*/
|
||||
public RTrim rtrim(AggregationExpression expression) {
|
||||
return rtrim().charsOf(expression);
|
||||
}
|
||||
|
||||
private RTrim createRTrim() {
|
||||
return usesFieldRef() ? RTrim.valueOf(fieldReference) : RTrim.valueOf(expression);
|
||||
}
|
||||
|
||||
private boolean usesFieldRef() {
|
||||
return fieldReference != null;
|
||||
}
|
||||
@@ -1072,4 +1197,257 @@ public class StringOperators {
|
||||
return new SubstrCP(append(Arrays.asList(start, nrOfChars)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link AggregationExpression} for {@code $trim} which removes whitespace or the specified characters from the
|
||||
* beginning and end of a string. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.1
|
||||
*/
|
||||
public static class Trim extends AbstractAggregationExpression {
|
||||
|
||||
private Trim(Object value) {
|
||||
super(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link Trim} using the value of the provided {@link Field fieldReference} as {@literal input} value.
|
||||
*
|
||||
* @param fieldReference must not be {@literal null}.
|
||||
* @return new instance of {@link LTrim}.
|
||||
*/
|
||||
public static Trim valueOf(String fieldReference) {
|
||||
|
||||
Assert.notNull(fieldReference, "FieldReference must not be null!");
|
||||
return new Trim(Collections.singletonMap("input", Fields.field(fieldReference)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link Trim} using the result of the provided {@link AggregationExpression} as {@literal input}
|
||||
* value.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return new instance of {@link Trim}.
|
||||
*/
|
||||
public static Trim valueOf(AggregationExpression expression) {
|
||||
|
||||
Assert.notNull(expression, "Expression must not be null!");
|
||||
return new Trim(Collections.singletonMap("input", expression));
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional specify the character(s) to trim from the beginning.
|
||||
*
|
||||
* @param chars must not be {@literal null}.
|
||||
* @return new instance of {@link Trim}.
|
||||
*/
|
||||
public Trim chars(String chars) {
|
||||
|
||||
Assert.notNull(chars, "Chars must not be null!");
|
||||
return new Trim(append("chars", chars));
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional specify the reference to the {@link Field field} holding the character values to trim from the
|
||||
* beginning.
|
||||
*
|
||||
* @param fieldReference must not be {@literal null}.
|
||||
* @return new instance of {@link Trim}.
|
||||
*/
|
||||
public Trim charsOf(String fieldReference) {
|
||||
return new Trim(append("chars", Fields.field(fieldReference)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional specify the {@link AggregationExpression} evaluating to the character sequence to trim from the
|
||||
* beginning.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return new instance of {@link Trim}.
|
||||
*/
|
||||
public Trim charsOf(AggregationExpression expression) {
|
||||
return new Trim(append("chars", expression));
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove whitespace or the specified characters from the beginning of a string.<br />
|
||||
*
|
||||
* @return new instance of {@link LTrim}.
|
||||
*/
|
||||
public LTrim left() {
|
||||
return new LTrim(argumentMap());
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove whitespace or the specified characters from the end of a string.<br />
|
||||
*
|
||||
* @return new instance of {@link RTrim}.
|
||||
*/
|
||||
public RTrim right() {
|
||||
return new RTrim(argumentMap());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getMongoMethod() {
|
||||
return "$trim";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link AggregationExpression} for {@code $ltrim} which removes whitespace or the specified characters from the
|
||||
* beginning of a string. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.1
|
||||
*/
|
||||
public static class LTrim extends AbstractAggregationExpression {
|
||||
|
||||
private LTrim(Object value) {
|
||||
super(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link LTrim} using the value of the provided {@link Field fieldReference} as {@literal input} value.
|
||||
*
|
||||
* @param fieldReference must not be {@literal null}.
|
||||
* @return new instance of {@link LTrim}.
|
||||
*/
|
||||
public static LTrim valueOf(String fieldReference) {
|
||||
|
||||
Assert.notNull(fieldReference, "FieldReference must not be null!");
|
||||
return new LTrim(Collections.singletonMap("input", Fields.field(fieldReference)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link LTrim} using the result of the provided {@link AggregationExpression} as {@literal input}
|
||||
* value.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return new instance of {@link LTrim}.
|
||||
*/
|
||||
public static LTrim valueOf(AggregationExpression expression) {
|
||||
|
||||
Assert.notNull(expression, "Expression must not be null!");
|
||||
return new LTrim(Collections.singletonMap("input", expression));
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional specify the character(s) to trim from the beginning.
|
||||
*
|
||||
* @param chars must not be {@literal null}.
|
||||
* @return new instance of {@link LTrim}.
|
||||
*/
|
||||
public LTrim chars(String chars) {
|
||||
|
||||
Assert.notNull(chars, "Chars must not be null!");
|
||||
return new LTrim(append("chars", chars));
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional specify the reference to the {@link Field field} holding the character values to trim from the
|
||||
* beginning.
|
||||
*
|
||||
* @param fieldReference must not be {@literal null}.
|
||||
* @return new instance of {@link LTrim}.
|
||||
*/
|
||||
public LTrim charsOf(String fieldReference) {
|
||||
return new LTrim(append("chars", Fields.field(fieldReference)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional specify the {@link AggregationExpression} evaluating to the character sequence to trim from the
|
||||
* beginning.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return new instance of {@link LTrim}.
|
||||
*/
|
||||
public LTrim charsOf(AggregationExpression expression) {
|
||||
return new LTrim(append("chars", expression));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getMongoMethod() {
|
||||
return "$ltrim";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link AggregationExpression} for {@code $rtrim} which removes whitespace or the specified characters from the end
|
||||
* of a string. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.1
|
||||
*/
|
||||
public static class RTrim extends AbstractAggregationExpression {
|
||||
|
||||
private RTrim(Object value) {
|
||||
super(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link RTrim} using the value of the provided {@link Field fieldReference} as {@literal input} value.
|
||||
*
|
||||
* @param fieldReference must not be {@literal null}.
|
||||
* @return new instance of {@link RTrim}.
|
||||
*/
|
||||
public static RTrim valueOf(String fieldReference) {
|
||||
|
||||
Assert.notNull(fieldReference, "FieldReference must not be null!");
|
||||
return new RTrim(Collections.singletonMap("input", Fields.field(fieldReference)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link RTrim} using the result of the provided {@link AggregationExpression} as {@literal input}
|
||||
* value.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return new instance of {@link RTrim}.
|
||||
*/
|
||||
public static RTrim valueOf(AggregationExpression expression) {
|
||||
|
||||
Assert.notNull(expression, "Expression must not be null!");
|
||||
return new RTrim(Collections.singletonMap("input", expression));
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional specify the character(s) to trim from the end.
|
||||
*
|
||||
* @param chars must not be {@literal null}.
|
||||
* @return new instance of {@link RTrim}.
|
||||
*/
|
||||
public RTrim chars(String chars) {
|
||||
|
||||
Assert.notNull(chars, "Chars must not be null!");
|
||||
return new RTrim(append("chars", chars));
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional specify the reference to the {@link Field field} holding the character values to trim from the end.
|
||||
*
|
||||
* @param fieldReference must not be {@literal null}.
|
||||
* @return new instance of {@link RTrim}.
|
||||
*/
|
||||
public RTrim charsOf(String fieldReference) {
|
||||
return new RTrim(append("chars", Fields.field(fieldReference)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional specify the {@link AggregationExpression} evaluating to the character sequence to trim from the end.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return new instance of {@link RTrim}.
|
||||
*/
|
||||
public RTrim charsOf(AggregationExpression expression) {
|
||||
return new RTrim(append("chars", expression));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getMongoMethod() {
|
||||
return "$rtrim";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.mongodb.DBRef;
|
||||
|
||||
@@ -59,9 +60,15 @@ public interface DbRefResolver {
|
||||
* @param id will never be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
DBRef createDbRef(@Nullable org.springframework.data.mongodb.core.mapping.DBRef annotation,
|
||||
MongoPersistentEntity<?> entity,
|
||||
Object id);
|
||||
default DBRef createDbRef(@Nullable org.springframework.data.mongodb.core.mapping.DBRef annotation,
|
||||
MongoPersistentEntity<?> entity, Object id) {
|
||||
|
||||
if (annotation != null && StringUtils.hasText(annotation.db())) {
|
||||
return new DBRef(annotation.db(), entity.getCollection(), id);
|
||||
}
|
||||
|
||||
return new DBRef(entity.getCollection(), id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Actually loads the {@link DBRef} from the datasource.
|
||||
|
||||
@@ -43,7 +43,6 @@ import org.springframework.dao.support.PersistenceExceptionTranslator;
|
||||
import org.springframework.data.mongodb.ClientSessionException;
|
||||
import org.springframework.data.mongodb.LazyLoadingException;
|
||||
import org.springframework.data.mongodb.MongoDbFactory;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.objenesis.ObjenesisStd;
|
||||
@@ -104,21 +103,6 @@ public class DefaultDbRefResolver implements DbRefResolver {
|
||||
return callback.resolve(property);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.convert.DbRefResolver#created(org.springframework.data.mongodb.core.mapping.MongoPersistentProperty, org.springframework.data.mongodb.core.mapping.MongoPersistentEntity, java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public DBRef createDbRef(@Nullable org.springframework.data.mongodb.core.mapping.DBRef annotation,
|
||||
MongoPersistentEntity<?> entity, Object id) {
|
||||
|
||||
if (annotation != null && StringUtils.hasText(annotation.db())) {
|
||||
return new DBRef(annotation.db(), entity.getCollection(), id);
|
||||
}
|
||||
|
||||
return new DBRef(entity.getCollection(), id);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.convert.DbRefResolver#fetch(com.mongodb.DBRef)
|
||||
|
||||
@@ -21,6 +21,7 @@ import java.util.Map;
|
||||
|
||||
import org.bson.Document;
|
||||
import org.bson.conversions.Bson;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
|
||||
import org.springframework.data.mongodb.util.BsonUtils;
|
||||
import org.springframework.lang.Nullable;
|
||||
@@ -58,6 +59,14 @@ class DocumentAccessor {
|
||||
this.document = document;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the underlying {@link Bson document}.
|
||||
* @since 2.1
|
||||
*/
|
||||
Bson getDocument() {
|
||||
return this.document;
|
||||
}
|
||||
|
||||
/**
|
||||
* Puts the given value into the backing {@link Document} based on the coordinates defined through the given
|
||||
* {@link MongoPersistentProperty}. By default this will be the plain field name. But field names might also consist
|
||||
@@ -103,13 +112,14 @@ class DocumentAccessor {
|
||||
public Object get(MongoPersistentProperty property) {
|
||||
|
||||
String fieldName = property.getFieldName();
|
||||
Map<String, Object> map = BsonUtils.asMap(document);
|
||||
|
||||
if (!fieldName.contains(".")) {
|
||||
return BsonUtils.asMap(this.document).get(fieldName);
|
||||
return map.get(fieldName);
|
||||
}
|
||||
|
||||
Iterator<String> parts = Arrays.asList(fieldName.split("\\.")).iterator();
|
||||
Map<String, Object> source = BsonUtils.asMap(this.document);
|
||||
Map<String, Object> source = map;
|
||||
Object result = null;
|
||||
|
||||
while (source != null && parts.hasNext()) {
|
||||
@@ -124,6 +134,17 @@ class DocumentAccessor {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the raw identifier for the given {@link MongoPersistentEntity} or the value of the default identifier
|
||||
* field.
|
||||
*
|
||||
* @param entity must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public Object getRawId(MongoPersistentEntity<?> entity) {
|
||||
return entity.hasIdProperty() ? get(entity.getRequiredIdProperty()) : BsonUtils.asMap(document).get("_id");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the underlying {@link Document} has a value ({@literal null} or non-{@literal null}) for the given
|
||||
* {@link MongoPersistentProperty}.
|
||||
@@ -131,21 +152,27 @@ class DocumentAccessor {
|
||||
* @param property must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public boolean hasValue(MongoPersistentProperty property) {
|
||||
|
||||
Assert.notNull(property, "Property must not be null!");
|
||||
|
||||
String fieldName = property.getFieldName();
|
||||
|
||||
|
||||
if (this.document instanceof Document) {
|
||||
|
||||
if (((Document) this.document).containsKey(fieldName)) {
|
||||
return true;
|
||||
}
|
||||
} else if (this.document instanceof DBObject) {
|
||||
if (((DBObject) this.document).containsField(fieldName)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!fieldName.contains(".")) {
|
||||
|
||||
if (this.document instanceof Document) {
|
||||
return ((Document) this.document).containsKey(fieldName);
|
||||
}
|
||||
|
||||
if (this.document instanceof DBObject) {
|
||||
return ((DBObject) this.document).containsField(fieldName);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
String[] parts = fieldName.split("\\.");
|
||||
|
||||
@@ -15,10 +15,13 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core.convert;
|
||||
|
||||
import java.text.Collator;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import org.bson.Document;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
@@ -41,11 +44,13 @@ import org.springframework.data.mongodb.core.geo.GeoJsonPoint;
|
||||
import org.springframework.data.mongodb.core.geo.GeoJsonPolygon;
|
||||
import org.springframework.data.mongodb.core.geo.Sphere;
|
||||
import org.springframework.data.mongodb.core.query.GeoCommand;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.NumberUtils;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
import com.mongodb.BasicDBList;
|
||||
import com.mongodb.Function;
|
||||
|
||||
/**
|
||||
* Wrapper class to contain useful geo structure converters for the usage with Mongo.
|
||||
@@ -58,6 +63,26 @@ import com.mongodb.BasicDBList;
|
||||
*/
|
||||
abstract class GeoConverters {
|
||||
|
||||
|
||||
private final static Map<String, Function<Document, GeoJson<?>>> converters;
|
||||
|
||||
static {
|
||||
|
||||
Collator caseInsensitive = Collator.getInstance();
|
||||
caseInsensitive.setStrength(Collator.PRIMARY);
|
||||
|
||||
Map<String, Function<Document, GeoJson<?>>> geoConverters = new TreeMap<>(caseInsensitive);
|
||||
geoConverters.put("point", DocumentToGeoJsonPointConverter.INSTANCE::convert);
|
||||
geoConverters.put("multipoint", DocumentToGeoJsonMultiPointConverter.INSTANCE::convert);
|
||||
geoConverters.put("linestring", DocumentToGeoJsonLineStringConverter.INSTANCE::convert);
|
||||
geoConverters.put("multilinestring", DocumentToGeoJsonMultiLineStringConverter.INSTANCE::convert);
|
||||
geoConverters.put("polygon", DocumentToGeoJsonPolygonConverter.INSTANCE::convert);
|
||||
geoConverters.put("multipolygon", DocumentToGeoJsonMultiPolygonConverter.INSTANCE::convert);
|
||||
geoConverters.put("geometrycollection", DocumentToGeoJsonGeometryCollectionConverter.INSTANCE::convert);
|
||||
|
||||
converters = geoConverters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Private constructor to prevent instantiation.
|
||||
*/
|
||||
@@ -91,7 +116,8 @@ abstract class GeoConverters {
|
||||
, DocumentToGeoJsonMultiLineStringConverter.INSTANCE //
|
||||
, DocumentToGeoJsonMultiPointConverter.INSTANCE //
|
||||
, DocumentToGeoJsonMultiPolygonConverter.INSTANCE //
|
||||
, DocumentToGeoJsonGeometryCollectionConverter.INSTANCE);
|
||||
, DocumentToGeoJsonGeometryCollectionConverter.INSTANCE //
|
||||
, DocumentToGeoJsonConverter.INSTANCE);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -101,7 +127,7 @@ abstract class GeoConverters {
|
||||
* @since 1.5
|
||||
*/
|
||||
@ReadingConverter
|
||||
static enum DocumentToPointConverter implements Converter<Document, Point> {
|
||||
enum DocumentToPointConverter implements Converter<Document, Point> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@@ -132,7 +158,7 @@ abstract class GeoConverters {
|
||||
* @author Thomas Darimont
|
||||
* @since 1.5
|
||||
*/
|
||||
static enum PointToDocumentConverter implements Converter<Point, Document> {
|
||||
enum PointToDocumentConverter implements Converter<Point, Document> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@@ -147,13 +173,13 @@ abstract class GeoConverters {
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a {@link Box} into a {@link BasicDBList}.
|
||||
* Converts a {@link Box} into a {@link Document}.
|
||||
*
|
||||
* @author Thomas Darimont
|
||||
* @since 1.5
|
||||
*/
|
||||
@WritingConverter
|
||||
static enum BoxToDocumentConverter implements Converter<Box, Document> {
|
||||
enum BoxToDocumentConverter implements Converter<Box, Document> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@@ -176,13 +202,13 @@ abstract class GeoConverters {
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a {@link BasicDBList} into a {@link Box}.
|
||||
* Converts a {@link Document} into a {@link Box}.
|
||||
*
|
||||
* @author Thomas Darimont
|
||||
* @since 1.5
|
||||
*/
|
||||
@ReadingConverter
|
||||
static enum DocumentToBoxConverter implements Converter<Document, Box> {
|
||||
enum DocumentToBoxConverter implements Converter<Document, Box> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@@ -205,12 +231,12 @@ abstract class GeoConverters {
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a {@link Circle} into a {@link BasicDBList}.
|
||||
* Converts a {@link Circle} into a {@link Document}.
|
||||
*
|
||||
* @author Thomas Darimont
|
||||
* @since 1.5
|
||||
*/
|
||||
static enum CircleToDocumentConverter implements Converter<Circle, Document> {
|
||||
enum CircleToDocumentConverter implements Converter<Circle, Document> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@@ -276,7 +302,7 @@ abstract class GeoConverters {
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a {@link Sphere} into a {@link BasicDBList}.
|
||||
* Converts a {@link Sphere} into a {@link Document}.
|
||||
*
|
||||
* @author Thomas Darimont
|
||||
* @since 1.5
|
||||
@@ -305,13 +331,13 @@ abstract class GeoConverters {
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a {@link BasicDBList} into a {@link Sphere}.
|
||||
* Converts a {@link Document} into a {@link Sphere}.
|
||||
*
|
||||
* @author Thomas Darimont
|
||||
* @since 1.5
|
||||
*/
|
||||
@ReadingConverter
|
||||
static enum DocumentToSphereConverter implements Converter<Document, Sphere> {
|
||||
enum DocumentToSphereConverter implements Converter<Document, Sphere> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@@ -347,12 +373,12 @@ abstract class GeoConverters {
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a {@link Polygon} into a {@link BasicDBList}.
|
||||
* Converts a {@link Polygon} into a {@link Document}.
|
||||
*
|
||||
* @author Thomas Darimont
|
||||
* @since 1.5
|
||||
*/
|
||||
static enum PolygonToDocumentConverter implements Converter<Polygon, Document> {
|
||||
enum PolygonToDocumentConverter implements Converter<Polygon, Document> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@@ -368,7 +394,7 @@ abstract class GeoConverters {
|
||||
}
|
||||
|
||||
List<Point> points = source.getPoints();
|
||||
List<Document> pointTuples = new ArrayList<Document>(points.size());
|
||||
List<Document> pointTuples = new ArrayList<>(points.size());
|
||||
|
||||
for (Point point : points) {
|
||||
pointTuples.add(PointToDocumentConverter.INSTANCE.convert(point));
|
||||
@@ -381,13 +407,13 @@ abstract class GeoConverters {
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a {@link BasicDBList} into a {@link Polygon}.
|
||||
* Converts a {@link Document} into a {@link Polygon}.
|
||||
*
|
||||
* @author Thomas Darimont
|
||||
* @since 1.5
|
||||
*/
|
||||
@ReadingConverter
|
||||
static enum DocumentToPolygonConverter implements Converter<Document, Polygon> {
|
||||
enum DocumentToPolygonConverter implements Converter<Document, Polygon> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@@ -404,7 +430,7 @@ abstract class GeoConverters {
|
||||
}
|
||||
|
||||
List<Document> points = (List<Document>) source.get("points");
|
||||
List<Point> newPoints = new ArrayList<Point>(points.size());
|
||||
List<Point> newPoints = new ArrayList<>(points.size());
|
||||
|
||||
for (Document element : points) {
|
||||
|
||||
@@ -417,12 +443,12 @@ abstract class GeoConverters {
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a {@link Sphere} into a {@link BasicDBList}.
|
||||
* Converts a {@link Sphere} into a {@link Document}.
|
||||
*
|
||||
* @author Thomas Darimont
|
||||
* @since 1.5
|
||||
*/
|
||||
static enum GeoCommandToDocumentConverter implements Converter<GeoCommand, Document> {
|
||||
enum GeoCommandToDocumentConverter implements Converter<GeoCommand, Document> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@@ -482,7 +508,7 @@ abstract class GeoConverters {
|
||||
* @since 1.7
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
static enum GeoJsonToDocumentConverter implements Converter<GeoJson, Document> {
|
||||
enum GeoJsonToDocumentConverter implements Converter<GeoJson, Document> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@@ -545,7 +571,7 @@ abstract class GeoConverters {
|
||||
* @author Christoph Strobl
|
||||
* @since 1.7
|
||||
*/
|
||||
static enum GeoJsonPointToDocumentConverter implements Converter<GeoJsonPoint, Document> {
|
||||
enum GeoJsonPointToDocumentConverter implements Converter<GeoJsonPoint, Document> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@@ -563,7 +589,7 @@ abstract class GeoConverters {
|
||||
* @author Christoph Strobl
|
||||
* @since 1.7
|
||||
*/
|
||||
static enum GeoJsonPolygonToDocumentConverter implements Converter<GeoJsonPolygon, Document> {
|
||||
enum GeoJsonPolygonToDocumentConverter implements Converter<GeoJsonPolygon, Document> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@@ -581,7 +607,7 @@ abstract class GeoConverters {
|
||||
* @author Christoph Strobl
|
||||
* @since 1.7
|
||||
*/
|
||||
static enum DocumentToGeoJsonPointConverter implements Converter<Document, GeoJsonPoint> {
|
||||
enum DocumentToGeoJsonPointConverter implements Converter<Document, GeoJsonPoint> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@@ -609,7 +635,7 @@ abstract class GeoConverters {
|
||||
* @author Christoph Strobl
|
||||
* @since 1.7
|
||||
*/
|
||||
static enum DocumentToGeoJsonPolygonConverter implements Converter<Document, GeoJsonPolygon> {
|
||||
enum DocumentToGeoJsonPolygonConverter implements Converter<Document, GeoJsonPolygon> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@@ -654,7 +680,7 @@ abstract class GeoConverters {
|
||||
String.format("Cannot convert type '%s' to MultiPolygon.", source.get("type")));
|
||||
|
||||
List dbl = (List) source.get("coordinates");
|
||||
List<GeoJsonPolygon> polygones = new ArrayList<GeoJsonPolygon>();
|
||||
List<GeoJsonPolygon> polygones = new ArrayList<>();
|
||||
|
||||
for (Object polygon : dbl) {
|
||||
polygones.add(toGeoJsonPolygon((List) polygon));
|
||||
@@ -668,7 +694,7 @@ abstract class GeoConverters {
|
||||
* @author Christoph Strobl
|
||||
* @since 1.7
|
||||
*/
|
||||
static enum DocumentToGeoJsonLineStringConverter implements Converter<Document, GeoJsonLineString> {
|
||||
enum DocumentToGeoJsonLineStringConverter implements Converter<Document, GeoJsonLineString> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@@ -696,7 +722,7 @@ abstract class GeoConverters {
|
||||
* @author Christoph Strobl
|
||||
* @since 1.7
|
||||
*/
|
||||
static enum DocumentToGeoJsonMultiPointConverter implements Converter<Document, GeoJsonMultiPoint> {
|
||||
enum DocumentToGeoJsonMultiPointConverter implements Converter<Document, GeoJsonMultiPoint> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@@ -724,7 +750,7 @@ abstract class GeoConverters {
|
||||
* @author Christoph Strobl
|
||||
* @since 1.7
|
||||
*/
|
||||
static enum DocumentToGeoJsonMultiLineStringConverter implements Converter<Document, GeoJsonMultiLineString> {
|
||||
enum DocumentToGeoJsonMultiLineStringConverter implements Converter<Document, GeoJsonMultiLineString> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@@ -756,7 +782,7 @@ abstract class GeoConverters {
|
||||
* @author Christoph Strobl
|
||||
* @since 1.7
|
||||
*/
|
||||
static enum DocumentToGeoJsonGeometryCollectionConverter implements Converter<Document, GeoJsonGeometryCollection> {
|
||||
enum DocumentToGeoJsonGeometryCollectionConverter implements Converter<Document, GeoJsonGeometryCollection> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@@ -775,41 +801,12 @@ abstract class GeoConverters {
|
||||
Assert.isTrue(ObjectUtils.nullSafeEquals(source.get("type"), "GeometryCollection"),
|
||||
String.format("Cannot convert type '%s' to GeometryCollection.", source.get("type")));
|
||||
|
||||
List<GeoJson<?>> geometries = new ArrayList<GeoJson<?>>();
|
||||
List<GeoJson<?>> geometries = new ArrayList<>();
|
||||
for (Object o : (List) source.get("geometries")) {
|
||||
geometries.add(convertGeometries((Document) o));
|
||||
geometries.add(toGenericGeoJson((Document) o));
|
||||
}
|
||||
|
||||
return new GeoJsonGeometryCollection(geometries);
|
||||
|
||||
}
|
||||
|
||||
private static GeoJson<?> convertGeometries(Document source) {
|
||||
|
||||
Object type = source.get("type");
|
||||
if (ObjectUtils.nullSafeEquals(type, "Point")) {
|
||||
return DocumentToGeoJsonPointConverter.INSTANCE.convert(source);
|
||||
}
|
||||
|
||||
if (ObjectUtils.nullSafeEquals(type, "MultiPoint")) {
|
||||
return DocumentToGeoJsonMultiPointConverter.INSTANCE.convert(source);
|
||||
}
|
||||
|
||||
if (ObjectUtils.nullSafeEquals(type, "LineString")) {
|
||||
return DocumentToGeoJsonLineStringConverter.INSTANCE.convert(source);
|
||||
}
|
||||
|
||||
if (ObjectUtils.nullSafeEquals(type, "MultiLineString")) {
|
||||
return DocumentToGeoJsonMultiLineStringConverter.INSTANCE.convert(source);
|
||||
}
|
||||
|
||||
if (ObjectUtils.nullSafeEquals(type, "Polygon")) {
|
||||
return DocumentToGeoJsonPolygonConverter.INSTANCE.convert(source);
|
||||
}
|
||||
if (ObjectUtils.nullSafeEquals(type, "MultiPolygon")) {
|
||||
return DocumentToGeoJsonMultiPolygonConverter.INSTANCE.convert(source);
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException(String.format("Cannot convert unknown GeoJson type %s", type));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -827,7 +824,7 @@ abstract class GeoConverters {
|
||||
@SuppressWarnings("unchecked")
|
||||
static List<Point> toListOfPoint(List listOfCoordinatePairs) {
|
||||
|
||||
List<Point> points = new ArrayList<Point>();
|
||||
List<Point> points = new ArrayList<>();
|
||||
|
||||
for (Object point : listOfCoordinatePairs) {
|
||||
|
||||
@@ -852,6 +849,46 @@ abstract class GeoConverters {
|
||||
return new GeoJsonPolygon(toListOfPoint((List) dbList.get(0)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Converter implementation transforming a {@link Document} into a concrete {@link GeoJson} based on the embedded
|
||||
* {@literal type} information.
|
||||
*
|
||||
* @since 2.1
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
@ReadingConverter
|
||||
enum DocumentToGeoJsonConverter implements Converter<Document, GeoJson> {
|
||||
INSTANCE;
|
||||
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object)
|
||||
*/
|
||||
@Nullable
|
||||
@Override
|
||||
public GeoJson convert(Document source) {
|
||||
return toGenericGeoJson(source);
|
||||
}
|
||||
}
|
||||
|
||||
private static GeoJson<?> toGenericGeoJson(Document source) {
|
||||
|
||||
String type = source.get("type", String.class);
|
||||
|
||||
if(type != null) {
|
||||
|
||||
Function<Document, GeoJson<?>> converter = converters.get(type);
|
||||
|
||||
if(converter != null){
|
||||
return converter.apply(source);
|
||||
}
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException(
|
||||
String.format("No converter found capable of converting GeoJson type %s.", type));
|
||||
}
|
||||
|
||||
private static double toPrimitiveDoubleValue(Object value) {
|
||||
|
||||
Assert.isInstanceOf(Number.class, value, "Argument must be a Number.");
|
||||
|
||||
@@ -15,20 +15,12 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core.convert;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import org.bson.Document;
|
||||
import org.bson.conversions.Bson;
|
||||
import org.bson.types.ObjectId;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.BeansException;
|
||||
@@ -42,6 +34,7 @@ import org.springframework.data.convert.TypeMapper;
|
||||
import org.springframework.data.mapping.Association;
|
||||
import org.springframework.data.mapping.MappingException;
|
||||
import org.springframework.data.mapping.PersistentPropertyAccessor;
|
||||
import org.springframework.data.mapping.PreferredConstructor;
|
||||
import org.springframework.data.mapping.PreferredConstructor.Parameter;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
|
||||
@@ -147,7 +140,8 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
*/
|
||||
public void setTypeMapper(@Nullable MongoTypeMapper typeMapper) {
|
||||
this.typeMapper = typeMapper == null
|
||||
? new DefaultMongoTypeMapper(DefaultMongoTypeMapper.DEFAULT_TYPE_KEY, mappingContext) : typeMapper;
|
||||
? new DefaultMongoTypeMapper(DefaultMongoTypeMapper.DEFAULT_TYPE_KEY, mappingContext)
|
||||
: typeMapper;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -249,13 +243,14 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
throw new MappingException(String.format(INVALID_TYPE_TO_READ, target, typeToUse.getType()));
|
||||
}
|
||||
|
||||
return read((MongoPersistentEntity<S>) mappingContext.getRequiredPersistentEntity(typeToUse), target, path);
|
||||
return read((MongoPersistentEntity<S>) entity, target, path);
|
||||
}
|
||||
|
||||
private ParameterValueProvider<MongoPersistentProperty> getParameterProvider(MongoPersistentEntity<?> entity,
|
||||
Bson source, DefaultSpELExpressionEvaluator evaluator, ObjectPath path) {
|
||||
DocumentAccessor source, SpELExpressionEvaluator evaluator, ObjectPath path) {
|
||||
|
||||
MongoDbPropertyValueProvider provider = new MongoDbPropertyValueProvider(source, evaluator, path);
|
||||
AssociationAwareMongoDbPropertyValueProvider provider = new AssociationAwareMongoDbPropertyValueProvider(source,
|
||||
evaluator, path);
|
||||
PersistentEntityParameterValueProvider<MongoPersistentProperty> parameterProvider = new PersistentEntityParameterValueProvider<>(
|
||||
entity, provider, path.getCurrentObject());
|
||||
|
||||
@@ -265,60 +260,105 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
|
||||
private <S extends Object> S read(final MongoPersistentEntity<S> entity, final Document bson, final ObjectPath path) {
|
||||
|
||||
DefaultSpELExpressionEvaluator evaluator = new DefaultSpELExpressionEvaluator(bson, spELContext);
|
||||
SpELExpressionEvaluator evaluator = new DefaultSpELExpressionEvaluator(bson, spELContext);
|
||||
DocumentAccessor documentAccessor = new DocumentAccessor(bson);
|
||||
|
||||
PreferredConstructor<S, MongoPersistentProperty> persistenceConstructor = entity.getPersistenceConstructor();
|
||||
|
||||
ParameterValueProvider<MongoPersistentProperty> provider = persistenceConstructor != null
|
||||
&& persistenceConstructor.hasParameters() ? getParameterProvider(entity, documentAccessor, evaluator, path)
|
||||
: NoOpParameterValueProvider.INSTANCE;
|
||||
|
||||
ParameterValueProvider<MongoPersistentProperty> provider = getParameterProvider(entity, bson, evaluator, path);
|
||||
EntityInstantiator instantiator = instantiators.getInstantiatorFor(entity);
|
||||
S instance = instantiator.createInstance(entity, provider);
|
||||
|
||||
PersistentPropertyAccessor accessor = new ConvertingPropertyAccessor(entity.getPropertyAccessor(instance),
|
||||
conversionService);
|
||||
|
||||
MongoPersistentProperty idProperty = entity.getIdProperty();
|
||||
DocumentAccessor documentAccessor = new DocumentAccessor(bson);
|
||||
|
||||
// make sure id property is set before all other properties
|
||||
Object idValue = null;
|
||||
|
||||
if (idProperty != null && documentAccessor.hasValue(idProperty)) {
|
||||
|
||||
idValue = readIdValue(path, evaluator, idProperty, documentAccessor);
|
||||
accessor.setProperty(idProperty, idValue);
|
||||
if (entity.requiresPropertyPopulation()) {
|
||||
return populateProperties(entity, documentAccessor, path, evaluator, instance);
|
||||
}
|
||||
|
||||
ObjectPath currentPath = path.push(instance, entity, idValue != null ? bson.get(idProperty.getFieldName()) : null);
|
||||
|
||||
MongoDbPropertyValueProvider valueProvider = new MongoDbPropertyValueProvider(documentAccessor, evaluator,
|
||||
currentPath);
|
||||
|
||||
DbRefResolverCallback callback = new DefaultDbRefResolverCallback(bson, currentPath, evaluator,
|
||||
MappingMongoConverter.this);
|
||||
readProperties(entity, accessor, idProperty, documentAccessor, valueProvider, callback);
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
private Object readIdValue(ObjectPath path, DefaultSpELExpressionEvaluator evaluator,
|
||||
MongoPersistentProperty idProperty, DocumentAccessor documentAccessor) {
|
||||
private <S> S populateProperties(MongoPersistentEntity<S> entity, DocumentAccessor documentAccessor, ObjectPath path,
|
||||
SpELExpressionEvaluator evaluator, S instance) {
|
||||
|
||||
PersistentPropertyAccessor<S> accessor = new ConvertingPropertyAccessor<>(entity.getPropertyAccessor(instance),
|
||||
conversionService);
|
||||
|
||||
// Make sure id property is set before all other properties
|
||||
|
||||
Object rawId = readAndPopulateIdentifier(accessor, documentAccessor, entity, path, evaluator);
|
||||
ObjectPath currentPath = path.push(accessor.getBean(), entity, rawId);
|
||||
|
||||
MongoDbPropertyValueProvider valueProvider = new MongoDbPropertyValueProvider(documentAccessor, evaluator,
|
||||
currentPath);
|
||||
|
||||
readProperties(entity, accessor, documentAccessor, valueProvider, currentPath, evaluator);
|
||||
|
||||
return accessor.getBean();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the identifier from either the bean backing the {@link PersistentPropertyAccessor} or the source document in
|
||||
* case the identifier has not be populated yet. In this case the identifier is set on the bean for further reference.
|
||||
*
|
||||
* @param accessor must not be {@literal null}.
|
||||
* @param document must not be {@literal null}.
|
||||
* @param entity must not be {@literal null}.
|
||||
* @param path
|
||||
* @param evaluator
|
||||
* @return
|
||||
*/
|
||||
private Object readAndPopulateIdentifier(PersistentPropertyAccessor<?> accessor, DocumentAccessor document,
|
||||
MongoPersistentEntity<?> entity, ObjectPath path, SpELExpressionEvaluator evaluator) {
|
||||
|
||||
Object rawId = document.getRawId(entity);
|
||||
|
||||
if (!entity.hasIdProperty() || rawId == null) {
|
||||
return rawId;
|
||||
}
|
||||
|
||||
MongoPersistentProperty idProperty = entity.getRequiredIdProperty();
|
||||
|
||||
if (idProperty.isImmutable() && entity.isConstructorArgument(idProperty)) {
|
||||
return rawId;
|
||||
}
|
||||
|
||||
accessor.setProperty(idProperty, readIdValue(path, evaluator, idProperty, rawId));
|
||||
|
||||
return rawId;
|
||||
}
|
||||
|
||||
private Object readIdValue(ObjectPath path, SpELExpressionEvaluator evaluator, MongoPersistentProperty idProperty,
|
||||
Object rawId) {
|
||||
|
||||
String expression = idProperty.getSpelExpression();
|
||||
Object resolvedValue = expression != null ? evaluator.evaluate(expression) : documentAccessor.get(idProperty);
|
||||
Object resolvedValue = expression != null ? evaluator.evaluate(expression) : rawId;
|
||||
|
||||
return resolvedValue != null ? readValue(resolvedValue, idProperty.getTypeInformation(), path) : null;
|
||||
}
|
||||
|
||||
private void readProperties(MongoPersistentEntity<?> entity, PersistentPropertyAccessor accessor,
|
||||
@Nullable MongoPersistentProperty idProperty, DocumentAccessor documentAccessor,
|
||||
MongoDbPropertyValueProvider valueProvider, DbRefResolverCallback callback) {
|
||||
private void readProperties(MongoPersistentEntity<?> entity, PersistentPropertyAccessor<?> accessor,
|
||||
DocumentAccessor documentAccessor, MongoDbPropertyValueProvider valueProvider, ObjectPath currentPath,
|
||||
SpELExpressionEvaluator evaluator) {
|
||||
|
||||
DbRefResolverCallback callback = null;
|
||||
|
||||
for (MongoPersistentProperty prop : entity) {
|
||||
|
||||
if (prop.isAssociation() && !entity.isConstructorArgument(prop)) {
|
||||
|
||||
if (callback == null) {
|
||||
callback = getDbRefResolverCallback(documentAccessor, currentPath, evaluator);
|
||||
}
|
||||
|
||||
readAssociation(prop.getRequiredAssociation(), accessor, documentAccessor, dbRefProxyHandler, callback);
|
||||
continue;
|
||||
}
|
||||
// we skip the id property since it was already set
|
||||
if (idProperty != null && idProperty.equals(prop)) {
|
||||
|
||||
// We skip the id property since it was already set
|
||||
|
||||
if (entity.isIdProperty(prop)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -327,6 +367,11 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
}
|
||||
|
||||
if (prop.isAssociation()) {
|
||||
|
||||
if (callback == null) {
|
||||
callback = getDbRefResolverCallback(documentAccessor, currentPath, evaluator);
|
||||
}
|
||||
|
||||
readAssociation(prop.getRequiredAssociation(), accessor, documentAccessor, dbRefProxyHandler, callback);
|
||||
continue;
|
||||
}
|
||||
@@ -335,7 +380,14 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
}
|
||||
}
|
||||
|
||||
private void readAssociation(Association<MongoPersistentProperty> association, PersistentPropertyAccessor accessor,
|
||||
private DbRefResolverCallback getDbRefResolverCallback(DocumentAccessor documentAccessor, ObjectPath currentPath,
|
||||
SpELExpressionEvaluator evaluator) {
|
||||
|
||||
return new DefaultDbRefResolverCallback(documentAccessor.getDocument(), currentPath, evaluator,
|
||||
MappingMongoConverter.this);
|
||||
}
|
||||
|
||||
private void readAssociation(Association<MongoPersistentProperty> association, PersistentPropertyAccessor<?> accessor,
|
||||
DocumentAccessor documentAccessor, DbRefProxyHandler handler, DbRefResolverCallback callback) {
|
||||
|
||||
MongoPersistentProperty property = association.getInverse();
|
||||
@@ -392,12 +444,23 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
removeFromMap(bson, "_id");
|
||||
}
|
||||
|
||||
boolean handledByCustomConverter = conversions.hasCustomWriteTarget(entityType, Document.class);
|
||||
if (!handledByCustomConverter && !(bson instanceof Collection)) {
|
||||
if (requiresTypeHint(entityType)) {
|
||||
typeMapper.writeType(type, bson);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a given type requires a type hint (aka {@literal _class} attribute) when writing to the document.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
* @return {@literal true} if not a simple type, {@link Collection} or type with custom write target.
|
||||
*/
|
||||
private boolean requiresTypeHint(Class<?> type) {
|
||||
|
||||
return !conversions.isSimpleType(type) && !ClassUtils.isAssignable(Collection.class, type)
|
||||
&& !conversions.hasCustomWriteTarget(type, Document.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal write conversion method which should be used for nested invocations.
|
||||
*
|
||||
@@ -427,7 +490,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
}
|
||||
|
||||
if (Collection.class.isAssignableFrom(entityType)) {
|
||||
writeCollectionInternal((Collection<?>) obj, ClassTypeInformation.LIST, (Collection) bson);
|
||||
writeCollectionInternal((Collection<?>) obj, ClassTypeInformation.LIST, (Collection<?>) bson);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -446,22 +509,23 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
throw new MappingException("No mapping metadata found for entity of type " + obj.getClass().getName());
|
||||
}
|
||||
|
||||
PersistentPropertyAccessor accessor = entity.getPropertyAccessor(obj);
|
||||
PersistentPropertyAccessor<?> accessor = entity.getPropertyAccessor(obj);
|
||||
DocumentAccessor dbObjectAccessor = new DocumentAccessor(bson);
|
||||
|
||||
MongoPersistentProperty idProperty = entity.getIdProperty();
|
||||
|
||||
if (idProperty != null && !dbObjectAccessor.hasValue(idProperty)) {
|
||||
|
||||
Object value = idMapper.convertId(accessor.getProperty(idProperty));
|
||||
Object value = idMapper.convertId(accessor.getProperty(idProperty), idProperty.getFieldType());
|
||||
|
||||
if (value != null) {
|
||||
dbObjectAccessor.put(idProperty, value);
|
||||
}
|
||||
}
|
||||
|
||||
writeProperties(bson, entity, accessor, dbObjectAccessor, idProperty);
|
||||
}
|
||||
|
||||
private void writeProperties(Bson bson, MongoPersistentEntity<?> entity, PersistentPropertyAccessor accessor,
|
||||
private void writeProperties(Bson bson, MongoPersistentEntity<?> entity, PersistentPropertyAccessor<?> accessor,
|
||||
DocumentAccessor dbObjectAccessor, @Nullable MongoPersistentProperty idProperty) {
|
||||
|
||||
// Write the properties
|
||||
@@ -489,8 +553,8 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
}
|
||||
}
|
||||
|
||||
private void writeAssociation(Association<MongoPersistentProperty> association, PersistentPropertyAccessor accessor,
|
||||
DocumentAccessor dbObjectAccessor) {
|
||||
private void writeAssociation(Association<MongoPersistentProperty> association,
|
||||
PersistentPropertyAccessor<?> accessor, DocumentAccessor dbObjectAccessor) {
|
||||
|
||||
MongoPersistentProperty inverseProp = association.getInverse();
|
||||
|
||||
@@ -555,8 +619,9 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
return;
|
||||
}
|
||||
|
||||
MongoPersistentEntity<?> entity = isSubtype(prop.getType(), obj.getClass())
|
||||
? mappingContext.getRequiredPersistentEntity(obj.getClass()) : mappingContext.getRequiredPersistentEntity(type);
|
||||
MongoPersistentEntity<?> entity = valueType.isSubTypeOf(prop.getType())
|
||||
? mappingContext.getRequiredPersistentEntity(obj.getClass())
|
||||
: mappingContext.getRequiredPersistentEntity(type);
|
||||
|
||||
Object existingValue = accessor.get(prop);
|
||||
Document document = existingValue instanceof Document ? (Document) existingValue : new Document();
|
||||
@@ -566,10 +631,6 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
accessor.put(prop, document);
|
||||
}
|
||||
|
||||
private boolean isSubtype(Class<?> left, Class<?> right) {
|
||||
return left.isAssignableFrom(right) && !left.equals(right);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns given object as {@link Collection}. Will return the {@link Collection} as is if the source is a
|
||||
* {@link Collection} already, will convert an array into a {@link Collection} or simply create a single element
|
||||
@@ -659,11 +720,13 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
* @param sink the {@link Collection} to write to.
|
||||
* @return
|
||||
*/
|
||||
private List<Object> writeCollectionInternal(Collection<?> source, @Nullable TypeInformation<?> type, Collection<?> sink) {
|
||||
@SuppressWarnings("unchecked")
|
||||
private List<Object> writeCollectionInternal(Collection<?> source, @Nullable TypeInformation<?> type,
|
||||
Collection<?> sink) {
|
||||
|
||||
TypeInformation<?> componentType = null;
|
||||
|
||||
List<Object> collection = sink instanceof List ? (List) sink : new ArrayList<>(sink);
|
||||
List<Object> collection = sink instanceof List ? (List<Object>) sink : new ArrayList<>(sink);
|
||||
|
||||
if (type != null) {
|
||||
componentType = type.getComponentType();
|
||||
@@ -777,7 +840,8 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
}
|
||||
|
||||
return conversions.hasCustomWriteTarget(key.getClass(), String.class)
|
||||
? (String) getPotentiallyConvertedSimpleWrite(key) : key.toString();
|
||||
? (String) getPotentiallyConvertedSimpleWrite(key)
|
||||
: key.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -867,7 +931,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
*/
|
||||
@Nullable
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
private Object getPotentiallyConvertedSimpleRead(@Nullable Object value, @Nullable Class<?> target) {
|
||||
private Object getPotentiallyConvertedSimpleRead(@Nullable Object value, @Nullable Class<?> target) {
|
||||
|
||||
if (value == null || target == null || ClassUtils.isAssignableValue(target, value)) {
|
||||
return value;
|
||||
@@ -912,7 +976,8 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
throw new MappingException("Cannot create a reference to an object with a NULL id.");
|
||||
}
|
||||
|
||||
return dbRefResolver.createDbRef(property == null ? null : property.getDBRef(), entity, idMapper.convertId(id));
|
||||
return dbRefResolver.createDbRef(property == null ? null : property.getDBRef(), entity,
|
||||
idMapper.convertId(id, idProperty != null ? idProperty.getFieldType() : ObjectId.class));
|
||||
}
|
||||
|
||||
throw new MappingException("No id property found on class " + entity.getType());
|
||||
@@ -942,9 +1007,8 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
Assert.notNull(targetType, "Target type must not be null!");
|
||||
Assert.notNull(path, "Object path must not be null!");
|
||||
|
||||
Class<?> collectionType = targetType.getType();
|
||||
collectionType = Collection.class.isAssignableFrom(collectionType) //
|
||||
? collectionType //
|
||||
Class<?> collectionType = targetType.isSubTypeOf(Collection.class) //
|
||||
? targetType.getType() //
|
||||
: List.class;
|
||||
|
||||
TypeInformation<?> componentType = targetType.getComponentType() != null //
|
||||
@@ -977,13 +1041,12 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
items.add(read(componentType, (BasicDBObject) element, path));
|
||||
} else {
|
||||
|
||||
if (element instanceof Collection) {
|
||||
if (!Object.class.equals(rawComponentType) && element instanceof Collection) {
|
||||
if (!rawComponentType.isArray() && !ClassUtils.isAssignable(Iterable.class, rawComponentType)) {
|
||||
throw new MappingException(
|
||||
String.format(INCOMPATIBLE_TYPES, element, element.getClass(), rawComponentType, path));
|
||||
}
|
||||
}
|
||||
|
||||
if (element instanceof List) {
|
||||
items.add(readCollectionOrArray(componentType, (Collection<Object>) element, path));
|
||||
} else {
|
||||
@@ -1270,12 +1333,14 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
* of the configured source {@link Document}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Mark Paluch
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
class MongoDbPropertyValueProvider implements PropertyValueProvider<MongoPersistentProperty> {
|
||||
|
||||
private final DocumentAccessor source;
|
||||
private final SpELExpressionEvaluator evaluator;
|
||||
private final ObjectPath path;
|
||||
final DocumentAccessor accessor;
|
||||
final SpELExpressionEvaluator evaluator;
|
||||
final ObjectPath path;
|
||||
|
||||
/**
|
||||
* Creates a new {@link MongoDbPropertyValueProvider} for the given source, {@link SpELExpressionEvaluator} and
|
||||
@@ -1285,15 +1350,8 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
* @param evaluator must not be {@literal null}.
|
||||
* @param path must not be {@literal null}.
|
||||
*/
|
||||
public MongoDbPropertyValueProvider(Bson source, SpELExpressionEvaluator evaluator, ObjectPath path) {
|
||||
|
||||
Assert.notNull(source, "Source document must no be null!");
|
||||
Assert.notNull(evaluator, "SpELExpressionEvaluator must not be null!");
|
||||
Assert.notNull(path, "ObjectPath must not be null!");
|
||||
|
||||
this.source = new DocumentAccessor(source);
|
||||
this.evaluator = evaluator;
|
||||
this.path = path;
|
||||
MongoDbPropertyValueProvider(Bson source, SpELExpressionEvaluator evaluator, ObjectPath path) {
|
||||
this(new DocumentAccessor(source), evaluator, path);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1304,13 +1362,13 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
* @param evaluator must not be {@literal null}.
|
||||
* @param path must not be {@literal null}.
|
||||
*/
|
||||
public MongoDbPropertyValueProvider(DocumentAccessor accessor, SpELExpressionEvaluator evaluator, ObjectPath path) {
|
||||
MongoDbPropertyValueProvider(DocumentAccessor accessor, SpELExpressionEvaluator evaluator, ObjectPath path) {
|
||||
|
||||
Assert.notNull(accessor, "DocumentAccessor must no be null!");
|
||||
Assert.notNull(evaluator, "SpELExpressionEvaluator must not be null!");
|
||||
Assert.notNull(path, "ObjectPath must not be null!");
|
||||
|
||||
this.source = accessor;
|
||||
this.accessor = accessor;
|
||||
this.evaluator = evaluator;
|
||||
this.path = path;
|
||||
}
|
||||
@@ -1323,7 +1381,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
public <T> T getPropertyValue(MongoPersistentProperty property) {
|
||||
|
||||
String expression = property.getSpelExpression();
|
||||
Object value = expression != null ? evaluator.evaluate(expression) : source.get(property);
|
||||
Object value = expression != null ? evaluator.evaluate(expression) : accessor.get(property);
|
||||
|
||||
if (value == null) {
|
||||
return null;
|
||||
@@ -1333,6 +1391,55 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link PropertyValueProvider} that is aware of {@link MongoPersistentProperty#isAssociation()} and that delegates
|
||||
* resolution to {@link DbRefResolver}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Christoph Strobl
|
||||
* @since 2.1
|
||||
*/
|
||||
class AssociationAwareMongoDbPropertyValueProvider extends MongoDbPropertyValueProvider {
|
||||
|
||||
/**
|
||||
* Creates a new {@link AssociationAwareMongoDbPropertyValueProvider} for the given source,
|
||||
* {@link SpELExpressionEvaluator} and {@link ObjectPath}.
|
||||
*
|
||||
* @param source must not be {@literal null}.
|
||||
* @param evaluator must not be {@literal null}.
|
||||
* @param path must not be {@literal null}.
|
||||
*/
|
||||
AssociationAwareMongoDbPropertyValueProvider(DocumentAccessor source, SpELExpressionEvaluator evaluator,
|
||||
ObjectPath path) {
|
||||
super(source, evaluator, path);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.convert.PropertyValueProvider#getPropertyValue(org.springframework.data.mapping.PersistentProperty)
|
||||
*/
|
||||
@Nullable
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T getPropertyValue(MongoPersistentProperty property) {
|
||||
|
||||
if (property.isDbReference() && property.getDBRef().lazy()) {
|
||||
|
||||
Object rawRefValue = accessor.get(property);
|
||||
if (rawRefValue == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
DbRefResolverCallback callback = new DefaultDbRefResolverCallback(accessor.getDocument(), path, evaluator,
|
||||
MappingMongoConverter.this);
|
||||
|
||||
DBRef dbref = rawRefValue instanceof DBRef ? (DBRef) rawRefValue : null;
|
||||
return (T) dbRefResolver.resolveDbRef(property, dbref, callback, dbRefProxyHandler);
|
||||
}
|
||||
|
||||
return super.getPropertyValue(property);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extension of {@link SpELExpressionParameterValueProvider} to recursively trigger value conversion on the raw
|
||||
* resolved SpEL value.
|
||||
@@ -1435,7 +1542,8 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
}
|
||||
|
||||
List<Document> referencedRawDocuments = dbrefs.size() == 1
|
||||
? Collections.singletonList(readRef(dbrefs.iterator().next())) : bulkReadRefs(dbrefs);
|
||||
? Collections.singletonList(readRef(dbrefs.iterator().next()))
|
||||
: bulkReadRefs(dbrefs);
|
||||
String collectionName = dbrefs.iterator().next().getCollectionName();
|
||||
|
||||
List<T> targeList = new ArrayList<>(dbrefs.size());
|
||||
@@ -1527,4 +1635,14 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
static class NestedDocument {
|
||||
|
||||
}
|
||||
|
||||
enum NoOpParameterValueProvider implements ParameterValueProvider<MongoPersistentProperty> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public <T> T getParameterValue(Parameter<T, MongoPersistentProperty> parameter) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,8 @@ package org.springframework.data.mongodb.core.convert;
|
||||
import org.bson.BsonValue;
|
||||
import org.bson.Document;
|
||||
import org.bson.conversions.Bson;
|
||||
import org.bson.types.ObjectId;
|
||||
import org.springframework.core.convert.ConversionException;
|
||||
import org.springframework.data.convert.EntityConverter;
|
||||
import org.springframework.data.convert.EntityReader;
|
||||
import org.springframework.data.convert.TypeMapper;
|
||||
@@ -83,7 +85,18 @@ public interface MongoConverter
|
||||
|
||||
if (sourceDocument.containsKey("$ref") && sourceDocument.containsKey("$id")) {
|
||||
|
||||
sourceDocument = dbRefResolver.fetch(new DBRef(sourceDocument.getString("$ref"), sourceDocument.get("$id")));
|
||||
Object id = sourceDocument.get("$id");
|
||||
String collection = sourceDocument.getString("$ref");
|
||||
|
||||
MongoPersistentEntity<?> entity = getMappingContext().getPersistentEntity(targetType);
|
||||
if (entity != null && entity.hasIdProperty()) {
|
||||
id = convertId(id, entity.getIdProperty().getFieldType());
|
||||
}
|
||||
|
||||
DBRef ref = sourceDocument.containsKey("$db") ? new DBRef(sourceDocument.getString("$db"), collection, id)
|
||||
: new DBRef(collection, id);
|
||||
|
||||
sourceDocument = dbRefResolver.fetch(ref);
|
||||
if (sourceDocument == null) {
|
||||
return null;
|
||||
}
|
||||
@@ -102,4 +115,38 @@ public interface MongoConverter
|
||||
}
|
||||
return getConversionService().convert(source, targetType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the given raw id value into either {@link ObjectId} or {@link String}.
|
||||
*
|
||||
* @param id
|
||||
* @param targetType
|
||||
* @return {@literal null} if source {@literal id} is already {@literal null}.
|
||||
* @since 2.2
|
||||
*/
|
||||
@Nullable
|
||||
default Object convertId(@Nullable Object id, Class<?> targetType) {
|
||||
|
||||
if (id == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (ClassUtils.isAssignable(ObjectId.class, targetType)) {
|
||||
|
||||
if (id instanceof String) {
|
||||
|
||||
if (ObjectId.isValid(id.toString())) {
|
||||
return new ObjectId(id.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
return getConversionService().canConvert(id.getClass(), targetType)
|
||||
? getConversionService().convert(id, targetType)
|
||||
: convertToMongoType(id, null);
|
||||
} catch (ConversionException o_O) {
|
||||
return convertToMongoType(id, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Currency;
|
||||
@@ -26,6 +27,7 @@ import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import org.bson.BsonTimestamp;
|
||||
import org.bson.Document;
|
||||
import org.bson.types.Binary;
|
||||
import org.bson.types.Code;
|
||||
@@ -86,6 +88,7 @@ abstract class MongoConverters {
|
||||
converters.add(LongToAtomicLongConverter.INSTANCE);
|
||||
converters.add(IntegerToAtomicIntegerConverter.INSTANCE);
|
||||
converters.add(BinaryToByteArrayConverter.INSTANCE);
|
||||
converters.add(BsonTimestampToInstantConverter.INSTANCE);
|
||||
|
||||
return converters;
|
||||
}
|
||||
@@ -465,4 +468,22 @@ abstract class MongoConverters {
|
||||
return source.getData();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link Converter} implementation converting {@link BsonTimestamp} into {@link Instant}.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.1.2
|
||||
*/
|
||||
@ReadingConverter
|
||||
enum BsonTimestampToInstantConverter implements Converter<BsonTimestamp, Instant> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Instant convert(BsonTimestamp source) {
|
||||
return Instant.ofEpochSecond(source.getTime(), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,13 +164,14 @@ public class MongoExampleMapper {
|
||||
if (exampleSpecAccessor.hasPropertySpecifier(mappedPropertyPath)) {
|
||||
|
||||
PropertyValueTransformer valueTransformer = exampleSpecAccessor.getValueTransformerForPath(mappedPropertyPath);
|
||||
value = valueTransformer.convert(value);
|
||||
if (value == null) {
|
||||
Optional converted = valueTransformer.apply(Optional.ofNullable(value));
|
||||
|
||||
if(!converted.isPresent()) {
|
||||
iter.remove();
|
||||
continue;
|
||||
}
|
||||
|
||||
entry.setValue(value);
|
||||
entry.setValue(converted.get());
|
||||
}
|
||||
|
||||
if (entry.getValue() instanceof String) {
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright 2018 the original author 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.convert;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.bson.Document;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
import com.mongodb.DBRef;
|
||||
|
||||
/**
|
||||
* No-Operation {@link org.springframework.data.mongodb.core.mapping.DBRef} resolver throwing
|
||||
* {@link UnsupportedOperationException} when attempting to resolve database references.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Christoph Strobl
|
||||
* @since 2.1
|
||||
*/
|
||||
public enum NoOpDbRefResolver implements DbRefResolver {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.convert.DbRefResolver#resolveDbRef(org.springframework.data.mongodb.core.mapping.MongoPersistentProperty, org.springframework.data.mongodb.core.convert.DbRefResolverCallback, org.springframework.data.mongodb.core.convert.DbRefProxyHandler)
|
||||
*/
|
||||
@Override
|
||||
@Nullable
|
||||
public Object resolveDbRef(MongoPersistentProperty property, @Nullable DBRef dbref, DbRefResolverCallback callback,
|
||||
DbRefProxyHandler proxyHandler) {
|
||||
|
||||
return handle();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.convert.DbRefResolver#fetch(com.mongodb.DBRef)
|
||||
*/
|
||||
@Override
|
||||
@Nullable
|
||||
public Document fetch(DBRef dbRef) {
|
||||
return handle();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.convert.DbRefResolver#bulkFetch(java.util.List)
|
||||
*/
|
||||
@Override
|
||||
public List<Document> bulkFetch(List<DBRef> dbRefs) {
|
||||
return handle();
|
||||
}
|
||||
|
||||
private <T> T handle() throws UnsupportedOperationException {
|
||||
throw new UnsupportedOperationException("DBRef resolution is not supported!");
|
||||
}
|
||||
}
|
||||
@@ -15,8 +15,6 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core.convert;
|
||||
|
||||
import lombok.Value;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -45,26 +43,33 @@ class ObjectPath {
|
||||
|
||||
static final ObjectPath ROOT = new ObjectPath();
|
||||
|
||||
private final ObjectPathItem[] items;
|
||||
private final @Nullable ObjectPath parent;
|
||||
private final @Nullable Object object;
|
||||
private final @Nullable Object idValue;
|
||||
private final String collection;
|
||||
|
||||
private ObjectPath() {
|
||||
this.items = new ObjectPathItem[0];
|
||||
|
||||
this.parent = null;
|
||||
this.object = null;
|
||||
this.idValue = null;
|
||||
this.collection = "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link ObjectPath} from the given parent {@link ObjectPath} by adding the provided
|
||||
* {@link ObjectPathItem} to it.
|
||||
* Creates a new {@link ObjectPath} from the given parent {@link ObjectPath} and adding the provided path values.
|
||||
*
|
||||
* @param parent must not be {@literal null}.
|
||||
* @param item
|
||||
* @param collection
|
||||
* @param idValue
|
||||
* @param collection
|
||||
*/
|
||||
private ObjectPath(ObjectPath parent, ObjectPath.ObjectPathItem item) {
|
||||
private ObjectPath(ObjectPath parent, Object object, @Nullable Object idValue, String collection) {
|
||||
|
||||
ObjectPathItem[] items = new ObjectPathItem[parent.items.length + 1];
|
||||
System.arraycopy(parent.items, 0, items, 0, parent.items.length);
|
||||
items[parent.items.length] = item;
|
||||
|
||||
this.items = items;
|
||||
this.parent = parent;
|
||||
this.object = object;
|
||||
this.idValue = idValue;
|
||||
this.collection = collection;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -80,8 +85,7 @@ class ObjectPath {
|
||||
Assert.notNull(object, "Object must not be null!");
|
||||
Assert.notNull(entity, "MongoPersistentEntity must not be null!");
|
||||
|
||||
ObjectPathItem item = new ObjectPathItem(object, id, entity.getCollection());
|
||||
return new ObjectPath(this, item);
|
||||
return new ObjectPath(this, object, id, entity.getCollection());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -100,15 +104,15 @@ class ObjectPath {
|
||||
Assert.notNull(id, "Id must not be null!");
|
||||
Assert.hasText(collection, "Collection name must not be null!");
|
||||
|
||||
for (ObjectPathItem item : items) {
|
||||
for (ObjectPath current = this; current != null; current = current.parent) {
|
||||
|
||||
Object object = item.getObject();
|
||||
Object object = current.getObject();
|
||||
|
||||
if (object == null || item.getIdValue() == null) {
|
||||
if (object == null || current.getIdValue() == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (collection.equals(item.getCollection()) && id.equals(item.getIdValue())) {
|
||||
if (collection.equals(current.getCollection()) && id.equals(current.getIdValue())) {
|
||||
return object;
|
||||
}
|
||||
}
|
||||
@@ -133,15 +137,15 @@ class ObjectPath {
|
||||
Assert.hasText(collection, "Collection name must not be null!");
|
||||
Assert.notNull(type, "Type must not be null!");
|
||||
|
||||
for (ObjectPathItem item : items) {
|
||||
for (ObjectPath current = this; current != null; current = current.parent) {
|
||||
|
||||
Object object = item.getObject();
|
||||
Object object = current.getObject();
|
||||
|
||||
if (object == null || item.getIdValue() == null) {
|
||||
if (object == null || current.getIdValue() == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (collection.equals(item.getCollection()) && id.equals(item.getIdValue())
|
||||
if (collection.equals(current.getCollection()) && id.equals(current.getIdValue())
|
||||
&& ClassUtils.isAssignable(type, object.getClass())) {
|
||||
return type.cast(object);
|
||||
}
|
||||
@@ -157,7 +161,21 @@ class ObjectPath {
|
||||
*/
|
||||
@Nullable
|
||||
Object getCurrentObject() {
|
||||
return items.length == 0 ? null : items[items.length - 1].getObject();
|
||||
return getObject();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Object getObject() {
|
||||
return object;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Object getIdValue() {
|
||||
return idValue;
|
||||
}
|
||||
|
||||
private String getCollection() {
|
||||
return collection;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -167,31 +185,16 @@ class ObjectPath {
|
||||
@Override
|
||||
public String toString() {
|
||||
|
||||
if (items.length == 0) {
|
||||
if (parent == null) {
|
||||
return "[empty]";
|
||||
}
|
||||
|
||||
List<String> strings = new ArrayList<>(items.length);
|
||||
List<String> strings = new ArrayList<>();
|
||||
|
||||
for (ObjectPathItem item : items) {
|
||||
strings.add(ObjectUtils.nullSafeToString(item.object));
|
||||
for (ObjectPath current = this; current != null; current = current.parent) {
|
||||
strings.add(ObjectUtils.nullSafeToString(current.getObject()));
|
||||
}
|
||||
|
||||
return StringUtils.collectionToDelimitedString(strings, " -> ");
|
||||
}
|
||||
|
||||
/**
|
||||
* An item in an {@link ObjectPath}.
|
||||
*
|
||||
* @author Thomas Darimont
|
||||
* @author Oliver Gierke
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@Value
|
||||
private static class ObjectPathItem {
|
||||
|
||||
Object object;
|
||||
@Nullable Object idValue;
|
||||
String collection;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ package org.springframework.data.mongodb.core.convert;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map.Entry;
|
||||
@@ -28,7 +29,6 @@ import org.bson.BsonValue;
|
||||
import org.bson.Document;
|
||||
import org.bson.conversions.Bson;
|
||||
import org.bson.types.ObjectId;
|
||||
import org.springframework.core.convert.ConversionException;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.data.domain.Example;
|
||||
@@ -50,6 +50,7 @@ import org.springframework.data.util.ClassTypeInformation;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.mongodb.BasicDBList;
|
||||
import com.mongodb.BasicDBObject;
|
||||
@@ -244,7 +245,16 @@ public class QueryMapper {
|
||||
*/
|
||||
protected Field createPropertyField(@Nullable MongoPersistentEntity<?> entity, String key,
|
||||
MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext) {
|
||||
return entity == null ? new Field(key) : new MetadataBackedField(key, entity, mappingContext);
|
||||
|
||||
if (entity == null) {
|
||||
return new Field(key);
|
||||
}
|
||||
|
||||
if (Field.ID_KEY.equals(key)) {
|
||||
return new MetadataBackedField(key, entity, mappingContext, entity.getIdProperty());
|
||||
}
|
||||
|
||||
return new MetadataBackedField(key, entity, mappingContext);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -290,7 +300,7 @@ public class QueryMapper {
|
||||
*/
|
||||
protected Document getMappedKeyword(Field property, Keyword keyword) {
|
||||
|
||||
boolean needsAssociationConversion = property.isAssociation() && !keyword.isExists();
|
||||
boolean needsAssociationConversion = property.isAssociation() && !keyword.isExists() && keyword.mayHoldDbRef();
|
||||
Object value = keyword.getValue();
|
||||
|
||||
Object convertedValue = needsAssociationConversion ? convertAssociation(value, property)
|
||||
@@ -312,7 +322,7 @@ public class QueryMapper {
|
||||
@SuppressWarnings("unchecked")
|
||||
protected Object getMappedValue(Field documentField, Object value) {
|
||||
|
||||
if (documentField.isIdField()) {
|
||||
if (documentField.isIdField() && !documentField.isAssociation()) {
|
||||
|
||||
if (isDBObject(value)) {
|
||||
DBObject valueDbo = (DBObject) value;
|
||||
@@ -322,11 +332,11 @@ public class QueryMapper {
|
||||
String inKey = valueDbo.containsField("$in") ? "$in" : "$nin";
|
||||
List<Object> ids = new ArrayList<Object>();
|
||||
for (Object id : (Iterable<?>) valueDbo.get(inKey)) {
|
||||
ids.add(convertId(id));
|
||||
ids.add(convertId(id, getIdTypeForField(documentField)));
|
||||
}
|
||||
resultDbo.put(inKey, ids);
|
||||
} else if (valueDbo.containsField("$ne")) {
|
||||
resultDbo.put("$ne", convertId(valueDbo.get("$ne")));
|
||||
resultDbo.put("$ne", convertId(valueDbo.get("$ne"), getIdTypeForField(documentField)));
|
||||
} else {
|
||||
return getMappedObject(resultDbo, Optional.empty());
|
||||
}
|
||||
@@ -341,18 +351,18 @@ public class QueryMapper {
|
||||
String inKey = valueDbo.containsKey("$in") ? "$in" : "$nin";
|
||||
List<Object> ids = new ArrayList<Object>();
|
||||
for (Object id : (Iterable<?>) valueDbo.get(inKey)) {
|
||||
ids.add(convertId(id));
|
||||
ids.add(convertId(id, getIdTypeForField(documentField)));
|
||||
}
|
||||
resultDbo.put(inKey, ids);
|
||||
} else if (valueDbo.containsKey("$ne")) {
|
||||
resultDbo.put("$ne", convertId(valueDbo.get("$ne")));
|
||||
resultDbo.put("$ne", convertId(valueDbo.get("$ne"), getIdTypeForField(documentField)));
|
||||
} else {
|
||||
return getMappedObject(resultDbo, Optional.empty());
|
||||
}
|
||||
return resultDbo;
|
||||
|
||||
} else {
|
||||
return convertId(value);
|
||||
return convertId(value, getIdTypeForField(documentField));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -367,6 +377,14 @@ public class QueryMapper {
|
||||
return convertSimpleOrDocument(value, documentField.getPropertyEntity());
|
||||
}
|
||||
|
||||
private boolean isIdField(Field documentField) {
|
||||
return documentField.getProperty() != null && documentField.getProperty().isIdProperty();
|
||||
}
|
||||
|
||||
private Class<?> getIdTypeForField(Field documentField) {
|
||||
return isIdField(documentField) ? documentField.getProperty().getFieldType() : ObjectId.class;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given {@link Field} represents an association reference that together with the given value
|
||||
* requires conversion to a {@link org.springframework.data.mongodb.core.mapping.DBRef} object. We check whether the
|
||||
@@ -468,7 +486,14 @@ public class QueryMapper {
|
||||
if (source instanceof DBRef) {
|
||||
|
||||
DBRef ref = (DBRef) source;
|
||||
return new DBRef(ref.getCollectionName(), convertId(ref.getId()));
|
||||
Object id = convertId(ref.getId(),
|
||||
property != null && property.isIdProperty() ? property.getFieldType() : ObjectId.class);
|
||||
|
||||
if (StringUtils.hasText(ref.getDatabaseName())) {
|
||||
return new DBRef(ref.getDatabaseName(), ref.getCollectionName(), id);
|
||||
} else {
|
||||
return new DBRef(ref.getCollectionName(), id);
|
||||
}
|
||||
}
|
||||
|
||||
if (source instanceof Iterable) {
|
||||
@@ -549,24 +574,24 @@ public class QueryMapper {
|
||||
*
|
||||
* @param id
|
||||
* @return
|
||||
* @since 2.2
|
||||
*/
|
||||
@Nullable
|
||||
public Object convertId(@Nullable Object id) {
|
||||
return convertId(id, ObjectId.class);
|
||||
}
|
||||
|
||||
if (id == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (id instanceof String) {
|
||||
return ObjectId.isValid(id.toString()) ? conversionService.convert(id, ObjectId.class) : id;
|
||||
}
|
||||
|
||||
try {
|
||||
return conversionService.canConvert(id.getClass(), ObjectId.class) ? conversionService.convert(id, ObjectId.class)
|
||||
: delegateConvertToMongoType(id, null);
|
||||
} catch (ConversionException o_O) {
|
||||
return delegateConvertToMongoType(id, null);
|
||||
}
|
||||
/**
|
||||
* Converts the given raw id value into either {@link ObjectId} or {@link Class targetType}.
|
||||
*
|
||||
* @param id can be {@literal null}.
|
||||
* @param targetType
|
||||
* @return the converted {@literal id} or {@literal null} if the source was already {@literal null}.
|
||||
* @since 2.2
|
||||
*/
|
||||
@Nullable
|
||||
public Object convertId(@Nullable Object id, Class<?> targetType) {
|
||||
return converter.convertId(id, targetType);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -610,10 +635,12 @@ public class QueryMapper {
|
||||
static class Keyword {
|
||||
|
||||
private static final String N_OR_PATTERN = "\\$.*or";
|
||||
private static final Set<String> NON_DBREF_CONVERTING_KEYWORDS = new HashSet<>(Arrays.asList("$", "$size", "$slice", "$gt", "$lt"));
|
||||
|
||||
private final String key;
|
||||
private final Object value;
|
||||
|
||||
|
||||
public Keyword(Bson source, String key) {
|
||||
this.key = key;
|
||||
this.value = BsonUtils.get(source, key);
|
||||
@@ -674,6 +701,15 @@ public class QueryMapper {
|
||||
return (T) value;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return {@literal true} if key may hold a DbRef.
|
||||
* @since 2.1.4
|
||||
*/
|
||||
public boolean mayHoldDbRef() {
|
||||
return !NON_DBREF_CONVERTING_KEYWORDS.contains(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the current keyword indicates a {@literal $jsonSchema} object.
|
||||
*
|
||||
@@ -850,15 +886,18 @@ public class QueryMapper {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.convert.QueryMapper.Field#isIdKey()
|
||||
* @see org.springframework.data.mongodb.core.convert.QueryMapper.Field#isIdField()
|
||||
*/
|
||||
@Override
|
||||
public boolean isIdField() {
|
||||
|
||||
MongoPersistentProperty idProperty = entity.getIdProperty();
|
||||
MongoPersistentProperty idProperty = (property != null && property.isIdProperty()) ? property
|
||||
: entity.getIdProperty();
|
||||
|
||||
if (idProperty != null) {
|
||||
return idProperty.getName().equals(name) || idProperty.getFieldName().equals(name);
|
||||
|
||||
return name.equals(idProperty.getName()) || name.equals(idProperty.getFieldName())
|
||||
|| name.endsWith("." + idProperty.getName()) || name.endsWith("." + idProperty.getFieldName());
|
||||
}
|
||||
|
||||
return DEFAULT_ID_NAMES.contains(name);
|
||||
|
||||
@@ -70,7 +70,9 @@ public @interface CompoundIndex {
|
||||
/**
|
||||
* @return
|
||||
* @see <a href="https://docs.mongodb.org/manual/core/index-creation/#index-creation-duplicate-dropping">https://docs.mongodb.org/manual/core/index-creation/#index-creation-duplicate-dropping</a>
|
||||
* @deprecated since 2.1. No longer supported by MongoDB as of server version 3.0.
|
||||
*/
|
||||
@Deprecated
|
||||
boolean dropDups() default false;
|
||||
|
||||
/**
|
||||
|
||||
@@ -36,6 +36,10 @@ import org.springframework.util.StringUtils;
|
||||
@SuppressWarnings("deprecation")
|
||||
public class Index implements IndexDefinition {
|
||||
|
||||
/**
|
||||
* @deprecated since 2.1. No longer supported by MongoDB as of server version 3.0.
|
||||
*/
|
||||
@Deprecated
|
||||
public enum Duplicates {
|
||||
RETAIN
|
||||
}
|
||||
@@ -43,7 +47,6 @@ public class Index implements IndexDefinition {
|
||||
private final Map<String, Direction> fieldSpec = new LinkedHashMap<String, Direction>();
|
||||
private @Nullable String name;
|
||||
private boolean unique = false;
|
||||
private boolean dropDuplicates = false;
|
||||
private boolean sparse = false;
|
||||
private boolean background = false;
|
||||
private long expire = -1;
|
||||
@@ -183,9 +186,6 @@ public class Index implements IndexDefinition {
|
||||
if (unique) {
|
||||
document.put("unique", true);
|
||||
}
|
||||
if (dropDuplicates) {
|
||||
document.put("dropDups", true);
|
||||
}
|
||||
if (sparse) {
|
||||
document.put("sparse", true);
|
||||
}
|
||||
|
||||
@@ -13,21 +13,22 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.data.mongodb.core.index;
|
||||
|
||||
/**
|
||||
* TODO: Revisit for a better pattern.
|
||||
* Provider interface to obtain {@link IndexOperations} by MongoDB collection name.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Jens Schauder
|
||||
* @since 2.0
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface IndexOperationsProvider {
|
||||
|
||||
/**
|
||||
* Returns the operations that can be performed on indexes
|
||||
*
|
||||
* @param collectionName name of the MongoDB collection, must not be {@literal null}.
|
||||
* @return index operations on the named collection
|
||||
*/
|
||||
IndexOperations indexOps(String collectionName);
|
||||
|
||||
@@ -56,7 +56,9 @@ public @interface Indexed {
|
||||
/**
|
||||
* @return
|
||||
* @see <a href="https://docs.mongodb.org/manual/core/index-creation/#index-creation-duplicate-dropping">https://docs.mongodb.org/manual/core/index-creation/#index-creation-duplicate-dropping</a>
|
||||
* @deprecated since 2.1. No longer supported by MongoDB as of server version 3.0.
|
||||
*/
|
||||
@Deprecated
|
||||
boolean dropDups() default false;
|
||||
|
||||
/**
|
||||
|
||||
@@ -18,6 +18,7 @@ package org.springframework.data.mongodb.core.index;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.data.mapping.context.MappingContextEvent;
|
||||
import org.springframework.data.mongodb.core.MongoTemplate;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
|
||||
@@ -38,7 +39,20 @@ import org.springframework.util.Assert;
|
||||
*/
|
||||
public class MongoMappingEventPublisher implements ApplicationEventPublisher {
|
||||
|
||||
private final MongoPersistentEntityIndexCreator indexCreator;
|
||||
private final ApplicationListener<MappingContextEvent<?, ?>> indexCreator;
|
||||
|
||||
/**
|
||||
* Creates a new {@link MongoMappingEventPublisher} for the given {@link ApplicationListener}.
|
||||
*
|
||||
* @param indexCreator must not be {@literal null}.
|
||||
* @since 2.1
|
||||
*/
|
||||
public MongoMappingEventPublisher(ApplicationListener<MappingContextEvent<?, ?>> indexCreator) {
|
||||
|
||||
Assert.notNull(indexCreator, "ApplicationListener must not be null!");
|
||||
|
||||
this.indexCreator = indexCreator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link MongoMappingEventPublisher} for the given {@link MongoPersistentEntityIndexCreator}.
|
||||
@@ -48,6 +62,7 @@ public class MongoMappingEventPublisher implements ApplicationEventPublisher {
|
||||
public MongoMappingEventPublisher(MongoPersistentEntityIndexCreator indexCreator) {
|
||||
|
||||
Assert.notNull(indexCreator, "MongoPersistentEntityIndexCreator must not be null!");
|
||||
|
||||
this.indexCreator = indexCreator;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2018 the original author 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;
|
||||
|
||||
/**
|
||||
* Provider interface to obtain {@link ReactiveIndexOperations} by MongoDB collection name.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 2.1
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface ReactiveIndexOperationsProvider {
|
||||
|
||||
/**
|
||||
* Returns the operations that can be performed on indexes.
|
||||
*
|
||||
* @param collectionName name of the MongoDB collection, must not be {@literal null}.
|
||||
* @return index operations on the named collection
|
||||
*/
|
||||
ReactiveIndexOperations indexOps(String collectionName);
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
/*
|
||||
* Copyright 2018 the original author 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 reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.dao.DataIntegrityViolationException;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.mongodb.UncategorizedMongoDbException;
|
||||
import org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexResolver.IndexDefinitionHolder;
|
||||
import org.springframework.data.mongodb.core.mapping.Document;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
|
||||
import org.springframework.data.mongodb.util.MongoDbErrorCodes;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
import com.mongodb.MongoException;
|
||||
|
||||
/**
|
||||
* Component that inspects {@link MongoPersistentEntity} instances contained in the given {@link MongoMappingContext}
|
||||
* for indexing metadata and ensures the indexes to be available using reactive infrastructure.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 2.1
|
||||
*/
|
||||
public class ReactiveMongoPersistentEntityIndexCreator {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(ReactiveMongoPersistentEntityIndexCreator.class);
|
||||
|
||||
private final Map<Class<?>, Boolean> classesSeen = new ConcurrentHashMap<Class<?>, Boolean>();
|
||||
private final MongoMappingContext mappingContext;
|
||||
private final ReactiveIndexOperationsProvider operationsProvider;
|
||||
private final IndexResolver indexResolver;
|
||||
|
||||
/**
|
||||
* Creates a new {@link ReactiveMongoPersistentEntityIndexCreator} for the given {@link MongoMappingContext},
|
||||
* {@link ReactiveIndexOperationsProvider}.
|
||||
*
|
||||
* @param mappingContext must not be {@literal null}.
|
||||
* @param operationsProvider must not be {@literal null}.
|
||||
*/
|
||||
public ReactiveMongoPersistentEntityIndexCreator(MongoMappingContext mappingContext,
|
||||
ReactiveIndexOperationsProvider operationsProvider) {
|
||||
this(mappingContext, operationsProvider, new MongoPersistentEntityIndexResolver(mappingContext));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link ReactiveMongoPersistentEntityIndexCreator} for the given {@link MongoMappingContext},
|
||||
* {@link ReactiveIndexOperationsProvider}, and {@link IndexResolver}.
|
||||
*
|
||||
* @param mappingContext must not be {@literal null}.
|
||||
* @param operationsProvider must not be {@literal null}.
|
||||
* @param indexResolver must not be {@literal null}.
|
||||
*/
|
||||
public ReactiveMongoPersistentEntityIndexCreator(MongoMappingContext mappingContext,
|
||||
ReactiveIndexOperationsProvider operationsProvider, IndexResolver indexResolver) {
|
||||
|
||||
Assert.notNull(mappingContext, "MongoMappingContext must not be null!");
|
||||
Assert.notNull(operationsProvider, "ReactiveIndexOperations must not be null!");
|
||||
Assert.notNull(indexResolver, "IndexResolver must not be null!");
|
||||
|
||||
this.mappingContext = mappingContext;
|
||||
this.operationsProvider = operationsProvider;
|
||||
this.indexResolver = indexResolver;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the current index creator was registered for the given {@link MappingContext}.
|
||||
*
|
||||
* @param context
|
||||
* @return
|
||||
*/
|
||||
public boolean isIndexCreatorFor(MappingContext<?, ?> context) {
|
||||
return this.mappingContext.equals(context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inspect entities for index creation.
|
||||
*
|
||||
* @return a {@link Mono} that completes without value after indexes were created.
|
||||
*/
|
||||
public Mono<Void> checkForIndexes(MongoPersistentEntity<?> entity) {
|
||||
|
||||
Class<?> type = entity.getType();
|
||||
|
||||
if (!classesSeen.containsKey(type)) {
|
||||
|
||||
if (this.classesSeen.put(type, Boolean.TRUE) == null) {
|
||||
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("Analyzing class " + type + " for index information.");
|
||||
}
|
||||
|
||||
return checkForAndCreateIndexes(entity);
|
||||
}
|
||||
}
|
||||
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
private Mono<Void> checkForAndCreateIndexes(MongoPersistentEntity<?> entity) {
|
||||
|
||||
List<Mono<?>> publishers = new ArrayList<>();
|
||||
|
||||
if (entity.isAnnotationPresent(Document.class)) {
|
||||
for (IndexDefinitionHolder indexToCreate : indexResolver.resolveIndexFor(entity.getTypeInformation())) {
|
||||
publishers.add(createIndex(indexToCreate));
|
||||
}
|
||||
}
|
||||
|
||||
return publishers.isEmpty() ? Mono.empty() : Flux.merge(publishers).then();
|
||||
}
|
||||
|
||||
Mono<String> createIndex(IndexDefinitionHolder indexDefinition) {
|
||||
|
||||
return operationsProvider.indexOps(indexDefinition.getCollection()).ensureIndex(indexDefinition) //
|
||||
.onErrorResume(ReactiveMongoPersistentEntityIndexCreator::isDataIntegrityViolation,
|
||||
e -> translateException(e, indexDefinition));
|
||||
|
||||
}
|
||||
|
||||
private Mono<? extends String> translateException(Throwable e, IndexDefinitionHolder indexDefinition) {
|
||||
|
||||
Mono<IndexInfo> existingIndex = fetchIndexInformation(indexDefinition);
|
||||
|
||||
Mono<String> defaultError = Mono.error(new DataIntegrityViolationException(
|
||||
String.format("Cannot create index for '%s' in collection '%s' with keys '%s' and options '%s'.",
|
||||
indexDefinition.getPath(), indexDefinition.getCollection(), indexDefinition.getIndexKeys(),
|
||||
indexDefinition.getIndexOptions()),
|
||||
e.getCause()));
|
||||
|
||||
return existingIndex.flatMap(it -> {
|
||||
return Mono.<String> error(new DataIntegrityViolationException(
|
||||
String.format("Index already defined as '%s'.", indexDefinition.getPath()), e.getCause()));
|
||||
}).switchIfEmpty(defaultError);
|
||||
}
|
||||
|
||||
private Mono<IndexInfo> fetchIndexInformation(IndexDefinitionHolder indexDefinition) {
|
||||
|
||||
Object indexNameToLookUp = indexDefinition.getIndexOptions().get("name");
|
||||
|
||||
Flux<IndexInfo> existingIndexes = operationsProvider.indexOps(indexDefinition.getCollection()).getIndexInfo();
|
||||
|
||||
return existingIndexes //
|
||||
.filter(indexInfo -> ObjectUtils.nullSafeEquals(indexNameToLookUp, indexInfo.getName())) //
|
||||
.next() //
|
||||
.doOnError(e -> {
|
||||
LOGGER.debug(
|
||||
String.format("Failed to load index information for collection '%s'.", indexDefinition.getCollection()),
|
||||
e);
|
||||
});
|
||||
}
|
||||
|
||||
private static boolean isDataIntegrityViolation(Throwable t) {
|
||||
|
||||
if (t instanceof UncategorizedMongoDbException) {
|
||||
|
||||
return t.getCause() instanceof MongoException
|
||||
&& MongoDbErrorCodes.isDataIntegrityViolationCode(((MongoException) t.getCause()).getCode());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -21,11 +21,6 @@ import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.context.expression.BeanFactoryAccessor;
|
||||
import org.springframework.context.expression.BeanFactoryResolver;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.mapping.Association;
|
||||
import org.springframework.data.mapping.AssociationHandler;
|
||||
@@ -38,7 +33,6 @@ import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.ParserContext;
|
||||
import org.springframework.expression.common.LiteralExpression;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
@@ -55,7 +49,7 @@ import org.springframework.util.StringUtils;
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class BasicMongoPersistentEntity<T> extends BasicPersistentEntity<T, MongoPersistentProperty>
|
||||
implements MongoPersistentEntity<T>, ApplicationContextAware {
|
||||
implements MongoPersistentEntity<T> {
|
||||
|
||||
private static final String AMBIGUOUS_FIELD_MAPPING = "Ambiguous field mapping detected! Both %s and %s map to the same field name %s! Disambiguate using @Field annotation!";
|
||||
private static final SpelExpressionParser PARSER = new SpelExpressionParser();
|
||||
@@ -63,7 +57,6 @@ public class BasicMongoPersistentEntity<T> extends BasicPersistentEntity<T, Mong
|
||||
private final String collection;
|
||||
private final String language;
|
||||
|
||||
private final StandardEvaluationContext context;
|
||||
private final @Nullable Expression expression;
|
||||
|
||||
/**
|
||||
@@ -79,8 +72,6 @@ public class BasicMongoPersistentEntity<T> extends BasicPersistentEntity<T, Mong
|
||||
Class<?> rawType = typeInformation.getType();
|
||||
String fallback = MongoCollectionUtils.getPreferredCollectionName(rawType);
|
||||
|
||||
this.context = new StandardEvaluationContext();
|
||||
|
||||
if (this.isAnnotationPresent(Document.class)) {
|
||||
Document document = this.getRequiredAnnotation(Document.class);
|
||||
|
||||
@@ -95,23 +86,15 @@ public class BasicMongoPersistentEntity<T> extends BasicPersistentEntity<T, Mong
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext)
|
||||
*/
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
|
||||
context.addPropertyAccessor(new BeanFactoryAccessor());
|
||||
context.setBeanResolver(new BeanFactoryResolver(applicationContext));
|
||||
context.setRootObject(applicationContext);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.mapping.MongoPersistentEntity#getCollection()
|
||||
*/
|
||||
public String getCollection() {
|
||||
return expression == null ? collection : expression.getValue(context, String.class);
|
||||
|
||||
return expression == null //
|
||||
? collection //
|
||||
: expression.getValue(getEvaluationContext(null), String.class);
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -67,8 +67,7 @@ public class BasicMongoPersistentProperty extends AnnotationBasedPersistentPrope
|
||||
/**
|
||||
* Creates a new {@link BasicMongoPersistentProperty}.
|
||||
*
|
||||
* @param field
|
||||
* @param propertyDescriptor
|
||||
* @param property
|
||||
* @param owner
|
||||
* @param simpleTypeHolder
|
||||
* @param fieldNamingStrategy
|
||||
@@ -144,6 +143,32 @@ public class BasicMongoPersistentProperty extends AnnotationBasedPersistentPrope
|
||||
return fieldName;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.mapping.MongoPersistentProperty#getFieldType()
|
||||
*/
|
||||
@Override
|
||||
public Class<?> getFieldType() {
|
||||
|
||||
if (!isIdProperty()) {
|
||||
return getType();
|
||||
}
|
||||
|
||||
MongoId idAnnotation = findAnnotation(MongoId.class);
|
||||
|
||||
if (idAnnotation == null) {
|
||||
return FieldType.OBJECT_ID.getJavaClass();
|
||||
}
|
||||
|
||||
FieldType fieldType = idAnnotation.targetType();
|
||||
|
||||
if (fieldType == FieldType.IMPLICIT) {
|
||||
return getType();
|
||||
}
|
||||
|
||||
return fieldType.getJavaClass();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if {@link org.springframework.data.mongodb.core.mapping.Field} having non blank
|
||||
* {@link org.springframework.data.mongodb.core.mapping.Field#value()} present.
|
||||
|
||||
@@ -24,20 +24,23 @@ import org.springframework.lang.Nullable;
|
||||
* {@link MongoPersistentProperty} caching access to {@link #isIdProperty()} and {@link #getFieldName()}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class CachingMongoPersistentProperty extends BasicMongoPersistentProperty {
|
||||
|
||||
private @Nullable Boolean isIdProperty;
|
||||
private @Nullable Boolean isAssociation;
|
||||
private @Nullable boolean dbRefResolved;
|
||||
private @Nullable DBRef dbref;
|
||||
private @Nullable String fieldName;
|
||||
private @Nullable Class<?> fieldType;
|
||||
private @Nullable Boolean usePropertyAccess;
|
||||
private @Nullable Boolean isTransient;
|
||||
|
||||
/**
|
||||
* Creates a new {@link CachingMongoPersistentProperty}.
|
||||
*
|
||||
* @param field
|
||||
* @param propertyDescriptor
|
||||
* @param property
|
||||
* @param owner
|
||||
* @param simpleTypeHolder
|
||||
* @param fieldNamingStrategy
|
||||
@@ -87,6 +90,20 @@ public class CachingMongoPersistentProperty extends BasicMongoPersistentProperty
|
||||
return this.fieldName;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.mapping.BasicMongoPersistentProperty#getFieldType()
|
||||
*/
|
||||
@Override
|
||||
public Class<?> getFieldType() {
|
||||
|
||||
if (this.fieldType == null) {
|
||||
this.fieldType = super.getFieldType();
|
||||
}
|
||||
|
||||
return this.fieldType;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mapping.model.AnnotationBasedPersistentProperty#usePropertyAccess()
|
||||
@@ -114,4 +131,28 @@ public class CachingMongoPersistentProperty extends BasicMongoPersistentProperty
|
||||
|
||||
return this.isTransient;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.mapping.BasicMongoPersistentProperty#isDbReference()
|
||||
*/
|
||||
@Override
|
||||
public boolean isDbReference() {
|
||||
return getDBRef() != null;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.mapping.BasicMongoPersistentProperty#getDBRef()
|
||||
*/
|
||||
@Override
|
||||
public DBRef getDBRef() {
|
||||
|
||||
if (!dbRefResolved) {
|
||||
this.dbref = super.getDBRef();
|
||||
this.dbRefResolved = true;
|
||||
}
|
||||
|
||||
return this.dbref;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright 2018 the original author 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.mapping;
|
||||
|
||||
import org.bson.types.ObjectId;
|
||||
|
||||
/**
|
||||
* Enumeration of field value types that can be used to represent a {@link org.bson.Document} field value. This
|
||||
* enumeration contains a subset of {@link org.bson.BsonType} that is supported by the mapping and conversion
|
||||
* components.
|
||||
* <p/>
|
||||
* Bson types are identified by a {@code byte} {@link #getBsonType() value}. This enumeration typically returns the
|
||||
* according bson type value except for {@link #IMPLICIT} which is a marker to derive the field type from a property.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 2.2
|
||||
* @see org.bson.BsonType
|
||||
*/
|
||||
public enum FieldType {
|
||||
|
||||
/**
|
||||
* Implicit type that is derived from the property value.
|
||||
*/
|
||||
IMPLICIT(-1, Object.class), STRING(2, String.class), OBJECT_ID(7, ObjectId.class);
|
||||
|
||||
private final int bsonType;
|
||||
private final Class<?> javaClass;
|
||||
|
||||
FieldType(int bsonType, Class<?> javaClass) {
|
||||
|
||||
this.bsonType = bsonType;
|
||||
this.javaClass = javaClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the BSON type identifier. Can be {@code -1} if {@link FieldType} maps to a synthetic Bson type.
|
||||
*
|
||||
* @return the BSON type identifier. Can be {@code -1} if {@link FieldType} maps to a synthetic Bson type.
|
||||
*/
|
||||
public int getBsonType() {
|
||||
return bsonType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Java class used to represent the type.
|
||||
*
|
||||
* @return the Java class used to represent the type.
|
||||
*/
|
||||
public Class<?> getJavaClass() {
|
||||
return javaClass;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright 2018 the original author 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.mapping;
|
||||
|
||||
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;
|
||||
import org.springframework.data.annotation.Id;
|
||||
|
||||
/**
|
||||
* {@link MongoId} represents a MongoDB specific {@link Id} annotation that allows customizing {@literal id} conversion.
|
||||
* Id properties use {@link org.springframework.data.mongodb.core.mapping.FieldType#IMPLICIT} as the default
|
||||
* {@literal id's} target type. This means that the actual property value is used. No conversion attempts to any other
|
||||
* type are made. <br />
|
||||
* In contrast to {@link Id @Id}, {@link String} {@literal id's} are stored as the such even when the actual value
|
||||
* represents a valid {@link org.bson.types.ObjectId#isValid(String) ObjectId hex String}. To trigger {@link String} to
|
||||
* {@link org.bson.types.ObjectId} conversion use {@link MongoId#targetType() @MongoId(FieldType.OBJECT_ID)}.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
* @since 2.2
|
||||
*/
|
||||
@Id
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.ANNOTATION_TYPE })
|
||||
public @interface MongoId {
|
||||
|
||||
/**
|
||||
* @return the preferred id type.
|
||||
* @see #targetType()
|
||||
*/
|
||||
@AliasFor("targetType")
|
||||
FieldType value() default FieldType.IMPLICIT;
|
||||
|
||||
/**
|
||||
* Get the preferred {@literal _id} type to be used. Defaults to {@link FieldType#IMPLICIT} which uses the property's
|
||||
* type. If defined different, the given value is attempted to be converted into the desired target type via
|
||||
* {@link org.springframework.data.mongodb.core.convert.MongoConverter#convertId(Object, Class)}.
|
||||
*
|
||||
* @return the preferred {@literal id} type. {@link FieldType#IMPLICIT} by default.
|
||||
*/
|
||||
@AliasFor("value")
|
||||
FieldType targetType() default FieldType.IMPLICIT;
|
||||
|
||||
}
|
||||
@@ -87,14 +87,7 @@ public class MongoMappingContext extends AbstractMappingContext<BasicMongoPersis
|
||||
*/
|
||||
@Override
|
||||
protected <T> BasicMongoPersistentEntity<T> createPersistentEntity(TypeInformation<T> typeInformation) {
|
||||
|
||||
BasicMongoPersistentEntity<T> entity = new BasicMongoPersistentEntity<T>(typeInformation);
|
||||
|
||||
if (context != null) {
|
||||
entity.setApplicationContext(context);
|
||||
}
|
||||
|
||||
return entity;
|
||||
return new BasicMongoPersistentEntity<T>(typeInformation);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -103,6 +96,9 @@ public class MongoMappingContext extends AbstractMappingContext<BasicMongoPersis
|
||||
*/
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
|
||||
super.setApplicationContext(applicationContext);
|
||||
|
||||
this.context = applicationContext;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,15 @@ public interface MongoPersistentProperty extends PersistentProperty<MongoPersist
|
||||
*/
|
||||
String getFieldName();
|
||||
|
||||
/**
|
||||
* Returns the {@link Class Java FieldType} of the field a property is persisted to.
|
||||
*
|
||||
* @return
|
||||
* @since 2.2
|
||||
* @see FieldType
|
||||
*/
|
||||
Class<?> getFieldType();
|
||||
|
||||
/**
|
||||
* Returns the order of the field if defined. Will return -1 if undefined.
|
||||
*
|
||||
|
||||
@@ -22,13 +22,13 @@ import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.bson.BsonObjectId;
|
||||
import org.bson.*;
|
||||
import org.bson.types.Binary;
|
||||
import org.bson.types.CodeWScope;
|
||||
import org.bson.types.CodeWithScope;
|
||||
import org.bson.types.Decimal128;
|
||||
import org.bson.types.ObjectId;
|
||||
import org.springframework.data.mapping.model.SimpleTypeHolder;
|
||||
import org.springframework.data.mongodb.util.MongoClientVersion;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
import com.mongodb.DBRef;
|
||||
|
||||
@@ -54,15 +54,29 @@ public abstract class MongoSimpleTypes {
|
||||
simpleTypes.add(ObjectId.class);
|
||||
simpleTypes.add(BsonObjectId.class);
|
||||
simpleTypes.add(CodeWScope.class);
|
||||
simpleTypes.add(CodeWithScope.class);
|
||||
simpleTypes.add(org.bson.Document.class);
|
||||
simpleTypes.add(Pattern.class);
|
||||
simpleTypes.add(Binary.class);
|
||||
simpleTypes.add(UUID.class);
|
||||
simpleTypes.add(Decimal128.class);
|
||||
|
||||
if (MongoClientVersion.isMongo34Driver()) {
|
||||
simpleTypes
|
||||
.add(ClassUtils.resolveClassName("org.bson.types.Decimal128", MongoSimpleTypes.class.getClassLoader()));
|
||||
}
|
||||
simpleTypes.add(BsonBinary.class);
|
||||
simpleTypes.add(BsonBoolean.class);
|
||||
simpleTypes.add(BsonDateTime.class);
|
||||
simpleTypes.add(BsonDbPointer.class);
|
||||
simpleTypes.add(BsonDecimal128.class);
|
||||
simpleTypes.add(BsonDocument.class);
|
||||
simpleTypes.add(BsonDocument.class);
|
||||
simpleTypes.add(BsonDouble.class);
|
||||
simpleTypes.add(BsonInt32.class);
|
||||
simpleTypes.add(BsonInt64.class);
|
||||
simpleTypes.add(BsonJavaScript.class);
|
||||
simpleTypes.add(BsonJavaScriptWithScope.class);
|
||||
simpleTypes.add(BsonObjectId.class);
|
||||
simpleTypes.add(BsonRegularExpression.class);
|
||||
simpleTypes.add(BsonString.class);
|
||||
simpleTypes.add(BsonTimestamp.class);
|
||||
|
||||
MONGO_SIMPLE_TYPES = Collections.unmodifiableSet(simpleTypes);
|
||||
}
|
||||
|
||||
@@ -15,8 +15,6 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core.mapping.event;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.beans.factory.ObjectFactory;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.core.Ordered;
|
||||
@@ -53,9 +51,7 @@ public class AuditingEventListener implements ApplicationListener<BeforeConvertE
|
||||
*/
|
||||
@Override
|
||||
public void onApplicationEvent(BeforeConvertEvent<Object> event) {
|
||||
|
||||
Optional.ofNullable(event.getSource())//
|
||||
.ifPresent(it -> auditingHandlerFactory.getObject().markAudited(it));
|
||||
event.mapSource(it -> auditingHandlerFactory.getObject().markAudited(it));
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user