Compare commits
100 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
77819a5d37 | ||
|
|
a7684e808b | ||
|
|
4114bffead | ||
|
|
06662b6889 | ||
|
|
a6eb8d69d4 | ||
|
|
f94afab567 | ||
|
|
f0f6185808 | ||
|
|
7f69e43856 | ||
|
|
1348127702 | ||
|
|
90078aa8c5 | ||
|
|
18c5ecd36f | ||
|
|
25dc56a840 | ||
|
|
919a07a7c5 | ||
|
|
239304160b | ||
|
|
a824bad2fd | ||
|
|
525eeccb9a | ||
|
|
bc257aa260 | ||
|
|
10737cd819 | ||
|
|
982cf84f70 | ||
|
|
8f1beff541 | ||
|
|
2658af1ac5 | ||
|
|
f59cd7e489 | ||
|
|
88805d0743 | ||
|
|
aab86d23c9 | ||
|
|
1d7cc2eb97 | ||
|
|
4d8f5d63c7 | ||
|
|
f7d65cf8d4 | ||
|
|
309148dd64 | ||
|
|
ee8436880b | ||
|
|
1ad975de0a | ||
|
|
f6314a321a | ||
|
|
91717e5566 | ||
|
|
b9f7f23b8f | ||
|
|
caab310cf8 | ||
|
|
6b116df70b | ||
|
|
12d86f30a9 | ||
|
|
297ff1587a | ||
|
|
c2a91aa7b4 | ||
|
|
5b7b69026b | ||
|
|
22cb17486c | ||
|
|
448df55ff7 | ||
|
|
a71e4bb313 | ||
|
|
7c3e80a1bc | ||
|
|
5f7e252f87 | ||
|
|
b44c6cb59f | ||
|
|
14467cb1f6 | ||
|
|
50715cd7c8 | ||
|
|
4bba7b4406 | ||
|
|
0d05e4b35d | ||
|
|
ab7069187e | ||
|
|
0ec840e34a | ||
|
|
5a783ba21f | ||
|
|
0edaf8799b | ||
|
|
9efc0970b7 | ||
|
|
ce237436d3 | ||
|
|
99824a498e | ||
|
|
2bfeb9cf20 | ||
|
|
bb889c7672 | ||
|
|
c2e43bd938 | ||
|
|
c74f91392a |
29
.travis.yml
29
.travis.yml
@@ -3,26 +3,26 @@ language: java
|
||||
jdk:
|
||||
- oraclejdk8
|
||||
|
||||
before_script:
|
||||
- mongod --version
|
||||
|
||||
services:
|
||||
- mongodb
|
||||
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
|
||||
- |-
|
||||
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=3.7.9
|
||||
|
||||
# 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
|
||||
|
||||
sudo: false
|
||||
@@ -30,9 +30,6 @@ 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:
|
||||
|
||||
42
pom.xml
42
pom.xml
@@ -5,7 +5,7 @@
|
||||
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-mongodb-parent</artifactId>
|
||||
<version>2.1.0.M1</version>
|
||||
<version>2.1.0.M3</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.M1</version>
|
||||
<version>2.1.0.M3</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.M1</springdata.commons>
|
||||
<mongo>3.6.2</mongo>
|
||||
<mongo.reactivestreams>1.7.0</mongo.reactivestreams>
|
||||
<springdata.commons>2.1.0.M3</springdata.commons>
|
||||
<mongo>3.8.0-beta2</mongo>
|
||||
<mongo.reactivestreams>1.9.0-beta1</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>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-mongodb-parent</artifactId>
|
||||
<version>2.1.0.M1</version>
|
||||
<version>2.1.0.M3</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.M1</version>
|
||||
<version>2.1.0.M3</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
<jpa>2.1.1</jpa>
|
||||
<hibernate>5.2.1.Final</hibernate>
|
||||
<java-module-name>spring.data.mongodb.cross.store</java-module-name>
|
||||
<project.root>${basedir}/..</project.root>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
@@ -49,7 +50,7 @@
|
||||
<dependency>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-mongodb</artifactId>
|
||||
<version>2.1.0.M1</version>
|
||||
<version>2.1.0.M3</version>
|
||||
</dependency>
|
||||
|
||||
<!-- reactive -->
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-mongodb-parent</artifactId>
|
||||
<version>2.1.0.M1</version>
|
||||
<version>2.1.0.M3</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-mongodb-parent</artifactId>
|
||||
<version>2.1.0.M1</version>
|
||||
<version>2.1.0.M3</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
<objenesis>1.3</objenesis>
|
||||
<equalsverifier>1.7.8</equalsverifier>
|
||||
<java-module-name>spring.data.mongodb</java-module-name>
|
||||
<project.root>${basedir}/..</project.root>
|
||||
<multithreadedtc>1.01</multithreadedtc>
|
||||
</properties>
|
||||
|
||||
@@ -215,7 +216,6 @@
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<version>${jackson}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
@@ -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,48 @@
|
||||
/*
|
||||
* 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.dao.NonTransientDataAccessException;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* {@link NonTransientDataAccessException} specific to MongoDB {@link com.mongodb.session.ClientSession} related data
|
||||
* access failures such as reading data using an already closed session.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.1
|
||||
*/
|
||||
public class ClientSessionException extends NonTransientDataAccessException {
|
||||
|
||||
/**
|
||||
* Constructor for {@link ClientSessionException}.
|
||||
*
|
||||
* @param msg the detail message. Must not be {@literal null}.
|
||||
*/
|
||||
public ClientSessionException(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 ClientSessionException(@Nullable String msg, @Nullable Throwable cause) {
|
||||
super(msg, cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,230 @@
|
||||
/*
|
||||
* 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();
|
||||
}
|
||||
|
||||
@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.getSession().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 (isTransactionActive(resourceHolder)) {
|
||||
resourceHolder.getSession().commitTransaction();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.transaction.support.ResourceHolderSynchronization#afterCompletion(int)
|
||||
*/
|
||||
@Override
|
||||
public void afterCompletion(int status) {
|
||||
|
||||
if (status == TransactionSynchronization.STATUS_ROLLED_BACK && isTransactionActive(this.resourceHolder)) {
|
||||
resourceHolder.getSession().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.getSession().close();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isTransactionActive(MongoResourceHolder resourceHolder) {
|
||||
|
||||
if (!resourceHolder.hasSession()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return resourceHolder.getSession().hasActiveTransaction();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,20 +20,22 @@ import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.dao.support.PersistenceExceptionTranslator;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Interface for factories creating {@link DB} instances.
|
||||
* Interface for factories creating {@link MongoDatabase} instances.
|
||||
*
|
||||
* @author Mark Pollack
|
||||
* @author Thomas Darimont
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public interface MongoDbFactory extends CodecRegistryProvider {
|
||||
public interface MongoDbFactory extends CodecRegistryProvider, MongoSessionProvider {
|
||||
|
||||
/**
|
||||
* Creates a default {@link DB} instance.
|
||||
* Creates a default {@link MongoDatabase} instance.
|
||||
*
|
||||
* @return
|
||||
* @throws DataAccessException
|
||||
@@ -56,6 +58,11 @@ public interface MongoDbFactory extends CodecRegistryProvider {
|
||||
*/
|
||||
PersistenceExceptionTranslator getExceptionTranslator();
|
||||
|
||||
/**
|
||||
* Get the legacy database entry point. Please consider {@link #getDb()} instead.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
DB getLegacyDb();
|
||||
|
||||
/**
|
||||
@@ -67,4 +74,35 @@ public interface MongoDbFactory extends CodecRegistryProvider {
|
||||
default CodecRegistry getCodecRegistry() {
|
||||
return getDb().getCodecRegistry();
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a {@link ClientSession} for given ClientSessionOptions.
|
||||
*
|
||||
* @param options must not be {@literal null}.
|
||||
* @return never {@literal null}.
|
||||
* @since 2.1
|
||||
*/
|
||||
ClientSession getSession(ClientSessionOptions options);
|
||||
|
||||
/**
|
||||
* Obtain a {@link ClientSession} bound instance of {@link MongoDbFactory} returning {@link MongoDatabase} instances
|
||||
* that are aware and bound to a new session with given {@link ClientSessionOptions options}.
|
||||
*
|
||||
* @param options must not be {@literal null}.
|
||||
* @return never {@literal null}.
|
||||
* @since 2.1
|
||||
*/
|
||||
default MongoDbFactory withSession(ClientSessionOptions options) {
|
||||
return withSession(getSession(options));
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a {@link ClientSession} bound instance of {@link MongoDbFactory} returning {@link MongoDatabase} instances
|
||||
* that are aware and bound to the given session.
|
||||
*
|
||||
* @param session must not be {@literal null}.
|
||||
* @return never {@literal null}.
|
||||
* @since 2.1
|
||||
*/
|
||||
MongoDbFactory withSession(ClientSession session);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
* 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
|
||||
* @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 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() && !getSession().getServerSession().isClosed();
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 getSession().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,490 @@
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
* @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 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 {
|
||||
mongoTransactionObject.commitTransaction();
|
||||
} catch (MongoException ex) {
|
||||
|
||||
throw new TransactionSystemException(String.format("Could not commit Mongo transaction for session %s.",
|
||||
debugString(mongoTransactionObject.getSession())), ex);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (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
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
void commitTransaction() {
|
||||
getRequiredSession().commitTransaction();
|
||||
}
|
||||
|
||||
/**
|
||||
* Rollback (abort) the transaction.
|
||||
*/
|
||||
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
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -16,11 +16,15 @@
|
||||
|
||||
package org.springframework.data.mongodb;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.bson.codecs.configuration.CodecRegistry;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
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;
|
||||
|
||||
/**
|
||||
@@ -65,4 +69,23 @@ public interface ReactiveMongoDatabaseFactory extends CodecRegistryProvider {
|
||||
default CodecRegistry getCodecRegistry() {
|
||||
return getMongoDatabase().getCodecRegistry();
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a {@link Mono} emitting a {@link ClientSession} for given {@link ClientSessionOptions options}.
|
||||
*
|
||||
* @param options must not be {@literal null}.
|
||||
* @return never {@literal null}.
|
||||
* @since 2.1
|
||||
*/
|
||||
Mono<ClientSession> getSession(ClientSessionOptions options);
|
||||
|
||||
/**
|
||||
* Obtain a {@link ClientSession} bound instance of {@link ReactiveMongoDatabaseFactory} returning
|
||||
* {@link MongoDatabase} instances that are aware and bound to the given session.
|
||||
*
|
||||
* @param session must not be {@literal null}.
|
||||
* @return never {@literal null}.
|
||||
* @since 2.1
|
||||
*/
|
||||
ReactiveMongoDatabaseFactory withSession(ClientSession session);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,215 @@
|
||||
/*
|
||||
* 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 java.lang.reflect.Method;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.Optional;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
import org.aopalliance.intercept.MethodInterceptor;
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
import org.springframework.core.MethodClassKey;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.ConcurrentReferenceHashMap;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
import com.mongodb.WriteConcern;
|
||||
import com.mongodb.session.ClientSession;
|
||||
|
||||
/**
|
||||
* {@link MethodInterceptor} implementation looking up and invoking an alternative target method having
|
||||
* {@link ClientSession} as its first argument. This allows seamless integration with the existing code base.
|
||||
* <p />
|
||||
* The {@link MethodInterceptor} is aware of methods on {@code MongoCollection} that my return new instances of itself
|
||||
* like (eg. {@link com.mongodb.reactivestreams.client.MongoCollection#withWriteConcern(WriteConcern)} and decorate them
|
||||
* if not already proxied.
|
||||
*
|
||||
* @param <D> Type of the actual Mongo Database.
|
||||
* @param <C> Type of the actual Mongo Collection.
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
* @since 2.1
|
||||
*/
|
||||
public class SessionAwareMethodInterceptor<D, C> implements MethodInterceptor {
|
||||
|
||||
private static final MethodCache METHOD_CACHE = new MethodCache();
|
||||
|
||||
private final ClientSession session;
|
||||
private final ClientSessionOperator collectionDecorator;
|
||||
private final ClientSessionOperator databaseDecorator;
|
||||
private final Object target;
|
||||
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.
|
||||
*
|
||||
* @param session the {@link ClientSession} to be used on invocation.
|
||||
* @param target the original target object.
|
||||
* @param databaseType the MongoDB database type
|
||||
* @param databaseDecorator a {@link ClientSessionOperator} used to create the proxy for an imperative / reactive
|
||||
* {@code MongoDatabase}.
|
||||
* @param collectionType the MongoDB collection type.
|
||||
* @param collectionDecorator a {@link ClientSessionOperator} used to create the proxy for an imperative / reactive
|
||||
* {@code MongoCollection}.
|
||||
* @param <T> target object type.
|
||||
*/
|
||||
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!");
|
||||
Assert.notNull(collectionDecorator, "Collection ClientSessionOperator must not be null!");
|
||||
|
||||
this.session = session;
|
||||
this.target = target;
|
||||
this.databaseType = ClassUtils.getUserClass(databaseType);
|
||||
this.collectionType = ClassUtils.getUserClass(collectionType);
|
||||
this.collectionDecorator = collectionDecorator;
|
||||
this.databaseDecorator = databaseDecorator;
|
||||
|
||||
this.targetType = ClassUtils.isAssignable(databaseType, target.getClass()) ? databaseType : collectionType;
|
||||
this.sessionType = sessionType;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.aopalliance.intercept.MethodInterceptor(org.aopalliance.intercept.MethodInvocation)
|
||||
*/
|
||||
@Nullable
|
||||
@Override
|
||||
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
|
||||
|
||||
if (requiresDecoration(methodInvocation.getMethod())) {
|
||||
|
||||
Object target = methodInvocation.proceed();
|
||||
if (target instanceof Proxy) {
|
||||
return target;
|
||||
}
|
||||
|
||||
return decorate(target);
|
||||
}
|
||||
|
||||
if (!requiresSession(methodInvocation.getMethod())) {
|
||||
return methodInvocation.proceed();
|
||||
}
|
||||
|
||||
Optional<Method> targetMethod = METHOD_CACHE.lookup(methodInvocation.getMethod(), targetType, sessionType);
|
||||
|
||||
return !targetMethod.isPresent() ? methodInvocation.proceed()
|
||||
: ReflectionUtils.invokeMethod(targetMethod.get(), target,
|
||||
prependSessionToArguments(session, methodInvocation));
|
||||
}
|
||||
|
||||
private boolean requiresDecoration(Method method) {
|
||||
|
||||
return ClassUtils.isAssignable(databaseType, method.getReturnType())
|
||||
|| ClassUtils.isAssignable(collectionType, method.getReturnType());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected Object decorate(Object target) {
|
||||
|
||||
return ClassUtils.isAssignable(databaseType, target.getClass()) ? databaseDecorator.apply(session, target)
|
||||
: collectionDecorator.apply(session, target);
|
||||
}
|
||||
|
||||
private static boolean requiresSession(Method method) {
|
||||
|
||||
if (method.getParameterCount() == 0
|
||||
|| !ClassUtils.isAssignable(ClientSession.class, method.getParameterTypes()[0])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static Object[] prependSessionToArguments(ClientSession session, MethodInvocation invocation) {
|
||||
|
||||
Object[] args = new Object[invocation.getArguments().length + 1];
|
||||
|
||||
args[0] = session;
|
||||
System.arraycopy(invocation.getArguments(), 0, args, 1, invocation.getArguments().length);
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple {@link Method} to {@link Method} caching facility for {@link ClientSession} overloaded targets.
|
||||
*
|
||||
* @since 2.1
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
static class MethodCache {
|
||||
|
||||
private final ConcurrentReferenceHashMap<MethodClassKey, Optional<Method>> cache = new ConcurrentReferenceHashMap<>();
|
||||
|
||||
/**
|
||||
* Lookup the target {@link Method}.
|
||||
*
|
||||
* @param method
|
||||
* @param targetClass
|
||||
* @return
|
||||
*/
|
||||
Optional<Method> lookup(Method method, Class<?> targetClass, Class<? extends ClientSession> sessionType) {
|
||||
|
||||
return cache.computeIfAbsent(new MethodClassKey(method, targetClass),
|
||||
val -> Optional.ofNullable(findTargetWithSession(method, targetClass, sessionType)));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Method findTargetWithSession(Method sourceMethod, Class<?> targetType,
|
||||
Class<? extends ClientSession> sessionType) {
|
||||
|
||||
Class<?>[] argTypes = sourceMethod.getParameterTypes();
|
||||
Class<?>[] args = new Class<?>[argTypes.length + 1];
|
||||
args[0] = sessionType;
|
||||
System.arraycopy(argTypes, 0, args, 1, argTypes.length);
|
||||
|
||||
return ReflectionUtils.findMethod(targetType, sourceMethod.getName(), args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the cache contains an entry for {@link Method} and {@link Class}.
|
||||
*
|
||||
* @param method
|
||||
* @param targetClass
|
||||
* @return
|
||||
*/
|
||||
boolean contains(Method method, Class<?> targetClass) {
|
||||
return cache.containsKey(new MethodClassKey(method, targetClass));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents an operation upon two operands of the same type, producing a result of the same type as the operands
|
||||
* accepting {@link ClientSession}. This is a specialization of {@link BiFunction} for the case where the operands and
|
||||
* the result are all of the same type.
|
||||
*
|
||||
* @param <T> the type of the operands and result of the operator
|
||||
*/
|
||||
public interface ClientSessionOperator<T> extends BiFunction<ClientSession, T, T> {}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -275,18 +275,9 @@ class DefaultBulkOperations implements BulkOperations {
|
||||
|
||||
try {
|
||||
|
||||
MongoCollection<Document> collection = mongoOperations.getCollection(collectionName);
|
||||
if (defaultWriteConcern != null) {
|
||||
collection = collection.withWriteConcern(defaultWriteConcern);
|
||||
}
|
||||
|
||||
return mongoOperations.execute(collectionName, collection -> {
|
||||
return collection.bulkWrite(models.stream().map(this::mapWriteModel).collect(Collectors.toList()), bulkOptions);
|
||||
|
||||
} catch (BulkWriteException o_O) {
|
||||
|
||||
DataAccessException toThrow = exceptionTranslator.translateExceptionIfPossible(o_O);
|
||||
throw toThrow == null ? o_O : toThrow;
|
||||
|
||||
});
|
||||
} finally {
|
||||
this.bulkOptions = getBulkWriteOptions(bulkOperationContext.getBulkMode());
|
||||
}
|
||||
|
||||
@@ -15,8 +15,6 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core;
|
||||
|
||||
import static org.springframework.data.mongodb.core.MongoTemplate.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
@@ -50,18 +48,22 @@ public class DefaultIndexOperations implements IndexOperations {
|
||||
|
||||
private static final String PARTIAL_FILTER_EXPRESSION_KEY = "partialFilterExpression";
|
||||
|
||||
private final MongoDbFactory mongoDbFactory;
|
||||
private final String collectionName;
|
||||
private final QueryMapper mapper;
|
||||
private final @Nullable Class<?> type;
|
||||
|
||||
private MongoOperations mongoOperations;
|
||||
|
||||
/**
|
||||
* Creates a new {@link DefaultIndexOperations}.
|
||||
*
|
||||
* @param mongoDbFactory must not be {@literal null}.
|
||||
* @param collectionName must not be {@literal null}.
|
||||
* @param queryMapper must not be {@literal null}.
|
||||
* @deprecated since 2.1. Please use
|
||||
* {@link DefaultIndexOperations#DefaultIndexOperations(MongoOperations, String, Class)}.
|
||||
*/
|
||||
@Deprecated
|
||||
public DefaultIndexOperations(MongoDbFactory mongoDbFactory, String collectionName, QueryMapper queryMapper) {
|
||||
this(mongoDbFactory, collectionName, queryMapper, null);
|
||||
}
|
||||
@@ -74,7 +76,10 @@ public class DefaultIndexOperations implements IndexOperations {
|
||||
* @param queryMapper must not be {@literal null}.
|
||||
* @param type Type used for mapping potential partial index filter expression. Can be {@literal null}.
|
||||
* @since 1.10
|
||||
* @deprecated since 2.1. Please use
|
||||
* {@link DefaultIndexOperations#DefaultIndexOperations(MongoOperations, String, Class)}.
|
||||
*/
|
||||
@Deprecated
|
||||
public DefaultIndexOperations(MongoDbFactory mongoDbFactory, String collectionName, QueryMapper queryMapper,
|
||||
@Nullable Class<?> type) {
|
||||
|
||||
@@ -82,10 +87,29 @@ public class DefaultIndexOperations implements IndexOperations {
|
||||
Assert.notNull(collectionName, "Collection name can not be null!");
|
||||
Assert.notNull(queryMapper, "QueryMapper must not be null!");
|
||||
|
||||
this.mongoDbFactory = mongoDbFactory;
|
||||
this.collectionName = collectionName;
|
||||
this.mapper = queryMapper;
|
||||
this.type = type;
|
||||
this.mongoOperations = new MongoTemplate(mongoDbFactory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link DefaultIndexOperations}.
|
||||
*
|
||||
* @param mongoOperations must not be {@literal null}.
|
||||
* @param collectionName must not be {@literal null} or empty.
|
||||
* @param type can be {@literal null}.
|
||||
* @since 2.1
|
||||
*/
|
||||
public DefaultIndexOperations(MongoOperations mongoOperations, String collectionName, @Nullable Class<?> type) {
|
||||
|
||||
Assert.notNull(mongoOperations, "MongoOperations must not be null!");
|
||||
Assert.hasText(collectionName, "Collection name must not be null or empty!");
|
||||
|
||||
this.mongoOperations = mongoOperations;
|
||||
this.mapper = new QueryMapper(mongoOperations.getConverter());
|
||||
this.collectionName = collectionName;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -187,11 +211,10 @@ public class DefaultIndexOperations implements IndexOperations {
|
||||
|
||||
Assert.notNull(callback, "CollectionCallback must not be null!");
|
||||
|
||||
try {
|
||||
MongoCollection<Document> collection = mongoDbFactory.getDb().getCollection(collectionName);
|
||||
return callback.doInCollection(collection);
|
||||
} catch (RuntimeException e) {
|
||||
throw potentiallyConvertRuntimeException(e, mongoDbFactory.getExceptionTranslator());
|
||||
if (type != null) {
|
||||
return mongoOperations.execute(type, callback);
|
||||
}
|
||||
|
||||
return mongoOperations.execute(collectionName, callback);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,175 @@
|
||||
/*
|
||||
* 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 lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
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.determineCollectionName(domainType);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -86,6 +86,13 @@ public interface ExecutableRemoveOperation {
|
||||
*/
|
||||
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.
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,262 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -29,6 +29,7 @@ import org.springframework.dao.InvalidDataAccessResourceUsageException;
|
||||
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.UncategorizedMongoDbException;
|
||||
import org.springframework.data.mongodb.util.MongoDbErrorCodes;
|
||||
import org.springframework.lang.Nullable;
|
||||
@@ -119,18 +120,28 @@ public class MongoExceptionTranslator implements PersistenceExceptionTranslator
|
||||
int code = ((MongoException) ex).getCode();
|
||||
|
||||
if (MongoDbErrorCodes.isDuplicateKeyCode(code)) {
|
||||
throw new DuplicateKeyException(ex.getMessage(), ex);
|
||||
return new DuplicateKeyException(ex.getMessage(), ex);
|
||||
} else if (MongoDbErrorCodes.isDataAccessResourceFailureCode(code)) {
|
||||
throw new DataAccessResourceFailureException(ex.getMessage(), ex);
|
||||
return new DataAccessResourceFailureException(ex.getMessage(), ex);
|
||||
} else if (MongoDbErrorCodes.isInvalidDataAccessApiUsageCode(code) || code == 10003 || code == 12001
|
||||
|| code == 12010 || code == 12011 || code == 12012) {
|
||||
throw new InvalidDataAccessApiUsageException(ex.getMessage(), ex);
|
||||
return new InvalidDataAccessApiUsageException(ex.getMessage(), ex);
|
||||
} else if (MongoDbErrorCodes.isPermissionDeniedCode(code)) {
|
||||
throw new PermissionDeniedDataAccessException(ex.getMessage(), ex);
|
||||
return new PermissionDeniedDataAccessException(ex.getMessage(), ex);
|
||||
}
|
||||
return new UncategorizedMongoDbException(ex.getMessage(), ex);
|
||||
}
|
||||
|
||||
// may interfere with OmitStackTraceInFastThrow (enabled by default).
|
||||
// see https://jira.spring.io/browse/DATAMONGO-1905
|
||||
if (ex instanceof IllegalStateException) {
|
||||
for (StackTraceElement elm : ex.getStackTrace()) {
|
||||
if (elm.getClassName().contains("ClientSession")) {
|
||||
return new ClientSessionException(ex.getMessage(), ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we get here, we have an exception that resulted from user code,
|
||||
// rather than the persistence provider, so we return null to indicate
|
||||
// that translation should not occur.
|
||||
|
||||
@@ -18,6 +18,8 @@ package org.springframework.data.mongodb.core;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.bson.Document;
|
||||
import org.springframework.data.geo.GeoResults;
|
||||
@@ -39,9 +41,12 @@ import org.springframework.data.mongodb.core.query.Query;
|
||||
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 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;
|
||||
@@ -151,6 +156,64 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
@Nullable
|
||||
<T> T execute(String collectionName, CollectionCallback<T> action);
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @param sessionOptions must not be {@literal null}.
|
||||
* @return new instance of {@link SessionScoped}. Never {@literal null}.
|
||||
* @since 2.1
|
||||
*/
|
||||
SessionScoped 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.
|
||||
* <p />
|
||||
* <strong>Note:</strong> It is up to the caller to manage the {@link ClientSession} lifecycle. Use the
|
||||
* {@link SessionScoped#execute(SessionCallback, Consumer)} hook to potentially close the {@link ClientSession}.
|
||||
*
|
||||
* @param sessionProvider must not be {@literal null}.
|
||||
* @since 2.1
|
||||
*/
|
||||
default SessionScoped withSession(Supplier<ClientSession> sessionProvider) {
|
||||
|
||||
Assert.notNull(sessionProvider, "SessionProvider must not be null!");
|
||||
|
||||
return new SessionScoped() {
|
||||
|
||||
private final Object lock = new Object();
|
||||
private @Nullable ClientSession session = null;
|
||||
|
||||
@Override
|
||||
public <T> T execute(SessionCallback<T> action, Consumer<ClientSession> onComplete) {
|
||||
|
||||
synchronized (lock) {
|
||||
if (session == null) {
|
||||
session = sessionProvider.get();
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
return action.doInSession(MongoOperations.this.withSession(session));
|
||||
} finally {
|
||||
onComplete.accept(session);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a {@link ClientSession} bound instance of {@link MongoOperations}.
|
||||
* <p />
|
||||
* <strong>Note:</strong> It is up to the caller to manage the {@link ClientSession} lifecycle.
|
||||
*
|
||||
* @param session must not be {@literal null}.
|
||||
* @return {@link ClientSession} bound instance of {@link MongoOperations}.
|
||||
* @since 2.1
|
||||
*/
|
||||
MongoOperations withSession(ClientSession session);
|
||||
|
||||
/**
|
||||
* Executes the given {@link Query} on the entity collection of the specified {@code entityType} backed by a Mongo DB
|
||||
* {@link Cursor}.
|
||||
@@ -769,7 +832,7 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers <a href="https://docs.mongodb.org/manual/reference/method/db.collection.findAndModify/">findAndModify<a/>
|
||||
* Triggers <a href="https://docs.mongodb.org/manual/reference/method/db.collection.findAndModify/">findAndModify <a/>
|
||||
* to apply provided {@link Update} on documents matching {@link Criteria} of given {@link Query}.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional
|
||||
@@ -782,7 +845,7 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
<T> T findAndModify(Query query, Update update, Class<T> entityClass);
|
||||
|
||||
/**
|
||||
* Triggers <a href="https://docs.mongodb.org/manual/reference/method/db.collection.findAndModify/">findAndModify<a/>
|
||||
* Triggers <a href="https://docs.mongodb.org/manual/reference/method/db.collection.findAndModify/">findAndModify <a/>
|
||||
* to apply provided {@link Update} on documents matching {@link Criteria} of given {@link Query}.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional
|
||||
@@ -796,7 +859,7 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
<T> T findAndModify(Query query, Update update, Class<T> entityClass, String collectionName);
|
||||
|
||||
/**
|
||||
* Triggers <a href="https://docs.mongodb.org/manual/reference/method/db.collection.findAndModify/">findAndModify<a/>
|
||||
* Triggers <a href="https://docs.mongodb.org/manual/reference/method/db.collection.findAndModify/">findAndModify <a/>
|
||||
* to apply provided {@link Update} on documents matching {@link Criteria} of given {@link Query} taking
|
||||
* {@link FindAndModifyOptions} into account.
|
||||
*
|
||||
@@ -813,7 +876,7 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
<T> T findAndModify(Query query, Update update, FindAndModifyOptions options, Class<T> entityClass);
|
||||
|
||||
/**
|
||||
* Triggers <a href="https://docs.mongodb.org/manual/reference/method/db.collection.findAndModify/">findAndModify<a/>
|
||||
* Triggers <a href="https://docs.mongodb.org/manual/reference/method/db.collection.findAndModify/">findAndModify <a/>
|
||||
* to apply provided {@link Update} on documents matching {@link Criteria} of given {@link Query} taking
|
||||
* {@link FindAndModifyOptions} into account.
|
||||
*
|
||||
@@ -953,7 +1016,7 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
* 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}.
|
||||
*/
|
||||
void insertAll(Collection<? extends Object> objectsToSave);
|
||||
|
||||
@@ -1142,6 +1205,7 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
* @param query the query document that specifies the criteria used to remove a record.
|
||||
* @param entityClass class that determines the collection to use.
|
||||
* @return the {@link DeleteResult} which lets you access the results of the previous delete.
|
||||
* @throws IllegalArgumentException when {@literal query} or {@literal entityClass} is {@literal null}.
|
||||
*/
|
||||
DeleteResult remove(Query query, Class<?> entityClass);
|
||||
|
||||
@@ -1153,6 +1217,8 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
* @param entityClass class of the pojo to be operated on. Can be {@literal null}.
|
||||
* @param collectionName name of the collection where the objects will removed, must not be {@literal null} or empty.
|
||||
* @return the {@link DeleteResult} which lets you access the results of the previous delete.
|
||||
* @throws IllegalArgumentException when {@literal query}, {@literal entityClass} or {@literal collectionName} is
|
||||
* {@literal null}.
|
||||
*/
|
||||
DeleteResult remove(Query query, Class<?> entityClass, String collectionName);
|
||||
|
||||
@@ -1165,6 +1231,7 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
* @param query the query document that specifies the criteria used to remove a record.
|
||||
* @param collectionName name of the collection where the objects will removed, must not be {@literal null} or empty.
|
||||
* @return the {@link DeleteResult} which lets you access the results of the previous delete.
|
||||
* @throws IllegalArgumentException when {@literal query} or {@literal collectionName} is {@literal null}.
|
||||
*/
|
||||
DeleteResult remove(Query query, String collectionName);
|
||||
|
||||
|
||||
@@ -61,7 +61,9 @@ import org.springframework.data.mapping.PropertyPath;
|
||||
import org.springframework.data.mapping.PropertyReferenceException;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
|
||||
import org.springframework.data.mongodb.MongoDatabaseUtils;
|
||||
import org.springframework.data.mongodb.MongoDbFactory;
|
||||
import org.springframework.data.mongodb.SessionSynchronization;
|
||||
import org.springframework.data.mongodb.core.BulkOperations.BulkMode;
|
||||
import org.springframework.data.mongodb.core.DefaultBulkOperations.BulkOperationContext;
|
||||
import org.springframework.data.mongodb.core.aggregation.Aggregation;
|
||||
@@ -124,6 +126,8 @@ import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.ResourceUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.mongodb.ClientSessionOptions;
|
||||
import com.mongodb.Cursor;
|
||||
import com.mongodb.DBCollection;
|
||||
import com.mongodb.DBCursor;
|
||||
import com.mongodb.Mongo;
|
||||
@@ -132,6 +136,7 @@ import com.mongodb.MongoException;
|
||||
import com.mongodb.ReadPreference;
|
||||
import com.mongodb.WriteConcern;
|
||||
import com.mongodb.client.AggregateIterable;
|
||||
import com.mongodb.client.ClientSession;
|
||||
import com.mongodb.client.DistinctIterable;
|
||||
import com.mongodb.client.FindIterable;
|
||||
import com.mongodb.client.MapReduceIterable;
|
||||
@@ -202,14 +207,27 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
private @Nullable ResourceLoader resourceLoader;
|
||||
private @Nullable MongoPersistentEntityIndexCreator indexCreator;
|
||||
|
||||
private SessionSynchronization sessionSynchronization = SessionSynchronization.ON_ACTUAL_TRANSACTION;
|
||||
|
||||
/**
|
||||
* Constructor used for a basic template configuration
|
||||
* Constructor used for a basic template configuration.
|
||||
*
|
||||
* @param mongoClient must not be {@literal null}.
|
||||
* @param databaseName must not be {@literal null} or empty.
|
||||
*/
|
||||
public MongoTemplate(MongoClient mongoClient, String databaseName) {
|
||||
this(new SimpleMongoDbFactory(mongoClient, databaseName), null);
|
||||
this(new SimpleMongoDbFactory(mongoClient, databaseName), (MongoConverter) null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor used for a basic template configuration.
|
||||
*
|
||||
* @param mongoClient must not be {@literal null}.
|
||||
* @param databaseName must not be {@literal null} or empty.
|
||||
* @since 2.1
|
||||
*/
|
||||
public MongoTemplate(com.mongodb.client.MongoClient mongoClient, String databaseName) {
|
||||
this(new SimpleMongoClientDbFactory(mongoClient, databaseName), (MongoConverter) null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -218,7 +236,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
* @param mongoDbFactory must not be {@literal null}.
|
||||
*/
|
||||
public MongoTemplate(MongoDbFactory mongoDbFactory) {
|
||||
this(mongoDbFactory, null);
|
||||
this(mongoDbFactory, (MongoConverter) null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -251,6 +269,20 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
}
|
||||
}
|
||||
|
||||
private MongoTemplate(MongoDbFactory dbFactory, MongoTemplate that) {
|
||||
|
||||
this.mongoDbFactory = dbFactory;
|
||||
this.exceptionTranslator = that.exceptionTranslator;
|
||||
this.sessionSynchronization = that.sessionSynchronization;
|
||||
this.mongoConverter = that.mongoConverter instanceof MappingMongoConverter ? getDefaultMongoConverter(dbFactory)
|
||||
: that.mongoConverter;
|
||||
this.queryMapper = that.queryMapper;
|
||||
this.updateMapper = that.updateMapper;
|
||||
this.schemaMapper = that.schemaMapper;
|
||||
this.projectionFactory = that.projectionFactory;
|
||||
this.mappingContext = that.mappingContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the {@link WriteResultChecking} to be used with the template. Setting {@literal null} will reset the
|
||||
* default of {@link #DEFAULT_WRITE_RESULT_CHECKING}.
|
||||
@@ -384,7 +416,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
Document mappedQuery = queryMapper.getMappedObject(query.getQueryObject(), persistentEntity);
|
||||
|
||||
FindIterable<Document> cursor = new QueryCursorPreparer(query, entityType)
|
||||
.prepare(collection.find(mappedQuery).projection(mappedFields));
|
||||
.prepare(collection.find(mappedQuery, Document.class).projection(mappedFields));
|
||||
|
||||
return new CloseableIterableCursorAdapter<T>(cursor, exceptionTranslator,
|
||||
new ProjectingReadCallback<>(mongoConverter, entityType, returnType, collectionName));
|
||||
@@ -496,10 +528,10 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
*/
|
||||
public <T> T execute(DbCallback<T> action) {
|
||||
|
||||
Assert.notNull(action, "DbCallbackmust not be null!");
|
||||
Assert.notNull(action, "DbCallback must not be null!");
|
||||
|
||||
try {
|
||||
MongoDatabase db = this.getDb();
|
||||
MongoDatabase db = prepareDatabase(this.doGetDatabase());
|
||||
return action.doInDB(db);
|
||||
} catch (RuntimeException e) {
|
||||
throw potentiallyConvertRuntimeException(e, exceptionTranslator);
|
||||
@@ -526,13 +558,48 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
Assert.notNull(callback, "CollectionCallback must not be null!");
|
||||
|
||||
try {
|
||||
MongoCollection<Document> collection = getAndPrepareCollection(getDb(), collectionName);
|
||||
MongoCollection<Document> collection = getAndPrepareCollection(doGetDatabase(), collectionName);
|
||||
return callback.doInCollection(collection);
|
||||
} catch (RuntimeException e) {
|
||||
throw potentiallyConvertRuntimeException(e, exceptionTranslator);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.MongoOperations#withSession(com.mongodb.ClientSessionOptions)
|
||||
*/
|
||||
@Override
|
||||
public SessionScoped withSession(ClientSessionOptions options) {
|
||||
|
||||
Assert.notNull(options, "ClientSessionOptions must not be null!");
|
||||
|
||||
return withSession(() -> mongoDbFactory.getSession(options));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.MongoOperations#withSession(com.mongodb.session.ClientSession)
|
||||
*/
|
||||
@Override
|
||||
public MongoTemplate withSession(ClientSession session) {
|
||||
|
||||
Assert.notNull(session, "ClientSession must not be null!");
|
||||
|
||||
return new SessionBoundMongoTemplate(session, MongoTemplate.this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Define if {@link MongoTemplate} should participate in transactions. Default is set to
|
||||
* {@link SessionSynchronization#ON_ACTUAL_TRANSACTION}.<br />
|
||||
* <strong>NOTE:</strong> MongoDB transactions require at least MongoDB 4.0.
|
||||
*
|
||||
* @since 2.1
|
||||
*/
|
||||
public void setSessionSynchronization(SessionSynchronization sessionSynchronization) {
|
||||
this.sessionSynchronization = sessionSynchronization;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.MongoOperations#createCollection(java.lang.Class)
|
||||
@@ -607,6 +674,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
|
||||
return execute(new DbCallback<Boolean>() {
|
||||
public Boolean doInDB(MongoDatabase db) throws MongoException, DataAccessException {
|
||||
|
||||
for (String name : db.listCollectionNames()) {
|
||||
if (name.equals(collectionName)) {
|
||||
return true;
|
||||
@@ -637,7 +705,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
public Void doInCollection(MongoCollection<Document> collection) throws MongoException, DataAccessException {
|
||||
collection.drop();
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("Dropped collection [{}]", collection.getNamespace().getCollectionName());
|
||||
LOGGER.debug("Dropped collection [{}]",
|
||||
collection.getNamespace() != null ? collection.getNamespace().getCollectionName() : collectionName);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -649,7 +718,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
* @see org.springframework.data.mongodb.core.ExecutableInsertOperation#indexOps(java.lang.String)
|
||||
*/
|
||||
public IndexOperations indexOps(String collectionName) {
|
||||
return new DefaultIndexOperations(getMongoDbFactory(), collectionName, queryMapper);
|
||||
return new DefaultIndexOperations(this, collectionName, null);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -657,8 +726,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
* @see org.springframework.data.mongodb.core.ExecutableInsertOperation#indexOps(java.lang.Class)
|
||||
*/
|
||||
public IndexOperations indexOps(Class<?> entityClass) {
|
||||
return new DefaultIndexOperations(getMongoDbFactory(), determineCollectionName(entityClass), queryMapper,
|
||||
entityClass);
|
||||
return new DefaultIndexOperations(this, determineCollectionName(entityClass), entityClass);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -837,10 +905,9 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
Class<T> mongoDriverCompatibleType = getMongoDbFactory().getCodecFor(resultClass).map(Codec::getEncoderClass)
|
||||
.orElse((Class) BsonValue.class);
|
||||
|
||||
MongoIterable<?> result = execute((db) -> {
|
||||
MongoIterable<?> result = execute(collectionName, (collection) -> {
|
||||
|
||||
DistinctIterable<T> iterable = db.getCollection(collectionName).distinct(mappedFieldName, mappedQuery,
|
||||
mongoDriverCompatibleType);
|
||||
DistinctIterable<T> iterable = collection.distinct(mappedFieldName, mappedQuery, mongoDriverCompatibleType);
|
||||
|
||||
return query.getCollation().map(Collation::toMongoCollation).map(iterable::collation).orElse(iterable);
|
||||
});
|
||||
@@ -1045,10 +1112,13 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
Assert.notNull(query, "Query must not be null!");
|
||||
Assert.hasText(collectionName, "Collection name must not be null or empty!");
|
||||
|
||||
CountOptions options = new CountOptions();
|
||||
query.getCollation().map(Collation::toMongoCollation).ifPresent(options::collation);
|
||||
|
||||
Document document = queryMapper.getMappedObject(query.getQueryObject(),
|
||||
Optional.ofNullable(entityClass).map(it -> mappingContext.getPersistentEntity(entityClass)));
|
||||
|
||||
return execute(collectionName, collection -> collection.count(document));
|
||||
return execute(collectionName, collection -> collection.count(document, options));
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -1095,8 +1165,9 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
protected MongoCollection<Document> prepareCollection(MongoCollection<Document> collection) {
|
||||
|
||||
if (this.readPreference != null) {
|
||||
return collection.withReadPreference(readPreference);
|
||||
collection = collection.withReadPreference(readPreference);
|
||||
}
|
||||
|
||||
return collection;
|
||||
}
|
||||
|
||||
@@ -1106,7 +1177,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
* In case of using MongoDB Java driver version 3 the returned {@link WriteConcern} will be defaulted to
|
||||
* {@link WriteConcern#ACKNOWLEDGED} when {@link WriteResultChecking} is set to {@link WriteResultChecking#EXCEPTION}.
|
||||
*
|
||||
* @param writeConcern any WriteConcern already configured or null
|
||||
* @param mongoAction any MongoAction already configured or null
|
||||
* @return The prepared WriteConcern or null
|
||||
*/
|
||||
@Nullable
|
||||
@@ -1362,6 +1433,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
MongoAction mongoAction = new MongoAction(writeConcern, MongoActionOperation.INSERT, collectionName,
|
||||
entityClass, document, null);
|
||||
WriteConcern writeConcernToUse = prepareWriteConcern(mongoAction);
|
||||
|
||||
if (writeConcernToUse == null) {
|
||||
collection.insertOne(document);
|
||||
} else {
|
||||
@@ -1421,10 +1493,10 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
collection.withWriteConcern(writeConcernToUse).insertOne(dbDoc);
|
||||
}
|
||||
} else if (writeConcernToUse == null) {
|
||||
collection.replaceOne(Filters.eq(ID_FIELD, dbDoc.get(ID_FIELD)), dbDoc, new UpdateOptions().upsert(true));
|
||||
collection.replaceOne(Filters.eq(ID_FIELD, dbDoc.get(ID_FIELD)), dbDoc, new ReplaceOptions().upsert(true));
|
||||
} else {
|
||||
collection.withWriteConcern(writeConcernToUse).replaceOne(Filters.eq(ID_FIELD, dbDoc.get(ID_FIELD)), dbDoc,
|
||||
new UpdateOptions().upsert(true));
|
||||
new ReplaceOptions().upsert(true));
|
||||
}
|
||||
return dbDoc.get(ID_FIELD);
|
||||
}
|
||||
@@ -1530,7 +1602,12 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
collection = writeConcernToUse != null ? collection.withWriteConcern(writeConcernToUse) : collection;
|
||||
|
||||
if (!UpdateMapper.isUpdateObject(updateObj)) {
|
||||
return collection.replaceOne(queryObj, updateObj, opts);
|
||||
|
||||
ReplaceOptions replaceOptions = new ReplaceOptions();
|
||||
replaceOptions.collation(opts.getCollation());
|
||||
replaceOptions.upsert(opts.isUpsert());
|
||||
|
||||
return collection.replaceOne(queryObj, updateObj, replaceOptions);
|
||||
} else {
|
||||
if (multi) {
|
||||
return collection.updateMany(queryObj, updateObj, opts);
|
||||
@@ -1566,7 +1643,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
Assert.notNull(object, "Object must not be null!");
|
||||
Assert.hasText(collectionName, "Collection name must not be null or empty!");
|
||||
|
||||
return doRemove(collectionName, getIdQueryFor(object), object.getClass());
|
||||
return doRemove(collectionName, getIdQueryFor(object), object.getClass(), false);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1655,7 +1732,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
|
||||
@Override
|
||||
public DeleteResult remove(Query query, String collectionName) {
|
||||
return doRemove(collectionName, query, null);
|
||||
return doRemove(collectionName, query, null, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1667,19 +1744,17 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
public DeleteResult remove(Query query, Class<?> entityClass, String collectionName) {
|
||||
|
||||
Assert.notNull(entityClass, "EntityClass must not be null!");
|
||||
return doRemove(collectionName, query, entityClass);
|
||||
return doRemove(collectionName, query, entityClass, true);
|
||||
}
|
||||
|
||||
protected <T> DeleteResult doRemove(final String collectionName, final Query query,
|
||||
@Nullable final Class<T> entityClass) {
|
||||
@Nullable final Class<T> entityClass, boolean multi) {
|
||||
|
||||
Assert.notNull(query, "Query must not be null!");
|
||||
Assert.hasText(collectionName, "Collection name must not be null or empty!");
|
||||
if (query == null) {
|
||||
throw new InvalidDataAccessApiUsageException("Query passed in to remove can't be null!");
|
||||
}
|
||||
|
||||
final Document queryObject = query.getQueryObject();
|
||||
final MongoPersistentEntity<?> entity = getPersistentEntity(entityClass);
|
||||
final Document queryObject = queryMapper.getMappedObject(query.getQueryObject(), entity);
|
||||
|
||||
return execute(collectionName, new CollectionCallback<DeleteResult>() {
|
||||
|
||||
@@ -1688,7 +1763,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
|
||||
maybeEmitEvent(new BeforeDeleteEvent<T>(queryObject, entityClass, collectionName));
|
||||
|
||||
Document mappedQuery = queryMapper.getMappedObject(queryObject, entity);
|
||||
Document removeQuery = queryObject;
|
||||
|
||||
DeleteOptions options = new DeleteOptions();
|
||||
query.getCollation().map(Collation::toMongoCollation).ifPresent(options::collation);
|
||||
@@ -1698,22 +1773,33 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
|
||||
WriteConcern writeConcernToUse = prepareWriteConcern(mongoAction);
|
||||
|
||||
DeleteResult dr = null;
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("Remove using query: {} in collection: {}.",
|
||||
new Object[] { serializeToJsonSafely(mappedQuery), collectionName });
|
||||
new Object[] { serializeToJsonSafely(removeQuery), collectionName });
|
||||
}
|
||||
|
||||
if (writeConcernToUse == null) {
|
||||
if (query.getLimit() > 0 || query.getSkip() > 0) {
|
||||
|
||||
dr = collection.deleteMany(mappedQuery, options);
|
||||
} else {
|
||||
dr = collection.withWriteConcern(writeConcernToUse).deleteMany(mappedQuery, options);
|
||||
MongoCursor<Document> cursor = new QueryCursorPreparer(query, entityClass)
|
||||
.prepare(collection.find(removeQuery).projection(new Document(ID_FIELD, 1))).iterator();
|
||||
|
||||
Set<Object> ids = new LinkedHashSet<>();
|
||||
while (cursor.hasNext()) {
|
||||
ids.add(cursor.next().get(ID_FIELD));
|
||||
}
|
||||
|
||||
removeQuery = new Document(ID_FIELD, new Document("$in", ids));
|
||||
}
|
||||
|
||||
MongoCollection<Document> collectionToUse = writeConcernToUse != null
|
||||
? collection.withWriteConcern(writeConcernToUse) : collection;
|
||||
|
||||
DeleteResult result = multi ? collectionToUse.deleteMany(removeQuery, options)
|
||||
: collection.deleteOne(removeQuery, options);
|
||||
|
||||
maybeEmitEvent(new AfterDeleteEvent<T>(queryObject, entityClass, collectionName));
|
||||
|
||||
return dr;
|
||||
return result;
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1753,30 +1839,48 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
public <T> MapReduceResults<T> mapReduce(Query query, String inputCollectionName, String mapFunction,
|
||||
String reduceFunction, @Nullable MapReduceOptions mapReduceOptions, Class<T> entityClass) {
|
||||
|
||||
Assert.notNull(query, "Query must not be null!");
|
||||
Assert.notNull(inputCollectionName, "InputCollectionName must not be null!");
|
||||
Assert.notNull(entityClass, "EntityClass must not be null!");
|
||||
Assert.notNull(reduceFunction, "ReduceFunction must not be null!");
|
||||
Assert.notNull(mapFunction, "MapFunction must not be null!");
|
||||
return new MapReduceResults<>(
|
||||
mapReduce(query, entityClass, inputCollectionName, mapFunction, reduceFunction, mapReduceOptions, entityClass),
|
||||
new Document());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param query
|
||||
* @param domainType
|
||||
* @param inputCollectionName
|
||||
* @param mapFunction
|
||||
* @param reduceFunction
|
||||
* @param mapReduceOptions
|
||||
* @param resultType
|
||||
* @return
|
||||
* @since 2.1
|
||||
*/
|
||||
public <T> List<T> mapReduce(Query query, Class<?> domainType, String inputCollectionName, String mapFunction,
|
||||
String reduceFunction, @Nullable MapReduceOptions mapReduceOptions, Class<T> resultType) {
|
||||
|
||||
Assert.notNull(domainType, "Domain type must not be null!");
|
||||
Assert.notNull(inputCollectionName, "Input collection name must not be null!");
|
||||
Assert.notNull(resultType, "Result type must not be null!");
|
||||
Assert.notNull(mapFunction, "Map function must not be null!");
|
||||
Assert.notNull(reduceFunction, "Reduce function must not be null!");
|
||||
|
||||
String mapFunc = replaceWithResourceIfNecessary(mapFunction);
|
||||
String reduceFunc = replaceWithResourceIfNecessary(reduceFunction);
|
||||
MongoCollection<Document> inputCollection = getCollection(inputCollectionName);
|
||||
MongoCollection<Document> inputCollection = getAndPrepareCollection(doGetDatabase(), inputCollectionName);
|
||||
|
||||
// MapReduceOp
|
||||
MapReduceIterable<Document> result = inputCollection.mapReduce(mapFunc, reduceFunc);
|
||||
if (query != null && result != null) {
|
||||
MapReduceIterable<Document> mapReduce = inputCollection.mapReduce(mapFunc, reduceFunc, Document.class);
|
||||
|
||||
if (query.getLimit() > 0 && mapReduceOptions.getLimit() == null) {
|
||||
result = result.limit(query.getLimit());
|
||||
if (query.getLimit() > 0 && mapReduceOptions != null && mapReduceOptions.getLimit() == null) {
|
||||
mapReduce = mapReduce.limit(query.getLimit());
|
||||
}
|
||||
if (query.getMeta() != null && query.getMeta().getMaxTimeMsec() != null) {
|
||||
result = result.maxTime(query.getMeta().getMaxTimeMsec(), TimeUnit.MILLISECONDS);
|
||||
if (query.getMeta().getMaxTimeMsec() != null) {
|
||||
mapReduce = mapReduce.maxTime(query.getMeta().getMaxTimeMsec(), TimeUnit.MILLISECONDS);
|
||||
}
|
||||
result = result.sort(getMappedSortObject(query, entityClass));
|
||||
mapReduce = mapReduce.sort(getMappedSortObject(query, domainType));
|
||||
|
||||
result = result.filter(queryMapper.getMappedObject(query.getQueryObject(), Optional.empty()));
|
||||
}
|
||||
mapReduce = mapReduce
|
||||
.filter(queryMapper.getMappedObject(query.getQueryObject(), mappingContext.getPersistentEntity(domainType)));
|
||||
|
||||
Optional<Collation> collation = query.getCollation();
|
||||
|
||||
@@ -1792,32 +1896,32 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
}
|
||||
|
||||
if (!CollectionUtils.isEmpty(mapReduceOptions.getScopeVariables())) {
|
||||
result = result.scope(new Document(mapReduceOptions.getScopeVariables()));
|
||||
mapReduce = mapReduce.scope(new Document(mapReduceOptions.getScopeVariables()));
|
||||
}
|
||||
if (mapReduceOptions.getLimit() != null && mapReduceOptions.getLimit().intValue() > 0) {
|
||||
result = result.limit(mapReduceOptions.getLimit());
|
||||
if (mapReduceOptions.getLimit() != null && mapReduceOptions.getLimit() > 0) {
|
||||
mapReduce = mapReduce.limit(mapReduceOptions.getLimit());
|
||||
}
|
||||
if (mapReduceOptions.getFinalizeFunction().filter(StringUtils::hasText).isPresent()) {
|
||||
result = result.finalizeFunction(mapReduceOptions.getFinalizeFunction().get());
|
||||
mapReduce = mapReduce.finalizeFunction(mapReduceOptions.getFinalizeFunction().get());
|
||||
}
|
||||
if (mapReduceOptions.getJavaScriptMode() != null) {
|
||||
result = result.jsMode(mapReduceOptions.getJavaScriptMode());
|
||||
mapReduce = mapReduce.jsMode(mapReduceOptions.getJavaScriptMode());
|
||||
}
|
||||
if (mapReduceOptions.getOutputSharded().isPresent()) {
|
||||
result = result.sharded(mapReduceOptions.getOutputSharded().get());
|
||||
mapReduce = mapReduce.sharded(mapReduceOptions.getOutputSharded().get());
|
||||
}
|
||||
}
|
||||
|
||||
result = collation.map(Collation::toMongoCollation).map(result::collation).orElse(result);
|
||||
mapReduce = collation.map(Collation::toMongoCollation).map(mapReduce::collation).orElse(mapReduce);
|
||||
|
||||
List<T> mappedResults = new ArrayList<T>();
|
||||
DocumentCallback<T> callback = new ReadDocumentCallback<T>(mongoConverter, entityClass, inputCollectionName);
|
||||
List<T> mappedResults = new ArrayList<>();
|
||||
DocumentCallback<T> callback = new ReadDocumentCallback<>(mongoConverter, resultType, inputCollectionName);
|
||||
|
||||
for (Document document : result) {
|
||||
for (Document document : mapReduce) {
|
||||
mappedResults.add(callback.doWith(document));
|
||||
}
|
||||
|
||||
return new MapReduceResults<T>(mappedResults, new Document());
|
||||
return mappedResults;
|
||||
}
|
||||
|
||||
public <T> GroupByResults<T> group(String inputCollectionName, GroupBy groupBy, Class<T> entityClass) {
|
||||
@@ -1847,13 +1951,13 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
}
|
||||
|
||||
if (document.containsKey("$reduce")) {
|
||||
document.put("$reduce", replaceWithResourceIfNecessary(document.get("$reduce").toString()));
|
||||
document.put("$reduce", replaceWithResourceIfNecessary(ObjectUtils.nullSafeToString(document.get("$reduce"))));
|
||||
}
|
||||
if (document.containsKey("$keyf")) {
|
||||
document.put("$keyf", replaceWithResourceIfNecessary(document.get("$keyf").toString()));
|
||||
document.put("$keyf", replaceWithResourceIfNecessary(ObjectUtils.nullSafeToString(document.get("$keyf"))));
|
||||
}
|
||||
if (document.containsKey("finalize")) {
|
||||
document.put("finalize", replaceWithResourceIfNecessary(document.get("finalize").toString()));
|
||||
document.put("finalize", replaceWithResourceIfNecessary(ObjectUtils.nullSafeToString(document.get("finalize"))));
|
||||
}
|
||||
|
||||
Document commandObject = new Document("group", document);
|
||||
@@ -1862,7 +1966,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
LOGGER.debug("Executing Group with Document [{}]", serializeToJsonSafely(commandObject));
|
||||
}
|
||||
|
||||
Document commandResult = executeCommand(commandObject);
|
||||
Document commandResult = executeCommand(commandObject, this.readPreference);
|
||||
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("Group command result = [{}]", commandResult);
|
||||
@@ -2087,7 +2191,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
|
||||
return execute(collectionName, (CollectionCallback<CloseableIterator<O>>) collection -> {
|
||||
|
||||
AggregateIterable<Document> cursor = collection.aggregate(pipeline) //
|
||||
AggregateIterable<Document> cursor = collection.aggregate(pipeline, Document.class) //
|
||||
.allowDiskUse(options.isAllowDiskUse()) //
|
||||
.useCursor(true);
|
||||
|
||||
@@ -2139,6 +2243,15 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
return new ExecutableAggregationOperationSupport(this).aggregateAndReturn(domainType);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.ExecutableAggregationOperation#aggregateAndReturn(java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public <T> ExecutableMapReduce<T> mapReduce(Class<T> domainType) {
|
||||
return new ExecutableMapReduceOperationSupport(this).mapReduce(domainType);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.ExecutableInsertOperation#insert(java.lang.Class)
|
||||
@@ -2194,7 +2307,15 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
}
|
||||
|
||||
public MongoDatabase getDb() {
|
||||
return mongoDbFactory.getDb();
|
||||
return doGetDatabase();
|
||||
}
|
||||
|
||||
protected MongoDatabase doGetDatabase() {
|
||||
return MongoDatabaseUtils.getDatabase(mongoDbFactory, sessionSynchronization);
|
||||
}
|
||||
|
||||
protected MongoDatabase prepareDatabase(MongoDatabase database) {
|
||||
return database;
|
||||
}
|
||||
|
||||
protected <T> void maybeEmitEvent(MongoMappingEvent<T> event) {
|
||||
@@ -2252,7 +2373,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
|
||||
// TODO: Emit a collection created event
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("Created collection [{}]", coll.getNamespace().getCollectionName());
|
||||
LOGGER.debug("Created collection [{}]",
|
||||
coll.getNamespace() != null ? coll.getNamespace().getCollectionName() : collectionName);
|
||||
}
|
||||
return coll;
|
||||
}
|
||||
@@ -2483,15 +2605,18 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
* @param savedObject
|
||||
* @param id
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
protected void populateIdIfNecessary(Object savedObject, Object id) {
|
||||
|
||||
if (id == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (savedObject instanceof Document) {
|
||||
Document document = (Document) savedObject;
|
||||
document.put(ID_FIELD, id);
|
||||
if (savedObject instanceof Map) {
|
||||
|
||||
Map<String, Object> map = (Map<String, Object>) savedObject;
|
||||
map.put(ID_FIELD, id);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2539,7 +2664,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
|
||||
try {
|
||||
T result = objectCallback
|
||||
.doWith(collectionCallback.doInCollection(getAndPrepareCollection(getDb(), collectionName)));
|
||||
.doWith(collectionCallback.doInCollection(getAndPrepareCollection(doGetDatabase(), collectionName)));
|
||||
return result;
|
||||
} catch (RuntimeException e) {
|
||||
throw potentiallyConvertRuntimeException(e, exceptionTranslator);
|
||||
@@ -2574,7 +2699,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
try {
|
||||
|
||||
FindIterable<Document> iterable = collectionCallback
|
||||
.doInCollection(getAndPrepareCollection(getDb(), collectionName));
|
||||
.doInCollection(getAndPrepareCollection(doGetDatabase(), collectionName));
|
||||
|
||||
if (preparer != null) {
|
||||
iterable = preparer.prepare(iterable);
|
||||
@@ -2610,7 +2735,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
|
||||
try {
|
||||
FindIterable<Document> iterable = collectionCallback
|
||||
.doInCollection(getAndPrepareCollection(getDb(), collectionName));
|
||||
.doInCollection(getAndPrepareCollection(doGetDatabase(), collectionName));
|
||||
|
||||
if (preparer != null) {
|
||||
iterable = preparer.prepare(iterable);
|
||||
@@ -2755,12 +2880,13 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
|
||||
public Document doInCollection(MongoCollection<Document> collection) throws MongoException, DataAccessException {
|
||||
|
||||
FindIterable<Document> iterable = collection.find(query);
|
||||
FindIterable<Document> iterable = collection.find(query, Document.class);
|
||||
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
|
||||
LOGGER.debug("findOne using query: {} fields: {} in db.collection: {}", serializeToJsonSafely(query),
|
||||
serializeToJsonSafely(fields.orElseGet(Document::new)), collection.getNamespace().getFullName());
|
||||
serializeToJsonSafely(fields.orElseGet(Document::new)),
|
||||
collection.getNamespace() != null ? collection.getNamespace().getFullName() : "n/a");
|
||||
}
|
||||
|
||||
if (fields.isPresent()) {
|
||||
@@ -2800,7 +2926,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
public FindIterable<Document> doInCollection(MongoCollection<Document> collection)
|
||||
throws MongoException, DataAccessException {
|
||||
|
||||
return collection.find(query).projection(fields);
|
||||
return collection.find(query, Document.class).projection(fields);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2903,7 +3029,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
|
||||
/**
|
||||
* Simple {@link DocumentCallback} that will transform {@link Document} into the given target type using the given
|
||||
* {@link MongoReader}.
|
||||
* {@link EntityReader}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Christoph Strobl
|
||||
@@ -3211,4 +3337,195 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
public MongoDbFactory getMongoDbFactory() {
|
||||
return mongoDbFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link BatchAggregationLoader} is a little helper that can process cursor results returned by an aggregation
|
||||
* command execution. On presence of a {@literal nextBatch} indicated by presence of an {@code id} field in the
|
||||
* {@code cursor} another {@code getMore} command gets executed reading the next batch of documents until all results
|
||||
* are loaded.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 1.10
|
||||
*/
|
||||
static class BatchAggregationLoader {
|
||||
|
||||
private static final String CURSOR_FIELD = "cursor";
|
||||
private static final String RESULT_FIELD = "result";
|
||||
private static final String BATCH_SIZE_FIELD = "batchSize";
|
||||
private static final String FIRST_BATCH = "firstBatch";
|
||||
private static final String NEXT_BATCH = "nextBatch";
|
||||
private static final String SERVER_USED = "serverUsed";
|
||||
private static final String OK = "ok";
|
||||
|
||||
private final MongoTemplate template;
|
||||
private final ReadPreference readPreference;
|
||||
private final int batchSize;
|
||||
|
||||
BatchAggregationLoader(MongoTemplate template, ReadPreference readPreference, int batchSize) {
|
||||
|
||||
this.template = template;
|
||||
this.readPreference = readPreference;
|
||||
this.batchSize = batchSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run aggregation command and fetch all results.
|
||||
*/
|
||||
Document aggregate(String collectionName, Aggregation aggregation, AggregationOperationContext context) {
|
||||
|
||||
Document command = prepareAggregationCommand(collectionName, aggregation, context, batchSize);
|
||||
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("Executing aggregation: {}", serializeToJsonSafely(command));
|
||||
}
|
||||
|
||||
return mergeAggregationResults(aggregateBatched(command, collectionName, batchSize));
|
||||
}
|
||||
|
||||
/**
|
||||
* Pre process the aggregation command sent to the server by adding {@code cursor} options to match execution on
|
||||
* different server versions.
|
||||
*/
|
||||
private static Document prepareAggregationCommand(String collectionName, Aggregation aggregation,
|
||||
@Nullable AggregationOperationContext context, int batchSize) {
|
||||
|
||||
AggregationOperationContext rootContext = context == null ? Aggregation.DEFAULT_CONTEXT : context;
|
||||
Document command = aggregation.toDocument(collectionName, rootContext);
|
||||
|
||||
if (!aggregation.getOptions().isExplain()) {
|
||||
command.put(CURSOR_FIELD, new Document(BATCH_SIZE_FIELD, batchSize));
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
private List<Document> aggregateBatched(Document command, String collectionName, int batchSize) {
|
||||
|
||||
List<Document> results = new ArrayList<>();
|
||||
|
||||
Document commandResult = template.executeCommand(command, readPreference);
|
||||
results.add(postProcessResult(commandResult));
|
||||
|
||||
while (hasNext(commandResult)) {
|
||||
|
||||
Document getMore = new Document("getMore", getNextBatchId(commandResult)) //
|
||||
.append("collection", collectionName) //
|
||||
.append(BATCH_SIZE_FIELD, batchSize);
|
||||
|
||||
commandResult = template.executeCommand(getMore, this.readPreference);
|
||||
results.add(postProcessResult(commandResult));
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private static Document postProcessResult(Document commandResult) {
|
||||
|
||||
if (!commandResult.containsKey(CURSOR_FIELD)) {
|
||||
return commandResult;
|
||||
}
|
||||
|
||||
Document resultObject = new Document(SERVER_USED, commandResult.get(SERVER_USED));
|
||||
resultObject.put(OK, commandResult.get(OK));
|
||||
|
||||
Document cursor = (Document) commandResult.get(CURSOR_FIELD);
|
||||
if (cursor.containsKey(FIRST_BATCH)) {
|
||||
resultObject.put(RESULT_FIELD, cursor.get(FIRST_BATCH));
|
||||
} else {
|
||||
resultObject.put(RESULT_FIELD, cursor.get(NEXT_BATCH));
|
||||
}
|
||||
|
||||
return resultObject;
|
||||
}
|
||||
|
||||
private static Document mergeAggregationResults(List<Document> batchResults) {
|
||||
|
||||
if (batchResults.size() == 1) {
|
||||
return batchResults.iterator().next();
|
||||
}
|
||||
|
||||
Document commandResult = new Document();
|
||||
List<Object> allResults = new ArrayList<>();
|
||||
|
||||
for (Document batchResult : batchResults) {
|
||||
|
||||
Collection documents = (Collection<?>) batchResult.get(RESULT_FIELD);
|
||||
if (!CollectionUtils.isEmpty(documents)) {
|
||||
allResults.addAll(documents);
|
||||
}
|
||||
}
|
||||
|
||||
// take general info from first batch
|
||||
commandResult.put(SERVER_USED, batchResults.iterator().next().get(SERVER_USED));
|
||||
commandResult.put(OK, batchResults.iterator().next().get(OK));
|
||||
|
||||
// and append the merged batchResults
|
||||
commandResult.put(RESULT_FIELD, allResults);
|
||||
|
||||
return commandResult;
|
||||
}
|
||||
|
||||
private static boolean hasNext(Document commandResult) {
|
||||
|
||||
if (!commandResult.containsKey(CURSOR_FIELD)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Object next = getNextBatchId(commandResult);
|
||||
return next != null && ((Number) next).longValue() != 0L;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static Object getNextBatchId(Document commandResult) {
|
||||
return ((Document) commandResult.get(CURSOR_FIELD)).get("id");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link MongoTemplate} extension bound to a specific {@link ClientSession} that is applied when interacting with the
|
||||
* server through the driver API.
|
||||
* <p />
|
||||
* The prepare steps for {@link MongoDatabase} and {@link MongoCollection} proxy the target and invoke the desired
|
||||
* target method matching the actual arguments plus a {@link ClientSession}.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.1
|
||||
*/
|
||||
static class SessionBoundMongoTemplate extends MongoTemplate {
|
||||
|
||||
private final MongoTemplate delegate;
|
||||
|
||||
/**
|
||||
* @param session must not be {@literal null}.
|
||||
* @param that must not be {@literal null}.
|
||||
*/
|
||||
SessionBoundMongoTemplate(ClientSession session, MongoTemplate that) {
|
||||
|
||||
super(that.getMongoDbFactory().withSession(session), that);
|
||||
|
||||
this.delegate = that;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.MongoTemplate#getCollection(java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public MongoCollection<Document> getCollection(String collectionName) {
|
||||
|
||||
// native MongoDB objects that offer methods with ClientSession must not be proxied.
|
||||
return delegate.getCollection(collectionName);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.MongoTemplate#getDb()
|
||||
*/
|
||||
@Override
|
||||
public MongoDatabase getDb() {
|
||||
|
||||
// native MongoDB objects that offer methods with ClientSession must not be proxied.
|
||||
return delegate.getDb();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,8 @@ import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.bson.Document;
|
||||
import org.reactivestreams.Publisher;
|
||||
@@ -31,16 +33,20 @@ 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;
|
||||
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 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;
|
||||
|
||||
/**
|
||||
@@ -141,6 +147,92 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
*/
|
||||
<T> Flux<T> execute(String collectionName, ReactiveCollectionCallback<T> action);
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* <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 sessionProvider must not be {@literal null}.
|
||||
* @return new instance of {@link ReactiveSessionScoped}. Never {@literal null}.
|
||||
* @since 2.1
|
||||
*/
|
||||
default ReactiveSessionScoped withSession(Supplier<ClientSession> sessionProvider) {
|
||||
|
||||
Assert.notNull(sessionProvider, "SessionProvider must not be null!");
|
||||
|
||||
return withSession(Mono.fromSupplier(sessionProvider));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 ReactiveSessionScoped}. Never {@literal null}.
|
||||
* @since 2.1
|
||||
*/
|
||||
ReactiveSessionScoped withSession(ClientSessionOptions sessionOptions);
|
||||
|
||||
/**
|
||||
* 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
|
||||
* {@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 ReactiveSessionScoped}. Never {@literal null}.
|
||||
* @since 2.1
|
||||
*/
|
||||
ReactiveSessionScoped withSession(Publisher<ClientSession> sessionProvider);
|
||||
|
||||
/**
|
||||
* Obtain a {@link ClientSession} bound instance of {@link ReactiveMongoOperations}.
|
||||
* <p />
|
||||
* <strong>Note:</strong> It is up to the caller to manage the {@link ClientSession} lifecycle.
|
||||
*
|
||||
* @param session must not be {@literal null}.
|
||||
* @return {@link ClientSession} bound instance of {@link ReactiveMongoOperations}.
|
||||
* @since 2.1
|
||||
*/
|
||||
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.
|
||||
*
|
||||
@@ -1153,6 +1245,42 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
<T> Flux<ChangeStreamEvent<T>> changeStream(List<Document> filter, Class<T> resultType, ChangeStreamOptions options,
|
||||
String collectionName);
|
||||
|
||||
/**
|
||||
* 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 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<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}.
|
||||
*
|
||||
|
||||
@@ -26,6 +26,8 @@ import reactor.util.function.Tuple2;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -37,6 +39,7 @@ import org.bson.codecs.Codec;
|
||||
import org.bson.conversions.Bson;
|
||||
import org.bson.types.ObjectId;
|
||||
import org.reactivestreams.Publisher;
|
||||
import org.reactivestreams.Subscriber;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.BeansException;
|
||||
@@ -57,10 +60,12 @@ import org.springframework.data.geo.Distance;
|
||||
import org.springframework.data.geo.GeoResult;
|
||||
import org.springframework.data.geo.Metric;
|
||||
import org.springframework.data.mapping.MappingException;
|
||||
import org.springframework.data.mapping.PersistentEntity;
|
||||
import org.springframework.data.mapping.PersistentPropertyAccessor;
|
||||
import org.springframework.data.mapping.PropertyPath;
|
||||
import org.springframework.data.mapping.PropertyReferenceException;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.mapping.context.MappingContextEvent;
|
||||
import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
|
||||
import org.springframework.data.mongodb.MongoDbFactory;
|
||||
import org.springframework.data.mongodb.ReactiveMongoDatabaseFactory;
|
||||
@@ -71,10 +76,9 @@ import org.springframework.data.mongodb.core.aggregation.PrefixingDelegatingAggr
|
||||
import org.springframework.data.mongodb.core.aggregation.TypeBasedAggregationOperationContext;
|
||||
import org.springframework.data.mongodb.core.aggregation.TypedAggregation;
|
||||
import org.springframework.data.mongodb.core.convert.*;
|
||||
import org.springframework.data.mongodb.core.index.IndexOperationsAdapter;
|
||||
import org.springframework.data.mongodb.core.index.MongoMappingEventPublisher;
|
||||
import org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexCreator;
|
||||
import org.springframework.data.mongodb.core.index.ReactiveIndexOperations;
|
||||
import org.springframework.data.mongodb.core.index.ReactiveMongoPersistentEntityIndexCreator;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
|
||||
@@ -87,6 +91,7 @@ import org.springframework.data.mongodb.core.mapping.event.BeforeConvertEvent;
|
||||
import org.springframework.data.mongodb.core.mapping.event.BeforeDeleteEvent;
|
||||
import org.springframework.data.mongodb.core.mapping.event.BeforeSaveEvent;
|
||||
import org.springframework.data.mongodb.core.mapping.event.MongoMappingEvent;
|
||||
import org.springframework.data.mongodb.core.mapreduce.MapReduceOptions;
|
||||
import org.springframework.data.mongodb.core.query.Collation;
|
||||
import org.springframework.data.mongodb.core.query.Criteria;
|
||||
import org.springframework.data.mongodb.core.query.NearQuery;
|
||||
@@ -101,10 +106,13 @@ import org.springframework.data.util.Pair;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.ResourceUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.mongodb.BasicDBObject;
|
||||
import com.mongodb.ClientSessionOptions;
|
||||
import com.mongodb.CursorType;
|
||||
import com.mongodb.DBCollection;
|
||||
import com.mongodb.DBCursor;
|
||||
@@ -113,10 +121,13 @@ import com.mongodb.Mongo;
|
||||
import com.mongodb.MongoException;
|
||||
import com.mongodb.ReadPreference;
|
||||
import com.mongodb.WriteConcern;
|
||||
import com.mongodb.client.model.CountOptions;
|
||||
import com.mongodb.client.model.CreateCollectionOptions;
|
||||
import com.mongodb.client.model.DeleteOptions;
|
||||
import com.mongodb.client.model.Filters;
|
||||
import com.mongodb.client.model.FindOneAndDeleteOptions;
|
||||
import com.mongodb.client.model.FindOneAndUpdateOptions;
|
||||
import com.mongodb.client.model.ReplaceOptions;
|
||||
import com.mongodb.client.model.ReturnDocument;
|
||||
import com.mongodb.client.model.UpdateOptions;
|
||||
import com.mongodb.client.model.ValidationOptions;
|
||||
@@ -125,8 +136,10 @@ import com.mongodb.client.result.DeleteResult;
|
||||
import com.mongodb.client.result.UpdateResult;
|
||||
import com.mongodb.reactivestreams.client.AggregatePublisher;
|
||||
import com.mongodb.reactivestreams.client.ChangeStreamPublisher;
|
||||
import com.mongodb.reactivestreams.client.ClientSession;
|
||||
import com.mongodb.reactivestreams.client.DistinctPublisher;
|
||||
import com.mongodb.reactivestreams.client.FindPublisher;
|
||||
import com.mongodb.reactivestreams.client.MapReducePublisher;
|
||||
import com.mongodb.reactivestreams.client.MongoClient;
|
||||
import com.mongodb.reactivestreams.client.MongoCollection;
|
||||
import com.mongodb.reactivestreams.client.MongoDatabase;
|
||||
@@ -175,13 +188,14 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
private final UpdateMapper updateMapper;
|
||||
private final JsonSchemaMapper schemaMapper;
|
||||
private final SpelAwareProxyProjectionFactory projectionFactory;
|
||||
private final ApplicationListener<MappingContextEvent<?, ?>> indexCreatorListener;
|
||||
|
||||
private @Nullable WriteConcern writeConcern;
|
||||
private WriteConcernResolver writeConcernResolver = DefaultWriteConcernResolver.INSTANCE;
|
||||
private WriteResultChecking writeResultChecking = WriteResultChecking.NONE;
|
||||
private @Nullable ReadPreference readPreference;
|
||||
private @Nullable ApplicationEventPublisher eventPublisher;
|
||||
private @Nullable MongoPersistentEntityIndexCreator indexCreator;
|
||||
private @Nullable ReactiveMongoPersistentEntityIndexCreator indexCreator;
|
||||
|
||||
/**
|
||||
* Constructor used for a basic template configuration.
|
||||
@@ -190,7 +204,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
* @param databaseName must not be {@literal null} or empty.
|
||||
*/
|
||||
public ReactiveMongoTemplate(MongoClient mongoClient, String databaseName) {
|
||||
this(new SimpleReactiveMongoDatabaseFactory(mongoClient, databaseName), null);
|
||||
this(new SimpleReactiveMongoDatabaseFactory(mongoClient, databaseName), (MongoConverter) null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -199,7 +213,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
* @param mongoDatabaseFactory must not be {@literal null}.
|
||||
*/
|
||||
public ReactiveMongoTemplate(ReactiveMongoDatabaseFactory mongoDatabaseFactory) {
|
||||
this(mongoDatabaseFactory, null);
|
||||
this(mongoDatabaseFactory, (MongoConverter) null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -210,6 +224,21 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
*/
|
||||
public ReactiveMongoTemplate(ReactiveMongoDatabaseFactory mongoDatabaseFactory,
|
||||
@Nullable MongoConverter mongoConverter) {
|
||||
this(mongoDatabaseFactory, mongoConverter, ReactiveMongoTemplate::handleSubscriptionException);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor used for a basic template configuration.
|
||||
*
|
||||
* @param mongoDatabaseFactory must not be {@literal null}.
|
||||
* @param mongoConverter can be {@literal null}.
|
||||
* @param subscriptionExceptionHandler exception handler called by {@link Flux#doOnError(Consumer)} on reactive type
|
||||
* materialization via {@link Publisher#subscribe(Subscriber)}. This callback is used during non-blocking
|
||||
* subscription of e.g. index creation {@link Publisher}s. Must not be {@literal null}.
|
||||
* @since 2.1
|
||||
*/
|
||||
public ReactiveMongoTemplate(ReactiveMongoDatabaseFactory mongoDatabaseFactory,
|
||||
@Nullable MongoConverter mongoConverter, Consumer<Throwable> subscriptionExceptionHandler) {
|
||||
|
||||
Assert.notNull(mongoDatabaseFactory, "ReactiveMongoDatabaseFactory must not be null!");
|
||||
|
||||
@@ -220,19 +249,47 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
this.updateMapper = new UpdateMapper(this.mongoConverter);
|
||||
this.schemaMapper = new MongoJsonSchemaMapper(this.mongoConverter);
|
||||
this.projectionFactory = new SpelAwareProxyProjectionFactory();
|
||||
this.indexCreatorListener = new IndexCreatorEventListener(subscriptionExceptionHandler);
|
||||
|
||||
// We always have a mapping context in the converter, whether it's a simple one or not
|
||||
mappingContext = this.mongoConverter.getMappingContext();
|
||||
// We create indexes based on mapping events
|
||||
this.mappingContext = this.mongoConverter.getMappingContext();
|
||||
|
||||
if (mappingContext instanceof MongoMappingContext) {
|
||||
indexCreator = new MongoPersistentEntityIndexCreator((MongoMappingContext) mappingContext,
|
||||
(collectionName) -> IndexOperationsAdapter.blocking(indexOps(collectionName)));
|
||||
eventPublisher = new MongoMappingEventPublisher(indexCreator);
|
||||
if (mappingContext instanceof ApplicationEventPublisherAware) {
|
||||
((ApplicationEventPublisherAware) mappingContext).setApplicationEventPublisher(eventPublisher);
|
||||
// We create indexes based on mapping events
|
||||
if (this.mappingContext instanceof MongoMappingContext) {
|
||||
|
||||
MongoMappingContext mongoMappingContext = (MongoMappingContext) this.mappingContext;
|
||||
this.indexCreator = new ReactiveMongoPersistentEntityIndexCreator(mongoMappingContext, this::indexOps);
|
||||
this.eventPublisher = new MongoMappingEventPublisher(this.indexCreatorListener);
|
||||
|
||||
mongoMappingContext.setApplicationEventPublisher(this.eventPublisher);
|
||||
this.mappingContext.getPersistentEntities()
|
||||
.forEach(entity -> onCheckForIndexes(entity, subscriptionExceptionHandler));
|
||||
}
|
||||
}
|
||||
|
||||
private ReactiveMongoTemplate(ReactiveMongoDatabaseFactory dbFactory, ReactiveMongoTemplate that) {
|
||||
|
||||
this.mongoDatabaseFactory = dbFactory;
|
||||
this.exceptionTranslator = that.exceptionTranslator;
|
||||
this.mongoConverter = that.mongoConverter;
|
||||
this.queryMapper = that.queryMapper;
|
||||
this.updateMapper = that.updateMapper;
|
||||
this.schemaMapper = that.schemaMapper;
|
||||
this.projectionFactory = that.projectionFactory;
|
||||
this.indexCreator = that.indexCreator;
|
||||
this.indexCreatorListener = that.indexCreatorListener;
|
||||
this.mappingContext = that.mappingContext;
|
||||
}
|
||||
|
||||
private void onCheckForIndexes(MongoPersistentEntity<?> entity, Consumer<Throwable> subscriptionExceptionHandler) {
|
||||
|
||||
if (indexCreator != null) {
|
||||
indexCreator.checkForIndexes(entity).subscribe(v -> {}, subscriptionExceptionHandler);
|
||||
}
|
||||
}
|
||||
|
||||
private static void handleSubscriptionException(Throwable t) {
|
||||
LOGGER.error("Unexpected exception during asynchronous execution", t);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -266,7 +323,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by @{link {@link #prepareCollection(MongoCollection)} to set the {@link ReadPreference} before any operations
|
||||
* Used by {@link {@link #prepareCollection(MongoCollection)} to set the {@link ReadPreference} before any operations
|
||||
* are performed.
|
||||
*
|
||||
* @param readPreference
|
||||
@@ -293,26 +350,28 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
}
|
||||
|
||||
/**
|
||||
* Inspects the given {@link ApplicationContext} for {@link MongoPersistentEntityIndexCreator} and those in turn if
|
||||
* they were registered for the current {@link MappingContext}. If no creator for the current {@link MappingContext}
|
||||
* can be found we manually add the internally created one as {@link ApplicationListener} to make sure indexes get
|
||||
* created appropriately for entity types persisted through this {@link ReactiveMongoTemplate} instance.
|
||||
* Inspects the given {@link ApplicationContext} for {@link ReactiveMongoPersistentEntityIndexCreator} and those in
|
||||
* turn if they were registered for the current {@link MappingContext}. If no creator for the current
|
||||
* {@link MappingContext} can be found we manually add the internally created one as {@link ApplicationListener} to
|
||||
* make sure indexes get created appropriately for entity types persisted through this {@link ReactiveMongoTemplate}
|
||||
* instance.
|
||||
*
|
||||
* @param context must not be {@literal null}.
|
||||
*/
|
||||
private void prepareIndexCreator(ApplicationContext context) {
|
||||
|
||||
String[] indexCreators = context.getBeanNamesForType(MongoPersistentEntityIndexCreator.class);
|
||||
String[] indexCreators = context.getBeanNamesForType(ReactiveMongoPersistentEntityIndexCreator.class);
|
||||
|
||||
for (String creator : indexCreators) {
|
||||
MongoPersistentEntityIndexCreator creatorBean = context.getBean(creator, MongoPersistentEntityIndexCreator.class);
|
||||
ReactiveMongoPersistentEntityIndexCreator creatorBean = context.getBean(creator,
|
||||
ReactiveMongoPersistentEntityIndexCreator.class);
|
||||
if (creatorBean.isIndexCreatorFor(mappingContext)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (context instanceof ConfigurableApplicationContext) {
|
||||
((ConfigurableApplicationContext) context).addApplicationListener(indexCreator);
|
||||
((ConfigurableApplicationContext) context).addApplicationListener(indexCreatorListener);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -402,9 +461,111 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
public <T> Flux<T> execute(String collectionName, ReactiveCollectionCallback<T> callback) {
|
||||
|
||||
Assert.notNull(callback, "ReactiveCollectionCallback must not be null!");
|
||||
|
||||
return createFlux(collectionName, callback);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.ReactiveMongoOperations#withSession(org.reactivestreams.Publisher, java.util.function.Consumer)
|
||||
*/
|
||||
@Override
|
||||
public ReactiveSessionScoped withSession(Publisher<ClientSession> sessionProvider) {
|
||||
|
||||
Mono<ClientSession> cachedSession = Mono.from(sessionProvider).cache();
|
||||
|
||||
return new ReactiveSessionScoped() {
|
||||
|
||||
@Override
|
||||
public <T> Flux<T> execute(ReactiveSessionCallback<T> action, Consumer<ClientSession> doFinally) {
|
||||
|
||||
return cachedSession.flatMapMany(session -> {
|
||||
|
||||
return ReactiveMongoTemplate.this.withSession(action, session) //
|
||||
.doFinally(signalType -> {
|
||||
doFinally.accept(session);
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.ReactiveMongoOperations#inTransaction()
|
||||
*/
|
||||
@Override
|
||||
public ReactiveSessionScoped inTransaction() {
|
||||
return inTransaction(mongoDatabaseFactory
|
||||
.getSession(ClientSessionOptions.builder().causallyConsistent(true).build()));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.ReactiveMongoOperations#inTransaction(org.reactivestreams.Publisher)
|
||||
*/
|
||||
@Override
|
||||
public ReactiveSessionScoped inTransaction(Publisher<ClientSession> sessionProvider) {
|
||||
|
||||
Mono<ClientSession> cachedSession = Mono.from(sessionProvider).cache();
|
||||
|
||||
return new ReactiveSessionScoped() {
|
||||
|
||||
@Override
|
||||
public <T> Flux<T> execute(ReactiveSessionCallback<T> action, Consumer<ClientSession> doFinally) {
|
||||
|
||||
return cachedSession.flatMapMany(session -> {
|
||||
|
||||
if (!session.hasActiveTransaction()) {
|
||||
session.startTransaction();
|
||||
}
|
||||
|
||||
return ReactiveMongoTemplate.this.withSession(action, session) //
|
||||
.materialize() //
|
||||
.flatMap(signal -> {
|
||||
|
||||
if (session.hasActiveTransaction()) {
|
||||
if (signal.isOnComplete()) {
|
||||
return Mono.from(session.commitTransaction()).thenReturn(signal);
|
||||
}
|
||||
if (signal.isOnError()) {
|
||||
return Mono.from(session.abortTransaction()).thenReturn(signal);
|
||||
}
|
||||
}
|
||||
return Mono.just(signal);
|
||||
}) //
|
||||
.<T> dematerialize() //
|
||||
.doFinally(signalType -> {
|
||||
doFinally.accept(session);
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private <T> Flux<T> withSession(ReactiveSessionCallback<T> action, ClientSession session) {
|
||||
|
||||
return Flux.from(action.doInSession(new ReactiveSessionBoundMongoTemplate(session, ReactiveMongoTemplate.this))) //
|
||||
.subscriberContext(ctx -> ReactiveMongoContext.setSession(ctx, Mono.just(session)));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.ReactiveMongoOperations#withSession(com.mongodb.session.ClientSession)
|
||||
*/
|
||||
public ReactiveMongoOperations withSession(ClientSession session) {
|
||||
return new ReactiveSessionBoundMongoTemplate(session, ReactiveMongoTemplate.this);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.ReactiveMongoOperations#withSession(com.mongodb.ClientSessionOptions)
|
||||
*/
|
||||
@Override
|
||||
public ReactiveSessionScoped withSession(ClientSessionOptions sessionOptions) {
|
||||
return withSession(mongoDatabaseFactory.getSession(sessionOptions));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a reusable Flux for a {@link ReactiveDatabaseCallback}. It's up to the developer to choose to obtain a new
|
||||
* {@link Flux} or to reuse the {@link Flux}.
|
||||
@@ -416,7 +577,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
|
||||
Assert.notNull(callback, "ReactiveDatabaseCallback must not be null!");
|
||||
|
||||
return Flux.defer(() -> callback.doInDB(getMongoDatabase())).onErrorMap(translateException());
|
||||
return Flux.defer(() -> callback.doInDB(prepareDatabase(doGetDatabase()))).onErrorMap(translateException());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -430,7 +591,8 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
|
||||
Assert.notNull(callback, "ReactiveDatabaseCallback must not be null!");
|
||||
|
||||
return Mono.defer(() -> Mono.from(callback.doInDB(getMongoDatabase()))).onErrorMap(translateException());
|
||||
return Mono.defer(() -> Mono.from(callback.doInDB(prepareDatabase(doGetDatabase()))))
|
||||
.onErrorMap(translateException());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -446,7 +608,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
Assert.notNull(callback, "ReactiveDatabaseCallback must not be null!");
|
||||
|
||||
Mono<MongoCollection<Document>> collectionPublisher = Mono
|
||||
.fromCallable(() -> getAndPrepareCollection(getMongoDatabase(), collectionName));
|
||||
.fromCallable(() -> getAndPrepareCollection(doGetDatabase(), collectionName));
|
||||
|
||||
return collectionPublisher.flatMapMany(callback::doInCollection).onErrorMap(translateException());
|
||||
}
|
||||
@@ -465,7 +627,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
Assert.notNull(callback, "ReactiveCollectionCallback must not be null!");
|
||||
|
||||
Mono<MongoCollection<Document>> collectionPublisher = Mono
|
||||
.fromCallable(() -> getAndPrepareCollection(getMongoDatabase(), collectionName));
|
||||
.fromCallable(() -> getAndPrepareCollection(doGetDatabase(), collectionName));
|
||||
|
||||
return collectionPublisher.flatMap(collection -> Mono.from(callback.doInCollection(collection)))
|
||||
.onErrorMap(translateException());
|
||||
@@ -547,7 +709,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
*/
|
||||
public Mono<Void> dropCollection(final String collectionName) {
|
||||
|
||||
return createMono(db -> db.getCollection(collectionName).drop()).doOnSuccess(success -> {
|
||||
return createMono(collectionName, collection -> collection.drop()).doOnSuccess(success -> {
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("Dropped collection [" + collectionName + "]");
|
||||
}
|
||||
@@ -563,6 +725,10 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
}
|
||||
|
||||
public MongoDatabase getMongoDatabase() {
|
||||
return doGetDatabase();
|
||||
}
|
||||
|
||||
protected MongoDatabase doGetDatabase() {
|
||||
return mongoDatabaseFactory.getMongoDatabase();
|
||||
}
|
||||
|
||||
@@ -618,7 +784,8 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
return createFlux(collectionName, collection -> {
|
||||
|
||||
Document mappedQuery = queryMapper.getMappedObject(query.getQueryObject(), getPersistentEntity(entityClass));
|
||||
FindPublisher<Document> findPublisher = collection.find(mappedQuery).projection(new Document("_id", 1));
|
||||
FindPublisher<Document> findPublisher = collection.find(mappedQuery, Document.class)
|
||||
.projection(new Document("_id", 1));
|
||||
|
||||
findPublisher = query.getCollation().map(Collation::toMongoCollation).map(findPublisher::collation)
|
||||
.orElse(findPublisher);
|
||||
@@ -820,8 +987,8 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
private <O> Flux<O> aggregateAndMap(MongoCollection<Document> collection, List<Document> pipeline,
|
||||
AggregationOptions options, ReadDocumentCallback<O> readCallback) {
|
||||
|
||||
AggregatePublisher<Document> cursor = collection.aggregate(pipeline).allowDiskUse(options.isAllowDiskUse())
|
||||
.useCursor(true);
|
||||
AggregatePublisher<Document> cursor = collection.aggregate(pipeline, Document.class)
|
||||
.allowDiskUse(options.isAllowDiskUse()).useCursor(true);
|
||||
|
||||
if (options.getCollation().isPresent()) {
|
||||
cursor = cursor.collation(options.getCollation().map(Collation::toMongoCollation).get());
|
||||
@@ -987,7 +1154,12 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
: queryMapper.getMappedObject(query.getQueryObject(),
|
||||
entityClass == null ? null : mappingContext.getPersistentEntity(entityClass));
|
||||
|
||||
return collection.count(Document);
|
||||
CountOptions options = new CountOptions();
|
||||
if (query != null) {
|
||||
query.getCollation().map(Collation::toMongoCollation).ifPresent(options::collation);
|
||||
}
|
||||
|
||||
return collection.count(Document, options);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1361,10 +1533,10 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
}
|
||||
} else if (writeConcernToUse == null) {
|
||||
publisher = collection.replaceOne(Filters.eq(ID_FIELD, document.get(ID_FIELD)), document,
|
||||
new UpdateOptions().upsert(true));
|
||||
new ReplaceOptions().upsert(true));
|
||||
} else {
|
||||
publisher = collection.withWriteConcern(writeConcernToUse)
|
||||
.replaceOne(Filters.eq(ID_FIELD, document.get(ID_FIELD)), document, new UpdateOptions().upsert(true));
|
||||
.replaceOne(Filters.eq(ID_FIELD, document.get(ID_FIELD)), document, new ReplaceOptions().upsert(true));
|
||||
}
|
||||
|
||||
return Mono.from(publisher).map(o -> document.get(ID_FIELD));
|
||||
@@ -1471,7 +1643,12 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
query.getCollation().map(Collation::toMongoCollation).ifPresent(updateOptions::collation);
|
||||
|
||||
if (!UpdateMapper.isUpdateObject(updateObj)) {
|
||||
return collectionToUse.replaceOne(queryObj, updateObj, updateOptions);
|
||||
|
||||
ReplaceOptions replaceOptions = new ReplaceOptions();
|
||||
replaceOptions.upsert(updateOptions.isUpsert());
|
||||
replaceOptions.collation(updateOptions.getCollation());
|
||||
|
||||
return collectionToUse.replaceOne(queryObj, updateObj, replaceOptions);
|
||||
}
|
||||
if (multi) {
|
||||
return collectionToUse.updateMany(queryObj, updateObj, updateOptions);
|
||||
@@ -1680,27 +1857,39 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
|
||||
return execute(collectionName, collection -> {
|
||||
|
||||
maybeEmitEvent(new BeforeDeleteEvent<T>(queryObject, entityClass, collectionName));
|
||||
Document removeQuey = queryMapper.getMappedObject(queryObject, entity);
|
||||
|
||||
Document dboq = queryMapper.getMappedObject(queryObject, entity);
|
||||
maybeEmitEvent(new BeforeDeleteEvent<T>(removeQuey, entityClass, collectionName));
|
||||
|
||||
MongoAction mongoAction = new MongoAction(writeConcern, MongoActionOperation.REMOVE, collectionName, entityClass,
|
||||
null, queryObject);
|
||||
null, removeQuey);
|
||||
|
||||
final DeleteOptions deleteOptions = new DeleteOptions();
|
||||
query.getCollation().map(Collation::toMongoCollation).ifPresent(deleteOptions::collation);
|
||||
|
||||
WriteConcern writeConcernToUse = prepareWriteConcern(mongoAction);
|
||||
MongoCollection<Document> collectionToUse = prepareCollection(collection, writeConcernToUse);
|
||||
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("Remove using query: {} in collection: {}.",
|
||||
new Object[] { serializeToJsonSafely(dboq), collectionName });
|
||||
new Object[] { serializeToJsonSafely(removeQuey), collectionName });
|
||||
}
|
||||
|
||||
query.getCollation().ifPresent(val -> {
|
||||
if (query.getLimit() > 0 || query.getSkip() > 0) {
|
||||
|
||||
// TODO: add collation support as soon as it's there! See https://jira.mongodb.org/browse/JAVARS-27
|
||||
throw new IllegalArgumentException("DeleteMany does currently not accept collation settings.");
|
||||
FindPublisher<Document> cursor = new QueryFindPublisherPreparer(query, entityClass)
|
||||
.prepare(collection.find(removeQuey)) //
|
||||
.projection(new Document(ID_FIELD, 1));
|
||||
|
||||
return Flux.from(cursor) //
|
||||
.map(doc -> doc.get(ID_FIELD)) //
|
||||
.collectList() //
|
||||
.flatMapMany(val -> {
|
||||
return collectionToUse.deleteMany(new Document(ID_FIELD, new Document("$in", val)), deleteOptions);
|
||||
});
|
||||
|
||||
return collectionToUse.deleteMany(dboq);
|
||||
} else {
|
||||
return collectionToUse.deleteMany(removeQuey, deleteOptions);
|
||||
}
|
||||
|
||||
}).doOnNext(deleteResult -> maybeEmitEvent(new AfterDeleteEvent<T>(queryObject, entityClass, collectionName)))
|
||||
.next();
|
||||
@@ -1832,6 +2021,115 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
return Flux.from(publisher).map(document -> new ChangeStreamEvent<>(document, resultType, getConverter()));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.ReactiveMongoOperations#mapReduce(org.springframework.data.mongodb.core.query.Query, java.lang.Class, java.lang.Class, java.lang.String, java.lang.String, org.springframework.data.mongodb.core.mapreduce.MapReduceOptions)
|
||||
*/
|
||||
public <T> Flux<T> mapReduce(Query filterQuery, Class<?> domainType, Class<T> resultType, String mapFunction,
|
||||
String reduceFunction, MapReduceOptions options) {
|
||||
|
||||
return mapReduce(filterQuery, domainType, determineCollectionName(domainType), resultType, mapFunction,
|
||||
reduceFunction, options);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.ReactiveMongoOperations#mapReduce(org.springframework.data.mongodb.core.query.Query, java.lang.Class, java.lang.String, java.lang.Class, java.lang.String, java.lang.String, org.springframework.data.mongodb.core.mapreduce.MapReduceOptions)
|
||||
*/
|
||||
public <T> Flux<T> mapReduce(Query filterQuery, Class<?> domainType, String inputCollectionName, Class<T> resultType,
|
||||
String mapFunction, String reduceFunction, MapReduceOptions options) {
|
||||
|
||||
Assert.notNull(filterQuery, "Filter query must not be null!");
|
||||
Assert.notNull(domainType, "Domain type must not be null!");
|
||||
Assert.hasText(inputCollectionName, "Input collection name must not be null or empty!");
|
||||
Assert.notNull(resultType, "Result type must not be null!");
|
||||
Assert.notNull(mapFunction, "Map function must not be null!");
|
||||
Assert.notNull(reduceFunction, "Reduce function must not be null!");
|
||||
Assert.notNull(options, "MapReduceOptions must not be null!");
|
||||
|
||||
assertLocalFunctionNames(mapFunction, reduceFunction);
|
||||
|
||||
return createFlux(inputCollectionName, collection -> {
|
||||
|
||||
Document mappedQuery = queryMapper.getMappedObject(filterQuery.getQueryObject(),
|
||||
mappingContext.getPersistentEntity(domainType));
|
||||
|
||||
MapReducePublisher<Document> publisher = collection.mapReduce(mapFunction, reduceFunction, Document.class);
|
||||
|
||||
if (StringUtils.hasText(options.getOutputCollection())) {
|
||||
publisher = publisher.collectionName(options.getOutputCollection());
|
||||
}
|
||||
|
||||
publisher.filter(mappedQuery);
|
||||
publisher.sort(getMappedSortObject(filterQuery, domainType));
|
||||
|
||||
if (filterQuery.getMeta().getMaxTimeMsec() != null) {
|
||||
publisher.maxTime(filterQuery.getMeta().getMaxTimeMsec(), TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
if (filterQuery.getLimit() > 0 || (options.getLimit() != null)) {
|
||||
|
||||
if (filterQuery.getLimit() > 0 && (options.getLimit() != null)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Both Query and MapReduceOptions define a limit. Please provide the limit only via one of the two.");
|
||||
}
|
||||
|
||||
if (filterQuery.getLimit() > 0) {
|
||||
publisher.limit(filterQuery.getLimit());
|
||||
}
|
||||
|
||||
if (options.getLimit() != null) {
|
||||
publisher.limit(options.getLimit());
|
||||
}
|
||||
}
|
||||
|
||||
Optional<Collation> collation = filterQuery.getCollation();
|
||||
|
||||
Optionals.ifAllPresent(filterQuery.getCollation(), options.getCollation(), (l, r) -> {
|
||||
throw new IllegalArgumentException(
|
||||
"Both Query and MapReduceOptions define a collation. Please provide the collation only via one of the two.");
|
||||
});
|
||||
|
||||
if (options.getCollation().isPresent()) {
|
||||
collation = options.getCollation();
|
||||
}
|
||||
|
||||
if (!CollectionUtils.isEmpty(options.getScopeVariables())) {
|
||||
publisher = publisher.scope(new Document(options.getScopeVariables()));
|
||||
}
|
||||
if (options.getLimit() != null && options.getLimit() > 0) {
|
||||
publisher = publisher.limit(options.getLimit());
|
||||
}
|
||||
if (options.getFinalizeFunction().filter(StringUtils::hasText).isPresent()) {
|
||||
publisher = publisher.finalizeFunction(options.getFinalizeFunction().get());
|
||||
}
|
||||
if (options.getJavaScriptMode() != null) {
|
||||
publisher = publisher.jsMode(options.getJavaScriptMode());
|
||||
}
|
||||
if (options.getOutputSharded().isPresent()) {
|
||||
publisher = publisher.sharded(options.getOutputSharded().get());
|
||||
}
|
||||
|
||||
publisher = collation.map(Collation::toMongoCollation).map(publisher::collation).orElse(publisher);
|
||||
|
||||
return Flux.from(publisher)
|
||||
.map(new ReadDocumentCallback<>(mongoConverter, resultType, inputCollectionName)::doWith);
|
||||
});
|
||||
}
|
||||
|
||||
private static void assertLocalFunctionNames(String... functions) {
|
||||
|
||||
for (String function : functions) {
|
||||
|
||||
if (ResourceUtils.isUrl(function)) {
|
||||
|
||||
throw new IllegalArgumentException(String.format(
|
||||
"Blocking accessing to resource %s is not allowed using reactive infrastructure. You may load the resource at startup and cache its value.",
|
||||
function));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.ReactiveFindOperation#query(java.lang.Class)
|
||||
@@ -1877,6 +2175,15 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
return new ReactiveAggregationOperationSupport(this).aggregateAndReturn(domainType);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.ReactiveMapReduceOperation#mapReduce(java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public <T> ReactiveMapReduce<T> mapReduce(Class<T> domainType) {
|
||||
return new ReactiveMapReduceOperationSupport(this).mapReduce(domainType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve and remove all documents matching the given {@code query} by calling {@link #find(Query, Class, String)}
|
||||
* and {@link #remove(Query, Class, String)}, whereas the {@link Query} for {@link #remove(Query, Class, String)} is
|
||||
@@ -2152,15 +2459,18 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
* @param savedObject
|
||||
* @param id
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private void populateIdIfNecessary(Object savedObject, @Nullable Object id) {
|
||||
|
||||
if (id == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (savedObject instanceof Document) {
|
||||
Document Document = (Document) savedObject;
|
||||
Document.put(ID_FIELD, id);
|
||||
if (savedObject instanceof Map) {
|
||||
|
||||
Map<String, Object> map = (Map<String, Object>) savedObject;
|
||||
map.put(ID_FIELD, id);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2184,7 +2494,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
private MongoCollection<Document> getAndPrepareCollection(MongoDatabase db, String collectionName) {
|
||||
|
||||
try {
|
||||
MongoCollection<Document> collection = db.getCollection(collectionName);
|
||||
MongoCollection<Document> collection = db.getCollection(collectionName, Document.class);
|
||||
return prepareCollection(collection);
|
||||
} catch (RuntimeException e) {
|
||||
throw potentiallyConvertRuntimeException(e, exceptionTranslator);
|
||||
@@ -2222,6 +2532,15 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
return collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param database
|
||||
* @return
|
||||
* @since 2.1
|
||||
*/
|
||||
protected MongoDatabase prepareDatabase(MongoDatabase database) {
|
||||
return database;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the WriteConcern before any processing is done using it. This allows a convenient way to apply custom
|
||||
* settings in sub-classes. <br />
|
||||
@@ -2310,7 +2629,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
Assert.notNull(action, "MongoDatabaseCallback must not be null!");
|
||||
|
||||
try {
|
||||
MongoDatabase db = this.getMongoDatabase();
|
||||
MongoDatabase db = this.doGetDatabase();
|
||||
return action.doInDatabase(db);
|
||||
} catch (RuntimeException e) {
|
||||
throw potentiallyConvertRuntimeException(e, exceptionTranslator);
|
||||
@@ -2471,7 +2790,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
public Publisher<Document> doInCollection(MongoCollection<Document> collection)
|
||||
throws MongoException, DataAccessException {
|
||||
|
||||
FindPublisher<Document> publisher = collection.find(query);
|
||||
FindPublisher<Document> publisher = collection.find(query, Document.class);
|
||||
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
|
||||
@@ -2513,13 +2832,13 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
public FindPublisher<Document> doInCollection(MongoCollection<Document> collection) {
|
||||
|
||||
FindPublisher<Document> findPublisher;
|
||||
if (query == null || query.isEmpty()) {
|
||||
findPublisher = collection.find();
|
||||
if (ObjectUtils.isEmpty(query)) {
|
||||
findPublisher = collection.find(Document.class);
|
||||
} else {
|
||||
findPublisher = collection.find(query);
|
||||
findPublisher = collection.find(query, Document.class);
|
||||
}
|
||||
|
||||
if (fields == null || fields.isEmpty()) {
|
||||
if (ObjectUtils.isEmpty(fields)) {
|
||||
return findPublisher;
|
||||
} else {
|
||||
return findPublisher.projection(fields);
|
||||
@@ -2745,7 +3064,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
private final Metric metric;
|
||||
|
||||
/**
|
||||
* Creates a new {@link GeoNearResultDbObjectCallback} using the given {@link DbObjectCallback} delegate for
|
||||
* Creates a new {@link GeoNearResultDbObjectCallback} using the given {@link DocumentCallback} delegate for
|
||||
* {@link GeoResult} content unmarshalling.
|
||||
*
|
||||
* @param delegate must not be {@literal null}.
|
||||
@@ -2856,6 +3175,10 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
*/
|
||||
static class NoOpDbRefResolver implements DbRefResolver {
|
||||
|
||||
/*
|
||||
* (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)
|
||||
*/
|
||||
@Override
|
||||
@Nullable
|
||||
public Object resolveDbRef(@Nonnull MongoPersistentProperty property, @Nonnull DBRef dbref,
|
||||
@@ -2863,6 +3186,10 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
return null;
|
||||
}
|
||||
|
||||
/*
|
||||
* (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
|
||||
@Nullable
|
||||
public DBRef createDbRef(org.springframework.data.mongodb.core.mapping.DBRef annotation,
|
||||
@@ -2870,14 +3197,91 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
return null;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.convert.DbRefResolver#fetch(com.mongodb.DBRef)
|
||||
*/
|
||||
@Override
|
||||
public Document fetch(DBRef dbRef) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.convert.DbRefResolver#bulkFetch(java.util.List)
|
||||
*/
|
||||
@Override
|
||||
public List<Document> bulkFetch(List<DBRef> dbRefs) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link MongoTemplate} extension bound to a specific {@link ClientSession} that is applied when interacting with the
|
||||
* server through the driver API.
|
||||
* <p />
|
||||
* The prepare steps for {@link MongoDatabase} and {@link MongoCollection} proxy the target and invoke the desired
|
||||
* target method matching the actual arguments plus a {@link ClientSession}.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.1
|
||||
*/
|
||||
static class ReactiveSessionBoundMongoTemplate extends ReactiveMongoTemplate {
|
||||
|
||||
private final ReactiveMongoTemplate delegate;
|
||||
|
||||
/**
|
||||
* @param session must not be {@literal null}.
|
||||
* @param that must not be {@literal null}.
|
||||
*/
|
||||
ReactiveSessionBoundMongoTemplate(ClientSession session, ReactiveMongoTemplate that) {
|
||||
|
||||
super(that.mongoDatabaseFactory.withSession(session), that);
|
||||
|
||||
this.delegate = that;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.ReactiveMongoTemplate#getCollection(java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public MongoCollection<Document> getCollection(String collectionName) {
|
||||
|
||||
// native MongoDB objects that offer methods with ClientSession must not be proxied.
|
||||
return delegate.getCollection(collectionName);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.ReactiveMongoTemplate#getMongoDatabase()
|
||||
*/
|
||||
@Override
|
||||
public MongoDatabase getMongoDatabase() {
|
||||
|
||||
// native MongoDB objects that offer methods with ClientSession must not be proxied.
|
||||
return delegate.getMongoDatabase();
|
||||
}
|
||||
}
|
||||
|
||||
@RequiredArgsConstructor
|
||||
class IndexCreatorEventListener implements ApplicationListener<MappingContextEvent<?, ?>> {
|
||||
|
||||
final Consumer<Throwable> subscriptionExceptionHandler;
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(MappingContextEvent<?, ?> event) {
|
||||
|
||||
if (!event.wasEmittedBy(mappingContext)) {
|
||||
return;
|
||||
}
|
||||
|
||||
PersistentEntity<?, ?> entity = event.getPersistentEntity();
|
||||
|
||||
// Double check type as Spring infrastructure does not consider nested generics
|
||||
if (entity instanceof MongoPersistentEntity) {
|
||||
onCheckForIndexes((MongoPersistentEntity<?>) entity, subscriptionExceptionHandler);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.core;
|
||||
|
||||
import org.reactivestreams.Publisher;
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
|
||||
/**
|
||||
* 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.reactivestreams.client.ClientSession
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface ReactiveSessionCallback<T> {
|
||||
|
||||
/**
|
||||
* Execute operations against a MongoDB instance via session bound {@link ReactiveMongoOperations}. The session is
|
||||
* inferred directly into the operation so that no further interaction is necessary.
|
||||
* <p />
|
||||
* Please note that only Spring Data-specific abstractions like {@link ReactiveMongoOperations#find(Query, Class)} and
|
||||
* others are enhanced with the {@link com.mongodb.session.ClientSession}. When obtaining plain MongoDB gateway
|
||||
* objects like {@link com.mongodb.reactivestreams.client.MongoCollection} or
|
||||
* {@link com.mongodb.reactivestreams.client.MongoDatabase} via eg.
|
||||
* {@link ReactiveMongoOperations#getCollection(String)} we leave responsibility for
|
||||
* {@link com.mongodb.session.ClientSession} again up to the caller.
|
||||
*
|
||||
* @param operations will never be {@literal null}.
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
Publisher<T> doInSession(ReactiveMongoOperations operations);
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* 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 java.util.function.Consumer;
|
||||
|
||||
import com.mongodb.reactivestreams.client.ClientSession;
|
||||
|
||||
/**
|
||||
* Gateway interface to execute {@link ClientSession} bound operations against MongoDB via a
|
||||
* {@link ReactiveSessionCallback}.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
* @since 2.1
|
||||
*/
|
||||
public interface ReactiveSessionScoped {
|
||||
|
||||
/**
|
||||
* Executes the given {@link ReactiveSessionCallback} within the {@link com.mongodb.session.ClientSession}.
|
||||
* <p/>
|
||||
* It is up to the caller to make sure the {@link com.mongodb.session.ClientSession} is {@link ClientSession#close()
|
||||
* closed} when done.
|
||||
*
|
||||
* @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 {@link Flux#empty()}.
|
||||
*/
|
||||
default <T> Flux<T> execute(ReactiveSessionCallback<T> action) {
|
||||
return execute(action, (session) -> {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the given {@link ReactiveSessionCallback} within the {@link com.mongodb.session.ClientSession}.
|
||||
* <p/>
|
||||
* It is up to the caller to make sure the {@link com.mongodb.session.ClientSession} is {@link ClientSession#close()
|
||||
* closed} when done.
|
||||
*
|
||||
* @param action callback object that specifies the MongoDB action the callback action. Must not be {@literal null}.
|
||||
* @param doFinally callback object that accepts {@link ClientSession} after invoking {@link ReactiveSessionCallback}.
|
||||
* 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 {@link Flux#empty()}.
|
||||
*/
|
||||
<T> Flux<T> execute(ReactiveSessionCallback<T> action, Consumer<ClientSession> doFinally);
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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.data.mongodb.core.query.Query;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Callback interface for executing operations within a {@link com.mongodb.session.ClientSession}.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.1
|
||||
* @see com.mongodb.session.ClientSession
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface SessionCallback<T> {
|
||||
|
||||
/**
|
||||
* Execute operations against a MongoDB instance via session bound {@link MongoOperations}. The session is inferred
|
||||
* directly into the operation so that no further interaction is necessary.
|
||||
* <p />
|
||||
* Please note that only Spring Data-specific abstractions like {@link MongoOperations#find(Query, Class)} and others
|
||||
* are enhanced with the {@link com.mongodb.session.ClientSession}. When obtaining plain MongoDB gateway objects like
|
||||
* {@link com.mongodb.client.MongoCollection} or {@link com.mongodb.client.MongoDatabase} via eg.
|
||||
* {@link MongoOperations#getCollection(String)} we leave responsibility for {@link com.mongodb.session.ClientSession}
|
||||
* again up to the caller.
|
||||
*
|
||||
* @param operations will never be {@literal null}.
|
||||
* @return can be {@literal null}.
|
||||
*/
|
||||
@Nullable
|
||||
T doInSession(MongoOperations operations);
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
import com.mongodb.client.ClientSession;
|
||||
|
||||
/**
|
||||
* Gateway interface to execute {@link ClientSession} bound operations against MongoDB via a {@link SessionCallback}.
|
||||
* <p />
|
||||
* The very same bound {@link ClientSession} is used for all invocations of {@code execute} on the instance.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
* @since 2.1
|
||||
*/
|
||||
public interface SessionScoped {
|
||||
|
||||
/**
|
||||
* Executes the given {@link SessionCallback} within the {@link com.mongodb.session.ClientSession}.
|
||||
* <p/>
|
||||
* It is up to the caller to make sure the {@link com.mongodb.session.ClientSession} is {@link ClientSession#close()
|
||||
* closed} when done.
|
||||
*
|
||||
* @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}.
|
||||
*/
|
||||
@Nullable
|
||||
default <T> T execute(SessionCallback<T> action) {
|
||||
return execute(action, session -> {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the given {@link SessionCallback} within the {@link com.mongodb.session.ClientSession}.
|
||||
* <p/>
|
||||
* It is up to the caller to make sure the {@link com.mongodb.session.ClientSession} is {@link ClientSession#close()
|
||||
* closed} when done.
|
||||
*
|
||||
* @param action callback object that specifies the MongoDB action the callback action. Must not be {@literal null}.
|
||||
* @param doFinally callback object that accepts {@link ClientSession} after invoking {@link SessionCallback}. This
|
||||
* {@link Consumer} is guaranteed to be notified in any case (successful and exceptional outcome of
|
||||
* {@link SessionCallback}).
|
||||
* @param <T> return type.
|
||||
* @return a result object returned by the action. Can be {@literal null}.
|
||||
*/
|
||||
@Nullable
|
||||
<T> T execute(SessionCallback<T> action, Consumer<ClientSession> doFinally);
|
||||
}
|
||||
@@ -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,43 +15,33 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core;
|
||||
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
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.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;
|
||||
|
||||
/**
|
||||
* Factory to create {@link DB} instances from a {@link MongoClient} instance.
|
||||
* Factory to create {@link MongoDatabase} instances from a {@link MongoClient} instance.
|
||||
*
|
||||
* @author Mark Pollack
|
||||
* @author Oliver Gierke
|
||||
* @author Thomas Darimont
|
||||
* @author Christoph Strobl
|
||||
* @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}.
|
||||
* @throws UnknownHostException
|
||||
* @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) {
|
||||
@@ -62,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) {
|
||||
@@ -70,81 +60,48 @@ public class SimpleMongoDbFactory implements DisposableBean, MongoDbFactory {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param client
|
||||
* @param mongoClient
|
||||
* @param databaseName
|
||||
* @param mongoInstanceCreated
|
||||
* @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("[\\w-]+"),
|
||||
"Database name must only contain letters, numbers, underscores and dashes!");
|
||||
|
||||
this.mongoClient = mongoClient;
|
||||
this.databaseName = databaseName;
|
||||
this.mongoInstanceCreated = mongoInstanceCreated;
|
||||
this.exceptionTranslator = 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;
|
||||
super(mongoClient, databaseName, mongoInstanceCreated, new MongoExceptionTranslator());
|
||||
}
|
||||
|
||||
/*
|
||||
* (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());
|
||||
}
|
||||
|
||||
/*
|
||||
* (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,19 +15,25 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core;
|
||||
|
||||
import java.net.UnknownHostException;
|
||||
import lombok.Value;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
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.ReactiveMongoDatabaseFactory;
|
||||
import org.springframework.data.mongodb.SessionAwareMethodInterceptor;
|
||||
import org.springframework.lang.Nullable;
|
||||
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;
|
||||
|
||||
/**
|
||||
@@ -51,9 +57,8 @@ public class SimpleReactiveMongoDatabaseFactory implements DisposableBean, React
|
||||
* Creates a new {@link SimpleReactiveMongoDatabaseFactory} instance from the given {@link ConnectionString}.
|
||||
*
|
||||
* @param connectionString must not be {@literal null}.
|
||||
* @throws UnknownHostException
|
||||
*/
|
||||
public SimpleReactiveMongoDatabaseFactory(ConnectionString connectionString) throws UnknownHostException {
|
||||
public SimpleReactiveMongoDatabaseFactory(ConnectionString connectionString) {
|
||||
this(MongoClients.create(connectionString), connectionString.getDatabase(), true);
|
||||
}
|
||||
|
||||
@@ -72,8 +77,8 @@ public class SimpleReactiveMongoDatabaseFactory implements DisposableBean, React
|
||||
|
||||
Assert.notNull(client, "MongoClient must not be null!");
|
||||
Assert.hasText(databaseName, "Database name must not be empty!");
|
||||
Assert.isTrue(databaseName.matches("[\\w-]+"),
|
||||
"Database name must only contain letters, numbers, underscores and dashes!");
|
||||
Assert.isTrue(databaseName.matches("[^/\\\\.$\"\\s]+"),
|
||||
"Database name must not contain slashes, dots, spaces, quotes, or dollar signs!");
|
||||
|
||||
this.mongo = client;
|
||||
this.databaseName = databaseName;
|
||||
@@ -129,4 +134,106 @@ public class SimpleReactiveMongoDatabaseFactory implements DisposableBean, React
|
||||
public PersistenceExceptionTranslator getExceptionTranslator() {
|
||||
return this.exceptionTranslator;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.ReactiveMongoDbFactory#getSession(com.mongodb.ClientSessionOptions)
|
||||
*/
|
||||
@Override
|
||||
public Mono<ClientSession> getSession(ClientSessionOptions options) {
|
||||
return Mono.from(mongo.startSession(options));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.ReactiveMongoDbFactory#withSession(com.mongodb.session.ClientSession)
|
||||
*/
|
||||
@Override
|
||||
public ReactiveMongoDatabaseFactory withSession(ClientSession session) {
|
||||
return new ClientSessionBoundMongoDbFactory(session, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link ClientSession} bound {@link ReactiveMongoDatabaseFactory} decorating the database with a
|
||||
* {@link SessionAwareMethodInterceptor}.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.1
|
||||
*/
|
||||
@Value
|
||||
static class ClientSessionBoundMongoDbFactory implements ReactiveMongoDatabaseFactory {
|
||||
|
||||
ClientSession session;
|
||||
ReactiveMongoDatabaseFactory delegate;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.ReactiveMongoDatabaseFactory#getMongoDatabase()
|
||||
*/
|
||||
@Override
|
||||
public MongoDatabase getMongoDatabase() throws DataAccessException {
|
||||
return decorateDatabase(delegate.getMongoDatabase());
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.ReactiveMongoDatabaseFactory#getMongoDatabase(java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public MongoDatabase getMongoDatabase(String dbName) throws DataAccessException {
|
||||
return decorateDatabase(delegate.getMongoDatabase(dbName));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.ReactiveMongoDatabaseFactory#getExceptionTranslator()
|
||||
*/
|
||||
@Override
|
||||
public PersistenceExceptionTranslator getExceptionTranslator() {
|
||||
return delegate.getExceptionTranslator();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.ReactiveMongoDatabaseFactory#getSession(com.mongodb.ClientSessionOptions)
|
||||
*/
|
||||
@Override
|
||||
public Mono<ClientSession> getSession(ClientSessionOptions options) {
|
||||
return delegate.getSession(options);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.ReactiveMongoDatabaseFactory#withSession(com.mongodb.session.ClientSession)
|
||||
*/
|
||||
@Override
|
||||
public ReactiveMongoDatabaseFactory withSession(ClientSession session) {
|
||||
return delegate.withSession(session);
|
||||
}
|
||||
|
||||
private MongoDatabase decorateDatabase(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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2016. the original author or authors.
|
||||
* Copyright 2016-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.
|
||||
@@ -20,12 +20,15 @@ import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.bson.Document;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
* @author Matt Morrissette
|
||||
* @since 1.10
|
||||
*/
|
||||
abstract class AbstractAggregationExpression implements AggregationExpression {
|
||||
@@ -46,29 +49,7 @@ abstract class AbstractAggregationExpression implements AggregationExpression {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public Document toDocument(Object value, AggregationOperationContext context) {
|
||||
|
||||
Object valueToUse;
|
||||
if (value instanceof List) {
|
||||
|
||||
List<Object> arguments = (List<Object>) value;
|
||||
List<Object> args = new ArrayList<Object>(arguments.size());
|
||||
|
||||
for (Object val : arguments) {
|
||||
args.add(unpack(val, context));
|
||||
}
|
||||
valueToUse = args;
|
||||
} else if (value instanceof java.util.Map) {
|
||||
|
||||
Document dbo = new Document();
|
||||
for (java.util.Map.Entry<String, Object> entry : ((java.util.Map<String, Object>) value).entrySet()) {
|
||||
dbo.put(entry.getKey(), unpack(entry.getValue(), context));
|
||||
}
|
||||
valueToUse = dbo;
|
||||
} else {
|
||||
valueToUse = unpack(value, context);
|
||||
}
|
||||
|
||||
return new Document(getMongoMethod(), valueToUse);
|
||||
return new Document(getMongoMethod(), unpack(value, context));
|
||||
}
|
||||
|
||||
protected static List<Field> asFields(String... fieldRefs) {
|
||||
@@ -94,14 +75,23 @@ abstract class AbstractAggregationExpression implements AggregationExpression {
|
||||
if (value instanceof List) {
|
||||
|
||||
List<Object> sourceList = (List<Object>) value;
|
||||
List<Object> mappedList = new ArrayList<Object>(sourceList.size());
|
||||
List<Object> mappedList = new ArrayList<>(sourceList.size());
|
||||
|
||||
sourceList.stream().map((item) -> unpack(item, context)).forEach(mappedList::add);
|
||||
|
||||
for (Object item : sourceList) {
|
||||
mappedList.add(unpack(item, context));
|
||||
}
|
||||
return mappedList;
|
||||
}
|
||||
|
||||
if (value instanceof Map) {
|
||||
|
||||
Document targetDocument = new Document();
|
||||
|
||||
Map<String, Object> sourceMap = (Map<String, Object>) value;
|
||||
sourceMap.forEach((k, v) -> targetDocument.append(k, unpack(v, context)));
|
||||
|
||||
return targetDocument;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -112,9 +102,7 @@ abstract class AbstractAggregationExpression implements AggregationExpression {
|
||||
List<Object> clone = new ArrayList<Object>((List) this.value);
|
||||
|
||||
if (value instanceof List) {
|
||||
for (Object val : (List) value) {
|
||||
clone.add(val);
|
||||
}
|
||||
clone.addAll((List) value);
|
||||
} else {
|
||||
clone.add(value);
|
||||
}
|
||||
@@ -127,10 +115,9 @@ abstract class AbstractAggregationExpression implements AggregationExpression {
|
||||
@SuppressWarnings("unchecked")
|
||||
protected java.util.Map<String, Object> append(String key, Object value) {
|
||||
|
||||
if (!(this.value instanceof java.util.Map)) {
|
||||
throw new IllegalArgumentException("o_O");
|
||||
}
|
||||
java.util.Map<String, Object> clone = new LinkedHashMap<String, Object>((java.util.Map<String, Object>) this.value);
|
||||
Assert.isInstanceOf(Map.class, this.value, "Value must be a type of Map!");
|
||||
|
||||
java.util.Map<String, Object> clone = new LinkedHashMap<>((java.util.Map) this.value);
|
||||
clone.put(key, value);
|
||||
return clone;
|
||||
|
||||
@@ -144,7 +131,67 @@ abstract class AbstractAggregationExpression implements AggregationExpression {
|
||||
if (value instanceof java.util.Map) {
|
||||
return new ArrayList<Object>(((java.util.Map) value).values());
|
||||
}
|
||||
return new ArrayList<Object>(Collections.singletonList(value));
|
||||
return new ArrayList<>(Collections.singletonList(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value at a given index.
|
||||
*
|
||||
* @param index
|
||||
* @param <T>
|
||||
* @return
|
||||
* @since 2.1
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
protected <T> T get(int index) {
|
||||
return (T) values().get(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value for a given key.
|
||||
*
|
||||
* @param key
|
||||
* @param <T>
|
||||
* @return
|
||||
* @since 2.1
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
protected <T> T get(Object key) {
|
||||
|
||||
Assert.isInstanceOf(Map.class, this.value, "Value must be a type of Map!");
|
||||
|
||||
return (T) ((java.util.Map<String, Object>) this.value).get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the argument map.
|
||||
*
|
||||
* @since 2.1
|
||||
* @return
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
protected java.util.Map<String, Object> argumentMap() {
|
||||
|
||||
Assert.isInstanceOf(Map.class, this.value, "Value must be a type of Map!");
|
||||
|
||||
return Collections.unmodifiableMap((java.util.Map) value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given key is available.
|
||||
*
|
||||
* @param key
|
||||
* @return
|
||||
* @since 2.1
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
protected boolean contains(Object key) {
|
||||
|
||||
if (!(this.value instanceof java.util.Map)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ((java.util.Map<String, Object>) this.value).containsKey(key);
|
||||
}
|
||||
|
||||
protected abstract String getMongoMethod();
|
||||
|
||||
@@ -64,6 +64,33 @@ public class Aggregation {
|
||||
*/
|
||||
public static final String CURRENT = SystemVariable.CURRENT.toString();
|
||||
|
||||
/**
|
||||
* A variable to conditionally exclude a field. In a {@code $projection}, a field set to the variable
|
||||
* {@literal REMOVE} is excluded from the output.
|
||||
*
|
||||
* <pre>
|
||||
* <code>
|
||||
*
|
||||
* db.books.aggregate( [
|
||||
* {
|
||||
* $project: {
|
||||
* title: 1,
|
||||
* "author.first": 1,
|
||||
* "author.last" : 1,
|
||||
* "author.middle": {
|
||||
* $cond: {
|
||||
* if: { $eq: [ "", "$author.middle" ] },
|
||||
* then: "$$REMOVE",
|
||||
* else: "$author.middle"
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* } ] )
|
||||
* </code>
|
||||
* </pre>
|
||||
*/
|
||||
public static final String REMOVE = SystemVariable.REMOVE.toString();
|
||||
|
||||
public static final AggregationOperationContext DEFAULT_CONTEXT = AggregationOperationRenderer.DEFAULT_CONTEXT;
|
||||
public static final AggregationOptions DEFAULT_OPTIONS = newAggregationOptions().build();
|
||||
|
||||
@@ -648,11 +675,12 @@ public class Aggregation {
|
||||
* Describes the system variables available in MongoDB aggregation framework pipeline expressions.
|
||||
*
|
||||
* @author Thomas Darimont
|
||||
* @see <a href="https://docs.mongodb.org/manual/reference/aggregation-variables">Aggregation Variables</a>
|
||||
* @author Christoph Strobl
|
||||
* @see <a href="https://docs.mongodb.com/manual/reference/aggregation-variables">Aggregation Variables</a>.
|
||||
*/
|
||||
enum SystemVariable {
|
||||
|
||||
ROOT, CURRENT;
|
||||
ROOT, CURRENT, REMOVE;
|
||||
|
||||
private static final String PREFIX = "$$";
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ class AggregationOperationRenderer {
|
||||
FieldsExposingAggregationOperation exposedFieldsOperation = (FieldsExposingAggregationOperation) operation;
|
||||
ExposedFields fields = exposedFieldsOperation.getFields();
|
||||
|
||||
if (operation instanceof InheritsFieldsAggregationOperation) {
|
||||
if (operation instanceof InheritsFieldsAggregationOperation || exposedFieldsOperation.inheritsFields()) {
|
||||
contextToUse = new InheritingExposedFieldsAggregationOperationContext(fields, contextToUse);
|
||||
} else {
|
||||
contextToUse = fields.exposesNoFields() ? DEFAULT_CONTEXT
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -33,9 +33,26 @@ public interface FieldsExposingAggregationOperation extends AggregationOperation
|
||||
*/
|
||||
ExposedFields getFields();
|
||||
|
||||
/**
|
||||
* @return {@literal true} to conditionally inherit fields from previous operations.
|
||||
* @since 2.0.6
|
||||
*/
|
||||
default boolean inheritsFields() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Marker interface for {@link AggregationOperation} that inherits fields from previous operations.
|
||||
*/
|
||||
interface InheritsFieldsAggregationOperation extends FieldsExposingAggregationOperation {}
|
||||
interface InheritsFieldsAggregationOperation extends FieldsExposingAggregationOperation {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.aggregation.FieldsExposingAggregationOperation#inheritsFields()
|
||||
*/
|
||||
@Override
|
||||
default boolean inheritsFields() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,11 +140,6 @@ public class ProjectionOperation implements FieldsExposingAggregationOperation {
|
||||
*/
|
||||
public ProjectionOperation andExclude(String... fieldNames) {
|
||||
|
||||
for (String fieldName : fieldNames) {
|
||||
Assert.isTrue(Fields.UNDERSCORE_ID.equals(fieldName),
|
||||
String.format(EXCLUSION_ERROR, fieldName, Fields.UNDERSCORE_ID));
|
||||
}
|
||||
|
||||
List<FieldProjection> excludeProjections = FieldProjection.from(Fields.fields(fieldNames), false);
|
||||
return new ProjectionOperation(this.projections, excludeProjections);
|
||||
}
|
||||
@@ -188,6 +183,18 @@ public class ProjectionOperation implements FieldsExposingAggregationOperation {
|
||||
return fields != null ? fields : ExposedFields.empty();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.aggregation.FieldsExposingAggregationOperation#inheritsFields()
|
||||
*/
|
||||
@Override
|
||||
public boolean inheritsFields() {
|
||||
|
||||
return projections.stream().filter(FieldProjection.class::isInstance) //
|
||||
.map(FieldProjection.class::cast) //
|
||||
.anyMatch(FieldProjection::isExcluded);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.aggregation.AggregationOperation#toDocument(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext)
|
||||
@@ -455,7 +462,7 @@ public class ProjectionOperation implements FieldsExposingAggregationOperation {
|
||||
}
|
||||
|
||||
if (value instanceof AggregationExpression) {
|
||||
return this.operation.and(new ExpressionProjection(Fields.field(alias), (AggregationExpression) value));
|
||||
return this.operation.and(new ExpressionProjection(Fields.field(alias, alias), (AggregationExpression) value));
|
||||
}
|
||||
|
||||
return this.operation.and(new FieldProjection(Fields.field(alias, getRequiredName()), null));
|
||||
@@ -1344,6 +1351,13 @@ public class ProjectionOperation implements FieldsExposingAggregationOperation {
|
||||
return projections;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@literal true} if this field is excluded.
|
||||
*/
|
||||
public boolean isExcluded() {
|
||||
return Boolean.FALSE.equals(value);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.aggregation.ProjectionOperation.Projection#toDocument(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext)
|
||||
|
||||
@@ -18,12 +18,12 @@ package org.springframework.data.mongodb.core.aggregation;
|
||||
import static org.springframework.data.mongodb.core.aggregation.Fields.*;
|
||||
|
||||
import org.bson.Document;
|
||||
import org.springframework.data.mapping.PersistentPropertyPath;
|
||||
import org.springframework.data.mapping.PropertyPath;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.mapping.context.PersistentPropertyPath;
|
||||
import org.springframework.data.mongodb.core.aggregation.ExposedFields.DirectFieldReference;
|
||||
import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExposedField;
|
||||
import org.springframework.data.mongodb.core.aggregation.ExposedFields.FieldReference;
|
||||
import org.springframework.data.mongodb.core.aggregation.ExposedFields.DirectFieldReference;
|
||||
import org.springframework.data.mongodb.core.convert.QueryMapper;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
|
||||
@@ -95,8 +95,8 @@ public class TypeBasedAggregationOperationContext implements AggregationOperatio
|
||||
|
||||
private FieldReference getReferenceFor(Field field) {
|
||||
|
||||
PersistentPropertyPath<MongoPersistentProperty> propertyPath = mappingContext.getPersistentPropertyPath(
|
||||
field.getTarget(), type);
|
||||
PersistentPropertyPath<MongoPersistentProperty> propertyPath = mappingContext
|
||||
.getPersistentPropertyPath(field.getTarget(), type);
|
||||
Field mappedField = field(field.getName(),
|
||||
propertyPath.toDotPath(MongoPersistentProperty.PropertyToFieldNameConverter.INSTANCE));
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ public interface DbRefResolver {
|
||||
* @return
|
||||
*/
|
||||
@Nullable
|
||||
Object resolveDbRef(MongoPersistentProperty property, DBRef dbref, DbRefResolverCallback callback,
|
||||
Object resolveDbRef(MongoPersistentProperty property, @Nullable DBRef dbref, DbRefResolverCallback callback,
|
||||
DbRefProxyHandler proxyHandler);
|
||||
|
||||
/**
|
||||
@@ -59,7 +59,8 @@ public interface DbRefResolver {
|
||||
* @param id will never be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
DBRef createDbRef(org.springframework.data.mongodb.core.mapping.DBRef annotation, MongoPersistentEntity<?> entity,
|
||||
DBRef createDbRef(@Nullable org.springframework.data.mongodb.core.mapping.DBRef annotation,
|
||||
MongoPersistentEntity<?> entity,
|
||||
Object id);
|
||||
|
||||
/**
|
||||
|
||||
@@ -40,6 +40,7 @@ import org.springframework.cglib.proxy.MethodProxy;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
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;
|
||||
@@ -51,7 +52,7 @@ import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.mongodb.DBRef;
|
||||
import com.mongodb.client.MongoDatabase;
|
||||
import com.mongodb.client.MongoCollection;
|
||||
import com.mongodb.client.model.Filters;
|
||||
|
||||
/**
|
||||
@@ -89,7 +90,7 @@ public class DefaultDbRefResolver implements DbRefResolver {
|
||||
* @see org.springframework.data.mongodb.core.convert.DbRefResolver#resolveDbRef(org.springframework.data.mongodb.core.mapping.MongoPersistentProperty, org.springframework.data.mongodb.core.convert.DbRefResolverCallback)
|
||||
*/
|
||||
@Override
|
||||
public Object resolveDbRef(MongoPersistentProperty property, DBRef dbref, DbRefResolverCallback callback,
|
||||
public Object resolveDbRef(MongoPersistentProperty property, @Nullable DBRef dbref, DbRefResolverCallback callback,
|
||||
DbRefProxyHandler handler) {
|
||||
|
||||
Assert.notNull(property, "Property must not be null!");
|
||||
@@ -108,7 +109,7 @@ public class DefaultDbRefResolver implements DbRefResolver {
|
||||
* @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(org.springframework.data.mongodb.core.mapping.DBRef annotation,
|
||||
public DBRef createDbRef(@Nullable org.springframework.data.mongodb.core.mapping.DBRef annotation,
|
||||
MongoPersistentEntity<?> entity, Object id) {
|
||||
|
||||
if (annotation != null && StringUtils.hasText(annotation.db())) {
|
||||
@@ -126,9 +127,7 @@ public class DefaultDbRefResolver implements DbRefResolver {
|
||||
public Document fetch(DBRef dbRef) {
|
||||
|
||||
StringUtils.hasText(dbRef.getDatabaseName());
|
||||
return (StringUtils.hasText(dbRef.getDatabaseName()) ? mongoDbFactory.getDb(dbRef.getDatabaseName())
|
||||
: mongoDbFactory.getDb()).getCollection(dbRef.getCollectionName(), Document.class)
|
||||
.find(Filters.eq("_id", dbRef.getId())).first();
|
||||
return getCollection(dbRef).find(Filters.eq("_id", dbRef.getId())).first();
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -158,9 +157,7 @@ public class DefaultDbRefResolver implements DbRefResolver {
|
||||
ids.add(ref.getId());
|
||||
}
|
||||
|
||||
MongoDatabase db = mongoDbFactory.getDb();
|
||||
|
||||
List<Document> result = db.getCollection(collection) //
|
||||
List<Document> result = getCollection(refs.iterator().next()) //
|
||||
.find(new Document("_id", new Document("$in", ids))) //
|
||||
.into(new ArrayList<>());
|
||||
|
||||
@@ -466,6 +463,11 @@ public class DefaultDbRefResolver implements DbRefResolver {
|
||||
} catch (RuntimeException ex) {
|
||||
|
||||
DataAccessException translatedException = this.exceptionTranslator.translateExceptionIfPossible(ex);
|
||||
|
||||
if (translatedException instanceof ClientSessionException) {
|
||||
throw new LazyLoadingException("Unable to lazily resolve DBRef! Invalid session state.", ex);
|
||||
}
|
||||
|
||||
throw new LazyLoadingException("Unable to lazily resolve DBRef!",
|
||||
translatedException != null ? translatedException : ex);
|
||||
}
|
||||
@@ -474,4 +476,17 @@ public class DefaultDbRefResolver implements DbRefResolver {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Customization hook for obtaining the {@link MongoCollection} for a given {@link DBRef}.
|
||||
*
|
||||
* @param dbref must not be {@literal null}.
|
||||
* @return the {@link MongoCollection} the given {@link DBRef} points to.
|
||||
* @since 2.1
|
||||
*/
|
||||
protected MongoCollection<Document> getCollection(DBRef dbref) {
|
||||
|
||||
return (StringUtils.hasText(dbref.getDatabaseName()) ? mongoDbFactory.getDb(dbref.getDatabaseName())
|
||||
: mongoDbFactory.getDb()).getCollection(dbref.getCollectionName(), Document.class);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.");
|
||||
|
||||
@@ -147,8 +147,7 @@ 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;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -264,7 +263,6 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
path);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private <S extends Object> S read(final MongoPersistentEntity<S> entity, final Document bson, final ObjectPath path) {
|
||||
|
||||
DefaultSpELExpressionEvaluator evaluator = new DefaultSpELExpressionEvaluator(bson, spELContext);
|
||||
@@ -316,7 +314,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
for (MongoPersistentProperty prop : entity) {
|
||||
|
||||
if (prop.isAssociation() && !entity.isConstructorArgument(prop)) {
|
||||
readAssociation(prop.getAssociation(), accessor, documentAccessor, dbRefProxyHandler, callback);
|
||||
readAssociation(prop.getRequiredAssociation(), accessor, documentAccessor, dbRefProxyHandler, callback);
|
||||
continue;
|
||||
}
|
||||
// we skip the id property since it was already set
|
||||
@@ -329,7 +327,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
}
|
||||
|
||||
if (prop.isAssociation()) {
|
||||
readAssociation(prop.getAssociation(), accessor, documentAccessor, dbRefProxyHandler, callback);
|
||||
readAssociation(prop.getRequiredAssociation(), accessor, documentAccessor, dbRefProxyHandler, callback);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -357,7 +355,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
*/
|
||||
public DBRef toDBRef(Object object, @Nullable MongoPersistentProperty referringProperty) {
|
||||
|
||||
org.springframework.data.mongodb.core.mapping.DBRef annotation = null;
|
||||
org.springframework.data.mongodb.core.mapping.DBRef annotation;
|
||||
|
||||
if (referringProperty != null) {
|
||||
annotation = referringProperty.getDBRef();
|
||||
@@ -378,7 +376,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
*
|
||||
* @see org.springframework.data.mongodb.core.convert.MongoWriter#write(java.lang.Object, com.mongodb.Document)
|
||||
*/
|
||||
public void write(final Object obj, final Bson bson) {
|
||||
public void write(Object obj, Bson bson) {
|
||||
|
||||
if (null == obj) {
|
||||
return;
|
||||
@@ -405,9 +403,10 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
*
|
||||
* @param obj
|
||||
* @param bson
|
||||
* @param typeHint
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
protected void writeInternal(@Nullable Object obj, final Bson bson, final TypeInformation<?> typeHint) {
|
||||
protected void writeInternal(@Nullable Object obj, Bson bson, @Nullable TypeInformation<?> typeHint) {
|
||||
|
||||
if (null == obj) {
|
||||
return;
|
||||
@@ -428,7 +427,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
}
|
||||
|
||||
if (Collection.class.isAssignableFrom(entityType)) {
|
||||
writeCollectionInternal((Collection<?>) obj, ClassTypeInformation.LIST, (BasicDBList) bson);
|
||||
writeCollectionInternal((Collection<?>) obj, ClassTypeInformation.LIST, (Collection) bson);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -437,7 +436,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
addCustomTypeKeyIfNecessary(typeHint, obj, bson);
|
||||
}
|
||||
|
||||
protected void writeInternal(@Nullable Object obj, final Bson bson, MongoPersistentEntity<?> entity) {
|
||||
protected void writeInternal(@Nullable Object obj, Bson bson, @Nullable MongoPersistentEntity<?> entity) {
|
||||
|
||||
if (obj == null) {
|
||||
return;
|
||||
@@ -463,7 +462,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
}
|
||||
|
||||
private void writeProperties(Bson bson, MongoPersistentEntity<?> entity, PersistentPropertyAccessor accessor,
|
||||
DocumentAccessor dbObjectAccessor, MongoPersistentProperty idProperty) {
|
||||
DocumentAccessor dbObjectAccessor, @Nullable MongoPersistentProperty idProperty) {
|
||||
|
||||
// Write the properties
|
||||
for (MongoPersistentProperty prop : entity) {
|
||||
@@ -472,7 +471,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
continue;
|
||||
}
|
||||
if (prop.isAssociation()) {
|
||||
writeAssociation(prop.getAssociation(), accessor, dbObjectAccessor);
|
||||
writeAssociation(prop.getRequiredAssociation(), accessor, dbObjectAccessor);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -499,7 +498,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "unchecked" })
|
||||
protected void writePropertyInternal(Object obj, DocumentAccessor accessor, MongoPersistentProperty prop) {
|
||||
protected void writePropertyInternal(@Nullable Object obj, DocumentAccessor accessor, MongoPersistentProperty prop) {
|
||||
|
||||
if (obj == null) {
|
||||
return;
|
||||
@@ -557,8 +556,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
}
|
||||
|
||||
MongoPersistentEntity<?> entity = isSubtype(prop.getType(), obj.getClass())
|
||||
? mappingContext.getRequiredPersistentEntity(obj.getClass())
|
||||
: mappingContext.getRequiredPersistentEntity(type);
|
||||
? mappingContext.getRequiredPersistentEntity(obj.getClass()) : mappingContext.getRequiredPersistentEntity(type);
|
||||
|
||||
Object existingValue = accessor.get(prop);
|
||||
Document document = existingValue instanceof Document ? (Document) existingValue : new Document();
|
||||
@@ -654,17 +652,19 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates the given {@link BasicDBList} with values from the given {@link Collection}.
|
||||
* Populates the given {@link Collection sink} with converted values from the given {@link Collection source}.
|
||||
*
|
||||
* @param source the collection to create a {@link BasicDBList} for, must not be {@literal null}.
|
||||
* @param source the collection to create a {@link Collection} for, must not be {@literal null}.
|
||||
* @param type the {@link TypeInformation} to consider or {@literal null} if unknown.
|
||||
* @param sink the {@link BasicDBList} to write to.
|
||||
* @param sink the {@link Collection} to write to.
|
||||
* @return
|
||||
*/
|
||||
private BasicDBList writeCollectionInternal(Collection<?> source, TypeInformation<?> type, BasicDBList sink) {
|
||||
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);
|
||||
|
||||
if (type != null) {
|
||||
componentType = type.getComponentType();
|
||||
}
|
||||
@@ -674,17 +674,17 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
Class<?> elementType = element == null ? null : element.getClass();
|
||||
|
||||
if (elementType == null || conversions.isSimpleType(elementType)) {
|
||||
sink.add(getPotentiallyConvertedSimpleWrite(element));
|
||||
collection.add(getPotentiallyConvertedSimpleWrite(element));
|
||||
} else if (element instanceof Collection || elementType.isArray()) {
|
||||
sink.add(writeCollectionInternal(asCollection(element), componentType, new BasicDBList()));
|
||||
collection.add(writeCollectionInternal(asCollection(element), componentType, new BasicDBList()));
|
||||
} else {
|
||||
Document document = new Document();
|
||||
writeInternal(element, document, componentType);
|
||||
sink.add(document);
|
||||
collection.add(document);
|
||||
}
|
||||
}
|
||||
|
||||
return sink;
|
||||
return collection;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -777,8 +777,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
}
|
||||
|
||||
return conversions.hasCustomWriteTarget(key.getClass(), String.class)
|
||||
? (String) getPotentiallyConvertedSimpleWrite(key)
|
||||
: key.toString();
|
||||
? (String) getPotentiallyConvertedSimpleWrite(key) : key.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -868,9 +867,9 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
*/
|
||||
@Nullable
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
private Object getPotentiallyConvertedSimpleRead(@Nullable Object value, Class<?> target) {
|
||||
private Object getPotentiallyConvertedSimpleRead(@Nullable Object value, @Nullable Class<?> target) {
|
||||
|
||||
if (value == null || target == null || target.isAssignableFrom(value.getClass())) {
|
||||
if (value == null || target == null || ClassUtils.isAssignableValue(target, value)) {
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -933,58 +932,62 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
* Reads the given {@link BasicDBList} into a collection of the given {@link TypeInformation}.
|
||||
*
|
||||
* @param targetType must not be {@literal null}.
|
||||
* @param sourceValue must not be {@literal null}.
|
||||
* @param source must not be {@literal null}.
|
||||
* @param path must not be {@literal null}.
|
||||
* @return the converted {@link Collection} or array, will never be {@literal null}.
|
||||
*/
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
private Object readCollectionOrArray(TypeInformation<?> targetType, List sourceValue, ObjectPath path) {
|
||||
@SuppressWarnings("unchecked")
|
||||
private Object readCollectionOrArray(TypeInformation<?> targetType, Collection<?> source, ObjectPath path) {
|
||||
|
||||
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 //
|
||||
: List.class;
|
||||
|
||||
TypeInformation<?> componentType = targetType.getComponentType() != null ? targetType.getComponentType()
|
||||
TypeInformation<?> componentType = targetType.getComponentType() != null //
|
||||
? targetType.getComponentType() //
|
||||
: ClassTypeInformation.OBJECT;
|
||||
Class<?> rawComponentType = componentType.getType();
|
||||
|
||||
collectionType = Collection.class.isAssignableFrom(collectionType) ? collectionType : List.class;
|
||||
Collection<Object> items = targetType.getType().isArray() ? new ArrayList<>(sourceValue.size())
|
||||
: CollectionFactory.createCollection(collectionType, rawComponentType, sourceValue.size());
|
||||
Collection<Object> items = targetType.getType().isArray() //
|
||||
? new ArrayList<>(source.size()) //
|
||||
: CollectionFactory.createCollection(collectionType, rawComponentType, source.size());
|
||||
|
||||
if (sourceValue.isEmpty()) {
|
||||
if (source.isEmpty()) {
|
||||
return getPotentiallyConvertedSimpleRead(items, targetType.getType());
|
||||
}
|
||||
|
||||
if (!DBRef.class.equals(rawComponentType) && isCollectionOfDbRefWhereBulkFetchIsPossible(sourceValue)) {
|
||||
if (!DBRef.class.equals(rawComponentType) && isCollectionOfDbRefWhereBulkFetchIsPossible(source)) {
|
||||
|
||||
List<Object> objects = bulkReadAndConvertDBRefs((List<DBRef>) sourceValue, componentType, path, rawComponentType);
|
||||
List<Object> objects = bulkReadAndConvertDBRefs((List<DBRef>) source, componentType, path, rawComponentType);
|
||||
return getPotentiallyConvertedSimpleRead(objects, targetType.getType());
|
||||
}
|
||||
|
||||
for (Object dbObjItem : sourceValue) {
|
||||
for (Object element : source) {
|
||||
|
||||
if (dbObjItem instanceof DBRef) {
|
||||
items.add(DBRef.class.equals(rawComponentType) ? dbObjItem
|
||||
: readAndConvertDBRef((DBRef) dbObjItem, componentType, path, rawComponentType));
|
||||
} else if (dbObjItem instanceof Document) {
|
||||
items.add(read(componentType, (Document) dbObjItem, path));
|
||||
} else if (dbObjItem instanceof BasicDBObject) {
|
||||
items.add(read(componentType, (BasicDBObject) dbObjItem, path));
|
||||
if (element instanceof DBRef) {
|
||||
items.add(DBRef.class.equals(rawComponentType) ? element
|
||||
: readAndConvertDBRef((DBRef) element, componentType, path, rawComponentType));
|
||||
} else if (element instanceof Document) {
|
||||
items.add(read(componentType, (Document) element, path));
|
||||
} else if (element instanceof BasicDBObject) {
|
||||
items.add(read(componentType, (BasicDBObject) element, path));
|
||||
} else {
|
||||
|
||||
if (dbObjItem instanceof Collection) {
|
||||
if (element instanceof Collection) {
|
||||
if (!rawComponentType.isArray() && !ClassUtils.isAssignable(Iterable.class, rawComponentType)) {
|
||||
throw new MappingException(
|
||||
String.format(INCOMPATIBLE_TYPES, dbObjItem, dbObjItem.getClass(), rawComponentType, path));
|
||||
String.format(INCOMPATIBLE_TYPES, element, element.getClass(), rawComponentType, path));
|
||||
}
|
||||
}
|
||||
|
||||
if (dbObjItem instanceof List) {
|
||||
items.add(readCollectionOrArray(ClassTypeInformation.OBJECT, (List) dbObjItem, path));
|
||||
if (element instanceof List) {
|
||||
items.add(readCollectionOrArray(componentType, (Collection<Object>) element, path));
|
||||
} else {
|
||||
items.add(getPotentiallyConvertedSimpleRead(dbObjItem, rawComponentType));
|
||||
items.add(getPotentiallyConvertedSimpleRead(element, rawComponentType));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1045,8 +1048,8 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
map.put(key, DBRef.class.equals(rawValueType) ? value
|
||||
: readAndConvertDBRef((DBRef) value, defaultedValueType, ObjectPath.ROOT, rawValueType));
|
||||
} else if (value instanceof List) {
|
||||
map.put(key,
|
||||
readCollectionOrArray(valueType != null ? valueType : ClassTypeInformation.LIST, (List) value, path));
|
||||
map.put(key, readCollectionOrArray(valueType != null ? valueType : ClassTypeInformation.LIST,
|
||||
(List<Object>) value, path));
|
||||
} else {
|
||||
map.put(key, getPotentiallyConvertedSimpleRead(value, rawValueType));
|
||||
}
|
||||
@@ -1084,8 +1087,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
"Cannot add key/value pair to %s. as map. Given Bson must be a Document or DBObject!", bson.getClass()));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static void addAllToMap(Bson bson, Map value) {
|
||||
private static void addAllToMap(Bson bson, Map<String, ?> value) {
|
||||
|
||||
if (bson instanceof Document) {
|
||||
((Document) bson).putAll(value);
|
||||
@@ -1140,10 +1142,8 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
return getPotentiallyConvertedSimpleWrite(obj);
|
||||
}
|
||||
|
||||
TypeInformation<?> typeHint = typeInformation;
|
||||
|
||||
if (obj instanceof List) {
|
||||
return maybeConvertList((List<Object>) obj, typeHint);
|
||||
return maybeConvertList((List<Object>) obj, typeInformation);
|
||||
}
|
||||
|
||||
if (obj instanceof Document) {
|
||||
@@ -1151,7 +1151,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
Document newValueDocument = new Document();
|
||||
for (String vk : ((Document) obj).keySet()) {
|
||||
Object o = ((Document) obj).get(vk);
|
||||
newValueDocument.put(vk, convertToMongoType(o, typeHint));
|
||||
newValueDocument.put(vk, convertToMongoType(o, typeInformation));
|
||||
}
|
||||
return newValueDocument;
|
||||
}
|
||||
@@ -1162,7 +1162,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
for (String vk : ((DBObject) obj).keySet()) {
|
||||
|
||||
Object o = ((DBObject) obj).get(vk);
|
||||
newValueDbo.put(vk, convertToMongoType(o, typeHint));
|
||||
newValueDbo.put(vk, convertToMongoType(o, typeInformation));
|
||||
}
|
||||
|
||||
return newValueDbo;
|
||||
@@ -1173,18 +1173,18 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
Document result = new Document();
|
||||
|
||||
for (Map.Entry<Object, Object> entry : ((Map<Object, Object>) obj).entrySet()) {
|
||||
result.put(entry.getKey().toString(), convertToMongoType(entry.getValue(), typeHint));
|
||||
result.put(entry.getKey().toString(), convertToMongoType(entry.getValue(), typeInformation));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
if (obj.getClass().isArray()) {
|
||||
return maybeConvertList(Arrays.asList((Object[]) obj), typeHint);
|
||||
return maybeConvertList(Arrays.asList((Object[]) obj), typeInformation);
|
||||
}
|
||||
|
||||
if (obj instanceof Collection) {
|
||||
return maybeConvertList((Collection<?>) obj, typeHint);
|
||||
return maybeConvertList((Collection<?>) obj, typeInformation);
|
||||
}
|
||||
|
||||
Document newDocument = new Document();
|
||||
@@ -1219,6 +1219,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
* @param recursively whether to apply the removal recursively
|
||||
* @return
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private Object removeTypeInfo(Object object, boolean recursively) {
|
||||
|
||||
if (!(object instanceof Document)) {
|
||||
@@ -1239,7 +1240,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
removeTypeInfo(element, recursively);
|
||||
}
|
||||
} else if (value instanceof List) {
|
||||
for (Object element : (List) value) {
|
||||
for (Object element : (List<Object>) value) {
|
||||
removeTypeInfo(element, recursively);
|
||||
}
|
||||
} else {
|
||||
@@ -1379,7 +1380,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
} else if (value instanceof DBRef) {
|
||||
return potentiallyReadOrResolveDbRef((DBRef) value, type, path, rawType);
|
||||
} else if (value instanceof List) {
|
||||
return (T) readCollectionOrArray(type, (List) value, path);
|
||||
return (T) readCollectionOrArray(type, (List<Object>) value, path);
|
||||
} else if (value instanceof Document) {
|
||||
return (T) read(type, (Document) value, path);
|
||||
} else if (value instanceof DBObject) {
|
||||
@@ -1434,8 +1435,7 @@ 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());
|
||||
@@ -1495,7 +1495,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
* @param source must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
private static boolean isCollectionOfDbRefWhereBulkFetchIsPossible(Iterable<Object> source) {
|
||||
private static boolean isCollectionOfDbRefWhereBulkFetchIsPossible(Iterable<?> source) {
|
||||
|
||||
Assert.notNull(source, "Iterable of DBRefs must not be null!");
|
||||
|
||||
|
||||
@@ -69,7 +69,6 @@ public interface MongoConverter
|
||||
Assert.notNull(targetType, "TargetType must not be null!");
|
||||
Assert.notNull(dbRefResolver, "DbRefResolver must not be null!");
|
||||
|
||||
|
||||
if (targetType != Object.class && ClassUtils.isAssignable(targetType, source.getClass())) {
|
||||
return (T) source;
|
||||
}
|
||||
|
||||
@@ -35,11 +35,11 @@ import org.springframework.data.domain.Example;
|
||||
import org.springframework.data.mapping.Association;
|
||||
import org.springframework.data.mapping.MappingException;
|
||||
import org.springframework.data.mapping.PersistentEntity;
|
||||
import org.springframework.data.mapping.PersistentPropertyPath;
|
||||
import org.springframework.data.mapping.PropertyPath;
|
||||
import org.springframework.data.mapping.PropertyReferenceException;
|
||||
import org.springframework.data.mapping.context.InvalidPersistentPropertyPath;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.mapping.context.PersistentPropertyPath;
|
||||
import org.springframework.data.mongodb.core.convert.MappingMongoConverter.NestedDocument;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -21,13 +21,14 @@ 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.Persistent;
|
||||
|
||||
/**
|
||||
* Identifies a domain object to be persisted to MongoDB.
|
||||
*
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
* @author Oliver Gierke ogierke@vmware.com
|
||||
* @author Jon Brisbin
|
||||
* @author Oliver Gierke
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
@Persistent
|
||||
@@ -36,6 +37,24 @@ import org.springframework.data.annotation.Persistent;
|
||||
@Target({ ElementType.TYPE })
|
||||
public @interface Document {
|
||||
|
||||
/**
|
||||
* The collection the document representing the entity is supposed to be stored in. If not configured, a default
|
||||
* collection name will be derived from the type's name. The attribute supports SpEL expressions to dynamically
|
||||
* calculate the collection to based on a per operation basis.
|
||||
*
|
||||
* @return the name of the collection to be used.
|
||||
*/
|
||||
@AliasFor("collection")
|
||||
String value() default "";
|
||||
|
||||
/**
|
||||
* The collection the document representing the entity is supposed to be stored in. If not configured, a default
|
||||
* collection name will be derived from the type's name. The attribute supports SpEL expressions to dynamically
|
||||
* calculate the collection to based on a per operation basis.
|
||||
*
|
||||
* @return the name of the collection to be used.
|
||||
*/
|
||||
@AliasFor("value")
|
||||
String collection() default "";
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,8 +37,8 @@ public class MapReduceOptions {
|
||||
|
||||
private Optional<String> outputDatabase = Optional.empty();
|
||||
private MapReduceCommand.OutputType outputType = MapReduceCommand.OutputType.REPLACE;
|
||||
private Map<String, Object> scopeVariables = new HashMap<String, Object>();
|
||||
private Map<String, Object> extraOptions = new HashMap<String, Object>();
|
||||
private Map<String, Object> scopeVariables = new HashMap<>();
|
||||
private Map<String, Object> extraOptions = new HashMap<>();
|
||||
private @Nullable Boolean jsMode;
|
||||
private Boolean verbose = Boolean.TRUE;
|
||||
private @Nullable Integer limit;
|
||||
|
||||
@@ -29,6 +29,7 @@ import java.util.stream.Collectors;
|
||||
import org.bson.BSON;
|
||||
import org.bson.BsonRegularExpression;
|
||||
import org.bson.Document;
|
||||
import org.bson.types.Binary;
|
||||
import org.springframework.data.domain.Example;
|
||||
import org.springframework.data.geo.Circle;
|
||||
import org.springframework.data.geo.Point;
|
||||
@@ -41,6 +42,7 @@ import org.springframework.data.mongodb.core.schema.JsonSchemaProperty;
|
||||
import org.springframework.data.mongodb.core.schema.MongoJsonSchema;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.Base64Utils;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
@@ -56,6 +58,7 @@ import com.mongodb.BasicDBList;
|
||||
* @author Thomas Darimont
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
* @author Andreas Zink
|
||||
*/
|
||||
public class Criteria implements CriteriaDefinition {
|
||||
|
||||
@@ -356,7 +359,7 @@ public class Criteria implements CriteriaDefinition {
|
||||
/**
|
||||
* Creates a criterion using the {@literal $type} operator.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
* @param types must not be {@literal null}.
|
||||
* @return this
|
||||
* @since 2.1
|
||||
* @see <a href="https://docs.mongodb.com/manual/reference/operator/query/type/">MongoDB Query operator: $type</a>
|
||||
@@ -622,6 +625,18 @@ public class Criteria implements CriteriaDefinition {
|
||||
return registerCriteriaChainElement(schemaCriteria);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use {@link BitwiseCriteriaOperators} as gateway to create a criterion using one of the
|
||||
* <a href="https://docs.mongodb.com/manual/reference/operator/query-bitwise/">bitwise operators</a> like
|
||||
* {@code $bitsAllClear}.
|
||||
*
|
||||
* @return new instance of {@link BitwiseCriteriaOperators}. Never {@literal null}.
|
||||
* @since 2.1
|
||||
*/
|
||||
public BitwiseCriteriaOperators bits() {
|
||||
return new BitwiseCriteriaOperatorsImpl(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an 'or' criteria using the $or operator for all of the provided criteria
|
||||
* <p>
|
||||
@@ -880,4 +895,324 @@ public class Criteria implements CriteriaDefinition {
|
||||
return value instanceof GeoJson
|
||||
|| (value instanceof GeoCommand && ((GeoCommand) value).getShape() instanceof GeoJson);
|
||||
}
|
||||
|
||||
/**
|
||||
* MongoDB specific <a href="https://docs.mongodb.com/manual/reference/operator/query-bitwise/">bitwise query
|
||||
* operators</a> like {@code $bitsAllClear, $bitsAllSet,...} for usage with {@link Criteria#bits()} and {@link Query}.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.1
|
||||
* @see <a href=
|
||||
* "https://docs.mongodb.com/manual/reference/operator/query-bitwise/">https://docs.mongodb.com/manual/reference/operator/query-bitwise/</a>
|
||||
* @currentRead Beyond the Shadows - Brent Weeks
|
||||
*/
|
||||
public interface BitwiseCriteriaOperators {
|
||||
|
||||
/**
|
||||
* Creates a criterion using {@literal $bitsAllClear} matching documents where all given bit positions are clear
|
||||
* (i.e. 0).
|
||||
*
|
||||
* @param numericBitmask non-negative numeric bitmask.
|
||||
* @return target {@link Criteria}.
|
||||
* @see <a href="https://docs.mongodb.com/manual/reference/operator/query/bitsAllClear/">MongoDB Query operator:
|
||||
* $bitsAllClear</a>
|
||||
* @since 2.1
|
||||
*/
|
||||
Criteria allClear(int numericBitmask);
|
||||
|
||||
/**
|
||||
* Creates a criterion using {@literal $bitsAllClear} matching documents where all given bit positions are clear
|
||||
* (i.e. 0).
|
||||
*
|
||||
* @param bitmask string representation of a bitmask that will be converted to its base64 encoded {@link Binary}
|
||||
* representation. Must not be {@literal null} nor empty.
|
||||
* @return target {@link Criteria}.
|
||||
* @throws IllegalArgumentException when bitmask is {@literal null} or empty.
|
||||
* @see <a href="https://docs.mongodb.com/manual/reference/operator/query/bitsAllClear/">MongoDB Query operator:
|
||||
* $bitsAllClear</a>
|
||||
* @since 2.1
|
||||
*/
|
||||
Criteria allClear(String bitmask);
|
||||
|
||||
/**
|
||||
* Creates a criterion using {@literal $bitsAllClear} matching documents where all given bit positions are clear
|
||||
* (i.e. 0).
|
||||
*
|
||||
* @param positions list of non-negative integer positions. Positions start at 0 from the least significant bit.
|
||||
* Must not be {@literal null} nor contain {@literal null} elements.
|
||||
* @return target {@link Criteria}.
|
||||
* @throws IllegalArgumentException when positions is {@literal null} or contains {@literal null} elements.
|
||||
* @see <a href="https://docs.mongodb.com/manual/reference/operator/query/bitsAllClear/">MongoDB Query operator:
|
||||
* $bitsAllClear</a>
|
||||
* @since 2.1
|
||||
*/
|
||||
Criteria allClear(List<Integer> positions);
|
||||
|
||||
/**
|
||||
* Creates a criterion using {@literal $bitsAllSet} matching documents where all given bit positions are set (i.e.
|
||||
* 1).
|
||||
*
|
||||
* @param numericBitmask non-negative numeric bitmask.
|
||||
* @return target {@link Criteria}.
|
||||
* @see <a href="https://docs.mongodb.com/manual/reference/operator/query/bitsAllSet/">MongoDB Query operator:
|
||||
* $bitsAllSet</a>
|
||||
* @since 2.1
|
||||
*/
|
||||
Criteria allSet(int numericBitmask);
|
||||
|
||||
/**
|
||||
* Creates a criterion using {@literal $bitsAllSet} matching documents where all given bit positions are set (i.e.
|
||||
* 1).
|
||||
*
|
||||
* @param bitmask string representation of a bitmask that will be converted to its base64 encoded {@link Binary}
|
||||
* representation. Must not be {@literal null} nor empty.
|
||||
* @return target {@link Criteria}.
|
||||
* @throws IllegalArgumentException when bitmask is {@literal null} or empty.
|
||||
* @see <a href="https://docs.mongodb.com/manual/reference/operator/query/bitsAllSet/">MongoDB Query operator:
|
||||
* $bitsAllSet</a>
|
||||
* @since 2.1
|
||||
*/
|
||||
Criteria allSet(String bitmask);
|
||||
|
||||
/**
|
||||
* Creates a criterion using {@literal $bitsAllSet} matching documents where all given bit positions are set (i.e.
|
||||
* 1).
|
||||
*
|
||||
* @param positions list of non-negative integer positions. Positions start at 0 from the least significant bit.
|
||||
* Must not be {@literal null} nor contain {@literal null} elements.
|
||||
* @return target {@link Criteria}.
|
||||
* @throws IllegalArgumentException when positions is {@literal null} or contains {@literal null} elements.
|
||||
* @see <a href="https://docs.mongodb.com/manual/reference/operator/query/bitsAllSet/">MongoDB Query operator:
|
||||
* $bitsAllSet</a>
|
||||
* @since 2.1
|
||||
*/
|
||||
Criteria allSet(List<Integer> positions);
|
||||
|
||||
/**
|
||||
* Creates a criterion using {@literal $bitsAllClear} matching documents where any given bit positions are clear
|
||||
* (i.e. 0).
|
||||
*
|
||||
* @param numericBitmask non-negative numeric bitmask.
|
||||
* @return target {@link Criteria}.
|
||||
* @see <a href="https://docs.mongodb.com/manual/reference/operator/query/bitsAnyClear/">MongoDB Query operator:
|
||||
* $bitsAnyClear</a>
|
||||
* @since 2.1
|
||||
*/
|
||||
Criteria anyClear(int numericBitmask);
|
||||
|
||||
/**
|
||||
* Creates a criterion using {@literal $bitsAllClear} matching documents where any given bit positions are clear
|
||||
* (i.e. 0).
|
||||
*
|
||||
* @param bitmask string representation of a bitmask that will be converted to its base64 encoded {@link Binary}
|
||||
* representation. Must not be {@literal null} nor empty.
|
||||
* @return target {@link Criteria}.
|
||||
* @throws IllegalArgumentException when bitmask is {@literal null} or empty.
|
||||
* @see <a href="https://docs.mongodb.com/manual/reference/operator/query/bitsAnyClear/">MongoDB Query operator:
|
||||
* $bitsAnyClear</a>
|
||||
* @since 2.1
|
||||
*/
|
||||
Criteria anyClear(String bitmask);
|
||||
|
||||
/**
|
||||
* Creates a criterion using {@literal $bitsAllClear} matching documents where any given bit positions are clear
|
||||
* (i.e. 0).
|
||||
*
|
||||
* @param positions list of non-negative integer positions. Positions start at 0 from the least significant bit.
|
||||
* Must not be {@literal null} nor contain {@literal null} elements.
|
||||
* @return target {@link Criteria}.
|
||||
* @throws IllegalArgumentException when positions is {@literal null} or contains {@literal null} elements.
|
||||
* @see <a href="https://docs.mongodb.com/manual/reference/operator/query/bitsAnyClear/">MongoDB Query operator:
|
||||
* $bitsAnyClear</a>
|
||||
* @since 2.1
|
||||
*/
|
||||
Criteria anyClear(List<Integer> positions);
|
||||
|
||||
/**
|
||||
* Creates a criterion using {@literal $bitsAllSet} matching documents where any given bit positions are set (i.e.
|
||||
* 1).
|
||||
*
|
||||
* @param numericBitmask non-negative numeric bitmask.
|
||||
* @return target {@link Criteria}.
|
||||
* @see <a href="https://docs.mongodb.com/manual/reference/operator/query/bitsAnySet/">MongoDB Query operator:
|
||||
* $bitsAnySet</a>
|
||||
* @since 2.1
|
||||
*/
|
||||
Criteria anySet(int numericBitmask);
|
||||
|
||||
/**
|
||||
* Creates a criterion using {@literal $bitsAnySet} matching documents where any given bit positions are set (i.e.
|
||||
* 1).
|
||||
*
|
||||
* @param bitmask string representation of a bitmask that will be converted to its base64 encoded {@link Binary}
|
||||
* representation. Must not be {@literal null} nor empty.
|
||||
* @return target {@link Criteria}.
|
||||
* @throws IllegalArgumentException when bitmask is {@literal null} or empty.
|
||||
* @see <a href="https://docs.mongodb.com/manual/reference/operator/query/bitsAnySet/">MongoDB Query operator:
|
||||
* $bitsAnySet</a>
|
||||
* @since 2.1
|
||||
*/
|
||||
Criteria anySet(String bitmask);
|
||||
|
||||
/**
|
||||
* Creates a criterion using {@literal $bitsAnySet} matching documents where any given bit positions are set (i.e.
|
||||
* 1).
|
||||
*
|
||||
* @param positions list of non-negative integer positions. Positions start at 0 from the least significant bit.
|
||||
* Must not be {@literal null} nor contain {@literal null} elements.
|
||||
* @return target {@link Criteria}.
|
||||
* @throws IllegalArgumentException when positions is {@literal null} or contains {@literal null} elements.
|
||||
* @see <a href="https://docs.mongodb.com/manual/reference/operator/query/bitsAnySet/">MongoDB Query operator:
|
||||
* $bitsAnySet</a>
|
||||
* @since 2.1
|
||||
*/
|
||||
Criteria anySet(List<Integer> positions);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Default implementation of {@link BitwiseCriteriaOperators}.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @currentRead Beyond the Shadows - Brent Weeks
|
||||
*/
|
||||
private static class BitwiseCriteriaOperatorsImpl implements BitwiseCriteriaOperators {
|
||||
|
||||
private final Criteria target;
|
||||
|
||||
BitwiseCriteriaOperatorsImpl(Criteria target) {
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.query.BitwiseCriteriaOperators#allClear(int)
|
||||
*/
|
||||
@Override
|
||||
public Criteria allClear(int numericBitmask) {
|
||||
return numericBitmask("$bitsAllClear", numericBitmask);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.query.BitwiseCriteriaOperators#allClear(java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public Criteria allClear(String bitmask) {
|
||||
return stringBitmask("$bitsAllClear", bitmask);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.query.BitwiseCriteriaOperators#allClear(java.util.List)
|
||||
*/
|
||||
@Override
|
||||
public Criteria allClear(List<Integer> positions) {
|
||||
return positions("$bitsAllClear", positions);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.query.BitwiseCriteriaOperators#allSet(int)
|
||||
*/
|
||||
@Override
|
||||
public Criteria allSet(int numericBitmask) {
|
||||
return numericBitmask("$bitsAllSet", numericBitmask);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.query.BitwiseCriteriaOperators#allSet(java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public Criteria allSet(String bitmask) {
|
||||
return stringBitmask("$bitsAllSet", bitmask);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.query.BitwiseCriteriaOperators#allSet(java.util.List)
|
||||
*/
|
||||
@Override
|
||||
public Criteria allSet(List<Integer> positions) {
|
||||
return positions("$bitsAllSet", positions);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.query.BitwiseCriteriaOperators#anyClear(int)
|
||||
*/
|
||||
@Override
|
||||
public Criteria anyClear(int numericBitmask) {
|
||||
return numericBitmask("$bitsAnyClear", numericBitmask);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.query.BitwiseCriteriaOperators#anyClear(java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public Criteria anyClear(String bitmask) {
|
||||
return stringBitmask("$bitsAnyClear", bitmask);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.query.BitwiseCriteriaOperators#anyClear(java.util.List)
|
||||
*/
|
||||
@Override
|
||||
public Criteria anyClear(List<Integer> positions) {
|
||||
return positions("$bitsAnyClear", positions);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.query.BitwiseCriteriaOperators#anySet(int)
|
||||
*/
|
||||
@Override
|
||||
public Criteria anySet(int numericBitmask) {
|
||||
return numericBitmask("$bitsAnySet", numericBitmask);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.query.BitwiseCriteriaOperators#anySet(java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public Criteria anySet(String bitmask) {
|
||||
return stringBitmask("$bitsAnySet", bitmask);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.query.BitwiseCriteriaOperators#anySet(java.util.Collection)
|
||||
*/
|
||||
@Override
|
||||
public Criteria anySet(List<Integer> positions) {
|
||||
return positions("$bitsAnySet", positions);
|
||||
}
|
||||
|
||||
private Criteria positions(String operator, List<Integer> positions) {
|
||||
|
||||
Assert.notNull(positions, "Positions must not be null!");
|
||||
Assert.noNullElements(positions.toArray(), "Positions must not contain null values.");
|
||||
|
||||
target.criteria.put(operator, positions);
|
||||
return target;
|
||||
}
|
||||
|
||||
private Criteria stringBitmask(String operator, String bitmask) {
|
||||
|
||||
Assert.hasText(bitmask, "Bitmask must not be null!");
|
||||
|
||||
target.criteria.put(operator, new Binary(Base64Utils.decodeFromString(bitmask)));
|
||||
return target;
|
||||
}
|
||||
|
||||
private Criteria numericBitmask(String operator, int bitmask) {
|
||||
|
||||
target.criteria.put(operator, bitmask);
|
||||
return target;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ public class Field {
|
||||
private final Map<String, Integer> criteria = new HashMap<String, Integer>();
|
||||
private final Map<String, Object> slices = new HashMap<String, Object>();
|
||||
private final Map<String, Criteria> elemMatchs = new HashMap<String, Criteria>();
|
||||
private @Nullable String postionKey;
|
||||
private @Nullable String positionKey;
|
||||
private int positionValue;
|
||||
|
||||
public Field include(String key) {
|
||||
@@ -78,7 +78,7 @@ public class Field {
|
||||
|
||||
Assert.hasText(field, "DocumentField must not be null or empty!");
|
||||
|
||||
postionKey = field;
|
||||
positionKey = field;
|
||||
positionValue = value;
|
||||
|
||||
return this;
|
||||
@@ -97,8 +97,8 @@ public class Field {
|
||||
document.put(entry.getKey(), new Document("$elemMatch", entry.getValue().getCriteriaObject()));
|
||||
}
|
||||
|
||||
if (postionKey != null) {
|
||||
document.put(postionKey + ".$", positionValue);
|
||||
if (positionKey != null) {
|
||||
document.put(positionKey + ".$", positionValue);
|
||||
}
|
||||
|
||||
return document;
|
||||
|
||||
@@ -24,10 +24,12 @@ import org.bson.Document;
|
||||
import org.springframework.data.domain.Range;
|
||||
import org.springframework.data.mongodb.core.schema.TypedJsonSchemaObject.ArrayJsonSchemaObject;
|
||||
import org.springframework.data.mongodb.core.schema.TypedJsonSchemaObject.BooleanJsonSchemaObject;
|
||||
import org.springframework.data.mongodb.core.schema.TypedJsonSchemaObject.DateJsonSchemaObject;
|
||||
import org.springframework.data.mongodb.core.schema.TypedJsonSchemaObject.NullJsonSchemaObject;
|
||||
import org.springframework.data.mongodb.core.schema.TypedJsonSchemaObject.NumericJsonSchemaObject;
|
||||
import org.springframework.data.mongodb.core.schema.TypedJsonSchemaObject.ObjectJsonSchemaObject;
|
||||
import org.springframework.data.mongodb.core.schema.TypedJsonSchemaObject.StringJsonSchemaObject;
|
||||
import org.springframework.data.mongodb.core.schema.TypedJsonSchemaObject.TimestampJsonSchemaObject;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
@@ -927,4 +929,64 @@ public class IdentifiableJsonSchemaProperty<T extends JsonSchemaObject> implemen
|
||||
return new NullJsonSchemaProperty(identifier, jsonSchemaObjectDelegate.generatedDescription());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience {@link JsonSchemaProperty} implementation for a {@code type : 'date'} property.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.1
|
||||
*/
|
||||
public static class DateJsonSchemaProperty extends IdentifiableJsonSchemaProperty<DateJsonSchemaObject> {
|
||||
|
||||
DateJsonSchemaProperty(String identifier, DateJsonSchemaObject schemaObject) {
|
||||
super(identifier, schemaObject);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param description must not be {@literal null}.
|
||||
* @return new instance of {@link DateJsonSchemaProperty}.
|
||||
* @see DateJsonSchemaProperty#description(String)
|
||||
*/
|
||||
public DateJsonSchemaProperty description(String description) {
|
||||
return new DateJsonSchemaProperty(identifier, jsonSchemaObjectDelegate.description(description));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return new instance of {@link DateJsonSchemaProperty}.
|
||||
* @see DateJsonSchemaProperty#generateDescription()
|
||||
*/
|
||||
public DateJsonSchemaProperty generatedDescription() {
|
||||
return new DateJsonSchemaProperty(identifier, jsonSchemaObjectDelegate.generatedDescription());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience {@link JsonSchemaProperty} implementation for a {@code type : 'timestamp'} property.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 2.1
|
||||
*/
|
||||
public static class TimestampJsonSchemaProperty extends IdentifiableJsonSchemaProperty<TimestampJsonSchemaObject> {
|
||||
|
||||
TimestampJsonSchemaProperty(String identifier, TimestampJsonSchemaObject schemaObject) {
|
||||
super(identifier, schemaObject);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param description must not be {@literal null}.
|
||||
* @return new instance of {@link TimestampJsonSchemaProperty}.
|
||||
* @see TimestampJsonSchemaProperty#description(String)
|
||||
*/
|
||||
public TimestampJsonSchemaProperty description(String description) {
|
||||
return new TimestampJsonSchemaProperty(identifier, jsonSchemaObjectDelegate.description(description));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return new instance of {@link TimestampJsonSchemaProperty}.
|
||||
* @see TimestampJsonSchemaProperty#generateDescription()
|
||||
*/
|
||||
public TimestampJsonSchemaProperty generatedDescription() {
|
||||
return new TimestampJsonSchemaProperty(identifier, jsonSchemaObjectDelegate.generatedDescription());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,10 +29,12 @@ import org.bson.Document;
|
||||
import org.bson.types.ObjectId;
|
||||
import org.springframework.data.mongodb.core.schema.TypedJsonSchemaObject.ArrayJsonSchemaObject;
|
||||
import org.springframework.data.mongodb.core.schema.TypedJsonSchemaObject.BooleanJsonSchemaObject;
|
||||
import org.springframework.data.mongodb.core.schema.TypedJsonSchemaObject.DateJsonSchemaObject;
|
||||
import org.springframework.data.mongodb.core.schema.TypedJsonSchemaObject.NullJsonSchemaObject;
|
||||
import org.springframework.data.mongodb.core.schema.TypedJsonSchemaObject.NumericJsonSchemaObject;
|
||||
import org.springframework.data.mongodb.core.schema.TypedJsonSchemaObject.ObjectJsonSchemaObject;
|
||||
import org.springframework.data.mongodb.core.schema.TypedJsonSchemaObject.StringJsonSchemaObject;
|
||||
import org.springframework.data.mongodb.core.schema.TypedJsonSchemaObject.TimestampJsonSchemaObject;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
@@ -125,6 +127,24 @@ public interface JsonSchemaObject {
|
||||
return new NullJsonSchemaObject();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link JsonSchemaObject} of {@code type : 'date'}.
|
||||
*
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
static DateJsonSchemaObject date() {
|
||||
return new DateJsonSchemaObject();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link JsonSchemaObject} of {@code type : 'timestamp'}.
|
||||
*
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
static TimestampJsonSchemaObject timestamp() {
|
||||
return new TimestampJsonSchemaObject();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link JsonSchemaObject} of given {@link Type}.
|
||||
*
|
||||
|
||||
@@ -20,10 +20,12 @@ import lombok.RequiredArgsConstructor;
|
||||
|
||||
import org.springframework.data.mongodb.core.schema.IdentifiableJsonSchemaProperty.ArrayJsonSchemaProperty;
|
||||
import org.springframework.data.mongodb.core.schema.IdentifiableJsonSchemaProperty.BooleanJsonSchemaProperty;
|
||||
import org.springframework.data.mongodb.core.schema.IdentifiableJsonSchemaProperty.DateJsonSchemaProperty;
|
||||
import org.springframework.data.mongodb.core.schema.IdentifiableJsonSchemaProperty.NullJsonSchemaProperty;
|
||||
import org.springframework.data.mongodb.core.schema.IdentifiableJsonSchemaProperty.NumericJsonSchemaProperty;
|
||||
import org.springframework.data.mongodb.core.schema.IdentifiableJsonSchemaProperty.ObjectJsonSchemaProperty;
|
||||
import org.springframework.data.mongodb.core.schema.IdentifiableJsonSchemaProperty.StringJsonSchemaProperty;
|
||||
import org.springframework.data.mongodb.core.schema.IdentifiableJsonSchemaProperty.TimestampJsonSchemaProperty;
|
||||
import org.springframework.data.mongodb.core.schema.IdentifiableJsonSchemaProperty.UntypedJsonSchemaProperty;
|
||||
import org.springframework.data.mongodb.core.schema.TypedJsonSchemaObject.NumericJsonSchemaObject;
|
||||
import org.springframework.data.mongodb.core.schema.TypedJsonSchemaObject.ObjectJsonSchemaObject;
|
||||
@@ -150,7 +152,7 @@ public interface JsonSchemaProperty extends JsonSchemaObject {
|
||||
*
|
||||
* @param identifier the {@literal property} name or {@literal patternProperty} regex. Must not be {@literal null} nor
|
||||
* {@literal empty}.
|
||||
* @return new instance of {@link ArrayJsonSchemaProperty}.
|
||||
* @return new instance of {@link BooleanJsonSchemaProperty}.
|
||||
*/
|
||||
static BooleanJsonSchemaProperty bool(String identifier) {
|
||||
return new BooleanJsonSchemaProperty(identifier, JsonSchemaObject.bool());
|
||||
@@ -161,12 +163,34 @@ public interface JsonSchemaProperty extends JsonSchemaObject {
|
||||
*
|
||||
* @param identifier the {@literal property} name or {@literal patternProperty} regex. Must not be {@literal null} nor
|
||||
* {@literal empty}.
|
||||
* @return new instance of {@link ArrayJsonSchemaProperty}.
|
||||
* @return new instance of {@link NullJsonSchemaProperty}.
|
||||
*/
|
||||
static NullJsonSchemaProperty nil(String identifier) {
|
||||
return new NullJsonSchemaProperty(identifier, JsonSchemaObject.nil());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link DateJsonSchemaProperty} with given {@literal identifier} of {@code type : 'date'}.
|
||||
*
|
||||
* @param identifier the {@literal property} name or {@literal patternProperty} regex. Must not be {@literal null} nor
|
||||
* {@literal empty}.
|
||||
* @return new instance of {@link DateJsonSchemaProperty}.
|
||||
*/
|
||||
static DateJsonSchemaProperty date(String identifier) {
|
||||
return new DateJsonSchemaProperty(identifier, JsonSchemaObject.date());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link TimestampJsonSchemaProperty} with given {@literal identifier} of {@code type : 'timestamp'}.
|
||||
*
|
||||
* @param identifier the {@literal property} name or {@literal patternProperty} regex. Must not be {@literal null} nor
|
||||
* {@literal empty}.
|
||||
* @return new instance of {@link TimestampJsonSchemaProperty}.
|
||||
*/
|
||||
static TimestampJsonSchemaProperty timestamp(String identifier) {
|
||||
return new TimestampJsonSchemaProperty(identifier, JsonSchemaObject.timestamp());
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a builder to create a {@link JsonSchemaProperty}.
|
||||
*
|
||||
|
||||
@@ -1447,4 +1447,189 @@ public class TypedJsonSchemaObject extends UntypedJsonSchemaObject {
|
||||
return "Must be null.";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link JsonSchemaObject} implementation of {@code type : 'date'} schema elements.<br />
|
||||
* Provides programmatic access to schema specifics via a fluent API producing immutable {@link JsonSchemaObject
|
||||
* schema objects}.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.1
|
||||
*/
|
||||
static class DateJsonSchemaObject extends TypedJsonSchemaObject {
|
||||
|
||||
DateJsonSchemaObject() {
|
||||
this(null, false, null);
|
||||
}
|
||||
|
||||
private DateJsonSchemaObject(@Nullable String description, boolean generateDescription,
|
||||
@Nullable Restrictions restrictions) {
|
||||
super(Type.dateType(), description, generateDescription, restrictions);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.schema.TypedJsonSchemaObject#possibleValues(java.util.Collection)
|
||||
*/
|
||||
@Override
|
||||
public DateJsonSchemaObject possibleValues(Collection<? extends Object> possibleValues) {
|
||||
return new DateJsonSchemaObject(description, generateDescription, restrictions.possibleValues(possibleValues));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.schema.TypedJsonSchemaObject#allOf(java.util.Collection)
|
||||
*/
|
||||
@Override
|
||||
public DateJsonSchemaObject allOf(Collection<JsonSchemaObject> allOf) {
|
||||
return new DateJsonSchemaObject(description, generateDescription, restrictions.allOf(allOf));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.schema.TypedJsonSchemaObject#anyOf(java.util.Collection)
|
||||
*/
|
||||
@Override
|
||||
public DateJsonSchemaObject anyOf(Collection<JsonSchemaObject> anyOf) {
|
||||
return new DateJsonSchemaObject(description, generateDescription, restrictions.anyOf(anyOf));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.schema.TypedJsonSchemaObject#oneOf(java.util.Collection)
|
||||
*/
|
||||
@Override
|
||||
public DateJsonSchemaObject oneOf(Collection<JsonSchemaObject> oneOf) {
|
||||
return new DateJsonSchemaObject(description, generateDescription, restrictions.oneOf(oneOf));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.schema.TypedJsonSchemaObject#notMatch(org.springframework.data.mongodb.core.schema.JsonSchemaObject)
|
||||
*/
|
||||
@Override
|
||||
public DateJsonSchemaObject notMatch(JsonSchemaObject notMatch) {
|
||||
return new DateJsonSchemaObject(description, generateDescription, restrictions.notMatch(notMatch));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.schema.TypedJsonSchemaObject#description(java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public DateJsonSchemaObject description(String description) {
|
||||
return new DateJsonSchemaObject(description, generateDescription, restrictions);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.schema.TypedJsonSchemaObject#generatedDescription()
|
||||
*/
|
||||
@Override
|
||||
public DateJsonSchemaObject generatedDescription() {
|
||||
return new DateJsonSchemaObject(description, true, restrictions);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.schema.TypedJsonSchemaObject#generateDescription()
|
||||
*/
|
||||
@Override
|
||||
protected String generateDescription() {
|
||||
return "Must be a date.";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link JsonSchemaObject} implementation of {@code type : 'timestamp'} schema elements.<br />
|
||||
* Provides programmatic access to schema specifics via a fluent API producing immutable {@link JsonSchemaObject
|
||||
* schema objects}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 2.1
|
||||
*/
|
||||
static class TimestampJsonSchemaObject extends TypedJsonSchemaObject {
|
||||
|
||||
TimestampJsonSchemaObject() {
|
||||
this(null, false, null);
|
||||
}
|
||||
|
||||
private TimestampJsonSchemaObject(@Nullable String description, boolean generateDescription,
|
||||
@Nullable Restrictions restrictions) {
|
||||
super(Type.timestampType(), description, generateDescription, restrictions);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.schema.TypedJsonSchemaObject#possibleValues(java.util.Collection)
|
||||
*/
|
||||
@Override
|
||||
public TimestampJsonSchemaObject possibleValues(Collection<? extends Object> possibleValues) {
|
||||
return new TimestampJsonSchemaObject(description, generateDescription,
|
||||
restrictions.possibleValues(possibleValues));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.schema.TypedJsonSchemaObject#allOf(java.util.Collection)
|
||||
*/
|
||||
@Override
|
||||
public TimestampJsonSchemaObject allOf(Collection<JsonSchemaObject> allOf) {
|
||||
return new TimestampJsonSchemaObject(description, generateDescription, restrictions.allOf(allOf));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.schema.TypedJsonSchemaObject#anyOf(java.util.Collection)
|
||||
*/
|
||||
@Override
|
||||
public TimestampJsonSchemaObject anyOf(Collection<JsonSchemaObject> anyOf) {
|
||||
return new TimestampJsonSchemaObject(description, generateDescription, restrictions.anyOf(anyOf));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.schema.TypedJsonSchemaObject#oneOf(java.util.Collection)
|
||||
*/
|
||||
@Override
|
||||
public TimestampJsonSchemaObject oneOf(Collection<JsonSchemaObject> oneOf) {
|
||||
return new TimestampJsonSchemaObject(description, generateDescription, restrictions.oneOf(oneOf));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.schema.TypedJsonSchemaObject#notMatch(org.springframework.data.mongodb.core.schema.JsonSchemaObject)
|
||||
*/
|
||||
@Override
|
||||
public TimestampJsonSchemaObject notMatch(JsonSchemaObject notMatch) {
|
||||
return new TimestampJsonSchemaObject(description, generateDescription, restrictions.notMatch(notMatch));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.schema.TypedJsonSchemaObject#description(java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public TimestampJsonSchemaObject description(String description) {
|
||||
return new TimestampJsonSchemaObject(description, generateDescription, restrictions);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.schema.TypedJsonSchemaObject#generatedDescription()
|
||||
*/
|
||||
@Override
|
||||
public TimestampJsonSchemaObject generatedDescription() {
|
||||
return new TimestampJsonSchemaObject(description, true, restrictions);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.schema.TypedJsonSchemaObject#generateDescription()
|
||||
*/
|
||||
@Override
|
||||
protected String generateDescription() {
|
||||
return "Must be a timestamp.";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ import com.mongodb.client.gridfs.GridFSFindIterable;
|
||||
* @author Thomas Darimont
|
||||
* @author Martin Baumgartner
|
||||
* @author Christoph Strobl
|
||||
* @author Hartmut Lang
|
||||
*/
|
||||
public interface GridFsOperations extends ResourcePatternResolver {
|
||||
|
||||
@@ -152,11 +153,21 @@ public interface GridFsOperations extends ResourcePatternResolver {
|
||||
* Returns the {@link GridFsResource} with the given file name.
|
||||
*
|
||||
* @param filename must not be {@literal null}.
|
||||
* @return the resource if it exists or {@literal null}.
|
||||
* @return the resource. Use {@link org.springframework.core.io.Resource#exists()} to check if the returned
|
||||
* {@link GridFsResource} is actually present.
|
||||
* @see ResourcePatternResolver#getResource(String)
|
||||
*/
|
||||
GridFsResource getResource(String filename);
|
||||
|
||||
/**
|
||||
* Returns the {@link GridFsResource} for a {@link com.mongodb.client.gridfs.model.GridFSFile}.
|
||||
*
|
||||
* @param file must not be {@literal null}.
|
||||
* @return the resource for the file.
|
||||
* @since 2.1
|
||||
*/
|
||||
GridFsResource getResource(com.mongodb.client.gridfs.model.GridFSFile file);
|
||||
|
||||
/**
|
||||
* Returns all {@link GridFsResource}s matching the given file name pattern.
|
||||
*
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
package org.springframework.data.mongodb.gridfs;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Optional;
|
||||
@@ -23,6 +24,8 @@ import java.util.Optional;
|
||||
import org.springframework.core.io.InputStreamResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.data.util.Optionals;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.mongodb.MongoGridFSException;
|
||||
import com.mongodb.client.gridfs.model.GridFSFile;
|
||||
@@ -38,8 +41,24 @@ import com.mongodb.client.gridfs.model.GridFSFile;
|
||||
public class GridFsResource extends InputStreamResource {
|
||||
|
||||
static final String CONTENT_TYPE_FIELD = "_contentType";
|
||||
private static final ByteArrayInputStream EMPTY_INPUT_STREAM = new ByteArrayInputStream(new byte[0]);
|
||||
|
||||
private final GridFSFile file;
|
||||
private final @Nullable GridFSFile file;
|
||||
private final String filename;
|
||||
|
||||
/**
|
||||
* Creates a new, absent {@link GridFsResource}.
|
||||
*
|
||||
* @param filename filename of the absent resource.
|
||||
* @since 2.1
|
||||
*/
|
||||
private GridFsResource(String filename) {
|
||||
|
||||
super(EMPTY_INPUT_STREAM, String.format("GridFs resource [%s]", filename));
|
||||
|
||||
this.file = null;
|
||||
this.filename = filename;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link GridFsResource} from the given {@link GridFSFile}.
|
||||
@@ -58,8 +77,35 @@ public class GridFsResource extends InputStreamResource {
|
||||
*/
|
||||
public GridFsResource(GridFSFile file, InputStream inputStream) {
|
||||
|
||||
super(inputStream);
|
||||
super(inputStream, String.format("GridFs resource [%s]", file.getFilename()));
|
||||
|
||||
this.file = file;
|
||||
this.filename = file.getFilename();
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain an absent {@link GridFsResource}.
|
||||
*
|
||||
* @param filename filename of the absent resource, must not be {@literal null}.
|
||||
* @return never {@literal null}.
|
||||
* @since 2.1
|
||||
*/
|
||||
public static GridFsResource absent(String filename) {
|
||||
|
||||
Assert.notNull(filename, "Filename must not be null");
|
||||
|
||||
return new GridFsResource(filename);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.core.io.InputStreamResource#getInputStream()
|
||||
*/
|
||||
@Override
|
||||
public InputStream getInputStream() throws IOException, IllegalStateException {
|
||||
|
||||
verifyExists();
|
||||
return super.getInputStream();
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -68,6 +114,8 @@ public class GridFsResource extends InputStreamResource {
|
||||
*/
|
||||
@Override
|
||||
public long contentLength() throws IOException {
|
||||
|
||||
verifyExists();
|
||||
return file.getLength();
|
||||
}
|
||||
|
||||
@@ -77,7 +125,16 @@ public class GridFsResource extends InputStreamResource {
|
||||
*/
|
||||
@Override
|
||||
public String getFilename() throws IllegalStateException {
|
||||
return file.getFilename();
|
||||
return filename;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.core.io.AbstractResource#exists()
|
||||
*/
|
||||
@Override
|
||||
public boolean exists() {
|
||||
return file != null;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -86,15 +143,30 @@ public class GridFsResource extends InputStreamResource {
|
||||
*/
|
||||
@Override
|
||||
public long lastModified() throws IOException {
|
||||
|
||||
verifyExists();
|
||||
return file.getUploadDate().getTime();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.core.io.AbstractResource#getDescription()
|
||||
*/
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return String.format("GridFs resource [%s]", this.getFilename());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link Resource}'s id.
|
||||
*
|
||||
* @return never {@literal null}.
|
||||
* @throws IllegalStateException if the file does not {@link #exists()}.
|
||||
*/
|
||||
public Object getId() {
|
||||
|
||||
Assert.state(exists(), () -> String.format("%s does not exist.", getDescription()));
|
||||
|
||||
return file.getId();
|
||||
}
|
||||
|
||||
@@ -104,14 +176,24 @@ public class GridFsResource extends InputStreamResource {
|
||||
* @return never {@literal null}.
|
||||
* @throws com.mongodb.MongoGridFSException in case no content type declared on {@link GridFSFile#getMetadata()} nor
|
||||
* provided via {@link GridFSFile#getContentType()}.
|
||||
* @throws IllegalStateException if the file does not {@link #exists()}.
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public String getContentType() {
|
||||
|
||||
Assert.state(exists(), () -> String.format("%s does not exist.", getDescription()));
|
||||
|
||||
return Optionals
|
||||
.firstNonEmpty(
|
||||
() -> Optional.ofNullable(file.getMetadata()).map(it -> it.get(CONTENT_TYPE_FIELD, String.class)),
|
||||
() -> Optional.ofNullable(file.getContentType()))
|
||||
.orElseThrow(() -> new MongoGridFSException("No contentType data for this GridFS file"));
|
||||
}
|
||||
|
||||
private void verifyExists() throws FileNotFoundException {
|
||||
|
||||
if (!exists()) {
|
||||
throw new FileNotFoundException(String.format("%s does not exist.", getDescription()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,12 +51,13 @@ import com.mongodb.client.gridfs.model.GridFSUploadOptions;
|
||||
* @author Martin Baumgartner
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
* @author Hartmut Lang
|
||||
*/
|
||||
public class GridFsTemplate implements GridFsOperations, ResourcePatternResolver {
|
||||
|
||||
private final MongoDbFactory dbFactory;
|
||||
|
||||
private final String bucket;
|
||||
private final @Nullable String bucket;
|
||||
private final MongoConverter converter;
|
||||
private final QueryMapper queryMapper;
|
||||
|
||||
@@ -77,7 +78,7 @@ public class GridFsTemplate implements GridFsOperations, ResourcePatternResolver
|
||||
* @param converter must not be {@literal null}.
|
||||
* @param bucket
|
||||
*/
|
||||
public GridFsTemplate(MongoDbFactory dbFactory, MongoConverter converter, String bucket) {
|
||||
public GridFsTemplate(MongoDbFactory dbFactory, MongoConverter converter, @Nullable String bucket) {
|
||||
|
||||
Assert.notNull(dbFactory, "MongoDbFactory must not be null!");
|
||||
Assert.notNull(converter, "MongoConverter must not be null!");
|
||||
@@ -227,8 +228,19 @@ public class GridFsTemplate implements GridFsOperations, ResourcePatternResolver
|
||||
*/
|
||||
public GridFsResource getResource(String location) {
|
||||
|
||||
GridFSFile file = findOne(query(whereFilename().is(location)));
|
||||
return file != null ? new GridFsResource(file, getGridFs().openDownloadStream(location)) : null;
|
||||
return Optional.ofNullable(findOne(query(whereFilename().is(location)))).map(this::getResource)
|
||||
.orElseGet(() -> GridFsResource.absent(location));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.gridfs.GridFsOperations#getResource(com.mongodb.client.gridfs.model.GridFSFile)
|
||||
*/
|
||||
public GridFsResource getResource(GridFSFile file) {
|
||||
|
||||
Assert.notNull(file, "GridFSFile must not be null!");
|
||||
|
||||
return new GridFsResource(file, getGridFs().openDownloadStream(file.getFilename()));
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -246,13 +258,13 @@ public class GridFsTemplate implements GridFsOperations, ResourcePatternResolver
|
||||
if (path.isPattern()) {
|
||||
|
||||
GridFSFindIterable files = find(query(whereFilename().regex(path.toRegex())));
|
||||
List<GridFsResource> resources = new ArrayList<GridFsResource>();
|
||||
List<GridFsResource> resources = new ArrayList<>();
|
||||
|
||||
for (GridFSFile file : files) {
|
||||
resources.add(new GridFsResource(file, getGridFs().openDownloadStream(file.getFilename())));
|
||||
}
|
||||
|
||||
return resources.toArray(new GridFsResource[resources.size()]);
|
||||
return resources.toArray(new GridFsResource[0]);
|
||||
}
|
||||
|
||||
return new GridFsResource[] { getResource(locationPattern) };
|
||||
|
||||
@@ -63,11 +63,10 @@ public class MongoRepositoryBean<T> extends CdiRepositoryBean<T> {
|
||||
* @see org.springframework.data.repository.cdi.CdiRepositoryBean#create(javax.enterprise.context.spi.CreationalContext, java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
protected T create(CreationalContext<T> creationalContext, Class<T> repositoryType, Optional<Object> customImplementation) {
|
||||
protected T create(CreationalContext<T> creationalContext, Class<T> repositoryType) {
|
||||
|
||||
MongoOperations mongoOperations = getDependencyInstance(operations, MongoOperations.class);
|
||||
MongoRepositoryFactory factory = new MongoRepositoryFactory(mongoOperations);
|
||||
|
||||
return customImplementation.isPresent() ? factory.getRepository(repositoryType, customImplementation.get()) : factory.getRepository(repositoryType);
|
||||
return create(() -> new MongoRepositoryFactory(mongoOperations), repositoryType);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,8 +15,9 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.repository.query;
|
||||
|
||||
import org.springframework.data.mongodb.core.ExecutableFindOperation.FindWithProjection;
|
||||
import org.springframework.data.mongodb.core.ExecutableFindOperation.ExecutableFind;
|
||||
import org.springframework.data.mongodb.core.ExecutableFindOperation.FindWithQuery;
|
||||
import org.springframework.data.mongodb.core.ExecutableFindOperation.TerminatingFind;
|
||||
import org.springframework.data.mongodb.core.MongoOperations;
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
import org.springframework.data.mongodb.repository.query.MongoQueryExecution.DeleteExecution;
|
||||
@@ -27,7 +28,6 @@ import org.springframework.data.mongodb.repository.query.MongoQueryExecution.Sli
|
||||
import org.springframework.data.repository.query.ParameterAccessor;
|
||||
import org.springframework.data.repository.query.RepositoryQuery;
|
||||
import org.springframework.data.repository.query.ResultProcessor;
|
||||
import org.springframework.data.repository.query.ReturnedType;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
@@ -42,7 +42,7 @@ public abstract class AbstractMongoQuery implements RepositoryQuery {
|
||||
|
||||
private final MongoQueryMethod method;
|
||||
private final MongoOperations operations;
|
||||
private final FindWithProjection<?> findOperationWithProjection;
|
||||
private final ExecutableFind<?> executableFind;
|
||||
|
||||
/**
|
||||
* Creates a new {@link AbstractMongoQuery} from the given {@link MongoQueryMethod} and {@link MongoOperations}.
|
||||
@@ -58,11 +58,10 @@ public abstract class AbstractMongoQuery implements RepositoryQuery {
|
||||
this.method = method;
|
||||
this.operations = operations;
|
||||
|
||||
ReturnedType returnedType = method.getResultProcessor().getReturnedType();
|
||||
MongoEntityMetadata<?> metadata = method.getEntityInformation();
|
||||
Class<?> type = metadata.getCollectionEntity().getType();
|
||||
|
||||
this.findOperationWithProjection = operations//
|
||||
.query(returnedType.getDomainType())//
|
||||
.inCollection(method.getEntityInformation().getCollectionName());
|
||||
this.executableFind = operations.query(type);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -90,8 +89,8 @@ public abstract class AbstractMongoQuery implements RepositoryQuery {
|
||||
Class<?> typeToRead = processor.getReturnedType().getTypeToRead();
|
||||
|
||||
FindWithQuery<?> find = typeToRead == null //
|
||||
? findOperationWithProjection //
|
||||
: findOperationWithProjection.as(typeToRead);
|
||||
? executableFind //
|
||||
: executableFind.as(typeToRead);
|
||||
|
||||
MongoQueryExecution execution = getExecution(accessor, find);
|
||||
|
||||
@@ -119,7 +118,11 @@ public abstract class AbstractMongoQuery implements RepositoryQuery {
|
||||
} else if (isExistsQuery()) {
|
||||
return q -> operation.matching(q).exists();
|
||||
} else {
|
||||
return q -> operation.matching(q).oneValue();
|
||||
return q -> {
|
||||
|
||||
TerminatingFind<?> find = operation.matching(q);
|
||||
return isLimiting() ? find.firstValue() : find.oneValue();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,4 +177,12 @@ public abstract class AbstractMongoQuery implements RepositoryQuery {
|
||||
* @since 1.5
|
||||
*/
|
||||
protected abstract boolean isDeleteQuery();
|
||||
|
||||
/**
|
||||
* Return whether the query has an explicit limit set.
|
||||
*
|
||||
* @return
|
||||
* @since 2.0.4
|
||||
*/
|
||||
protected abstract boolean isLimiting();
|
||||
}
|
||||
|
||||
@@ -22,18 +22,20 @@ import org.reactivestreams.Publisher;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.data.convert.EntityInstantiators;
|
||||
import org.springframework.data.mongodb.core.MongoOperations;
|
||||
import org.springframework.data.mongodb.core.ReactiveFindOperation.FindWithProjection;
|
||||
import org.springframework.data.mongodb.core.ReactiveFindOperation.FindWithQuery;
|
||||
import org.springframework.data.mongodb.core.ReactiveFindOperation.TerminatingFind;
|
||||
import org.springframework.data.mongodb.core.ReactiveMongoOperations;
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
import org.springframework.data.mongodb.repository.query.ReactiveMongoQueryExecution.CollectionExecution;
|
||||
import org.springframework.data.mongodb.repository.query.ReactiveMongoQueryExecution.DeleteExecution;
|
||||
import org.springframework.data.mongodb.repository.query.ReactiveMongoQueryExecution.GeoNearExecution;
|
||||
import org.springframework.data.mongodb.repository.query.ReactiveMongoQueryExecution.ResultProcessingConverter;
|
||||
import org.springframework.data.mongodb.repository.query.ReactiveMongoQueryExecution.ResultProcessingExecution;
|
||||
import org.springframework.data.mongodb.repository.query.ReactiveMongoQueryExecution.SingleEntityExecution;
|
||||
import org.springframework.data.mongodb.repository.query.ReactiveMongoQueryExecution.TailExecution;
|
||||
import org.springframework.data.repository.query.ParameterAccessor;
|
||||
import org.springframework.data.repository.query.RepositoryQuery;
|
||||
import org.springframework.data.repository.query.ResultProcessor;
|
||||
import org.springframework.data.repository.query.ReturnedType;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
@@ -48,6 +50,7 @@ public abstract class AbstractReactiveMongoQuery implements RepositoryQuery {
|
||||
private final ReactiveMongoQueryMethod method;
|
||||
private final ReactiveMongoOperations operations;
|
||||
private final EntityInstantiators instantiators;
|
||||
private final FindWithProjection<?> findOperationWithProjection;
|
||||
|
||||
/**
|
||||
* Creates a new {@link AbstractReactiveMongoQuery} from the given {@link MongoQueryMethod} and
|
||||
@@ -64,6 +67,11 @@ public abstract class AbstractReactiveMongoQuery implements RepositoryQuery {
|
||||
this.method = method;
|
||||
this.operations = operations;
|
||||
this.instantiators = new EntityInstantiators();
|
||||
|
||||
MongoEntityMetadata<?> metadata = method.getEntityInformation();
|
||||
Class<?> type = metadata.getCollectionEntity().getType();
|
||||
|
||||
this.findOperationWithProjection = operations.query(type);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -103,10 +111,16 @@ public abstract class AbstractReactiveMongoQuery implements RepositoryQuery {
|
||||
applyQueryMetaAttributesWhenPresent(query);
|
||||
|
||||
ResultProcessor processor = method.getResultProcessor().withDynamicProjection(parameterAccessor);
|
||||
Class<?> typeToRead = processor.getReturnedType().getTypeToRead();
|
||||
|
||||
FindWithQuery<?> find = typeToRead == null //
|
||||
? findOperationWithProjection //
|
||||
: findOperationWithProjection.as(typeToRead);
|
||||
|
||||
String collection = method.getEntityInformation().getCollectionName();
|
||||
|
||||
ReactiveMongoQueryExecution execution = getExecution(query, parameterAccessor,
|
||||
new ResultProcessingConverter(processor, operations, instantiators));
|
||||
new ResultProcessingConverter(processor, operations, instantiators), find);
|
||||
|
||||
return execution.execute(query, processor.getReturnedType().getDomainType(), collection);
|
||||
}
|
||||
@@ -120,11 +134,11 @@ public abstract class AbstractReactiveMongoQuery implements RepositoryQuery {
|
||||
* @return
|
||||
*/
|
||||
private ReactiveMongoQueryExecution getExecution(Query query, MongoParameterAccessor accessor,
|
||||
Converter<Object, Object> resultProcessing) {
|
||||
return new ResultProcessingExecution(getExecutionToWrap(accessor), resultProcessing);
|
||||
Converter<Object, Object> resultProcessing, FindWithQuery<?> operation) {
|
||||
return new ResultProcessingExecution(getExecutionToWrap(accessor, operation), resultProcessing);
|
||||
}
|
||||
|
||||
private ReactiveMongoQueryExecution getExecutionToWrap(MongoParameterAccessor accessor) {
|
||||
private ReactiveMongoQueryExecution getExecutionToWrap(MongoParameterAccessor accessor, FindWithQuery<?> operation) {
|
||||
|
||||
if (isDeleteQuery()) {
|
||||
return new DeleteExecution(operations, method);
|
||||
@@ -133,9 +147,20 @@ public abstract class AbstractReactiveMongoQuery implements RepositoryQuery {
|
||||
} else if (isTailable(method)) {
|
||||
return new TailExecution(operations, accessor.getPageable());
|
||||
} else if (method.isCollectionQuery()) {
|
||||
return new CollectionExecution(operations, accessor.getPageable());
|
||||
return (q, t, c) -> operation.matching(q.with(accessor.getPageable())).all();
|
||||
} else if (isCountQuery()) {
|
||||
return (q, t, c) -> operation.matching(q).count();
|
||||
} else {
|
||||
return new SingleEntityExecution(operations, isCountQuery());
|
||||
return (q, t, c) -> {
|
||||
|
||||
TerminatingFind<?> find = operation.matching(q);
|
||||
|
||||
if (isCountQuery()) {
|
||||
return find.count();
|
||||
}
|
||||
|
||||
return isLimiting() ? find.first() : find.one();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,4 +211,12 @@ public abstract class AbstractReactiveMongoQuery implements RepositoryQuery {
|
||||
* @since 1.5
|
||||
*/
|
||||
protected abstract boolean isDeleteQuery();
|
||||
|
||||
/**
|
||||
* Return whether the query has an explicit limit set.
|
||||
*
|
||||
* @return
|
||||
* @since 2.0.4
|
||||
*/
|
||||
protected abstract boolean isLimiting();
|
||||
}
|
||||
|
||||
@@ -19,19 +19,27 @@ import lombok.EqualsAndHashCode;
|
||||
import lombok.Value;
|
||||
import lombok.experimental.UtilityClass;
|
||||
|
||||
import java.io.StringWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.xml.bind.DatatypeConverter;
|
||||
|
||||
import org.bson.BSON;
|
||||
import org.bson.codecs.BinaryCodec;
|
||||
import org.bson.codecs.Codec;
|
||||
import org.bson.codecs.UuidCodec;
|
||||
import org.bson.json.JsonWriter;
|
||||
import org.bson.types.Binary;
|
||||
import org.springframework.data.mongodb.CodecRegistryProvider;
|
||||
import org.springframework.data.mongodb.repository.query.StringBasedMongoQuery.ParameterBinding;
|
||||
import org.springframework.data.repository.query.EvaluationContextProvider;
|
||||
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
@@ -41,6 +49,7 @@ import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.mongodb.DBObject;
|
||||
import com.mongodb.MongoClient;
|
||||
import com.mongodb.util.JSON;
|
||||
|
||||
/**
|
||||
@@ -56,7 +65,8 @@ import com.mongodb.util.JSON;
|
||||
class ExpressionEvaluatingParameterBinder {
|
||||
|
||||
private final SpelExpressionParser expressionParser;
|
||||
private final EvaluationContextProvider evaluationContextProvider;
|
||||
private final QueryMethodEvaluationContextProvider evaluationContextProvider;
|
||||
private final CodecRegistryProvider codecRegistryProvider;
|
||||
|
||||
/**
|
||||
* Creates new {@link ExpressionEvaluatingParameterBinder}
|
||||
@@ -65,13 +75,14 @@ class ExpressionEvaluatingParameterBinder {
|
||||
* @param evaluationContextProvider must not be {@literal null}.
|
||||
*/
|
||||
public ExpressionEvaluatingParameterBinder(SpelExpressionParser expressionParser,
|
||||
EvaluationContextProvider evaluationContextProvider) {
|
||||
QueryMethodEvaluationContextProvider evaluationContextProvider) {
|
||||
|
||||
Assert.notNull(expressionParser, "ExpressionParser must not be null!");
|
||||
Assert.notNull(evaluationContextProvider, "EvaluationContextProvider must not be null!");
|
||||
|
||||
this.expressionParser = expressionParser;
|
||||
this.evaluationContextProvider = evaluationContextProvider;
|
||||
this.codecRegistryProvider = () -> MongoClient.getDefaultCodecRegistry();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -212,18 +223,37 @@ class ExpressionEvaluatingParameterBinder {
|
||||
|
||||
if (value instanceof byte[]) {
|
||||
|
||||
String base64representation = DatatypeConverter.printBase64Binary((byte[]) value);
|
||||
|
||||
if (!binding.isQuoted()) {
|
||||
return "{ '$binary' : '" + base64representation + "', '$type' : '" + BSON.B_GENERAL + "'}";
|
||||
if (binding.isQuoted()) {
|
||||
return DatatypeConverter.printBase64Binary((byte[]) value);
|
||||
}
|
||||
|
||||
return base64representation;
|
||||
return encode(new Binary((byte[]) value), BinaryCodec::new);
|
||||
}
|
||||
|
||||
if (value instanceof UUID) {
|
||||
|
||||
if (binding.isQuoted()) {
|
||||
return value.toString();
|
||||
}
|
||||
|
||||
return encode((UUID) value, UuidCodec::new);
|
||||
}
|
||||
|
||||
return JSON.serialize(value);
|
||||
}
|
||||
|
||||
private <T> String encode(T value, Supplier<Codec<T>> defaultCodec) {
|
||||
|
||||
Codec<T> codec = codecRegistryProvider.getCodecFor((Class<T>) value.getClass()).orElseGet(defaultCodec);
|
||||
|
||||
StringWriter writer = new StringWriter();
|
||||
|
||||
codec.encode(new JsonWriter(writer), value, null);
|
||||
writer.flush();
|
||||
|
||||
return writer.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluates the given {@code expressionString}.
|
||||
*
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.repository.query;
|
||||
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
|
||||
import org.springframework.data.repository.core.EntityMetadata;
|
||||
|
||||
/**
|
||||
@@ -30,4 +31,12 @@ public interface MongoEntityMetadata<T> extends EntityMetadata<T> {
|
||||
* @return
|
||||
*/
|
||||
String getCollectionName();
|
||||
|
||||
/**
|
||||
* Returns the {@link MongoPersistentEntity} that supposed to determine the collection to be queried.
|
||||
*
|
||||
* @return
|
||||
* @since 2.0.4
|
||||
*/
|
||||
MongoPersistentEntity<?> getCollectionEntity();
|
||||
}
|
||||
|
||||
@@ -30,9 +30,9 @@ import org.springframework.data.geo.Distance;
|
||||
import org.springframework.data.geo.Metrics;
|
||||
import org.springframework.data.geo.Point;
|
||||
import org.springframework.data.geo.Shape;
|
||||
import org.springframework.data.mapping.PersistentPropertyPath;
|
||||
import org.springframework.data.mapping.PropertyPath;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.mapping.context.PersistentPropertyPath;
|
||||
import org.springframework.data.mongodb.core.index.GeoSpatialIndexType;
|
||||
import org.springframework.data.mongodb.core.index.GeoSpatialIndexed;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
|
||||
@@ -61,10 +61,10 @@ import org.springframework.util.ClassUtils;
|
||||
class MongoQueryCreator extends AbstractQueryCreator<Query, Criteria> {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MongoQueryCreator.class);
|
||||
private final MongoParameterAccessor accessor;
|
||||
private final boolean isGeoNearQuery;
|
||||
|
||||
private final MongoParameterAccessor accessor;
|
||||
private final MappingContext<?, MongoPersistentProperty> context;
|
||||
private final boolean isGeoNearQuery;
|
||||
|
||||
/**
|
||||
* Creates a new {@link MongoQueryCreator} from the given {@link PartTree}, {@link ConvertingParameterAccessor} and
|
||||
|
||||
@@ -160,4 +160,13 @@ public class PartTreeMongoQuery extends AbstractMongoQuery {
|
||||
protected boolean isDeleteQuery() {
|
||||
return tree.isDelete();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.repository.query.AbstractMongoQuery#isLimiting()
|
||||
*/
|
||||
@Override
|
||||
protected boolean isLimiting() {
|
||||
return tree.isLimiting();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,29 +44,13 @@ import com.mongodb.client.result.DeleteResult;
|
||||
* various flavors.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Christoph Strobl
|
||||
* @since 2.0
|
||||
*/
|
||||
interface ReactiveMongoQueryExecution {
|
||||
|
||||
Object execute(Query query, Class<?> type, String collection);
|
||||
|
||||
/**
|
||||
* {@link ReactiveMongoQueryExecution} for collection returning queries.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
final class CollectionExecution implements ReactiveMongoQueryExecution {
|
||||
|
||||
private final @NonNull ReactiveMongoOperations operations;
|
||||
private final Pageable pageable;
|
||||
|
||||
@Override
|
||||
public Object execute(Query query, Class<?> type, String collection) {
|
||||
return operations.find(query.with(pageable), type, collection);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link ReactiveMongoQueryExecution} for collection returning queries using tailable cursors.
|
||||
*
|
||||
@@ -84,23 +68,6 @@ interface ReactiveMongoQueryExecution {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link ReactiveMongoQueryExecution} to return a single entity.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
final class SingleEntityExecution implements ReactiveMongoQueryExecution {
|
||||
|
||||
private final ReactiveMongoOperations operations;
|
||||
private final boolean countProjection;
|
||||
|
||||
@Override
|
||||
public Object execute(Query query, Class<?> type, String collection) {
|
||||
return countProjection ? operations.count(query, type, collection) : operations.findOne(query, type, collection);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link MongoQueryExecution} to execute geo-near queries.
|
||||
*
|
||||
|
||||
@@ -118,7 +118,7 @@ public class ReactivePartTreeMongoQuery extends AbstractReactiveMongoQuery {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.repository.query.AbstractMongoQuery#createCountQuery(org.springframework.data.mongodb.repository.query.ConvertingParameterAccessor)
|
||||
* @see org.springframework.data.mongodb.repository.query.AbstractReactiveMongoQuery#createCountQuery(org.springframework.data.mongodb.repository.query.ConvertingParameterAccessor)
|
||||
*/
|
||||
@Override
|
||||
protected Query createCountQuery(ConvertingParameterAccessor accessor) {
|
||||
@@ -127,7 +127,7 @@ public class ReactivePartTreeMongoQuery extends AbstractReactiveMongoQuery {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.repository.query.AbstractMongoQuery#isCountQuery()
|
||||
* @see org.springframework.data.mongodb.repository.query.AbstractReactiveMongoQuery#isCountQuery()
|
||||
*/
|
||||
@Override
|
||||
protected boolean isCountQuery() {
|
||||
@@ -136,10 +136,19 @@ public class ReactivePartTreeMongoQuery extends AbstractReactiveMongoQuery {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.repository.query.AbstractMongoQuery#isDeleteQuery()
|
||||
* @see org.springframework.data.mongodb.repository.query.AbstractReactiveMongoQuery#isDeleteQuery()
|
||||
*/
|
||||
@Override
|
||||
protected boolean isDeleteQuery() {
|
||||
return tree.isDelete();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.repository.query.AbstractReactiveMongoQuery#isLimiting()
|
||||
*/
|
||||
@Override
|
||||
protected boolean isLimiting() {
|
||||
return tree.isLimiting();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,8 @@ import org.springframework.data.mongodb.core.query.Query;
|
||||
import org.springframework.data.mongodb.repository.query.ExpressionEvaluatingParameterBinder.BindingContext;
|
||||
import org.springframework.data.mongodb.repository.query.StringBasedMongoQuery.ParameterBinding;
|
||||
import org.springframework.data.mongodb.repository.query.StringBasedMongoQuery.ParameterBindingParser;
|
||||
import org.springframework.data.repository.query.EvaluationContextProvider;
|
||||
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
|
||||
import org.springframework.data.spel.EvaluationContextProvider;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@@ -62,13 +63,13 @@ public class ReactiveStringBasedMongoQuery extends AbstractReactiveMongoQuery {
|
||||
* @param evaluationContextProvider must not be {@literal null}.
|
||||
*/
|
||||
public ReactiveStringBasedMongoQuery(ReactiveMongoQueryMethod method, ReactiveMongoOperations mongoOperations,
|
||||
SpelExpressionParser expressionParser, EvaluationContextProvider evaluationContextProvider) {
|
||||
SpelExpressionParser expressionParser, QueryMethodEvaluationContextProvider evaluationContextProvider) {
|
||||
this(method.getAnnotatedQuery(), method, mongoOperations, expressionParser, evaluationContextProvider);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link ReactiveStringBasedMongoQuery} for the given {@link String}, {@link MongoQueryMethod},
|
||||
* {@link MongoOperations}, {@link SpelExpressionParser} and {@link EvaluationContextProvider}.
|
||||
* {@link MongoOperations}, {@link SpelExpressionParser} and {@link QueryMethodEvaluationContextProvider}.
|
||||
*
|
||||
* @param query must not be {@literal null}.
|
||||
* @param method must not be {@literal null}.
|
||||
@@ -77,7 +78,7 @@ public class ReactiveStringBasedMongoQuery extends AbstractReactiveMongoQuery {
|
||||
*/
|
||||
public ReactiveStringBasedMongoQuery(String query, ReactiveMongoQueryMethod method,
|
||||
ReactiveMongoOperations mongoOperations, SpelExpressionParser expressionParser,
|
||||
EvaluationContextProvider evaluationContextProvider) {
|
||||
QueryMethodEvaluationContextProvider evaluationContextProvider) {
|
||||
|
||||
super(method, mongoOperations);
|
||||
|
||||
@@ -104,7 +105,7 @@ public class ReactiveStringBasedMongoQuery extends AbstractReactiveMongoQuery {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.repository.query.AbstractMongoQuery#createQuery(org.springframework.data.mongodb.repository.query.ConvertingParameterAccessor)
|
||||
* @see org.springframework.data.mongodb.repository.query.AbstractReactiveMongoQuery#createQuery(org.springframework.data.mongodb.repository.query.ConvertingParameterAccessor)
|
||||
*/
|
||||
@Override
|
||||
protected Query createQuery(ConvertingParameterAccessor accessor) {
|
||||
@@ -125,7 +126,7 @@ public class ReactiveStringBasedMongoQuery extends AbstractReactiveMongoQuery {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.repository.query.AbstractMongoQuery#isCountQuery()
|
||||
* @see org.springframework.data.mongodb.repository.query.AbstractReactiveMongoQuery#isCountQuery()
|
||||
*/
|
||||
@Override
|
||||
protected boolean isCountQuery() {
|
||||
@@ -134,11 +135,20 @@ public class ReactiveStringBasedMongoQuery extends AbstractReactiveMongoQuery {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.repository.query.AbstractMongoQuery#isDeleteQuery()
|
||||
* @see org.springframework.data.mongodb.repository.query.AbstractReactiveMongoQuery#isDeleteQuery()
|
||||
*/
|
||||
@Override
|
||||
protected boolean isDeleteQuery() {
|
||||
return this.isDeleteQuery;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.repository.query.AbstractReactiveMongoQuery#isLimiting()
|
||||
*/
|
||||
@Override
|
||||
protected boolean isLimiting() {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.repository.query;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@@ -26,7 +28,7 @@ import org.springframework.util.Assert;
|
||||
class SimpleMongoEntityMetadata<T> implements MongoEntityMetadata<T> {
|
||||
|
||||
private final Class<T> type;
|
||||
private final MongoPersistentEntity<?> collectionEntity;
|
||||
private final @Getter MongoPersistentEntity<?> collectionEntity;
|
||||
|
||||
/**
|
||||
* Creates a new {@link SimpleMongoEntityMetadata} using the given type and {@link MongoPersistentEntity} to use for
|
||||
|
||||
@@ -28,7 +28,7 @@ import org.springframework.data.mongodb.core.MongoOperations;
|
||||
import org.springframework.data.mongodb.core.query.BasicQuery;
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
import org.springframework.data.mongodb.repository.query.ExpressionEvaluatingParameterBinder.BindingContext;
|
||||
import org.springframework.data.repository.query.EvaluationContextProvider;
|
||||
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -62,7 +62,8 @@ public class StringBasedMongoQuery extends AbstractMongoQuery {
|
||||
private final ExpressionEvaluatingParameterBinder parameterBinder;
|
||||
|
||||
/**
|
||||
* Creates a new {@link StringBasedMongoQuery} for the given {@link MongoQueryMethod} and {@link MongoOperations}.
|
||||
* Creates a new {@link StringBasedMongoQuery} for the given {@link MongoQueryMethod}, {@link MongoOperations},
|
||||
* {@link SpelExpressionParser} and {@link QueryMethodEvaluationContextProvider}.
|
||||
*
|
||||
* @param method must not be {@literal null}.
|
||||
* @param mongoOperations must not be {@literal null}.
|
||||
@@ -70,13 +71,13 @@ public class StringBasedMongoQuery extends AbstractMongoQuery {
|
||||
* @param evaluationContextProvider must not be {@literal null}.
|
||||
*/
|
||||
public StringBasedMongoQuery(MongoQueryMethod method, MongoOperations mongoOperations,
|
||||
SpelExpressionParser expressionParser, EvaluationContextProvider evaluationContextProvider) {
|
||||
SpelExpressionParser expressionParser, QueryMethodEvaluationContextProvider evaluationContextProvider) {
|
||||
this(method.getAnnotatedQuery(), method, mongoOperations, expressionParser, evaluationContextProvider);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link StringBasedMongoQuery} for the given {@link String}, {@link MongoQueryMethod},
|
||||
* {@link MongoOperations}, {@link SpelExpressionParser} and {@link EvaluationContextProvider}.
|
||||
* {@link MongoOperations}, {@link SpelExpressionParser} and {@link QueryMethodEvaluationContextProvider}.
|
||||
*
|
||||
* @param query must not be {@literal null}.
|
||||
* @param method must not be {@literal null}.
|
||||
@@ -84,7 +85,7 @@ public class StringBasedMongoQuery extends AbstractMongoQuery {
|
||||
* @param expressionParser must not be {@literal null}.
|
||||
*/
|
||||
public StringBasedMongoQuery(String query, MongoQueryMethod method, MongoOperations mongoOperations,
|
||||
SpelExpressionParser expressionParser, EvaluationContextProvider evaluationContextProvider) {
|
||||
SpelExpressionParser expressionParser, QueryMethodEvaluationContextProvider evaluationContextProvider) {
|
||||
|
||||
super(method, mongoOperations);
|
||||
|
||||
@@ -174,6 +175,15 @@ public class StringBasedMongoQuery extends AbstractMongoQuery {
|
||||
return countBooleanValues(isCountQuery, isExistsQuery, isDeleteQuery) > 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.repository.query.AbstractMongoQuery#isLimiting()
|
||||
*/
|
||||
@Override
|
||||
protected boolean isLimiting() {
|
||||
return false;
|
||||
}
|
||||
|
||||
private static int countBooleanValues(boolean... values) {
|
||||
|
||||
int count = 0;
|
||||
|
||||
@@ -39,9 +39,9 @@ import org.springframework.data.repository.core.RepositoryMetadata;
|
||||
import org.springframework.data.repository.core.support.RepositoryComposition.RepositoryFragments;
|
||||
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
|
||||
import org.springframework.data.repository.core.support.RepositoryFragment;
|
||||
import org.springframework.data.repository.query.EvaluationContextProvider;
|
||||
import org.springframework.data.repository.query.QueryLookupStrategy;
|
||||
import org.springframework.data.repository.query.QueryLookupStrategy.Key;
|
||||
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
|
||||
import org.springframework.data.repository.query.RepositoryQuery;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.lang.Nullable;
|
||||
@@ -131,7 +131,7 @@ public class MongoRepositoryFactory extends RepositoryFactorySupport {
|
||||
*/
|
||||
@Override
|
||||
protected Optional<QueryLookupStrategy> getQueryLookupStrategy(@Nullable Key key,
|
||||
EvaluationContextProvider evaluationContextProvider) {
|
||||
QueryMethodEvaluationContextProvider evaluationContextProvider) {
|
||||
return Optional.of(new MongoQueryLookupStrategy(operations, evaluationContextProvider, mappingContext));
|
||||
}
|
||||
|
||||
@@ -160,10 +160,11 @@ public class MongoRepositoryFactory extends RepositoryFactorySupport {
|
||||
private static class MongoQueryLookupStrategy implements QueryLookupStrategy {
|
||||
|
||||
private final MongoOperations operations;
|
||||
private final EvaluationContextProvider evaluationContextProvider;
|
||||
private final QueryMethodEvaluationContextProvider evaluationContextProvider;
|
||||
MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext;
|
||||
|
||||
public MongoQueryLookupStrategy(MongoOperations operations, EvaluationContextProvider evaluationContextProvider,
|
||||
public MongoQueryLookupStrategy(MongoOperations operations,
|
||||
QueryMethodEvaluationContextProvider evaluationContextProvider,
|
||||
MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext) {
|
||||
|
||||
this.operations = operations;
|
||||
|
||||
@@ -59,7 +59,7 @@ public class QuerydslMongoPredicateExecutor<T> implements QuerydslPredicateExecu
|
||||
|
||||
/**
|
||||
* Creates a new {@link QuerydslMongoPredicateExecutor} for the given {@link MongoEntityInformation} and
|
||||
* {@link MongoTemplate}. Uses the {@link SimpleEntityPathResolver} to create an {@link EntityPath} for the given
|
||||
* {@link MongoOperations}. Uses the {@link SimpleEntityPathResolver} to create an {@link EntityPath} for the given
|
||||
* domain class.
|
||||
*
|
||||
* @param entityInformation must not be {@literal null}.
|
||||
@@ -72,7 +72,7 @@ public class QuerydslMongoPredicateExecutor<T> implements QuerydslPredicateExecu
|
||||
|
||||
/**
|
||||
* Creates a new {@link QuerydslMongoPredicateExecutor} for the given {@link MongoEntityInformation},
|
||||
* {@link MongoTemplate} and {@link EntityPathResolver}.
|
||||
* {@link MongoOperations} and {@link EntityPathResolver}.
|
||||
*
|
||||
* @param entityInformation must not be {@literal null}.
|
||||
* @param mongoOperations must not be {@literal null}.
|
||||
@@ -108,19 +108,19 @@ public class QuerydslMongoPredicateExecutor<T> implements QuerydslPredicateExecu
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.querydsl.QueryDslPredicateExecutor#findAll(com.mysema.query.types.Predicate)
|
||||
* @see org.springframework.data.querydsl.QuerydslPredicateExecutor#findAll(com.querydsl.core.types.Predicate)
|
||||
*/
|
||||
@Override
|
||||
public List<T> findAll(Predicate predicate) {
|
||||
|
||||
Assert.notNull(predicate, "Predicate must not be null!");
|
||||
|
||||
return createQueryFor(predicate).fetchResults().getResults();
|
||||
return createQueryFor(predicate).fetch();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.querydsl.QueryDslPredicateExecutor#findAll(com.mysema.query.types.Predicate, com.mysema.query.types.OrderSpecifier<?>[])
|
||||
* @see org.springframework.data.querydsl.QuerydslPredicateExecutor#findAll(com.querydsl.core.types.Predicate, com.querydsl.core.types.OrderSpecifier<?>[])
|
||||
*/
|
||||
@Override
|
||||
public List<T> findAll(Predicate predicate, OrderSpecifier<?>... orders) {
|
||||
@@ -128,12 +128,12 @@ public class QuerydslMongoPredicateExecutor<T> implements QuerydslPredicateExecu
|
||||
Assert.notNull(predicate, "Predicate must not be null!");
|
||||
Assert.notNull(orders, "Order specifiers must not be null!");
|
||||
|
||||
return createQueryFor(predicate).orderBy(orders).fetchResults().getResults();
|
||||
return createQueryFor(predicate).orderBy(orders).fetch();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.querydsl.QueryDslPredicateExecutor#findAll(com.mysema.query.types.Predicate, org.springframework.data.domain.Sort)
|
||||
* @see org.springframework.data.querydsl.QuerydslPredicateExecutor#findAll(com.querydsl.core.types.Predicate, org.springframework.data.domain.Sort)
|
||||
*/
|
||||
@Override
|
||||
public List<T> findAll(Predicate predicate, Sort sort) {
|
||||
@@ -141,24 +141,24 @@ public class QuerydslMongoPredicateExecutor<T> implements QuerydslPredicateExecu
|
||||
Assert.notNull(predicate, "Predicate must not be null!");
|
||||
Assert.notNull(sort, "Sort must not be null!");
|
||||
|
||||
return applySorting(createQueryFor(predicate), sort).fetchResults().getResults();
|
||||
return applySorting(createQueryFor(predicate), sort).fetch();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.querydsl.QueryDslPredicateExecutor#findAll(com.mysema.query.types.OrderSpecifier[])
|
||||
* @see org.springframework.data.querydsl.QuerydslPredicateExecutor#findAll(com.querydsl.core.types.OrderSpecifier[])
|
||||
*/
|
||||
@Override
|
||||
public Iterable<T> findAll(OrderSpecifier<?>... orders) {
|
||||
|
||||
Assert.notNull(orders, "Order specifiers must not be null!");
|
||||
|
||||
return createQuery().orderBy(orders).fetchResults().getResults();
|
||||
return createQuery().orderBy(orders).fetch();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.querydsl.QueryDslPredicateExecutor#findAll(com.mysema.query.types.Predicate, org.springframework.data.domain.Pageable)
|
||||
* @see org.springframework.data.querydsl.QuerydslPredicateExecutor#findAll(com.querydsl.core.types.Predicate, org.springframework.data.domain.Pageable)
|
||||
*/
|
||||
@Override
|
||||
public Page<T> findAll(Predicate predicate, Pageable pageable) {
|
||||
@@ -168,13 +168,12 @@ public class QuerydslMongoPredicateExecutor<T> implements QuerydslPredicateExecu
|
||||
|
||||
AbstractMongodbQuery<T, SpringDataMongodbQuery<T>> query = createQueryFor(predicate);
|
||||
|
||||
return PageableExecutionUtils.getPage(applyPagination(query, pageable).fetchResults().getResults(), pageable,
|
||||
() -> createQueryFor(predicate).fetchCount());
|
||||
return PageableExecutionUtils.getPage(applyPagination(query, pageable).fetch(), pageable, query::fetchCount);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.querydsl.QueryDslPredicateExecutor#count(com.mysema.query.types.Predicate)
|
||||
* @see org.springframework.data.querydsl.QuerydslPredicateExecutor#count(com.querydsl.core.types.Predicate)
|
||||
*/
|
||||
@Override
|
||||
public long count(Predicate predicate) {
|
||||
@@ -186,7 +185,7 @@ public class QuerydslMongoPredicateExecutor<T> implements QuerydslPredicateExecu
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.querydsl.QueryDslPredicateExecutor#exists(com.mysema.query.types.Predicate)
|
||||
* @see org.springframework.data.querydsl.QuerydslPredicateExecutor#exists(com.querydsl.core.types.Predicate)
|
||||
*/
|
||||
@Override
|
||||
public boolean exists(Predicate predicate) {
|
||||
@@ -197,7 +196,7 @@ public class QuerydslMongoPredicateExecutor<T> implements QuerydslPredicateExecu
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link MongodbQuery} for the given {@link Predicate}.
|
||||
* Creates a {@link AbstractMongodbQuery} for the given {@link Predicate}.
|
||||
*
|
||||
* @param predicate
|
||||
* @return
|
||||
@@ -207,12 +206,12 @@ public class QuerydslMongoPredicateExecutor<T> implements QuerydslPredicateExecu
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link MongodbQuery}.
|
||||
* Creates a {@link AbstractMongodbQuery}.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private AbstractMongodbQuery<T, SpringDataMongodbQuery<T>> createQuery() {
|
||||
return new SpringDataMongodbQuery<T>(mongoOperations, entityInformation.getJavaType());
|
||||
return new SpringDataMongodbQuery<>(mongoOperations, entityInformation.getJavaType());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -248,13 +247,13 @@ public class QuerydslMongoPredicateExecutor<T> implements QuerydslPredicateExecu
|
||||
return query;
|
||||
}
|
||||
|
||||
sort.stream().map(this::toOrder).forEach(it -> query.orderBy(it));
|
||||
sort.stream().map(this::toOrder).forEach(query::orderBy);
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms a plain {@link Order} into a QueryDsl specific {@link OrderSpecifier}.
|
||||
* Transforms a plain {@link Order} into a Querydsl specific {@link OrderSpecifier}.
|
||||
*
|
||||
* @param order
|
||||
* @return
|
||||
|
||||
@@ -36,9 +36,9 @@ import org.springframework.data.repository.core.NamedQueries;
|
||||
import org.springframework.data.repository.core.RepositoryInformation;
|
||||
import org.springframework.data.repository.core.RepositoryMetadata;
|
||||
import org.springframework.data.repository.core.support.ReactiveRepositoryFactorySupport;
|
||||
import org.springframework.data.repository.query.EvaluationContextProvider;
|
||||
import org.springframework.data.repository.query.QueryLookupStrategy;
|
||||
import org.springframework.data.repository.query.QueryLookupStrategy.Key;
|
||||
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
|
||||
import org.springframework.data.repository.query.RepositoryQuery;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.lang.Nullable;
|
||||
@@ -99,7 +99,7 @@ public class ReactiveMongoRepositoryFactory extends ReactiveRepositoryFactorySup
|
||||
*/
|
||||
@Override
|
||||
protected Optional<QueryLookupStrategy> getQueryLookupStrategy(@Nullable Key key,
|
||||
EvaluationContextProvider evaluationContextProvider) {
|
||||
QueryMethodEvaluationContextProvider evaluationContextProvider) {
|
||||
return Optional.of(new MongoQueryLookupStrategy(operations, evaluationContextProvider, mappingContext));
|
||||
}
|
||||
|
||||
@@ -130,7 +130,7 @@ public class ReactiveMongoRepositoryFactory extends ReactiveRepositoryFactorySup
|
||||
private static class MongoQueryLookupStrategy implements QueryLookupStrategy {
|
||||
|
||||
private final ReactiveMongoOperations operations;
|
||||
private final EvaluationContextProvider evaluationContextProvider;
|
||||
private final QueryMethodEvaluationContextProvider evaluationContextProvider;
|
||||
private final MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext;
|
||||
|
||||
/*
|
||||
|
||||
@@ -100,7 +100,7 @@ public class SimpleMongoRepository<T, ID> implements MongoRepository<T, ID> {
|
||||
if (allNew) {
|
||||
|
||||
List<S> result = source.stream().collect(Collectors.toList());
|
||||
mongoOperations.insertAll(result);
|
||||
mongoOperations.insert(result, entityInformation.getCollectionName());
|
||||
return result;
|
||||
|
||||
} else {
|
||||
|
||||
@@ -45,6 +45,7 @@ import org.springframework.util.Assert;
|
||||
* @author Mark Paluch
|
||||
* @author Oliver Gierke
|
||||
* @author Christoph Strobl
|
||||
* @author Ruben J Garcia
|
||||
* @since 2.0
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@@ -91,13 +92,13 @@ public class SimpleReactiveMongoRepository<T, ID extends Serializable> implement
|
||||
q.limit(2);
|
||||
|
||||
return mongoOperations.find(q, example.getProbeType(), entityInformation.getCollectionName()).buffer(2)
|
||||
.flatMap(vals -> {
|
||||
.map(vals -> {
|
||||
|
||||
if (vals.size() > 1) {
|
||||
return Mono.error(new IncorrectResultSizeDataAccessException(1));
|
||||
throw new IncorrectResultSizeDataAccessException(1);
|
||||
}
|
||||
return Mono.just(vals.iterator().next());
|
||||
}).single();
|
||||
return vals.iterator().next();
|
||||
}).next();
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -314,8 +315,7 @@ public class SimpleReactiveMongoRepository<T, ID extends Serializable> implement
|
||||
|
||||
Assert.notNull(entityStream, "The given Publisher of entities must not be null!");
|
||||
|
||||
return Flux.from(entityStream)
|
||||
.flatMap(entity -> entityInformation.isNew(entity) ? //
|
||||
return Flux.from(entityStream).flatMap(entity -> entityInformation.isNew(entity) ? //
|
||||
mongoOperations.insert(entity, entityInformation.getCollectionName()).then(Mono.just(entity)) : //
|
||||
mongoOperations.save(entity, entityInformation.getCollectionName()).then(Mono.just(entity)));
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ package org.springframework.data.mongodb.repository.support;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
@@ -27,10 +28,12 @@ import org.springframework.data.mongodb.core.convert.MongoConverter;
|
||||
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.util.BsonUtils;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
import com.mongodb.BasicDBList;
|
||||
import com.mongodb.BasicDBObject;
|
||||
import com.mongodb.DBObject;
|
||||
import com.mongodb.DBRef;
|
||||
@@ -93,7 +96,7 @@ class SpringDataMongodbSerializer extends MongodbSerializer {
|
||||
return super.visit(expr, context);
|
||||
}
|
||||
|
||||
return converter.convertToMongoType(expr.getConstant());
|
||||
return toQuerydslMongoType(expr.getConstant());
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -128,7 +131,8 @@ class SpringDataMongodbSerializer extends MongodbSerializer {
|
||||
Document mappedIdValue = mapper.getMappedObject((BasicDBObject) superIdValue, Optional.empty());
|
||||
return (DBObject) JSON.parse(mappedIdValue.toJson());
|
||||
}
|
||||
return super.asDBObject(key, value instanceof Pattern ? value : converter.convertToMongoType(value));
|
||||
|
||||
return super.asDBObject(key, value instanceof Pattern ? value : toQuerydslMongoType(value));
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -231,4 +235,25 @@ class SpringDataMongodbSerializer extends MongodbSerializer {
|
||||
|
||||
return property;
|
||||
}
|
||||
|
||||
private Object toQuerydslMongoType(Object source) {
|
||||
|
||||
Object target = converter.convertToMongoType(source);
|
||||
|
||||
if (target instanceof List) {
|
||||
|
||||
List<Object> newList = new BasicDBList();
|
||||
|
||||
for (Object item : (List) target) {
|
||||
if (item instanceof Document) {
|
||||
newList.add(new BasicDBObject(BsonUtils.asMap((Document) item)));
|
||||
} else {
|
||||
newList.add(item);
|
||||
}
|
||||
}
|
||||
return newList;
|
||||
}
|
||||
|
||||
return target;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* 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 kotlin.reflect.KClass
|
||||
|
||||
/**
|
||||
* Extension for [ExecutableMapReduceOperation.mapReduce] providing a [KClass] based variant.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.1
|
||||
*/
|
||||
fun <T : Any> ExecutableMapReduceOperation.mapReduce(entityClass: KClass<T>): ExecutableMapReduceOperation.MapReduceWithMapFunction<T> =
|
||||
mapReduce(entityClass.java)
|
||||
|
||||
/**
|
||||
* Extension for [ExecutableMapReduceOperation.mapReduce] leveraging reified type parameters.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.1
|
||||
*/
|
||||
inline fun <reified T : Any> ExecutableMapReduceOperation.mapReduce(): ExecutableMapReduceOperation.MapReduceWithMapFunction<T> =
|
||||
mapReduce(T::class.java)
|
||||
|
||||
/**
|
||||
* Extension for [ExecutableMapReduceOperation.MapReduceWithProjection.as] providing a [KClass] based variant.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.1
|
||||
*/
|
||||
fun <T : Any> ExecutableMapReduceOperation.MapReduceWithProjection<T>.asType(resultType: KClass<T>): ExecutableMapReduceOperation.MapReduceWithQuery<T> =
|
||||
`as`(resultType.java)
|
||||
|
||||
/**
|
||||
* Extension for [ExecutableMapReduceOperation.MapReduceWithProjection.as] leveraging reified type parameters.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.1
|
||||
*/
|
||||
inline fun <reified T : Any> ExecutableMapReduceOperation.MapReduceWithProjection<T>.asType(): ExecutableMapReduceOperation.MapReduceWithQuery<T> =
|
||||
`as`(T::class.java)
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* 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 kotlin.reflect.KClass
|
||||
|
||||
/**
|
||||
* Extension for [ReactiveMapReduceOperation.mapReduce] providing a [KClass] based variant.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.1
|
||||
*/
|
||||
fun <T : Any> ReactiveMapReduceOperation.mapReduce(entityClass: KClass<T>): ReactiveMapReduceOperation.MapReduceWithMapFunction<T> =
|
||||
mapReduce(entityClass.java)
|
||||
|
||||
/**
|
||||
* Extension for [ReactiveMapReduceOperation.mapReduce] leveraging reified type parameters.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.1
|
||||
*/
|
||||
inline fun <reified T : Any> ReactiveMapReduceOperation.mapReduce(): ReactiveMapReduceOperation.MapReduceWithMapFunction<T> =
|
||||
mapReduce(T::class.java)
|
||||
|
||||
/**
|
||||
* Extension for [ReactiveMapReduceOperation.MapReduceWithProjection.as] providing a [KClass] based variant.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.1
|
||||
*/
|
||||
fun <T : Any> ReactiveMapReduceOperation.MapReduceWithProjection<T>.asType(resultType: KClass<T>): ReactiveMapReduceOperation.MapReduceWithQuery<T> =
|
||||
`as`(resultType.java)
|
||||
|
||||
/**
|
||||
* Extension for [ReactiveMapReduceOperation.MapReduceWithProjection.as] leveraging reified type parameters.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.1
|
||||
*/
|
||||
inline fun <reified T : Any> ReactiveMapReduceOperation.MapReduceWithProjection<T>.asType(): ReactiveMapReduceOperation.MapReduceWithQuery<T> =
|
||||
`as`(T::class.java)
|
||||
@@ -16,7 +16,7 @@
|
||||
package org.springframework.data.mongodb.core.query
|
||||
|
||||
/**
|
||||
* Extension for [Criteria.is] providing an `isEqualTo` alias since `in` is a reserved keyword in Kotlin.
|
||||
* Extension for [Criteria.is] providing an `isEqualTo` alias since `is` is a reserved keyword in Kotlin.
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
* @since 2.0
|
||||
|
||||
@@ -0,0 +1,239 @@
|
||||
/*
|
||||
* 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 static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import javax.transaction.Status;
|
||||
import javax.transaction.UserTransaction;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import org.springframework.transaction.TransactionStatus;
|
||||
import org.springframework.transaction.jta.JtaTransactionManager;
|
||||
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
|
||||
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
|
||||
import com.mongodb.client.ClientSession;
|
||||
import com.mongodb.client.MongoDatabase;
|
||||
import com.mongodb.session.ServerSession;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class MongoDatabaseUtilsUnitTests {
|
||||
|
||||
@Mock ClientSession session;
|
||||
@Mock ServerSession serverSession;
|
||||
@Mock MongoDbFactory dbFactory;
|
||||
@Mock MongoDatabase db;
|
||||
|
||||
@Mock UserTransaction userTransaction;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
|
||||
when(dbFactory.getSession(any())).thenReturn(session);
|
||||
|
||||
when(dbFactory.withSession(session)).thenReturn(dbFactory);
|
||||
|
||||
when(dbFactory.getDb()).thenReturn(db);
|
||||
|
||||
when(session.getServerSession()).thenReturn(serverSession);
|
||||
when(session.hasActiveTransaction()).thenReturn(true);
|
||||
|
||||
when(serverSession.isClosed()).thenReturn(false);
|
||||
}
|
||||
|
||||
@After
|
||||
public void verifyTransactionSynchronizationManagerState() {
|
||||
|
||||
assertTrue(TransactionSynchronizationManager.getResourceMap().isEmpty());
|
||||
assertFalse(TransactionSynchronizationManager.isSynchronizationActive());
|
||||
assertNull(TransactionSynchronizationManager.getCurrentTransactionName());
|
||||
assertFalse(TransactionSynchronizationManager.isCurrentTransactionReadOnly());
|
||||
assertNull(TransactionSynchronizationManager.getCurrentTransactionIsolationLevel());
|
||||
assertFalse(TransactionSynchronizationManager.isActualTransactionActive());
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1920
|
||||
public void shouldNotStartSessionWhenNoTransactionOngoing() {
|
||||
|
||||
MongoDatabaseUtils.getDatabase(dbFactory, SessionSynchronization.ON_ACTUAL_TRANSACTION);
|
||||
|
||||
verify(dbFactory, never()).getSession(any());
|
||||
verify(dbFactory, never()).withSession(any(ClientSession.class));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1920
|
||||
public void shouldParticipateInOngoingJtaTransactionWithCommitWhenSessionSychronizationIsAny() throws Exception {
|
||||
|
||||
when(userTransaction.getStatus()).thenReturn(Status.STATUS_NO_TRANSACTION, Status.STATUS_ACTIVE,
|
||||
Status.STATUS_ACTIVE);
|
||||
|
||||
JtaTransactionManager txManager = new JtaTransactionManager(userTransaction);
|
||||
TransactionTemplate txTemplate = new TransactionTemplate(txManager);
|
||||
|
||||
txTemplate.execute(new TransactionCallbackWithoutResult() {
|
||||
|
||||
@Override
|
||||
protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
|
||||
|
||||
assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isTrue();
|
||||
assertThat(transactionStatus.isNewTransaction()).isTrue();
|
||||
assertThat(TransactionSynchronizationManager.hasResource(dbFactory)).isFalse();
|
||||
|
||||
MongoDatabaseUtils.getDatabase(dbFactory, SessionSynchronization.ALWAYS);
|
||||
|
||||
assertThat(TransactionSynchronizationManager.hasResource(dbFactory)).isTrue();
|
||||
}
|
||||
});
|
||||
|
||||
verify(userTransaction).begin();
|
||||
|
||||
verify(session).startTransaction();
|
||||
verify(session).commitTransaction();
|
||||
verify(session).close();
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1920
|
||||
public void shouldParticipateInOngoingJtaTransactionWithRollbackWhenSessionSychronizationIsAny() throws Exception {
|
||||
|
||||
when(userTransaction.getStatus()).thenReturn(Status.STATUS_NO_TRANSACTION, Status.STATUS_ACTIVE,
|
||||
Status.STATUS_ACTIVE);
|
||||
|
||||
JtaTransactionManager txManager = new JtaTransactionManager(userTransaction);
|
||||
TransactionTemplate txTemplate = new TransactionTemplate(txManager);
|
||||
|
||||
txTemplate.execute(new TransactionCallbackWithoutResult() {
|
||||
|
||||
@Override
|
||||
protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
|
||||
|
||||
assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isTrue();
|
||||
assertThat(transactionStatus.isNewTransaction()).isTrue();
|
||||
assertThat(TransactionSynchronizationManager.hasResource(dbFactory)).isFalse();
|
||||
|
||||
MongoDatabaseUtils.getDatabase(dbFactory, SessionSynchronization.ALWAYS);
|
||||
|
||||
assertThat(TransactionSynchronizationManager.hasResource(dbFactory)).isTrue();
|
||||
|
||||
transactionStatus.setRollbackOnly();
|
||||
}
|
||||
});
|
||||
|
||||
verify(userTransaction).rollback();
|
||||
|
||||
verify(session).startTransaction();
|
||||
verify(session).abortTransaction();
|
||||
verify(session).close();
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1920
|
||||
public void shouldNotParticipateInOngoingJtaTransactionWithRollbackWhenSessionSychronizationIsNative()
|
||||
throws Exception {
|
||||
|
||||
when(userTransaction.getStatus()).thenReturn(Status.STATUS_NO_TRANSACTION, Status.STATUS_ACTIVE,
|
||||
Status.STATUS_ACTIVE);
|
||||
|
||||
JtaTransactionManager txManager = new JtaTransactionManager(userTransaction);
|
||||
TransactionTemplate txTemplate = new TransactionTemplate(txManager);
|
||||
|
||||
txTemplate.execute(new TransactionCallbackWithoutResult() {
|
||||
|
||||
@Override
|
||||
protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
|
||||
|
||||
assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isTrue();
|
||||
assertThat(transactionStatus.isNewTransaction()).isTrue();
|
||||
assertThat(TransactionSynchronizationManager.hasResource(dbFactory)).isFalse();
|
||||
|
||||
MongoDatabaseUtils.getDatabase(dbFactory, SessionSynchronization.ON_ACTUAL_TRANSACTION);
|
||||
|
||||
assertThat(TransactionSynchronizationManager.hasResource(dbFactory)).isFalse();
|
||||
|
||||
transactionStatus.setRollbackOnly();
|
||||
}
|
||||
});
|
||||
|
||||
verify(userTransaction).rollback();
|
||||
|
||||
verify(session, never()).startTransaction();
|
||||
verify(session, never()).abortTransaction();
|
||||
verify(session, never()).close();
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1920
|
||||
public void shouldParticipateInOngoingMongoTransactionWhenSessionSychronizationIsNative() {
|
||||
|
||||
MongoTransactionManager txManager = new MongoTransactionManager(dbFactory);
|
||||
TransactionTemplate txTemplate = new TransactionTemplate(txManager);
|
||||
|
||||
txTemplate.execute(new TransactionCallbackWithoutResult() {
|
||||
|
||||
@Override
|
||||
protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
|
||||
|
||||
assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isTrue();
|
||||
assertThat(transactionStatus.isNewTransaction()).isTrue();
|
||||
assertThat(TransactionSynchronizationManager.hasResource(dbFactory)).isTrue();
|
||||
|
||||
MongoDatabaseUtils.getDatabase(dbFactory, SessionSynchronization.ON_ACTUAL_TRANSACTION);
|
||||
|
||||
transactionStatus.setRollbackOnly();
|
||||
}
|
||||
});
|
||||
|
||||
verify(session).startTransaction();
|
||||
verify(session).abortTransaction();
|
||||
verify(session).close();
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1920
|
||||
public void shouldParticipateInOngoingMongoTransactionWhenSessionSychronizationIsAny() {
|
||||
|
||||
MongoTransactionManager txManager = new MongoTransactionManager(dbFactory);
|
||||
TransactionTemplate txTemplate = new TransactionTemplate(txManager);
|
||||
|
||||
txTemplate.execute(new TransactionCallbackWithoutResult() {
|
||||
|
||||
@Override
|
||||
protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
|
||||
|
||||
assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isTrue();
|
||||
assertThat(transactionStatus.isNewTransaction()).isTrue();
|
||||
assertThat(TransactionSynchronizationManager.hasResource(dbFactory)).isTrue();
|
||||
|
||||
MongoDatabaseUtils.getDatabase(dbFactory, SessionSynchronization.ALWAYS);
|
||||
|
||||
transactionStatus.setRollbackOnly();
|
||||
}
|
||||
});
|
||||
|
||||
verify(session).startTransaction();
|
||||
verify(session).abortTransaction();
|
||||
verify(session).close();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,333 @@
|
||||
/*
|
||||
* 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 static org.assertj.core.api.Assertions.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import org.springframework.data.mongodb.core.MongoTemplate;
|
||||
import org.springframework.transaction.TransactionDefinition;
|
||||
import org.springframework.transaction.TransactionStatus;
|
||||
import org.springframework.transaction.UnexpectedRollbackException;
|
||||
import org.springframework.transaction.support.DefaultTransactionDefinition;
|
||||
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
|
||||
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
|
||||
import com.mongodb.client.ClientSession;
|
||||
import com.mongodb.client.MongoDatabase;
|
||||
import com.mongodb.session.ServerSession;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class MongoTransactionManagerUnitTests {
|
||||
|
||||
@Mock ClientSession session;
|
||||
@Mock ClientSession session2;
|
||||
@Mock ServerSession serverSession;
|
||||
@Mock MongoDbFactory dbFactory;
|
||||
@Mock MongoDbFactory dbFactory2;
|
||||
@Mock MongoDatabase db;
|
||||
@Mock MongoDatabase db2;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
|
||||
when(dbFactory.getSession(any())).thenReturn(session, session2);
|
||||
|
||||
when(dbFactory.withSession(session)).thenReturn(dbFactory);
|
||||
when(dbFactory.withSession(session2)).thenReturn(dbFactory2);
|
||||
|
||||
when(dbFactory.getDb()).thenReturn(db);
|
||||
when(dbFactory2.getDb()).thenReturn(db2);
|
||||
|
||||
when(session.getServerSession()).thenReturn(serverSession);
|
||||
when(session2.getServerSession()).thenReturn(serverSession);
|
||||
|
||||
when(serverSession.isClosed()).thenReturn(false);
|
||||
}
|
||||
|
||||
@After
|
||||
public void verifyTransactionSynchronizationManager() {
|
||||
|
||||
assertTrue(TransactionSynchronizationManager.getResourceMap().isEmpty());
|
||||
assertFalse(TransactionSynchronizationManager.isSynchronizationActive());
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1920
|
||||
public void triggerCommitCorrectly() {
|
||||
|
||||
MongoTransactionManager txManager = new MongoTransactionManager(dbFactory);
|
||||
TransactionStatus txStatus = txManager.getTransaction(new DefaultTransactionDefinition());
|
||||
|
||||
MongoTemplate template = new MongoTemplate(dbFactory);
|
||||
|
||||
template.execute(db -> {
|
||||
db.drop();
|
||||
return null;
|
||||
});
|
||||
|
||||
verify(dbFactory).withSession(eq(session));
|
||||
|
||||
txManager.commit(txStatus);
|
||||
|
||||
verify(session).startTransaction();
|
||||
verify(session).commitTransaction();
|
||||
verify(session).close();
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1920
|
||||
public void participateInOnGoingTransactionWithCommit() {
|
||||
|
||||
MongoTransactionManager txManager = new MongoTransactionManager(dbFactory);
|
||||
TransactionStatus txStatus = txManager.getTransaction(new DefaultTransactionDefinition());
|
||||
|
||||
MongoTemplate template = new MongoTemplate(dbFactory);
|
||||
|
||||
template.execute(db -> {
|
||||
db.drop();
|
||||
return null;
|
||||
});
|
||||
|
||||
TransactionTemplate txTemplate = new TransactionTemplate(txManager);
|
||||
txTemplate.execute(new TransactionCallbackWithoutResult() {
|
||||
|
||||
@Override
|
||||
protected void doInTransactionWithoutResult(TransactionStatus status) {
|
||||
|
||||
template.execute(db -> {
|
||||
db.drop();
|
||||
return null;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
verify(dbFactory, times(2)).withSession(eq(session));
|
||||
|
||||
txManager.commit(txStatus);
|
||||
|
||||
verify(session).startTransaction();
|
||||
verify(session).commitTransaction();
|
||||
verify(session).close();
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1920
|
||||
public void participateInOnGoingTransactionWithRollbackOnly() {
|
||||
|
||||
MongoTransactionManager txManager = new MongoTransactionManager(dbFactory);
|
||||
TransactionStatus txStatus = txManager.getTransaction(new DefaultTransactionDefinition());
|
||||
|
||||
MongoTemplate template = new MongoTemplate(dbFactory);
|
||||
|
||||
template.execute(db -> {
|
||||
db.drop();
|
||||
return null;
|
||||
});
|
||||
|
||||
TransactionTemplate txTemplate = new TransactionTemplate(txManager);
|
||||
txTemplate.execute(new TransactionCallbackWithoutResult() {
|
||||
|
||||
@Override
|
||||
protected void doInTransactionWithoutResult(TransactionStatus status) {
|
||||
|
||||
template.execute(db -> {
|
||||
db.drop();
|
||||
return null;
|
||||
});
|
||||
|
||||
status.setRollbackOnly();
|
||||
}
|
||||
});
|
||||
|
||||
verify(dbFactory, times(2)).withSession(eq(session));
|
||||
|
||||
assertThatExceptionOfType(UnexpectedRollbackException.class).isThrownBy(() -> txManager.commit(txStatus));
|
||||
|
||||
verify(session).startTransaction();
|
||||
verify(session).abortTransaction();
|
||||
verify(session).close();
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1920
|
||||
public void triggerRollbackCorrectly() {
|
||||
|
||||
MongoTransactionManager txManager = new MongoTransactionManager(dbFactory);
|
||||
TransactionStatus txStatus = txManager.getTransaction(new DefaultTransactionDefinition());
|
||||
|
||||
MongoTemplate template = new MongoTemplate(dbFactory);
|
||||
|
||||
template.execute(db -> {
|
||||
db.drop();
|
||||
return null;
|
||||
});
|
||||
|
||||
verify(dbFactory).withSession(eq(session));
|
||||
|
||||
txManager.rollback(txStatus);
|
||||
|
||||
verify(session).startTransaction();
|
||||
verify(session).abortTransaction();
|
||||
verify(session).close();
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1920
|
||||
public void suspendTransactionWhilePropagationNotSupported() {
|
||||
|
||||
MongoTransactionManager txManager = new MongoTransactionManager(dbFactory);
|
||||
TransactionStatus txStatus = txManager.getTransaction(new DefaultTransactionDefinition());
|
||||
|
||||
MongoTemplate template = new MongoTemplate(dbFactory);
|
||||
|
||||
template.execute(db -> {
|
||||
db.drop();
|
||||
return null;
|
||||
});
|
||||
|
||||
TransactionTemplate txTemplate = new TransactionTemplate(txManager);
|
||||
txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_NOT_SUPPORTED);
|
||||
txTemplate.execute(new TransactionCallbackWithoutResult() {
|
||||
|
||||
@Override
|
||||
protected void doInTransactionWithoutResult(TransactionStatus status) {
|
||||
|
||||
template.execute(db -> {
|
||||
db.drop();
|
||||
return null;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
template.execute(MongoDatabase::listCollections);
|
||||
txManager.commit(txStatus);
|
||||
|
||||
verify(session).startTransaction();
|
||||
verify(session2, never()).startTransaction();
|
||||
|
||||
verify(dbFactory, times(2)).withSession(eq(session));
|
||||
verify(dbFactory, never()).withSession(eq(session2));
|
||||
|
||||
verify(db, times(2)).drop();
|
||||
verify(db).listCollections();
|
||||
|
||||
verify(session).close();
|
||||
verify(session2, never()).close();
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1920
|
||||
public void suspendTransactionWhilePropagationRequiresNew() {
|
||||
|
||||
MongoTransactionManager txManager = new MongoTransactionManager(dbFactory);
|
||||
TransactionStatus txStatus = txManager.getTransaction(new DefaultTransactionDefinition());
|
||||
|
||||
MongoTemplate template = new MongoTemplate(dbFactory);
|
||||
|
||||
template.execute(db -> {
|
||||
db.drop();
|
||||
return null;
|
||||
});
|
||||
|
||||
TransactionTemplate txTemplate = new TransactionTemplate(txManager);
|
||||
txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
|
||||
txTemplate.execute(new TransactionCallbackWithoutResult() {
|
||||
|
||||
@Override
|
||||
protected void doInTransactionWithoutResult(TransactionStatus status) {
|
||||
|
||||
template.execute(db -> {
|
||||
db.drop();
|
||||
return null;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
template.execute(MongoDatabase::listCollections);
|
||||
txManager.commit(txStatus);
|
||||
|
||||
verify(session).startTransaction();
|
||||
verify(session2).startTransaction();
|
||||
|
||||
verify(dbFactory, times(2)).withSession(eq(session));
|
||||
verify(dbFactory).withSession(eq(session2));
|
||||
|
||||
verify(db).drop();
|
||||
verify(db2).drop();
|
||||
verify(db).listCollections();
|
||||
|
||||
verify(session).close();
|
||||
verify(session2).close();
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1920
|
||||
public void readonlyShouldInitiateASessionStartAndCommitTransaction() {
|
||||
|
||||
MongoTransactionManager txManager = new MongoTransactionManager(dbFactory);
|
||||
|
||||
DefaultTransactionDefinition readonlyTxDefinition = new DefaultTransactionDefinition();
|
||||
readonlyTxDefinition.setReadOnly(true);
|
||||
|
||||
TransactionStatus txStatus = txManager.getTransaction(readonlyTxDefinition);
|
||||
|
||||
MongoTemplate template = new MongoTemplate(dbFactory);
|
||||
|
||||
template.execute(db -> {
|
||||
db.drop();
|
||||
return null;
|
||||
});
|
||||
|
||||
verify(dbFactory).withSession(eq(session));
|
||||
|
||||
txManager.commit(txStatus);
|
||||
|
||||
verify(session).startTransaction();
|
||||
verify(session).commitTransaction();
|
||||
verify(session).close();
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1920
|
||||
public void readonlyShouldInitiateASessionStartAndRollbackTransaction() {
|
||||
|
||||
MongoTransactionManager txManager = new MongoTransactionManager(dbFactory);
|
||||
|
||||
DefaultTransactionDefinition readonlyTxDefinition = new DefaultTransactionDefinition();
|
||||
readonlyTxDefinition.setReadOnly(true);
|
||||
|
||||
TransactionStatus txStatus = txManager.getTransaction(readonlyTxDefinition);
|
||||
|
||||
MongoTemplate template = new MongoTemplate(dbFactory);
|
||||
|
||||
template.execute(db -> {
|
||||
db.drop();
|
||||
return null;
|
||||
});
|
||||
|
||||
verify(dbFactory).withSession(eq(session));
|
||||
|
||||
txManager.rollback(txStatus);
|
||||
|
||||
verify(session).startTransaction();
|
||||
verify(session).abortTransaction();
|
||||
verify(session).close();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
/*
|
||||
* 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 static org.assertj.core.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.*;
|
||||
import static org.mockito.Mockito.any;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Proxy;
|
||||
|
||||
import org.bson.Document;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import org.springframework.aop.framework.ProxyFactory;
|
||||
import org.springframework.data.mongodb.SessionAwareMethodInterceptor.MethodCache;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
import com.mongodb.MongoClient;
|
||||
import com.mongodb.client.ClientSession;
|
||||
import com.mongodb.client.MongoCollection;
|
||||
import com.mongodb.client.MongoDatabase;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link SessionAwareMethodInterceptor}.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class SessionAwareMethodInterceptorUnitTests {
|
||||
|
||||
@Mock ClientSession session;
|
||||
@Mock MongoCollection<Document> targetCollection;
|
||||
@Mock MongoDatabase targetDatabase;
|
||||
|
||||
MongoCollection collection;
|
||||
MongoDatabase database;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
|
||||
collection = createProxyInstance(session, targetCollection, MongoCollection.class);
|
||||
database = createProxyInstance(session, targetDatabase, MongoDatabase.class);
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1880
|
||||
public void proxyFactoryOnCollectionDelegatesToMethodWithSession() {
|
||||
|
||||
collection.find();
|
||||
|
||||
verify(targetCollection).find(eq(session));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1880
|
||||
public void proxyFactoryOnCollectionWithSessionInArgumentListProceedsWithExecution() {
|
||||
|
||||
ClientSession yetAnotherSession = mock(ClientSession.class);
|
||||
collection.find(yetAnotherSession);
|
||||
|
||||
verify(targetCollection).find(eq(yetAnotherSession));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1880
|
||||
public void proxyFactoryOnDatabaseDelegatesToMethodWithSession() {
|
||||
|
||||
database.drop();
|
||||
|
||||
verify(targetDatabase).drop(eq(session));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1880
|
||||
public void proxyFactoryOnDatabaseWithSessionInArgumentListProceedsWithExecution() {
|
||||
|
||||
ClientSession yetAnotherSession = mock(ClientSession.class);
|
||||
database.drop(yetAnotherSession);
|
||||
|
||||
verify(targetDatabase).drop(eq(yetAnotherSession));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1880
|
||||
public void justMoveOnIfNoOverloadWithSessionAvailable() {
|
||||
|
||||
collection.getReadPreference();
|
||||
|
||||
verify(targetCollection).getReadPreference();
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1880
|
||||
public void usesCacheForMethodLookup() {
|
||||
|
||||
MethodCache cache = (MethodCache) ReflectionTestUtils.getField(SessionAwareMethodInterceptor.class, "METHOD_CACHE");
|
||||
Method countMethod = ClassUtils.getMethod(MongoCollection.class, "count");
|
||||
|
||||
assertThat(cache.contains(countMethod, MongoCollection.class)).isFalse();
|
||||
|
||||
collection.count();
|
||||
|
||||
assertThat(cache.contains(countMethod, MongoCollection.class)).isTrue();
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1880
|
||||
public void cachesNullForMethodsThatDoNotHaveASessionOverload() {
|
||||
|
||||
MethodCache cache = (MethodCache) ReflectionTestUtils.getField(SessionAwareMethodInterceptor.class, "METHOD_CACHE");
|
||||
Method readConcernMethod = ClassUtils.getMethod(MongoCollection.class, "getReadConcern");
|
||||
|
||||
assertThat(cache.contains(readConcernMethod, MongoCollection.class)).isFalse();
|
||||
|
||||
collection.getReadConcern();
|
||||
|
||||
collection.getReadConcern();
|
||||
|
||||
assertThat(cache.contains(readConcernMethod, MongoCollection.class)).isTrue();
|
||||
assertThat(cache.lookup(readConcernMethod, MongoCollection.class, ClientSession.class)).isEmpty();
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1880
|
||||
public void proxiesNewDbInstanceReturnedByMethod() {
|
||||
|
||||
MongoDatabase otherDb = mock(MongoDatabase.class);
|
||||
when(targetDatabase.withCodecRegistry(any())).thenReturn(otherDb);
|
||||
|
||||
MongoDatabase target = database.withCodecRegistry(MongoClient.getDefaultCodecRegistry());
|
||||
assertThat(target).isInstanceOf(Proxy.class).isNotSameAs(database).isNotSameAs(targetDatabase);
|
||||
|
||||
target.drop();
|
||||
|
||||
verify(otherDb).drop(eq(session));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1880
|
||||
public void proxiesNewCollectionInstanceReturnedByMethod() {
|
||||
|
||||
MongoCollection otherCollection = mock(MongoCollection.class);
|
||||
when(targetCollection.withCodecRegistry(any())).thenReturn(otherCollection);
|
||||
|
||||
MongoCollection target = collection.withCodecRegistry(MongoClient.getDefaultCodecRegistry());
|
||||
assertThat(target).isInstanceOf(Proxy.class).isNotSameAs(collection).isNotSameAs(targetCollection);
|
||||
|
||||
target.drop();
|
||||
|
||||
verify(otherCollection).drop(eq(session));
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user