Compare commits
154 Commits
2.1.0.M3
...
2.1.3.RELE
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bd8bd4f568 | ||
|
|
c75f29dc42 | ||
|
|
e493af7266 | ||
|
|
8d892e5924 | ||
|
|
053299f243 | ||
|
|
872659cc00 | ||
|
|
96978a6194 | ||
|
|
2253d3e301 | ||
|
|
5982ee84f7 | ||
|
|
dd2af6462d | ||
|
|
622643bf24 | ||
|
|
51cc55baac | ||
|
|
0b106e5649 | ||
|
|
8975d93ab3 | ||
|
|
e25b6c49f5 | ||
|
|
7a70c205de | ||
|
|
6045efa450 | ||
|
|
7b0816b3ee | ||
|
|
14e4ea736d | ||
|
|
32e7d9ab7f | ||
|
|
7f35ad9e45 | ||
|
|
60228f6e5a | ||
|
|
7604492b7f | ||
|
|
4680fe0e77 | ||
|
|
b4228c88d3 | ||
|
|
f6ef8c94c8 | ||
|
|
0d0dafa85e | ||
|
|
29aa34619f | ||
|
|
7f19f769c4 | ||
|
|
a40e89d90a | ||
|
|
6b2350200a | ||
|
|
fb50b0f6e7 | ||
|
|
ab568229b5 | ||
|
|
7f9c1bd774 | ||
|
|
670a0978da | ||
|
|
a502ffabc3 | ||
|
|
ffe4e9b914 | ||
|
|
914bdd9434 | ||
|
|
3cd9542483 | ||
|
|
586bf858f9 | ||
|
|
3478fd5ab3 | ||
|
|
fa5f523c92 | ||
|
|
2191ab3bba | ||
|
|
a79142931f | ||
|
|
1ba210366d | ||
|
|
16aa611007 | ||
|
|
13e29eb81f | ||
|
|
fe90950880 | ||
|
|
492dec8ecf | ||
|
|
a1ac2f7c1d | ||
|
|
04e53316c6 | ||
|
|
a991b96518 | ||
|
|
d53c5cf5c4 | ||
|
|
90779bbb27 | ||
|
|
892cc2e69a | ||
|
|
a69f1b4d51 | ||
|
|
7859ee1013 | ||
|
|
a58562ba69 | ||
|
|
779b0da358 | ||
|
|
ff1703f7c9 | ||
|
|
7b23f8eee2 | ||
|
|
cc97c5a961 | ||
|
|
08a57e58fd | ||
|
|
9d27d2ff8e | ||
|
|
3eba7de073 | ||
|
|
3dc6cab132 | ||
|
|
799fa6c87e | ||
|
|
c58032cf37 | ||
|
|
67c3f02dcc | ||
|
|
208bd6ae52 | ||
|
|
64419751c0 | ||
|
|
cd089d4a54 | ||
|
|
e484337dcf | ||
|
|
e4da45baed | ||
|
|
03246f04b8 | ||
|
|
50070dfc64 | ||
|
|
029d50e526 | ||
|
|
9764ce0147 | ||
|
|
c00f461d06 | ||
|
|
4205516446 | ||
|
|
beced8184f | ||
|
|
64dc3dbb1d | ||
|
|
7b67ad4f6c | ||
|
|
c2373d05fe | ||
|
|
da63788a52 | ||
|
|
016892085c | ||
|
|
4f9c0fa6b3 | ||
|
|
e1393847be | ||
|
|
ff6f5d9ef3 | ||
|
|
d4f351a37c | ||
|
|
67281916c2 | ||
|
|
f8b2781ec8 | ||
|
|
58116dfd63 | ||
|
|
5f2c411501 | ||
|
|
ac84c7bf57 | ||
|
|
db2c05e8fc | ||
|
|
5a735138fc | ||
|
|
7f28aaf60d | ||
|
|
8b8eb3cfe5 | ||
|
|
7f9352f9b8 | ||
|
|
9d1471bb28 | ||
|
|
088928c64a | ||
|
|
648bfdfc67 | ||
|
|
390b00d5fe | ||
|
|
98433250c8 | ||
|
|
323b0a8479 | ||
|
|
d1b1dfbae9 | ||
|
|
d1dea13c32 | ||
|
|
ba2ab183ed | ||
|
|
1eab66aff4 | ||
|
|
8cc4ef3c3f | ||
|
|
1e49c95e41 | ||
|
|
7d06f2b040 | ||
|
|
b7755e71f6 | ||
|
|
0ec82e1f2e | ||
|
|
e18f506edd | ||
|
|
46ed58b465 | ||
|
|
fb8084c9f7 | ||
|
|
5a0171203d | ||
|
|
c1d840d87d | ||
|
|
ed1f2c7833 | ||
|
|
c545c855b9 | ||
|
|
1b7678a6af | ||
|
|
0d06e141a3 | ||
|
|
2d36fc3050 | ||
|
|
78c2ab290d | ||
|
|
88150eca54 | ||
|
|
30b86e7612 | ||
|
|
d3976f5199 | ||
|
|
f587e1f42a | ||
|
|
bd5815dbcb | ||
|
|
ac89ce1b2c | ||
|
|
fa880f1c5c | ||
|
|
56e61a2965 | ||
|
|
fcb8647c59 | ||
|
|
07e0e78aec | ||
|
|
0c0f47f76f | ||
|
|
18313db8fb | ||
|
|
05f325687c | ||
|
|
8145b84dbe | ||
|
|
794b026f73 | ||
|
|
8f11916014 | ||
|
|
c5129aca45 | ||
|
|
dfede781fb | ||
|
|
fe43ba470b | ||
|
|
06622bed35 | ||
|
|
2bac54c70f | ||
|
|
daae696c78 | ||
|
|
9f77aba8bb | ||
|
|
2d9232ca04 | ||
|
|
e90c5bad2c | ||
|
|
bec79e6d7b | ||
|
|
619d344f57 | ||
|
|
6f1b9d3fa4 |
@@ -13,12 +13,12 @@ before_install:
|
||||
- |-
|
||||
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
|
||||
global:
|
||||
- MONGO_VERSION=3.7.9
|
||||
- MONGO_VERSION=4.0.0
|
||||
|
||||
addons:
|
||||
apt:
|
||||
|
||||
32
pom.xml
32
pom.xml
@@ -5,7 +5,7 @@
|
||||
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-mongodb-parent</artifactId>
|
||||
<version>2.1.0.M3</version>
|
||||
<version>2.1.3.RELEASE</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.M3</version>
|
||||
<version>2.1.3.RELEASE</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.M3</springdata.commons>
|
||||
<mongo>3.8.0-beta2</mongo>
|
||||
<mongo.reactivestreams>1.9.0-beta1</mongo.reactivestreams>
|
||||
<springdata.commons>2.1.3.RELEASE</springdata.commons>
|
||||
<mongo>3.8.2</mongo>
|
||||
<mongo.reactivestreams>1.9.2</mongo.reactivestreams>
|
||||
<jmh.version>1.19</jmh.version>
|
||||
</properties>
|
||||
|
||||
@@ -138,6 +138,24 @@
|
||||
</modules>
|
||||
</profile>
|
||||
|
||||
<profile>
|
||||
<id>distribute</id>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.asciidoctor</groupId>
|
||||
<artifactId>asciidoctor-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<attributes>
|
||||
<mongo-reactivestreams>${mongo.reactivestreams}</mongo-reactivestreams>
|
||||
<reactor>${reactor}</reactor>
|
||||
</attributes>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
|
||||
</profiles>
|
||||
|
||||
<dependencies>
|
||||
@@ -151,8 +169,8 @@
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>spring-libs-milestone</id>
|
||||
<url>https://repo.spring.io/libs-milestone</url>
|
||||
<id>spring-libs-release</id>
|
||||
<url>https://repo.spring.io/libs-release</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-mongodb-parent</artifactId>
|
||||
<version>2.1.0.M3</version>
|
||||
<version>2.1.3.RELEASE</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.M3</version>
|
||||
<version>2.1.3.RELEASE</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
<dependency>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-mongodb</artifactId>
|
||||
<version>2.1.0.M3</version>
|
||||
<version>2.1.3.RELEASE</version>
|
||||
</dependency>
|
||||
|
||||
<!-- reactive -->
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-mongodb-parent</artifactId>
|
||||
<version>2.1.0.M3</version>
|
||||
<version>2.1.3.RELEASE</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.M3</version>
|
||||
<version>2.1.3.RELEASE</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
||||
@@ -110,7 +110,7 @@ public class MongoDatabaseUtils {
|
||||
|
||||
ClientSession session = doGetSession(factory, sessionSynchronization);
|
||||
|
||||
if(session == null) {
|
||||
if (session == null) {
|
||||
return StringUtils.hasText(dbName) ? factory.getDb(dbName) : factory.getDb();
|
||||
}
|
||||
|
||||
@@ -118,6 +118,25 @@ public class MongoDatabaseUtils {
|
||||
return StringUtils.hasText(dbName) ? factoryToUse.getDb(dbName) : factoryToUse.getDb();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the {@link MongoDbFactory} is actually bound to a {@link ClientSession} that has an active transaction, or
|
||||
* if a {@link TransactionSynchronization} has been registered for the {@link MongoDbFactory resource} and if the
|
||||
* associated {@link ClientSession} has an {@link ClientSession#hasActiveTransaction() active transaction}.
|
||||
*
|
||||
* @param dbFactory the resource to check transactions for. Must not be {@literal null}.
|
||||
* @return {@literal true} if the factory has an ongoing transaction.
|
||||
* @since 2.1.3
|
||||
*/
|
||||
public static boolean isTransactionActive(MongoDbFactory dbFactory) {
|
||||
|
||||
if (dbFactory.isTransactionActive()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
MongoResourceHolder resourceHolder = (MongoResourceHolder) TransactionSynchronizationManager.getResource(dbFactory);
|
||||
return resourceHolder != null && resourceHolder.hasActiveTransaction();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static ClientSession doGetSession(MongoDbFactory dbFactory, SessionSynchronization sessionSynchronization) {
|
||||
|
||||
@@ -140,7 +159,7 @@ public class MongoDatabaseUtils {
|
||||
// init a non native MongoDB transaction by registering a MongoSessionSynchronization
|
||||
|
||||
resourceHolder = new MongoResourceHolder(createClientSession(dbFactory), dbFactory);
|
||||
resourceHolder.getSession().startTransaction();
|
||||
resourceHolder.getRequiredSession().startTransaction();
|
||||
|
||||
TransactionSynchronizationManager
|
||||
.registerSynchronization(new MongoSessionSynchronization(resourceHolder, dbFactory));
|
||||
@@ -187,8 +206,8 @@ public class MongoDatabaseUtils {
|
||||
@Override
|
||||
protected void processResourceAfterCommit(MongoResourceHolder resourceHolder) {
|
||||
|
||||
if (isTransactionActive(resourceHolder)) {
|
||||
resourceHolder.getSession().commitTransaction();
|
||||
if (resourceHolder.hasActiveTransaction()) {
|
||||
resourceHolder.getRequiredSession().commitTransaction();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,8 +218,8 @@ public class MongoDatabaseUtils {
|
||||
@Override
|
||||
public void afterCompletion(int status) {
|
||||
|
||||
if (status == TransactionSynchronization.STATUS_ROLLED_BACK && isTransactionActive(this.resourceHolder)) {
|
||||
resourceHolder.getSession().abortTransaction();
|
||||
if (status == TransactionSynchronization.STATUS_ROLLED_BACK && this.resourceHolder.hasActiveTransaction()) {
|
||||
resourceHolder.getRequiredSession().abortTransaction();
|
||||
}
|
||||
|
||||
super.afterCompletion(status);
|
||||
@@ -214,17 +233,8 @@ public class MongoDatabaseUtils {
|
||||
protected void releaseResource(MongoResourceHolder resourceHolder, Object resourceKey) {
|
||||
|
||||
if (resourceHolder.hasActiveSession()) {
|
||||
resourceHolder.getSession().close();
|
||||
resourceHolder.getRequiredSession().close();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isTransactionActive(MongoResourceHolder resourceHolder) {
|
||||
|
||||
if (!resourceHolder.hasSession()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return resourceHolder.getSession().hasActiveTransaction();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,7 +62,10 @@ public interface MongoDbFactory extends CodecRegistryProvider, MongoSessionProvi
|
||||
* Get the legacy database entry point. Please consider {@link #getDb()} instead.
|
||||
*
|
||||
* @return
|
||||
* @deprecated since 2.1, use {@link #getDb()}. This method will be removed with a future version as it works only
|
||||
* with the legacy MongoDB driver.
|
||||
*/
|
||||
@Deprecated
|
||||
DB getLegacyDb();
|
||||
|
||||
/**
|
||||
@@ -105,4 +108,15 @@ public interface MongoDbFactory extends CodecRegistryProvider, MongoSessionProvi
|
||||
* @since 2.1
|
||||
*/
|
||||
MongoDbFactory withSession(ClientSession session);
|
||||
|
||||
/**
|
||||
* Returns if the given {@link MongoDbFactory} is bound to a {@link ClientSession} that has an
|
||||
* {@link ClientSession#hasActiveTransaction() active transaction}.
|
||||
*
|
||||
* @return {@literal true} if there's an active transaction, {@literal false} otherwise.
|
||||
* @since 2.1.3
|
||||
*/
|
||||
default boolean isTransactionActive() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ import com.mongodb.client.ClientSession;
|
||||
* <strong>Note:</strong> Intended for internal usage only.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
* @since 2.1
|
||||
* @see MongoTransactionManager
|
||||
* @see org.springframework.data.mongodb.core.MongoTemplate
|
||||
@@ -57,6 +58,22 @@ class MongoResourceHolder extends ResourceHolderSupport {
|
||||
return session;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the required associated {@link ClientSession}.
|
||||
* @throws IllegalStateException if no {@link ClientSession} is associated with this {@link MongoResourceHolder}.
|
||||
* @since 2.1.3
|
||||
*/
|
||||
ClientSession getRequiredSession() {
|
||||
|
||||
ClientSession session = getSession();
|
||||
|
||||
if (session == null) {
|
||||
throw new IllegalStateException("No session available!");
|
||||
}
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the associated {@link MongoDbFactory}.
|
||||
*/
|
||||
@@ -101,7 +118,21 @@ class MongoResourceHolder extends ResourceHolderSupport {
|
||||
return false;
|
||||
}
|
||||
|
||||
return hasServerSession() && !getSession().getServerSession().isClosed();
|
||||
return hasServerSession() && !getRequiredSession().getServerSession().isClosed();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@literal true} if the session has an active transaction.
|
||||
* @since 2.1.3
|
||||
* @see #hasActiveSession()
|
||||
*/
|
||||
boolean hasActiveTransaction() {
|
||||
|
||||
if (!hasActiveSession()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return getRequiredSession().hasActiveTransaction();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -111,7 +142,7 @@ class MongoResourceHolder extends ResourceHolderSupport {
|
||||
boolean hasServerSession() {
|
||||
|
||||
try {
|
||||
return getSession().getServerSession() != null;
|
||||
return getRequiredSession().getServerSession() != null;
|
||||
} catch (IllegalStateException serverSessionClosed) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.mongodb;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* A specific {@link ClientSessionException} related to issues with a transaction such as aborted or non existing
|
||||
* transactions.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.1
|
||||
*/
|
||||
public class MongoTransactionException extends ClientSessionException {
|
||||
|
||||
/**
|
||||
* Constructor for {@link MongoTransactionException}.
|
||||
*
|
||||
* @param msg the detail message. Must not be {@literal null}.
|
||||
*/
|
||||
public MongoTransactionException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for {@link ClientSessionException}.
|
||||
*
|
||||
* @param msg the detail message. Can be {@literal null}.
|
||||
* @param cause the root cause. Can be {@literal null}.
|
||||
*/
|
||||
public MongoTransactionException(@Nullable String msg, @Nullable Throwable cause) {
|
||||
super(msg, cause);
|
||||
}
|
||||
}
|
||||
@@ -47,13 +47,18 @@ import com.mongodb.client.ClientSession;
|
||||
* Application code is required to retrieve the {@link com.mongodb.client.MongoDatabase} via
|
||||
* {@link MongoDatabaseUtils#getDatabase(MongoDbFactory)} instead of a standard {@link MongoDbFactory#getDb()} call.
|
||||
* Spring classes such as {@link org.springframework.data.mongodb.core.MongoTemplate} use this strategy implicitly.
|
||||
*
|
||||
* <p />
|
||||
* By default failure of a {@literal commit} operation raises a {@link TransactionSystemException}. One may override
|
||||
* {@link #doCommit(MongoTransactionObject)} to implement the
|
||||
* <a href="https://docs.mongodb.com/manual/core/transactions/#retry-commit-operation">Retry Commit Operation</a>
|
||||
* behavior as outlined in the MongoDB reference manual.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
* @currentRead Shadow's Edge - Brent Weeks
|
||||
* @since 2.1
|
||||
* @see <a href="https://www.mongodb.com/transactions">MongoDB Transaction Documentation</a>
|
||||
* @see MongoDatabaseUtils#getDatabase(MongoDbFactory, SessionSynchronization)
|
||||
* @see MongoDatabaseUtils#getDatabase(MongoDbFactory, SessionSynchronization)
|
||||
*/
|
||||
public class MongoTransactionManager extends AbstractPlatformTransactionManager
|
||||
implements ResourceTransactionManager, InitializingBean {
|
||||
@@ -70,7 +75,7 @@ public class MongoTransactionManager extends AbstractPlatformTransactionManager
|
||||
* <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)
|
||||
*/
|
||||
@@ -181,7 +186,7 @@ public class MongoTransactionManager extends AbstractPlatformTransactionManager
|
||||
* org.springframework.transaction.support.AbstractPlatformTransactionManager#doCommit(org.springframework.transaction.support.DefaultTransactionStatus)
|
||||
*/
|
||||
@Override
|
||||
protected void doCommit(DefaultTransactionStatus status) throws TransactionException {
|
||||
protected final void doCommit(DefaultTransactionStatus status) throws TransactionException {
|
||||
|
||||
MongoTransactionObject mongoTransactionObject = extractMongoTransaction(status);
|
||||
|
||||
@@ -191,14 +196,45 @@ public class MongoTransactionManager extends AbstractPlatformTransactionManager
|
||||
}
|
||||
|
||||
try {
|
||||
mongoTransactionObject.commitTransaction();
|
||||
} catch (MongoException ex) {
|
||||
doCommit(mongoTransactionObject);
|
||||
} catch (Exception ex) {
|
||||
|
||||
throw new TransactionSystemException(String.format("Could not commit Mongo transaction for session %s.",
|
||||
debugString(mongoTransactionObject.getSession())), ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Customization hook to perform an actual commit of the given transaction.<br />
|
||||
* If a commit operation encounters an error, the MongoDB driver throws a {@link MongoException} holding
|
||||
* {@literal error labels}. <br />
|
||||
* By default those labels are ignored, nevertheless one might check for
|
||||
* {@link MongoException#UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL transient commit errors labels} and retry the the
|
||||
* commit. <br />
|
||||
* <code>
|
||||
* <pre>
|
||||
* int retries = 3;
|
||||
* do {
|
||||
* try {
|
||||
* transactionObject.commitTransaction();
|
||||
* break;
|
||||
* } catch (MongoException ex) {
|
||||
* if (!ex.hasErrorLabel(MongoException.UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL)) {
|
||||
* throw ex;
|
||||
* }
|
||||
* }
|
||||
* Thread.sleep(500);
|
||||
* } while (--retries > 0);
|
||||
* </pre>
|
||||
* </code>
|
||||
*
|
||||
* @param transactionObject never {@literal null}.
|
||||
* @throws Exception in case of transaction errors.
|
||||
*/
|
||||
protected void doCommit(MongoTransactionObject transactionObject) throws Exception {
|
||||
transactionObject.commitTransaction();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* org.springframework.transaction.support.AbstractPlatformTransactionManager#doRollback(org.springframework.transaction.support.DefaultTransactionStatus)
|
||||
@@ -386,7 +422,7 @@ public class MongoTransactionManager extends AbstractPlatformTransactionManager
|
||||
* @since 2.1
|
||||
* @see MongoResourceHolder
|
||||
*/
|
||||
static class MongoTransactionObject implements SmartTransactionObject {
|
||||
protected static class MongoTransactionObject implements SmartTransactionObject {
|
||||
|
||||
private @Nullable MongoResourceHolder resourceHolder;
|
||||
|
||||
@@ -406,7 +442,7 @@ public class MongoTransactionManager extends AbstractPlatformTransactionManager
|
||||
/**
|
||||
* @return {@literal true} if a {@link MongoResourceHolder} is set.
|
||||
*/
|
||||
boolean hasResourceHolder() {
|
||||
final boolean hasResourceHolder() {
|
||||
return resourceHolder != null;
|
||||
}
|
||||
|
||||
@@ -428,14 +464,14 @@ public class MongoTransactionManager extends AbstractPlatformTransactionManager
|
||||
/**
|
||||
* Commit the transaction.
|
||||
*/
|
||||
void commitTransaction() {
|
||||
public void commitTransaction() {
|
||||
getRequiredSession().commitTransaction();
|
||||
}
|
||||
|
||||
/**
|
||||
* Rollback (abort) the transaction.
|
||||
*/
|
||||
void abortTransaction() {
|
||||
public void abortTransaction() {
|
||||
getRequiredSession().abortTransaction();
|
||||
}
|
||||
|
||||
@@ -451,7 +487,7 @@ public class MongoTransactionManager extends AbstractPlatformTransactionManager
|
||||
}
|
||||
|
||||
@Nullable
|
||||
ClientSession getSession() {
|
||||
public ClientSession getSession() {
|
||||
return resourceHolder != null ? resourceHolder.getSession() : null;
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ import org.springframework.data.mongodb.core.ReactiveMongoOperations;
|
||||
import org.springframework.data.mongodb.core.ReactiveMongoTemplate;
|
||||
import org.springframework.data.mongodb.core.SimpleReactiveMongoDatabaseFactory;
|
||||
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
|
||||
import org.springframework.data.mongodb.core.convert.NoOpDbRefResolver;
|
||||
|
||||
import com.mongodb.reactivestreams.client.MongoClient;
|
||||
|
||||
@@ -80,8 +81,7 @@ public abstract class AbstractReactiveMongoConfiguration extends MongoConfigurat
|
||||
@Bean
|
||||
public MappingMongoConverter mappingMongoConverter() throws Exception {
|
||||
|
||||
MappingMongoConverter converter = new MappingMongoConverter(ReactiveMongoTemplate.NO_OP_REF_RESOLVER,
|
||||
mongoMappingContext());
|
||||
MappingMongoConverter converter = new MappingMongoConverter(NoOpDbRefResolver.INSTANCE, mongoMappingContext());
|
||||
converter.setCustomConversions(customConversions());
|
||||
|
||||
return converter;
|
||||
|
||||
@@ -60,6 +60,7 @@ import org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexCre
|
||||
import org.springframework.data.mongodb.core.mapping.Document;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
|
||||
import org.springframework.data.mongodb.core.mapping.event.ValidatingMongoEventListener;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
@@ -75,6 +76,7 @@ import org.w3c.dom.Element;
|
||||
* @author Thomas Darimont
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
* @author Zied Yaich
|
||||
*/
|
||||
public class MappingMongoConverterParser implements BeanDefinitionParser {
|
||||
|
||||
@@ -159,6 +161,7 @@ public class MappingMongoConverterParser implements BeanDefinitionParser {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private BeanDefinition potentiallyCreateValidatingMongoEventListener(Element element, ParserContext parserContext) {
|
||||
|
||||
String disableValidation = element.getAttribute("disable-validation");
|
||||
@@ -180,6 +183,7 @@ public class MappingMongoConverterParser implements BeanDefinitionParser {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private RuntimeBeanReference getValidator(Object source, ParserContext parserContext) {
|
||||
|
||||
if (!JSR_303_PRESENT) {
|
||||
@@ -197,7 +201,7 @@ public class MappingMongoConverterParser implements BeanDefinitionParser {
|
||||
}
|
||||
|
||||
public static String potentiallyCreateMappingContext(Element element, ParserContext parserContext,
|
||||
BeanDefinition conversionsDefinition, String converterId) {
|
||||
@Nullable BeanDefinition conversionsDefinition, @Nullable String converterId) {
|
||||
|
||||
String ctxRef = element.getAttribute("mapping-context-ref");
|
||||
|
||||
@@ -211,7 +215,7 @@ public class MappingMongoConverterParser implements BeanDefinitionParser {
|
||||
BeanDefinitionBuilder mappingContextBuilder = BeanDefinitionBuilder
|
||||
.genericBeanDefinition(MongoMappingContext.class);
|
||||
|
||||
Set<String> classesToAdd = getInititalEntityClasses(element);
|
||||
Set<String> classesToAdd = getInitialEntityClasses(element);
|
||||
|
||||
if (classesToAdd != null) {
|
||||
mappingContextBuilder.addPropertyValue("initialEntitySet", classesToAdd);
|
||||
@@ -262,6 +266,7 @@ public class MappingMongoConverterParser implements BeanDefinitionParser {
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private BeanDefinition getCustomConversions(Element element, ParserContext parserContext) {
|
||||
|
||||
List<Element> customConvertersElements = DomUtils.getChildElementsByTagName(element, "custom-converters");
|
||||
@@ -269,7 +274,7 @@ public class MappingMongoConverterParser implements BeanDefinitionParser {
|
||||
if (customConvertersElements.size() == 1) {
|
||||
|
||||
Element customerConvertersElement = customConvertersElements.get(0);
|
||||
ManagedList<BeanMetadataElement> converterBeans = new ManagedList<BeanMetadataElement>();
|
||||
ManagedList<BeanMetadataElement> converterBeans = new ManagedList<>();
|
||||
List<Element> converterElements = DomUtils.getChildElementsByTagName(customerConvertersElement, "converter");
|
||||
|
||||
if (converterElements != null) {
|
||||
@@ -285,9 +290,7 @@ public class MappingMongoConverterParser implements BeanDefinitionParser {
|
||||
provider.addExcludeFilter(new NegatingFilter(new AssignableTypeFilter(Converter.class),
|
||||
new AssignableTypeFilter(GenericConverter.class)));
|
||||
|
||||
for (BeanDefinition candidate : provider.findCandidateComponents(packageToScan)) {
|
||||
converterBeans.add(candidate);
|
||||
}
|
||||
converterBeans.addAll(provider.findCandidateComponents(packageToScan));
|
||||
}
|
||||
|
||||
BeanDefinitionBuilder conversionsBuilder = BeanDefinitionBuilder.rootBeanDefinition(MongoCustomConversions.class);
|
||||
@@ -304,7 +307,8 @@ public class MappingMongoConverterParser implements BeanDefinitionParser {
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Set<String> getInititalEntityClasses(Element element) {
|
||||
@Nullable
|
||||
private static Set<String> getInitialEntityClasses(Element element) {
|
||||
|
||||
String basePackage = element.getAttribute(BASE_PACKAGE);
|
||||
|
||||
@@ -317,7 +321,7 @@ public class MappingMongoConverterParser implements BeanDefinitionParser {
|
||||
componentProvider.addIncludeFilter(new AnnotationTypeFilter(Document.class));
|
||||
componentProvider.addIncludeFilter(new AnnotationTypeFilter(Persistent.class));
|
||||
|
||||
Set<String> classes = new ManagedSet<String>();
|
||||
Set<String> classes = new ManagedSet<>();
|
||||
for (BeanDefinition candidate : componentProvider.findCandidateComponents(basePackage)) {
|
||||
classes.add(candidate.getBeanClassName());
|
||||
}
|
||||
@@ -325,6 +329,7 @@ public class MappingMongoConverterParser implements BeanDefinitionParser {
|
||||
return classes;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public BeanMetadataElement parseConverter(Element element, ParserContext parserContext) {
|
||||
|
||||
String converterRef = element.getAttribute("ref");
|
||||
@@ -375,7 +380,7 @@ public class MappingMongoConverterParser implements BeanDefinitionParser {
|
||||
|
||||
Assert.notNull(filters, "TypeFilters must not be null");
|
||||
|
||||
this.delegates = new HashSet<TypeFilter>(Arrays.asList(filters));
|
||||
this.delegates = new HashSet<>(Arrays.asList(filters));
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -35,6 +35,8 @@ import com.mongodb.MongoCredential;
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author Oliver Gierke
|
||||
* @author Stephen Tyler Conrad
|
||||
* @author Mark Paluch
|
||||
* @since 1.7
|
||||
*/
|
||||
public class MongoCredentialPropertyEditor extends PropertyEditorSupport {
|
||||
@@ -98,6 +100,12 @@ public class MongoCredentialPropertyEditor extends PropertyEditorSupport {
|
||||
verifyDatabasePresent(database);
|
||||
credentials.add(MongoCredential.createScramSha1Credential(userNameAndPassword[0], database,
|
||||
userNameAndPassword[1].toCharArray()));
|
||||
} else if (MongoCredential.SCRAM_SHA_256_MECHANISM.equals(authMechanism)) {
|
||||
|
||||
verifyUsernameAndPasswordPresent(userNameAndPassword);
|
||||
verifyDatabasePresent(database);
|
||||
credentials.add(MongoCredential.createScramSha256Credential(userNameAndPassword[0], database,
|
||||
userNameAndPassword[1].toCharArray()));
|
||||
} else {
|
||||
throw new IllegalArgumentException(
|
||||
String.format("Cannot create MongoCredentials for unknown auth mechanism '%s'!", authMechanism));
|
||||
@@ -164,7 +172,7 @@ public class MongoCredentialPropertyEditor extends PropertyEditorSupport {
|
||||
private static Properties extractOptions(String text) {
|
||||
|
||||
int optionsSeparationIndex = text.lastIndexOf(OPTIONS_DELIMITER);
|
||||
int dbSeparationIndex = text.lastIndexOf(OPTIONS_DELIMITER);
|
||||
int dbSeparationIndex = text.lastIndexOf(DATABASE_DELIMITER);
|
||||
|
||||
if (optionsSeparationIndex == -1 || dbSeparationIndex > optionsSeparationIndex) {
|
||||
return new Properties();
|
||||
@@ -173,7 +181,13 @@ public class MongoCredentialPropertyEditor extends PropertyEditorSupport {
|
||||
Properties properties = new Properties();
|
||||
|
||||
for (String option : text.substring(optionsSeparationIndex + 1).split(OPTION_VALUE_DELIMITER)) {
|
||||
|
||||
String[] optionArgs = option.split("=");
|
||||
|
||||
if (optionArgs.length == 1) {
|
||||
throw new IllegalArgumentException(String.format("Query parameter '%s' has no value!", optionArgs[0]));
|
||||
}
|
||||
|
||||
properties.put(optionArgs[0], optionArgs[1]);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
/*
|
||||
* Copyright 2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.mongodb.core;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.bson.Document;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.mongodb.core.aggregation.Aggregation;
|
||||
import org.springframework.data.mongodb.core.aggregation.AggregationOperation;
|
||||
import org.springframework.data.mongodb.core.aggregation.AggregationOperationContext;
|
||||
import org.springframework.data.mongodb.core.aggregation.AggregationOptions;
|
||||
import org.springframework.data.mongodb.core.aggregation.CountOperation;
|
||||
import org.springframework.data.mongodb.core.aggregation.TypeBasedAggregationOperationContext;
|
||||
import org.springframework.data.mongodb.core.aggregation.TypedAggregation;
|
||||
import org.springframework.data.mongodb.core.convert.QueryMapper;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
|
||||
import org.springframework.data.mongodb.core.query.CriteriaDefinition;
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
/**
|
||||
* Utility methods to map {@link org.springframework.data.mongodb.core.aggregation.Aggregation} pipeline definitions and
|
||||
* create type-bound {@link AggregationOperationContext}.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
* @since 2.1
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
class AggregationUtil {
|
||||
|
||||
QueryMapper queryMapper;
|
||||
MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext;
|
||||
|
||||
/**
|
||||
* Prepare the {@link AggregationOperationContext} for a given aggregation by either returning the context itself it
|
||||
* is not {@literal null}, create a {@link TypeBasedAggregationOperationContext} if the aggregation contains type
|
||||
* information (is a {@link TypedAggregation}) or use the {@link Aggregation#DEFAULT_CONTEXT}.
|
||||
*
|
||||
* @param aggregation must not be {@literal null}.
|
||||
* @param context can be {@literal null}.
|
||||
* @return the root {@link AggregationOperationContext} to use.
|
||||
*/
|
||||
AggregationOperationContext prepareAggregationContext(Aggregation aggregation,
|
||||
@Nullable AggregationOperationContext context) {
|
||||
|
||||
if (context != null) {
|
||||
return context;
|
||||
}
|
||||
|
||||
if (aggregation instanceof TypedAggregation) {
|
||||
return new TypeBasedAggregationOperationContext(((TypedAggregation) aggregation).getInputType(), mappingContext,
|
||||
queryMapper);
|
||||
}
|
||||
|
||||
return Aggregation.DEFAULT_CONTEXT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract and map the aggregation pipeline into a {@link List} of {@link Document}.
|
||||
*
|
||||
* @param aggregation
|
||||
* @param context
|
||||
* @return
|
||||
*/
|
||||
List<Document> createPipeline(Aggregation aggregation, AggregationOperationContext context) {
|
||||
|
||||
if (!ObjectUtils.nullSafeEquals(context, Aggregation.DEFAULT_CONTEXT)) {
|
||||
return aggregation.toPipeline(context);
|
||||
}
|
||||
|
||||
return mapAggregationPipeline(aggregation.toPipeline(context));
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the command and map the aggregation pipeline.
|
||||
*
|
||||
* @param aggregation
|
||||
* @param context
|
||||
* @return
|
||||
*/
|
||||
Document createCommand(String collection, Aggregation aggregation, AggregationOperationContext context) {
|
||||
|
||||
Document command = aggregation.toDocument(collection, context);
|
||||
|
||||
if (!ObjectUtils.nullSafeEquals(context, Aggregation.DEFAULT_CONTEXT)) {
|
||||
return command;
|
||||
}
|
||||
|
||||
command.put("pipeline", mapAggregationPipeline(command.get("pipeline", List.class)));
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@code $count} aggregation for {@link Query} and optionally a {@link Class entity class}.
|
||||
*
|
||||
* @param query must not be {@literal null}.
|
||||
* @param entityClass can be {@literal null} if the {@link Query} object is empty.
|
||||
* @return the {@link Aggregation} pipeline definition to run a {@code $count} aggregation.
|
||||
*/
|
||||
Aggregation createCountAggregation(Query query, @Nullable Class<?> entityClass) {
|
||||
|
||||
List<AggregationOperation> pipeline = computeCountAggregationPipeline(query, entityClass);
|
||||
|
||||
Aggregation aggregation = entityClass != null ? Aggregation.newAggregation(entityClass, pipeline)
|
||||
: Aggregation.newAggregation(pipeline);
|
||||
aggregation.withOptions(AggregationOptions.builder().collation(query.getCollation().orElse(null)).build());
|
||||
|
||||
return aggregation;
|
||||
}
|
||||
|
||||
private List<AggregationOperation> computeCountAggregationPipeline(Query query, @Nullable Class<?> entityType) {
|
||||
|
||||
CountOperation count = Aggregation.count().as("totalEntityCount");
|
||||
if (query.getQueryObject().isEmpty()) {
|
||||
return Collections.singletonList(count);
|
||||
}
|
||||
|
||||
Assert.notNull(entityType, "Entity type must not be null!");
|
||||
|
||||
Document mappedQuery = queryMapper.getMappedObject(query.getQueryObject(),
|
||||
mappingContext.getPersistentEntity(entityType));
|
||||
|
||||
CriteriaDefinition criteria = new CriteriaDefinition() {
|
||||
|
||||
@Override
|
||||
public Document getCriteriaObject() {
|
||||
return mappedQuery;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String getKey() {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
return Arrays.asList(Aggregation.match(criteria), count);
|
||||
}
|
||||
|
||||
private List<Document> mapAggregationPipeline(List<Document> pipeline) {
|
||||
|
||||
return pipeline.stream().map(val -> queryMapper.getMappedObject(val, Optional.empty()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
@@ -17,8 +17,10 @@ package org.springframework.data.mongodb.core;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.time.Instant;
|
||||
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
|
||||
|
||||
import org.bson.BsonValue;
|
||||
import org.bson.Document;
|
||||
import org.springframework.data.mongodb.core.convert.MongoConverter;
|
||||
import org.springframework.data.mongodb.core.messaging.Message;
|
||||
@@ -26,6 +28,7 @@ import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
import com.mongodb.client.model.changestream.ChangeStreamDocument;
|
||||
import com.mongodb.client.model.changestream.OperationType;
|
||||
|
||||
/**
|
||||
* {@link Message} implementation specific to MongoDB <a href="https://docs.mongodb.com/manual/changeStreams/">Change
|
||||
@@ -38,11 +41,17 @@ import com.mongodb.client.model.changestream.ChangeStreamDocument;
|
||||
@EqualsAndHashCode
|
||||
public class ChangeStreamEvent<T> {
|
||||
|
||||
@SuppressWarnings("rawtypes") //
|
||||
private static final AtomicReferenceFieldUpdater<ChangeStreamEvent, Object> CONVERTED_UPDATER = AtomicReferenceFieldUpdater
|
||||
.newUpdater(ChangeStreamEvent.class, Object.class, "converted");
|
||||
|
||||
private final @Nullable ChangeStreamDocument<Document> raw;
|
||||
|
||||
private final Class<T> targetType;
|
||||
private final MongoConverter converter;
|
||||
private final AtomicReference<T> converted = new AtomicReference<>();
|
||||
|
||||
// accessed through CONVERTED_UPDATER.
|
||||
private volatile @Nullable T converted;
|
||||
|
||||
/**
|
||||
* @param raw can be {@literal null}.
|
||||
@@ -67,6 +76,58 @@ public class ChangeStreamEvent<T> {
|
||||
return raw;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link ChangeStreamDocument#getClusterTime() cluster time} as {@link Instant} the event was emitted at.
|
||||
*
|
||||
* @return can be {@literal null}.
|
||||
*/
|
||||
@Nullable
|
||||
public Instant getTimestamp() {
|
||||
|
||||
return raw != null && raw.getClusterTime() != null
|
||||
? converter.getConversionService().convert(raw.getClusterTime(), Instant.class) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link ChangeStreamDocument#getResumeToken() resume token} for this event.
|
||||
*
|
||||
* @return can be {@literal null}.
|
||||
*/
|
||||
@Nullable
|
||||
public BsonValue getResumeToken() {
|
||||
return raw != null ? raw.getResumeToken() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link ChangeStreamDocument#getOperationType() operation type} for this event.
|
||||
*
|
||||
* @return can be {@literal null}.
|
||||
*/
|
||||
@Nullable
|
||||
public OperationType getOperationType() {
|
||||
return raw != null ? raw.getOperationType() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the database name the event was originated at.
|
||||
*
|
||||
* @return can be {@literal null}.
|
||||
*/
|
||||
@Nullable
|
||||
public String getDatabaseName() {
|
||||
return raw != null ? raw.getNamespace().getDatabaseName() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the collection name the event was originated at.
|
||||
*
|
||||
* @return can be {@literal null}.
|
||||
*/
|
||||
@Nullable
|
||||
public String getCollectionName() {
|
||||
return raw != null ? raw.getNamespace().getCollectionName() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the potentially converted {@link ChangeStreamDocument#getFullDocument()}.
|
||||
*
|
||||
@@ -80,36 +141,48 @@ public class ChangeStreamEvent<T> {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (raw.getFullDocument() == null) {
|
||||
return targetType.cast(raw.getFullDocument());
|
||||
Document fullDocument = raw.getFullDocument();
|
||||
|
||||
if (fullDocument == null) {
|
||||
return targetType.cast(fullDocument);
|
||||
}
|
||||
|
||||
return getConverted();
|
||||
return getConverted(fullDocument);
|
||||
}
|
||||
|
||||
private T getConverted() {
|
||||
@SuppressWarnings("unchecked")
|
||||
private T getConverted(Document fullDocument) {
|
||||
return (T) doGetConverted(fullDocument);
|
||||
}
|
||||
|
||||
private Object doGetConverted(Document fullDocument) {
|
||||
|
||||
Object result = CONVERTED_UPDATER.get(this);
|
||||
|
||||
T result = converted.get();
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (ClassUtils.isAssignable(Document.class, raw.getFullDocument().getClass())) {
|
||||
if (ClassUtils.isAssignable(Document.class, fullDocument.getClass())) {
|
||||
|
||||
result = converter.read(targetType, raw.getFullDocument());
|
||||
return converted.compareAndSet(null, result) ? result : converted.get();
|
||||
result = converter.read(targetType, fullDocument);
|
||||
return CONVERTED_UPDATER.compareAndSet(this, null, result) ? result : CONVERTED_UPDATER.get(this);
|
||||
}
|
||||
|
||||
if (converter.getConversionService().canConvert(raw.getFullDocument().getClass(), targetType)) {
|
||||
if (converter.getConversionService().canConvert(fullDocument.getClass(), targetType)) {
|
||||
|
||||
result = converter.getConversionService().convert(raw.getFullDocument(), targetType);
|
||||
return converted.compareAndSet(null, result) ? result : converted.get();
|
||||
result = converter.getConversionService().convert(fullDocument, targetType);
|
||||
return CONVERTED_UPDATER.compareAndSet(this, null, result) ? result : CONVERTED_UPDATER.get(this);
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException(String.format("No converter found capable of converting %s to %s",
|
||||
raw.getFullDocument().getClass(), targetType));
|
||||
fullDocument.getClass(), targetType));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ChangeStreamEvent {" + "raw=" + raw + ", targetType=" + targetType + '}';
|
||||
|
||||
@@ -17,6 +17,7 @@ package org.springframework.data.mongodb.core;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Arrays;
|
||||
import java.util.Optional;
|
||||
|
||||
@@ -46,6 +47,7 @@ public class ChangeStreamOptions {
|
||||
private @Nullable BsonValue resumeToken;
|
||||
private @Nullable FullDocument fullDocumentLookup;
|
||||
private @Nullable Collation collation;
|
||||
private @Nullable Instant resumeTimestamp;
|
||||
|
||||
protected ChangeStreamOptions() {}
|
||||
|
||||
@@ -77,6 +79,13 @@ public class ChangeStreamOptions {
|
||||
return Optional.ofNullable(collation);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@link Optional#empty()} if not set.
|
||||
*/
|
||||
public Optional<Instant> getResumeTimestamp() {
|
||||
return Optional.ofNullable(resumeTimestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return empty {@link ChangeStreamOptions}.
|
||||
*/
|
||||
@@ -106,6 +115,7 @@ public class ChangeStreamOptions {
|
||||
private @Nullable BsonValue resumeToken;
|
||||
private @Nullable FullDocument fullDocumentLookup;
|
||||
private @Nullable Collation collation;
|
||||
private @Nullable Instant resumeTimestamp;
|
||||
|
||||
private ChangeStreamOptionsBuilder() {}
|
||||
|
||||
@@ -200,6 +210,20 @@ public class ChangeStreamOptions {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the cluster time to resume from.
|
||||
*
|
||||
* @param resumeTimestamp must not be {@literal null}.
|
||||
* @return this.
|
||||
*/
|
||||
public ChangeStreamOptionsBuilder resumeAt(Instant resumeTimestamp) {
|
||||
|
||||
Assert.notNull(resumeTimestamp, "ResumeTimestamp must not be null!");
|
||||
|
||||
this.resumeTimestamp = resumeTimestamp;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the built {@link ChangeStreamOptions}
|
||||
*/
|
||||
@@ -211,6 +235,7 @@ public class ChangeStreamOptions {
|
||||
options.resumeToken = resumeToken;
|
||||
options.fullDocumentLookup = fullDocumentLookup;
|
||||
options.collation = collation;
|
||||
options.resumeTimestamp = resumeTimestamp;
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,683 @@
|
||||
/*
|
||||
* Copyright 2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.mongodb.core;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
import org.bson.Document;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
import org.springframework.data.mapping.IdentifierAccessor;
|
||||
import org.springframework.data.mapping.MappingException;
|
||||
import org.springframework.data.mapping.PersistentPropertyAccessor;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
|
||||
import org.springframework.data.mongodb.core.convert.MongoWriter;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoSimpleTypes;
|
||||
import org.springframework.data.mongodb.core.query.Criteria;
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
|
||||
import com.mongodb.util.JSONParseException;
|
||||
|
||||
/**
|
||||
* Common operations performed on an entity in the context of it's mapping metadata.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Mark Paluch
|
||||
* @since 2.1
|
||||
* @see MongoTemplate
|
||||
* @see ReactiveMongoTemplate
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
class EntityOperations {
|
||||
|
||||
private static final String ID_FIELD = "_id";
|
||||
|
||||
private final @NonNull MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> context;
|
||||
|
||||
/**
|
||||
* Creates a new {@link Entity} for the given bean.
|
||||
*
|
||||
* @param entity must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
public <T> Entity<T> forEntity(T entity) {
|
||||
|
||||
Assert.notNull(entity, "Bean must not be null!");
|
||||
|
||||
if (entity instanceof String) {
|
||||
return new UnmappedEntity(parse(entity.toString()));
|
||||
}
|
||||
|
||||
if (entity instanceof Map) {
|
||||
return new SimpleMappedEntity((Map<String, Object>) entity);
|
||||
}
|
||||
|
||||
return MappedEntity.of(entity, context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link AdaptibleEntity} for the given bean and {@link ConversionService}.
|
||||
*
|
||||
* @param entity must not be {@literal null}.
|
||||
* @param conversionService must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
public <T> AdaptibleEntity<T> forEntity(T entity, ConversionService conversionService) {
|
||||
|
||||
Assert.notNull(entity, "Bean must not be null!");
|
||||
Assert.notNull(conversionService, "ConversionService must not be null!");
|
||||
|
||||
if (entity instanceof String) {
|
||||
return new UnmappedEntity(parse(entity.toString()));
|
||||
}
|
||||
|
||||
if (entity instanceof Map) {
|
||||
return new SimpleMappedEntity((Map<String, Object>) entity);
|
||||
}
|
||||
|
||||
return AdaptibleMappedEntity.of(entity, context, conversionService);
|
||||
}
|
||||
|
||||
public String determineCollectionName(@Nullable Class<?> entityClass) {
|
||||
|
||||
if (entityClass == null) {
|
||||
throw new InvalidDataAccessApiUsageException(
|
||||
"No class parameter provided, entity collection can't be determined!");
|
||||
}
|
||||
|
||||
return context.getRequiredPersistentEntity(entityClass).getCollection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the collection name to be used for the given entity.
|
||||
*
|
||||
* @param obj can be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
@Nullable
|
||||
public String determineEntityCollectionName(@Nullable Object obj) {
|
||||
return null == obj ? null : determineCollectionName(obj.getClass());
|
||||
}
|
||||
|
||||
public Query getByIdInQuery(Collection<?> entities) {
|
||||
|
||||
MultiValueMap<String, Object> byIds = new LinkedMultiValueMap<>();
|
||||
|
||||
entities.stream() //
|
||||
.map(this::forEntity) //
|
||||
.forEach(it -> byIds.add(it.getIdFieldName(), it.getId()));
|
||||
|
||||
Criteria[] criterias = byIds.entrySet().stream() //
|
||||
.map(it -> Criteria.where(it.getKey()).in(it.getValue())) //
|
||||
.toArray(Criteria[]::new);
|
||||
|
||||
return new Query(criterias.length == 1 ? criterias[0] : new Criteria().orOperator(criterias));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the identifier property. Considers mapping information but falls back to the MongoDB default of
|
||||
* {@code _id} if no identifier property can be found.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public String getIdPropertyName(Class<?> type) {
|
||||
|
||||
Assert.notNull(type, "Type must not be null!");
|
||||
|
||||
MongoPersistentEntity<?> persistentEntity = context.getPersistentEntity(type);
|
||||
|
||||
if (persistentEntity != null && persistentEntity.getIdProperty() != null) {
|
||||
return persistentEntity.getRequiredIdProperty().getName();
|
||||
}
|
||||
|
||||
return ID_FIELD;
|
||||
}
|
||||
|
||||
private static Document parse(String source) {
|
||||
|
||||
try {
|
||||
return Document.parse(source);
|
||||
} catch (JSONParseException | org.bson.json.JsonParseException o_O) {
|
||||
throw new MappingException("Could not parse given String to save into a JSON document!", o_O);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A representation of information about an entity.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @since 2.1
|
||||
*/
|
||||
interface Entity<T> {
|
||||
|
||||
/**
|
||||
* Returns the field name of the identifier of the entity.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
String getIdFieldName();
|
||||
|
||||
/**
|
||||
* Returns the identifier of the entity.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
Object getId();
|
||||
|
||||
/**
|
||||
* Returns the {@link Query} to find the entity by its identifier.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
Query getByIdQuery();
|
||||
|
||||
/**
|
||||
* Returns the {@link Query} to find the entity in its current version.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
Query getQueryForVersion();
|
||||
|
||||
/**
|
||||
* Maps the backing entity into a {@link MappedDocument} using the given {@link MongoWriter}.
|
||||
*
|
||||
* @param writer must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
MappedDocument toMappedDocument(MongoWriter<? super T> writer);
|
||||
|
||||
/**
|
||||
* Asserts that the identifier type is updatable in case its not already set.
|
||||
*/
|
||||
default void assertUpdateableIdIfNotSet() {}
|
||||
|
||||
/**
|
||||
* Returns whether the entity is versioned, i.e. if it contains a version property.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
default boolean isVersionedEntity() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the version if the entity has a version property, {@literal null} otherwise.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Nullable
|
||||
Object getVersion();
|
||||
|
||||
/**
|
||||
* Returns the underlying bean.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
T getBean();
|
||||
|
||||
/**
|
||||
* Returns whether the entity is considered to be new.
|
||||
*
|
||||
* @return
|
||||
* @since 2.1.2
|
||||
*/
|
||||
boolean isNew();
|
||||
}
|
||||
|
||||
/**
|
||||
* Information and commands on an entity.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @since 2.1
|
||||
*/
|
||||
interface AdaptibleEntity<T> extends Entity<T> {
|
||||
|
||||
/**
|
||||
* Populates the identifier of the backing entity if it has an identifier property and there's no identifier
|
||||
* currently present.
|
||||
*
|
||||
* @param id must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
@Nullable
|
||||
T populateIdIfNecessary(@Nullable Object id);
|
||||
|
||||
/**
|
||||
* Initializes the version property of the of the current entity if available.
|
||||
*
|
||||
* @return the entity with the version property updated if available.
|
||||
*/
|
||||
T initializeVersionProperty();
|
||||
|
||||
/**
|
||||
* Increments the value of the version property if available.
|
||||
*
|
||||
* @return the entity with the version property incremented if available.
|
||||
*/
|
||||
T incrementVersion();
|
||||
|
||||
/**
|
||||
* Returns the current version value if the entity has a version property.
|
||||
*
|
||||
* @return the current version or {@literal null} in case it's uninitialized or the entity doesn't expose a version
|
||||
* property.
|
||||
*/
|
||||
@Nullable
|
||||
Number getVersion();
|
||||
}
|
||||
|
||||
@RequiredArgsConstructor
|
||||
private static class UnmappedEntity<T extends Map<String, Object>> implements AdaptibleEntity<T> {
|
||||
|
||||
private final T map;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.EntityOperations.PersistableSource#getIdPropertyName()
|
||||
*/
|
||||
@Override
|
||||
public String getIdFieldName() {
|
||||
return ID_FIELD;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.EntityOperations.PersistableSource#getId()
|
||||
*/
|
||||
@Override
|
||||
public Object getId() {
|
||||
return map.get(ID_FIELD);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.EntityOperations.PersistableSource#getByIdQuery()
|
||||
*/
|
||||
@Override
|
||||
public Query getByIdQuery() {
|
||||
return Query.query(Criteria.where(ID_FIELD).is(map.get(ID_FIELD)));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.EntityOperations.MutablePersistableSource#populateIdIfNecessary(java.lang.Object)
|
||||
*/
|
||||
@Nullable
|
||||
@Override
|
||||
public T populateIdIfNecessary(@Nullable Object id) {
|
||||
|
||||
map.put(ID_FIELD, id);
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.EntityOperations.PersistableSource#getQueryForVersion()
|
||||
*/
|
||||
@Override
|
||||
public Query getQueryForVersion() {
|
||||
throw new MappingException("Cannot query for version on plain Documents!");
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.EntityOperations.PersistableSource#toMappedDocument(org.springframework.data.mongodb.core.convert.MongoWriter)
|
||||
*/
|
||||
@Override
|
||||
public MappedDocument toMappedDocument(MongoWriter<? super T> writer) {
|
||||
return MappedDocument.of(map instanceof Document //
|
||||
? (Document) map //
|
||||
: new Document(map));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.EntityOperations.MutablePersistableSource#initializeVersionProperty()
|
||||
*/
|
||||
@Override
|
||||
public T initializeVersionProperty() {
|
||||
return map;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.EntityOperations.MutablePersistableSource#getVersion()
|
||||
*/
|
||||
@Override
|
||||
@Nullable
|
||||
public Number getVersion() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.EntityOperations.MutablePersistableSource#incrementVersion()
|
||||
*/
|
||||
@Override
|
||||
public T incrementVersion() {
|
||||
return map;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.EntityOperations.PersistableSource#getBean()
|
||||
*/
|
||||
@Override
|
||||
public T getBean() {
|
||||
return map;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.EntityOperations.Entity#isNew()
|
||||
*/
|
||||
@Override
|
||||
public boolean isNew() {
|
||||
return map.get(ID_FIELD) != null;
|
||||
}
|
||||
}
|
||||
|
||||
private static class SimpleMappedEntity<T extends Map<String, Object>> extends UnmappedEntity<T> {
|
||||
|
||||
SimpleMappedEntity(T map) {
|
||||
super(map);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.EntityOperations.PersistableSource#toMappedDocument(org.springframework.data.mongodb.core.convert.MongoWriter)
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public MappedDocument toMappedDocument(MongoWriter<? super T> writer) {
|
||||
|
||||
T bean = getBean();
|
||||
bean = (T) (bean instanceof Document //
|
||||
? (Document) bean //
|
||||
: new Document(bean));
|
||||
Document document = new Document();
|
||||
writer.write(bean, document);
|
||||
|
||||
return MappedDocument.of(document);
|
||||
}
|
||||
}
|
||||
|
||||
@RequiredArgsConstructor(access = AccessLevel.PROTECTED)
|
||||
private static class MappedEntity<T> implements Entity<T> {
|
||||
|
||||
private final @NonNull MongoPersistentEntity<?> entity;
|
||||
private final @NonNull IdentifierAccessor idAccessor;
|
||||
private final @NonNull PersistentPropertyAccessor<T> propertyAccessor;
|
||||
|
||||
private static <T> MappedEntity<T> of(T bean,
|
||||
MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> context) {
|
||||
|
||||
MongoPersistentEntity<?> entity = context.getRequiredPersistentEntity(bean.getClass());
|
||||
IdentifierAccessor identifierAccessor = entity.getIdentifierAccessor(bean);
|
||||
PersistentPropertyAccessor<T> propertyAccessor = entity.getPropertyAccessor(bean);
|
||||
|
||||
return new MappedEntity<>(entity, identifierAccessor, propertyAccessor);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.EntityOperations.PersistableSource#getIdPropertyName()
|
||||
*/
|
||||
@Override
|
||||
public String getIdFieldName() {
|
||||
return entity.getRequiredIdProperty().getFieldName();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.EntityOperations.PersistableSource#getId()
|
||||
*/
|
||||
@Override
|
||||
public Object getId() {
|
||||
return idAccessor.getRequiredIdentifier();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.EntityOperations.PersistableSource#getByIdQuery()
|
||||
*/
|
||||
@Override
|
||||
public Query getByIdQuery() {
|
||||
|
||||
if (!entity.hasIdProperty()) {
|
||||
throw new MappingException("No id property found for object of type " + entity.getType() + "!");
|
||||
}
|
||||
|
||||
MongoPersistentProperty idProperty = entity.getRequiredIdProperty();
|
||||
|
||||
return Query.query(Criteria.where(idProperty.getName()).is(getId()));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.EntityOperations.PersistableSource#getQueryForVersion(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public Query getQueryForVersion() {
|
||||
|
||||
MongoPersistentProperty idProperty = entity.getRequiredIdProperty();
|
||||
MongoPersistentProperty property = entity.getRequiredVersionProperty();
|
||||
|
||||
return new Query(Criteria.where(idProperty.getName()).is(getId())//
|
||||
.and(property.getName()).is(getVersion()));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.EntityOperations.PersistableSource#toMappedDocument(org.springframework.data.mongodb.core.convert.MongoWriter)
|
||||
*/
|
||||
@Override
|
||||
public MappedDocument toMappedDocument(MongoWriter<? super T> writer) {
|
||||
|
||||
T bean = propertyAccessor.getBean();
|
||||
|
||||
Document document = new Document();
|
||||
writer.write(bean, document);
|
||||
|
||||
if (document.containsKey(ID_FIELD) && document.get(ID_FIELD) == null) {
|
||||
document.remove(ID_FIELD);
|
||||
}
|
||||
|
||||
return MappedDocument.of(document);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.EntityOperations.Entity#assertUpdateableIdIfNotSet()
|
||||
*/
|
||||
public void assertUpdateableIdIfNotSet() {
|
||||
|
||||
if (!entity.hasIdProperty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
MongoPersistentProperty property = entity.getRequiredIdProperty();
|
||||
Object propertyValue = idAccessor.getIdentifier();
|
||||
|
||||
if (propertyValue != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!MongoSimpleTypes.AUTOGENERATED_ID_TYPES.contains(property.getType())) {
|
||||
throw new InvalidDataAccessApiUsageException(
|
||||
String.format("Cannot autogenerate id of type %s for entity of type %s!", property.getType().getName(),
|
||||
entity.getType().getName()));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.EntityOperations.PersistableSource#isVersionedEntity()
|
||||
*/
|
||||
@Override
|
||||
public boolean isVersionedEntity() {
|
||||
return entity.hasVersionProperty();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.EntityOperations.PersistableSource#getVersion()
|
||||
*/
|
||||
@Override
|
||||
@Nullable
|
||||
public Object getVersion() {
|
||||
return propertyAccessor.getProperty(entity.getRequiredVersionProperty());
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.EntityOperations.PersistableSource#getBean()
|
||||
*/
|
||||
@Override
|
||||
public T getBean() {
|
||||
return propertyAccessor.getBean();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.EntityOperations.Entity#isNew()
|
||||
*/
|
||||
@Override
|
||||
public boolean isNew() {
|
||||
return entity.isNew(propertyAccessor.getBean());
|
||||
}
|
||||
}
|
||||
|
||||
private static class AdaptibleMappedEntity<T> extends MappedEntity<T> implements AdaptibleEntity<T> {
|
||||
|
||||
private final MongoPersistentEntity<?> entity;
|
||||
private final ConvertingPropertyAccessor<T> propertyAccessor;
|
||||
private final IdentifierAccessor identifierAccessor;
|
||||
|
||||
private AdaptibleMappedEntity(MongoPersistentEntity<?> entity, IdentifierAccessor identifierAccessor,
|
||||
ConvertingPropertyAccessor<T> propertyAccessor) {
|
||||
|
||||
super(entity, identifierAccessor, propertyAccessor);
|
||||
|
||||
this.entity = entity;
|
||||
this.propertyAccessor = propertyAccessor;
|
||||
this.identifierAccessor = identifierAccessor;
|
||||
}
|
||||
|
||||
private static <T> AdaptibleEntity<T> of(T bean,
|
||||
MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> context,
|
||||
ConversionService conversionService) {
|
||||
|
||||
MongoPersistentEntity<?> entity = context.getRequiredPersistentEntity(bean.getClass());
|
||||
IdentifierAccessor identifierAccessor = entity.getIdentifierAccessor(bean);
|
||||
PersistentPropertyAccessor<T> propertyAccessor = entity.getPropertyAccessor(bean);
|
||||
|
||||
return new AdaptibleMappedEntity<>(entity, identifierAccessor,
|
||||
new ConvertingPropertyAccessor<>(propertyAccessor, conversionService));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.EntityOperations.AdaptibleEntity#populateIdIfNecessary(java.lang.Object)
|
||||
*/
|
||||
@Nullable
|
||||
@Override
|
||||
public T populateIdIfNecessary(@Nullable Object id) {
|
||||
|
||||
if (id == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
T bean = propertyAccessor.getBean();
|
||||
MongoPersistentProperty idProperty = entity.getIdProperty();
|
||||
|
||||
if (idProperty == null) {
|
||||
return bean;
|
||||
}
|
||||
|
||||
if (identifierAccessor.getIdentifier() != null) {
|
||||
return bean;
|
||||
}
|
||||
|
||||
propertyAccessor.setProperty(idProperty, id);
|
||||
|
||||
return propertyAccessor.getBean();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.EntityOperations.MappedEntity#getVersion()
|
||||
*/
|
||||
@Override
|
||||
@Nullable
|
||||
public Number getVersion() {
|
||||
|
||||
MongoPersistentProperty versionProperty = entity.getRequiredVersionProperty();
|
||||
|
||||
return propertyAccessor.getProperty(versionProperty, Number.class);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.EntityOperations.AdaptibleEntity#initializeVersionProperty()
|
||||
*/
|
||||
@Override
|
||||
public T initializeVersionProperty() {
|
||||
|
||||
if (!entity.hasVersionProperty()) {
|
||||
return propertyAccessor.getBean();
|
||||
}
|
||||
|
||||
MongoPersistentProperty versionProperty = entity.getRequiredVersionProperty();
|
||||
|
||||
propertyAccessor.setProperty(versionProperty, versionProperty.getType().isPrimitive() ? 1 : 0);
|
||||
|
||||
return propertyAccessor.getBean();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.EntityOperations.AdaptibleEntity#incrementVersion()
|
||||
*/
|
||||
@Override
|
||||
public T incrementVersion() {
|
||||
|
||||
MongoPersistentProperty versionProperty = entity.getRequiredVersionProperty();
|
||||
Number version = getVersion();
|
||||
Number nextVersion = version == null ? 0 : version.longValue() + 1;
|
||||
|
||||
propertyAccessor.setProperty(versionProperty, nextVersion);
|
||||
|
||||
return propertyAccessor.getBean();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -119,11 +119,11 @@ class ExecutableAggregationOperationSupport implements ExecutableAggregationOper
|
||||
TypedAggregation<?> typedAggregation = (TypedAggregation<?>) aggregation;
|
||||
|
||||
if (typedAggregation.getInputType() != null) {
|
||||
return template.determineCollectionName(typedAggregation.getInputType());
|
||||
return template.getCollectionName(typedAggregation.getInputType());
|
||||
}
|
||||
}
|
||||
|
||||
return template.determineCollectionName(domainType);
|
||||
return template.getCollectionName(domainType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -230,7 +230,7 @@ class ExecutableFindOperationSupport implements ExecutableFindOperation {
|
||||
}
|
||||
|
||||
private String getCollectionName() {
|
||||
return StringUtils.hasText(collection) ? collection : template.determineCollectionName(domainType);
|
||||
return StringUtils.hasText(collection) ? collection : template.getCollectionName(domainType);
|
||||
}
|
||||
|
||||
private String asString() {
|
||||
|
||||
@@ -63,17 +63,19 @@ public interface ExecutableInsertOperation {
|
||||
* Insert exactly one object.
|
||||
*
|
||||
* @param object must not be {@literal null}.
|
||||
* @return the inserted object.
|
||||
* @throws IllegalArgumentException if object is {@literal null}.
|
||||
*/
|
||||
void one(T object);
|
||||
T one(T object);
|
||||
|
||||
/**
|
||||
* Insert a collection of objects.
|
||||
*
|
||||
* @param objects must not be {@literal null}.
|
||||
* @return the inserted objects.
|
||||
* @throws IllegalArgumentException if objects is {@literal null}.
|
||||
*/
|
||||
void all(Collection<? extends T> objects);
|
||||
Collection<? extends T> all(Collection<? extends T> objects);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -72,11 +72,11 @@ class ExecutableInsertOperationSupport implements ExecutableInsertOperation {
|
||||
* @see org.springframework.data.mongodb.core.ExecutableInsertOperation.TerminatingInsert#insert(java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public void one(T object) {
|
||||
public T one(T object) {
|
||||
|
||||
Assert.notNull(object, "Object must not be null!");
|
||||
|
||||
template.insert(object, getCollectionName());
|
||||
return template.insert(object, getCollectionName());
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -84,11 +84,11 @@ class ExecutableInsertOperationSupport implements ExecutableInsertOperation {
|
||||
* @see org.springframework.data.mongodb.core.ExecutableInsertOperation.TerminatingInsert#all(java.util.Collection)
|
||||
*/
|
||||
@Override
|
||||
public void all(Collection<? extends T> objects) {
|
||||
public Collection<T> all(Collection<? extends T> objects) {
|
||||
|
||||
Assert.notNull(objects, "Objects must not be null!");
|
||||
|
||||
template.insert(objects, getCollectionName());
|
||||
return template.insert(objects, getCollectionName());
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -129,7 +129,7 @@ class ExecutableInsertOperationSupport implements ExecutableInsertOperation {
|
||||
}
|
||||
|
||||
private String getCollectionName() {
|
||||
return StringUtils.hasText(collection) ? collection : template.determineCollectionName(domainType);
|
||||
return StringUtils.hasText(collection) ? collection : template.getCollectionName(domainType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,10 +15,11 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.mongodb.core.mapreduce.MapReduceOptions;
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
import org.springframework.lang.Nullable;
|
||||
@@ -67,8 +68,9 @@ class ExecutableMapReduceOperationSupport implements ExecutableMapReduceOperatio
|
||||
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) {
|
||||
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;
|
||||
@@ -169,7 +171,7 @@ class ExecutableMapReduceOperationSupport implements ExecutableMapReduceOperatio
|
||||
}
|
||||
|
||||
private String getCollectionName() {
|
||||
return StringUtils.hasText(collection) ? collection : template.determineCollectionName(domainType);
|
||||
return StringUtils.hasText(collection) ? collection : template.getCollectionName(domainType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,26 +53,6 @@ public interface ExecutableRemoveOperation {
|
||||
*/
|
||||
<T> ExecutableRemove<T> remove(Class<T> domainType);
|
||||
|
||||
/**
|
||||
* Collection override (optional).
|
||||
*
|
||||
* @param <T>
|
||||
* @author Christoph Strobl
|
||||
* @since 2.0
|
||||
*/
|
||||
interface RemoveWithCollection<T> extends RemoveWithQuery<T> {
|
||||
|
||||
/**
|
||||
* Explicitly set the name of the collection to perform the query 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 RemoveWithCollection}.
|
||||
* @throws IllegalArgumentException if collection is {@literal null}.
|
||||
*/
|
||||
RemoveWithQuery<T> inCollection(String collection);
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
* @since 2.0
|
||||
@@ -104,6 +84,27 @@ public interface ExecutableRemoveOperation {
|
||||
List<T> findAndRemove();
|
||||
}
|
||||
|
||||
/**
|
||||
* Collection override (optional).
|
||||
*
|
||||
* @param <T>
|
||||
* @author Christoph Strobl
|
||||
* @since 2.0
|
||||
*/
|
||||
interface RemoveWithCollection<T> extends RemoveWithQuery<T> {
|
||||
|
||||
/**
|
||||
* Explicitly set the name of the collection to perform the query 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 RemoveWithCollection}.
|
||||
* @throws IllegalArgumentException if collection is {@literal null}.
|
||||
*/
|
||||
RemoveWithQuery<T> inCollection(String collection);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
* @since 2.0
|
||||
|
||||
@@ -123,7 +123,7 @@ class ExecutableRemoveOperationSupport implements ExecutableRemoveOperation {
|
||||
}
|
||||
|
||||
private String getCollectionName() {
|
||||
return StringUtils.hasText(collection) ? collection : template.determineCollectionName(domainType);
|
||||
return StringUtils.hasText(collection) ? collection : template.getCollectionName(domainType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,13 +19,13 @@ import java.util.Optional;
|
||||
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
import org.springframework.data.mongodb.core.query.Update;
|
||||
|
||||
import com.mongodb.client.result.UpdateResult;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
import com.mongodb.client.result.UpdateResult;
|
||||
|
||||
/**
|
||||
* {@link ExecutableUpdateOperation} allows creation and execution of MongoDB update / findAndModify operations in a
|
||||
* fluent API style. <br />
|
||||
* {@link ExecutableUpdateOperation} allows creation and execution of MongoDB update / findAndModify / findAndReplace
|
||||
* operations in a fluent API style. <br />
|
||||
* The starting {@literal domainType} is used for mapping the {@link Query} provided via {@code matching}, as well as
|
||||
* the {@link Update} via {@code apply} into the MongoDB specific representations. The collection to operate on is by
|
||||
* default derived from the initial {@literal domainType} and can be defined there via
|
||||
@@ -57,6 +57,91 @@ public interface ExecutableUpdateOperation {
|
||||
*/
|
||||
<T> ExecutableUpdate<T> update(Class<T> domainType);
|
||||
|
||||
/**
|
||||
* Trigger findAndModify execution by calling one of the terminating methods.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
* @since 2.0
|
||||
*/
|
||||
interface TerminatingFindAndModify<T> {
|
||||
|
||||
/**
|
||||
* Find, modify and return the first matching document.
|
||||
*
|
||||
* @return {@link Optional#empty()} if nothing found.
|
||||
*/
|
||||
default Optional<T> findAndModify() {
|
||||
return Optional.ofNullable(findAndModifyValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* Find, modify and return the first matching document.
|
||||
*
|
||||
* @return {@literal null} if nothing found.
|
||||
*/
|
||||
@Nullable
|
||||
T findAndModifyValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger
|
||||
* <a href="https://docs.mongodb.com/manual/reference/method/db.collection.findOneAndReplace/">findOneAndReplace<a/>
|
||||
* execution by calling one of the terminating methods.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 2.1
|
||||
*/
|
||||
interface TerminatingFindAndReplace<T> {
|
||||
|
||||
/**
|
||||
* Find, replace and return the first matching document.
|
||||
*
|
||||
* @return {@link Optional#empty()} if nothing found.
|
||||
*/
|
||||
default Optional<T> findAndReplace() {
|
||||
return Optional.ofNullable(findAndReplaceValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* Find, replace and return the first matching document.
|
||||
*
|
||||
* @return {@literal null} if nothing found.
|
||||
*/
|
||||
@Nullable
|
||||
T findAndReplaceValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger update execution by calling one of the terminating methods.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.0
|
||||
*/
|
||||
interface TerminatingUpdate<T> extends TerminatingFindAndModify<T>, FindAndModifyWithOptions<T> {
|
||||
|
||||
/**
|
||||
* Update all matching documents in the collection.
|
||||
*
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
UpdateResult all();
|
||||
|
||||
/**
|
||||
* Update the first document in the collection.
|
||||
*
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
UpdateResult first();
|
||||
|
||||
/**
|
||||
* Creates a new document if no documents match the filter query or updates the matching ones.
|
||||
*
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
UpdateResult upsert();
|
||||
}
|
||||
|
||||
/**
|
||||
* Declare the {@link Update} to apply.
|
||||
*
|
||||
@@ -73,6 +158,16 @@ public interface ExecutableUpdateOperation {
|
||||
* @throws IllegalArgumentException if update is {@literal null}.
|
||||
*/
|
||||
TerminatingUpdate<T> apply(Update update);
|
||||
|
||||
/**
|
||||
* Specify {@code replacement} object.
|
||||
*
|
||||
* @param replacement must not be {@literal null}.
|
||||
* @return new instance of {@link FindAndReplaceOptions}.
|
||||
* @throws IllegalArgumentException if options is {@literal null}.
|
||||
* @since 2.1
|
||||
*/
|
||||
FindAndReplaceWithProjection<T> replaceWith(T replacement);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -131,56 +226,43 @@ public interface ExecutableUpdateOperation {
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger findAndModify execution by calling one of the terminating methods.
|
||||
* Define {@link FindAndReplaceOptions}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Christoph Strobl
|
||||
* @since 2.1
|
||||
*/
|
||||
interface TerminatingFindAndModify<T> {
|
||||
interface FindAndReplaceWithOptions<T> extends TerminatingFindAndReplace<T> {
|
||||
|
||||
/**
|
||||
* Find, modify and return the first matching document.
|
||||
* Explicitly define {@link FindAndReplaceOptions} for the {@link Update}.
|
||||
*
|
||||
* @return {@link Optional#empty()} if nothing found.
|
||||
* @param options must not be {@literal null}.
|
||||
* @return new instance of {@link FindAndReplaceOptions}.
|
||||
* @throws IllegalArgumentException if options is {@literal null}.
|
||||
*/
|
||||
default Optional<T> findAndModify() {
|
||||
return Optional.ofNullable(findAndModifyValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* Find, modify and return the first matching document.
|
||||
*
|
||||
* @return {@literal null} if nothing found.
|
||||
*/
|
||||
@Nullable
|
||||
T findAndModifyValue();
|
||||
FindAndReplaceWithProjection<T> withOptions(FindAndReplaceOptions options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger update execution by calling one of the terminating methods.
|
||||
* Result type override (Optional).
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.0
|
||||
* @since 2.1
|
||||
*/
|
||||
interface TerminatingUpdate<T> extends TerminatingFindAndModify<T>, FindAndModifyWithOptions<T> {
|
||||
interface FindAndReplaceWithProjection<T> extends FindAndReplaceWithOptions<T> {
|
||||
|
||||
/**
|
||||
* Update all matching documents in the collection.
|
||||
* Define the target type fields should be mapped to. <br />
|
||||
* Skip this step if you are anyway only interested in the original domain type.
|
||||
*
|
||||
* @return never {@literal null}.
|
||||
* @param resultType must not be {@literal null}.
|
||||
* @param <R> result type.
|
||||
* @return new instance of {@link FindAndReplaceWithProjection}.
|
||||
* @throws IllegalArgumentException if resultType is {@literal null}.
|
||||
*/
|
||||
UpdateResult all();
|
||||
<R> FindAndReplaceWithOptions<R> as(Class<R> resultType);
|
||||
|
||||
/**
|
||||
* Update the first document in the collection.
|
||||
*
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
UpdateResult first();
|
||||
|
||||
/**
|
||||
* Creates a new document if no documents match the filter query or updates the matching ones.
|
||||
*
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
UpdateResult upsert();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -51,7 +51,7 @@ class ExecutableUpdateOperationSupport implements ExecutableUpdateOperation {
|
||||
|
||||
Assert.notNull(domainType, "DomainType must not be null!");
|
||||
|
||||
return new ExecutableUpdateSupport<>(template, domainType, ALL_QUERY, null, null, null);
|
||||
return new ExecutableUpdateSupport<>(template, domainType, ALL_QUERY, null, null, null, null, null, domainType);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -61,14 +61,18 @@ class ExecutableUpdateOperationSupport implements ExecutableUpdateOperation {
|
||||
@RequiredArgsConstructor
|
||||
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
|
||||
static class ExecutableUpdateSupport<T>
|
||||
implements ExecutableUpdate<T>, UpdateWithCollection<T>, UpdateWithQuery<T>, TerminatingUpdate<T> {
|
||||
implements ExecutableUpdate<T>, UpdateWithCollection<T>, UpdateWithQuery<T>, TerminatingUpdate<T>,
|
||||
FindAndReplaceWithOptions<T>, TerminatingFindAndReplace<T>, FindAndReplaceWithProjection<T> {
|
||||
|
||||
@NonNull MongoTemplate template;
|
||||
@NonNull Class<T> domainType;
|
||||
@NonNull Class domainType;
|
||||
Query query;
|
||||
@Nullable Update update;
|
||||
@Nullable String collection;
|
||||
@Nullable FindAndModifyOptions options;
|
||||
@Nullable FindAndModifyOptions findAndModifyOptions;
|
||||
@Nullable FindAndReplaceOptions findAndReplaceOptions;
|
||||
@Nullable Object replacement;
|
||||
@NonNull Class<T> targetType;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
@@ -79,7 +83,8 @@ class ExecutableUpdateOperationSupport implements ExecutableUpdateOperation {
|
||||
|
||||
Assert.notNull(update, "Update must not be null!");
|
||||
|
||||
return new ExecutableUpdateSupport<>(template, domainType, query, update, collection, options);
|
||||
return new ExecutableUpdateSupport<>(template, domainType, query, update, collection, findAndModifyOptions,
|
||||
findAndReplaceOptions, replacement, targetType);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -91,7 +96,8 @@ class ExecutableUpdateOperationSupport implements ExecutableUpdateOperation {
|
||||
|
||||
Assert.hasText(collection, "Collection must not be null nor empty!");
|
||||
|
||||
return new ExecutableUpdateSupport<>(template, domainType, query, update, collection, options);
|
||||
return new ExecutableUpdateSupport<>(template, domainType, query, update, collection, findAndModifyOptions,
|
||||
findAndReplaceOptions, replacement, targetType);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -103,7 +109,34 @@ class ExecutableUpdateOperationSupport implements ExecutableUpdateOperation {
|
||||
|
||||
Assert.notNull(options, "Options must not be null!");
|
||||
|
||||
return new ExecutableUpdateSupport<>(template, domainType, query, update, collection, options);
|
||||
return new ExecutableUpdateSupport<>(template, domainType, query, update, collection, options,
|
||||
findAndReplaceOptions, replacement, targetType);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.ExecutableUpdateOperation.UpdateWithUpdate#replaceWith(Object)
|
||||
*/
|
||||
@Override
|
||||
public FindAndReplaceWithProjection<T> replaceWith(T replacement) {
|
||||
|
||||
Assert.notNull(replacement, "Replacement must not be null!");
|
||||
|
||||
return new ExecutableUpdateSupport<>(template, domainType, query, update, collection, findAndModifyOptions,
|
||||
findAndReplaceOptions, replacement, targetType);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.ExecutableUpdateOperation.FindAndReplaceWithOptions#withOptions(org.springframework.data.mongodb.core.FindAndReplaceOptions)
|
||||
*/
|
||||
@Override
|
||||
public FindAndReplaceWithProjection<T> withOptions(FindAndReplaceOptions options) {
|
||||
|
||||
Assert.notNull(options, "Options must not be null!");
|
||||
|
||||
return new ExecutableUpdateSupport<>(template, domainType, query, update, collection, findAndModifyOptions,
|
||||
options, replacement, targetType);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -115,7 +148,21 @@ class ExecutableUpdateOperationSupport implements ExecutableUpdateOperation {
|
||||
|
||||
Assert.notNull(query, "Query must not be null!");
|
||||
|
||||
return new ExecutableUpdateSupport<>(template, domainType, query, update, collection, options);
|
||||
return new ExecutableUpdateSupport<>(template, domainType, query, update, collection, findAndModifyOptions,
|
||||
findAndReplaceOptions, replacement, targetType);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.ReactiveUpdateOperation.FindAndReplaceWithProjection#as(java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public <R> FindAndReplaceWithOptions<R> as(Class<R> resultType) {
|
||||
|
||||
Assert.notNull(resultType, "ResultType must not be null!");
|
||||
|
||||
return new ExecutableUpdateSupport<>(template, domainType, query, update, collection, findAndModifyOptions,
|
||||
findAndReplaceOptions, replacement, resultType);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -151,7 +198,22 @@ class ExecutableUpdateOperationSupport implements ExecutableUpdateOperation {
|
||||
*/
|
||||
@Override
|
||||
public @Nullable T findAndModifyValue() {
|
||||
return template.findAndModify(query, update, options != null ? options : new FindAndModifyOptions(), domainType, getCollectionName());
|
||||
|
||||
return template.findAndModify(query, update,
|
||||
findAndModifyOptions != null ? findAndModifyOptions : new FindAndModifyOptions(), targetType,
|
||||
getCollectionName());
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.ExecutableUpdateOperation.TerminatingFindAndReplace#findAndReplaceValue()
|
||||
*/
|
||||
@Override
|
||||
public @Nullable T findAndReplaceValue() {
|
||||
|
||||
return (T) template.findAndReplace(query, replacement,
|
||||
findAndReplaceOptions != null ? findAndReplaceOptions : FindAndReplaceOptions.empty(), domainType,
|
||||
getCollectionName(), targetType);
|
||||
}
|
||||
|
||||
private UpdateResult doUpdate(boolean multi, boolean upsert) {
|
||||
@@ -159,7 +221,7 @@ class ExecutableUpdateOperationSupport implements ExecutableUpdateOperation {
|
||||
}
|
||||
|
||||
private String getCollectionName() {
|
||||
return StringUtils.hasText(collection) ? collection : template.determineCollectionName(domainType);
|
||||
return StringUtils.hasText(collection) ? collection : template.getCollectionName(domainType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,15 +36,17 @@ public class FindAndModifyOptions {
|
||||
/**
|
||||
* Static factory method to create a FindAndModifyOptions instance
|
||||
*
|
||||
* @return a new instance
|
||||
* @return new instance of {@link FindAndModifyOptions}.
|
||||
*/
|
||||
public static FindAndModifyOptions options() {
|
||||
return new FindAndModifyOptions();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param options
|
||||
* @return
|
||||
* Create new {@link FindAndModifyOptions} based on option of given {@litearl source}.
|
||||
*
|
||||
* @param source can be {@literal null}.
|
||||
* @return new instance of {@link FindAndModifyOptions}.
|
||||
* @since 2.0
|
||||
*/
|
||||
public static FindAndModifyOptions of(@Nullable FindAndModifyOptions source) {
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Copyright 2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.mongodb.core;
|
||||
|
||||
/**
|
||||
* Options for
|
||||
* <a href="https://docs.mongodb.com/manual/reference/method/db.collection.findOneAndReplace/">findOneAndReplace<a/>.
|
||||
* <br />
|
||||
* Defaults to
|
||||
* <dl>
|
||||
* <dt>returnNew</dt>
|
||||
* <dd>false</dd>
|
||||
* <dt>upsert</dt>
|
||||
* <dd>false</dd>
|
||||
* </dl>
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Christoph Strobl
|
||||
* @since 2.1
|
||||
*/
|
||||
public class FindAndReplaceOptions {
|
||||
|
||||
private boolean returnNew;
|
||||
private boolean upsert;
|
||||
|
||||
/**
|
||||
* Static factory method to create a {@link FindAndReplaceOptions} instance.
|
||||
* <dl>
|
||||
* <dt>returnNew</dt>
|
||||
* <dd>false</dd>
|
||||
* <dt>upsert</dt>
|
||||
* <dd>false</dd>
|
||||
* </dl>
|
||||
*
|
||||
* @return new instance of {@link FindAndReplaceOptions}.
|
||||
*/
|
||||
public static FindAndReplaceOptions options() {
|
||||
return new FindAndReplaceOptions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Static factory method to create a {@link FindAndReplaceOptions} instance with
|
||||
* <dl>
|
||||
* <dt>returnNew</dt>
|
||||
* <dd>false</dd>
|
||||
* <dt>upsert</dt>
|
||||
* <dd>false</dd>
|
||||
* </dl>
|
||||
*
|
||||
* @return new instance of {@link FindAndReplaceOptions}.
|
||||
*/
|
||||
public static FindAndReplaceOptions empty() {
|
||||
return new FindAndReplaceOptions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the replacement document.
|
||||
*
|
||||
* @return this.
|
||||
*/
|
||||
public FindAndReplaceOptions returnNew() {
|
||||
|
||||
this.returnNew = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert a new document if not exists.
|
||||
*
|
||||
* @return this.
|
||||
*/
|
||||
public FindAndReplaceOptions upsert() {
|
||||
|
||||
this.upsert = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the bit indicating to return the replacement document.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean isReturnNew() {
|
||||
return returnNew;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the bit indicating if to create a new document if not exists.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean isUpsert() {
|
||||
return upsert;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright 2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.mongodb.core;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.bson.Document;
|
||||
import org.bson.conversions.Bson;
|
||||
import org.springframework.data.mongodb.core.query.Update;
|
||||
import org.springframework.data.util.StreamUtils;
|
||||
|
||||
import com.mongodb.client.model.Filters;
|
||||
|
||||
/**
|
||||
* A MongoDB document in its mapped state. I.e. after a source document has been mapped using mapping information of the
|
||||
* entity the source document was supposed to represent.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @since 2.1
|
||||
*/
|
||||
@RequiredArgsConstructor(staticName = "of")
|
||||
public class MappedDocument {
|
||||
|
||||
private static final String ID_FIELD = "_id";
|
||||
private static final Document ID_ONLY_PROJECTION = new Document(ID_FIELD, 1);
|
||||
|
||||
private final @Getter Document document;
|
||||
|
||||
public static Document getIdOnlyProjection() {
|
||||
return ID_ONLY_PROJECTION;
|
||||
}
|
||||
|
||||
public static Document getIdIn(Collection<?> ids) {
|
||||
return new Document(ID_FIELD, new Document("$in", ids));
|
||||
}
|
||||
|
||||
public static List<Object> toIds(Collection<Document> documents) {
|
||||
|
||||
return documents.stream()//
|
||||
.map(it -> it.get(ID_FIELD))//
|
||||
.collect(StreamUtils.toUnmodifiableList());
|
||||
}
|
||||
|
||||
public boolean hasId() {
|
||||
return document.containsKey(ID_FIELD);
|
||||
}
|
||||
|
||||
public boolean hasNonNullId() {
|
||||
return hasId() && document.get(ID_FIELD) != null;
|
||||
}
|
||||
|
||||
public Object getId() {
|
||||
return document.get(ID_FIELD);
|
||||
}
|
||||
|
||||
public <T> T getId(Class<T> type) {
|
||||
return document.get(ID_FIELD, type);
|
||||
}
|
||||
|
||||
public boolean isIdPresent(Class<?> type) {
|
||||
return type.isInstance(getId());
|
||||
}
|
||||
|
||||
public Bson getIdFilter() {
|
||||
return Filters.eq(ID_FIELD, document.get(ID_FIELD));
|
||||
}
|
||||
|
||||
public Update updateWithoutId() {
|
||||
return Update.fromDocument(document, ID_FIELD);
|
||||
}
|
||||
}
|
||||
@@ -233,6 +233,15 @@ public abstract class MongoDbFactorySupport<C> implements MongoDbFactory {
|
||||
return delegate.withSession(session);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.MongoDbFactory#isTransactionActive()
|
||||
*/
|
||||
@Override
|
||||
public boolean isTransactionActive() {
|
||||
return session != null && session.hasActiveTransaction();
|
||||
}
|
||||
|
||||
private MongoDatabase proxyMongoDatabase(MongoDatabase database) {
|
||||
return createProxyInstance(session, database, MongoDatabase.class);
|
||||
}
|
||||
@@ -241,7 +250,8 @@ public abstract class MongoDbFactorySupport<C> implements MongoDbFactory {
|
||||
return createProxyInstance(session, database, MongoDatabase.class);
|
||||
}
|
||||
|
||||
private MongoCollection<?> proxyCollection(com.mongodb.session.ClientSession session, MongoCollection<?> collection) {
|
||||
private MongoCollection<?> proxyCollection(com.mongodb.session.ClientSession session,
|
||||
MongoCollection<?> collection) {
|
||||
return createProxyInstance(session, collection, MongoCollection.class);
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
package org.springframework.data.mongodb.core;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
@@ -30,6 +31,7 @@ import org.springframework.dao.PermissionDeniedDataAccessException;
|
||||
import org.springframework.dao.support.PersistenceExceptionTranslator;
|
||||
import org.springframework.data.mongodb.BulkOperationException;
|
||||
import org.springframework.data.mongodb.ClientSessionException;
|
||||
import org.springframework.data.mongodb.MongoTransactionException;
|
||||
import org.springframework.data.mongodb.UncategorizedMongoDbException;
|
||||
import org.springframework.data.mongodb.util.MongoDbErrorCodes;
|
||||
import org.springframework.lang.Nullable;
|
||||
@@ -52,17 +54,17 @@ import com.mongodb.bulk.BulkWriteError;
|
||||
*/
|
||||
public class MongoExceptionTranslator implements PersistenceExceptionTranslator {
|
||||
|
||||
private static final Set<String> DULICATE_KEY_EXCEPTIONS = new HashSet<String>(
|
||||
private static final Set<String> DUPLICATE_KEY_EXCEPTIONS = new HashSet<>(
|
||||
Arrays.asList("MongoException.DuplicateKey", "DuplicateKeyException"));
|
||||
|
||||
private static final Set<String> RESOURCE_FAILURE_EXCEPTIONS = new HashSet<String>(
|
||||
private static final Set<String> RESOURCE_FAILURE_EXCEPTIONS = new HashSet<>(
|
||||
Arrays.asList("MongoException.Network", "MongoSocketException", "MongoException.CursorNotFound",
|
||||
"MongoCursorNotFoundException", "MongoServerSelectionException", "MongoTimeoutException"));
|
||||
|
||||
private static final Set<String> RESOURCE_USAGE_EXCEPTIONS = new HashSet<String>(
|
||||
Arrays.asList("MongoInternalException"));
|
||||
private static final Set<String> RESOURCE_USAGE_EXCEPTIONS = new HashSet<>(
|
||||
Collections.singletonList("MongoInternalException"));
|
||||
|
||||
private static final Set<String> DATA_INTEGRETY_EXCEPTIONS = new HashSet<String>(
|
||||
private static final Set<String> DATA_INTEGRITY_EXCEPTIONS = new HashSet<>(
|
||||
Arrays.asList("WriteConcernException", "MongoWriteException", "MongoBulkWriteException"));
|
||||
|
||||
/*
|
||||
@@ -80,7 +82,7 @@ public class MongoExceptionTranslator implements PersistenceExceptionTranslator
|
||||
|
||||
String exception = ClassUtils.getShortName(ClassUtils.getUserClass(ex.getClass()));
|
||||
|
||||
if (DULICATE_KEY_EXCEPTIONS.contains(exception)) {
|
||||
if (DUPLICATE_KEY_EXCEPTIONS.contains(exception)) {
|
||||
return new DuplicateKeyException(ex.getMessage(), ex);
|
||||
}
|
||||
|
||||
@@ -92,7 +94,7 @@ public class MongoExceptionTranslator implements PersistenceExceptionTranslator
|
||||
return new InvalidDataAccessResourceUsageException(ex.getMessage(), ex);
|
||||
}
|
||||
|
||||
if (DATA_INTEGRETY_EXCEPTIONS.contains(exception)) {
|
||||
if (DATA_INTEGRITY_EXCEPTIONS.contains(exception)) {
|
||||
|
||||
if (ex instanceof MongoServerException) {
|
||||
if (((MongoServerException) ex).getCode() == 11000) {
|
||||
@@ -128,6 +130,10 @@ public class MongoExceptionTranslator implements PersistenceExceptionTranslator
|
||||
return new InvalidDataAccessApiUsageException(ex.getMessage(), ex);
|
||||
} else if (MongoDbErrorCodes.isPermissionDeniedCode(code)) {
|
||||
return new PermissionDeniedDataAccessException(ex.getMessage(), ex);
|
||||
} else if (MongoDbErrorCodes.isClientSessionFailureCode(code)) {
|
||||
return new ClientSessionException(ex.getMessage(), ex);
|
||||
} else if (MongoDbErrorCodes.isTransactionFailureCode(code)) {
|
||||
return new MongoTransactionException(ex.getMessage(), ex);
|
||||
}
|
||||
return new UncategorizedMongoDbException(ex.getMessage(), ex);
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ import org.springframework.data.mongodb.core.aggregation.Aggregation;
|
||||
import org.springframework.data.mongodb.core.aggregation.AggregationOptions;
|
||||
import org.springframework.data.mongodb.core.aggregation.AggregationResults;
|
||||
import org.springframework.data.mongodb.core.aggregation.TypedAggregation;
|
||||
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
|
||||
import org.springframework.data.mongodb.core.convert.MongoConverter;
|
||||
import org.springframework.data.mongodb.core.index.IndexOperations;
|
||||
import org.springframework.data.mongodb.core.mapreduce.GroupBy;
|
||||
@@ -42,6 +43,7 @@ import org.springframework.data.mongodb.core.query.Update;
|
||||
import org.springframework.data.util.CloseableIterator;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
import com.mongodb.ClientSessionOptions;
|
||||
import com.mongodb.Cursor;
|
||||
@@ -381,7 +383,7 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
* Returns a new {@link BulkOperations} for the given entity type and collection name.
|
||||
*
|
||||
* @param mode the {@link BulkMode} to use for bulk operations, must not be {@literal null}.
|
||||
* @param entityClass the name of the entity class. Can be {@literal null}.
|
||||
* @param entityType the name of the entity class. Can be {@literal null}.
|
||||
* @param collectionName the name of the collection to work on, must not be {@literal null} or empty.
|
||||
* @return {@link BulkOperations} on the named collection associated with the given entity class.
|
||||
*/
|
||||
@@ -420,8 +422,6 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
* Execute a group operation over the entire collection. The group operation entity class should match the 'shape' of
|
||||
* the returned object that takes int account the initial document structure as well as any finalize functions.
|
||||
*
|
||||
* @param criteria The criteria that restricts the row that are considered for grouping. If not specified all rows are
|
||||
* considered.
|
||||
* @param inputCollectionName the collection where the group operation will read from
|
||||
* @param groupBy the conditions under which the group operation will be performed, e.g. keys, initial document,
|
||||
* reduce function.
|
||||
@@ -894,6 +894,167 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
<T> T findAndModify(Query query, Update update, FindAndModifyOptions options, Class<T> entityClass,
|
||||
String collectionName);
|
||||
|
||||
/**
|
||||
* Triggers
|
||||
* <a href="https://docs.mongodb.com/manual/reference/method/db.collection.findOneAndReplace/">findOneAndReplace<a/>
|
||||
* to replace a single document matching {@link Criteria} of given {@link Query} with the {@code replacement}
|
||||
* document. <br />
|
||||
* The collection name is derived from the {@literal replacement} type. <br />
|
||||
* Options are defaulted to {@link FindAndReplaceOptions#empty()}. <br />
|
||||
* <strong>NOTE:</strong> The replacement entity must not hold an {@literal id}.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional
|
||||
* fields specification. Must not be {@literal null}.
|
||||
* @param replacement the replacement document. Must not be {@literal null}.
|
||||
* @return the converted object that was updated or {@literal null}, if not found.
|
||||
* @since 2.1
|
||||
*/
|
||||
@Nullable
|
||||
default <T> T findAndReplace(Query query, T replacement) {
|
||||
return findAndReplace(query, replacement, FindAndReplaceOptions.empty());
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers
|
||||
* <a href="https://docs.mongodb.com/manual/reference/method/db.collection.findOneAndReplace/">findOneAndReplace<a/>
|
||||
* to replace a single document matching {@link Criteria} of given {@link Query} with the {@code replacement}
|
||||
* document.<br />
|
||||
* Options are defaulted to {@link FindAndReplaceOptions#empty()}. <br />
|
||||
* <strong>NOTE:</strong> The replacement entity must not hold an {@literal id}.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional
|
||||
* fields specification. Must not be {@literal null}.
|
||||
* @param replacement the replacement document. Must not be {@literal null}.
|
||||
* @param collectionName the collection to query. Must not be {@literal null}.
|
||||
* @return the converted object that was updated or {@literal null}, if not found.
|
||||
* @since 2.1
|
||||
*/
|
||||
@Nullable
|
||||
default <T> T findAndReplace(Query query, T replacement, String collectionName) {
|
||||
return findAndReplace(query, replacement, FindAndReplaceOptions.empty(), collectionName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers
|
||||
* <a href="https://docs.mongodb.com/manual/reference/method/db.collection.findOneAndReplace/">findOneAndReplace<a/>
|
||||
* to replace a single document matching {@link Criteria} of given {@link Query} with the {@code replacement} document
|
||||
* taking {@link FindAndReplaceOptions} into account.<br />
|
||||
* <strong>NOTE:</strong> The replacement entity must not hold an {@literal id}.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional
|
||||
* fields specification. Must not be {@literal null}.
|
||||
* @param replacement the replacement document. Must not be {@literal null}.
|
||||
* @param options the {@link FindAndModifyOptions} holding additional information. Must not be {@literal null}.
|
||||
* @return the converted object that was updated or {@literal null}, if not found. Depending on the value of
|
||||
* {@link FindAndReplaceOptions#isReturnNew()} this will either be the object as it was before the update or
|
||||
* as it is after the update.
|
||||
* @since 2.1
|
||||
*/
|
||||
@Nullable
|
||||
default <T> T findAndReplace(Query query, T replacement, FindAndReplaceOptions options) {
|
||||
return findAndReplace(query, replacement, options, getCollectionName(ClassUtils.getUserClass(replacement)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers
|
||||
* <a href="https://docs.mongodb.com/manual/reference/method/db.collection.findOneAndReplace/">findOneAndReplace<a/>
|
||||
* to replace a single document matching {@link Criteria} of given {@link Query} with the {@code replacement} document
|
||||
* taking {@link FindAndReplaceOptions} into account.<br />
|
||||
* <strong>NOTE:</strong> The replacement entity must not hold an {@literal id}.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional
|
||||
* fields specification. Must not be {@literal null}.
|
||||
* @param replacement the replacement document. Must not be {@literal null}.
|
||||
* @param options the {@link FindAndModifyOptions} holding additional information. Must not be {@literal null}.
|
||||
* @return the converted object that was updated or {@literal null}, if not found. Depending on the value of
|
||||
* {@link FindAndReplaceOptions#isReturnNew()} this will either be the object as it was before the update or
|
||||
* as it is after the update.
|
||||
* @since 2.1
|
||||
*/
|
||||
@Nullable
|
||||
default <T> T findAndReplace(Query query, T replacement, FindAndReplaceOptions options, String collectionName) {
|
||||
|
||||
Assert.notNull(replacement, "Replacement must not be null!");
|
||||
return findAndReplace(query, replacement, options, (Class<T>) ClassUtils.getUserClass(replacement), collectionName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers
|
||||
* <a href="https://docs.mongodb.com/manual/reference/method/db.collection.findOneAndReplace/">findOneAndReplace<a/>
|
||||
* to replace a single document matching {@link Criteria} of given {@link Query} with the {@code replacement} document
|
||||
* taking {@link FindAndReplaceOptions} into account.<br />
|
||||
* <strong>NOTE:</strong> The replacement entity must not hold an {@literal id}.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional
|
||||
* fields specification. Must not be {@literal null}.
|
||||
* @param replacement the replacement document. Must not be {@literal null}.
|
||||
* @param options the {@link FindAndModifyOptions} holding additional information. Must not be {@literal null}.
|
||||
* @param entityType the parametrized type. Must not be {@literal null}.
|
||||
* @param collectionName the collection to query. Must not be {@literal null}.
|
||||
* @return the converted object that was updated or {@literal null}, if not found. Depending on the value of
|
||||
* {@link FindAndReplaceOptions#isReturnNew()} this will either be the object as it was before the update or
|
||||
* as it is after the update.
|
||||
* @since 2.1
|
||||
*/
|
||||
@Nullable
|
||||
default <T> T findAndReplace(Query query, T replacement, FindAndReplaceOptions options, Class<T> entityType,
|
||||
String collectionName) {
|
||||
|
||||
return findAndReplace(query, replacement, options, entityType, collectionName, entityType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers
|
||||
* <a href="https://docs.mongodb.com/manual/reference/method/db.collection.findOneAndReplace/">findOneAndReplace<a/>
|
||||
* to replace a single document matching {@link Criteria} of given {@link Query} with the {@code replacement} document
|
||||
* taking {@link FindAndReplaceOptions} into account.<br />
|
||||
* <strong>NOTE:</strong> The replacement entity must not hold an {@literal id}.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional
|
||||
* fields specification. Must not be {@literal null}.
|
||||
* @param replacement the replacement document. Must not be {@literal null}.
|
||||
* @param options the {@link FindAndModifyOptions} holding additional information. Must not be {@literal null}.
|
||||
* @param entityType the type used for mapping the {@link Query} to domain type fields and deriving the collection
|
||||
* from. Must not be {@literal null}.
|
||||
* @param resultType the parametrized type projection return type. Must not be {@literal null}, use the domain type of
|
||||
* {@code Object.class} instead.
|
||||
* @return the converted object that was updated or {@literal null}, if not found. Depending on the value of
|
||||
* {@link FindAndReplaceOptions#isReturnNew()} this will either be the object as it was before the update or
|
||||
* as it is after the update.
|
||||
* @since 2.1
|
||||
*/
|
||||
@Nullable
|
||||
default <S, T> T findAndReplace(Query query, S replacement, FindAndReplaceOptions options, Class<S> entityType,
|
||||
Class<T> resultType) {
|
||||
|
||||
return findAndReplace(query, replacement, options, entityType,
|
||||
getCollectionName(ClassUtils.getUserClass(entityType)), resultType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers
|
||||
* <a href="https://docs.mongodb.com/manual/reference/method/db.collection.findOneAndReplace/">findOneAndReplace<a/>
|
||||
* to replace a single document matching {@link Criteria} of given {@link Query} with the {@code replacement} document
|
||||
* taking {@link FindAndReplaceOptions} into account.<br />
|
||||
* <strong>NOTE:</strong> The replacement entity must not hold an {@literal id}.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional
|
||||
* fields specification. Must not be {@literal null}.
|
||||
* @param replacement the replacement document. Must not be {@literal null}.
|
||||
* @param options the {@link FindAndModifyOptions} holding additional information. Must not be {@literal null}.
|
||||
* @param entityType the type used for mapping the {@link Query} to domain type fields. Must not be {@literal null}.
|
||||
* @param collectionName the collection to query. Must not be {@literal null}.
|
||||
* @param resultType the parametrized type projection return type. Must not be {@literal null}, use the domain type of
|
||||
* {@code Object.class} instead.
|
||||
* @return the converted object that was updated or {@literal null}, if not found. Depending on the value of
|
||||
* {@link FindAndReplaceOptions#isReturnNew()} this will either be the object as it was before the update or
|
||||
* as it is after the update.
|
||||
* @since 2.1
|
||||
*/
|
||||
@Nullable
|
||||
<S, T> T findAndReplace(Query query, S replacement, FindAndReplaceOptions options, Class<S> entityType,
|
||||
String collectionName, Class<T> resultType);
|
||||
|
||||
/**
|
||||
* Map the results of an ad-hoc query on the collection for the entity type to a single instance of an object of the
|
||||
* specified type. The first document that matches the query is returned and also removed from the collection in the
|
||||
@@ -980,8 +1141,9 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
* Insert is used to initially store the object into the database. To update an existing object use the save method.
|
||||
*
|
||||
* @param objectToSave the object to store in the collection. Must not be {@literal null}.
|
||||
* @return the inserted object.
|
||||
*/
|
||||
void insert(Object objectToSave);
|
||||
<T> T insert(T objectToSave);
|
||||
|
||||
/**
|
||||
* Insert the object into the specified collection.
|
||||
@@ -993,32 +1155,36 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
*
|
||||
* @param objectToSave the object to store in the collection. Must not be {@literal null}.
|
||||
* @param collectionName name of the collection to store the object in. Must not be {@literal null}.
|
||||
* @return the inserted object.
|
||||
*/
|
||||
void insert(Object objectToSave, String collectionName);
|
||||
<T> T insert(T objectToSave, String collectionName);
|
||||
|
||||
/**
|
||||
* Insert a Collection of objects into a collection in a single batch write to the database.
|
||||
*
|
||||
* @param batchToSave the batch of objects to save. Must not be {@literal null}.
|
||||
* @param entityClass class that determines the collection to use. Must not be {@literal null}.
|
||||
* @return the inserted objects that.
|
||||
*/
|
||||
void insert(Collection<? extends Object> batchToSave, Class<?> entityClass);
|
||||
<T> Collection<T> insert(Collection<? extends T> batchToSave, Class<?> entityClass);
|
||||
|
||||
/**
|
||||
* Insert a batch of objects into the specified collection in a single batch write to the database.
|
||||
*
|
||||
* @param batchToSave the list of objects to save. Must not be {@literal null}.
|
||||
* @param collectionName name of the collection to store the object in. Must not be {@literal null}.
|
||||
* @return the inserted objects that.
|
||||
*/
|
||||
void insert(Collection<? extends Object> batchToSave, String collectionName);
|
||||
<T> Collection<T> insert(Collection<? extends T> batchToSave, String collectionName);
|
||||
|
||||
/**
|
||||
* Insert a mixed Collection of objects into a database collection determining the collection name to use based on the
|
||||
* class.
|
||||
*
|
||||
* @param objectsToSave the list of objects to save. Must not be {@literal null}.
|
||||
* @return the inserted objects.
|
||||
*/
|
||||
void insertAll(Collection<? extends Object> objectsToSave);
|
||||
<T> Collection<T> insertAll(Collection<? extends T> objectsToSave);
|
||||
|
||||
/**
|
||||
* Save the object to the collection for the entity type of the object to save. This will perform an insert if the
|
||||
@@ -1034,8 +1200,9 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
* Conversion"</a> for more details.
|
||||
*
|
||||
* @param objectToSave the object to store in the collection. Must not be {@literal null}.
|
||||
* @return the saved object.
|
||||
*/
|
||||
void save(Object objectToSave);
|
||||
<T> T save(T objectToSave);
|
||||
|
||||
/**
|
||||
* Save the object to the specified collection. This will perform an insert if the object is not already present, that
|
||||
@@ -1052,8 +1219,9 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
*
|
||||
* @param objectToSave the object to store in the collection. Must not be {@literal null}.
|
||||
* @param collectionName name of the collection to store the object in. Must not be {@literal null}.
|
||||
* @return the saved object.
|
||||
*/
|
||||
void save(Object objectToSave, String collectionName);
|
||||
<T> T save(T objectToSave, String collectionName);
|
||||
|
||||
/**
|
||||
* Performs an upsert. If no document is found that matches the query, a new document is created and inserted by
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright 2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.mongodb.core;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import org.bson.Document;
|
||||
import org.springframework.data.mapping.SimplePropertyHandler;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
|
||||
import org.springframework.data.projection.ProjectionFactory;
|
||||
import org.springframework.data.projection.ProjectionInformation;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* Common operations performed on properties of an entity like extracting fields information for projection creation.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.1
|
||||
*/
|
||||
@RequiredArgsConstructor(access = AccessLevel.PACKAGE)
|
||||
class PropertyOperations {
|
||||
|
||||
private final MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext;
|
||||
|
||||
/**
|
||||
* For cases where {@code fields} is {@link Document#isEmpty() empty} include only fields that are required for
|
||||
* creating the projection (target) type if the {@code targetType} is a {@literal DTO projection} or a
|
||||
* {@literal closed interface projection}.
|
||||
*
|
||||
* @param projectionFactory must not be {@literal null}.
|
||||
* @param fields must not be {@literal null}.
|
||||
* @param domainType must not be {@literal null}.
|
||||
* @param targetType must not be {@literal null}.
|
||||
* @return {@link Document} with fields to be included.
|
||||
*/
|
||||
Document computeFieldsForProjection(ProjectionFactory projectionFactory, Document fields, Class<?> domainType,
|
||||
Class<?> targetType) {
|
||||
|
||||
if (!fields.isEmpty() || ClassUtils.isAssignable(domainType, targetType)) {
|
||||
return fields;
|
||||
}
|
||||
|
||||
Document projectedFields = new Document();
|
||||
|
||||
if (targetType.isInterface()) {
|
||||
|
||||
ProjectionInformation projectionInformation = projectionFactory.getProjectionInformation(targetType);
|
||||
|
||||
if (projectionInformation.isClosed()) {
|
||||
projectionInformation.getInputProperties().forEach(it -> projectedFields.append(it.getName(), 1));
|
||||
}
|
||||
} else {
|
||||
mappingContext.getRequiredPersistentEntity(targetType).doWithProperties(
|
||||
(SimplePropertyHandler) persistentProperty -> projectedFields.append(persistentProperty.getName(), 1));
|
||||
}
|
||||
|
||||
return projectedFields;
|
||||
}
|
||||
}
|
||||
@@ -30,5 +30,4 @@ import com.mongodb.reactivestreams.client.MongoCollection;
|
||||
public interface ReactiveCollectionCallback<T> {
|
||||
|
||||
Publisher<T> doInCollection(MongoCollection<Document> collection) throws MongoException, DataAccessException;
|
||||
|
||||
}
|
||||
|
||||
@@ -85,6 +85,23 @@ public interface ReactiveFindOperation {
|
||||
*/
|
||||
Flux<T> all();
|
||||
|
||||
/**
|
||||
* Get all matching elements using a {@link com.mongodb.CursorType#TailableAwait tailable cursor}. The stream will
|
||||
* not be completed unless the {@link org.reactivestreams.Subscription} is
|
||||
* {@link org.reactivestreams.Subscription#cancel() canceled}.
|
||||
* <p />
|
||||
* However, the stream may become dead, or invalid, if either the query returns no match or the cursor returns the
|
||||
* document at the "end" of the collection and then the application deletes that document.
|
||||
* <p />
|
||||
* A stream that is no longer in use must be {@link reactor.core.Disposable#dispose()} disposed} otherwise the
|
||||
* streams will linger and exhaust resources. <br/>
|
||||
* <strong>NOTE:</strong> Requires a capped collection.
|
||||
*
|
||||
* @return the {@link Flux} emitting converted objects.
|
||||
* @since 2.1
|
||||
*/
|
||||
Flux<T> tail();
|
||||
|
||||
/**
|
||||
* Get the number of matching elements.
|
||||
*
|
||||
|
||||
@@ -169,6 +169,15 @@ class ReactiveFindOperationSupport implements ReactiveFindOperation {
|
||||
return doFind(null);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.ReactiveFindOperation.TerminatingFind#tail()
|
||||
*/
|
||||
@Override
|
||||
public Flux<T> tail() {
|
||||
return doFind(template.new TailingQueryFindPublisherPreparer(query, domainType));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.ReactiveFindOperation.FindWithQuery#near(org.springframework.data.mongodb.core.query.NearQuery)
|
||||
|
||||
@@ -19,7 +19,6 @@ import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
@@ -27,6 +26,7 @@ import org.bson.Document;
|
||||
import org.reactivestreams.Publisher;
|
||||
import org.reactivestreams.Subscription;
|
||||
import org.springframework.data.geo.GeoResult;
|
||||
import org.springframework.data.mongodb.ReactiveMongoDatabaseFactory;
|
||||
import org.springframework.data.mongodb.core.aggregation.Aggregation;
|
||||
import org.springframework.data.mongodb.core.aggregation.AggregationOptions;
|
||||
import org.springframework.data.mongodb.core.aggregation.TypedAggregation;
|
||||
@@ -41,6 +41,7 @@ import org.springframework.data.mongodb.core.query.Query;
|
||||
import org.springframework.data.mongodb.core.query.Update;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
import com.mongodb.ClientSessionOptions;
|
||||
import com.mongodb.ReadPreference;
|
||||
@@ -688,6 +689,160 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
<T> Mono<T> findAndModify(Query query, Update update, FindAndModifyOptions options, Class<T> entityClass,
|
||||
String collectionName);
|
||||
|
||||
/**
|
||||
* Triggers
|
||||
* <a href="https://docs.mongodb.com/manual/reference/method/db.collection.findOneAndReplace/">findOneAndReplace<a/>
|
||||
* to replace a single document matching {@link Criteria} of given {@link Query} with the {@code replacement}
|
||||
* document. <br />
|
||||
* Options are defaulted to {@link FindAndReplaceOptions#empty()}. <br />
|
||||
* <strong>NOTE:</strong> The replacement entity must not hold an {@literal id}.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional
|
||||
* fields specification. Must not be {@literal null}.
|
||||
* @param replacement the replacement document. Must not be {@literal null}.
|
||||
* @return the converted object that was updated or {@link Mono#empty()}, if not found.
|
||||
* @since 2.1
|
||||
*/
|
||||
default <T> Mono<T> findAndReplace(Query query, T replacement) {
|
||||
return findAndReplace(query, replacement, FindAndReplaceOptions.empty());
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers
|
||||
* <a href="https://docs.mongodb.com/manual/reference/method/db.collection.findOneAndReplace/">findOneAndReplace<a/>
|
||||
* to replace a single document matching {@link Criteria} of given {@link Query} with the {@code replacement}
|
||||
* document. <br />
|
||||
* Options are defaulted to {@link FindAndReplaceOptions#empty()}. <br />
|
||||
* <strong>NOTE:</strong> The replacement entity must not hold an {@literal id}.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional
|
||||
* fields specification. Must not be {@literal null}.
|
||||
* @param replacement the replacement document. Must not be {@literal null}.
|
||||
* @param collectionName the collection to query. Must not be {@literal null}.
|
||||
* @return the converted object that was updated or {@link Mono#empty()}, if not found.
|
||||
* @since 2.1
|
||||
*/
|
||||
default <T> Mono<T> findAndReplace(Query query, T replacement, String collectionName) {
|
||||
return findAndReplace(query, replacement, FindAndReplaceOptions.empty(), collectionName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers
|
||||
* <a href="https://docs.mongodb.com/manual/reference/method/db.collection.findOneAndReplace/">findOneAndReplace<a/>
|
||||
* to replace a single document matching {@link Criteria} of given {@link Query} with the {@code replacement} document
|
||||
* taking {@link FindAndReplaceOptions} into account. <br />
|
||||
* <strong>NOTE:</strong> The replacement entity must not hold an {@literal id}.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional
|
||||
* fields specification. Must not be {@literal null}.
|
||||
* @param replacement the replacement document. Must not be {@literal null}.
|
||||
* @param options the {@link FindAndModifyOptions} holding additional information. Must not be {@literal null}.
|
||||
* @return the converted object that was updated or {@link Mono#empty()}, if not found. Depending on the value of
|
||||
* {@link FindAndReplaceOptions#isReturnNew()} this will either be the object as it was before the update or
|
||||
* as it is after the update.
|
||||
* @since 2.1
|
||||
*/
|
||||
default <T> Mono<T> findAndReplace(Query query, T replacement, FindAndReplaceOptions options) {
|
||||
return findAndReplace(query, replacement, options, getCollectionName(ClassUtils.getUserClass(replacement)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers
|
||||
* <a href="https://docs.mongodb.com/manual/reference/method/db.collection.findOneAndReplace/">findOneAndReplace<a/>
|
||||
* to replace a single document matching {@link Criteria} of given {@link Query} with the {@code replacement} document
|
||||
* taking {@link FindAndReplaceOptions} into account. <br />
|
||||
* <strong>NOTE:</strong> The replacement entity must not hold an {@literal id}.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional
|
||||
* fields specification. Must not be {@literal null}.
|
||||
* @param replacement the replacement document. Must not be {@literal null}.
|
||||
* @param options the {@link FindAndModifyOptions} holding additional information. Must not be {@literal null}.
|
||||
* @return the converted object that was updated or {@link Mono#empty()}, if not found. Depending on the value of
|
||||
* {@link FindAndReplaceOptions#isReturnNew()} this will either be the object as it was before the update or
|
||||
* as it is after the update.
|
||||
* @since 2.1
|
||||
*/
|
||||
default <T> Mono<T> findAndReplace(Query query, T replacement, FindAndReplaceOptions options, String collectionName) {
|
||||
|
||||
Assert.notNull(replacement, "Replacement must not be null!");
|
||||
return findAndReplace(query, replacement, options, (Class<T>) ClassUtils.getUserClass(replacement), collectionName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers
|
||||
* <a href="https://docs.mongodb.com/manual/reference/method/db.collection.findOneAndReplace/">findOneAndReplace<a/>
|
||||
* to replace a single document matching {@link Criteria} of given {@link Query} with the {@code replacement} document
|
||||
* taking {@link FindAndReplaceOptions} into account. <br />
|
||||
* <strong>NOTE:</strong> The replacement entity must not hold an {@literal id}.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional
|
||||
* fields specification. Must not be {@literal null}.
|
||||
* @param replacement the replacement document. Must not be {@literal null}.
|
||||
* @param options the {@link FindAndModifyOptions} holding additional information. Must not be {@literal null}.
|
||||
* @param entityType the parametrized type. Must not be {@literal null}.
|
||||
* @param collectionName the collection to query. Must not be {@literal null}.
|
||||
* @return the converted object that was updated or {@link Mono#empty()}, if not found. Depending on the value of
|
||||
* {@link FindAndReplaceOptions#isReturnNew()} this will either be the object as it was before the update or
|
||||
* as it is after the update.
|
||||
* @since 2.1
|
||||
*/
|
||||
default <T> Mono<T> findAndReplace(Query query, T replacement, FindAndReplaceOptions options, Class<T> entityType,
|
||||
String collectionName) {
|
||||
|
||||
return findAndReplace(query, replacement, options, entityType, collectionName, entityType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers
|
||||
* <a href="https://docs.mongodb.com/manual/reference/method/db.collection.findOneAndReplace/">findOneAndReplace<a/>
|
||||
* to replace a single document matching {@link Criteria} of given {@link Query} with the {@code replacement} document
|
||||
* taking {@link FindAndReplaceOptions} into account. <br />
|
||||
* <strong>NOTE:</strong> The replacement entity must not hold an {@literal id}.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional
|
||||
* fields specification. Must not be {@literal null}.
|
||||
* @param replacement the replacement document. Must not be {@literal null}.
|
||||
* @param options the {@link FindAndModifyOptions} holding additional information. Must not be {@literal null}.
|
||||
* @param entityType the type used for mapping the {@link Query} to domain type fields and deriving the collection
|
||||
* from. Must not be {@literal null}.
|
||||
* @param resultType the parametrized type projection return type. Must not be {@literal null}, use the domain type of
|
||||
* {@code Object.class} instead.
|
||||
* @return the converted object that was updated or {@link Mono#empty()}, if not found. Depending on the value of
|
||||
* {@link FindAndReplaceOptions#isReturnNew()} this will either be the object as it was before the update or
|
||||
* as it is after the update.
|
||||
* @since 2.1
|
||||
*/
|
||||
default <S, T> Mono<T> findAndReplace(Query query, S replacement, FindAndReplaceOptions options, Class<S> entityType,
|
||||
Class<T> resultType) {
|
||||
|
||||
return findAndReplace(query, replacement, options, entityType,
|
||||
getCollectionName(ClassUtils.getUserClass(entityType)), resultType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers
|
||||
* <a href="https://docs.mongodb.com/manual/reference/method/db.collection.findOneAndReplace/">findOneAndReplace<a/>
|
||||
* to replace a single document matching {@link Criteria} of given {@link Query} with the {@code replacement} document
|
||||
* taking {@link FindAndReplaceOptions} into account. <br />
|
||||
* <strong>NOTE:</strong> The replacement entity must not hold an {@literal id}.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional
|
||||
* fields specification. Must not be {@literal null}.
|
||||
* @param replacement the replacement document. Must not be {@literal null}.
|
||||
* @param options the {@link FindAndModifyOptions} holding additional information. Must not be {@literal null}.
|
||||
* @param entityType the type used for mapping the {@link Query} to domain type fields and deriving the collection
|
||||
* from. Must not be {@literal null}.
|
||||
* @param collectionName the collection to query. Must not be {@literal null}.
|
||||
* @param resultType resultType the parametrized type projection return type. Must not be {@literal null}, use the
|
||||
* domain type of {@code Object.class} instead.
|
||||
* @return the converted object that was updated or {@link Mono#empty()}, if not found. Depending on the value of
|
||||
* {@link FindAndReplaceOptions#isReturnNew()} this will either be the object as it was before the update or
|
||||
* as it is after the update.
|
||||
* @since 2.1
|
||||
*/
|
||||
<S, T> Mono<T> findAndReplace(Query query, S replacement, FindAndReplaceOptions options, Class<S> entityType,
|
||||
String collectionName, Class<T> resultType);
|
||||
|
||||
/**
|
||||
* Map the results of an ad-hoc query on the collection for the entity type to a single instance of an object of the
|
||||
* specified type. The first document that matches the query is returned and also removed from the collection in the
|
||||
@@ -772,7 +927,7 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
* Insert is used to initially store the object into the database. To update an existing object use the save method.
|
||||
*
|
||||
* @param objectToSave the object to store in the collection. Must not be {@literal null}.
|
||||
* @return the saved object.
|
||||
* @return the inserted object.
|
||||
*/
|
||||
<T> Mono<T> insert(T objectToSave);
|
||||
|
||||
@@ -786,7 +941,7 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
*
|
||||
* @param objectToSave the object to store in the collection. Must not be {@literal null}.
|
||||
* @param collectionName name of the collection to store the object in. Must not be {@literal null}.
|
||||
* @return the saved object.
|
||||
* @return the inserted object.
|
||||
*/
|
||||
<T> Mono<T> insert(T objectToSave, String collectionName);
|
||||
|
||||
@@ -795,7 +950,7 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
*
|
||||
* @param batchToSave the batch of objects to save. Must not be {@literal null}.
|
||||
* @param entityClass class that determines the collection to use. Must not be {@literal null}.
|
||||
* @return the saved objects.
|
||||
* @return the inserted objects .
|
||||
*/
|
||||
<T> Flux<T> insert(Collection<? extends T> batchToSave, Class<?> entityClass);
|
||||
|
||||
@@ -804,7 +959,7 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
*
|
||||
* @param batchToSave the list of objects to save. Must not be {@literal null}.
|
||||
* @param collectionName name of the collection to store the object in. Must not be {@literal null}.
|
||||
* @return the saved objects.
|
||||
* @return the inserted objects.
|
||||
*/
|
||||
<T> Flux<T> insert(Collection<? extends T> batchToSave, String collectionName);
|
||||
|
||||
@@ -832,7 +987,7 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
* Insert is used to initially store the object into the database. To update an existing object use the save method.
|
||||
*
|
||||
* @param objectToSave the object to store in the collection. Must not be {@literal null}.
|
||||
* @return the saved object.
|
||||
* @return the inserted objects.
|
||||
*/
|
||||
<T> Mono<T> insert(Mono<? extends T> objectToSave);
|
||||
|
||||
@@ -841,7 +996,7 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
*
|
||||
* @param batchToSave the publisher which provides objects to save. Must not be {@literal null}.
|
||||
* @param entityClass class that determines the collection to use. Must not be {@literal null}.
|
||||
* @return the saved objects.
|
||||
* @return the inserted objects.
|
||||
*/
|
||||
<T> Flux<T> insertAll(Mono<? extends Collection<? extends T>> batchToSave, Class<?> entityClass);
|
||||
|
||||
@@ -850,7 +1005,7 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
*
|
||||
* @param batchToSave the publisher which provides objects to save. Must not be {@literal null}.
|
||||
* @param collectionName name of the collection to store the object in. Must not be {@literal null}.
|
||||
* @return the saved objects.
|
||||
* @return the inserted objects.
|
||||
*/
|
||||
<T> Flux<T> insertAll(Mono<? extends Collection<? extends T>> batchToSave, String collectionName);
|
||||
|
||||
@@ -859,7 +1014,7 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
* class.
|
||||
*
|
||||
* @param objectsToSave the publisher which provides objects to save. Must not be {@literal null}.
|
||||
* @return the saved objects.
|
||||
* @return the inserted objects.
|
||||
*/
|
||||
<T> Flux<T> insertAll(Mono<? extends Collection<? extends T>> objectsToSave);
|
||||
|
||||
@@ -1202,7 +1357,57 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
<T> Flux<T> tail(Query query, Class<T> entityClass, String collectionName);
|
||||
|
||||
/**
|
||||
* Subscribe to a MongoDB <a href="https://docs.mongodb.com/manual/changeStreams/">Change Streams</a> via the reactive
|
||||
* Subscribe to a MongoDB <a href="https://docs.mongodb.com/manual/changeStreams/">Change Stream</a> for all events in
|
||||
* the configured default database via the reactive infrastructure. Use the optional provided {@link Aggregation} to
|
||||
* filter events. The stream will not be completed unless the {@link org.reactivestreams.Subscription} is
|
||||
* {@link Subscription#cancel() canceled}.
|
||||
* <p />
|
||||
* The {@link ChangeStreamEvent#getBody()} is mapped to the {@literal resultType} while the
|
||||
* {@link ChangeStreamEvent#getRaw()} contains the unmodified payload.
|
||||
* <p />
|
||||
* Use {@link ChangeStreamOptions} to set arguments like {@link ChangeStreamOptions#getResumeToken() the resumseToken}
|
||||
* for resuming change streams.
|
||||
*
|
||||
* @param options must not be {@literal null}. Use {@link ChangeStreamOptions#empty()}.
|
||||
* @param targetType the result type to use.
|
||||
* @param <T>
|
||||
* @return the {@link Flux} emitting {@link ChangeStreamEvent events} as they arrive.
|
||||
* @since 2.1
|
||||
* @see ReactiveMongoDatabaseFactory#getMongoDatabase()
|
||||
* @see ChangeStreamOptions#getFilter()
|
||||
*/
|
||||
default <T> Flux<ChangeStreamEvent<T>> changeStream(ChangeStreamOptions options, Class<T> targetType) {
|
||||
return changeStream(null, options, targetType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribe to a MongoDB <a href="https://docs.mongodb.com/manual/changeStreams/">Change Stream</a> for all events in
|
||||
* the given collection via the reactive infrastructure. Use the optional provided {@link Aggregation} to filter
|
||||
* events. The stream will not be completed unless the {@link org.reactivestreams.Subscription} is
|
||||
* {@link Subscription#cancel() canceled}.
|
||||
* <p />
|
||||
* The {@link ChangeStreamEvent#getBody()} is mapped to the {@literal resultType} while the
|
||||
* {@link ChangeStreamEvent#getRaw()} contains the unmodified payload.
|
||||
* <p />
|
||||
* Use {@link ChangeStreamOptions} to set arguments like {@link ChangeStreamOptions#getResumeToken() the resumseToken}
|
||||
* for resuming change streams.
|
||||
*
|
||||
* @param collectionName the collection to watch. Can be {@literal null} to watch all collections.
|
||||
* @param options must not be {@literal null}. Use {@link ChangeStreamOptions#empty()}.
|
||||
* @param targetType the result type to use.
|
||||
* @param <T>
|
||||
* @return the {@link Flux} emitting {@link ChangeStreamEvent events} as they arrive.
|
||||
* @since 2.1
|
||||
* @see ChangeStreamOptions#getFilter()
|
||||
*/
|
||||
default <T> Flux<ChangeStreamEvent<T>> changeStream(@Nullable String collectionName, ChangeStreamOptions options,
|
||||
Class<T> targetType) {
|
||||
|
||||
return changeStream(null, collectionName, options, targetType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribe to a MongoDB <a href="https://docs.mongodb.com/manual/changeStreams/">Change Stream</a> via the reactive
|
||||
* infrastructure. Use the optional provided {@link Aggregation} to filter events. The stream will not be completed
|
||||
* unless the {@link org.reactivestreams.Subscription} is {@link Subscription#cancel() canceled}.
|
||||
* <p />
|
||||
@@ -1212,38 +1417,17 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
* Use {@link ChangeStreamOptions} to set arguments like {@link ChangeStreamOptions#getResumeToken() the resumseToken}
|
||||
* for resuming change streams.
|
||||
*
|
||||
* @param filter can be {@literal null}.
|
||||
* @param resultType must not be {@literal null}.
|
||||
* @param options must not be {@literal null}.
|
||||
* @param collectionName must not be {@literal null} nor empty.
|
||||
* @param database the database to watch. Can be {@literal null}, uses configured default if so.
|
||||
* @param collectionName the collection to watch. Can be {@literal null}, watches all collections if so.
|
||||
* @param options must not be {@literal null}. Use {@link ChangeStreamOptions#empty()}.
|
||||
* @param targetType the result type to use.
|
||||
* @param <T>
|
||||
* @return
|
||||
* @return the {@link Flux} emitting {@link ChangeStreamEvent events} as they arrive.
|
||||
* @since 2.1
|
||||
* @see ChangeStreamOptions#getFilter()
|
||||
*/
|
||||
<T> Flux<ChangeStreamEvent<T>> changeStream(@Nullable Aggregation filter, Class<T> resultType,
|
||||
ChangeStreamOptions options, String collectionName);
|
||||
|
||||
/**
|
||||
* Subscribe to a MongoDB <a href="https://docs.mongodb.com/manual/changeStreams/">Change Streams</a> via the reactive
|
||||
* infrastructure. Use the optional provided aggregation chain to filter events. The stream will not be completed
|
||||
* unless the {@link org.reactivestreams.Subscription} is {@link Subscription#cancel() canceled}.
|
||||
* <p />
|
||||
* The {@link ChangeStreamEvent#getBody()} is mapped to the {@literal resultType} while the
|
||||
* {@link ChangeStreamEvent#getRaw()} contains the unmodified payload.
|
||||
* <p />
|
||||
* Use {@link ChangeStreamOptions} to set arguments like {@link ChangeStreamOptions#getResumeToken() the resumeToken}
|
||||
* for resuming change streams.
|
||||
*
|
||||
* @param filter can be empty, must not be {@literal null}.
|
||||
* @param resultType must not be {@literal null}.
|
||||
* @param options must not be {@literal null}.
|
||||
* @param collectionName must not be {@literal null} nor empty.
|
||||
* @param <T>
|
||||
* @return
|
||||
* @since 2.1
|
||||
*/
|
||||
<T> Flux<ChangeStreamEvent<T>> changeStream(List<Document> filter, Class<T> resultType, ChangeStreamOptions options,
|
||||
String collectionName);
|
||||
<T> Flux<ChangeStreamEvent<T>> changeStream(@Nullable String database, @Nullable String collectionName,
|
||||
ChangeStreamOptions options, Class<T> targetType);
|
||||
|
||||
/**
|
||||
* Execute a map-reduce operation. Use {@link MapReduceOptions} to optionally specify an output collection and other
|
||||
@@ -1288,4 +1472,13 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
*/
|
||||
MongoConverter getConverter();
|
||||
|
||||
/**
|
||||
* The collection name used for the specified class by this template.
|
||||
*
|
||||
* @param entityClass must not be {@literal null}.
|
||||
* @return
|
||||
* @since 2.1
|
||||
*/
|
||||
String getCollectionName(Class<?> entityClass);
|
||||
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -18,12 +18,13 @@ package org.springframework.data.mongodb.core;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
import org.springframework.data.mongodb.core.query.Update;
|
||||
|
||||
import com.mongodb.client.result.UpdateResult;
|
||||
|
||||
/**
|
||||
* {@link ReactiveUpdateOperation} allows creation and execution of reactive MongoDB update / findAndModify operations
|
||||
* in a fluent API style. <br />
|
||||
* {@link ReactiveUpdateOperation} allows creation and execution of reactive MongoDB update / findAndModify /
|
||||
* findAndReplace operations in a fluent API style. <br />
|
||||
* The starting {@literal domainType} is used for mapping the {@link Query} provided via {@code matching}, as well as
|
||||
* the {@link org.springframework.data.mongodb.core.query.Update} via {@code apply} into the MongoDB specific
|
||||
* representations. The collection to operate on is by default derived from the initial {@literal domainType} and can be
|
||||
@@ -68,6 +69,22 @@ public interface ReactiveUpdateOperation {
|
||||
Mono<T> findAndModify();
|
||||
}
|
||||
|
||||
/**
|
||||
* Compose findAndReplace execution by calling one of the terminating methods.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 2.1
|
||||
*/
|
||||
interface TerminatingFindAndReplace<T> {
|
||||
|
||||
/**
|
||||
* Find, replace and return the first matching document.
|
||||
*
|
||||
* @return {@link Mono#empty()} if nothing found. Never {@literal null}.
|
||||
*/
|
||||
Mono<T> findAndReplace();
|
||||
}
|
||||
|
||||
/**
|
||||
* Compose update execution by calling one of the terminating methods.
|
||||
*/
|
||||
@@ -108,6 +125,16 @@ public interface ReactiveUpdateOperation {
|
||||
* @throws IllegalArgumentException if update is {@literal null}.
|
||||
*/
|
||||
TerminatingUpdate<T> apply(org.springframework.data.mongodb.core.query.Update update);
|
||||
|
||||
/**
|
||||
* Specify {@code replacement} object.
|
||||
*
|
||||
* @param replacement must not be {@literal null}.
|
||||
* @return new instance of {@link FindAndReplaceOptions}.
|
||||
* @throws IllegalArgumentException if options is {@literal null}.
|
||||
* @since 2.1
|
||||
*/
|
||||
FindAndReplaceWithProjection<T> replaceWith(T replacement);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -157,5 +184,45 @@ public interface ReactiveUpdateOperation {
|
||||
TerminatingFindAndModify<T> withOptions(FindAndModifyOptions options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Define {@link FindAndReplaceOptions}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Christoph Strobl
|
||||
* @since 2.1
|
||||
*/
|
||||
interface FindAndReplaceWithOptions<T> extends TerminatingFindAndReplace<T> {
|
||||
|
||||
/**
|
||||
* Explicitly define {@link FindAndReplaceOptions} for the {@link Update}.
|
||||
*
|
||||
* @param options must not be {@literal null}.
|
||||
* @return new instance of {@link FindAndReplaceOptions}.
|
||||
* @throws IllegalArgumentException if options is {@literal null}.
|
||||
*/
|
||||
FindAndReplaceWithProjection<T> withOptions(FindAndReplaceOptions options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Result type override (Optional).
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.1
|
||||
*/
|
||||
interface FindAndReplaceWithProjection<T> extends FindAndReplaceWithOptions<T> {
|
||||
|
||||
/**
|
||||
* Define the target type fields should be mapped to. <br />
|
||||
* Skip this step if you are anyway only interested in the original domain type.
|
||||
*
|
||||
* @param resultType must not be {@literal null}.
|
||||
* @param <R> result type.
|
||||
* @return new instance of {@link FindAndReplaceWithProjection}.
|
||||
* @throws IllegalArgumentException if resultType is {@literal null}.
|
||||
*/
|
||||
<R> FindAndReplaceWithOptions<R> as(Class<R> resultType);
|
||||
|
||||
}
|
||||
|
||||
interface ReactiveUpdate<T> extends UpdateWithCollection<T>, UpdateWithQuery<T>, UpdateWithUpdate<T> {}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import lombok.experimental.FieldDefaults;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
@@ -50,20 +51,24 @@ class ReactiveUpdateOperationSupport implements ReactiveUpdateOperation {
|
||||
|
||||
Assert.notNull(domainType, "DomainType must not be null!");
|
||||
|
||||
return new ReactiveUpdateSupport<>(template, domainType, ALL_QUERY, null, null, null);
|
||||
return new ReactiveUpdateSupport<>(template, domainType, ALL_QUERY, null, null, null, null, null, domainType);
|
||||
}
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
|
||||
static class ReactiveUpdateSupport<T>
|
||||
implements ReactiveUpdate<T>, UpdateWithCollection<T>, UpdateWithQuery<T>, TerminatingUpdate<T> {
|
||||
implements ReactiveUpdate<T>, UpdateWithCollection<T>, UpdateWithQuery<T>, TerminatingUpdate<T>,
|
||||
FindAndReplaceWithOptions<T>, FindAndReplaceWithProjection<T>, TerminatingFindAndReplace<T> {
|
||||
|
||||
@NonNull ReactiveMongoTemplate template;
|
||||
@NonNull Class<T> domainType;
|
||||
@NonNull Class<?> domainType;
|
||||
Query query;
|
||||
org.springframework.data.mongodb.core.query.Update update;
|
||||
String collection;
|
||||
FindAndModifyOptions options;
|
||||
@Nullable String collection;
|
||||
@Nullable FindAndModifyOptions findAndModifyOptions;
|
||||
@Nullable FindAndReplaceOptions findAndReplaceOptions;
|
||||
@Nullable Object replacement;
|
||||
@NonNull Class<T> targetType;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
@@ -74,7 +79,8 @@ class ReactiveUpdateOperationSupport implements ReactiveUpdateOperation {
|
||||
|
||||
Assert.notNull(update, "Update must not be null!");
|
||||
|
||||
return new ReactiveUpdateSupport<>(template, domainType, query, update, collection, options);
|
||||
return new ReactiveUpdateSupport<>(template, domainType, query, update, collection, findAndModifyOptions,
|
||||
findAndReplaceOptions, replacement, targetType);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -86,7 +92,8 @@ class ReactiveUpdateOperationSupport implements ReactiveUpdateOperation {
|
||||
|
||||
Assert.hasText(collection, "Collection must not be null nor empty!");
|
||||
|
||||
return new ReactiveUpdateSupport<>(template, domainType, query, update, collection, options);
|
||||
return new ReactiveUpdateSupport<>(template, domainType, query, update, collection, findAndModifyOptions,
|
||||
findAndReplaceOptions, replacement, targetType);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -116,7 +123,18 @@ class ReactiveUpdateOperationSupport implements ReactiveUpdateOperation {
|
||||
|
||||
String collectionName = getCollectionName();
|
||||
|
||||
return template.findAndModify(query, update, options, domainType, collectionName);
|
||||
return template.findAndModify(query, update, findAndModifyOptions, targetType, collectionName);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.ReactiveUpdateOperation.TerminatingFindAndReplace#findAndReplace()
|
||||
*/
|
||||
@Override
|
||||
public Mono<T> findAndReplace() {
|
||||
return template.findAndReplace(query, replacement,
|
||||
findAndReplaceOptions != null ? findAndReplaceOptions : new FindAndReplaceOptions(), (Class) domainType,
|
||||
getCollectionName(), targetType);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -128,7 +146,8 @@ class ReactiveUpdateOperationSupport implements ReactiveUpdateOperation {
|
||||
|
||||
Assert.notNull(query, "Query must not be null!");
|
||||
|
||||
return new ReactiveUpdateSupport<>(template, domainType, query, update, collection, options);
|
||||
return new ReactiveUpdateSupport<>(template, domainType, query, update, collection, findAndModifyOptions,
|
||||
findAndReplaceOptions, replacement, targetType);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -149,7 +168,47 @@ class ReactiveUpdateOperationSupport implements ReactiveUpdateOperation {
|
||||
|
||||
Assert.notNull(options, "Options must not be null!");
|
||||
|
||||
return new ReactiveUpdateSupport<>(template, domainType, query, update, collection, options);
|
||||
return new ReactiveUpdateSupport<>(template, domainType, query, update, collection, options,
|
||||
findAndReplaceOptions, replacement, targetType);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.ReactiveUpdateOperation.UpdateWithUpdate#replaceWith(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public FindAndReplaceWithProjection<T> replaceWith(T replacement) {
|
||||
|
||||
Assert.notNull(replacement, "Replacement must not be null!");
|
||||
|
||||
return new ReactiveUpdateSupport<>(template, domainType, query, update, collection, findAndModifyOptions,
|
||||
findAndReplaceOptions, replacement, targetType);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.ReactiveUpdateOperation.FindAndReplaceWithOptions#withOptions(org.springframework.data.mongodb.core.FindAndReplaceOptions)
|
||||
*/
|
||||
@Override
|
||||
public FindAndReplaceWithProjection<T> withOptions(FindAndReplaceOptions options) {
|
||||
|
||||
Assert.notNull(options, "Options must not be null!");
|
||||
|
||||
return new ReactiveUpdateSupport<>(template, domainType, query, update, collection, findAndModifyOptions, options,
|
||||
replacement, targetType);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.ReactiveUpdateOperation.FindAndReplaceWithProjection#as(java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public <R> FindAndReplaceWithOptions<R> as(Class<R> resultType) {
|
||||
|
||||
Assert.notNull(resultType, "ResultType must not be null!");
|
||||
|
||||
return new ReactiveUpdateSupport<>(template, domainType, query, update, collection, findAndModifyOptions,
|
||||
findAndReplaceOptions, replacement, resultType);
|
||||
}
|
||||
|
||||
private Mono<UpdateResult> doUpdate(boolean multi, boolean upsert) {
|
||||
|
||||
@@ -23,6 +23,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.bson.Document;
|
||||
import org.springframework.data.mongodb.core.aggregation.Aggregation.SystemVariable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
@@ -92,6 +93,10 @@ abstract class AbstractAggregationExpression implements AggregationExpression {
|
||||
return targetDocument;
|
||||
}
|
||||
|
||||
if (value instanceof SystemVariable) {
|
||||
return value.toString();
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ class AggregationOperationRenderer {
|
||||
* {@link Document} representation.
|
||||
*
|
||||
* @param operations must not be {@literal null}.
|
||||
* @param context must not be {@literal null}.
|
||||
* @param rootContext must not be {@literal null}.
|
||||
* @return the {@link List} of {@link Document}.
|
||||
*/
|
||||
static List<Document> toDocument(List<AggregationOperation> operations, AggregationOperationContext rootContext) {
|
||||
|
||||
@@ -267,6 +267,19 @@ public class ArrayOperators {
|
||||
return (usesFieldRef() ? In.arrayOf(fieldReference) : In.arrayOf(expression)).containsValue(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link AggregationExpression} that converts the associated expression into an object.
|
||||
* <strong>NOTE:</strong> Requires MongoDB 3.6 or later.
|
||||
*
|
||||
* @return new instance of {@link ArrayToObject}.
|
||||
* @since 2.1
|
||||
*/
|
||||
public ArrayToObject toObject() {
|
||||
|
||||
return usesFieldRef() ? ArrayToObject.arrayValueOfToObject(fieldReference)
|
||||
: ArrayToObject.arrayValueOfToObject(expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
@@ -1497,4 +1510,59 @@ public class ArrayOperators {
|
||||
In containsValue(Object value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link AggregationExpression} for {@code $arrayToObject} that transforms an array into a single document. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 3.6 or later.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @see <a href=
|
||||
* "https://docs.mongodb.com/manual/reference/operator/aggregation/arrayToObject/">https://docs.mongodb.com/manual/reference/operator/aggregation/arrayToObject/</a>
|
||||
* @since 2.1
|
||||
*/
|
||||
public static class ArrayToObject extends AbstractAggregationExpression {
|
||||
|
||||
private ArrayToObject(Object value) {
|
||||
super(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the given array (e.g. an array of two-element arrays, a field reference to an array,...) to an object.
|
||||
*
|
||||
* @param array must not be {@literal null}.
|
||||
* @return new instance of {@link ArrayToObject}.
|
||||
*/
|
||||
public static ArrayToObject arrayToObject(Object array) {
|
||||
return new ArrayToObject(array);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the array pointed to by the given {@link Field field reference} to an object.
|
||||
*
|
||||
* @param fieldReference must not be {@literal null}.
|
||||
* @return new instance of {@link ArrayToObject}.
|
||||
*/
|
||||
public static ArrayToObject arrayValueOfToObject(String fieldReference) {
|
||||
return new ArrayToObject(Fields.field(fieldReference));
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the result array of the given {@link AggregationExpression expression} to an object.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return new instance of {@link ArrayToObject}.
|
||||
*/
|
||||
public static ArrayToObject arrayValueOfToObject(AggregationExpression expression) {
|
||||
return new ArrayToObject(expression);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.aggregation.AbstractAggregationExpression#getMongoMethod()
|
||||
*/
|
||||
@Override
|
||||
protected String getMongoMethod() {
|
||||
return "$arrayToObject";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,695 @@
|
||||
/*
|
||||
* Copyright 2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.mongodb.core.aggregation;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import org.springframework.data.mongodb.core.schema.JsonSchemaObject.Type;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Gateway to {@literal convert} aggregation operations.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.1
|
||||
*/
|
||||
public class ConvertOperators {
|
||||
|
||||
/**
|
||||
* Take the field referenced by given {@literal fieldReference}.
|
||||
*
|
||||
* @param fieldReference must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public static ConvertOperatorFactory valueOf(String fieldReference) {
|
||||
return new ConvertOperatorFactory(fieldReference);
|
||||
}
|
||||
|
||||
/**
|
||||
* Take the value resulting from the given {@link AggregationExpression}.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public static ConvertOperatorFactory valueOf(AggregationExpression expression) {
|
||||
return new ConvertOperatorFactory(expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public static class ConvertOperatorFactory {
|
||||
|
||||
private final @Nullable String fieldReference;
|
||||
private final @Nullable AggregationExpression expression;
|
||||
|
||||
/**
|
||||
* Creates new {@link ConvertOperatorFactory} for given {@literal fieldReference}.
|
||||
*
|
||||
* @param fieldReference must not be {@literal null}.
|
||||
*/
|
||||
public ConvertOperatorFactory(String fieldReference) {
|
||||
|
||||
Assert.notNull(fieldReference, "FieldReference must not be null!");
|
||||
|
||||
this.fieldReference = fieldReference;
|
||||
this.expression = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link ConvertOperatorFactory} for given {@link AggregationExpression}.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
*/
|
||||
public ConvertOperatorFactory(AggregationExpression expression) {
|
||||
|
||||
Assert.notNull(expression, "Expression must not be null!");
|
||||
|
||||
this.fieldReference = null;
|
||||
this.expression = expression;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link Convert aggregation expression} that takes the associated value and converts it into the type
|
||||
* specified by the given {@code stringTypeIdentifier}. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @param stringTypeIdentifier must not be {@literal null}.
|
||||
* @return new instance of {@link Convert}.
|
||||
*/
|
||||
public Convert convertTo(String stringTypeIdentifier) {
|
||||
return createConvert().to(stringTypeIdentifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link Convert aggregation expression} that takes the associated value and converts it into the type
|
||||
* specified by the given {@code numericTypeIdentifier}. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @param numericTypeIdentifier must not be {@literal null}.
|
||||
* @return new instance of {@link Convert}.
|
||||
*/
|
||||
public Convert convertTo(int numericTypeIdentifier) {
|
||||
return createConvert().to(numericTypeIdentifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link Convert aggregation expression} that takes the associated value and converts it into the type
|
||||
* specified by the given {@link Type}. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
* @return new instance of {@link Convert}.
|
||||
*/
|
||||
public Convert convertTo(Type type) {
|
||||
return createConvert().to(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link Convert aggregation expression} that takes the associated value and converts it into the type
|
||||
* specified by the value of the given {@link Field field reference}. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @param fieldReference must not be {@literal null}.
|
||||
* @return new instance of {@link Convert}.
|
||||
*/
|
||||
public Convert convertToTypeOf(String fieldReference) {
|
||||
return createConvert().toTypeOf(fieldReference);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link Convert aggregation expression} that takes the associated value and converts it into the type
|
||||
* specified by the given {@link AggregationExpression expression}. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return new instance of {@link Convert}.
|
||||
*/
|
||||
public Convert convertToTypeOf(AggregationExpression expression) {
|
||||
return createConvert().toTypeOf(expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link ToBool aggregation expression} for {@code $toBool} that converts a value to boolean. Shorthand
|
||||
* for {@link #convertTo(String) #convertTo("bool")}. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @return new instance of {@link ToBool}.
|
||||
*/
|
||||
public ToBool convertToBoolean() {
|
||||
return ToBool.toBoolean(valueObject());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link ToDate aggregation expression} for {@code $toDate} that converts a value to a date. Shorthand
|
||||
* for {@link #convertTo(String) #convertTo("date")}. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @return new instance of {@link ToDate}.
|
||||
*/
|
||||
public ToDate convertToDate() {
|
||||
return ToDate.toDate(valueObject());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link ToDecimal aggregation expression} for {@code $toDecimal} that converts a value to a decimal.
|
||||
* Shorthand for {@link #convertTo(String) #convertTo("decimal")}. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @return new instance of {@link ToDecimal}.
|
||||
*/
|
||||
public ToDecimal convertToDecimal() {
|
||||
return ToDecimal.toDecimal(valueObject());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link ToDouble aggregation expression} for {@code $toDouble} that converts a value to a decimal.
|
||||
* Shorthand for {@link #convertTo(String) #convertTo("double")}. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @return new instance of {@link ToDouble}.
|
||||
*/
|
||||
public ToDouble convertToDouble() {
|
||||
return ToDouble.toDouble(valueObject());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link ToInt aggregation expression} for {@code $toInt} that converts a value to an int. Shorthand
|
||||
* for {@link #convertTo(String) #convertTo("int")}. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @return new instance of {@link ToInt}.
|
||||
*/
|
||||
public ToInt convertToInt() {
|
||||
return ToInt.toInt(valueObject());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link ToInt aggregation expression} for {@code $toLong} that converts a value to a long. Shorthand
|
||||
* for {@link #convertTo(String) #convertTo("long")}. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @return new instance of {@link ToInt}.
|
||||
*/
|
||||
public ToLong convertToLong() {
|
||||
return ToLong.toLong(valueObject());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link ToInt aggregation expression} for {@code $toObjectId} that converts a value to a objectId. Shorthand
|
||||
* for {@link #convertTo(String) #convertTo("objectId")}. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @return new instance of {@link ToInt}.
|
||||
*/
|
||||
public ToObjectId convertToObjectId() {
|
||||
return ToObjectId.toObjectId(valueObject());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link ToInt aggregation expression} for {@code $toString} that converts a value to a string. Shorthand
|
||||
* for {@link #convertTo(String) #convertTo("string")}. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @return new instance of {@link ToInt}.
|
||||
*/
|
||||
public ToString convertToString() {
|
||||
return ToString.toString(valueObject());
|
||||
}
|
||||
|
||||
private Convert createConvert() {
|
||||
return usesFieldRef() ? Convert.convertValueOf(fieldReference) : Convert.convertValueOf(expression);
|
||||
}
|
||||
|
||||
private Object valueObject() {
|
||||
return usesFieldRef() ? Fields.field(fieldReference) : expression;
|
||||
}
|
||||
|
||||
private boolean usesFieldRef() {
|
||||
return fieldReference != null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link AggregationExpression} for {@code $convert} that converts a value to a specified type. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @see <a href=
|
||||
* "https://docs.mongodb.com/manual/reference/operator/aggregation/convert/">https://docs.mongodb.com/manual/reference/operator/aggregation/convert/</a>
|
||||
* @since 2.1
|
||||
*/
|
||||
public static class Convert extends AbstractAggregationExpression {
|
||||
|
||||
private Convert(Object value) {
|
||||
super(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link Convert} using the given value for the {@literal input} attribute.
|
||||
*
|
||||
* @param value must not be {@literal null}.
|
||||
* @return new instance of {@link Convert}.
|
||||
*/
|
||||
public static Convert convertValue(Object value) {
|
||||
return new Convert(Collections.singletonMap("input", value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link Convert} using the value of the provided {@link Field fieldReference} as {@literal input}
|
||||
* value.
|
||||
*
|
||||
* @param fieldReference must not be {@literal null}.
|
||||
* @return new instance of {@link Convert}.
|
||||
*/
|
||||
public static Convert convertValueOf(String fieldReference) {
|
||||
return convertValue(Fields.field(fieldReference));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link Convert} using the result of the provided {@link AggregationExpression expression} as
|
||||
* {@literal input} value.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return new instance of {@link Convert}.
|
||||
*/
|
||||
public static Convert convertValueOf(AggregationExpression expression) {
|
||||
return convertValue(expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the conversion target type via its {@link String} representation.
|
||||
* <ul>
|
||||
* <li>double</li>
|
||||
* <li>string</li>
|
||||
* <li>objectId</li>
|
||||
* <li>bool</li>
|
||||
* <li>date</li>
|
||||
* <li>int</li>
|
||||
* <li>long</li>
|
||||
* <li>decimal</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param stringTypeIdentifier must not be {@literal null}.
|
||||
* @return new instance of {@link Convert}.
|
||||
*/
|
||||
public Convert to(String stringTypeIdentifier) {
|
||||
return new Convert(append("to", stringTypeIdentifier));
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the conversion target type via its numeric representation.
|
||||
* <dl>
|
||||
* <dt>1</dt>
|
||||
* <dd>double</dd>
|
||||
* <dt>2</dt>
|
||||
* <dd>string</li>
|
||||
* <dt>7</dt>
|
||||
* <dd>objectId</li>
|
||||
* <dt>8</dt>
|
||||
* <dd>bool</dd>
|
||||
* <dt>9</dt>
|
||||
* <dd>date</dd>
|
||||
* <dt>16</dt>
|
||||
* <dd>int</dd>
|
||||
* <dt>18</dt>
|
||||
* <dd>long</dd>
|
||||
* <dt>19</dt>
|
||||
* <dd>decimal</dd>
|
||||
* </dl>
|
||||
*
|
||||
* @param numericTypeIdentifier must not be {@literal null}.
|
||||
* @return new instance of {@link Convert}.
|
||||
*/
|
||||
public Convert to(int numericTypeIdentifier) {
|
||||
return new Convert(append("to", numericTypeIdentifier));
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the conversion target type.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
* @return new instance of {@link Convert}.
|
||||
*/
|
||||
public Convert to(Type type) {
|
||||
|
||||
String typeString = Type.BOOLEAN.equals(type) ? "bool" : type.value().toString();
|
||||
return to(typeString);
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the conversion target type via the value of the given field.
|
||||
*
|
||||
* @param fieldReference must not be {@literal null}.
|
||||
* @return new instance of {@link Convert}.
|
||||
*/
|
||||
public Convert toTypeOf(String fieldReference) {
|
||||
return new Convert(append("to", Fields.field(fieldReference)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the conversion target type via the value of the given {@link AggregationExpression expression}.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return new instance of {@link Convert}.
|
||||
*/
|
||||
public Convert toTypeOf(AggregationExpression expression) {
|
||||
return new Convert(append("to", expression));
|
||||
}
|
||||
|
||||
/**
|
||||
* Optionally specify the value to return on encountering an error during conversion.
|
||||
*
|
||||
* @param value must not be {@literal null}.
|
||||
* @return new instance of {@link Convert}.
|
||||
*/
|
||||
public Convert onErrorReturn(Object value) {
|
||||
return new Convert(append("onError", value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Optionally specify the field holding the value to return on encountering an error during conversion.
|
||||
*
|
||||
* @param fieldReference must not be {@literal null}.
|
||||
* @return new instance of {@link Convert}.
|
||||
*/
|
||||
public Convert onErrorReturnValueOf(String fieldReference) {
|
||||
return onErrorReturn(Fields.field(fieldReference));
|
||||
}
|
||||
|
||||
/**
|
||||
* Optionally specify the expression to evaluate and return on encountering an error during conversion.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return new instance of {@link Convert}.
|
||||
*/
|
||||
public Convert onErrorReturnValueOf(AggregationExpression expression) {
|
||||
return onErrorReturn(expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* Optionally specify the value to return when the input is {@literal null} or missing.
|
||||
*
|
||||
* @param value must not be {@literal null}.
|
||||
* @return new instance of {@link Convert}.
|
||||
*/
|
||||
public Convert onNullReturn(Object value) {
|
||||
return new Convert(append("onNull", value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Optionally specify the field holding the value to return when the input is {@literal null} or missing.
|
||||
*
|
||||
* @param fieldReference must not be {@literal null}.
|
||||
* @return new instance of {@link Convert}.
|
||||
*/
|
||||
public Convert onNullReturnValueOf(String fieldReference) {
|
||||
return onNullReturn(Fields.field(fieldReference));
|
||||
}
|
||||
|
||||
/**
|
||||
* Optionally specify the expression to evaluate and return when the input is {@literal null} or missing.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return new instance of {@link Convert}.
|
||||
*/
|
||||
public Convert onNullReturnValueOf(AggregationExpression expression) {
|
||||
return onNullReturn(expression);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getMongoMethod() {
|
||||
return "$convert";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link AggregationExpression} for {@code $toBool} that converts a value to {@literal boolean}. Shorthand for
|
||||
* {@link Convert#to(String) Convert#to("bool")}. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @see <a href=
|
||||
* "https://docs.mongodb.com/manual/reference/operator/aggregation/toBool/">https://docs.mongodb.com/manual/reference/operator/aggregation/toBool/</a>
|
||||
* @since 2.1
|
||||
*/
|
||||
public static class ToBool extends AbstractAggregationExpression {
|
||||
|
||||
private ToBool(Object value) {
|
||||
super(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link ToBool} using the given value as input.
|
||||
*
|
||||
* @param value must not be {@literal null}.
|
||||
* @return new instance of {@link ToBool}.
|
||||
*/
|
||||
public static ToBool toBoolean(Object value) {
|
||||
return new ToBool(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getMongoMethod() {
|
||||
return "$toBool";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link AggregationExpression} for {@code $toDate} that converts a value to {@literal date}. Shorthand for
|
||||
* {@link Convert#to(String) Convert#to("date")}. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @see <a href=
|
||||
* "https://docs.mongodb.com/manual/reference/operator/aggregation/toDate/">https://docs.mongodb.com/manual/reference/operator/aggregation/toDate/</a>
|
||||
* @since 2.1
|
||||
*/
|
||||
public static class ToDate extends AbstractAggregationExpression {
|
||||
|
||||
private ToDate(Object value) {
|
||||
super(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link ToDate} using the given value as input.
|
||||
*
|
||||
* @param value must not be {@literal null}.
|
||||
* @return new instance of {@link ToDate}.
|
||||
*/
|
||||
public static ToDate toDate(Object value) {
|
||||
return new ToDate(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getMongoMethod() {
|
||||
return "$toDate";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link AggregationExpression} for {@code $toDecimal} that converts a value to {@literal decimal}. Shorthand for
|
||||
* {@link Convert#to(String) Convert#to("decimal")}. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @see <a href=
|
||||
* "https://docs.mongodb.com/manual/reference/operator/aggregation/toDecimal/">https://docs.mongodb.com/manual/reference/operator/aggregation/toDecimal/</a>
|
||||
* @since 2.1
|
||||
*/
|
||||
public static class ToDecimal extends AbstractAggregationExpression {
|
||||
|
||||
private ToDecimal(Object value) {
|
||||
super(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link ToDecimal} using the given value as input.
|
||||
*
|
||||
* @param value must not be {@literal null}.
|
||||
* @return new instance of {@link ToDecimal}.
|
||||
*/
|
||||
public static ToDecimal toDecimal(Object value) {
|
||||
return new ToDecimal(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getMongoMethod() {
|
||||
return "$toDecimal";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link AggregationExpression} for {@code $toDouble} that converts a value to {@literal double}. Shorthand for
|
||||
* {@link Convert#to(String) Convert#to("double")}. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @see <a href=
|
||||
* "https://docs.mongodb.com/manual/reference/operator/aggregation/toDouble/">https://docs.mongodb.com/manual/reference/operator/aggregation/toDouble/</a>
|
||||
* @since 2.1
|
||||
*/
|
||||
public static class ToDouble extends AbstractAggregationExpression {
|
||||
|
||||
private ToDouble(Object value) {
|
||||
super(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link ToDouble} using the given value as input.
|
||||
*
|
||||
* @param value must not be {@literal null}.
|
||||
* @return new instance of {@link ToDouble}.
|
||||
*/
|
||||
public static ToDouble toDouble(Object value) {
|
||||
return new ToDouble(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getMongoMethod() {
|
||||
return "$toDouble";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link AggregationExpression} for {@code $toInt} that converts a value to {@literal integer}. Shorthand for
|
||||
* {@link Convert#to(String) Convert#to("int")}. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @see <a href=
|
||||
* "https://docs.mongodb.com/manual/reference/operator/aggregation/toInt/">https://docs.mongodb.com/manual/reference/operator/aggregation/toInt/</a>
|
||||
* @since 2.1
|
||||
*/
|
||||
public static class ToInt extends AbstractAggregationExpression {
|
||||
|
||||
private ToInt(Object value) {
|
||||
super(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link ToInt} using the given value as input.
|
||||
*
|
||||
* @param value must not be {@literal null}.
|
||||
* @return new instance of {@link ToInt}.
|
||||
*/
|
||||
public static ToInt toInt(Object value) {
|
||||
return new ToInt(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getMongoMethod() {
|
||||
return "$toInt";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link AggregationExpression} for {@code $toLong} that converts a value to {@literal long}. Shorthand for
|
||||
* {@link Convert#to(String) Convert#to("long")}. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @see <a href=
|
||||
* "https://docs.mongodb.com/manual/reference/operator/aggregation/toLong/">https://docs.mongodb.com/manual/reference/operator/aggregation/toLong/</a>
|
||||
* @since 2.1
|
||||
*/
|
||||
public static class ToLong extends AbstractAggregationExpression {
|
||||
|
||||
private ToLong(Object value) {
|
||||
super(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link ToLong} using the given value as input.
|
||||
*
|
||||
* @param value must not be {@literal null}.
|
||||
* @return new instance of {@link ToLong}.
|
||||
*/
|
||||
public static ToLong toLong(Object value) {
|
||||
return new ToLong(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getMongoMethod() {
|
||||
return "$toLong";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link AggregationExpression} for {@code $toObjectId} that converts a value to {@literal objectId}. Shorthand for
|
||||
* {@link Convert#to(String) Convert#to("objectId")}. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @see <a href=
|
||||
* "https://docs.mongodb.com/manual/reference/operator/aggregation/toObjectId/">https://docs.mongodb.com/manual/reference/operator/aggregation/toObjectId/</a>
|
||||
* @since 2.1
|
||||
*/
|
||||
public static class ToObjectId extends AbstractAggregationExpression {
|
||||
|
||||
private ToObjectId(Object value) {
|
||||
super(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link ToObjectId} using the given value as input.
|
||||
*
|
||||
* @param value must not be {@literal null}.
|
||||
* @return new instance of {@link ToObjectId}.
|
||||
*/
|
||||
public static ToObjectId toObjectId(Object value) {
|
||||
return new ToObjectId(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getMongoMethod() {
|
||||
return "$toObjectId";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link AggregationExpression} for {@code $toString} that converts a value to {@literal string}. Shorthand for
|
||||
* {@link Convert#to(String) Convert#to("string")}. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @see <a href=
|
||||
* "https://docs.mongodb.com/manual/reference/operator/aggregation/toString/">https://docs.mongodb.com/manual/reference/operator/aggregation/toString/</a>
|
||||
* @since 2.1
|
||||
*/
|
||||
public static class ToString extends AbstractAggregationExpression {
|
||||
|
||||
private ToString(Object value) {
|
||||
super(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link ToString} using the given value as input.
|
||||
*
|
||||
* @param value must not be {@literal null}.
|
||||
* @return new instance of {@link ToString}.
|
||||
*/
|
||||
public static ToString toString(Object value) {
|
||||
return new ToString(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getMongoMethod() {
|
||||
return "$toString";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,7 @@ import java.util.Map;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Gateway to {@literal Date} aggregation operations.
|
||||
@@ -178,7 +179,7 @@ public class DateOperators {
|
||||
* Create a {@link Timezone} for the {@link AggregationExpression} resulting in the Olson Timezone Identifier or UTC
|
||||
* Offset.
|
||||
*
|
||||
* @param value the {@link AggregationExpression} resulting in the timezone.
|
||||
* @param expression the {@link AggregationExpression} resulting in the timezone.
|
||||
* @return new instance of {@link Timezone}.
|
||||
*/
|
||||
public static Timezone ofExpression(AggregationExpression expression) {
|
||||
@@ -380,6 +381,17 @@ public class DateOperators {
|
||||
return applyTimezone(DateToString.dateToString(dateReference()).toString(format), timezone);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link AggregationExpression} that converts a date object to a string according to the server default
|
||||
* format.
|
||||
*
|
||||
* @return new instance of {@link DateToString}.
|
||||
* @since 2.1
|
||||
*/
|
||||
public DateToString toStringWithDefaultFormat() {
|
||||
return applyTimezone(DateToString.dateToString(dateReference()).defaultFormat(), timezone);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link AggregationExpression} that returns the weekday number in ISO 8601-2018 format, ranging from 1
|
||||
* (for Monday) to 7 (for Sunday).
|
||||
@@ -1352,6 +1364,11 @@ public class DateOperators {
|
||||
Assert.notNull(format, "Format must not be null!");
|
||||
return new DateToString(argumentMap(value, format, Timezone.none()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public DateToString defaultFormat() {
|
||||
return new DateToString(argumentMap(value, null, Timezone.none()));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1392,7 +1409,43 @@ public class DateOperators {
|
||||
public DateToString withTimezone(Timezone timezone) {
|
||||
|
||||
Assert.notNull(timezone, "Timezone must not be null.");
|
||||
return new DateToString(argumentMap(get("date"), get("format"), timezone));
|
||||
return new DateToString(append("timezone", timezone));
|
||||
}
|
||||
|
||||
/**
|
||||
* Optionally specify the value to return when the date is {@literal null} or missing. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @param value must not be {@literal null}.
|
||||
* @return new instance of {@link DateToString}.
|
||||
* @since 2.1
|
||||
*/
|
||||
public DateToString onNullReturn(Object value) {
|
||||
return new DateToString(append("onNull", value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Optionally specify the field holding the value to return when the date is {@literal null} or missing. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @param fieldReference must not be {@literal null}.
|
||||
* @return new instance of {@link DateToString}.
|
||||
* @since 2.1
|
||||
*/
|
||||
public DateToString onNullReturnValueOf(String fieldReference) {
|
||||
return onNullReturn(Fields.field(fieldReference));
|
||||
}
|
||||
|
||||
/**
|
||||
* Optionally specify the expression to evaluate and return when the date is {@literal null} or missing. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return new instance of {@link DateToString}.
|
||||
* @since 2.1
|
||||
*/
|
||||
public DateToString onNullReturnValueOf(AggregationExpression expression) {
|
||||
return onNullReturn(expression);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1400,10 +1453,14 @@ public class DateOperators {
|
||||
return "$dateToString";
|
||||
}
|
||||
|
||||
private static java.util.Map<String, Object> argumentMap(Object date, String format, Timezone timezone) {
|
||||
private static java.util.Map<String, Object> argumentMap(Object date, @Nullable String format, Timezone timezone) {
|
||||
|
||||
java.util.Map<String, Object> args = new LinkedHashMap<>(2);
|
||||
|
||||
if (StringUtils.hasText(format)) {
|
||||
args.put("format", format);
|
||||
}
|
||||
|
||||
java.util.Map<String, Object> args = new LinkedHashMap<String, Object>(2);
|
||||
args.put("format", format);
|
||||
args.put("date", date);
|
||||
|
||||
if (!ObjectUtils.nullSafeEquals(timezone, Timezone.none())) {
|
||||
@@ -1412,6 +1469,25 @@ public class DateOperators {
|
||||
return args;
|
||||
}
|
||||
|
||||
protected java.util.Map<String, Object> append(String key, Object value) {
|
||||
|
||||
java.util.Map<String, Object> clone = new LinkedHashMap<>(argumentMap());
|
||||
|
||||
if (value instanceof Timezone) {
|
||||
|
||||
if (ObjectUtils.nullSafeEquals(value, Timezone.none())) {
|
||||
clone.remove("timezone");
|
||||
} else {
|
||||
clone.put("timezone", ((Timezone) value).value);
|
||||
}
|
||||
|
||||
} else {
|
||||
clone.put(key, value);
|
||||
}
|
||||
|
||||
return clone;
|
||||
}
|
||||
|
||||
public interface FormatBuilder {
|
||||
|
||||
/**
|
||||
@@ -1421,6 +1497,16 @@ public class DateOperators {
|
||||
* @return
|
||||
*/
|
||||
DateToString toString(String format);
|
||||
|
||||
/**
|
||||
* Creates new {@link DateToString} using the server default string format ({@code %Y-%m-%dT%H:%M:%S.%LZ}) for
|
||||
* dates. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @return new instance of {@link DateToString}.
|
||||
* @since 2.1
|
||||
*/
|
||||
DateToString defaultFormat();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2269,6 +2355,20 @@ public class DateOperators {
|
||||
return new DateFromString(appendTimezone(argumentMap(), timezone));
|
||||
}
|
||||
|
||||
/**
|
||||
* Optionally set the date format to use. If not specified {@code %Y-%m-%dT%H:%M:%S.%LZ} is used.<br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @param format must not be {@literal null}.
|
||||
* @return new instance of {@link DateFromString}.
|
||||
* @throws IllegalArgumentException if given {@literal format} is {@literal null}.
|
||||
*/
|
||||
public DateFromString withFormat(String format) {
|
||||
|
||||
Assert.notNull(format, "Format must not be null!");
|
||||
return new DateFromString(append("format", format));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getMongoMethod() {
|
||||
return "$dateFromString";
|
||||
|
||||
@@ -17,7 +17,9 @@ package org.springframework.data.mongodb.core.aggregation;
|
||||
|
||||
import org.bson.Document;
|
||||
import org.springframework.data.mongodb.core.query.NearQuery;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Represents a {@code geoNear} aggregation operation.
|
||||
@@ -28,26 +30,55 @@ import org.springframework.util.Assert;
|
||||
* @author Thomas Darimont
|
||||
* @author Christoph Strobl
|
||||
* @since 1.3
|
||||
* @see <a href="https://docs.mongodb.com/manual/reference/operator/aggregation/geoNear/">MongoDB Aggregation Framework:
|
||||
* $geoNear</a>
|
||||
*/
|
||||
public class GeoNearOperation implements AggregationOperation {
|
||||
|
||||
private final NearQuery nearQuery;
|
||||
private final String distanceField;
|
||||
private final @Nullable String indexKey;
|
||||
|
||||
/**
|
||||
* Creates a new {@link GeoNearOperation} from the given {@link NearQuery} and the given distance field. The
|
||||
* {@code distanceField} defines output field that contains the calculated distance.
|
||||
*
|
||||
* @param query must not be {@literal null}.
|
||||
* @param nearQuery must not be {@literal null}.
|
||||
* @param distanceField must not be {@literal null}.
|
||||
*/
|
||||
public GeoNearOperation(NearQuery nearQuery, String distanceField) {
|
||||
this(nearQuery, distanceField, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link GeoNearOperation} from the given {@link NearQuery} and the given distance field. The
|
||||
* {@code distanceField} defines output field that contains the calculated distance.
|
||||
*
|
||||
* @param nearQuery must not be {@literal null}.
|
||||
* @param distanceField must not be {@literal null}.
|
||||
* @param indexKey can be {@literal null};
|
||||
* @since 2.1
|
||||
*/
|
||||
private GeoNearOperation(NearQuery nearQuery, String distanceField, @Nullable String indexKey) {
|
||||
|
||||
Assert.notNull(nearQuery, "NearQuery must not be null.");
|
||||
Assert.hasLength(distanceField, "Distance field must not be null or empty.");
|
||||
|
||||
this.nearQuery = nearQuery;
|
||||
this.distanceField = distanceField;
|
||||
this.indexKey = indexKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Optionally specify the geospatial index to use via the field to use in the calculation. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @param key the geospatial index field to use when calculating the distance.
|
||||
* @return new instance of {@link GeoNearOperation}.
|
||||
* @since 2.1
|
||||
*/
|
||||
public GeoNearOperation useIndex(String key) {
|
||||
return new GeoNearOperation(nearQuery, distanceField, key);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -60,6 +91,10 @@ public class GeoNearOperation implements AggregationOperation {
|
||||
Document command = context.getMappedObject(nearQuery.toDocument());
|
||||
command.put("distanceField", distanceField);
|
||||
|
||||
if (StringUtils.hasText(indexKey)) {
|
||||
command.put("key", indexKey);
|
||||
}
|
||||
|
||||
return new Document("$geoNear", command);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,8 +103,8 @@ public class GraphLookupOperation implements InheritsFieldsAggregationOperation
|
||||
|
||||
graphLookup.put("startWith", mappedStartWith.size() == 1 ? mappedStartWith.iterator().next() : mappedStartWith);
|
||||
|
||||
graphLookup.put("connectFromField", connectFrom.getName());
|
||||
graphLookup.put("connectToField", connectTo.getName());
|
||||
graphLookup.put("connectFromField", connectFrom.getTarget());
|
||||
graphLookup.put("connectToField", connectTo.getTarget());
|
||||
graphLookup.put("as", as.getName());
|
||||
|
||||
if (maxDepth != null) {
|
||||
@@ -112,7 +112,7 @@ public class GraphLookupOperation implements InheritsFieldsAggregationOperation
|
||||
}
|
||||
|
||||
if (depthField != null) {
|
||||
graphLookup.put("depthField", depthField.getName());
|
||||
graphLookup.put("depthField", depthField.getTarget());
|
||||
}
|
||||
|
||||
if (restrictSearchWithMatch != null) {
|
||||
|
||||
@@ -0,0 +1,298 @@
|
||||
/*
|
||||
* Copyright 2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.mongodb.core.aggregation;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
|
||||
import org.bson.Document;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Gateway for
|
||||
* <a href="https://docs.mongodb.com/manual/meta/aggregation-quick-reference/#object-expression-operators">object
|
||||
* expression operators</a>.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.1
|
||||
*/
|
||||
public class ObjectOperators {
|
||||
|
||||
/**
|
||||
* Take the value referenced by given {@literal fieldReference}.
|
||||
*
|
||||
* @param fieldReference must not be {@literal null}.
|
||||
* @return new instance of {@link ObjectOperatorFactory}.
|
||||
*/
|
||||
public static ObjectOperatorFactory valueOf(String fieldReference) {
|
||||
return new ObjectOperatorFactory(Fields.field(fieldReference));
|
||||
}
|
||||
|
||||
/**
|
||||
* Take the value provided by the given {@link AggregationExpression}.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return new instance of {@link ObjectOperatorFactory}.
|
||||
*/
|
||||
public static ObjectOperatorFactory valueOf(AggregationExpression expression) {
|
||||
return new ObjectOperatorFactory(expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public static class ObjectOperatorFactory {
|
||||
|
||||
private final Object value;
|
||||
|
||||
/**
|
||||
* Creates new {@link ObjectOperatorFactory} for given {@literal value}.
|
||||
*
|
||||
* @param value must not be {@literal null}.
|
||||
*/
|
||||
public ObjectOperatorFactory(Object value) {
|
||||
|
||||
Assert.notNull(value, "Value must not be null!");
|
||||
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link MergeObjects aggregation expression} that takes the associated value and uses
|
||||
* {@literal $mergeObjects} as an accumulator within the {@literal $group} stage. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @return new instance of {@link MergeObjects}.
|
||||
*/
|
||||
public MergeObjects merge() {
|
||||
return MergeObjects.merge(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link MergeObjects aggregation expression} that takes the associated value and combines it with the
|
||||
* given values (documents or mapped objects) into a single document. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @return new instance of {@link MergeObjects}.
|
||||
*/
|
||||
public MergeObjects mergeWith(Object... values) {
|
||||
return merge().mergeWith(values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link MergeObjects aggregation expression} that takes the associated value and combines it with the
|
||||
* values of the given {@link Field field references} into a single document. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @return new instance of {@link MergeObjects}.
|
||||
*/
|
||||
public MergeObjects mergeWithValuesOf(String... fieldReferences) {
|
||||
return merge().mergeWithValuesOf(fieldReferences);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link MergeObjects aggregation expression} that takes the associated value and combines it with the
|
||||
* result values of the given {@link Aggregation expressions} into a single document. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @return new instance of {@link MergeObjects}.
|
||||
*/
|
||||
public MergeObjects mergeWithValuesOf(AggregationExpression... expression) {
|
||||
return merge().mergeWithValuesOf(expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link ObjectToArray aggregation expression} that takes the associated value and converts it to an
|
||||
* array of {@link Document documents} that contain two fields {@literal k} and {@literal v} each. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 3.6 or later.
|
||||
*
|
||||
* @since 2.1
|
||||
*/
|
||||
public ObjectToArray toArray() {
|
||||
return ObjectToArray.toArray(value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link AggregationExpression} for {@code $mergeObjects} that combines multiple documents into a single document.
|
||||
* <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @see <a href=
|
||||
* "https://docs.mongodb.com/manual/reference/operator/aggregation/mergeObjects/">https://docs.mongodb.com/manual/reference/operator/aggregation/mergeObjects/</a>
|
||||
* @since 2.1
|
||||
*/
|
||||
public static class MergeObjects extends AbstractAggregationExpression {
|
||||
|
||||
private MergeObjects(Object value) {
|
||||
super(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link MergeObjects aggregation expression} that takes given values and combines them into a single
|
||||
* document. <br />
|
||||
*
|
||||
* @param values must not be {@literal null}.
|
||||
* @return new instance of {@link MergeObjects}.
|
||||
*/
|
||||
public static MergeObjects merge(Object... values) {
|
||||
return new MergeObjects(Arrays.asList(values));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link MergeObjects aggregation expression} that takes the given {@link Field field references} and
|
||||
* combines them into a single document.
|
||||
*
|
||||
* @param fieldReferences must not be {@literal null}.
|
||||
* @return new instance of {@link MergeObjects}.
|
||||
*/
|
||||
public static MergeObjects mergeValuesOf(String... fieldReferences) {
|
||||
return merge(Arrays.stream(fieldReferences).map(Fields::field).toArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link MergeObjects aggregation expression} that takes the result of the given {@link Aggregation
|
||||
* expressions} and combines them into a single document.
|
||||
*
|
||||
* @param expressions must not be {@literal null}.
|
||||
* @return new instance of {@link MergeObjects}.
|
||||
*/
|
||||
public static MergeObjects mergeValuesOf(AggregationExpression... expressions) {
|
||||
return merge(expressions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link MergeObjects aggregation expression} by adding the given {@link Field field references}.
|
||||
*
|
||||
* @param fieldReferences must not be {@literal null}.
|
||||
* @return new instance of {@link MergeObjects}.
|
||||
*/
|
||||
public MergeObjects mergeWithValuesOf(String... fieldReferences) {
|
||||
return mergeWith(Arrays.stream(fieldReferences).map(Fields::field).toArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link MergeObjects aggregation expression} by adding the given {@link AggregationExpression
|
||||
* expressions}.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return new instance of {@link MergeObjects}.
|
||||
*/
|
||||
public MergeObjects mergeWithValuesOf(AggregationExpression... expression) {
|
||||
return mergeWith(expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link MergeObjects aggregation expression} by adding the given values (documents or mapped objects).
|
||||
*
|
||||
* @param values must not be {@literal null}.
|
||||
* @return new instance of {@link MergeObjects}.
|
||||
*/
|
||||
public MergeObjects mergeWith(Object... values) {
|
||||
return new MergeObjects(append(Arrays.asList(values)));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.aggregation.AbstractAggregationExpression#toDocument(java.lang.Object, org.springframework.data.mongodb.core.aggregation.AggregationOperationContext)
|
||||
*/
|
||||
@Override
|
||||
public Document toDocument(Object value, AggregationOperationContext context) {
|
||||
return super.toDocument(potentiallyExtractSingleValue(value), context);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Object potentiallyExtractSingleValue(Object value) {
|
||||
|
||||
if (value instanceof Collection) {
|
||||
|
||||
Collection<Object> collection = ((Collection<Object>) value);
|
||||
if (collection.size() == 1) {
|
||||
return collection.iterator().next();
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.aggregation.AbstractAggregationExpression#getMongoMethod()
|
||||
*/
|
||||
@Override
|
||||
protected String getMongoMethod() {
|
||||
return "$mergeObjects";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link AggregationExpression} for {@code $objectToArray} that converts a document to an array of {@link Document
|
||||
* documents} that each contains two fields {@literal k} and {@literal v}. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 3.6 or later.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @see <a href=
|
||||
* "https://docs.mongodb.com/manual/reference/operator/aggregation/objectToArray/">https://docs.mongodb.com/manual/reference/operator/aggregation/objectToArray/</a>
|
||||
* @since 2.1
|
||||
*/
|
||||
public static class ObjectToArray extends AbstractAggregationExpression {
|
||||
|
||||
private ObjectToArray(Object value) {
|
||||
super(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link ObjectToArray aggregation expression} that takes the value pointed to by given {@link Field
|
||||
* fieldReference} and converts it to an array.
|
||||
*
|
||||
* @param fieldReference must not be {@literal null}.
|
||||
* @return new instance of {@link ObjectToArray}.
|
||||
*/
|
||||
public static ObjectToArray valueOfToArray(String fieldReference) {
|
||||
return toArray(Fields.field(fieldReference));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link ObjectToArray aggregation expression} that takes the result value of the given
|
||||
* {@link AggregationExpression expression} and converts it to an array.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return new instance of {@link ObjectToArray}.
|
||||
*/
|
||||
public static ObjectToArray valueOfToArray(AggregationExpression expression) {
|
||||
return toArray(expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link ObjectToArray aggregation expression} that takes the given value and converts it to an array.
|
||||
*
|
||||
* @param value must not be {@literal null}.
|
||||
* @return new instance of {@link ObjectToArray}.
|
||||
*/
|
||||
public static ObjectToArray toArray(Object value) {
|
||||
return new ObjectToArray(value);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.aggregation.AbstractAggregationExpression#getMongoMethod()
|
||||
*/
|
||||
@Override
|
||||
protected String getMongoMethod() {
|
||||
return "$objectToArray";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -102,7 +102,7 @@ public class PrefixingDelegatingAggregationOperationContext implements Aggregati
|
||||
}
|
||||
|
||||
private String prefixKey(String key) {
|
||||
return (key.startsWith("$") || blacklist.contains(key)) ? key : (prefix + "." + key);
|
||||
return (key.startsWith("$") || isBlacklisted(key)) ? key : (prefix + "." + key);
|
||||
}
|
||||
|
||||
private Object prefixCollection(Collection<Object> sourceCollection) {
|
||||
@@ -119,4 +119,23 @@ public class PrefixingDelegatingAggregationOperationContext implements Aggregati
|
||||
|
||||
return prefixed;
|
||||
}
|
||||
|
||||
private boolean isBlacklisted(String key) {
|
||||
|
||||
if (blacklist.contains(key)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!key.contains(".")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (String blacklisted : blacklist) {
|
||||
if (key.startsWith(blacklisted + ".")) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1204,6 +1204,18 @@ public class ProjectionOperation implements FieldsExposingAggregationOperation {
|
||||
return this.operation.and(DateOperators.DateToString.dateOf(getRequiredName()).toString(format));
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a {@code $dateToString} expression that takes the date representation of the previously mentioned field
|
||||
* using the server default format. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @return
|
||||
* @since 2.1
|
||||
*/
|
||||
public ProjectionOperationBuilder dateAsFormattedString() {
|
||||
return this.operation.and(DateOperators.DateToString.dateOf(getRequiredName()).defaultFormat());
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a {@code $let} expression that binds variables for use in the specified expression, and returns the
|
||||
* result of the expression.
|
||||
|
||||
@@ -350,8 +350,7 @@ public class StringOperators {
|
||||
* @return
|
||||
*/
|
||||
public StrLenBytes length() {
|
||||
return usesFieldRef() ? StrLenBytes.stringLengthOf(fieldReference)
|
||||
: StrLenBytes.stringLengthOf(expression);
|
||||
return usesFieldRef() ? StrLenBytes.stringLengthOf(fieldReference) : StrLenBytes.stringLengthOf(expression);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -391,6 +390,132 @@ public class StringOperators {
|
||||
return usesFieldRef() ? SubstrCP.valueOf(fieldReference) : SubstrCP.valueOf(expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link AggregationExpression} that takes the associated string representation and trims whitespaces
|
||||
* from the beginning and end. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @return new instance of {@link Trim}.
|
||||
* @since 2.1
|
||||
*/
|
||||
public Trim trim() {
|
||||
return createTrim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link AggregationExpression} that takes the associated string representation and trims the given
|
||||
* character sequence from the beginning and end. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @param chars must not be {@literal null}.
|
||||
* @return new instance of {@link Trim}.
|
||||
* @since 2.1
|
||||
*/
|
||||
public Trim trim(String chars) {
|
||||
return trim().chars(chars);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link AggregationExpression} that takes the associated string representation and trims the character
|
||||
* sequence resulting from the given {@link AggregationExpression} from the beginning and end. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return new instance of {@link Trim}.
|
||||
* @since 2.1
|
||||
*/
|
||||
public Trim trim(AggregationExpression expression) {
|
||||
return trim().charsOf(expression);
|
||||
}
|
||||
|
||||
private Trim createTrim() {
|
||||
return usesFieldRef() ? Trim.valueOf(fieldReference) : Trim.valueOf(expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link AggregationExpression} that takes the associated string representation and trims whitespaces
|
||||
* from the beginning. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @return new instance of {@link LTrim}.
|
||||
* @since 2.1
|
||||
*/
|
||||
public LTrim ltrim() {
|
||||
return createLTrim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link AggregationExpression} that takes the associated string representation and trims the given
|
||||
* character sequence from the beginning. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @param chars must not be {@literal null}.
|
||||
* @return new instance of {@link LTrim}.
|
||||
* @since 2.1
|
||||
*/
|
||||
public LTrim ltrim(String chars) {
|
||||
return ltrim().chars(chars);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link AggregationExpression} that takes the associated string representation and trims the character
|
||||
* sequence resulting from the given {@link AggregationExpression} from the beginning. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return new instance of {@link LTrim}.
|
||||
* @since 2.1
|
||||
*/
|
||||
public LTrim ltrim(AggregationExpression expression) {
|
||||
return ltrim().charsOf(expression);
|
||||
}
|
||||
|
||||
private LTrim createLTrim() {
|
||||
return usesFieldRef() ? LTrim.valueOf(fieldReference) : LTrim.valueOf(expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link AggregationExpression} that takes the associated string representation and trims whitespaces
|
||||
* from the end. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @return new instance of {@link RTrim}.
|
||||
* @since 2.1
|
||||
*/
|
||||
public RTrim rtrim() {
|
||||
return createRTrim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link AggregationExpression} that takes the associated string representation and trims the given
|
||||
* character sequence from the end. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @param chars must not be {@literal null}.
|
||||
* @return new instance of {@link RTrim}.
|
||||
* @since 2.1
|
||||
*/
|
||||
public RTrim rtrim(String chars) {
|
||||
return rtrim().chars(chars);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link AggregationExpression} that takes the associated string representation and trims the character
|
||||
* sequence resulting from the given {@link AggregationExpression} from the end. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return new instance of {@link RTrim}.
|
||||
* @since 2.1
|
||||
*/
|
||||
public RTrim rtrim(AggregationExpression expression) {
|
||||
return rtrim().charsOf(expression);
|
||||
}
|
||||
|
||||
private RTrim createRTrim() {
|
||||
return usesFieldRef() ? RTrim.valueOf(fieldReference) : RTrim.valueOf(expression);
|
||||
}
|
||||
|
||||
private boolean usesFieldRef() {
|
||||
return fieldReference != null;
|
||||
}
|
||||
@@ -1072,4 +1197,257 @@ public class StringOperators {
|
||||
return new SubstrCP(append(Arrays.asList(start, nrOfChars)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link AggregationExpression} for {@code $trim} which removes whitespace or the specified characters from the
|
||||
* beginning and end of a string. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.1
|
||||
*/
|
||||
public static class Trim extends AbstractAggregationExpression {
|
||||
|
||||
private Trim(Object value) {
|
||||
super(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link Trim} using the value of the provided {@link Field fieldReference} as {@literal input} value.
|
||||
*
|
||||
* @param fieldReference must not be {@literal null}.
|
||||
* @return new instance of {@link LTrim}.
|
||||
*/
|
||||
public static Trim valueOf(String fieldReference) {
|
||||
|
||||
Assert.notNull(fieldReference, "FieldReference must not be null!");
|
||||
return new Trim(Collections.singletonMap("input", Fields.field(fieldReference)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link Trim} using the result of the provided {@link AggregationExpression} as {@literal input}
|
||||
* value.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return new instance of {@link Trim}.
|
||||
*/
|
||||
public static Trim valueOf(AggregationExpression expression) {
|
||||
|
||||
Assert.notNull(expression, "Expression must not be null!");
|
||||
return new Trim(Collections.singletonMap("input", expression));
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional specify the character(s) to trim from the beginning.
|
||||
*
|
||||
* @param chars must not be {@literal null}.
|
||||
* @return new instance of {@link Trim}.
|
||||
*/
|
||||
public Trim chars(String chars) {
|
||||
|
||||
Assert.notNull(chars, "Chars must not be null!");
|
||||
return new Trim(append("chars", chars));
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional specify the reference to the {@link Field field} holding the character values to trim from the
|
||||
* beginning.
|
||||
*
|
||||
* @param fieldReference must not be {@literal null}.
|
||||
* @return new instance of {@link Trim}.
|
||||
*/
|
||||
public Trim charsOf(String fieldReference) {
|
||||
return new Trim(append("chars", Fields.field(fieldReference)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional specify the {@link AggregationExpression} evaluating to the character sequence to trim from the
|
||||
* beginning.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return new instance of {@link Trim}.
|
||||
*/
|
||||
public Trim charsOf(AggregationExpression expression) {
|
||||
return new Trim(append("chars", expression));
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove whitespace or the specified characters from the beginning of a string.<br />
|
||||
*
|
||||
* @return new instance of {@link LTrim}.
|
||||
*/
|
||||
public LTrim left() {
|
||||
return new LTrim(argumentMap());
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove whitespace or the specified characters from the end of a string.<br />
|
||||
*
|
||||
* @return new instance of {@link RTrim}.
|
||||
*/
|
||||
public RTrim right() {
|
||||
return new RTrim(argumentMap());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getMongoMethod() {
|
||||
return "$trim";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link AggregationExpression} for {@code $ltrim} which removes whitespace or the specified characters from the
|
||||
* beginning of a string. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.1
|
||||
*/
|
||||
public static class LTrim extends AbstractAggregationExpression {
|
||||
|
||||
private LTrim(Object value) {
|
||||
super(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link LTrim} using the value of the provided {@link Field fieldReference} as {@literal input} value.
|
||||
*
|
||||
* @param fieldReference must not be {@literal null}.
|
||||
* @return new instance of {@link LTrim}.
|
||||
*/
|
||||
public static LTrim valueOf(String fieldReference) {
|
||||
|
||||
Assert.notNull(fieldReference, "FieldReference must not be null!");
|
||||
return new LTrim(Collections.singletonMap("input", Fields.field(fieldReference)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link LTrim} using the result of the provided {@link AggregationExpression} as {@literal input}
|
||||
* value.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return new instance of {@link LTrim}.
|
||||
*/
|
||||
public static LTrim valueOf(AggregationExpression expression) {
|
||||
|
||||
Assert.notNull(expression, "Expression must not be null!");
|
||||
return new LTrim(Collections.singletonMap("input", expression));
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional specify the character(s) to trim from the beginning.
|
||||
*
|
||||
* @param chars must not be {@literal null}.
|
||||
* @return new instance of {@link LTrim}.
|
||||
*/
|
||||
public LTrim chars(String chars) {
|
||||
|
||||
Assert.notNull(chars, "Chars must not be null!");
|
||||
return new LTrim(append("chars", chars));
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional specify the reference to the {@link Field field} holding the character values to trim from the
|
||||
* beginning.
|
||||
*
|
||||
* @param fieldReference must not be {@literal null}.
|
||||
* @return new instance of {@link LTrim}.
|
||||
*/
|
||||
public LTrim charsOf(String fieldReference) {
|
||||
return new LTrim(append("chars", Fields.field(fieldReference)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional specify the {@link AggregationExpression} evaluating to the character sequence to trim from the
|
||||
* beginning.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return new instance of {@link LTrim}.
|
||||
*/
|
||||
public LTrim charsOf(AggregationExpression expression) {
|
||||
return new LTrim(append("chars", expression));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getMongoMethod() {
|
||||
return "$ltrim";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link AggregationExpression} for {@code $rtrim} which removes whitespace or the specified characters from the end
|
||||
* of a string. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.1
|
||||
*/
|
||||
public static class RTrim extends AbstractAggregationExpression {
|
||||
|
||||
private RTrim(Object value) {
|
||||
super(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link RTrim} using the value of the provided {@link Field fieldReference} as {@literal input} value.
|
||||
*
|
||||
* @param fieldReference must not be {@literal null}.
|
||||
* @return new instance of {@link RTrim}.
|
||||
*/
|
||||
public static RTrim valueOf(String fieldReference) {
|
||||
|
||||
Assert.notNull(fieldReference, "FieldReference must not be null!");
|
||||
return new RTrim(Collections.singletonMap("input", Fields.field(fieldReference)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link RTrim} using the result of the provided {@link AggregationExpression} as {@literal input}
|
||||
* value.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return new instance of {@link RTrim}.
|
||||
*/
|
||||
public static RTrim valueOf(AggregationExpression expression) {
|
||||
|
||||
Assert.notNull(expression, "Expression must not be null!");
|
||||
return new RTrim(Collections.singletonMap("input", expression));
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional specify the character(s) to trim from the end.
|
||||
*
|
||||
* @param chars must not be {@literal null}.
|
||||
* @return new instance of {@link RTrim}.
|
||||
*/
|
||||
public RTrim chars(String chars) {
|
||||
|
||||
Assert.notNull(chars, "Chars must not be null!");
|
||||
return new RTrim(append("chars", chars));
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional specify the reference to the {@link Field field} holding the character values to trim from the end.
|
||||
*
|
||||
* @param fieldReference must not be {@literal null}.
|
||||
* @return new instance of {@link RTrim}.
|
||||
*/
|
||||
public RTrim charsOf(String fieldReference) {
|
||||
return new RTrim(append("chars", Fields.field(fieldReference)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional specify the {@link AggregationExpression} evaluating to the character sequence to trim from the end.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return new instance of {@link RTrim}.
|
||||
*/
|
||||
public RTrim charsOf(AggregationExpression expression) {
|
||||
return new RTrim(append("chars", expression));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getMongoMethod() {
|
||||
return "$rtrim";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.mongodb.DBRef;
|
||||
|
||||
@@ -59,9 +60,15 @@ public interface DbRefResolver {
|
||||
* @param id will never be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
DBRef createDbRef(@Nullable org.springframework.data.mongodb.core.mapping.DBRef annotation,
|
||||
MongoPersistentEntity<?> entity,
|
||||
Object id);
|
||||
default DBRef createDbRef(@Nullable org.springframework.data.mongodb.core.mapping.DBRef annotation,
|
||||
MongoPersistentEntity<?> entity, Object id) {
|
||||
|
||||
if (annotation != null && StringUtils.hasText(annotation.db())) {
|
||||
return new DBRef(annotation.db(), entity.getCollection(), id);
|
||||
}
|
||||
|
||||
return new DBRef(entity.getCollection(), id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Actually loads the {@link DBRef} from the datasource.
|
||||
|
||||
@@ -43,7 +43,6 @@ import org.springframework.dao.support.PersistenceExceptionTranslator;
|
||||
import org.springframework.data.mongodb.ClientSessionException;
|
||||
import org.springframework.data.mongodb.LazyLoadingException;
|
||||
import org.springframework.data.mongodb.MongoDbFactory;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.objenesis.ObjenesisStd;
|
||||
@@ -104,21 +103,6 @@ public class DefaultDbRefResolver implements DbRefResolver {
|
||||
return callback.resolve(property);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.convert.DbRefResolver#created(org.springframework.data.mongodb.core.mapping.MongoPersistentProperty, org.springframework.data.mongodb.core.mapping.MongoPersistentEntity, java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public DBRef createDbRef(@Nullable org.springframework.data.mongodb.core.mapping.DBRef annotation,
|
||||
MongoPersistentEntity<?> entity, Object id) {
|
||||
|
||||
if (annotation != null && StringUtils.hasText(annotation.db())) {
|
||||
return new DBRef(annotation.db(), entity.getCollection(), id);
|
||||
}
|
||||
|
||||
return new DBRef(entity.getCollection(), id);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.convert.DbRefResolver#fetch(com.mongodb.DBRef)
|
||||
|
||||
@@ -21,6 +21,7 @@ import java.util.Map;
|
||||
|
||||
import org.bson.Document;
|
||||
import org.bson.conversions.Bson;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
|
||||
import org.springframework.data.mongodb.util.BsonUtils;
|
||||
import org.springframework.lang.Nullable;
|
||||
@@ -58,6 +59,14 @@ class DocumentAccessor {
|
||||
this.document = document;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the underlying {@link Bson document}.
|
||||
* @since 2.1
|
||||
*/
|
||||
Bson getDocument() {
|
||||
return this.document;
|
||||
}
|
||||
|
||||
/**
|
||||
* Puts the given value into the backing {@link Document} based on the coordinates defined through the given
|
||||
* {@link MongoPersistentProperty}. By default this will be the plain field name. But field names might also consist
|
||||
@@ -103,13 +112,14 @@ class DocumentAccessor {
|
||||
public Object get(MongoPersistentProperty property) {
|
||||
|
||||
String fieldName = property.getFieldName();
|
||||
Map<String, Object> map = BsonUtils.asMap(document);
|
||||
|
||||
if (!fieldName.contains(".")) {
|
||||
return BsonUtils.asMap(this.document).get(fieldName);
|
||||
return map.get(fieldName);
|
||||
}
|
||||
|
||||
Iterator<String> parts = Arrays.asList(fieldName.split("\\.")).iterator();
|
||||
Map<String, Object> source = BsonUtils.asMap(this.document);
|
||||
Map<String, Object> source = map;
|
||||
Object result = null;
|
||||
|
||||
while (source != null && parts.hasNext()) {
|
||||
@@ -124,6 +134,17 @@ class DocumentAccessor {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the raw identifier for the given {@link MongoPersistentEntity} or the value of the default identifier
|
||||
* field.
|
||||
*
|
||||
* @param entity must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public Object getRawId(MongoPersistentEntity<?> entity) {
|
||||
return entity.hasIdProperty() ? get(entity.getRequiredIdProperty()) : BsonUtils.asMap(document).get("_id");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the underlying {@link Document} has a value ({@literal null} or non-{@literal null}) for the given
|
||||
* {@link MongoPersistentProperty}.
|
||||
@@ -131,21 +152,27 @@ class DocumentAccessor {
|
||||
* @param property must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public boolean hasValue(MongoPersistentProperty property) {
|
||||
|
||||
Assert.notNull(property, "Property must not be null!");
|
||||
|
||||
String fieldName = property.getFieldName();
|
||||
|
||||
|
||||
if (this.document instanceof Document) {
|
||||
|
||||
if (((Document) this.document).containsKey(fieldName)) {
|
||||
return true;
|
||||
}
|
||||
} else if (this.document instanceof DBObject) {
|
||||
if (((DBObject) this.document).containsField(fieldName)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!fieldName.contains(".")) {
|
||||
|
||||
if (this.document instanceof Document) {
|
||||
return ((Document) this.document).containsKey(fieldName);
|
||||
}
|
||||
|
||||
if (this.document instanceof DBObject) {
|
||||
return ((DBObject) this.document).containsField(fieldName);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
String[] parts = fieldName.split("\\.");
|
||||
|
||||
@@ -15,17 +15,8 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core.convert;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import org.bson.Document;
|
||||
import org.bson.conversions.Bson;
|
||||
@@ -42,6 +33,7 @@ import org.springframework.data.convert.TypeMapper;
|
||||
import org.springframework.data.mapping.Association;
|
||||
import org.springframework.data.mapping.MappingException;
|
||||
import org.springframework.data.mapping.PersistentPropertyAccessor;
|
||||
import org.springframework.data.mapping.PreferredConstructor;
|
||||
import org.springframework.data.mapping.PreferredConstructor.Parameter;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
|
||||
@@ -147,7 +139,8 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
*/
|
||||
public void setTypeMapper(@Nullable MongoTypeMapper typeMapper) {
|
||||
this.typeMapper = typeMapper == null
|
||||
? new DefaultMongoTypeMapper(DefaultMongoTypeMapper.DEFAULT_TYPE_KEY, mappingContext) : typeMapper;
|
||||
? new DefaultMongoTypeMapper(DefaultMongoTypeMapper.DEFAULT_TYPE_KEY, mappingContext)
|
||||
: typeMapper;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -249,13 +242,14 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
throw new MappingException(String.format(INVALID_TYPE_TO_READ, target, typeToUse.getType()));
|
||||
}
|
||||
|
||||
return read((MongoPersistentEntity<S>) mappingContext.getRequiredPersistentEntity(typeToUse), target, path);
|
||||
return read((MongoPersistentEntity<S>) entity, target, path);
|
||||
}
|
||||
|
||||
private ParameterValueProvider<MongoPersistentProperty> getParameterProvider(MongoPersistentEntity<?> entity,
|
||||
Bson source, DefaultSpELExpressionEvaluator evaluator, ObjectPath path) {
|
||||
DocumentAccessor source, SpELExpressionEvaluator evaluator, ObjectPath path) {
|
||||
|
||||
MongoDbPropertyValueProvider provider = new MongoDbPropertyValueProvider(source, evaluator, path);
|
||||
AssociationAwareMongoDbPropertyValueProvider provider = new AssociationAwareMongoDbPropertyValueProvider(source,
|
||||
evaluator, path);
|
||||
PersistentEntityParameterValueProvider<MongoPersistentProperty> parameterProvider = new PersistentEntityParameterValueProvider<>(
|
||||
entity, provider, path.getCurrentObject());
|
||||
|
||||
@@ -265,60 +259,105 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
|
||||
private <S extends Object> S read(final MongoPersistentEntity<S> entity, final Document bson, final ObjectPath path) {
|
||||
|
||||
DefaultSpELExpressionEvaluator evaluator = new DefaultSpELExpressionEvaluator(bson, spELContext);
|
||||
SpELExpressionEvaluator evaluator = new DefaultSpELExpressionEvaluator(bson, spELContext);
|
||||
DocumentAccessor documentAccessor = new DocumentAccessor(bson);
|
||||
|
||||
PreferredConstructor<S, MongoPersistentProperty> persistenceConstructor = entity.getPersistenceConstructor();
|
||||
|
||||
ParameterValueProvider<MongoPersistentProperty> provider = persistenceConstructor != null
|
||||
&& persistenceConstructor.hasParameters() ? getParameterProvider(entity, documentAccessor, evaluator, path)
|
||||
: NoOpParameterValueProvider.INSTANCE;
|
||||
|
||||
ParameterValueProvider<MongoPersistentProperty> provider = getParameterProvider(entity, bson, evaluator, path);
|
||||
EntityInstantiator instantiator = instantiators.getInstantiatorFor(entity);
|
||||
S instance = instantiator.createInstance(entity, provider);
|
||||
|
||||
PersistentPropertyAccessor accessor = new ConvertingPropertyAccessor(entity.getPropertyAccessor(instance),
|
||||
conversionService);
|
||||
|
||||
MongoPersistentProperty idProperty = entity.getIdProperty();
|
||||
DocumentAccessor documentAccessor = new DocumentAccessor(bson);
|
||||
|
||||
// make sure id property is set before all other properties
|
||||
Object idValue = null;
|
||||
|
||||
if (idProperty != null && documentAccessor.hasValue(idProperty)) {
|
||||
|
||||
idValue = readIdValue(path, evaluator, idProperty, documentAccessor);
|
||||
accessor.setProperty(idProperty, idValue);
|
||||
if (entity.requiresPropertyPopulation()) {
|
||||
return populateProperties(entity, documentAccessor, path, evaluator, instance);
|
||||
}
|
||||
|
||||
ObjectPath currentPath = path.push(instance, entity, idValue != null ? bson.get(idProperty.getFieldName()) : null);
|
||||
|
||||
MongoDbPropertyValueProvider valueProvider = new MongoDbPropertyValueProvider(documentAccessor, evaluator,
|
||||
currentPath);
|
||||
|
||||
DbRefResolverCallback callback = new DefaultDbRefResolverCallback(bson, currentPath, evaluator,
|
||||
MappingMongoConverter.this);
|
||||
readProperties(entity, accessor, idProperty, documentAccessor, valueProvider, callback);
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
private Object readIdValue(ObjectPath path, DefaultSpELExpressionEvaluator evaluator,
|
||||
MongoPersistentProperty idProperty, DocumentAccessor documentAccessor) {
|
||||
private <S> S populateProperties(MongoPersistentEntity<S> entity, DocumentAccessor documentAccessor, ObjectPath path,
|
||||
SpELExpressionEvaluator evaluator, S instance) {
|
||||
|
||||
PersistentPropertyAccessor<S> accessor = new ConvertingPropertyAccessor<>(entity.getPropertyAccessor(instance),
|
||||
conversionService);
|
||||
|
||||
// Make sure id property is set before all other properties
|
||||
|
||||
Object rawId = readAndPopulateIdentifier(accessor, documentAccessor, entity, path, evaluator);
|
||||
ObjectPath currentPath = path.push(accessor.getBean(), entity, rawId);
|
||||
|
||||
MongoDbPropertyValueProvider valueProvider = new MongoDbPropertyValueProvider(documentAccessor, evaluator,
|
||||
currentPath);
|
||||
|
||||
readProperties(entity, accessor, documentAccessor, valueProvider, currentPath, evaluator);
|
||||
|
||||
return accessor.getBean();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the identifier from either the bean backing the {@link PersistentPropertyAccessor} or the source document in
|
||||
* case the identifier has not be populated yet. In this case the identifier is set on the bean for further reference.
|
||||
*
|
||||
* @param accessor must not be {@literal null}.
|
||||
* @param document must not be {@literal null}.
|
||||
* @param entity must not be {@literal null}.
|
||||
* @param path
|
||||
* @param evaluator
|
||||
* @return
|
||||
*/
|
||||
private Object readAndPopulateIdentifier(PersistentPropertyAccessor<?> accessor, DocumentAccessor document,
|
||||
MongoPersistentEntity<?> entity, ObjectPath path, SpELExpressionEvaluator evaluator) {
|
||||
|
||||
Object rawId = document.getRawId(entity);
|
||||
|
||||
if (!entity.hasIdProperty() || rawId == null) {
|
||||
return rawId;
|
||||
}
|
||||
|
||||
MongoPersistentProperty idProperty = entity.getRequiredIdProperty();
|
||||
|
||||
if (idProperty.isImmutable() && entity.isConstructorArgument(idProperty)) {
|
||||
return rawId;
|
||||
}
|
||||
|
||||
accessor.setProperty(idProperty, readIdValue(path, evaluator, idProperty, rawId));
|
||||
|
||||
return rawId;
|
||||
}
|
||||
|
||||
private Object readIdValue(ObjectPath path, SpELExpressionEvaluator evaluator, MongoPersistentProperty idProperty,
|
||||
Object rawId) {
|
||||
|
||||
String expression = idProperty.getSpelExpression();
|
||||
Object resolvedValue = expression != null ? evaluator.evaluate(expression) : documentAccessor.get(idProperty);
|
||||
Object resolvedValue = expression != null ? evaluator.evaluate(expression) : rawId;
|
||||
|
||||
return resolvedValue != null ? readValue(resolvedValue, idProperty.getTypeInformation(), path) : null;
|
||||
}
|
||||
|
||||
private void readProperties(MongoPersistentEntity<?> entity, PersistentPropertyAccessor accessor,
|
||||
@Nullable MongoPersistentProperty idProperty, DocumentAccessor documentAccessor,
|
||||
MongoDbPropertyValueProvider valueProvider, DbRefResolverCallback callback) {
|
||||
private void readProperties(MongoPersistentEntity<?> entity, PersistentPropertyAccessor<?> accessor,
|
||||
DocumentAccessor documentAccessor, MongoDbPropertyValueProvider valueProvider, ObjectPath currentPath,
|
||||
SpELExpressionEvaluator evaluator) {
|
||||
|
||||
DbRefResolverCallback callback = null;
|
||||
|
||||
for (MongoPersistentProperty prop : entity) {
|
||||
|
||||
if (prop.isAssociation() && !entity.isConstructorArgument(prop)) {
|
||||
|
||||
if (callback == null) {
|
||||
callback = getDbRefResolverCallback(documentAccessor, currentPath, evaluator);
|
||||
}
|
||||
|
||||
readAssociation(prop.getRequiredAssociation(), accessor, documentAccessor, dbRefProxyHandler, callback);
|
||||
continue;
|
||||
}
|
||||
// we skip the id property since it was already set
|
||||
if (idProperty != null && idProperty.equals(prop)) {
|
||||
|
||||
// We skip the id property since it was already set
|
||||
|
||||
if (entity.isIdProperty(prop)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -327,6 +366,11 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
}
|
||||
|
||||
if (prop.isAssociation()) {
|
||||
|
||||
if (callback == null) {
|
||||
callback = getDbRefResolverCallback(documentAccessor, currentPath, evaluator);
|
||||
}
|
||||
|
||||
readAssociation(prop.getRequiredAssociation(), accessor, documentAccessor, dbRefProxyHandler, callback);
|
||||
continue;
|
||||
}
|
||||
@@ -335,7 +379,14 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
}
|
||||
}
|
||||
|
||||
private void readAssociation(Association<MongoPersistentProperty> association, PersistentPropertyAccessor accessor,
|
||||
private DbRefResolverCallback getDbRefResolverCallback(DocumentAccessor documentAccessor, ObjectPath currentPath,
|
||||
SpELExpressionEvaluator evaluator) {
|
||||
|
||||
return new DefaultDbRefResolverCallback(documentAccessor.getDocument(), currentPath, evaluator,
|
||||
MappingMongoConverter.this);
|
||||
}
|
||||
|
||||
private void readAssociation(Association<MongoPersistentProperty> association, PersistentPropertyAccessor<?> accessor,
|
||||
DocumentAccessor documentAccessor, DbRefProxyHandler handler, DbRefResolverCallback callback) {
|
||||
|
||||
MongoPersistentProperty property = association.getInverse();
|
||||
@@ -392,12 +443,23 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
removeFromMap(bson, "_id");
|
||||
}
|
||||
|
||||
boolean handledByCustomConverter = conversions.hasCustomWriteTarget(entityType, Document.class);
|
||||
if (!handledByCustomConverter && !(bson instanceof Collection)) {
|
||||
if (requiresTypeHint(entityType)) {
|
||||
typeMapper.writeType(type, bson);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a given type requires a type hint (aka {@literal _class} attribute) when writing to the document.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
* @return {@literal true} if not a simple type, {@link Collection} or type with custom write target.
|
||||
*/
|
||||
private boolean requiresTypeHint(Class<?> type) {
|
||||
|
||||
return !conversions.isSimpleType(type) && !ClassUtils.isAssignable(Collection.class, type)
|
||||
&& !conversions.hasCustomWriteTarget(type, Document.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal write conversion method which should be used for nested invocations.
|
||||
*
|
||||
@@ -427,7 +489,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
}
|
||||
|
||||
if (Collection.class.isAssignableFrom(entityType)) {
|
||||
writeCollectionInternal((Collection<?>) obj, ClassTypeInformation.LIST, (Collection) bson);
|
||||
writeCollectionInternal((Collection<?>) obj, ClassTypeInformation.LIST, (Collection<?>) bson);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -446,10 +508,10 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
throw new MappingException("No mapping metadata found for entity of type " + obj.getClass().getName());
|
||||
}
|
||||
|
||||
PersistentPropertyAccessor accessor = entity.getPropertyAccessor(obj);
|
||||
PersistentPropertyAccessor<?> accessor = entity.getPropertyAccessor(obj);
|
||||
DocumentAccessor dbObjectAccessor = new DocumentAccessor(bson);
|
||||
|
||||
MongoPersistentProperty idProperty = entity.getIdProperty();
|
||||
|
||||
if (idProperty != null && !dbObjectAccessor.hasValue(idProperty)) {
|
||||
|
||||
Object value = idMapper.convertId(accessor.getProperty(idProperty));
|
||||
@@ -458,10 +520,11 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
dbObjectAccessor.put(idProperty, value);
|
||||
}
|
||||
}
|
||||
|
||||
writeProperties(bson, entity, accessor, dbObjectAccessor, idProperty);
|
||||
}
|
||||
|
||||
private void writeProperties(Bson bson, MongoPersistentEntity<?> entity, PersistentPropertyAccessor accessor,
|
||||
private void writeProperties(Bson bson, MongoPersistentEntity<?> entity, PersistentPropertyAccessor<?> accessor,
|
||||
DocumentAccessor dbObjectAccessor, @Nullable MongoPersistentProperty idProperty) {
|
||||
|
||||
// Write the properties
|
||||
@@ -489,8 +552,8 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
}
|
||||
}
|
||||
|
||||
private void writeAssociation(Association<MongoPersistentProperty> association, PersistentPropertyAccessor accessor,
|
||||
DocumentAccessor dbObjectAccessor) {
|
||||
private void writeAssociation(Association<MongoPersistentProperty> association,
|
||||
PersistentPropertyAccessor<?> accessor, DocumentAccessor dbObjectAccessor) {
|
||||
|
||||
MongoPersistentProperty inverseProp = association.getInverse();
|
||||
|
||||
@@ -555,8 +618,9 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
return;
|
||||
}
|
||||
|
||||
MongoPersistentEntity<?> entity = isSubtype(prop.getType(), obj.getClass())
|
||||
? mappingContext.getRequiredPersistentEntity(obj.getClass()) : mappingContext.getRequiredPersistentEntity(type);
|
||||
MongoPersistentEntity<?> entity = isSubTypeOf(obj.getClass(), prop.getType())
|
||||
? mappingContext.getRequiredPersistentEntity(obj.getClass())
|
||||
: mappingContext.getRequiredPersistentEntity(type);
|
||||
|
||||
Object existingValue = accessor.get(prop);
|
||||
Document document = existingValue instanceof Document ? (Document) existingValue : new Document();
|
||||
@@ -566,10 +630,6 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
accessor.put(prop, document);
|
||||
}
|
||||
|
||||
private boolean isSubtype(Class<?> left, Class<?> right) {
|
||||
return left.isAssignableFrom(right) && !left.equals(right);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns given object as {@link Collection}. Will return the {@link Collection} as is if the source is a
|
||||
* {@link Collection} already, will convert an array into a {@link Collection} or simply create a single element
|
||||
@@ -659,11 +719,13 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
* @param sink the {@link Collection} to write to.
|
||||
* @return
|
||||
*/
|
||||
private List<Object> writeCollectionInternal(Collection<?> source, @Nullable TypeInformation<?> type, Collection<?> sink) {
|
||||
@SuppressWarnings("unchecked")
|
||||
private List<Object> writeCollectionInternal(Collection<?> source, @Nullable TypeInformation<?> type,
|
||||
Collection<?> sink) {
|
||||
|
||||
TypeInformation<?> componentType = null;
|
||||
|
||||
List<Object> collection = sink instanceof List ? (List) sink : new ArrayList<>(sink);
|
||||
List<Object> collection = sink instanceof List ? (List<Object>) sink : new ArrayList<>(sink);
|
||||
|
||||
if (type != null) {
|
||||
componentType = type.getComponentType();
|
||||
@@ -777,7 +839,8 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
}
|
||||
|
||||
return conversions.hasCustomWriteTarget(key.getClass(), String.class)
|
||||
? (String) getPotentiallyConvertedSimpleWrite(key) : key.toString();
|
||||
? (String) getPotentiallyConvertedSimpleWrite(key)
|
||||
: key.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -867,7 +930,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
*/
|
||||
@Nullable
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
private Object getPotentiallyConvertedSimpleRead(@Nullable Object value, @Nullable Class<?> target) {
|
||||
private Object getPotentiallyConvertedSimpleRead(@Nullable Object value, @Nullable Class<?> target) {
|
||||
|
||||
if (value == null || target == null || ClassUtils.isAssignableValue(target, value)) {
|
||||
return value;
|
||||
@@ -943,7 +1006,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
Assert.notNull(path, "Object path must not be null!");
|
||||
|
||||
Class<?> collectionType = targetType.getType();
|
||||
collectionType = Collection.class.isAssignableFrom(collectionType) //
|
||||
collectionType = isSubTypeOf(collectionType, Collection.class) //
|
||||
? collectionType //
|
||||
: List.class;
|
||||
|
||||
@@ -977,13 +1040,12 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
items.add(read(componentType, (BasicDBObject) element, path));
|
||||
} else {
|
||||
|
||||
if (element instanceof Collection) {
|
||||
if (!Object.class.equals(rawComponentType) && element instanceof Collection) {
|
||||
if (!rawComponentType.isArray() && !ClassUtils.isAssignable(Iterable.class, rawComponentType)) {
|
||||
throw new MappingException(
|
||||
String.format(INCOMPATIBLE_TYPES, element, element.getClass(), rawComponentType, path));
|
||||
}
|
||||
}
|
||||
|
||||
if (element instanceof List) {
|
||||
items.add(readCollectionOrArray(componentType, (Collection<Object>) element, path));
|
||||
} else {
|
||||
@@ -1270,12 +1332,14 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
* of the configured source {@link Document}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Mark Paluch
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
class MongoDbPropertyValueProvider implements PropertyValueProvider<MongoPersistentProperty> {
|
||||
|
||||
private final DocumentAccessor source;
|
||||
private final SpELExpressionEvaluator evaluator;
|
||||
private final ObjectPath path;
|
||||
final DocumentAccessor accessor;
|
||||
final SpELExpressionEvaluator evaluator;
|
||||
final ObjectPath path;
|
||||
|
||||
/**
|
||||
* Creates a new {@link MongoDbPropertyValueProvider} for the given source, {@link SpELExpressionEvaluator} and
|
||||
@@ -1285,15 +1349,8 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
* @param evaluator must not be {@literal null}.
|
||||
* @param path must not be {@literal null}.
|
||||
*/
|
||||
public MongoDbPropertyValueProvider(Bson source, SpELExpressionEvaluator evaluator, ObjectPath path) {
|
||||
|
||||
Assert.notNull(source, "Source document must no be null!");
|
||||
Assert.notNull(evaluator, "SpELExpressionEvaluator must not be null!");
|
||||
Assert.notNull(path, "ObjectPath must not be null!");
|
||||
|
||||
this.source = new DocumentAccessor(source);
|
||||
this.evaluator = evaluator;
|
||||
this.path = path;
|
||||
MongoDbPropertyValueProvider(Bson source, SpELExpressionEvaluator evaluator, ObjectPath path) {
|
||||
this(new DocumentAccessor(source), evaluator, path);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1304,13 +1361,13 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
* @param evaluator must not be {@literal null}.
|
||||
* @param path must not be {@literal null}.
|
||||
*/
|
||||
public MongoDbPropertyValueProvider(DocumentAccessor accessor, SpELExpressionEvaluator evaluator, ObjectPath path) {
|
||||
MongoDbPropertyValueProvider(DocumentAccessor accessor, SpELExpressionEvaluator evaluator, ObjectPath path) {
|
||||
|
||||
Assert.notNull(accessor, "DocumentAccessor must no be null!");
|
||||
Assert.notNull(evaluator, "SpELExpressionEvaluator must not be null!");
|
||||
Assert.notNull(path, "ObjectPath must not be null!");
|
||||
|
||||
this.source = accessor;
|
||||
this.accessor = accessor;
|
||||
this.evaluator = evaluator;
|
||||
this.path = path;
|
||||
}
|
||||
@@ -1323,7 +1380,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
public <T> T getPropertyValue(MongoPersistentProperty property) {
|
||||
|
||||
String expression = property.getSpelExpression();
|
||||
Object value = expression != null ? evaluator.evaluate(expression) : source.get(property);
|
||||
Object value = expression != null ? evaluator.evaluate(expression) : accessor.get(property);
|
||||
|
||||
if (value == null) {
|
||||
return null;
|
||||
@@ -1333,6 +1390,55 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link PropertyValueProvider} that is aware of {@link MongoPersistentProperty#isAssociation()} and that delegates
|
||||
* resolution to {@link DbRefResolver}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Christoph Strobl
|
||||
* @since 2.1
|
||||
*/
|
||||
class AssociationAwareMongoDbPropertyValueProvider extends MongoDbPropertyValueProvider {
|
||||
|
||||
/**
|
||||
* Creates a new {@link AssociationAwareMongoDbPropertyValueProvider} for the given source,
|
||||
* {@link SpELExpressionEvaluator} and {@link ObjectPath}.
|
||||
*
|
||||
* @param source must not be {@literal null}.
|
||||
* @param evaluator must not be {@literal null}.
|
||||
* @param path must not be {@literal null}.
|
||||
*/
|
||||
AssociationAwareMongoDbPropertyValueProvider(DocumentAccessor source, SpELExpressionEvaluator evaluator,
|
||||
ObjectPath path) {
|
||||
super(source, evaluator, path);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.convert.PropertyValueProvider#getPropertyValue(org.springframework.data.mapping.PersistentProperty)
|
||||
*/
|
||||
@Nullable
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T getPropertyValue(MongoPersistentProperty property) {
|
||||
|
||||
if (property.isDbReference() && property.getDBRef().lazy()) {
|
||||
|
||||
Object rawRefValue = accessor.get(property);
|
||||
if (rawRefValue == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
DbRefResolverCallback callback = new DefaultDbRefResolverCallback(accessor.getDocument(), path, evaluator,
|
||||
MappingMongoConverter.this);
|
||||
|
||||
DBRef dbref = rawRefValue instanceof DBRef ? (DBRef) rawRefValue : null;
|
||||
return (T) dbRefResolver.resolveDbRef(property, dbref, callback, dbRefProxyHandler);
|
||||
}
|
||||
|
||||
return super.getPropertyValue(property);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extension of {@link SpELExpressionParameterValueProvider} to recursively trigger value conversion on the raw
|
||||
* resolved SpEL value.
|
||||
@@ -1435,7 +1541,8 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
}
|
||||
|
||||
List<Document> referencedRawDocuments = dbrefs.size() == 1
|
||||
? Collections.singletonList(readRef(dbrefs.iterator().next())) : bulkReadRefs(dbrefs);
|
||||
? Collections.singletonList(readRef(dbrefs.iterator().next()))
|
||||
: bulkReadRefs(dbrefs);
|
||||
String collectionName = dbrefs.iterator().next().getCollectionName();
|
||||
|
||||
List<T> targeList = new ArrayList<>(dbrefs.size());
|
||||
@@ -1517,6 +1624,17 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given type is a sub type of the given reference, i.e. assignable but not the exact same type.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
* @param reference must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
private static boolean isSubTypeOf(Class<?> type, Class<?> reference) {
|
||||
return !type.equals(reference) && reference.isAssignableFrom(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Marker class used to indicate we have a non root document object here that might be used within an update - so we
|
||||
* need to preserve type hints for potential nested elements but need to remove it on top level.
|
||||
@@ -1527,4 +1645,14 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
static class NestedDocument {
|
||||
|
||||
}
|
||||
|
||||
enum NoOpParameterValueProvider implements ParameterValueProvider<MongoPersistentProperty> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public <T> T getParameterValue(Parameter<T, MongoPersistentProperty> parameter) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Currency;
|
||||
@@ -26,6 +27,7 @@ import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import org.bson.BsonTimestamp;
|
||||
import org.bson.Document;
|
||||
import org.bson.types.Binary;
|
||||
import org.bson.types.Code;
|
||||
@@ -86,6 +88,7 @@ abstract class MongoConverters {
|
||||
converters.add(LongToAtomicLongConverter.INSTANCE);
|
||||
converters.add(IntegerToAtomicIntegerConverter.INSTANCE);
|
||||
converters.add(BinaryToByteArrayConverter.INSTANCE);
|
||||
converters.add(BsonTimestampToInstantConverter.INSTANCE);
|
||||
|
||||
return converters;
|
||||
}
|
||||
@@ -465,4 +468,22 @@ abstract class MongoConverters {
|
||||
return source.getData();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link Converter} implementation converting {@link BsonTimestamp} into {@link Instant}.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.1.2
|
||||
*/
|
||||
@ReadingConverter
|
||||
enum BsonTimestampToInstantConverter implements Converter<BsonTimestamp, Instant> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Instant convert(BsonTimestamp source) {
|
||||
return Instant.ofEpochSecond(source.getTime(), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,13 +164,14 @@ public class MongoExampleMapper {
|
||||
if (exampleSpecAccessor.hasPropertySpecifier(mappedPropertyPath)) {
|
||||
|
||||
PropertyValueTransformer valueTransformer = exampleSpecAccessor.getValueTransformerForPath(mappedPropertyPath);
|
||||
value = valueTransformer.convert(value);
|
||||
if (value == null) {
|
||||
Optional converted = valueTransformer.apply(Optional.ofNullable(value));
|
||||
|
||||
if(!converted.isPresent()) {
|
||||
iter.remove();
|
||||
continue;
|
||||
}
|
||||
|
||||
entry.setValue(value);
|
||||
entry.setValue(converted.get());
|
||||
}
|
||||
|
||||
if (entry.getValue() instanceof String) {
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright 2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.mongodb.core.convert;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.bson.Document;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
import com.mongodb.DBRef;
|
||||
|
||||
/**
|
||||
* No-Operation {@link org.springframework.data.mongodb.core.mapping.DBRef} resolver throwing
|
||||
* {@link UnsupportedOperationException} when attempting to resolve database references.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Christoph Strobl
|
||||
* @since 2.1
|
||||
*/
|
||||
public enum NoOpDbRefResolver implements DbRefResolver {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.convert.DbRefResolver#resolveDbRef(org.springframework.data.mongodb.core.mapping.MongoPersistentProperty, org.springframework.data.mongodb.core.convert.DbRefResolverCallback, org.springframework.data.mongodb.core.convert.DbRefProxyHandler)
|
||||
*/
|
||||
@Override
|
||||
@Nullable
|
||||
public Object resolveDbRef(MongoPersistentProperty property, @Nullable DBRef dbref, DbRefResolverCallback callback,
|
||||
DbRefProxyHandler proxyHandler) {
|
||||
|
||||
return handle();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.convert.DbRefResolver#fetch(com.mongodb.DBRef)
|
||||
*/
|
||||
@Override
|
||||
@Nullable
|
||||
public Document fetch(DBRef dbRef) {
|
||||
return handle();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.convert.DbRefResolver#bulkFetch(java.util.List)
|
||||
*/
|
||||
@Override
|
||||
public List<Document> bulkFetch(List<DBRef> dbRefs) {
|
||||
return handle();
|
||||
}
|
||||
|
||||
private <T> T handle() throws UnsupportedOperationException {
|
||||
throw new UnsupportedOperationException("DBRef resolution is not supported!");
|
||||
}
|
||||
}
|
||||
@@ -15,8 +15,6 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core.convert;
|
||||
|
||||
import lombok.Value;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -45,26 +43,33 @@ class ObjectPath {
|
||||
|
||||
static final ObjectPath ROOT = new ObjectPath();
|
||||
|
||||
private final ObjectPathItem[] items;
|
||||
private final @Nullable ObjectPath parent;
|
||||
private final @Nullable Object object;
|
||||
private final @Nullable Object idValue;
|
||||
private final String collection;
|
||||
|
||||
private ObjectPath() {
|
||||
this.items = new ObjectPathItem[0];
|
||||
|
||||
this.parent = null;
|
||||
this.object = null;
|
||||
this.idValue = null;
|
||||
this.collection = "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link ObjectPath} from the given parent {@link ObjectPath} by adding the provided
|
||||
* {@link ObjectPathItem} to it.
|
||||
* Creates a new {@link ObjectPath} from the given parent {@link ObjectPath} and adding the provided path values.
|
||||
*
|
||||
* @param parent must not be {@literal null}.
|
||||
* @param item
|
||||
* @param collection
|
||||
* @param idValue
|
||||
* @param collection
|
||||
*/
|
||||
private ObjectPath(ObjectPath parent, ObjectPath.ObjectPathItem item) {
|
||||
private ObjectPath(ObjectPath parent, Object object, @Nullable Object idValue, String collection) {
|
||||
|
||||
ObjectPathItem[] items = new ObjectPathItem[parent.items.length + 1];
|
||||
System.arraycopy(parent.items, 0, items, 0, parent.items.length);
|
||||
items[parent.items.length] = item;
|
||||
|
||||
this.items = items;
|
||||
this.parent = parent;
|
||||
this.object = object;
|
||||
this.idValue = idValue;
|
||||
this.collection = collection;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -80,8 +85,7 @@ class ObjectPath {
|
||||
Assert.notNull(object, "Object must not be null!");
|
||||
Assert.notNull(entity, "MongoPersistentEntity must not be null!");
|
||||
|
||||
ObjectPathItem item = new ObjectPathItem(object, id, entity.getCollection());
|
||||
return new ObjectPath(this, item);
|
||||
return new ObjectPath(this, object, id, entity.getCollection());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -100,15 +104,15 @@ class ObjectPath {
|
||||
Assert.notNull(id, "Id must not be null!");
|
||||
Assert.hasText(collection, "Collection name must not be null!");
|
||||
|
||||
for (ObjectPathItem item : items) {
|
||||
for (ObjectPath current = this; current != null; current = current.parent) {
|
||||
|
||||
Object object = item.getObject();
|
||||
Object object = current.getObject();
|
||||
|
||||
if (object == null || item.getIdValue() == null) {
|
||||
if (object == null || current.getIdValue() == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (collection.equals(item.getCollection()) && id.equals(item.getIdValue())) {
|
||||
if (collection.equals(current.getCollection()) && id.equals(current.getIdValue())) {
|
||||
return object;
|
||||
}
|
||||
}
|
||||
@@ -133,15 +137,15 @@ class ObjectPath {
|
||||
Assert.hasText(collection, "Collection name must not be null!");
|
||||
Assert.notNull(type, "Type must not be null!");
|
||||
|
||||
for (ObjectPathItem item : items) {
|
||||
for (ObjectPath current = this; current != null; current = current.parent) {
|
||||
|
||||
Object object = item.getObject();
|
||||
Object object = current.getObject();
|
||||
|
||||
if (object == null || item.getIdValue() == null) {
|
||||
if (object == null || current.getIdValue() == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (collection.equals(item.getCollection()) && id.equals(item.getIdValue())
|
||||
if (collection.equals(current.getCollection()) && id.equals(current.getIdValue())
|
||||
&& ClassUtils.isAssignable(type, object.getClass())) {
|
||||
return type.cast(object);
|
||||
}
|
||||
@@ -157,7 +161,21 @@ class ObjectPath {
|
||||
*/
|
||||
@Nullable
|
||||
Object getCurrentObject() {
|
||||
return items.length == 0 ? null : items[items.length - 1].getObject();
|
||||
return getObject();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Object getObject() {
|
||||
return object;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Object getIdValue() {
|
||||
return idValue;
|
||||
}
|
||||
|
||||
private String getCollection() {
|
||||
return collection;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -167,31 +185,16 @@ class ObjectPath {
|
||||
@Override
|
||||
public String toString() {
|
||||
|
||||
if (items.length == 0) {
|
||||
if (parent == null) {
|
||||
return "[empty]";
|
||||
}
|
||||
|
||||
List<String> strings = new ArrayList<>(items.length);
|
||||
List<String> strings = new ArrayList<>();
|
||||
|
||||
for (ObjectPathItem item : items) {
|
||||
strings.add(ObjectUtils.nullSafeToString(item.object));
|
||||
for (ObjectPath current = this; current != null; current = current.parent) {
|
||||
strings.add(ObjectUtils.nullSafeToString(current.getObject()));
|
||||
}
|
||||
|
||||
return StringUtils.collectionToDelimitedString(strings, " -> ");
|
||||
}
|
||||
|
||||
/**
|
||||
* An item in an {@link ObjectPath}.
|
||||
*
|
||||
* @author Thomas Darimont
|
||||
* @author Oliver Gierke
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@Value
|
||||
private static class ObjectPathItem {
|
||||
|
||||
Object object;
|
||||
@Nullable Object idValue;
|
||||
String collection;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -312,7 +312,7 @@ public class QueryMapper {
|
||||
@SuppressWarnings("unchecked")
|
||||
protected Object getMappedValue(Field documentField, Object value) {
|
||||
|
||||
if (documentField.isIdField()) {
|
||||
if (documentField.isIdField() && !documentField.isAssociation()) {
|
||||
|
||||
if (isDBObject(value)) {
|
||||
DBObject valueDbo = (DBObject) value;
|
||||
@@ -850,15 +850,18 @@ public class QueryMapper {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.convert.QueryMapper.Field#isIdKey()
|
||||
* @see org.springframework.data.mongodb.core.convert.QueryMapper.Field#isIdField()
|
||||
*/
|
||||
@Override
|
||||
public boolean isIdField() {
|
||||
|
||||
MongoPersistentProperty idProperty = entity.getIdProperty();
|
||||
MongoPersistentProperty idProperty = (property != null && property.isIdProperty()) ? property
|
||||
: entity.getIdProperty();
|
||||
|
||||
if (idProperty != null) {
|
||||
return idProperty.getName().equals(name) || idProperty.getFieldName().equals(name);
|
||||
|
||||
return name.equals(idProperty.getName()) || name.equals(idProperty.getFieldName())
|
||||
|| name.endsWith("." + idProperty.getName()) || name.endsWith("." + idProperty.getFieldName());
|
||||
}
|
||||
|
||||
return DEFAULT_ID_NAMES.contains(name);
|
||||
|
||||
@@ -70,7 +70,9 @@ public @interface CompoundIndex {
|
||||
/**
|
||||
* @return
|
||||
* @see <a href="https://docs.mongodb.org/manual/core/index-creation/#index-creation-duplicate-dropping">https://docs.mongodb.org/manual/core/index-creation/#index-creation-duplicate-dropping</a>
|
||||
* @deprecated since 2.1. No longer supported by MongoDB as of server version 3.0.
|
||||
*/
|
||||
@Deprecated
|
||||
boolean dropDups() default false;
|
||||
|
||||
/**
|
||||
|
||||
@@ -36,6 +36,10 @@ import org.springframework.util.StringUtils;
|
||||
@SuppressWarnings("deprecation")
|
||||
public class Index implements IndexDefinition {
|
||||
|
||||
/**
|
||||
* @deprecated since 2.1. No longer supported by MongoDB as of server version 3.0.
|
||||
*/
|
||||
@Deprecated
|
||||
public enum Duplicates {
|
||||
RETAIN
|
||||
}
|
||||
@@ -43,7 +47,6 @@ public class Index implements IndexDefinition {
|
||||
private final Map<String, Direction> fieldSpec = new LinkedHashMap<String, Direction>();
|
||||
private @Nullable String name;
|
||||
private boolean unique = false;
|
||||
private boolean dropDuplicates = false;
|
||||
private boolean sparse = false;
|
||||
private boolean background = false;
|
||||
private long expire = -1;
|
||||
@@ -183,9 +186,6 @@ public class Index implements IndexDefinition {
|
||||
if (unique) {
|
||||
document.put("unique", true);
|
||||
}
|
||||
if (dropDuplicates) {
|
||||
document.put("dropDups", true);
|
||||
}
|
||||
if (sparse) {
|
||||
document.put("sparse", true);
|
||||
}
|
||||
|
||||
@@ -56,7 +56,9 @@ public @interface Indexed {
|
||||
/**
|
||||
* @return
|
||||
* @see <a href="https://docs.mongodb.org/manual/core/index-creation/#index-creation-duplicate-dropping">https://docs.mongodb.org/manual/core/index-creation/#index-creation-duplicate-dropping</a>
|
||||
* @deprecated since 2.1. No longer supported by MongoDB as of server version 3.0.
|
||||
*/
|
||||
@Deprecated
|
||||
boolean dropDups() default false;
|
||||
|
||||
/**
|
||||
|
||||
@@ -24,11 +24,14 @@ import org.springframework.lang.Nullable;
|
||||
* {@link MongoPersistentProperty} caching access to {@link #isIdProperty()} and {@link #getFieldName()}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class CachingMongoPersistentProperty extends BasicMongoPersistentProperty {
|
||||
|
||||
private @Nullable Boolean isIdProperty;
|
||||
private @Nullable Boolean isAssociation;
|
||||
private @Nullable boolean dbRefResolved;
|
||||
private @Nullable DBRef dbref;
|
||||
private @Nullable String fieldName;
|
||||
private @Nullable Boolean usePropertyAccess;
|
||||
private @Nullable Boolean isTransient;
|
||||
@@ -36,8 +39,7 @@ public class CachingMongoPersistentProperty extends BasicMongoPersistentProperty
|
||||
/**
|
||||
* Creates a new {@link CachingMongoPersistentProperty}.
|
||||
*
|
||||
* @param field
|
||||
* @param propertyDescriptor
|
||||
* @param property
|
||||
* @param owner
|
||||
* @param simpleTypeHolder
|
||||
* @param fieldNamingStrategy
|
||||
@@ -114,4 +116,28 @@ public class CachingMongoPersistentProperty extends BasicMongoPersistentProperty
|
||||
|
||||
return this.isTransient;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.mapping.BasicMongoPersistentProperty#isDbReference()
|
||||
*/
|
||||
@Override
|
||||
public boolean isDbReference() {
|
||||
return getDBRef() != null;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.mapping.BasicMongoPersistentProperty#getDBRef()
|
||||
*/
|
||||
@Override
|
||||
public DBRef getDBRef() {
|
||||
|
||||
if (!dbRefResolved) {
|
||||
this.dbref = super.getDBRef();
|
||||
this.dbRefResolved = true;
|
||||
}
|
||||
|
||||
return this.dbref;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,13 +22,13 @@ import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.bson.BsonObjectId;
|
||||
import org.bson.*;
|
||||
import org.bson.types.Binary;
|
||||
import org.bson.types.CodeWScope;
|
||||
import org.bson.types.CodeWithScope;
|
||||
import org.bson.types.Decimal128;
|
||||
import org.bson.types.ObjectId;
|
||||
import org.springframework.data.mapping.model.SimpleTypeHolder;
|
||||
import org.springframework.data.mongodb.util.MongoClientVersion;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
import com.mongodb.DBRef;
|
||||
|
||||
@@ -54,15 +54,29 @@ public abstract class MongoSimpleTypes {
|
||||
simpleTypes.add(ObjectId.class);
|
||||
simpleTypes.add(BsonObjectId.class);
|
||||
simpleTypes.add(CodeWScope.class);
|
||||
simpleTypes.add(CodeWithScope.class);
|
||||
simpleTypes.add(org.bson.Document.class);
|
||||
simpleTypes.add(Pattern.class);
|
||||
simpleTypes.add(Binary.class);
|
||||
simpleTypes.add(UUID.class);
|
||||
simpleTypes.add(Decimal128.class);
|
||||
|
||||
if (MongoClientVersion.isMongo34Driver()) {
|
||||
simpleTypes
|
||||
.add(ClassUtils.resolveClassName("org.bson.types.Decimal128", MongoSimpleTypes.class.getClassLoader()));
|
||||
}
|
||||
simpleTypes.add(BsonBinary.class);
|
||||
simpleTypes.add(BsonBoolean.class);
|
||||
simpleTypes.add(BsonDateTime.class);
|
||||
simpleTypes.add(BsonDbPointer.class);
|
||||
simpleTypes.add(BsonDecimal128.class);
|
||||
simpleTypes.add(BsonDocument.class);
|
||||
simpleTypes.add(BsonDocument.class);
|
||||
simpleTypes.add(BsonDouble.class);
|
||||
simpleTypes.add(BsonInt32.class);
|
||||
simpleTypes.add(BsonInt64.class);
|
||||
simpleTypes.add(BsonJavaScript.class);
|
||||
simpleTypes.add(BsonJavaScriptWithScope.class);
|
||||
simpleTypes.add(BsonObjectId.class);
|
||||
simpleTypes.add(BsonRegularExpression.class);
|
||||
simpleTypes.add(BsonString.class);
|
||||
simpleTypes.add(BsonTimestamp.class);
|
||||
|
||||
MONGO_SIMPLE_TYPES = Collections.unmodifiableSet(simpleTypes);
|
||||
}
|
||||
|
||||
@@ -15,8 +15,6 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core.mapping.event;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.beans.factory.ObjectFactory;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.core.Ordered;
|
||||
@@ -53,9 +51,7 @@ public class AuditingEventListener implements ApplicationListener<BeforeConvertE
|
||||
*/
|
||||
@Override
|
||||
public void onApplicationEvent(BeforeConvertEvent<Object> event) {
|
||||
|
||||
Optional.ofNullable(event.getSource())//
|
||||
.ifPresent(it -> auditingHandlerFactory.getObject().markAudited(it));
|
||||
event.mapSource(it -> auditingHandlerFactory.getObject().markAudited(it));
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core.mapping.event;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.bson.Document;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
import org.springframework.lang.Nullable;
|
||||
@@ -72,4 +74,19 @@ public class MongoMappingEvent<T> extends ApplicationEvent {
|
||||
public T getSource() {
|
||||
return (T) super.getSource();
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows client code to change the underlying source instance by applying the given {@link Function}.
|
||||
*
|
||||
* @param mapper the {@link Function} to apply, will only be applied if the source is not {@literal null}.
|
||||
* @since 2.1
|
||||
*/
|
||||
final void mapSource(Function<T, T> mapper) {
|
||||
|
||||
if (source == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.source = mapper.apply(getSource());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,8 @@ import org.springframework.data.mongodb.core.query.Collation;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
import com.mongodb.MapReduceCommand;
|
||||
import com.mongodb.MapReduceCommand.OutputType;
|
||||
import com.mongodb.client.model.MapReduceAction;
|
||||
|
||||
/**
|
||||
* @author Mark Pollack
|
||||
@@ -295,6 +297,37 @@ public class MapReduceOptions {
|
||||
return collation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@link MapReduceAction} derived from {@link com.mongodb.MapReduceCommand.OutputType}.
|
||||
*
|
||||
* @return the mapped action or {@literal null} if the action maps to inline output.
|
||||
* @since 2.0.10
|
||||
*/
|
||||
@Nullable
|
||||
public MapReduceAction getMapReduceAction() {
|
||||
|
||||
switch (outputType) {
|
||||
case MERGE:
|
||||
return MapReduceAction.MERGE;
|
||||
case REDUCE:
|
||||
return MapReduceAction.REDUCE;
|
||||
case REPLACE:
|
||||
return MapReduceAction.REPLACE;
|
||||
case INLINE:
|
||||
return null;
|
||||
default:
|
||||
throw new IllegalStateException(String.format("Unknown output type %s for map reduce command.", outputType));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@literal true} if {@link OutputType#INLINE} is used.
|
||||
* @since 2.0.10
|
||||
*/
|
||||
public boolean usesInlineOutput() {
|
||||
return OutputType.INLINE.equals(outputType);
|
||||
}
|
||||
|
||||
public Document getOptionsObject() {
|
||||
|
||||
Document cmd = new Document();
|
||||
@@ -328,7 +361,7 @@ public class MapReduceOptions {
|
||||
|
||||
Document out = new Document();
|
||||
|
||||
switch (outputType) {
|
||||
switch (getOutputType()) {
|
||||
case INLINE:
|
||||
out.put("inline", 1);
|
||||
break;
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core.messaging;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
import org.bson.BsonValue;
|
||||
import org.bson.Document;
|
||||
import org.springframework.data.mongodb.core.ChangeStreamOptions;
|
||||
@@ -34,7 +36,7 @@ import com.mongodb.client.model.changestream.FullDocument;
|
||||
* using the synchronous MongoDB Java driver.
|
||||
* <p/>
|
||||
* The most trivial use case is subscribing to all events of a specific {@link com.mongodb.client.MongoCollection
|
||||
* collection}.
|
||||
* collection}
|
||||
*
|
||||
* <pre>
|
||||
* <code>
|
||||
@@ -42,6 +44,15 @@ import com.mongodb.client.model.changestream.FullDocument;
|
||||
* </code>
|
||||
* </pre>
|
||||
*
|
||||
* or {@link com.mongodb.client.MongoDatabase} which receives events from all {@link com.mongodb.client.MongoCollection
|
||||
* collections} in that database.
|
||||
*
|
||||
* <pre>
|
||||
* <code>
|
||||
* ChangeStreamRequest<Document> request = new ChangeStreamRequest<>(System.out::println, RequestOptions.justDatabase("test"));
|
||||
* </code>
|
||||
* </pre>
|
||||
*
|
||||
* For more advanced scenarios {@link ChangeStreamOptions} offers abstractions for options like filtering, resuming,...
|
||||
*
|
||||
* <pre>
|
||||
@@ -154,21 +165,23 @@ public class ChangeStreamRequest<T>
|
||||
*/
|
||||
public static class ChangeStreamRequestOptions implements SubscriptionRequest.RequestOptions {
|
||||
|
||||
private final String collectionName;
|
||||
private final @Nullable String databaseName;
|
||||
private final @Nullable String collectionName;
|
||||
private final ChangeStreamOptions options;
|
||||
|
||||
/**
|
||||
* Create new {@link ChangeStreamRequestOptions}.
|
||||
*
|
||||
* @param collectionName must not be {@literal null}.
|
||||
* @param collectionName can be {@literal null}.
|
||||
* @param options must not be {@literal null}.
|
||||
*/
|
||||
public ChangeStreamRequestOptions(String collectionName, ChangeStreamOptions options) {
|
||||
public ChangeStreamRequestOptions(@Nullable String databaseName, @Nullable String collectionName,
|
||||
ChangeStreamOptions options) {
|
||||
|
||||
Assert.notNull(collectionName, "CollectionName must not be null!");
|
||||
Assert.notNull(options, "Options must not be null!");
|
||||
|
||||
this.collectionName = collectionName;
|
||||
this.databaseName = databaseName;
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
@@ -176,7 +189,8 @@ public class ChangeStreamRequest<T>
|
||||
|
||||
Assert.notNull(options, "Options must not be null!");
|
||||
|
||||
return new ChangeStreamRequestOptions(options.getCollectionName(), ChangeStreamOptions.builder().build());
|
||||
return new ChangeStreamRequestOptions(options.getDatabaseName(), options.getCollectionName(),
|
||||
ChangeStreamOptions.builder().build());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -196,6 +210,15 @@ public class ChangeStreamRequest<T>
|
||||
public String getCollectionName() {
|
||||
return collectionName;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.monitor.SubscriptionRequest.RequestOptions#getDatabaseName()
|
||||
*/
|
||||
@Override
|
||||
public String getDatabaseName() {
|
||||
return databaseName;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -207,12 +230,27 @@ public class ChangeStreamRequest<T>
|
||||
*/
|
||||
public static class ChangeStreamRequestBuilder<T> {
|
||||
|
||||
private @Nullable String databaseName;
|
||||
private @Nullable String collectionName;
|
||||
private @Nullable MessageListener<ChangeStreamDocument<Document>, ? super T> listener;
|
||||
private ChangeStreamOptionsBuilder delegate = ChangeStreamOptions.builder();
|
||||
|
||||
private ChangeStreamRequestBuilder() {}
|
||||
|
||||
/**
|
||||
* Set the name of the {@link com.mongodb.client.MongoDatabase} to listen to.
|
||||
*
|
||||
* @param databaseName must not be {@literal null} nor empty.
|
||||
* @return this.
|
||||
*/
|
||||
public ChangeStreamRequestBuilder<T> database(String databaseName) {
|
||||
|
||||
Assert.hasText(databaseName, "DatabaseName must not be null!");
|
||||
|
||||
this.databaseName = databaseName;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the name of the {@link com.mongodb.client.MongoCollection} to listen to.
|
||||
*
|
||||
@@ -317,6 +355,22 @@ public class ChangeStreamRequest<T>
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the cluster time at which to resume listening.
|
||||
*
|
||||
* @param clusterTime must not be {@literal null}.
|
||||
* @return this.
|
||||
* @see ChangeStreamOptions#getResumeTimestamp()
|
||||
* @see ChangeStreamOptionsBuilder#resumeAt(java.time.Instant)
|
||||
*/
|
||||
public ChangeStreamRequestBuilder<T> resumeAt(Instant clusterTime) {
|
||||
|
||||
Assert.notNull(clusterTime, "ClusterTime must not be null!");
|
||||
|
||||
this.delegate.resumeAt(clusterTime);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link FullDocument} lookup to {@link FullDocument#UPDATE_LOOKUP}.
|
||||
*
|
||||
@@ -339,9 +393,9 @@ public class ChangeStreamRequest<T>
|
||||
public ChangeStreamRequest<T> build() {
|
||||
|
||||
Assert.notNull(listener, "MessageListener must not be null!");
|
||||
Assert.hasText(collectionName, "CollectionName must not be null!");
|
||||
|
||||
return new ChangeStreamRequest<>(listener, new ChangeStreamRequestOptions(collectionName, delegate.build()));
|
||||
return new ChangeStreamRequest<>(listener,
|
||||
new ChangeStreamRequestOptions(databaseName, collectionName, delegate.build()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,14 +17,16 @@ package org.springframework.data.mongodb.core.messaging;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import org.bson.BsonDocument;
|
||||
import org.bson.BsonTimestamp;
|
||||
import org.bson.BsonValue;
|
||||
import org.bson.Document;
|
||||
import org.springframework.data.mongodb.core.ChangeStreamEvent;
|
||||
import org.springframework.data.mongodb.core.ChangeStreamOptions;
|
||||
@@ -42,10 +44,12 @@ import org.springframework.data.mongodb.core.messaging.SubscriptionRequest.Reque
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.ErrorHandler;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.mongodb.MongoNamespace;
|
||||
import com.mongodb.client.ChangeStreamIterable;
|
||||
import com.mongodb.client.MongoCursor;
|
||||
import com.mongodb.client.MongoDatabase;
|
||||
import com.mongodb.client.model.Collation;
|
||||
import com.mongodb.client.model.changestream.ChangeStreamDocument;
|
||||
import com.mongodb.client.model.changestream.FullDocument;
|
||||
@@ -54,6 +58,7 @@ import com.mongodb.client.model.changestream.FullDocument;
|
||||
* {@link Task} implementation for obtaining {@link ChangeStreamDocument ChangeStreamDocuments} from MongoDB.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
* @since 2.1
|
||||
*/
|
||||
class ChangeStreamTask extends CursorReadingTask<ChangeStreamDocument<Document>, Object> {
|
||||
@@ -73,7 +78,7 @@ class ChangeStreamTask extends CursorReadingTask<ChangeStreamDocument<Document>,
|
||||
mongoConverter = template.getConverter();
|
||||
}
|
||||
|
||||
/*
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.messaging.CursorReadingTask#initCursor(org.springframework.data.mongodb.core.MongoTemplate, org.springframework.data.mongodb.core.messaging.SubscriptionRequest.RequestOptions, java.lang.Class)
|
||||
*/
|
||||
@@ -84,7 +89,9 @@ class ChangeStreamTask extends CursorReadingTask<ChangeStreamDocument<Document>,
|
||||
List<Document> filter = Collections.emptyList();
|
||||
BsonDocument resumeToken = new BsonDocument();
|
||||
Collation collation = null;
|
||||
FullDocument fullDocument = FullDocument.DEFAULT;
|
||||
FullDocument fullDocument = ClassUtils.isAssignable(Document.class, targetType) ? FullDocument.DEFAULT
|
||||
: FullDocument.UPDATE_LOOKUP;
|
||||
BsonTimestamp startAt = null;
|
||||
|
||||
if (options instanceof ChangeStreamRequest.ChangeStreamRequestOptions) {
|
||||
|
||||
@@ -107,16 +114,32 @@ class ChangeStreamTask extends CursorReadingTask<ChangeStreamDocument<Document>,
|
||||
fullDocument = changeStreamOptions.getFullDocumentLookup()
|
||||
.orElseGet(() -> ClassUtils.isAssignable(Document.class, targetType) ? FullDocument.DEFAULT
|
||||
: FullDocument.UPDATE_LOOKUP);
|
||||
|
||||
startAt = changeStreamOptions.getResumeTimestamp().map(it -> new BsonTimestamp((int) it.getEpochSecond(), 0))
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
ChangeStreamIterable<Document> iterable = filter.isEmpty()
|
||||
? template.getCollection(options.getCollectionName()).watch(Document.class)
|
||||
: template.getCollection(options.getCollectionName()).watch(filter, Document.class);
|
||||
MongoDatabase db = StringUtils.hasText(options.getDatabaseName())
|
||||
? template.getMongoDbFactory().getDb(options.getDatabaseName()) : template.getDb();
|
||||
|
||||
ChangeStreamIterable<Document> iterable;
|
||||
|
||||
if (StringUtils.hasText(options.getCollectionName())) {
|
||||
iterable = filter.isEmpty() ? db.getCollection(options.getCollectionName()).watch(Document.class)
|
||||
: db.getCollection(options.getCollectionName()).watch(filter, Document.class);
|
||||
|
||||
} else {
|
||||
iterable = filter.isEmpty() ? db.watch(Document.class) : db.watch(filter, Document.class);
|
||||
}
|
||||
|
||||
if (!resumeToken.isEmpty()) {
|
||||
iterable = iterable.resumeAfter(resumeToken);
|
||||
}
|
||||
|
||||
if (startAt != null) {
|
||||
iterable.startAtOperationTime(startAt);
|
||||
}
|
||||
|
||||
if (collation != null) {
|
||||
iterable = iterable.collation(collation);
|
||||
}
|
||||
@@ -126,13 +149,15 @@ class ChangeStreamTask extends CursorReadingTask<ChangeStreamDocument<Document>,
|
||||
return iterable.iterator();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Document> prepareFilter(MongoTemplate template, ChangeStreamOptions options) {
|
||||
|
||||
if (!options.getFilter().isPresent()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
Object filter = options.getFilter().get();
|
||||
Object filter = options.getFilter().orElse(null);
|
||||
|
||||
if (filter instanceof Aggregation) {
|
||||
Aggregation agg = (Aggregation) filter;
|
||||
AggregationOperationContext context = agg instanceof TypedAggregation
|
||||
@@ -141,26 +166,39 @@ class ChangeStreamTask extends CursorReadingTask<ChangeStreamDocument<Document>,
|
||||
: Aggregation.DEFAULT_CONTEXT;
|
||||
|
||||
return agg.toPipeline(new PrefixingDelegatingAggregationOperationContext(context, "fullDocument", blacklist));
|
||||
} else if (filter instanceof List) {
|
||||
return (List<Document>) filter;
|
||||
} else {
|
||||
throw new IllegalArgumentException(
|
||||
"ChangeStreamRequestOptions.filter mut be either an Aggregation or a plain list of Documents");
|
||||
}
|
||||
|
||||
if (filter instanceof List) {
|
||||
return (List<Document>) filter;
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException(
|
||||
"ChangeStreamRequestOptions.filter mut be either an Aggregation or a plain list of Documents");
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.messaging.CursorReadingTask#createMessage(java.lang.Object, java.lang.Class, org.springframework.data.mongodb.core.messaging.SubscriptionRequest.RequestOptions)
|
||||
*/
|
||||
@Override
|
||||
protected Message<ChangeStreamDocument<Document>, Object> createMessage(ChangeStreamDocument<Document> source,
|
||||
Class<Object> targetType, RequestOptions options) {
|
||||
|
||||
// namespace might be null for eg. OperationType.INVALIDATE
|
||||
MongoNamespace namespace = Optional.ofNullable(source.getNamespace())
|
||||
.orElse(new MongoNamespace("unknown", options.getCollectionName()));
|
||||
MongoNamespace namespace = source.getNamespace() != null ? source.getNamespace()
|
||||
: createNamespaceFromOptions(options);
|
||||
|
||||
return new ChangeStreamEventMessage<>(new ChangeStreamEvent<>(source, targetType, mongoConverter), MessageProperties
|
||||
.builder().databaseName(namespace.getDatabaseName()).collectionName(namespace.getCollectionName()).build());
|
||||
}
|
||||
|
||||
MongoNamespace createNamespaceFromOptions(RequestOptions options) {
|
||||
|
||||
String collectionName = StringUtils.hasText(options.getCollectionName()) ? options.getCollectionName() : "unknown";
|
||||
String databaseName = StringUtils.hasText(options.getDatabaseName()) ? options.getDatabaseName() : "unknown";
|
||||
|
||||
return new MongoNamespace(databaseName, collectionName);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link Message} implementation for ChangeStreams
|
||||
*
|
||||
@@ -172,7 +210,7 @@ class ChangeStreamTask extends CursorReadingTask<ChangeStreamDocument<Document>,
|
||||
private final ChangeStreamEvent<T> delegate;
|
||||
private final MessageProperties messageProperties;
|
||||
|
||||
/*
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.messaging.Message#getRaw()
|
||||
*/
|
||||
@@ -182,7 +220,7 @@ class ChangeStreamTask extends CursorReadingTask<ChangeStreamDocument<Document>,
|
||||
return delegate.getRaw();
|
||||
}
|
||||
|
||||
/*
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.messaging.Message#getBody()
|
||||
*/
|
||||
@@ -192,7 +230,7 @@ class ChangeStreamTask extends CursorReadingTask<ChangeStreamDocument<Document>,
|
||||
return delegate.getBody();
|
||||
}
|
||||
|
||||
/*
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.messaging.Message#getProperties()
|
||||
*/
|
||||
@@ -200,5 +238,32 @@ class ChangeStreamTask extends CursorReadingTask<ChangeStreamDocument<Document>,
|
||||
public MessageProperties getProperties() {
|
||||
return this.messageProperties;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the resume token or {@literal null} if not set.
|
||||
* @see ChangeStreamEvent#getResumeToken()
|
||||
*/
|
||||
@Nullable
|
||||
BsonValue getResumeToken() {
|
||||
return delegate.getResumeToken();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the cluster time of the event or {@literal null}.
|
||||
* @see ChangeStreamEvent#getTimestamp()
|
||||
*/
|
||||
@Nullable
|
||||
Instant getTimestamp() {
|
||||
return delegate.getTimestamp();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link ChangeStreamEvent} from the message.
|
||||
*
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
ChangeStreamEvent<T> getChangeStreamEvent() {
|
||||
return delegate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,10 +24,10 @@ import org.springframework.data.mongodb.core.MongoTemplate;
|
||||
import org.springframework.data.mongodb.core.messaging.Message.MessageProperties;
|
||||
import org.springframework.data.mongodb.core.messaging.SubscriptionRequest.RequestOptions;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ErrorHandler;
|
||||
|
||||
import com.mongodb.client.MongoCursor;
|
||||
import com.mysema.commons.lang.Assert;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
@@ -202,7 +202,7 @@ abstract class CursorReadingTask<T, R> implements Task {
|
||||
public boolean awaitStart(Duration timeout) throws InterruptedException {
|
||||
|
||||
Assert.notNull(timeout, "Timeout must not be null!");
|
||||
Assert.isFalse(timeout.isNegative(), "Timeout must not be negative!");
|
||||
Assert.isTrue(!timeout.isNegative(), "Timeout must not be negative!");
|
||||
|
||||
return awaitStart.await(timeout.toNanos(), TimeUnit.NANOSECONDS);
|
||||
}
|
||||
|
||||
@@ -15,7 +15,10 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core.messaging;
|
||||
|
||||
import org.springframework.data.mongodb.MongoDbFactory;
|
||||
import org.springframework.data.mongodb.core.messaging.SubscriptionRequest.RequestOptions;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* The actual {@link SubscriptionRequest} sent to the {@link MessageListenerContainer}. This wrapper type allows passing
|
||||
@@ -51,8 +54,93 @@ public interface SubscriptionRequest<S, T, O extends RequestOptions> {
|
||||
interface RequestOptions {
|
||||
|
||||
/**
|
||||
* @return the name of the collection to subscribe to. Never {@literal null}.
|
||||
* Get the database name of the db.
|
||||
*
|
||||
* @return the name of the database to subscribe to. Can be {@literal null} in which case the default
|
||||
* {@link MongoDbFactory#getDb() database} is used.
|
||||
*/
|
||||
@Nullable
|
||||
default String getDatabaseName() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the collection name.
|
||||
*
|
||||
* @return the name of the collection to subscribe to. Can be {@literal null}.
|
||||
*/
|
||||
@Nullable
|
||||
String getCollectionName();
|
||||
|
||||
/**
|
||||
* Create empty options.
|
||||
*
|
||||
* @return new instance of empty {@link RequestOptions}.
|
||||
*/
|
||||
static RequestOptions none() {
|
||||
return () -> null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create options with the provided database.
|
||||
*
|
||||
* @param database must not be {@literal null}.
|
||||
* @return new instance of empty {@link RequestOptions}.
|
||||
*/
|
||||
static RequestOptions justDatabase(String database) {
|
||||
|
||||
Assert.notNull(database, "Database must not be null!");
|
||||
|
||||
return new RequestOptions() {
|
||||
|
||||
@Override
|
||||
public String getCollectionName() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDatabaseName() {
|
||||
return database;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create options with the provided collection.
|
||||
*
|
||||
* @param collection must not be {@literal null}.
|
||||
* @return new instance of empty {@link RequestOptions}.
|
||||
*/
|
||||
static RequestOptions justCollection(String collection) {
|
||||
|
||||
Assert.notNull(collection, "Collection must not be null!");
|
||||
return () -> collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create options with the provided database and collection.
|
||||
*
|
||||
* @param database must not be {@literal null}.
|
||||
* @param collection must not be {@literal null}.
|
||||
* @return new instance of empty {@link RequestOptions}.
|
||||
*/
|
||||
static RequestOptions of(String database, String collection) {
|
||||
|
||||
Assert.notNull(database, "Database must not be null!");
|
||||
Assert.notNull(collection, "Collection must not be null!");
|
||||
|
||||
return new RequestOptions() {
|
||||
|
||||
@Override
|
||||
public String getCollectionName() {
|
||||
return collection;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDatabaseName() {
|
||||
return database;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -868,8 +868,17 @@ public class Criteria implements CriteriaDefinition {
|
||||
return right == null;
|
||||
}
|
||||
|
||||
if (left instanceof Pattern) {
|
||||
return right instanceof Pattern ? ((Pattern) left).pattern().equals(((Pattern) right).pattern()) : false;
|
||||
if (Pattern.class.isInstance(left)) {
|
||||
|
||||
if (!Pattern.class.isInstance(right)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Pattern leftPattern = (Pattern) left;
|
||||
Pattern rightPattern = (Pattern) right;
|
||||
|
||||
return leftPattern.pattern().equals(rightPattern.pattern()) //
|
||||
&& leftPattern.flags() == rightPattern.flags();
|
||||
}
|
||||
|
||||
return ObjectUtils.nullSafeEquals(left, right);
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core.query;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
@@ -50,6 +51,7 @@ public class Meta {
|
||||
|
||||
private final Map<String, Object> values = new LinkedHashMap<String, Object>(2);
|
||||
private final Set<CursorOption> flags = new LinkedHashSet<CursorOption>();
|
||||
private Integer cursorBatchSize;
|
||||
|
||||
/**
|
||||
* @return {@literal null} if not set.
|
||||
@@ -65,7 +67,7 @@ public class Meta {
|
||||
* @param maxTimeMsec
|
||||
*/
|
||||
public void setMaxTimeMsec(long maxTimeMsec) {
|
||||
setMaxTime(maxTimeMsec, TimeUnit.MILLISECONDS);
|
||||
setMaxTime(Duration.ofMillis(maxTimeMsec));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -73,11 +75,25 @@ public class Meta {
|
||||
*
|
||||
* @param timeout
|
||||
* @param timeUnit
|
||||
* @deprecated since 2.1. Use {@link #setMaxTime(Duration)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public void setMaxTime(long timeout, @Nullable TimeUnit timeUnit) {
|
||||
setValue(MetaKey.MAX_TIME_MS.key, (timeUnit != null ? timeUnit : TimeUnit.MILLISECONDS).toMillis(timeout));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the maximum time limit for processing operations.
|
||||
*
|
||||
* @param timeout must not be {@literal null}.
|
||||
* @since 2.1
|
||||
*/
|
||||
public void setMaxTime(Duration timeout) {
|
||||
|
||||
Assert.notNull(timeout, "Timeout must not be null!");
|
||||
setValue(MetaKey.MAX_TIME_MS.key, timeout.toMillis());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@literal null} if not set.
|
||||
*/
|
||||
@@ -90,13 +106,15 @@ public class Meta {
|
||||
* Only scan the specified number of documents.
|
||||
*
|
||||
* @param maxScan
|
||||
* @deprecated since 2.1 due to deprecation in MongoDB 4.0.
|
||||
*/
|
||||
@Deprecated
|
||||
public void setMaxScan(long maxScan) {
|
||||
setValue(MetaKey.MAX_SCAN.key, maxScan);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a comment to the query.
|
||||
* Add a comment to the query that is propagated to the profile log.
|
||||
*
|
||||
* @param comment
|
||||
*/
|
||||
@@ -116,7 +134,9 @@ public class Meta {
|
||||
* Using snapshot prevents the cursor from returning a document more than once.
|
||||
*
|
||||
* @param useSnapshot
|
||||
* @deprecated since 2.1 due to deprecation as of MongoDB 3.6
|
||||
*/
|
||||
@Deprecated
|
||||
public void setSnapshot(boolean useSnapshot) {
|
||||
setValue(MetaKey.SNAPSHOT.key, useSnapshot);
|
||||
}
|
||||
@@ -128,6 +148,27 @@ public class Meta {
|
||||
return getValue(MetaKey.SNAPSHOT.key, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@literal null} if not set.
|
||||
* @since 2.1
|
||||
*/
|
||||
@Nullable
|
||||
public Integer getCursorBatchSize() {
|
||||
return cursorBatchSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the batch size (number of documents to return in each response) for a query. <br />
|
||||
* Use {@literal 0 (zero)} for no limit. A <strong>negative limit</strong> closes the cursor after returning a single
|
||||
* batch indicating to the server that the client will not ask for a subsequent one.
|
||||
*
|
||||
* @param cursorBatchSize The number of documents to return per batch.
|
||||
* @since 2.1
|
||||
*/
|
||||
public void setCursorBatchSize(int cursorBatchSize) {
|
||||
this.cursorBatchSize = cursorBatchSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add {@link CursorOption} influencing behavior of the {@link com.mongodb.DBCursor}.
|
||||
*
|
||||
@@ -153,7 +194,7 @@ public class Meta {
|
||||
* @return
|
||||
*/
|
||||
public boolean hasValues() {
|
||||
return !this.values.isEmpty() || !this.flags.isEmpty();
|
||||
return !this.values.isEmpty() || !this.flags.isEmpty() || this.cursorBatchSize != null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -18,6 +18,7 @@ package org.springframework.data.mongodb.core.query;
|
||||
import static org.springframework.data.mongodb.core.query.SerializationUtils.*;
|
||||
import static org.springframework.util.ObjectUtils.*;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
@@ -289,7 +290,7 @@ public class Query {
|
||||
|
||||
/**
|
||||
* @param maxTimeMsec
|
||||
* @return
|
||||
* @return this.
|
||||
* @see Meta#setMaxTimeMsec(long)
|
||||
* @since 1.6
|
||||
*/
|
||||
@@ -302,22 +303,38 @@ public class Query {
|
||||
/**
|
||||
* @param timeout
|
||||
* @param timeUnit
|
||||
* @return
|
||||
* @return this.
|
||||
* @see Meta#setMaxTime(long, TimeUnit)
|
||||
* @since 1.6
|
||||
* @deprecated since 2.1. Use {@link #maxTime(Duration)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public Query maxTime(long timeout, TimeUnit timeUnit) {
|
||||
|
||||
meta.setMaxTime(timeout, timeUnit);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param timeout
|
||||
* @return this.
|
||||
* @see Meta#setMaxTime(Duration)
|
||||
* @since 2.1
|
||||
*/
|
||||
public Query maxTime(Duration timeout) {
|
||||
|
||||
meta.setMaxTime(timeout);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param maxScan
|
||||
* @return
|
||||
* @return this.
|
||||
* @see Meta#setMaxScan(long)
|
||||
* @since 1.6
|
||||
* @deprecated since 2.1 due to deprecation in MongoDB 4.0.
|
||||
*/
|
||||
@Deprecated
|
||||
public Query maxScan(long maxScan) {
|
||||
|
||||
meta.setMaxScan(maxScan);
|
||||
@@ -325,8 +342,10 @@ public class Query {
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a comment to the query that is propagated to the profile log.
|
||||
*
|
||||
* @param comment
|
||||
* @return
|
||||
* @return this.
|
||||
* @see Meta#setComment(String)
|
||||
* @since 1.6
|
||||
*/
|
||||
@@ -337,10 +356,12 @@ public class Query {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return
|
||||
* @return this.
|
||||
* @see Meta#setSnapshot(boolean)
|
||||
* @since 1.6
|
||||
* @deprecated since 2.1 due to deprecation as of MongoDB 3.6
|
||||
*/
|
||||
@Deprecated
|
||||
public Query useSnapshot() {
|
||||
|
||||
meta.setSnapshot(true);
|
||||
@@ -348,7 +369,23 @@ public class Query {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return
|
||||
* Set the number of documents to return in each response batch. <br />
|
||||
* Use {@literal 0 (zero)} for no limit. A <strong>negative limit</strong> closes the cursor after returning a single
|
||||
* batch indicating to the server that the client will not ask for a subsequent one.
|
||||
*
|
||||
* @param batchSize The number of documents to return per batch.
|
||||
* @return this.
|
||||
* @see Meta#setCursorBatchSize(int)
|
||||
* @since 2.1
|
||||
*/
|
||||
public Query cursorBatchSize(int batchSize) {
|
||||
|
||||
meta.setCursorBatchSize(batchSize);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return this.
|
||||
* @see org.springframework.data.mongodb.core.query.Meta.CursorOption#NO_TIMEOUT
|
||||
* @since 1.10
|
||||
*/
|
||||
@@ -359,7 +396,7 @@ public class Query {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return
|
||||
* @return this.
|
||||
* @see org.springframework.data.mongodb.core.query.Meta.CursorOption#EXHAUST
|
||||
* @since 1.10
|
||||
*/
|
||||
@@ -370,7 +407,9 @@ public class Query {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return
|
||||
* Allows querying of a replica slave.
|
||||
*
|
||||
* @return this.
|
||||
* @see org.springframework.data.mongodb.core.query.Meta.CursorOption#SLAVE_OK
|
||||
* @since 1.10
|
||||
*/
|
||||
@@ -381,7 +420,7 @@ public class Query {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return
|
||||
* @return this.
|
||||
* @see org.springframework.data.mongodb.core.query.Meta.CursorOption#PARTIAL
|
||||
* @since 1.10
|
||||
*/
|
||||
@@ -392,7 +431,7 @@ public class Query {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return never {@literal null}.
|
||||
* @return never {@literal null}.ø
|
||||
* @since 1.6
|
||||
*/
|
||||
public Meta getMeta() {
|
||||
|
||||
@@ -893,18 +893,14 @@ public class Update {
|
||||
/**
|
||||
* Forces values to be added at the given {@literal position}.
|
||||
*
|
||||
* @param position needs to be greater than or equal to zero.
|
||||
* @param position the position offset. As of MongoDB 3.6 use a negative value to indicate starting from the end,
|
||||
* counting (but not including) the last element of the array.
|
||||
* @return never {@literal null}.
|
||||
* @since 1.7
|
||||
*/
|
||||
public PushOperatorBuilder atPosition(int position) {
|
||||
|
||||
if (position < 0) {
|
||||
throw new IllegalArgumentException("Position must be greater than or equal to zero.");
|
||||
}
|
||||
|
||||
this.modifiers.addModifier(new PositionModifier(position));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@@ -52,6 +52,7 @@ import com.mongodb.client.gridfs.model.GridFSUploadOptions;
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
* @author Hartmut Lang
|
||||
* @author Niklas Helge Hanft
|
||||
*/
|
||||
public class GridFsTemplate implements GridFsOperations, ResourcePatternResolver {
|
||||
|
||||
@@ -228,7 +229,8 @@ public class GridFsTemplate implements GridFsOperations, ResourcePatternResolver
|
||||
*/
|
||||
public GridFsResource getResource(String location) {
|
||||
|
||||
return Optional.ofNullable(findOne(query(whereFilename().is(location)))).map(this::getResource)
|
||||
return Optional.ofNullable(findOne(query(whereFilename().is(location)))) //
|
||||
.map(this::getResource) //
|
||||
.orElseGet(() -> GridFsResource.absent(location));
|
||||
}
|
||||
|
||||
@@ -240,7 +242,7 @@ public class GridFsTemplate implements GridFsOperations, ResourcePatternResolver
|
||||
|
||||
Assert.notNull(file, "GridFSFile must not be null!");
|
||||
|
||||
return new GridFsResource(file, getGridFs().openDownloadStream(file.getFilename()));
|
||||
return new GridFsResource(file, getGridFs().openDownloadStream(file.getObjectId()));
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -261,7 +263,7 @@ public class GridFsTemplate implements GridFsOperations, ResourcePatternResolver
|
||||
List<GridFsResource> resources = new ArrayList<>();
|
||||
|
||||
for (GridFSFile file : files) {
|
||||
resources.add(new GridFsResource(file, getGridFs().openDownloadStream(file.getFilename())));
|
||||
resources.add(getResource(file));
|
||||
}
|
||||
|
||||
return resources.toArray(new GridFsResource[0]);
|
||||
|
||||
@@ -21,7 +21,6 @@ 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.QueryAnnotation;
|
||||
|
||||
/**
|
||||
@@ -50,6 +49,16 @@ public @interface Meta {
|
||||
*/
|
||||
long maxScanDocuments() default -1;
|
||||
|
||||
/**
|
||||
* Sets the number of documents to return per batch. <br />
|
||||
* Use {@literal 0 (zero)} for no limit. A <strong>negative limit</strong> closes the cursor after returning a single
|
||||
* batch indicating to the server that the client will not ask for a subsequent one.
|
||||
*
|
||||
* @return {@literal 0 (zero)} by default.
|
||||
* @since 2.1
|
||||
*/
|
||||
int cursorBatchSize() default 0;
|
||||
|
||||
/**
|
||||
* Add a comment to the query.
|
||||
*
|
||||
|
||||
@@ -30,6 +30,7 @@ import org.springframework.data.repository.query.QueryByExampleExecutor;
|
||||
* @author Christoph Strobl
|
||||
* @author Thomas Darimont
|
||||
* @author Mark Paluch
|
||||
* @author Khaled Baklouti
|
||||
*/
|
||||
@NoRepositoryBean
|
||||
public interface MongoRepository<T, ID> extends PagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T> {
|
||||
@@ -39,7 +40,7 @@ public interface MongoRepository<T, ID> extends PagingAndSortingRepository<T, ID
|
||||
* @see org.springframework.data.repository.CrudRepository#saveAll(java.lang.Iterable)
|
||||
*/
|
||||
@Override
|
||||
<S extends T> List<S> saveAll(Iterable<S> entites);
|
||||
<S extends T> List<S> saveAll(Iterable<S> entities);
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
|
||||
@@ -77,4 +77,22 @@ public @interface Query {
|
||||
* @return
|
||||
*/
|
||||
boolean delete() default false;
|
||||
|
||||
/**
|
||||
* Defines a default sort order for the given query.<br />
|
||||
* <strong>NOTE:</strong> The so set defaults can be altered / overwritten using an explicit
|
||||
* {@link org.springframework.data.domain.Sort} argument of the query method.
|
||||
*
|
||||
* <pre>
|
||||
* <code>
|
||||
*
|
||||
* @Query(sort = "{ age : -1 }") // order by age descending
|
||||
* List<Person> findByFirstname(String firstname);
|
||||
* </code>
|
||||
* </pre>
|
||||
*
|
||||
* @return
|
||||
* @since 2.1
|
||||
*/
|
||||
String sort() default "";
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.repository.query;
|
||||
|
||||
import org.bson.Document;
|
||||
import org.springframework.data.mongodb.core.ExecutableFindOperation.ExecutableFind;
|
||||
import org.springframework.data.mongodb.core.ExecutableFindOperation.FindWithQuery;
|
||||
import org.springframework.data.mongodb.core.ExecutableFindOperation.TerminatingFind;
|
||||
@@ -84,6 +85,7 @@ public abstract class AbstractMongoQuery implements RepositoryQuery {
|
||||
Query query = createQuery(accessor);
|
||||
|
||||
applyQueryMetaAttributesWhenPresent(query);
|
||||
query = applyAnnotatedDefaultSortIfPresent(query);
|
||||
|
||||
ResultProcessor processor = method.getResultProcessor().withDynamicProjection(accessor);
|
||||
Class<?> typeToRead = processor.getReturnedType().getTypeToRead();
|
||||
@@ -110,7 +112,7 @@ public abstract class AbstractMongoQuery implements RepositoryQuery {
|
||||
} else if (method.isStreamQuery()) {
|
||||
return q -> operation.matching(q).stream();
|
||||
} else if (method.isCollectionQuery()) {
|
||||
return q -> operation.matching(q.with(accessor.getPageable())).all();
|
||||
return q -> operation.matching(q.with(accessor.getPageable()).with(accessor.getSort())).all();
|
||||
} else if (method.isPageQuery()) {
|
||||
return new PagedExecution(operation, accessor.getPageable());
|
||||
} else if (isCountQuery()) {
|
||||
@@ -135,6 +137,23 @@ public abstract class AbstractMongoQuery implements RepositoryQuery {
|
||||
return query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a default sort derived from {@link org.springframework.data.mongodb.repository.Query#sort()} to the given
|
||||
* {@link Query} if present.
|
||||
*
|
||||
* @param query the {@link Query} to potentially apply the sort to.
|
||||
* @return the query with potential default sort applied.
|
||||
* @since 2.1
|
||||
*/
|
||||
Query applyAnnotatedDefaultSortIfPresent(Query query) {
|
||||
|
||||
if (!method.hasAnnotatedSort()) {
|
||||
return query;
|
||||
}
|
||||
|
||||
return QueryUtils.decorateSort(query, Document.parse(method.getAnnotatedSort()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link Query} instance using the given {@link ConvertingParameterAccessor}. Will delegate to
|
||||
* {@link #createQuery(ConvertingParameterAccessor)} by default but allows customization of the count query to be
|
||||
|
||||
@@ -18,6 +18,7 @@ package org.springframework.data.mongodb.repository.query;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.bson.Document;
|
||||
import org.reactivestreams.Publisher;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.data.convert.EntityInstantiators;
|
||||
@@ -31,11 +32,9 @@ import org.springframework.data.mongodb.repository.query.ReactiveMongoQueryExecu
|
||||
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.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;
|
||||
|
||||
/**
|
||||
@@ -109,6 +108,7 @@ public abstract class AbstractReactiveMongoQuery implements RepositoryQuery {
|
||||
Query query = createQuery(new ConvertingParameterAccessor(operations.getConverter(), parameterAccessor));
|
||||
|
||||
applyQueryMetaAttributesWhenPresent(query);
|
||||
query = applyAnnotatedDefaultSortIfPresent(query);
|
||||
|
||||
ResultProcessor processor = method.getResultProcessor().withDynamicProjection(parameterAccessor);
|
||||
Class<?> typeToRead = processor.getReturnedType().getTypeToRead();
|
||||
@@ -119,7 +119,7 @@ public abstract class AbstractReactiveMongoQuery implements RepositoryQuery {
|
||||
|
||||
String collection = method.getEntityInformation().getCollectionName();
|
||||
|
||||
ReactiveMongoQueryExecution execution = getExecution(query, parameterAccessor,
|
||||
ReactiveMongoQueryExecution execution = getExecution(parameterAccessor,
|
||||
new ResultProcessingConverter(processor, operations, instantiators), find);
|
||||
|
||||
return execution.execute(query, processor.getReturnedType().getDomainType(), collection);
|
||||
@@ -128,12 +128,11 @@ public abstract class AbstractReactiveMongoQuery implements RepositoryQuery {
|
||||
/**
|
||||
* Returns the execution instance to use.
|
||||
*
|
||||
* @param query must not be {@literal null}.
|
||||
* @param accessor must not be {@literal null}.
|
||||
* @param resultProcessing must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
private ReactiveMongoQueryExecution getExecution(Query query, MongoParameterAccessor accessor,
|
||||
private ReactiveMongoQueryExecution getExecution(MongoParameterAccessor accessor,
|
||||
Converter<Object, Object> resultProcessing, FindWithQuery<?> operation) {
|
||||
return new ResultProcessingExecution(getExecutionToWrap(accessor, operation), resultProcessing);
|
||||
}
|
||||
@@ -145,11 +144,13 @@ public abstract class AbstractReactiveMongoQuery implements RepositoryQuery {
|
||||
} else if (method.isGeoNearQuery()) {
|
||||
return new GeoNearExecution(operations, accessor, method.getReturnType());
|
||||
} else if (isTailable(method)) {
|
||||
return new TailExecution(operations, accessor.getPageable());
|
||||
return (q, t, c) -> operation.matching(q.with(accessor.getPageable())).tail();
|
||||
} else if (method.isCollectionQuery()) {
|
||||
return (q, t, c) -> operation.matching(q.with(accessor.getPageable())).all();
|
||||
} else if (isCountQuery()) {
|
||||
return (q, t, c) -> operation.matching(q).count();
|
||||
} else if (isExistsQuery()) {
|
||||
return (q, t, c) -> operation.matching(q).exists();
|
||||
} else {
|
||||
return (q, t, c) -> {
|
||||
|
||||
@@ -177,6 +178,23 @@ public abstract class AbstractReactiveMongoQuery implements RepositoryQuery {
|
||||
return query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a default sort derived from {@link org.springframework.data.mongodb.repository.Query#sort()} to the given
|
||||
* {@link Query} if present.
|
||||
*
|
||||
* @param query the {@link Query} to potentially apply the sort to.
|
||||
* @return the query with potential default sort applied.
|
||||
* @since 2.1
|
||||
*/
|
||||
Query applyAnnotatedDefaultSortIfPresent(Query query) {
|
||||
|
||||
if (!method.hasAnnotatedSort()) {
|
||||
return query;
|
||||
}
|
||||
|
||||
return QueryUtils.decorateSort(query, Document.parse(method.getAnnotatedSort()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link Query} instance using the given {@link ConvertingParameterAccessor}. Will delegate to
|
||||
* {@link #createQuery(ConvertingParameterAccessor)} by default but allows customization of the count query to be
|
||||
@@ -204,6 +222,14 @@ public abstract class AbstractReactiveMongoQuery implements RepositoryQuery {
|
||||
*/
|
||||
protected abstract boolean isCountQuery();
|
||||
|
||||
/**
|
||||
* Returns whether the query should get an exists projection applied.
|
||||
*
|
||||
* @return
|
||||
* @since 2.0.9
|
||||
*/
|
||||
protected abstract boolean isExistsQuery();
|
||||
|
||||
/**
|
||||
* Return weather the query should delete matching documents.
|
||||
*
|
||||
|
||||
@@ -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.repository.query;
|
||||
|
||||
import lombok.experimental.UtilityClass;
|
||||
|
||||
/**
|
||||
* Utility class containing methods to interact with boolean values.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 2.0.9
|
||||
*/
|
||||
@UtilityClass
|
||||
class BooleanUtil {
|
||||
|
||||
/**
|
||||
* Count the number of {@literal true} values.
|
||||
*
|
||||
* @param values
|
||||
* @return the number of values that are {@literal true}.
|
||||
*/
|
||||
static int countBooleanTrueValues(boolean... values) {
|
||||
|
||||
int count = 0;
|
||||
|
||||
for (boolean value : values) {
|
||||
|
||||
if (value) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
}
|
||||
@@ -16,16 +16,19 @@
|
||||
package org.springframework.data.mongodb.repository.query;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.Value;
|
||||
import lombok.experimental.UtilityClass;
|
||||
|
||||
import java.io.StringWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
@@ -205,6 +208,7 @@ class ExpressionEvaluatingParameterBinder {
|
||||
* @param binding must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private String getParameterValueForBinding(MongoParameterAccessor accessor, MongoParameters parameters,
|
||||
ParameterBinding binding) {
|
||||
|
||||
@@ -221,37 +225,7 @@ class ExpressionEvaluatingParameterBinder {
|
||||
return binding.isExpression() ? JSON.serialize(value) : QuotedString.unquote(JSON.serialize(value));
|
||||
}
|
||||
|
||||
if (value instanceof byte[]) {
|
||||
|
||||
if (binding.isQuoted()) {
|
||||
return DatatypeConverter.printBase64Binary((byte[]) value);
|
||||
}
|
||||
|
||||
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();
|
||||
return EncodableValue.create(value).encode(codecRegistryProvider, binding.isQuoted());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -473,4 +447,221 @@ class ExpressionEvaluatingParameterBinder {
|
||||
return quoted.substring(1, quoted.length() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Value object encapsulating a bindable value, that can be encoded to be represented as JSON (BSON).
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
abstract static class EncodableValue {
|
||||
|
||||
/**
|
||||
* Obtain a {@link EncodableValue} given {@code value}.
|
||||
*
|
||||
* @param value the value to encode, may be {@literal null}.
|
||||
* @return the {@link EncodableValue} for {@code value}.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static EncodableValue create(@Nullable Object value) {
|
||||
|
||||
if (value instanceof byte[]) {
|
||||
return new BinaryValue((byte[]) value);
|
||||
}
|
||||
|
||||
if (value instanceof UUID) {
|
||||
return new UuidValue((UUID) value);
|
||||
}
|
||||
|
||||
if (value instanceof Collection) {
|
||||
|
||||
Collection<?> collection = (Collection<?>) value;
|
||||
Class<?> commonElement = CollectionUtils.findCommonElementType(collection);
|
||||
|
||||
if (commonElement != null) {
|
||||
|
||||
if (UUID.class.isAssignableFrom(commonElement)) {
|
||||
return new UuidCollection((Collection<UUID>) value);
|
||||
}
|
||||
|
||||
if (byte[].class.isAssignableFrom(commonElement)) {
|
||||
return new BinaryCollectionValue((Collection<byte[]>) value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new ObjectValue(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode the encapsulated value.
|
||||
*
|
||||
* @param provider
|
||||
* @param quoted
|
||||
* @return
|
||||
*/
|
||||
public abstract String encode(CodecRegistryProvider provider, boolean quoted);
|
||||
|
||||
/**
|
||||
* Encode a {@code value} to JSON.
|
||||
*
|
||||
* @param provider
|
||||
* @param value
|
||||
* @param defaultCodec
|
||||
* @param <V>
|
||||
* @return
|
||||
*/
|
||||
protected <V> String encode(CodecRegistryProvider provider, V value, Supplier<Codec<V>> defaultCodec) {
|
||||
|
||||
StringWriter writer = new StringWriter();
|
||||
|
||||
doEncode(provider, writer, value, defaultCodec);
|
||||
|
||||
return writer.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode a {@link Collection} to JSON and potentially apply a {@link Function mapping function} before encoding.
|
||||
*
|
||||
* @param provider
|
||||
* @param value
|
||||
* @param mappingFunction
|
||||
* @param defaultCodec
|
||||
* @param <I> Input value type.
|
||||
* @param <V> Target type.
|
||||
* @return
|
||||
*/
|
||||
protected <I, V> String encodeCollection(CodecRegistryProvider provider, Iterable<I> value,
|
||||
Function<I, V> mappingFunction, Supplier<Codec<V>> defaultCodec) {
|
||||
|
||||
StringWriter writer = new StringWriter();
|
||||
|
||||
writer.append("[");
|
||||
value.forEach(it -> {
|
||||
|
||||
if (writer.getBuffer().length() > 1) {
|
||||
writer.append(", ");
|
||||
}
|
||||
|
||||
doEncode(provider, writer, mappingFunction.apply(it), defaultCodec);
|
||||
});
|
||||
|
||||
writer.append("]");
|
||||
writer.flush();
|
||||
|
||||
return writer.toString();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <V> void doEncode(CodecRegistryProvider provider, StringWriter writer, V value,
|
||||
Supplier<Codec<V>> defaultCodec) {
|
||||
|
||||
Codec<V> codec = provider.getCodecFor((Class<V>) value.getClass()).orElseGet(defaultCodec);
|
||||
|
||||
JsonWriter jsonWriter = new JsonWriter(writer);
|
||||
codec.encode(jsonWriter, value, null);
|
||||
jsonWriter.flush();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link EncodableValue} for {@code byte[]} to render to {@literal $binary}.
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
static class BinaryValue extends EncodableValue {
|
||||
|
||||
private final byte[] value;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.repository.query.ExpressionEvaluatingParameterBinder.EncodableValue#encode(org.springframework.data.mongodb.CodecRegistryProvider, boolean)
|
||||
*/
|
||||
@Override
|
||||
public String encode(CodecRegistryProvider provider, boolean quoted) {
|
||||
|
||||
if (quoted) {
|
||||
return DatatypeConverter.printBase64Binary(this.value);
|
||||
}
|
||||
|
||||
return encode(provider, new Binary(this.value), BinaryCodec::new);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link EncodableValue} for {@link Collection} containing only {@code byte[]} items to render to a BSON list
|
||||
* containing {@literal $binary}.
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
static class BinaryCollectionValue extends EncodableValue {
|
||||
|
||||
private final Collection<byte[]> value;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.repository.query.ExpressionEvaluatingParameterBinder.EncodableValue#encode(org.springframework.data.mongodb.CodecRegistryProvider, boolean)
|
||||
*/
|
||||
@Override
|
||||
public String encode(CodecRegistryProvider provider, boolean quoted) {
|
||||
return encodeCollection(provider, this.value, Binary::new, BinaryCodec::new);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link EncodableValue} for {@link UUID} to render to {@literal $binary}.
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
static class UuidValue extends EncodableValue {
|
||||
|
||||
private final UUID value;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.repository.query.ExpressionEvaluatingParameterBinder.EncodableValue#encode(org.springframework.data.mongodb.CodecRegistryProvider, boolean)
|
||||
*/
|
||||
@Override
|
||||
public String encode(CodecRegistryProvider provider, boolean quoted) {
|
||||
|
||||
if (quoted) {
|
||||
return this.value.toString();
|
||||
}
|
||||
|
||||
return encode(provider, this.value, UuidCodec::new);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link EncodableValue} for {@link Collection} containing only {@link UUID} items to render to a BSON list
|
||||
* containing {@literal $binary}.
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
static class UuidCollection extends EncodableValue {
|
||||
|
||||
private final Collection<UUID> value;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.repository.query.ExpressionEvaluatingParameterBinder.EncodableValue#encode(org.springframework.data.mongodb.CodecRegistryProvider, boolean)
|
||||
*/
|
||||
@Override
|
||||
public String encode(CodecRegistryProvider provider, boolean quoted) {
|
||||
return encodeCollection(provider, this.value, Function.identity(), UuidCodec::new);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fallback-{@link EncodableValue} for {@link Object}-typed values.
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
static class ObjectValue extends EncodableValue {
|
||||
|
||||
private final @Nullable Object value;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.repository.query.ExpressionEvaluatingParameterBinder.EncodableValue#encode(org.springframework.data.mongodb.CodecRegistryProvider, boolean)
|
||||
*/
|
||||
@Override
|
||||
public String encode(CodecRegistryProvider provider, boolean quoted) {
|
||||
return JSON.serialize(this.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@ public interface MongoParameterAccessor extends ParameterAccessor {
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Nullable
|
||||
Point getGeoNearLocation();
|
||||
|
||||
/**
|
||||
|
||||
@@ -19,6 +19,7 @@ import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.domain.Range;
|
||||
import org.springframework.data.domain.Range.Bound;
|
||||
import org.springframework.data.geo.Distance;
|
||||
import org.springframework.data.geo.Point;
|
||||
import org.springframework.data.mongodb.core.query.Term;
|
||||
@@ -66,9 +67,9 @@ public class MongoParametersParameterAccessor extends ParametersParameterAccesso
|
||||
}
|
||||
|
||||
int maxDistanceIndex = mongoParameters.getMaxDistanceIndex();
|
||||
Distance maxDistance = maxDistanceIndex == -1 ? null : (Distance) getValue(maxDistanceIndex);
|
||||
Bound<Distance> maxDistance = maxDistanceIndex == -1 ? Bound.unbounded() : Bound.inclusive((Distance) getValue(maxDistanceIndex));
|
||||
|
||||
return new Range<Distance>(null, maxDistance);
|
||||
return Range.of(Bound.unbounded(), maxDistance);
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -21,6 +21,7 @@ import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.Optional;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -113,9 +114,8 @@ class MongoQueryCreator extends AbstractQueryCreator<Query, Criteria> {
|
||||
|
||||
PersistentPropertyPath<MongoPersistentProperty> path = context.getPersistentPropertyPath(part.getProperty());
|
||||
MongoPersistentProperty property = path.getLeafProperty();
|
||||
Criteria criteria = from(part, property, where(path.toDotPath()), (PotentiallyConvertingIterator) iterator);
|
||||
|
||||
return criteria;
|
||||
return from(part, property, where(path.toDotPath()), iterator);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -132,7 +132,7 @@ class MongoQueryCreator extends AbstractQueryCreator<Query, Criteria> {
|
||||
PersistentPropertyPath<MongoPersistentProperty> path = context.getPersistentPropertyPath(part.getProperty());
|
||||
MongoPersistentProperty property = path.getLeafProperty();
|
||||
|
||||
return from(part, property, base.and(path.toDotPath()), (PotentiallyConvertingIterator) iterator);
|
||||
return from(part, property, base.and(path.toDotPath()), iterator);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -206,7 +206,9 @@ class MongoQueryCreator extends AbstractQueryCreator<Query, Criteria> {
|
||||
case NOT_CONTAINING:
|
||||
return createContainingCriteria(part, property, criteria.not(), parameters);
|
||||
case REGEX:
|
||||
return criteria.regex(parameters.next().toString());
|
||||
|
||||
Object param = parameters.next();
|
||||
return param instanceof Pattern ? criteria.regex((Pattern) param) : criteria.regex(param.toString());
|
||||
case EXISTS:
|
||||
return criteria.exists((Boolean) parameters.next());
|
||||
case TRUE:
|
||||
|
||||
@@ -16,13 +16,14 @@
|
||||
package org.springframework.data.mongodb.repository.query;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.data.geo.GeoPage;
|
||||
import org.springframework.data.geo.GeoResult;
|
||||
import org.springframework.data.geo.GeoResults;
|
||||
@@ -40,6 +41,7 @@ import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.ConcurrentReferenceHashMap;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
@@ -57,6 +59,7 @@ public class MongoQueryMethod extends QueryMethod {
|
||||
|
||||
private final Method method;
|
||||
private final MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext;
|
||||
private final Map<Class<? extends Annotation>, Optional<Annotation>> annotationCache;
|
||||
|
||||
private @Nullable MongoEntityMetadata<?> metadata;
|
||||
|
||||
@@ -77,6 +80,7 @@ public class MongoQueryMethod extends QueryMethod {
|
||||
|
||||
this.method = method;
|
||||
this.mappingContext = mappingContext;
|
||||
this.annotationCache = new ConcurrentReferenceHashMap<>();
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -110,9 +114,8 @@ public class MongoQueryMethod extends QueryMethod {
|
||||
|
||||
private Optional<String> findAnnotatedQuery() {
|
||||
|
||||
return Optional.ofNullable(getQueryAnnotation()) //
|
||||
.map(AnnotationUtils::getValue) //
|
||||
.map(it -> (String) it) //
|
||||
return lookupQueryAnnotation() //
|
||||
.map(Query::value) //
|
||||
.filter(StringUtils::hasText);
|
||||
}
|
||||
|
||||
@@ -121,10 +124,11 @@ public class MongoQueryMethod extends QueryMethod {
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Nullable
|
||||
String getFieldSpecification() {
|
||||
|
||||
return Optional.ofNullable(getQueryAnnotation()) //
|
||||
.map(it -> (String) AnnotationUtils.getValue(it, "fields")) //
|
||||
return lookupQueryAnnotation() //
|
||||
.map(Query::fields) //
|
||||
.filter(StringUtils::hasText) //
|
||||
.orElse(null);
|
||||
}
|
||||
@@ -156,8 +160,7 @@ public class MongoQueryMethod extends QueryMethod {
|
||||
MongoPersistentEntity<?> collectionEntity = domainClass.isAssignableFrom(returnedObjectType) ? returnedEntity
|
||||
: managedEntity;
|
||||
|
||||
this.metadata = new SimpleMongoEntityMetadata<Object>((Class<Object>) returnedEntity.getType(),
|
||||
collectionEntity);
|
||||
this.metadata = new SimpleMongoEntityMetadata<>((Class<Object>) returnedEntity.getType(), collectionEntity);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,7 +210,11 @@ public class MongoQueryMethod extends QueryMethod {
|
||||
*/
|
||||
@Nullable
|
||||
Query getQueryAnnotation() {
|
||||
return AnnotatedElementUtils.findMergedAnnotation(method, Query.class);
|
||||
return lookupQueryAnnotation().orElse(null);
|
||||
}
|
||||
|
||||
Optional<Query> lookupQueryAnnotation() {
|
||||
return doFindAnnotation(Query.class);
|
||||
}
|
||||
|
||||
TypeInformation<?> getReturnType() {
|
||||
@@ -230,7 +237,7 @@ public class MongoQueryMethod extends QueryMethod {
|
||||
*/
|
||||
@Nullable
|
||||
Meta getMetaAnnotation() {
|
||||
return AnnotatedElementUtils.findMergedAnnotation(method, Meta.class);
|
||||
return doFindAnnotation(Meta.class).orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -241,7 +248,7 @@ public class MongoQueryMethod extends QueryMethod {
|
||||
*/
|
||||
@Nullable
|
||||
Tailable getTailableAnnotation() {
|
||||
return AnnotatedElementUtils.findMergedAnnotation(method, Tailable.class);
|
||||
return doFindAnnotation(Tailable.class).orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -266,6 +273,10 @@ public class MongoQueryMethod extends QueryMethod {
|
||||
metaAttributes.setMaxScan(meta.maxScanDocuments());
|
||||
}
|
||||
|
||||
if (meta.cursorBatchSize() != 0) {
|
||||
metaAttributes.setCursorBatchSize(meta.cursorBatchSize());
|
||||
}
|
||||
|
||||
if (StringUtils.hasText(meta.comment())) {
|
||||
metaAttributes.setComment(meta.comment());
|
||||
}
|
||||
@@ -283,4 +294,35 @@ public class MongoQueryMethod extends QueryMethod {
|
||||
|
||||
return metaAttributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the query method is decorated with an non empty {@link Query#sort()}.
|
||||
*
|
||||
* @return true if method annotated with {@link Query} having an non empty sort attribute.
|
||||
* @since 2.1
|
||||
*/
|
||||
public boolean hasAnnotatedSort() {
|
||||
return lookupQueryAnnotation().map(it -> !it.sort().isEmpty()).orElse(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the sort value, used as default, extracted from the {@link Query} annotation.
|
||||
*
|
||||
* @return the {@link Query#sort()} value.
|
||||
* @throws IllegalStateException if method not annotated with {@link Query}. Make sure to check
|
||||
* {@link #hasAnnotatedQuery()} first.
|
||||
* @since 2.1
|
||||
*/
|
||||
public String getAnnotatedSort() {
|
||||
|
||||
return lookupQueryAnnotation().map(Query::sort).orElseThrow(() -> new IllegalStateException(
|
||||
"Expected to find @Query annotation but did not. Make sure to check hasAnnotatedSort() before."));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <A extends Annotation> Optional<A> doFindAnnotation(Class<A> annotationType) {
|
||||
|
||||
return (Optional<A>) this.annotationCache.computeIfAbsent(annotationType,
|
||||
it -> Optional.ofNullable(AnnotatedElementUtils.findMergedAnnotation(method, it)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright 2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.mongodb.repository.query;
|
||||
|
||||
import org.aopalliance.intercept.MethodInterceptor;
|
||||
import org.bson.Document;
|
||||
import org.springframework.aop.framework.ProxyFactory;
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
|
||||
/**
|
||||
* Internal utility class to help avoid duplicate code required in both the reactive and the sync {@link Query} support
|
||||
* offered by repositories.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.1
|
||||
* @currentRead Assassin's Apprentice - Robin Hobb
|
||||
*/
|
||||
class QueryUtils {
|
||||
|
||||
/**
|
||||
* Decorate {@link Query} and add a default sort expression to the given {@link Query}. Attributes of the given
|
||||
* {@code sort} may be overwritten by the sort explicitly defined by the {@link Query} itself.
|
||||
*
|
||||
* @param query the {@link Query} to decorate.
|
||||
* @param defaultSort the default sort expression to apply to the query.
|
||||
* @return the query having the given {@code sort} applied.
|
||||
*/
|
||||
static Query decorateSort(Query query, Document defaultSort) {
|
||||
|
||||
if (defaultSort.isEmpty()) {
|
||||
return query;
|
||||
}
|
||||
|
||||
ProxyFactory factory = new ProxyFactory(query);
|
||||
factory.addAdvice((MethodInterceptor) invocation -> {
|
||||
|
||||
if (!invocation.getMethod().getName().equals("getSortObject")) {
|
||||
return invocation.proceed();
|
||||
}
|
||||
|
||||
Document combinedSort = new Document(defaultSort);
|
||||
combinedSort.putAll((Document) invocation.proceed());
|
||||
return combinedSort;
|
||||
});
|
||||
|
||||
return (Query) factory.getProxy();
|
||||
}
|
||||
}
|
||||
@@ -51,23 +51,6 @@ interface ReactiveMongoQueryExecution {
|
||||
|
||||
Object execute(Query query, Class<?> type, String collection);
|
||||
|
||||
/**
|
||||
* {@link ReactiveMongoQueryExecution} for collection returning queries using tailable cursors.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
final class TailExecution implements ReactiveMongoQueryExecution {
|
||||
|
||||
private final @NonNull ReactiveMongoOperations operations;
|
||||
private final Pageable pageable;
|
||||
|
||||
@Override
|
||||
public Object execute(Query query, Class<?> type, String collection) {
|
||||
return operations.tail(query.with(pageable), type, collection);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link MongoQueryExecution} to execute geo-near queries.
|
||||
*
|
||||
|
||||
@@ -79,7 +79,7 @@ public class ReactiveMongoQueryMethod extends MongoQueryMethod {
|
||||
ClassUtils.getShortName(method.getDeclaringClass()), method.getName()));
|
||||
}
|
||||
|
||||
if (!multiWrapper && !singleWrapperWithWrappedPageableResult) {
|
||||
if (!multiWrapper) {
|
||||
throw new IllegalStateException(String.format(
|
||||
"Method has to use a either multi-item reactive wrapper return type or a wrapped Page/Slice type. Offending method: %s",
|
||||
method.toString()));
|
||||
|
||||
@@ -134,6 +134,15 @@ public class ReactivePartTreeMongoQuery extends AbstractReactiveMongoQuery {
|
||||
return tree.isCountProjection();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.repository.query.AbstractReactiveMongoQuery#isExistsQuery()
|
||||
*/
|
||||
@Override
|
||||
protected boolean isExistsQuery() {
|
||||
return tree.isExistsProjection();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.repository.query.AbstractReactiveMongoQuery#isDeleteQuery()
|
||||
|
||||
@@ -28,7 +28,6 @@ import org.springframework.data.mongodb.repository.query.ExpressionEvaluatingPar
|
||||
import org.springframework.data.mongodb.repository.query.StringBasedMongoQuery.ParameterBinding;
|
||||
import org.springframework.data.mongodb.repository.query.StringBasedMongoQuery.ParameterBindingParser;
|
||||
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;
|
||||
|
||||
@@ -41,13 +40,14 @@ import org.springframework.util.Assert;
|
||||
*/
|
||||
public class ReactiveStringBasedMongoQuery extends AbstractReactiveMongoQuery {
|
||||
|
||||
private static final String COUND_AND_DELETE = "Manually defined query for %s cannot be both a count and delete query at the same time!";
|
||||
private static final String COUNT_EXISTS_AND_DELETE = "Manually defined query for %s cannot be a count and exists or delete query at the same time!";
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ReactiveStringBasedMongoQuery.class);
|
||||
private static final ParameterBindingParser BINDING_PARSER = ParameterBindingParser.INSTANCE;
|
||||
|
||||
private final String query;
|
||||
private final String fieldSpec;
|
||||
private final boolean isCountQuery;
|
||||
private final boolean isExistsQuery;
|
||||
private final boolean isDeleteQuery;
|
||||
private final List<ParameterBinding> queryParameterBindings;
|
||||
private final List<ParameterBinding> fieldSpecParameterBindings;
|
||||
@@ -93,11 +93,23 @@ public class ReactiveStringBasedMongoQuery extends AbstractReactiveMongoQuery {
|
||||
this.fieldSpec = BINDING_PARSER.parseAndCollectParameterBindingsFromQueryIntoBindings(
|
||||
method.getFieldSpecification(), this.fieldSpecParameterBindings);
|
||||
|
||||
this.isCountQuery = method.hasAnnotatedQuery() ? method.getQueryAnnotation().count() : false;
|
||||
this.isDeleteQuery = method.hasAnnotatedQuery() ? method.getQueryAnnotation().delete() : false;
|
||||
if (method.hasAnnotatedQuery()) {
|
||||
|
||||
if (isCountQuery && isDeleteQuery) {
|
||||
throw new IllegalArgumentException(String.format(COUND_AND_DELETE, method));
|
||||
org.springframework.data.mongodb.repository.Query queryAnnotation = method.getQueryAnnotation();
|
||||
|
||||
this.isCountQuery = queryAnnotation.count();
|
||||
this.isExistsQuery = queryAnnotation.exists();
|
||||
this.isDeleteQuery = queryAnnotation.delete();
|
||||
|
||||
if (hasAmbiguousProjectionFlags(this.isCountQuery, this.isExistsQuery, this.isDeleteQuery)) {
|
||||
throw new IllegalArgumentException(String.format(COUNT_EXISTS_AND_DELETE, method));
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
this.isCountQuery = false;
|
||||
this.isExistsQuery = false;
|
||||
this.isDeleteQuery = false;
|
||||
}
|
||||
|
||||
this.parameterBinder = new ExpressionEvaluatingParameterBinder(expressionParser, evaluationContextProvider);
|
||||
@@ -133,6 +145,15 @@ public class ReactiveStringBasedMongoQuery extends AbstractReactiveMongoQuery {
|
||||
return isCountQuery;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.repository.query.AbstractReactiveMongoQuery#isExistsQuery()
|
||||
*/
|
||||
@Override
|
||||
protected boolean isExistsQuery() {
|
||||
return isExistsQuery;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.repository.query.AbstractReactiveMongoQuery#isDeleteQuery()
|
||||
@@ -151,4 +172,9 @@ public class ReactiveStringBasedMongoQuery extends AbstractReactiveMongoQuery {
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean hasAmbiguousProjectionFlags(boolean isCountQuery, boolean isExistsQuery,
|
||||
boolean isDeleteQuery) {
|
||||
return BooleanUtil.countBooleanTrueValues(isCountQuery, isExistsQuery, isDeleteQuery) > 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -20,7 +20,9 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.regex.PatternSyntaxException;
|
||||
|
||||
import org.bson.BSON;
|
||||
import org.bson.Document;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -37,6 +39,7 @@ import org.springframework.util.StringUtils;
|
||||
import com.mongodb.DBObject;
|
||||
import com.mongodb.DBRef;
|
||||
import com.mongodb.util.JSON;
|
||||
import com.mongodb.util.JSONCallback;
|
||||
|
||||
/**
|
||||
* Query to use a plain JSON String to create the {@link Query} to actually execute.
|
||||
@@ -170,11 +173,6 @@ public class StringBasedMongoQuery extends AbstractMongoQuery {
|
||||
return this.isDeleteQuery;
|
||||
}
|
||||
|
||||
private static boolean hasAmbiguousProjectionFlags(boolean isCountQuery, boolean isExistsQuery,
|
||||
boolean isDeleteQuery) {
|
||||
return countBooleanValues(isCountQuery, isExistsQuery, isDeleteQuery) > 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.repository.query.AbstractMongoQuery#isLimiting()
|
||||
@@ -184,18 +182,9 @@ public class StringBasedMongoQuery extends AbstractMongoQuery {
|
||||
return false;
|
||||
}
|
||||
|
||||
private static int countBooleanValues(boolean... values) {
|
||||
|
||||
int count = 0;
|
||||
|
||||
for (boolean value : values) {
|
||||
|
||||
if (value) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
private static boolean hasAmbiguousProjectionFlags(boolean isCountQuery, boolean isExistsQuery,
|
||||
boolean isDeleteQuery) {
|
||||
return BooleanUtil.countBooleanTrueValues(isCountQuery, isExistsQuery, isDeleteQuery) > 1;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -239,7 +228,8 @@ public class StringBasedMongoQuery extends AbstractMongoQuery {
|
||||
String transformedInput = transformQueryAndCollectExpressionParametersIntoBindings(input, bindings);
|
||||
String parseableInput = makeParameterReferencesParseable(transformedInput);
|
||||
|
||||
collectParameterReferencesIntoBindings(bindings, JSON.parse(parseableInput));
|
||||
collectParameterReferencesIntoBindings(bindings,
|
||||
JSON.parse(parseableInput, new LenientPatternDecodingCallback()));
|
||||
|
||||
return transformedInput;
|
||||
}
|
||||
@@ -374,6 +364,43 @@ public class StringBasedMongoQuery extends AbstractMongoQuery {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link JSONCallback} with lenient handling for {@link PatternSyntaxException} falling back to a placeholder
|
||||
* {@link Pattern} for intermediate query document rendering.
|
||||
*/
|
||||
private static class LenientPatternDecodingCallback extends JSONCallback {
|
||||
|
||||
private static final Pattern EMPTY_MARKER = Pattern.compile("__Spring_Data_MongoDB_Bind_Marker__");
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.mongodb.util.JSONCallback#objectDone()
|
||||
*/
|
||||
@Override
|
||||
public Object objectDone() {
|
||||
return exceptionSwallowingStackReducingObjectDone();
|
||||
}
|
||||
|
||||
private Object exceptionSwallowingStackReducingObjectDone/*CauseWeJustNeedTheStructureNotTheActualValue*/() {
|
||||
|
||||
Object value;
|
||||
|
||||
try {
|
||||
return super.objectDone();
|
||||
} catch (PatternSyntaxException e) {
|
||||
value = EMPTY_MARKER;
|
||||
}
|
||||
|
||||
if (!isStackEmpty()) {
|
||||
_put(curName(), value);
|
||||
} else {
|
||||
value = !BSON.hasDecodeHooks() ? value : BSON.applyDecodingHooks(value);
|
||||
setRoot(value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A generic parameter binding with name or position information.
|
||||
*
|
||||
|
||||
@@ -15,12 +15,10 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.repository.support;
|
||||
|
||||
import org.springframework.data.domain.Persistable;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
|
||||
import org.springframework.data.mongodb.repository.query.MongoEntityInformation;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* Support class responsible for creating {@link MongoEntityInformation} instances for a given
|
||||
@@ -47,10 +45,6 @@ final class MongoEntityInformationSupport {
|
||||
|
||||
Assert.notNull(entity, "Entity must not be null!");
|
||||
|
||||
MappingMongoEntityInformation<T, ID> entityInformation = new MappingMongoEntityInformation<T, ID>(
|
||||
(MongoPersistentEntity<T>) entity, (Class<ID>) idType);
|
||||
|
||||
return ClassUtils.isAssignable(Persistable.class, entity.getType())
|
||||
? new PersistableMongoEntityInformation<T, ID>(entityInformation) : entityInformation;
|
||||
return new MappingMongoEntityInformation<>((MongoPersistentEntity<T>) entity, (Class<ID>) idType);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,437 @@
|
||||
/*
|
||||
* 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.repository.support;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.bson.BsonJavaScript;
|
||||
import org.bson.BsonRegularExpression;
|
||||
import org.bson.Document;
|
||||
import org.bson.types.ObjectId;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.mongodb.DBRef;
|
||||
import com.querydsl.core.types.*;
|
||||
import com.querydsl.mongodb.MongodbOps;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Serializes the given Querydsl query to a Document query for MongoDB.
|
||||
* </p>
|
||||
* <p>
|
||||
* Original implementation source {@link com.querydsl.mongodb.MongodbSerializer} by {@literal The Querydsl Team}
|
||||
* (<a href="http://www.querydsl.com/team">http://www.querydsl.com/team</a>) licensed under the Apache License, Version
|
||||
* 2.0.
|
||||
* </p>
|
||||
* Modified to use {@link Document} instead of {@link com.mongodb.DBObject}, updated nullable types and code format. Use
|
||||
* Bson specific types and add {@link QuerydslMongoOps#NO_MATCH}.
|
||||
*
|
||||
* @author laimw
|
||||
* @author Mark Paluch
|
||||
* @author Christoph Strobl
|
||||
* @since 2.1
|
||||
*/
|
||||
abstract class MongodbDocumentSerializer implements Visitor<Object, Void> {
|
||||
|
||||
@Nullable
|
||||
Object handle(Expression<?> expression) {
|
||||
return expression.accept(this, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the MongoDB specific query document.
|
||||
*
|
||||
* @param predicate must not be {@literal null}.
|
||||
* @return empty {@link Document} by default.
|
||||
*/
|
||||
Document toQuery(Predicate predicate) {
|
||||
|
||||
Object value = handle(predicate);
|
||||
|
||||
if (value == null) {
|
||||
return new Document();
|
||||
}
|
||||
|
||||
Assert.isInstanceOf(Document.class, value,
|
||||
() -> String.format("Invalid type. Expected Document but found %s", value.getClass()));
|
||||
|
||||
return (Document) value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the MongoDB specific sort document.
|
||||
*
|
||||
* @param orderBys must not be {@literal null}.
|
||||
* @return empty {@link Document} by default.
|
||||
*/
|
||||
Document toSort(List<OrderSpecifier<?>> orderBys) {
|
||||
|
||||
Document sort = new Document();
|
||||
|
||||
orderBys.forEach(orderSpecifier -> {
|
||||
|
||||
Object key = orderSpecifier.getTarget().accept(this, null);
|
||||
|
||||
Assert.notNull(key, () -> String.format("Mapped sort key for %s must not be null!", orderSpecifier));
|
||||
sort.append(key.toString(), orderSpecifier.getOrder() == Order.ASC ? 1 : -1);
|
||||
});
|
||||
|
||||
return sort;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.querydsl.core.types.Visitor#visit(com.querydsl.core.types.Constant, java.lang.Void)
|
||||
*/
|
||||
@Override
|
||||
public Object visit(Constant<?> expr, Void context) {
|
||||
|
||||
if (!Enum.class.isAssignableFrom(expr.getType())) {
|
||||
return expr.getConstant();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked") // Guarded by previous check
|
||||
Constant<? extends Enum<?>> expectedExpr = (Constant<? extends Enum<?>>) expr;
|
||||
return expectedExpr.getConstant().name();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.querydsl.core.types.Visitor#visit(com.querydsl.core.types.TemplateExpression, java.lang.Void)
|
||||
*/
|
||||
@Override
|
||||
public Object visit(TemplateExpression<?> expr, Void context) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.querydsl.core.types.Visitor#visit(com.querydsl.core.types.FactoryExpression, java.lang.Void)
|
||||
*/
|
||||
@Override
|
||||
public Object visit(FactoryExpression<?> expr, Void context) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
protected String asDBKey(Operation<?> expr, int index) {
|
||||
|
||||
String key = (String) asDBValue(expr, index);
|
||||
|
||||
Assert.hasText(key, () -> String.format("Mapped key must not be null nor empty for expression %s.", expr));
|
||||
return key;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected Object asDBValue(Operation<?> expr, int index) {
|
||||
return expr.getArg(index).accept(this, null);
|
||||
}
|
||||
|
||||
private String regexValue(Operation<?> expr, int index) {
|
||||
|
||||
Object value = expr.getArg(index).accept(this, null);
|
||||
|
||||
Assert.notNull(value, () -> String.format("Regex for %s must not be null.", expr));
|
||||
return Pattern.quote(value.toString());
|
||||
}
|
||||
|
||||
protected Document asDocument(String key, @Nullable Object value) {
|
||||
return new Document(key, value);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Object visit(Operation<?> expr, Void context) {
|
||||
|
||||
Operator op = expr.getOperator();
|
||||
if (op == Ops.EQ) {
|
||||
|
||||
if (expr.getArg(0) instanceof Operation) {
|
||||
Operation<?> lhs = (Operation<?>) expr.getArg(0);
|
||||
if (lhs.getOperator() == Ops.COL_SIZE || lhs.getOperator() == Ops.ARRAY_SIZE) {
|
||||
return asDocument(asDBKey(lhs, 0), asDocument("$size", asDBValue(expr, 1)));
|
||||
} else {
|
||||
throw new UnsupportedOperationException("Illegal operation " + expr);
|
||||
}
|
||||
} else if (expr.getArg(0) instanceof Path) {
|
||||
Path<?> path = (Path<?>) expr.getArg(0);
|
||||
Constant<?> constant = (Constant<?>) expr.getArg(1);
|
||||
return asDocument(asDBKey(expr, 0), convert(path, constant));
|
||||
}
|
||||
} else if (op == Ops.STRING_IS_EMPTY) {
|
||||
return asDocument(asDBKey(expr, 0), "");
|
||||
} else if (op == Ops.AND) {
|
||||
|
||||
Map<Object, Object> lhs = (Map<Object, Object>) handle(expr.getArg(0));
|
||||
Map<Object, Object> rhs = (Map<Object, Object>) handle(expr.getArg(1));
|
||||
|
||||
LinkedHashSet<Entry<Object, Object>> lhs2 = new LinkedHashSet<>(lhs.entrySet());
|
||||
lhs2.retainAll(rhs.entrySet());
|
||||
|
||||
if (lhs2.isEmpty()) {
|
||||
lhs.putAll(rhs);
|
||||
return lhs;
|
||||
} else {
|
||||
List<Object> list = new ArrayList<>(2);
|
||||
list.add(handle(expr.getArg(0)));
|
||||
list.add(handle(expr.getArg(1)));
|
||||
return asDocument("$and", list);
|
||||
}
|
||||
|
||||
} else if (op == Ops.NOT) {
|
||||
// Handle the not's child
|
||||
Operation<?> subOperation = (Operation<?>) expr.getArg(0);
|
||||
Operator subOp = subOperation.getOperator();
|
||||
if (subOp == Ops.IN) {
|
||||
return visit(
|
||||
ExpressionUtils.operation(Boolean.class, Ops.NOT_IN, subOperation.getArg(0), subOperation.getArg(1)),
|
||||
context);
|
||||
} else {
|
||||
Document arg = (Document) handle(expr.getArg(0));
|
||||
return negate(arg);
|
||||
}
|
||||
|
||||
} else if (op == Ops.OR) {
|
||||
|
||||
List<Object> list = new ArrayList<>(2);
|
||||
list.add(handle(expr.getArg(0)));
|
||||
list.add(handle(expr.getArg(1)));
|
||||
return asDocument("$or", list);
|
||||
|
||||
} else if (op == Ops.NE) {
|
||||
|
||||
Path<?> path = (Path<?>) expr.getArg(0);
|
||||
Constant<?> constant = (Constant<?>) expr.getArg(1);
|
||||
return asDocument(asDBKey(expr, 0), asDocument("$ne", convert(path, constant)));
|
||||
|
||||
} else if (op == Ops.STARTS_WITH) {
|
||||
return asDocument(asDBKey(expr, 0), new BsonRegularExpression("^" + regexValue(expr, 1)));
|
||||
} else if (op == Ops.STARTS_WITH_IC) {
|
||||
return asDocument(asDBKey(expr, 0), new BsonRegularExpression("^" + regexValue(expr, 1), "i"));
|
||||
} else if (op == Ops.ENDS_WITH) {
|
||||
return asDocument(asDBKey(expr, 0), new BsonRegularExpression(regexValue(expr, 1) + "$"));
|
||||
} else if (op == Ops.ENDS_WITH_IC) {
|
||||
return asDocument(asDBKey(expr, 0), new BsonRegularExpression(regexValue(expr, 1) + "$", "i"));
|
||||
} else if (op == Ops.EQ_IGNORE_CASE) {
|
||||
return asDocument(asDBKey(expr, 0), new BsonRegularExpression("^" + regexValue(expr, 1) + "$", "i"));
|
||||
} else if (op == Ops.STRING_CONTAINS) {
|
||||
return asDocument(asDBKey(expr, 0), new BsonRegularExpression(".*" + regexValue(expr, 1) + ".*"));
|
||||
} else if (op == Ops.STRING_CONTAINS_IC) {
|
||||
return asDocument(asDBKey(expr, 0), new BsonRegularExpression(".*" + regexValue(expr, 1) + ".*", "i"));
|
||||
} else if (op == Ops.MATCHES) {
|
||||
return asDocument(asDBKey(expr, 0), new BsonRegularExpression(asDBValue(expr, 1).toString()));
|
||||
} else if (op == Ops.MATCHES_IC) {
|
||||
return asDocument(asDBKey(expr, 0), new BsonRegularExpression(asDBValue(expr, 1).toString(), "i"));
|
||||
} else if (op == Ops.LIKE) {
|
||||
|
||||
String regex = ExpressionUtils.likeToRegex((Expression) expr.getArg(1)).toString();
|
||||
return asDocument(asDBKey(expr, 0), new BsonRegularExpression(regex));
|
||||
} else if (op == Ops.BETWEEN) {
|
||||
|
||||
Document value = new Document("$gte", asDBValue(expr, 1));
|
||||
value.append("$lte", asDBValue(expr, 2));
|
||||
return asDocument(asDBKey(expr, 0), value);
|
||||
} else if (op == Ops.IN) {
|
||||
|
||||
int constIndex = 0;
|
||||
int exprIndex = 1;
|
||||
if (expr.getArg(1) instanceof Constant<?>) {
|
||||
constIndex = 1;
|
||||
exprIndex = 0;
|
||||
}
|
||||
if (Collection.class.isAssignableFrom(expr.getArg(constIndex).getType())) {
|
||||
@SuppressWarnings("unchecked") // guarded by previous check
|
||||
Collection<?> values = ((Constant<? extends Collection<?>>) expr.getArg(constIndex)).getConstant();
|
||||
return asDocument(asDBKey(expr, exprIndex), asDocument("$in", values));
|
||||
} else {
|
||||
Path<?> path = (Path<?>) expr.getArg(exprIndex);
|
||||
Constant<?> constant = (Constant<?>) expr.getArg(constIndex);
|
||||
return asDocument(asDBKey(expr, exprIndex), convert(path, constant));
|
||||
}
|
||||
} else if (op == Ops.NOT_IN) {
|
||||
|
||||
int constIndex = 0;
|
||||
int exprIndex = 1;
|
||||
if (expr.getArg(1) instanceof Constant<?>) {
|
||||
|
||||
constIndex = 1;
|
||||
exprIndex = 0;
|
||||
}
|
||||
if (Collection.class.isAssignableFrom(expr.getArg(constIndex).getType())) {
|
||||
|
||||
@SuppressWarnings("unchecked") // guarded by previous check
|
||||
Collection<?> values = ((Constant<? extends Collection<?>>) expr.getArg(constIndex)).getConstant();
|
||||
return asDocument(asDBKey(expr, exprIndex), asDocument("$nin", values));
|
||||
} else {
|
||||
|
||||
Path<?> path = (Path<?>) expr.getArg(exprIndex);
|
||||
Constant<?> constant = (Constant<?>) expr.getArg(constIndex);
|
||||
return asDocument(asDBKey(expr, exprIndex), asDocument("$ne", convert(path, constant)));
|
||||
}
|
||||
} else if (op == Ops.COL_IS_EMPTY) {
|
||||
|
||||
List<Object> list = new ArrayList<>(2);
|
||||
list.add(asDocument(asDBKey(expr, 0), new ArrayList<Object>()));
|
||||
list.add(asDocument(asDBKey(expr, 0), asDocument("$exists", false)));
|
||||
return asDocument("$or", list);
|
||||
} else if (op == Ops.LT) {
|
||||
return asDocument(asDBKey(expr, 0), asDocument("$lt", asDBValue(expr, 1)));
|
||||
} else if (op == Ops.GT) {
|
||||
return asDocument(asDBKey(expr, 0), asDocument("$gt", asDBValue(expr, 1)));
|
||||
} else if (op == Ops.LOE) {
|
||||
return asDocument(asDBKey(expr, 0), asDocument("$lte", asDBValue(expr, 1)));
|
||||
} else if (op == Ops.GOE) {
|
||||
return asDocument(asDBKey(expr, 0), asDocument("$gte", asDBValue(expr, 1)));
|
||||
} else if (op == Ops.IS_NULL) {
|
||||
return asDocument(asDBKey(expr, 0), asDocument("$exists", false));
|
||||
} else if (op == Ops.IS_NOT_NULL) {
|
||||
return asDocument(asDBKey(expr, 0), asDocument("$exists", true));
|
||||
} else if (op == Ops.CONTAINS_KEY) {
|
||||
|
||||
Path<?> path = (Path<?>) expr.getArg(0);
|
||||
Expression<?> key = expr.getArg(1);
|
||||
return asDocument(visit(path, context) + "." + key.toString(), asDocument("$exists", true));
|
||||
} else if (op == MongodbOps.NEAR) {
|
||||
return asDocument(asDBKey(expr, 0), asDocument("$near", asDBValue(expr, 1)));
|
||||
} else if (op == MongodbOps.NEAR_SPHERE) {
|
||||
return asDocument(asDBKey(expr, 0), asDocument("$nearSphere", asDBValue(expr, 1)));
|
||||
} else if (op == MongodbOps.ELEM_MATCH) {
|
||||
return asDocument(asDBKey(expr, 0), asDocument("$elemMatch", asDBValue(expr, 1)));
|
||||
} else if (op == QuerydslMongoOps.NO_MATCH) {
|
||||
return new Document("$where", new BsonJavaScript("function() { return false }"));
|
||||
}
|
||||
|
||||
throw new UnsupportedOperationException("Illegal operation " + expr);
|
||||
}
|
||||
|
||||
private Object negate(Document arg) {
|
||||
|
||||
List<Object> list = new ArrayList<>();
|
||||
for (Map.Entry<String, Object> entry : arg.entrySet()) {
|
||||
|
||||
if (entry.getKey().equals("$or")) {
|
||||
list.add(asDocument("$nor", entry.getValue()));
|
||||
} else if (entry.getKey().equals("$and")) {
|
||||
|
||||
List<Object> list2 = new ArrayList<>();
|
||||
for (Object o : ((Collection) entry.getValue())) {
|
||||
list2.add(negate((Document) o));
|
||||
}
|
||||
list.add(asDocument("$or", list2));
|
||||
} else if (entry.getValue() instanceof Pattern || entry.getValue() instanceof BsonRegularExpression) {
|
||||
list.add(asDocument(entry.getKey(), asDocument("$not", entry.getValue())));
|
||||
} else if (entry.getValue() instanceof Document) {
|
||||
list.add(negate(entry.getKey(), (Document) entry.getValue()));
|
||||
} else {
|
||||
list.add(asDocument(entry.getKey(), asDocument("$ne", entry.getValue())));
|
||||
}
|
||||
}
|
||||
return list.size() == 1 ? list.get(0) : asDocument("$or", list);
|
||||
}
|
||||
|
||||
private Object negate(String key, Document value) {
|
||||
|
||||
if (value.size() == 1) {
|
||||
return asDocument(key, asDocument("$not", value));
|
||||
} else {
|
||||
|
||||
List<Object> list2 = new ArrayList<>();
|
||||
for (Map.Entry<String, Object> entry2 : value.entrySet()) {
|
||||
list2.add(asDocument(key, asDocument("$not", asDocument(entry2.getKey(), entry2.getValue()))));
|
||||
}
|
||||
|
||||
return asDocument("$or", list2);
|
||||
}
|
||||
}
|
||||
|
||||
protected Object convert(Path<?> property, Constant<?> constant) {
|
||||
|
||||
if (isReference(property)) {
|
||||
return asReference(constant.getConstant());
|
||||
} else if (isId(property)) {
|
||||
|
||||
if (isReference(property.getMetadata().getParent())) {
|
||||
return asReferenceKey(property.getMetadata().getParent().getType(), constant.getConstant());
|
||||
} else if (constant.getType().equals(String.class) && isImplicitObjectIdConversion()) {
|
||||
|
||||
String id = (String) constant.getConstant();
|
||||
return ObjectId.isValid(id) ? new ObjectId(id) : id;
|
||||
}
|
||||
}
|
||||
return visit(constant, null);
|
||||
}
|
||||
|
||||
protected boolean isImplicitObjectIdConversion() {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected DBRef asReferenceKey(Class<?> entity, Object id) {
|
||||
// TODO override in subclass
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
protected abstract DBRef asReference(Object constant);
|
||||
|
||||
protected abstract boolean isReference(@Nullable Path<?> arg);
|
||||
|
||||
protected boolean isId(Path<?> arg) {
|
||||
// TODO override in subclass
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String visit(Path<?> expr, Void context) {
|
||||
|
||||
PathMetadata metadata = expr.getMetadata();
|
||||
|
||||
if (metadata.getParent() != null) {
|
||||
|
||||
Path<?> parent = metadata.getParent();
|
||||
if (parent.getMetadata().getPathType() == PathType.DELEGATE) {
|
||||
parent = parent.getMetadata().getParent();
|
||||
}
|
||||
if (metadata.getPathType() == PathType.COLLECTION_ANY) {
|
||||
return visit(parent, context);
|
||||
} else if (parent.getMetadata().getPathType() != PathType.VARIABLE) {
|
||||
|
||||
String rv = getKeyForPath(expr, metadata);
|
||||
String parentStr = visit(parent, context);
|
||||
return rv != null ? parentStr + "." + rv : parentStr;
|
||||
}
|
||||
}
|
||||
return getKeyForPath(expr, metadata);
|
||||
}
|
||||
|
||||
protected String getKeyForPath(Path<?> expr, PathMetadata metadata) {
|
||||
return metadata.getElement().toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object visit(SubQueryExpression<?> expr, Void context) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object visit(ParamExpression<?> expr, Void context) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017-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.repository.support;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import org.springframework.data.domain.Persistable;
|
||||
import org.springframework.data.mongodb.repository.query.MongoEntityInformation;
|
||||
|
||||
/**
|
||||
* {@link MongoEntityInformation} implementation wrapping an existing {@link MongoEntityInformation} considering
|
||||
* {@link Persistable} types by delegating {@link #isNew(Object)} and {@link #getId(Object)} to the corresponding
|
||||
* {@link Persistable#isNew()} and {@link Persistable#getId()} implementations.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author Oliver Gierke
|
||||
* @since 1.10
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
class PersistableMongoEntityInformation<T, ID> implements MongoEntityInformation<T, ID> {
|
||||
|
||||
private final @NonNull MongoEntityInformation<T, ID> delegate;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.repository.MongoEntityInformation#getCollectionName()
|
||||
*/
|
||||
@Override
|
||||
public String getCollectionName() {
|
||||
return delegate.getCollectionName();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.repository.MongoEntityInformation#getIdAttribute()
|
||||
*/
|
||||
@Override
|
||||
public String getIdAttribute() {
|
||||
return delegate.getIdAttribute();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.core.EntityInformation#isNew(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public boolean isNew(T t) {
|
||||
|
||||
if (t instanceof Persistable) {
|
||||
return ((Persistable<ID>) t).isNew();
|
||||
}
|
||||
|
||||
return delegate.isNew(t);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.core.EntityInformation#getId(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public ID getId(T t) {
|
||||
|
||||
if (t instanceof Persistable) {
|
||||
return ((Persistable<ID>) t).getId();
|
||||
}
|
||||
|
||||
return delegate.getId(t);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.core.support.PersistentEntityInformation#getIdType()
|
||||
*/
|
||||
@Override
|
||||
public Class<ID> getIdType() {
|
||||
return delegate.getIdType();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.core.support.EntityMetadata#getJavaType()
|
||||
*/
|
||||
@Override
|
||||
public Class<T> getJavaType() {
|
||||
return delegate.getJavaType();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
/*
|
||||
* 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.repository.support;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.bson.Document;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
import com.querydsl.core.DefaultQueryMetadata;
|
||||
import com.querydsl.core.QueryModifiers;
|
||||
import com.querydsl.core.SimpleQuery;
|
||||
import com.querydsl.core.support.QueryMixin;
|
||||
import com.querydsl.core.types.Expression;
|
||||
import com.querydsl.core.types.FactoryExpression;
|
||||
import com.querydsl.core.types.OrderSpecifier;
|
||||
import com.querydsl.core.types.ParamExpression;
|
||||
import com.querydsl.core.types.Predicate;
|
||||
|
||||
/**
|
||||
* {@code QuerydslAbstractMongodbQuery} provides a base class for general Querydsl query implementation.
|
||||
* <p>
|
||||
* Original implementation source {@link com.querydsl.mongodb.AbstractMongodbQuery} by {@literal The Querydsl Team}
|
||||
* (<a href="http://www.querydsl.com/team">http://www.querydsl.com/team</a>) licensed under the Apache License, Version
|
||||
* 2.0.
|
||||
* </p>
|
||||
* Modified for usage with {@link MongodbDocumentSerializer}.
|
||||
*
|
||||
* @param <Q> concrete subtype
|
||||
* @author laimw
|
||||
* @author Mark Paluch
|
||||
* @author Christoph Strobl
|
||||
* @since 2.1
|
||||
*/
|
||||
public abstract class QuerydslAbstractMongodbQuery<K, Q extends QuerydslAbstractMongodbQuery<K, Q>>
|
||||
implements SimpleQuery<Q> {
|
||||
|
||||
private final MongodbDocumentSerializer serializer;
|
||||
private final QueryMixin<Q> queryMixin;
|
||||
|
||||
/**
|
||||
* Create a new MongodbQuery instance
|
||||
*
|
||||
* @param serializer serializer
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
QuerydslAbstractMongodbQuery(MongodbDocumentSerializer serializer) {
|
||||
|
||||
this.queryMixin = new QueryMixin<>((Q) this, new DefaultQueryMetadata(), false);
|
||||
this.serializer = serializer;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.querydsl.core.SimpleQuery#distinct()
|
||||
*/
|
||||
@Override
|
||||
public Q distinct() {
|
||||
return queryMixin.distinct();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.querydsl.core.FilteredClause#where(com.querydsl.core.types.Predicate[])
|
||||
*/
|
||||
@Override
|
||||
public Q where(Predicate... e) {
|
||||
return queryMixin.where(e);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.querydsl.core.SimpleQuery#limit(long)
|
||||
*/
|
||||
@Override
|
||||
public Q limit(long limit) {
|
||||
return queryMixin.limit(limit);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.querydsl.core.SimpleQuery#offset()
|
||||
*/
|
||||
@Override
|
||||
public Q offset(long offset) {
|
||||
return queryMixin.offset(offset);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.querydsl.core.SimpleQuery#restrict(com.querydsl.core.QueryModifiers)
|
||||
*/
|
||||
@Override
|
||||
public Q restrict(QueryModifiers modifiers) {
|
||||
return queryMixin.restrict(modifiers);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.querydsl.core.SimpleQuery#orderBy(com.querydsl.core.types.OrderSpecifier)
|
||||
*/
|
||||
@Override
|
||||
public Q orderBy(OrderSpecifier<?>... o) {
|
||||
return queryMixin.orderBy(o);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.querydsl.core.SimpleQuery#set(com.querydsl.core.types.ParamExpression, Object)
|
||||
*/
|
||||
@Override
|
||||
public <T> Q set(ParamExpression<T> param, T value) {
|
||||
return queryMixin.set(param, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the actual projection {@link Document} from a given projectionExpression by serializing the contained
|
||||
* {@link Expression expressions} individually.
|
||||
*
|
||||
* @param projectionExpression the computed projection {@link Document}.
|
||||
* @return never {@literal null}. An empty {@link Document} by default.
|
||||
* @see MongodbDocumentSerializer#handle(Expression)
|
||||
*/
|
||||
protected Document createProjection(@Nullable Expression<?> projectionExpression) {
|
||||
|
||||
if (!(projectionExpression instanceof FactoryExpression)) {
|
||||
return new Document();
|
||||
}
|
||||
|
||||
Document projection = new Document();
|
||||
((FactoryExpression<?>) projectionExpression).getArgs().stream() //
|
||||
.filter(Expression.class::isInstance) //
|
||||
.map(Expression.class::cast) //
|
||||
.map(serializer::handle) //
|
||||
.forEach(it -> projection.append(it.toString(), 1));
|
||||
|
||||
return projection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the filer {@link Document} from the given {@link Predicate}.
|
||||
*
|
||||
* @param predicate can be {@literal null}.
|
||||
* @return an empty {@link Document} if predicate is {@literal null}.
|
||||
* @see MongodbDocumentSerializer#toQuery(Predicate)
|
||||
*/
|
||||
protected Document createQuery(@Nullable Predicate predicate) {
|
||||
|
||||
if (predicate == null) {
|
||||
return new Document();
|
||||
}
|
||||
|
||||
return serializer.toQuery(predicate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the sort {@link Document} from the given list of {@link OrderSpecifier order specifiers}.
|
||||
*
|
||||
* @param orderSpecifiers can be {@literal null}.
|
||||
* @return an empty {@link Document} if predicate is {@literal null}.
|
||||
* @see MongodbDocumentSerializer#toSort(List)
|
||||
*/
|
||||
protected Document createSort(List<OrderSpecifier<?>> orderSpecifiers) {
|
||||
return serializer.toSort(orderSpecifiers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the actual {@link QueryMixin} delegate.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
QueryMixin<Q> getQueryMixin() {
|
||||
return queryMixin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the where definition as a Document instance
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
Document asDocument() {
|
||||
return createQuery(queryMixin.getMetadata().getWhere());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return asDocument().toString();
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user