Compare commits
255 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
85b47d66f1 | ||
|
|
0d8fe46f3b | ||
|
|
0bc78f99dd | ||
|
|
e4f450f667 | ||
|
|
49cd44295c | ||
|
|
767d97a831 | ||
|
|
bbe8410979 | ||
|
|
c0a4bdb548 | ||
|
|
673a81af0e | ||
|
|
977032620e | ||
|
|
c7263e5b11 | ||
|
|
eed9b2470a | ||
|
|
c350be1f52 | ||
|
|
65b058ffd9 | ||
|
|
4a5789d67e | ||
|
|
7b05cfad94 | ||
|
|
a16a9fe1fe | ||
|
|
2f98a6656b | ||
|
|
9e2f6055a3 | ||
|
|
7f58538292 | ||
|
|
2f208d712c | ||
|
|
63d9875576 | ||
|
|
b7ffff4769 | ||
|
|
715ae26f3c | ||
|
|
00350edd32 | ||
|
|
38e1d0d92d | ||
|
|
8f00ffd291 | ||
|
|
0af8d6839e | ||
|
|
9b02897db5 | ||
|
|
99203b397a | ||
|
|
eda1c79315 | ||
|
|
e7150f525e | ||
|
|
7d6b5ae5fe | ||
|
|
d70e459ffe | ||
|
|
a26e780957 | ||
|
|
8fb0e1326b | ||
|
|
9014f770d8 | ||
|
|
f128e6df15 | ||
|
|
270456ed81 | ||
|
|
4e960a9682 | ||
|
|
061c28f84a | ||
|
|
cba7eaba4c | ||
|
|
ada7e199a4 | ||
|
|
977e5e4c5c | ||
|
|
c8307d5a39 | ||
|
|
dcf184888e | ||
|
|
59d0042d13 | ||
|
|
8af904b81f | ||
|
|
ffceed8da9 | ||
|
|
34d66a276a | ||
|
|
e71ec874ab | ||
|
|
f24e8e5361 | ||
|
|
bf86f39b2d | ||
|
|
f662d7ca0d | ||
|
|
62eb719b1e | ||
|
|
69b582823a | ||
|
|
297ef98239 | ||
|
|
f71f107445 | ||
|
|
36e2d80d71 | ||
|
|
467536cb34 | ||
|
|
302c8031f9 | ||
|
|
7c6e951c7c | ||
|
|
92cc2a582a | ||
|
|
24171b3ae2 | ||
|
|
456c1ad26a | ||
|
|
fc41793d5d | ||
|
|
afef243634 | ||
|
|
869b88702d | ||
|
|
aca403c112 | ||
|
|
df0372eee1 | ||
|
|
c4c6267d91 | ||
|
|
73d5886aae | ||
|
|
0db47169cf | ||
|
|
ec16b873b7 | ||
|
|
2a3a4cf030 | ||
|
|
df2b2a2f68 | ||
|
|
fd0a402c99 | ||
|
|
6bd0f758fe | ||
|
|
10c0203605 | ||
|
|
82b33331fc | ||
|
|
75b5a548b6 | ||
|
|
0c481feb72 | ||
|
|
c8a791d367 | ||
|
|
510028a834 | ||
|
|
1a86761e2e | ||
|
|
30da62181f | ||
|
|
a977b8a790 | ||
|
|
f3e067f59f | ||
|
|
dbfd4e5c62 | ||
|
|
c574e5cf8a | ||
|
|
f9f4c4621b | ||
|
|
23254c10dc | ||
|
|
255491c446 | ||
|
|
1d943d62a3 | ||
|
|
7538b1a1a5 | ||
|
|
828c074167 | ||
|
|
87ab1ac48c | ||
|
|
454afd9877 | ||
|
|
45971b212c | ||
|
|
68370c16fb | ||
|
|
d2c9b47366 | ||
|
|
4d7ee0e741 | ||
|
|
e7f3a2436d | ||
|
|
4ef1ff6aff | ||
|
|
b6ad32d7d4 | ||
|
|
e875f9ea33 | ||
|
|
9db9d16cf8 | ||
|
|
f00991dc29 | ||
|
|
bacbd7133e | ||
|
|
f38f6d67ab | ||
|
|
3f27e8e152 | ||
|
|
23177fef0c | ||
|
|
f3b90c2b8a | ||
|
|
d57c5a9529 | ||
|
|
986ea39f90 | ||
|
|
5bd7ff1413 | ||
|
|
93b9f23b07 | ||
|
|
42ab7d2f63 | ||
|
|
a6a2f0bde9 | ||
|
|
7d0b070d1f | ||
|
|
81bc3c599b | ||
|
|
403f0019d5 | ||
|
|
4f65bb0810 | ||
|
|
ef29e69a87 | ||
|
|
5cffb3c07c | ||
|
|
61d3a0bd1f | ||
|
|
82d67c1dbb | ||
|
|
85a30ec915 | ||
|
|
c70c29b2c7 | ||
|
|
2a5ae0da37 | ||
|
|
826015e9c1 | ||
|
|
9dda0a2f93 | ||
|
|
7dfe460433 | ||
|
|
73a0f04933 | ||
|
|
a1c165921d | ||
|
|
3872b379cd | ||
|
|
98fe043b95 | ||
|
|
c217618d9d | ||
|
|
b1020d19ba | ||
|
|
a481636429 | ||
|
|
efa9a2d408 | ||
|
|
149a703ecc | ||
|
|
2b715c54d3 | ||
|
|
ece261aadb | ||
|
|
dae0ac3b4d | ||
|
|
5ab75eb65a | ||
|
|
e96ef8e18f | ||
|
|
82af678cab | ||
|
|
6ed274bd9b | ||
|
|
48ac7e75ba | ||
|
|
a51c96298f | ||
|
|
f1354c4508 | ||
|
|
ff7588f648 | ||
|
|
124036fe36 | ||
|
|
80c5b536df | ||
|
|
2ee33b1444 | ||
|
|
eec6cea507 | ||
|
|
90d03d92d8 | ||
|
|
9a48e32565 | ||
|
|
ede6927b65 | ||
|
|
2edc29f758 | ||
|
|
5bd9bcca75 | ||
|
|
54f75e653b | ||
|
|
7b33f56e33 | ||
|
|
829eed7d6c | ||
|
|
5d3a3e1fe2 | ||
|
|
e71ec4fc41 | ||
|
|
f065e295e9 | ||
|
|
d23e1b3247 | ||
|
|
a1e8e6f3bc | ||
|
|
e4030197e8 | ||
|
|
2885c35511 | ||
|
|
a2a33390b6 | ||
|
|
bf606bcc47 | ||
|
|
e91809957d | ||
|
|
313a7b86e8 | ||
|
|
9d7473487f | ||
|
|
2734a7d8d4 | ||
|
|
0f85808531 | ||
|
|
7c0afda0a6 | ||
|
|
dbe17249b7 | ||
|
|
cfe0baadd1 | ||
|
|
8d0143ad76 | ||
|
|
43fee26950 | ||
|
|
70145a4c86 | ||
|
|
f7a90e93c5 | ||
|
|
3e78ce212a | ||
|
|
5a87dec2d5 | ||
|
|
f4556406bd | ||
|
|
af6d1eff0c | ||
|
|
191993caef | ||
|
|
dbed948c73 | ||
|
|
8f232e4983 | ||
|
|
e961d3c995 | ||
|
|
699d5f40f5 | ||
|
|
4b58ecc041 | ||
|
|
4b8fb812fa | ||
|
|
78137c882d | ||
|
|
8033b05cb7 | ||
|
|
a1a0675976 | ||
|
|
a88748d798 | ||
|
|
c2fc09e324 | ||
|
|
be8e70225a | ||
|
|
92079ca200 | ||
|
|
52b13ccf58 | ||
|
|
d73c2e3602 | ||
|
|
39e301d98d | ||
|
|
a7d865eb5e | ||
|
|
cde39008cf | ||
|
|
ffaa7cae6f | ||
|
|
2ef9844219 | ||
|
|
6f13837890 | ||
|
|
1ca2f5c3f1 | ||
|
|
a5e209379c | ||
|
|
bcb5628840 | ||
|
|
0b27635d67 | ||
|
|
198ebaa7d8 | ||
|
|
bebb1b6ce4 | ||
|
|
f59a38575a | ||
|
|
da70b34f13 | ||
|
|
f4f24ec1a7 | ||
|
|
f1365c5c55 | ||
|
|
71364255ca | ||
|
|
22952c3ef0 | ||
|
|
27b3b604c9 | ||
|
|
bce280d02b | ||
|
|
4b6ab894bf | ||
|
|
38c1d7fc37 | ||
|
|
43bef87966 | ||
|
|
c2498d41a1 | ||
|
|
c4182262ce | ||
|
|
3149c6e35b | ||
|
|
6d662461b8 | ||
|
|
597d6825f7 | ||
|
|
8b060455c2 | ||
|
|
2bf60ac827 | ||
|
|
f3d6f405c9 | ||
|
|
bd985a6589 | ||
|
|
c1417c4e4b | ||
|
|
36515abad4 | ||
|
|
cba9088b5e | ||
|
|
a33aece85d | ||
|
|
076d334b3c | ||
|
|
e644692a8a | ||
|
|
1d547ec150 | ||
|
|
ce3066dc59 | ||
|
|
c11dcd19ee | ||
|
|
1d60cd7e98 | ||
|
|
4700b4dda2 | ||
|
|
91f1dc1c6a | ||
|
|
70d87a9f71 | ||
|
|
427b468891 | ||
|
|
30ed3350c7 | ||
|
|
78e04f0b42 | ||
|
|
f311bdfbb4 |
2
.github/workflows/project.yml
vendored
2
.github/workflows/project.yml
vendored
@@ -36,7 +36,7 @@ jobs:
|
||||
token: ${{ secrets.GH_ISSUES_TOKEN_SPRING_DATA }}
|
||||
Feedback-Provided:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository_owner == 'spring-projects' && github.event.action == 'created' && contains(join(github.event.issue.labels.*.name, ', '), 'waiting-for-feedback')
|
||||
if: github.repository_owner == 'spring-projects' && github.event_name == 'issue_comment' && github.event.action == 'created' && github.actor != 'spring-projects-issues' && github.event.pull_request == null && github.event.issue.state == 'open' && contains(toJSON(github.event.issue.labels), 'waiting-for-feedback')
|
||||
steps:
|
||||
- name: Update Project Card
|
||||
uses: peter-evans/create-or-update-project-card@v1.1.2
|
||||
|
||||
3
.mvn/wrapper/maven-wrapper.properties
vendored
3
.mvn/wrapper/maven-wrapper.properties
vendored
@@ -1 +1,2 @@
|
||||
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.5.4/apache-maven-3.5.4-bin.zip
|
||||
#Mon Oct 11 14:30:24 CEST 2021
|
||||
distributionUrl=https\://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.3/apache-maven-3.8.3-bin.zip
|
||||
|
||||
2
CI.adoc
2
CI.adoc
@@ -1,6 +1,6 @@
|
||||
= Continuous Integration
|
||||
|
||||
image:https://jenkins.spring.io/buildStatus/icon?job=spring-data-mongodb%2Fmaster&subject=Moore%20(master)[link=https://jenkins.spring.io/view/SpringData/job/spring-data-mongodb/]
|
||||
image:https://jenkins.spring.io/buildStatus/icon?job=spring-data-mongodb%2Fmain&subject=Moore%20(main)[link=https://jenkins.spring.io/view/SpringData/job/spring-data-mongodb/]
|
||||
image:https://jenkins.spring.io/buildStatus/icon?job=spring-data-mongodb%2F2.1.x&subject=Lovelace%20(2.1.x)[link=https://jenkins.spring.io/view/SpringData/job/spring-data-mongodb/]
|
||||
image:https://jenkins.spring.io/buildStatus/icon?job=spring-data-mongodb%2F1.10.x&subject=Ingalls%20(1.10.x)[link=https://jenkins.spring.io/view/SpringData/job/spring-data-mongodb/]
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
= Spring Data contribution guidelines
|
||||
|
||||
You find the contribution guidelines for Spring Data projects https://github.com/spring-projects/spring-data-build/blob/master/CONTRIBUTING.adoc[here].
|
||||
You find the contribution guidelines for Spring Data projects https://github.com/spring-projects/spring-data-build/blob/main/CONTRIBUTING.adoc[here].
|
||||
|
||||
128
Jenkinsfile
vendored
128
Jenkinsfile
vendored
@@ -3,7 +3,7 @@ pipeline {
|
||||
|
||||
triggers {
|
||||
pollSCM 'H/10 * * * *'
|
||||
upstream(upstreamProjects: "spring-data-commons/master", threshold: hudson.model.Result.SUCCESS)
|
||||
upstream(upstreamProjects: "spring-data-commons/main", threshold: hudson.model.Result.SUCCESS)
|
||||
}
|
||||
|
||||
options {
|
||||
@@ -14,6 +14,22 @@ pipeline {
|
||||
stages {
|
||||
stage("Docker images") {
|
||||
parallel {
|
||||
stage('Publish JDK 8 + MongoDB 5.0') {
|
||||
when {
|
||||
changeset "ci/openjdk8-mongodb-5.0/**"
|
||||
}
|
||||
agent { label 'data' }
|
||||
options { timeout(time: 30, unit: 'MINUTES') }
|
||||
|
||||
steps {
|
||||
script {
|
||||
def image = docker.build("springci/spring-data-openjdk8-with-mongodb-5.0.0", "ci/openjdk8-mongodb-5.0/")
|
||||
docker.withRegistry('', 'hub.docker.com-springbuildmaster') {
|
||||
image.push()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
stage('Publish JDK 8 + MongoDB 4.0') {
|
||||
when {
|
||||
changeset "ci/openjdk8-mongodb-4.0/**"
|
||||
@@ -23,39 +39,39 @@ pipeline {
|
||||
|
||||
steps {
|
||||
script {
|
||||
def image = docker.build("springci/spring-data-openjdk8-with-mongodb-4.0", "ci/openjdk8-mongodb-4.0/")
|
||||
def image = docker.build("springci/spring-data-openjdk8-with-mongodb-4.0.23", "ci/openjdk8-mongodb-4.0/")
|
||||
docker.withRegistry('', 'hub.docker.com-springbuildmaster') {
|
||||
image.push()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
stage('Publish JDK 8 + MongoDB 4.2') {
|
||||
stage('Publish JDK 8 + MongoDB 4.4') {
|
||||
when {
|
||||
changeset "ci/openjdk8-mongodb-4.2/**"
|
||||
changeset "ci/openjdk8-mongodb-4.4/**"
|
||||
}
|
||||
agent { label 'data' }
|
||||
options { timeout(time: 30, unit: 'MINUTES') }
|
||||
|
||||
steps {
|
||||
script {
|
||||
def image = docker.build("springci/spring-data-openjdk8-with-mongodb-4.2.0", "ci/openjdk8-mongodb-4.2/")
|
||||
def image = docker.build("springci/spring-data-openjdk8-with-mongodb-4.4.4", "ci/openjdk8-mongodb-4.4/")
|
||||
docker.withRegistry('', 'hub.docker.com-springbuildmaster') {
|
||||
image.push()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
stage('Publish JDK 15 + MongoDB 4.2') {
|
||||
stage('Publish JDK 16 + MongoDB 4.4') {
|
||||
when {
|
||||
changeset "ci/openjdk15-mongodb-4.2/**"
|
||||
changeset "ci/openjdk16-mongodb-4.4/**"
|
||||
}
|
||||
agent { label 'data' }
|
||||
options { timeout(time: 30, unit: 'MINUTES') }
|
||||
|
||||
steps {
|
||||
script {
|
||||
def image = docker.build("springci/spring-data-openjdk15-with-mongodb-4.2.0", "ci/openjdk15-mongodb-4.2/")
|
||||
def image = docker.build("springci/spring-data-openjdk16-with-mongodb-4.4.4", "ci/openjdk16-mongodb-4.4/")
|
||||
docker.withRegistry('', 'hub.docker.com-springbuildmaster') {
|
||||
image.push()
|
||||
}
|
||||
@@ -67,8 +83,9 @@ pipeline {
|
||||
|
||||
stage("test: baseline (jdk8)") {
|
||||
when {
|
||||
beforeAgent(true)
|
||||
anyOf {
|
||||
branch 'master'
|
||||
branch(pattern: "main|(\\d\\.\\d\\.x)", comparator: "REGEXP")
|
||||
not { triggeredBy 'UpstreamCause' }
|
||||
}
|
||||
}
|
||||
@@ -76,16 +93,19 @@ pipeline {
|
||||
label 'data'
|
||||
}
|
||||
options { timeout(time: 30, unit: 'MINUTES') }
|
||||
environment {
|
||||
ARTIFACTORY = credentials('02bd1690-b54f-4c9f-819d-a77cb7a9822c')
|
||||
}
|
||||
steps {
|
||||
script {
|
||||
docker.withRegistry('', 'hub.docker.com-springbuildmaster') {
|
||||
docker.image('springci/spring-data-openjdk8-with-mongodb-4.2.0:latest').inside('-v $HOME:/tmp/jenkins-home') {
|
||||
docker.image('springci/spring-data-openjdk8-with-mongodb-4.0.23:latest').inside('-v $HOME:/tmp/jenkins-home') {
|
||||
sh 'mkdir -p /tmp/mongodb/db /tmp/mongodb/log'
|
||||
sh 'mongod --setParameter transactionLifetimeLimitSeconds=90 --setParameter maxTransactionLockRequestTimeoutMillis=10000 --dbpath /tmp/mongodb/db --replSet rs0 --fork --logpath /tmp/mongodb/log/mongod.log &'
|
||||
sh 'sleep 10'
|
||||
sh 'mongo --eval "rs.initiate({_id: \'rs0\', members:[{_id: 0, host: \'127.0.0.1:27017\'}]});"'
|
||||
sh 'sleep 15'
|
||||
sh 'MAVEN_OPTS="-Duser.name=jenkins -Duser.home=/tmp/jenkins-home" ./mvnw clean dependency:list test -Duser.name=jenkins -Dsort -U -B'
|
||||
sh 'MAVEN_OPTS="-Duser.name=jenkins -Duser.home=/tmp/jenkins-home" ./mvnw -s settings.xml clean dependency:list test -Duser.name=jenkins -Dsort -U -B'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -94,8 +114,9 @@ pipeline {
|
||||
|
||||
stage("Test other configurations") {
|
||||
when {
|
||||
beforeAgent(true)
|
||||
allOf {
|
||||
branch 'master'
|
||||
branch(pattern: "main|(\\d\\.\\d\\.x)", comparator: "REGEXP")
|
||||
not { triggeredBy 'UpstreamCause' }
|
||||
}
|
||||
}
|
||||
@@ -105,58 +126,91 @@ pipeline {
|
||||
label 'data'
|
||||
}
|
||||
options { timeout(time: 30, unit: 'MINUTES') }
|
||||
environment {
|
||||
ARTIFACTORY = credentials('02bd1690-b54f-4c9f-819d-a77cb7a9822c')
|
||||
}
|
||||
steps {
|
||||
script {
|
||||
docker.withRegistry('', 'hub.docker.com-springbuildmaster') {
|
||||
docker.image('springci/spring-data-openjdk8-with-mongodb-4.0:latest').inside('-v $HOME:/tmp/jenkins-home') {
|
||||
docker.image('springci/spring-data-openjdk8-with-mongodb-4.0.23:latest').inside('-v $HOME:/tmp/jenkins-home') {
|
||||
sh 'mkdir -p /tmp/mongodb/db /tmp/mongodb/log'
|
||||
sh 'mongod --setParameter transactionLifetimeLimitSeconds=90 --setParameter maxTransactionLockRequestTimeoutMillis=10000 --dbpath /tmp/mongodb/db --replSet rs0 --fork --logpath /tmp/mongodb/log/mongod.log &'
|
||||
sh 'sleep 10'
|
||||
sh 'mongo --eval "rs.initiate({_id: \'rs0\', members:[{_id: 0, host: \'127.0.0.1:27017\'}]});"'
|
||||
sh 'sleep 15'
|
||||
sh 'MAVEN_OPTS="-Duser.name=jenkins -Duser.home=/tmp/jenkins-home" ./mvnw clean dependency:list test -Duser.name=jenkins -Dsort -U -B'
|
||||
sh 'MAVEN_OPTS="-Duser.name=jenkins -Duser.home=/tmp/jenkins-home" ./mvnw -s settings.xml clean dependency:list test -Duser.name=jenkins -Dsort -U -B'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage("test: mongodb 4.2 (jdk8)") {
|
||||
stage("test: mongodb 4.4 (jdk8)") {
|
||||
agent {
|
||||
label 'data'
|
||||
}
|
||||
options { timeout(time: 30, unit: 'MINUTES') }
|
||||
environment {
|
||||
ARTIFACTORY = credentials('02bd1690-b54f-4c9f-819d-a77cb7a9822c')
|
||||
}
|
||||
steps {
|
||||
script {
|
||||
docker.withRegistry('', 'hub.docker.com-springbuildmaster') {
|
||||
docker.image('springci/spring-data-openjdk8-with-mongodb-4.2.0:latest').inside('-v $HOME:/tmp/jenkins-home') {
|
||||
docker.image('springci/spring-data-openjdk8-with-mongodb-4.4.4:latest').inside('-v $HOME:/tmp/jenkins-home') {
|
||||
sh 'mkdir -p /tmp/mongodb/db /tmp/mongodb/log'
|
||||
sh 'mongod --setParameter transactionLifetimeLimitSeconds=90 --setParameter maxTransactionLockRequestTimeoutMillis=10000 --dbpath /tmp/mongodb/db --replSet rs0 --fork --logpath /tmp/mongodb/log/mongod.log &'
|
||||
sh 'sleep 10'
|
||||
sh 'mongo --eval "rs.initiate({_id: \'rs0\', members:[{_id: 0, host: \'127.0.0.1:27017\'}]});"'
|
||||
sh 'sleep 15'
|
||||
sh 'MAVEN_OPTS="-Duser.name=jenkins -Duser.home=/tmp/jenkins-home" ./mvnw clean dependency:list test -Duser.name=jenkins -Dsort -U -B'
|
||||
sh 'MAVEN_OPTS="-Duser.name=jenkins -Duser.home=/tmp/jenkins-home" ./mvnw -s settings.xml clean dependency:list test -Duser.name=jenkins -Dsort -U -B'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage("test: baseline (jdk15)") {
|
||||
stage("test: mongodb 5.0 (jdk8)") {
|
||||
agent {
|
||||
label 'data'
|
||||
}
|
||||
options { timeout(time: 30, unit: 'MINUTES') }
|
||||
environment {
|
||||
ARTIFACTORY = credentials('02bd1690-b54f-4c9f-819d-a77cb7a9822c')
|
||||
}
|
||||
steps {
|
||||
script {
|
||||
docker.withRegistry('', 'hub.docker.com-springbuildmaster') {
|
||||
docker.image('springci/spring-data-openjdk8-with-mongodb-5.0.0:latest').inside('-v $HOME:/tmp/jenkins-home') {
|
||||
sh 'mkdir -p /tmp/mongodb/db /tmp/mongodb/log'
|
||||
sh 'mongod --setParameter transactionLifetimeLimitSeconds=90 --setParameter maxTransactionLockRequestTimeoutMillis=10000 --dbpath /tmp/mongodb/db --replSet rs0 --fork --logpath /tmp/mongodb/log/mongod.log &'
|
||||
sh 'sleep 10'
|
||||
sh 'mongo --eval "rs.initiate({_id: \'rs0\', members:[{_id: 0, host: \'127.0.0.1:27017\'}]});"'
|
||||
sh 'sleep 15'
|
||||
sh 'MAVEN_OPTS="-Duser.name=jenkins -Duser.home=/tmp/jenkins-home" ./mvnw -s settings.xml clean dependency:list test -Duser.name=jenkins -Dsort -U -B'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage("test: baseline (jdk16)") {
|
||||
agent {
|
||||
label 'data'
|
||||
}
|
||||
options { timeout(time: 30, unit: 'MINUTES') }
|
||||
environment {
|
||||
ARTIFACTORY = credentials('02bd1690-b54f-4c9f-819d-a77cb7a9822c')
|
||||
}
|
||||
steps {
|
||||
script {
|
||||
docker.withRegistry('', 'hub.docker.com-springbuildmaster') {
|
||||
docker.image('springci/spring-data-openjdk15-with-mongodb-4.2.0:latest').inside('-v $HOME:/tmp/jenkins-home') {
|
||||
docker.image('springci/spring-data-openjdk16-with-mongodb-4.4.4:latest').inside('-v $HOME:/tmp/jenkins-home') {
|
||||
sh 'mkdir -p /tmp/mongodb/db /tmp/mongodb/log'
|
||||
sh 'mongod --setParameter transactionLifetimeLimitSeconds=90 --setParameter maxTransactionLockRequestTimeoutMillis=10000 --dbpath /tmp/mongodb/db --replSet rs0 --fork --logpath /tmp/mongodb/log/mongod.log &'
|
||||
sh 'sleep 10'
|
||||
sh 'mongo --eval "rs.initiate({_id: \'rs0\', members:[{_id: 0, host: \'127.0.0.1:27017\'}]});"'
|
||||
sh 'sleep 15'
|
||||
sh 'MAVEN_OPTS="-Duser.name=jenkins -Duser.home=/tmp/jenkins-home" ./mvnw -Pjava11 clean dependency:list test -Duser.name=jenkins -Dsort -U -B'
|
||||
sh 'MAVEN_OPTS="-Duser.name=jenkins -Duser.home=/tmp/jenkins-home" ./mvnw -s settings.xml -Pjava11 clean dependency:list test -Duser.name=jenkins -Dsort -U -B'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -167,8 +221,9 @@ pipeline {
|
||||
|
||||
stage('Release to artifactory') {
|
||||
when {
|
||||
beforeAgent(true)
|
||||
anyOf {
|
||||
branch 'master'
|
||||
branch(pattern: "main|(\\d\\.\\d\\.x)", comparator: "REGEXP")
|
||||
not { triggeredBy 'UpstreamCause' }
|
||||
}
|
||||
}
|
||||
@@ -185,7 +240,7 @@ pipeline {
|
||||
script {
|
||||
docker.withRegistry('', 'hub.docker.com-springbuildmaster') {
|
||||
docker.image('adoptopenjdk/openjdk8:latest').inside('-v $HOME:/tmp/jenkins-home') {
|
||||
sh 'MAVEN_OPTS="-Duser.name=jenkins -Duser.home=/tmp/jenkins-home" ./mvnw -Pci,artifactory ' +
|
||||
sh 'MAVEN_OPTS="-Duser.name=jenkins -Duser.home=/tmp/jenkins-home" ./mvnw -s settings.xml -Pci,artifactory ' +
|
||||
'-Dartifactory.server=https://repo.spring.io ' +
|
||||
"-Dartifactory.username=${ARTIFACTORY_USR} " +
|
||||
"-Dartifactory.password=${ARTIFACTORY_PSW} " +
|
||||
@@ -198,35 +253,6 @@ pipeline {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage('Publish documentation') {
|
||||
when {
|
||||
branch 'master'
|
||||
}
|
||||
agent {
|
||||
label 'data'
|
||||
}
|
||||
options { timeout(time: 20, unit: 'MINUTES') }
|
||||
|
||||
environment {
|
||||
ARTIFACTORY = credentials('02bd1690-b54f-4c9f-819d-a77cb7a9822c')
|
||||
}
|
||||
|
||||
steps {
|
||||
script {
|
||||
docker.withRegistry('', 'hub.docker.com-springbuildmaster') {
|
||||
docker.image('adoptopenjdk/openjdk8:latest').inside('-v $HOME:/tmp/jenkins-home') {
|
||||
sh 'MAVEN_OPTS="-Duser.name=jenkins -Duser.home=/tmp/jenkins-home" ./mvnw -Pci,distribute ' +
|
||||
'-Dartifactory.server=https://repo.spring.io ' +
|
||||
"-Dartifactory.username=${ARTIFACTORY_USR} " +
|
||||
"-Dartifactory.password=${ARTIFACTORY_PSW} " +
|
||||
"-Dartifactory.distribution-repository=temp-private-local " +
|
||||
'-Dmaven.test.skip=true clean deploy -U -B'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
post {
|
||||
|
||||
202
LICENSE.txt
Normal file
202
LICENSE.txt
Normal file
@@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
https://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright {yyyy} {name of copyright owner}
|
||||
|
||||
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
|
||||
|
||||
https://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.
|
||||
122
README.adoc
122
README.adoc
@@ -1,6 +1,6 @@
|
||||
image:https://spring.io/badges/spring-data-mongodb/ga.svg[Spring Data MongoDB,link=https://projects.spring.io/spring-data-mongodb#quick-start] image:https://spring.io/badges/spring-data-mongodb/snapshot.svg[Spring Data MongoDB,link=https://projects.spring.io/spring-data-mongodb#quick-start]
|
||||
|
||||
= Spring Data MongoDB image:https://jenkins.spring.io/buildStatus/icon?job=spring-data-mongodb%2Fmaster&subject=Build[link=https://jenkins.spring.io/view/SpringData/job/spring-data-mongodb/] https://gitter.im/spring-projects/spring-data[image:https://badges.gitter.im/spring-projects/spring-data.svg[Gitter]]
|
||||
= Spring Data MongoDB image:https://jenkins.spring.io/buildStatus/icon?job=spring-data-mongodb%2Fmain&subject=Build[link=https://jenkins.spring.io/view/SpringData/job/spring-data-mongodb/] https://gitter.im/spring-projects/spring-data[image:https://badges.gitter.im/spring-projects/spring-data.svg[Gitter]]
|
||||
|
||||
The primary goal of the https://projects.spring.io/spring-data[Spring Data] project is to make it easier to build Spring-powered applications that use new data access technologies such as non-relational databases, map-reduce frameworks, and cloud based data services.
|
||||
|
||||
@@ -8,10 +8,12 @@ The Spring Data MongoDB project aims to provide a familiar and consistent Spring
|
||||
The Spring Data MongoDB project provides integration with the MongoDB document database.
|
||||
Key functional areas of Spring Data MongoDB are a POJO centric model for interacting with a MongoDB `+Document+` and easily writing a repository style data access layer.
|
||||
|
||||
[[code-of-conduct]]
|
||||
== Code of Conduct
|
||||
|
||||
This project is governed by the https://github.com/spring-projects/.github/blob/e3cc2ff230d8f1dca06535aa6b5a4a23815861d4/CODE_OF_CONDUCT.md[Spring Code of Conduct]. By participating, you are expected to uphold this code of conduct. Please report unacceptable behavior to spring-code-of-conduct@pivotal.io.
|
||||
|
||||
[[getting-started]]
|
||||
== Getting Started
|
||||
|
||||
Here is a quick teaser of an application using Spring Data Repositories in Java:
|
||||
@@ -59,6 +61,7 @@ class ApplicationConfig extends AbstractMongoClientConfiguration {
|
||||
}
|
||||
----
|
||||
|
||||
[[maven-configuration]]
|
||||
=== Maven configuration
|
||||
|
||||
Add the Maven dependency:
|
||||
@@ -68,24 +71,25 @@ Add the Maven dependency:
|
||||
<dependency>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-mongodb</artifactId>
|
||||
<version>${version}.RELEASE</version>
|
||||
<version>${version}</version>
|
||||
</dependency>
|
||||
----
|
||||
|
||||
If you'd rather like the latest snapshots of the upcoming major version, use our Maven snapshot repository and declare the appropriate dependency version.
|
||||
If you'd rather like the latest snapshots of the upcoming major version, use our Maven snapshot repository
|
||||
and declare the appropriate dependency version.
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<dependency>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-mongodb</artifactId>
|
||||
<version>${version}.BUILD-SNAPSHOT</version>
|
||||
<version>${version}-SNAPSHOT</version>
|
||||
</dependency>
|
||||
|
||||
<repository>
|
||||
<id>spring-libs-snapshot</id>
|
||||
<id>spring-snapshot</id>
|
||||
<name>Spring Snapshot Repository</name>
|
||||
<url>https://repo.spring.io/libs-snapshot</url>
|
||||
<url>https://repo.spring.io/snapshot</url>
|
||||
</repository>
|
||||
----
|
||||
|
||||
@@ -98,7 +102,7 @@ Some of the changes affect the initial setup configuration as well as compile/ru
|
||||
|
||||
.Changed XML Namespace Elements and Attributes:
|
||||
|===
|
||||
Element / Attribute | 2.x | 3.x
|
||||
| Element / Attribute | 2.x | 3.x
|
||||
|
||||
| `<mongo:mongo-client />`
|
||||
| Used to create a `com.mongodb.MongoClient`
|
||||
@@ -116,7 +120,7 @@ Use `<mongo:client-settings cluster-hosts="..." />` instead
|
||||
|
||||
.Removed XML Namespace Elements and Attributes:
|
||||
|===
|
||||
Element / Attribute | Replacement in 3.x | Comment
|
||||
| Element / Attribute | Replacement in 3.x | Comment
|
||||
|
||||
| `<mongo:db-factory mongo-ref="..." />`
|
||||
| `<mongo:db-factory mongo-client-ref="..." />`
|
||||
@@ -133,7 +137,7 @@ Element / Attribute | Replacement in 3.x | Comment
|
||||
|
||||
.New XML Namespace Elements and Attributes:
|
||||
|===
|
||||
Element | Comment
|
||||
| Element | Comment
|
||||
|
||||
| `<mongo:db-factory mongo-client-ref="..." />`
|
||||
| Replacement for `<mongo:db-factory mongo-ref="..." />`
|
||||
@@ -153,7 +157,7 @@ Element | Comment
|
||||
|
||||
.Java API changes
|
||||
|===
|
||||
Type | Comment
|
||||
| Type | Comment
|
||||
|
||||
| `MongoClientFactoryBean`
|
||||
| Creates `com.mongodb.client.MongoClient` instead of `com.mongodb.MongoClient` +
|
||||
@@ -174,7 +178,7 @@ Uses `MongoClientSettings` instead of `MongoClientOptions`.
|
||||
|
||||
.Removed Java API:
|
||||
|===
|
||||
2.x | Replacement in 3.x | Comment
|
||||
| 2.x | Replacement in 3.x | Comment
|
||||
|
||||
| `MongoClientOptionsFactoryBean`
|
||||
| `MongoClientSettingsFactoryBean`
|
||||
@@ -226,6 +230,7 @@ static class Config extends AbstractMongoClientConfiguration {
|
||||
----
|
||||
====
|
||||
|
||||
[[getting-help]]
|
||||
== Getting Help
|
||||
|
||||
Having trouble with Spring Data? We’d love to help!
|
||||
@@ -239,6 +244,7 @@ If you are just starting out with Spring, try one of the https://spring.io/guide
|
||||
You can also chat with the community on https://gitter.im/spring-projects/spring-data[Gitter].
|
||||
* Report bugs with Spring Data MongoDB at https://github.com/spring-projects/spring-data-mongodb/issues[github.com/spring-projects/spring-data-mongodb/issues].
|
||||
|
||||
[[reporting-issues]]
|
||||
== Reporting Issues
|
||||
|
||||
Spring Data uses Github as issue tracking system to record bugs and feature requests.
|
||||
@@ -249,10 +255,85 @@ If you want to raise an issue, please follow the recommendations below:
|
||||
* Please provide as much information as possible with the issue report, we like to know the version of Spring Data that you are using, the JVM version, Stacktrace, etc.
|
||||
* If you need to paste code, or include a stack trace use https://guides.github.com/features/mastering-markdown/[Markdown] code fences +++```+++.
|
||||
|
||||
[[guides]]
|
||||
== Guides
|
||||
|
||||
The https://spring.io/[spring.io] site contains several guides that show how to use Spring Data step-by-step:
|
||||
|
||||
* https://spring.io/guides/gs/accessing-data-mongodb/[Accessing Data with MongoDB] is a very basic guide that shows you how to create a simple application and how to access data using repositories.
|
||||
* https://spring.io/guides/gs/accessing-mongodb-data-rest/[Accessing MongoDB Data with REST] is a guide to creating a REST web service exposing data stored in MongoDB through repositories.
|
||||
|
||||
[[examples]]
|
||||
== Examples
|
||||
|
||||
* https://github.com/spring-projects/spring-data-examples/[Spring Data Examples] contains example projects that explain specific features in more detail.
|
||||
|
||||
[[building-from-source]]
|
||||
== Building from Source
|
||||
|
||||
You don’t need to build from source to use Spring Data (binaries in https://repo.spring.io[repo.spring.io]), but if you want to try out the latest and greatest, Spring Data can be easily built with the https://github.com/takari/maven-wrapper[maven wrapper].
|
||||
You also need JDK 1.8.
|
||||
You do not need to build from source to use Spring Data. Binaries are available in https://repo.spring.io[repo.spring.io].
|
||||
and accessible from Maven using the Maven configuration noted <<maven-configuration,above>>.
|
||||
|
||||
NOTE: Configuration for Gradle is similar to Maven.
|
||||
|
||||
The best way to get started is by creating a Spring Boot project using MongoDB on https://start.spring.io[start.spring.io].
|
||||
Follow this https://start.spring.io/#type=maven-project&language=java&platformVersion=2.5.4&packaging=jar&jvmVersion=1.8&groupId=com.example&artifactId=demo&name=demo&description=Demo%20project%20for%20Spring%20Boot&packageName=com.example.demo&dependencies=data-mongodb[link]
|
||||
to build an imperative application and this https://start.spring.io/#type=maven-project&language=java&platformVersion=2.5.4&packaging=jar&jvmVersion=1.8&groupId=com.example&artifactId=demo&name=demo&description=Demo%20project%20for%20Spring%20Boot&packageName=com.example.demo&dependencies=data-mongodb-reactive[link]
|
||||
to build a reactive one.
|
||||
|
||||
However, if you want to try out the latest and greatest, Spring Data can be easily built with the https://github.com/takari/maven-wrapper[maven wrapper]
|
||||
and minimally JDK 8 (https://www.oracle.com/java/technologies/downloads/[JDK downloads]).
|
||||
|
||||
In order to build Spring Data MongoDB, first you will need to https://www.mongodb.com/try/download/community[download]
|
||||
and https://docs.mongodb.com/manual/installation/[install a MongoDB distribution].
|
||||
|
||||
Once you have installed MongoDB, you need to start a MongoDB server. It is convenient to set an environment variable to
|
||||
your MongoDB installation (e.g. `MONGODB_HOME`).
|
||||
|
||||
To run the full test suite a https://docs.mongodb.com/manual/tutorial/deploy-replica-set/[MongoDB Replica Set] is required.
|
||||
|
||||
To run the MongoDB server enter the following command from a command-line:
|
||||
|
||||
[source,bash]
|
||||
----
|
||||
$ $MONGODB_HOME/bin/mongod --dbpath $MONGODB_HOME/runtime/data --ipv6 --port 27017 --replSet rs0
|
||||
...
|
||||
"msg":"Successfully connected to host"
|
||||
----
|
||||
|
||||
Once the MongoDB server starts up, you should see the message (`msg`), "_Successfully connected to host_".
|
||||
|
||||
Notice the `--dbpath` option to the `mongod` command. You can set this to anything you like, but in this case, we set
|
||||
the absolute path to a sub-directory (`runtime/data/`) under the MongoDB installation directory (in `$MONGODB_HOME`).
|
||||
|
||||
You need to initialize the MongoDB replica set only once on the first time the MongoDB server is started.
|
||||
To initialize the replica set, start a mongo client:
|
||||
|
||||
[source,bash]
|
||||
----
|
||||
$ $MONGODB_HOME/bin/mongo
|
||||
MongoDB server version: 5.0.0
|
||||
...
|
||||
----
|
||||
|
||||
Then enter the following command:
|
||||
|
||||
[source,bash]
|
||||
----
|
||||
mongo> rs.initiate({ _id: 'rs0', members: [ { _id: 0, host: '127.0.0.1:27017' } ] })
|
||||
----
|
||||
|
||||
Finally, on UNIX-based system (for example, Linux or Mac OS X) you may need to adjust the `ulimit`.
|
||||
In case you need to, you can adjust the `ulimit` with the following command (32768 is just a recommendation):
|
||||
|
||||
[source,bash]
|
||||
----
|
||||
$ ulimit -n 32768
|
||||
----
|
||||
|
||||
You can use `ulimit -a` again to verify the `ulimit` on "_open files_" was set appropriately.
|
||||
|
||||
Now you are ready to build Spring Data MongoDB. Simply enter the following `mvnw` (Maven Wrapper) command:
|
||||
|
||||
[source,bash]
|
||||
----
|
||||
@@ -261,7 +342,8 @@ You also need JDK 1.8.
|
||||
|
||||
If you want to build with the regular `mvn` command, you will need https://maven.apache.org/run-maven/index.html[Maven v3.5.0 or above].
|
||||
|
||||
_Also see link:CONTRIBUTING.adoc[CONTRIBUTING.adoc] if you wish to submit pull requests, and in particular please sign the https://cla.pivotal.io/sign/spring[Contributor’s Agreement] before your first non-trivial change._
|
||||
_Also see link:CONTRIBUTING.adoc[CONTRIBUTING.adoc] if you wish to submit pull requests, and in particular, please sign
|
||||
the https://cla.pivotal.io/sign/spring[Contributor’s Agreement] before your first non-trivial change._
|
||||
|
||||
=== Building reference documentation
|
||||
|
||||
@@ -274,17 +356,7 @@ Building the documentation builds also the project without running tests.
|
||||
|
||||
The generated documentation is available from `target/site/reference/html/index.html`.
|
||||
|
||||
== Guides
|
||||
|
||||
The https://spring.io/[spring.io] site contains several guides that show how to use Spring Data step-by-step:
|
||||
|
||||
* https://spring.io/guides/gs/accessing-data-mongodb/[Accessing Data with MongoDB] is a very basic guide that shows you how to create a simple application and how to access data using repositories.
|
||||
* https://spring.io/guides/gs/accessing-mongodb-data-rest/[Accessing MongoDB Data with REST] is a guide to creating a REST web service exposing data stored in MongoDB through repositories.
|
||||
|
||||
== Examples
|
||||
|
||||
* https://github.com/spring-projects/spring-data-examples/[Spring Data Examples] contains example projects that explain specific features in more detail.
|
||||
|
||||
[[license]]
|
||||
== License
|
||||
|
||||
Spring Data MongoDB is Open Source software released under the https://www.apache.org/licenses/LICENSE-2.0.html[Apache 2.0 license].
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
FROM adoptopenjdk/openjdk11:latest
|
||||
|
||||
ENV TZ=Etc/UTC
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
RUN set -eux; \
|
||||
apt-get update && apt-get install -y apt-transport-https apt-utils gnupg2 ; \
|
||||
apt-key adv --keyserver hkps://keyserver.ubuntu.com:443 --recv e162f504a20cdf15827f718d4b7c549a058f8b6b ; \
|
||||
echo "deb [ arch=amd64 ] https://repo.mongodb.org/apt/ubuntu bionic/mongodb-org/4.2 multiverse" | tee /etc/apt/sources.list.d/mongodb-org-4.2.list; \
|
||||
echo ${TZ} > /etc/timezone;
|
||||
|
||||
RUN apt-get update ; \
|
||||
apt-get install -y mongodb-org=4.2.0 mongodb-org-server=4.2.0 mongodb-org-shell=4.2.0 mongodb-org-mongos=4.2.0 mongodb-org-tools=4.2.0 ; \
|
||||
apt-get clean; \
|
||||
rm -rf /var/lib/apt/lists/*;
|
||||
18
ci/openjdk11-mongodb-4.4/Dockerfile
Normal file
18
ci/openjdk11-mongodb-4.4/Dockerfile
Normal file
@@ -0,0 +1,18 @@
|
||||
FROM adoptopenjdk/openjdk11:latest
|
||||
|
||||
ENV TZ=Etc/UTC
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
RUN set -eux; \
|
||||
sed -i -e 's/archive.ubuntu.com/mirror.one.com/g' /etc/apt/sources.list; \
|
||||
sed -i -e 's/security.ubuntu.com/mirror.one.com/g' /etc/apt/sources.list; \
|
||||
sed -i -e 's/http/https/g' /etc/apt/sources.list ; \
|
||||
apt-get update && apt-get install -y apt-transport-https apt-utils gnupg2 ; \
|
||||
apt-key adv --keyserver hkps://keyserver.ubuntu.com:443 --recv 656408E390CFB1F5 ; \
|
||||
echo "deb [ arch=amd64 ] https://repo.mongodb.org/apt/ubuntu bionic/mongodb-org/4.4 multiverse" | tee /etc/apt/sources.list.d/mongodb-org-4.4.list; \
|
||||
echo ${TZ} > /etc/timezone;
|
||||
|
||||
RUN apt-get update ; \
|
||||
apt-get install -y mongodb-org=4.4.4 mongodb-org-server=4.4.4 mongodb-org-shell=4.4.4 mongodb-org-mongos=4.4.4 mongodb-org-tools=4.4.4 ; \
|
||||
apt-get clean; \
|
||||
rm -rf /var/lib/apt/lists/*;
|
||||
@@ -1,15 +0,0 @@
|
||||
FROM adoptopenjdk/openjdk15:latest
|
||||
|
||||
ENV TZ=Etc/UTC
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
RUN set -eux; \
|
||||
apt-get update && apt-get install -y apt-transport-https apt-utils gnupg2 ; \
|
||||
apt-key adv --keyserver hkps://keyserver.ubuntu.com:443 --recv e162f504a20cdf15827f718d4b7c549a058f8b6b ; \
|
||||
echo "deb [ arch=amd64 ] https://repo.mongodb.org/apt/ubuntu bionic/mongodb-org/4.2 multiverse" | tee /etc/apt/sources.list.d/mongodb-org-4.2.list; \
|
||||
echo ${TZ} > /etc/timezone;
|
||||
|
||||
RUN apt-get update ; \
|
||||
apt-get install -y mongodb-org=4.2.0 mongodb-org-server=4.2.0 mongodb-org-shell=4.2.0 mongodb-org-mongos=4.2.0 mongodb-org-tools=4.2.0 ; \
|
||||
apt-get clean; \
|
||||
rm -rf /var/lib/apt/lists/*;
|
||||
18
ci/openjdk16-mongodb-4.4/Dockerfile
Normal file
18
ci/openjdk16-mongodb-4.4/Dockerfile
Normal file
@@ -0,0 +1,18 @@
|
||||
FROM adoptopenjdk/openjdk16:latest
|
||||
|
||||
ENV TZ=Etc/UTC
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
RUN set -eux; \
|
||||
sed -i -e 's/archive.ubuntu.com/mirror.one.com/g' /etc/apt/sources.list; \
|
||||
sed -i -e 's/security.ubuntu.com/mirror.one.com/g' /etc/apt/sources.list; \
|
||||
sed -i -e 's/http/https/g' /etc/apt/sources.list ; \
|
||||
apt-get update && apt-get install -y apt-transport-https apt-utils gnupg2 ; \
|
||||
apt-key adv --keyserver hkps://keyserver.ubuntu.com:443 --recv 656408E390CFB1F5 ; \
|
||||
echo "deb [ arch=amd64 ] https://repo.mongodb.org/apt/ubuntu bionic/mongodb-org/4.4 multiverse" | tee /etc/apt/sources.list.d/mongodb-org-4.4.list; \
|
||||
echo ${TZ} > /etc/timezone;
|
||||
|
||||
RUN apt-get update ; \
|
||||
apt-get install -y mongodb-org=4.4.4 mongodb-org-server=4.4.4 mongodb-org-shell=4.4.4 mongodb-org-mongos=4.4.4 mongodb-org-tools=4.4.4 ; \
|
||||
apt-get clean; \
|
||||
rm -rf /var/lib/apt/lists/*;
|
||||
@@ -4,12 +4,15 @@ ENV TZ=Etc/UTC
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
RUN RUN set -eux; \
|
||||
sed -i -e 's/archive.ubuntu.com/mirror.one.com/g' /etc/apt/sources.list; \
|
||||
sed -i -e 's/security.ubuntu.com/mirror.one.com/g' /etc/apt/sources.list; \
|
||||
sed -i -e 's/http/https/g' /etc/apt/sources.list ; \
|
||||
apt-get update && apt-get install -y apt-transport-https apt-utils gnupg2 ; \
|
||||
apt-key adv --keyserver hkps://keyserver.ubuntu.com:443 --recv 9DA31620334BD75D9DCB49F368818C72E52529D4 ; \
|
||||
echo "deb [ arch=amd64 ] https://repo.mongodb.org/apt/ubuntu bionic/mongodb-org/4.0 multiverse" | tee /etc/apt/sources.list.d/mongodb-org-4.0.list; \
|
||||
echo ${TZ} > /etc/timezone;
|
||||
|
||||
RUN apt-get update ; \
|
||||
apt-get install -y mongodb-org=4.0.14 mongodb-org-server=4.0.14 mongodb-org-shell=4.0.14 mongodb-org-mongos=4.0.14 mongodb-org-tools=4.0.14 ; \
|
||||
apt-get install -y mongodb-org=4.0.23 mongodb-org-server=4.0.23 mongodb-org-shell=4.0.23 mongodb-org-mongos=4.0.23 mongodb-org-tools=4.0.23 ; \
|
||||
apt-get clean; \
|
||||
rm -rf /var/lib/apt/lists/*;
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
FROM adoptopenjdk/openjdk8:latest
|
||||
|
||||
ENV TZ=Etc/UTC
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
RUN set -eux; \
|
||||
apt-get update && apt-get install -y apt-transport-https apt-utils gnupg2 ; \
|
||||
apt-key adv --keyserver hkps://keyserver.ubuntu.com:443 --recv e162f504a20cdf15827f718d4b7c549a058f8b6b ; \
|
||||
echo "deb [ arch=amd64 ] https://repo.mongodb.org/apt/ubuntu bionic/mongodb-org/4.2 multiverse" | tee /etc/apt/sources.list.d/mongodb-org-4.2.list; \
|
||||
echo ${TZ} > /etc/timezone;
|
||||
|
||||
RUN apt-get update ; \
|
||||
apt-get install -y mongodb-org=4.2.0 mongodb-org-server=4.2.0 mongodb-org-shell=4.2.0 mongodb-org-mongos=4.2.0 mongodb-org-tools=4.2.0 ; \
|
||||
apt-get clean; \
|
||||
rm -rf /var/lib/apt/lists/*;
|
||||
20
ci/openjdk8-mongodb-4.4/Dockerfile
Normal file
20
ci/openjdk8-mongodb-4.4/Dockerfile
Normal file
@@ -0,0 +1,20 @@
|
||||
FROM adoptopenjdk/openjdk8:latest
|
||||
|
||||
ENV TZ=Etc/UTC
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
RUN set -eux; \
|
||||
sed -i -e 's/archive.ubuntu.com/mirror.one.com/g' /etc/apt/sources.list; \
|
||||
sed -i -e 's/security.ubuntu.com/mirror.one.com/g' /etc/apt/sources.list; \
|
||||
sed -i -e 's/http/https/g' /etc/apt/sources.list ; \
|
||||
apt-get update && apt-get install -y apt-transport-https apt-utils gnupg2 ; \
|
||||
apt-key adv --keyserver hkps://keyserver.ubuntu.com:443 --recv 656408E390CFB1F5 ; \
|
||||
echo "deb [ arch=amd64 ] https://repo.mongodb.org/apt/ubuntu bionic/mongodb-org/4.4 multiverse" | tee /etc/apt/sources.list.d/mongodb-org-4.4.list; \
|
||||
echo ${TZ} > /etc/timezone;
|
||||
|
||||
RUN apt-get update ; \
|
||||
ln -T /bin/true /usr/bin/systemctl ; \
|
||||
apt-get install -y mongodb-org=4.4.4 mongodb-org-server=4.4.4 mongodb-org-shell=4.4.4 mongodb-org-mongos=4.4.4 mongodb-org-tools=4.4.4 ; \
|
||||
rm /usr/bin/systemctl ; \
|
||||
apt-get clean; \
|
||||
rm -rf /var/lib/apt/lists/*;
|
||||
20
ci/openjdk8-mongodb-5.0/Dockerfile
Normal file
20
ci/openjdk8-mongodb-5.0/Dockerfile
Normal file
@@ -0,0 +1,20 @@
|
||||
FROM adoptopenjdk/openjdk8:latest
|
||||
|
||||
ENV TZ=Etc/UTC
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
RUN set -eux; \
|
||||
sed -i -e 's/archive.ubuntu.com/mirror.one.com/g' /etc/apt/sources.list; \
|
||||
sed -i -e 's/security.ubuntu.com/mirror.one.com/g' /etc/apt/sources.list; \
|
||||
sed -i -e 's/http/https/g' /etc/apt/sources.list ; \
|
||||
apt-get update && apt-get install -y apt-transport-https apt-utils gnupg2 wget ; \
|
||||
# MongoDB 5.0 release signing key
|
||||
apt-key adv --keyserver hkps://keyserver.ubuntu.com:443 --recv B00A0BD1E2C63C11 ; \
|
||||
# Needed when MongoDB creates a 5.0 folder.
|
||||
echo "deb [ arch=amd64 ] https://repo.mongodb.org/apt/ubuntu bionic/mongodb-org/5.0 multiverse" | tee /etc/apt/sources.list.d/mongodb-org-5.0.list; \
|
||||
echo ${TZ} > /etc/timezone;
|
||||
|
||||
RUN apt-get update; \
|
||||
apt-get install -y mongodb-org=5.0.3 mongodb-org-server=5.0.3 mongodb-org-shell=5.0.3 mongodb-org-mongos=5.0.3 mongodb-org-tools=5.0.3; \
|
||||
apt-get clean; \
|
||||
rm -rf /var/lib/apt/lists/*;
|
||||
21
pom.xml
21
pom.xml
@@ -5,7 +5,7 @@
|
||||
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-mongodb-parent</artifactId>
|
||||
<version>3.2.0-M2</version>
|
||||
<version>3.3.0-RC1</version>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<name>Spring Data MongoDB</name>
|
||||
@@ -15,7 +15,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.data.build</groupId>
|
||||
<artifactId>spring-data-parent</artifactId>
|
||||
<version>2.5.0-M2</version>
|
||||
<version>2.6.0-RC1</version>
|
||||
</parent>
|
||||
|
||||
<modules>
|
||||
@@ -26,8 +26,8 @@
|
||||
<properties>
|
||||
<project.type>multi</project.type>
|
||||
<dist.id>spring-data-mongodb</dist.id>
|
||||
<springdata.commons>2.5.0-M2</springdata.commons>
|
||||
<mongo>4.1.1</mongo>
|
||||
<springdata.commons>2.6.0-RC1</springdata.commons>
|
||||
<mongo>4.4.0-beta1</mongo>
|
||||
<mongo.reactivestreams>${mongo}</mongo.reactivestreams>
|
||||
<jmh.version>1.19</jmh.version>
|
||||
</properties>
|
||||
@@ -141,11 +141,11 @@
|
||||
<id>sonatype-libs-snapshot</id>
|
||||
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
|
||||
<releases>
|
||||
<enabled>false</enabled>
|
||||
</releases>
|
||||
<enabled>false</enabled>
|
||||
</releases>
|
||||
<snapshots>
|
||||
<enabled>true</enabled>
|
||||
</snapshots>
|
||||
<enabled>true</enabled>
|
||||
</snapshots>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
@@ -158,11 +158,6 @@
|
||||
<id>spring-libs-milestone</id>
|
||||
<url>https://repo.spring.io/libs-milestone</url>
|
||||
</pluginRepository>
|
||||
<pluginRepository>
|
||||
<id>bintray-plugins</id>
|
||||
<name>bintray-plugins</name>
|
||||
<url>https://jcenter.bintray.com</url>
|
||||
</pluginRepository>
|
||||
</pluginRepositories>
|
||||
|
||||
</project>
|
||||
|
||||
29
settings.xml
Normal file
29
settings.xml
Normal file
@@ -0,0 +1,29 @@
|
||||
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0
|
||||
https://maven.apache.org/xsd/settings-1.0.0.xsd">
|
||||
|
||||
<servers>
|
||||
<server>
|
||||
<id>spring-plugins-release</id>
|
||||
<username>${env.ARTIFACTORY_USR}</username>
|
||||
<password>${env.ARTIFACTORY_PSW}</password>
|
||||
</server>
|
||||
<server>
|
||||
<id>spring-libs-snapshot</id>
|
||||
<username>${env.ARTIFACTORY_USR}</username>
|
||||
<password>${env.ARTIFACTORY_PSW}</password>
|
||||
</server>
|
||||
<server>
|
||||
<id>spring-libs-milestone</id>
|
||||
<username>${env.ARTIFACTORY_USR}</username>
|
||||
<password>${env.ARTIFACTORY_PSW}</password>
|
||||
</server>
|
||||
<server>
|
||||
<id>spring-libs-release</id>
|
||||
<username>${env.ARTIFACTORY_USR}</username>
|
||||
<password>${env.ARTIFACTORY_PSW}</password>
|
||||
</server>
|
||||
</servers>
|
||||
|
||||
</settings>
|
||||
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-mongodb-parent</artifactId>
|
||||
<version>3.2.0-M2</version>
|
||||
<version>3.3.0-RC1</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-mongodb-parent</artifactId>
|
||||
<version>3.2.0-M2</version>
|
||||
<version>3.3.0-RC1</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-mongodb-parent</artifactId>
|
||||
<version>3.2.0-M2</version>
|
||||
<version>3.3.0-RC1</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -87,6 +87,13 @@
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.google.code.findbugs</groupId>
|
||||
<artifactId>jsr305</artifactId>
|
||||
<version>3.0.2</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- reactive -->
|
||||
|
||||
<dependency>
|
||||
@@ -310,6 +317,15 @@
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- jMolecules -->
|
||||
|
||||
<dependency>
|
||||
<groupId>org.jmolecules</groupId>
|
||||
<artifactId>jmolecules-ddd</artifactId>
|
||||
<version>${jmolecules}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
* Copyright 2021 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
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.mongodb;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.bson.Document;
|
||||
import org.bson.codecs.DocumentCodec;
|
||||
import org.bson.codecs.configuration.CodecRegistry;
|
||||
import org.springframework.data.mongodb.util.json.ParameterBindingDocumentCodec;
|
||||
import org.springframework.data.util.Lazy;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* A {@link MongoExpression} using the {@link ParameterBindingDocumentCodec} for parsing a raw ({@literal json})
|
||||
* expression. The expression will be wrapped within <code>{ ... }</code> if necessary. The actual parsing and parameter
|
||||
* binding of placeholders like {@code ?0} is delayed upon first call on the the target {@link Document} via
|
||||
* {@link #toDocument()}.
|
||||
* <br />
|
||||
*
|
||||
* <pre class="code">
|
||||
* $toUpper : $name -> { '$toUpper' : '$name' }
|
||||
*
|
||||
* { '$toUpper' : '$name' } -> { '$toUpper' : '$name' }
|
||||
*
|
||||
* { '$toUpper' : '?0' }, "$name" -> { '$toUpper' : '$name' }
|
||||
* </pre>
|
||||
*
|
||||
* Some types might require a special {@link org.bson.codecs.Codec}. If so, make sure to provide a {@link CodecRegistry}
|
||||
* containing the required {@link org.bson.codecs.Codec codec} via {@link #withCodecRegistry(CodecRegistry)}.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 3.2
|
||||
*/
|
||||
public class BindableMongoExpression implements MongoExpression {
|
||||
|
||||
private final String expressionString;
|
||||
|
||||
private final @Nullable CodecRegistryProvider codecRegistryProvider;
|
||||
|
||||
private final @Nullable Object[] args;
|
||||
|
||||
private final Lazy<Document> target;
|
||||
|
||||
/**
|
||||
* Create a new instance of {@link BindableMongoExpression}.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @param args can be {@literal null}.
|
||||
*/
|
||||
public BindableMongoExpression(String expression, @Nullable Object[] args) {
|
||||
this(expression, null, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new instance of {@link BindableMongoExpression}.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @param codecRegistryProvider can be {@literal null}.
|
||||
* @param args can be {@literal null}.
|
||||
*/
|
||||
public BindableMongoExpression(String expression, @Nullable CodecRegistryProvider codecRegistryProvider,
|
||||
@Nullable Object[] args) {
|
||||
|
||||
this.expressionString = expression;
|
||||
this.codecRegistryProvider = codecRegistryProvider;
|
||||
this.args = args;
|
||||
this.target = Lazy.of(this::parse);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide the {@link CodecRegistry} used to convert expressions.
|
||||
*
|
||||
* @param codecRegistry must not be {@literal null}.
|
||||
* @return new instance of {@link BindableMongoExpression}.
|
||||
*/
|
||||
public BindableMongoExpression withCodecRegistry(CodecRegistry codecRegistry) {
|
||||
return new BindableMongoExpression(expressionString, () -> codecRegistry, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide the arguments to bind to the placeholders via their index.
|
||||
*
|
||||
* @param args must not be {@literal null}.
|
||||
* @return new instance of {@link BindableMongoExpression}.
|
||||
*/
|
||||
public BindableMongoExpression bind(Object... args) {
|
||||
return new BindableMongoExpression(expressionString, codecRegistryProvider, args);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.MongoExpression#toDocument()
|
||||
*/
|
||||
@Override
|
||||
public Document toDocument() {
|
||||
return target.get();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return "BindableMongoExpression{" + "expressionString='" + expressionString + '\'' + ", args="
|
||||
+ Arrays.toString(args) + '}';
|
||||
}
|
||||
|
||||
private Document parse() {
|
||||
|
||||
String expression = wrapJsonIfNecessary(expressionString);
|
||||
|
||||
if (ObjectUtils.isEmpty(args)) {
|
||||
|
||||
if (codecRegistryProvider == null) {
|
||||
return Document.parse(expression);
|
||||
}
|
||||
|
||||
return Document.parse(expression, codecRegistryProvider.getCodecFor(Document.class)
|
||||
.orElseGet(() -> new DocumentCodec(codecRegistryProvider.getCodecRegistry())));
|
||||
}
|
||||
|
||||
ParameterBindingDocumentCodec codec = codecRegistryProvider == null ? new ParameterBindingDocumentCodec()
|
||||
: new ParameterBindingDocumentCodec(codecRegistryProvider.getCodecRegistry());
|
||||
return codec.decode(expression, args);
|
||||
}
|
||||
|
||||
private static String wrapJsonIfNecessary(String json) {
|
||||
|
||||
if (StringUtils.hasText(json) && (json.startsWith("{") && json.endsWith("}"))) {
|
||||
return json;
|
||||
}
|
||||
|
||||
return "{" + json + "}";
|
||||
}
|
||||
}
|
||||
@@ -20,8 +20,8 @@ import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Helper class featuring helper methods for working with MongoDb collections.
|
||||
* <p/>
|
||||
* <p/>
|
||||
* <br />
|
||||
* <br />
|
||||
* Mainly intended for internal use within the framework.
|
||||
*
|
||||
* @author Thomas Risberg
|
||||
|
||||
@@ -30,7 +30,7 @@ import com.mongodb.client.MongoDatabase;
|
||||
* Helper class for managing a {@link MongoDatabase} instances via {@link MongoDatabaseFactory}. Used for obtaining
|
||||
* {@link ClientSession session bound} resources, such as {@link MongoDatabase} and
|
||||
* {@link com.mongodb.client.MongoCollection} suitable for transactional usage.
|
||||
* <p />
|
||||
* <br />
|
||||
* <strong>Note:</strong> Intended for internal usage only.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
@@ -43,7 +43,7 @@ public class MongoDatabaseUtils {
|
||||
/**
|
||||
* Obtain the default {@link MongoDatabase database} form the given {@link MongoDatabaseFactory factory} using
|
||||
* {@link SessionSynchronization#ON_ACTUAL_TRANSACTION native session synchronization}.
|
||||
* <p />
|
||||
* <br />
|
||||
* Registers a {@link MongoSessionSynchronization MongoDB specific transaction synchronization} within the current
|
||||
* {@link Thread} if {@link TransactionSynchronizationManager#isSynchronizationActive() synchronization is active}.
|
||||
*
|
||||
@@ -56,7 +56,7 @@ public class MongoDatabaseUtils {
|
||||
|
||||
/**
|
||||
* Obtain the default {@link MongoDatabase database} form the given {@link MongoDatabaseFactory factory}.
|
||||
* <p />
|
||||
* <br />
|
||||
* Registers a {@link MongoSessionSynchronization MongoDB specific transaction synchronization} within the current
|
||||
* {@link Thread} if {@link TransactionSynchronizationManager#isSynchronizationActive() synchronization is active}.
|
||||
*
|
||||
@@ -71,7 +71,7 @@ public class MongoDatabaseUtils {
|
||||
/**
|
||||
* Obtain the {@link MongoDatabase database} with given name form the given {@link MongoDatabaseFactory factory} using
|
||||
* {@link SessionSynchronization#ON_ACTUAL_TRANSACTION native session synchronization}.
|
||||
* <p />
|
||||
* <br />
|
||||
* Registers a {@link MongoSessionSynchronization MongoDB specific transaction synchronization} within the current
|
||||
* {@link Thread} if {@link TransactionSynchronizationManager#isSynchronizationActive() synchronization is active}.
|
||||
*
|
||||
@@ -85,7 +85,7 @@ public class MongoDatabaseUtils {
|
||||
|
||||
/**
|
||||
* Obtain the {@link MongoDatabase database} with given name form the given {@link MongoDatabaseFactory factory}.
|
||||
* <p />
|
||||
* <br />
|
||||
* Registers a {@link MongoSessionSynchronization MongoDB specific transaction synchronization} within the current
|
||||
* {@link Thread} if {@link TransactionSynchronizationManager#isSynchronizationActive() synchronization is active}.
|
||||
*
|
||||
@@ -104,7 +104,8 @@ public class MongoDatabaseUtils {
|
||||
|
||||
Assert.notNull(factory, "Factory must not be null!");
|
||||
|
||||
if (!TransactionSynchronizationManager.isSynchronizationActive()) {
|
||||
if (sessionSynchronization == SessionSynchronization.NEVER
|
||||
|| !TransactionSynchronizationManager.isSynchronizationActive()) {
|
||||
return StringUtils.hasText(dbName) ? factory.getMongoDatabase(dbName) : factory.getMongoDatabase();
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright 2021 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
|
||||
*
|
||||
* https://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;
|
||||
|
||||
/**
|
||||
* Wrapper object for MongoDB expressions like {@code $toUpper : $name} that manifest as {@link org.bson.Document} when
|
||||
* passed on to the driver.
|
||||
* <br />
|
||||
* A set of predefined {@link MongoExpression expressions}, including a
|
||||
* {@link org.springframework.data.mongodb.core.aggregation.AggregationSpELExpression SpEL based variant} for method
|
||||
* like expressions (eg. {@code toUpper(name)}) are available via the
|
||||
* {@link org.springframework.data.mongodb.core.aggregation Aggregation API}.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 3.2
|
||||
* @see org.springframework.data.mongodb.core.aggregation.ArithmeticOperators
|
||||
* @see org.springframework.data.mongodb.core.aggregation.ArrayOperators
|
||||
* @see org.springframework.data.mongodb.core.aggregation.ComparisonOperators
|
||||
* @see org.springframework.data.mongodb.core.aggregation.ConditionalOperators
|
||||
* @see org.springframework.data.mongodb.core.aggregation.ConvertOperators
|
||||
* @see org.springframework.data.mongodb.core.aggregation.DateOperators
|
||||
* @see org.springframework.data.mongodb.core.aggregation.ObjectOperators
|
||||
* @see org.springframework.data.mongodb.core.aggregation.SetOperators
|
||||
* @see org.springframework.data.mongodb.core.aggregation.StringOperators
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface MongoExpression {
|
||||
|
||||
/**
|
||||
* Create a new {@link MongoExpression} from plain {@link String} (eg. {@code $toUpper : $name}). <br />
|
||||
* The given expression will be wrapped with <code>{ ... }</code> to match an actual MongoDB {@link org.bson.Document}
|
||||
* if necessary.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return new instance of {@link MongoExpression}.
|
||||
*/
|
||||
static MongoExpression create(String expression) {
|
||||
return new BindableMongoExpression(expression, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link MongoExpression} from plain {@link String} containing placeholders (eg. {@code $toUpper : ?0})
|
||||
* that will be resolved on first call of {@link #toDocument()}. <br />
|
||||
* The given expression will be wrapped with <code>{ ... }</code> to match an actual MongoDB {@link org.bson.Document}
|
||||
* if necessary.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return new instance of {@link MongoExpression}.
|
||||
*/
|
||||
static MongoExpression create(String expression, Object... args) {
|
||||
return new BindableMongoExpression(expression, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain the native {@link org.bson.Document} representation.
|
||||
*
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
org.bson.Document toDocument();
|
||||
}
|
||||
@@ -24,7 +24,7 @@ import com.mongodb.client.ClientSession;
|
||||
/**
|
||||
* MongoDB specific {@link ResourceHolderSupport resource holder}, wrapping a {@link ClientSession}.
|
||||
* {@link MongoTransactionManager} binds instances of this class to the thread.
|
||||
* <p />
|
||||
* <br />
|
||||
* <strong>Note:</strong> Intended for internal usage only.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
|
||||
@@ -37,18 +37,18 @@ import com.mongodb.client.ClientSession;
|
||||
/**
|
||||
* A {@link org.springframework.transaction.PlatformTransactionManager} implementation that manages
|
||||
* {@link ClientSession} based transactions for a single {@link MongoDatabaseFactory}.
|
||||
* <p />
|
||||
* <br />
|
||||
* Binds a {@link ClientSession} from the specified {@link MongoDatabaseFactory} to the thread.
|
||||
* <p />
|
||||
* <br />
|
||||
* {@link TransactionDefinition#isReadOnly() Readonly} transactions operate on a {@link ClientSession} and enable causal
|
||||
* consistency, and also {@link ClientSession#startTransaction() start}, {@link ClientSession#commitTransaction()
|
||||
* commit} or {@link ClientSession#abortTransaction() abort} a transaction.
|
||||
* <p />
|
||||
* <br />
|
||||
* Application code is required to retrieve the {@link com.mongodb.client.MongoDatabase} via
|
||||
* {@link MongoDatabaseUtils#getDatabase(MongoDatabaseFactory)} instead of a standard
|
||||
* {@link MongoDatabaseFactory#getMongoDatabase()} call. Spring classes such as
|
||||
* {@link org.springframework.data.mongodb.core.MongoTemplate} use this strategy implicitly.
|
||||
* <p />
|
||||
* <br />
|
||||
* 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>
|
||||
@@ -69,11 +69,11 @@ public class MongoTransactionManager extends AbstractPlatformTransactionManager
|
||||
|
||||
/**
|
||||
* Create a new {@link MongoTransactionManager} for bean-style usage.
|
||||
* <p />
|
||||
* <br />
|
||||
* <strong>Note:</strong>The {@link MongoDatabaseFactory db factory} has to be
|
||||
* {@link #setDbFactory(MongoDatabaseFactory) set} before using the instance. Use this constructor to prepare a
|
||||
* {@link MongoTransactionManager} via a {@link org.springframework.beans.factory.BeanFactory}.
|
||||
* <p />
|
||||
* <br />
|
||||
* Optionally it is possible to set default {@link TransactionOptions transaction options} defining
|
||||
* {@link com.mongodb.ReadConcern} and {@link com.mongodb.WriteConcern}.
|
||||
*
|
||||
@@ -212,8 +212,8 @@ public class MongoTransactionManager extends AbstractPlatformTransactionManager
|
||||
* 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 />
|
||||
* <pre>
|
||||
* <code>
|
||||
* <pre>
|
||||
* int retries = 3;
|
||||
* do {
|
||||
* try {
|
||||
@@ -226,8 +226,8 @@ public class MongoTransactionManager extends AbstractPlatformTransactionManager
|
||||
* }
|
||||
* Thread.sleep(500);
|
||||
* } while (--retries > 0);
|
||||
* </pre>
|
||||
* </code>
|
||||
* </pre>
|
||||
*
|
||||
* @param transactionObject never {@literal null}.
|
||||
* @throws Exception in case of transaction errors.
|
||||
|
||||
@@ -36,7 +36,7 @@ import com.mongodb.reactivestreams.client.MongoDatabase;
|
||||
* Helper class for managing reactive {@link MongoDatabase} instances via {@link ReactiveMongoDatabaseFactory}. Used for
|
||||
* obtaining {@link ClientSession session bound} resources, such as {@link MongoDatabase} and {@link MongoCollection}
|
||||
* suitable for transactional usage.
|
||||
* <p />
|
||||
* <br />
|
||||
* <strong>Note:</strong> Intended for internal usage only.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
@@ -75,7 +75,7 @@ public class ReactiveMongoDatabaseUtils {
|
||||
/**
|
||||
* Obtain the default {@link MongoDatabase database} form the given {@link ReactiveMongoDatabaseFactory factory} using
|
||||
* {@link SessionSynchronization#ON_ACTUAL_TRANSACTION native session synchronization}.
|
||||
* <p />
|
||||
* <br />
|
||||
* Registers a {@link MongoSessionSynchronization MongoDB specific transaction synchronization} within the subscriber
|
||||
* {@link Context} if {@link TransactionSynchronizationManager#isSynchronizationActive() synchronization is active}.
|
||||
*
|
||||
@@ -88,7 +88,7 @@ public class ReactiveMongoDatabaseUtils {
|
||||
|
||||
/**
|
||||
* Obtain the default {@link MongoDatabase database} form the given {@link ReactiveMongoDatabaseFactory factory}.
|
||||
* <p />
|
||||
* <br />
|
||||
* Registers a {@link MongoSessionSynchronization MongoDB specific transaction synchronization} within the subscriber
|
||||
* {@link Context} if {@link TransactionSynchronizationManager#isSynchronizationActive() synchronization is active}.
|
||||
*
|
||||
@@ -104,7 +104,7 @@ public class ReactiveMongoDatabaseUtils {
|
||||
/**
|
||||
* Obtain the {@link MongoDatabase database} with given name form the given {@link ReactiveMongoDatabaseFactory
|
||||
* factory} using {@link SessionSynchronization#ON_ACTUAL_TRANSACTION native session synchronization}.
|
||||
* <p />
|
||||
* <br />
|
||||
* Registers a {@link MongoSessionSynchronization MongoDB specific transaction synchronization} within the subscriber
|
||||
* {@link Context} if {@link TransactionSynchronizationManager#isSynchronizationActive() synchronization is active}.
|
||||
*
|
||||
@@ -119,7 +119,7 @@ public class ReactiveMongoDatabaseUtils {
|
||||
/**
|
||||
* Obtain the {@link MongoDatabase database} with given name form the given {@link ReactiveMongoDatabaseFactory
|
||||
* factory}.
|
||||
* <p />
|
||||
* <br />
|
||||
* Registers a {@link MongoSessionSynchronization MongoDB specific transaction synchronization} within the subscriber
|
||||
* {@link Context} if {@link TransactionSynchronizationManager#isSynchronizationActive() synchronization is active}.
|
||||
*
|
||||
@@ -138,6 +138,10 @@ public class ReactiveMongoDatabaseUtils {
|
||||
|
||||
Assert.notNull(factory, "DatabaseFactory must not be null!");
|
||||
|
||||
if (sessionSynchronization == SessionSynchronization.NEVER) {
|
||||
return getMongoDatabaseOrDefault(dbName, factory);
|
||||
}
|
||||
|
||||
return TransactionSynchronizationManager.forCurrentTransaction()
|
||||
.filter(TransactionSynchronizationManager::isSynchronizationActive) //
|
||||
.flatMap(synchronizationManager -> {
|
||||
|
||||
@@ -24,7 +24,7 @@ import com.mongodb.reactivestreams.client.ClientSession;
|
||||
/**
|
||||
* MongoDB specific resource holder, wrapping a {@link ClientSession}. {@link ReactiveMongoTransactionManager} binds
|
||||
* instances of this class to the subscriber context.
|
||||
* <p />
|
||||
* <br />
|
||||
* <strong>Note:</strong> Intended for internal usage only.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
|
||||
@@ -38,21 +38,21 @@ import com.mongodb.reactivestreams.client.ClientSession;
|
||||
* A {@link org.springframework.transaction.ReactiveTransactionManager} implementation that manages
|
||||
* {@link com.mongodb.reactivestreams.client.ClientSession} based transactions for a single
|
||||
* {@link org.springframework.data.mongodb.ReactiveMongoDatabaseFactory}.
|
||||
* <p />
|
||||
* <br />
|
||||
* Binds a {@link ClientSession} from the specified
|
||||
* {@link org.springframework.data.mongodb.ReactiveMongoDatabaseFactory} to the subscriber
|
||||
* {@link reactor.util.context.Context}.
|
||||
* <p />
|
||||
* <br />
|
||||
* {@link org.springframework.transaction.TransactionDefinition#isReadOnly() Readonly} transactions operate on a
|
||||
* {@link ClientSession} and enable causal consistency, and also {@link ClientSession#startTransaction() start},
|
||||
* {@link com.mongodb.reactivestreams.client.ClientSession#commitTransaction() commit} or
|
||||
* {@link ClientSession#abortTransaction() abort} a transaction.
|
||||
* <p />
|
||||
* <br />
|
||||
* Application code is required to retrieve the {@link com.mongodb.reactivestreams.client.MongoDatabase} via
|
||||
* {@link org.springframework.data.mongodb.ReactiveMongoDatabaseUtils#getDatabase(ReactiveMongoDatabaseFactory)} instead
|
||||
* of a standard {@link org.springframework.data.mongodb.ReactiveMongoDatabaseFactory#getMongoDatabase()} call. Spring
|
||||
* classes such as {@link org.springframework.data.mongodb.core.ReactiveMongoTemplate} use this strategy implicitly.
|
||||
* <p />
|
||||
* <br />
|
||||
* By default failure of a {@literal commit} operation raises a {@link TransactionSystemException}. You can override
|
||||
* {@link #doCommit(TransactionSynchronizationManager, ReactiveMongoTransactionObject)} to implement the
|
||||
* <a href="https://docs.mongodb.com/manual/core/transactions/#retry-commit-operation">Retry Commit Operation</a>
|
||||
@@ -71,11 +71,11 @@ public class ReactiveMongoTransactionManager extends AbstractReactiveTransaction
|
||||
|
||||
/**
|
||||
* Create a new {@link ReactiveMongoTransactionManager} for bean-style usage.
|
||||
* <p />
|
||||
* <br />
|
||||
* <strong>Note:</strong>The {@link org.springframework.data.mongodb.ReactiveMongoDatabaseFactory db factory} has to
|
||||
* be {@link #setDatabaseFactory(ReactiveMongoDatabaseFactory)} set} before using the instance. Use this constructor
|
||||
* to prepare a {@link ReactiveMongoTransactionManager} via a {@link org.springframework.beans.factory.BeanFactory}.
|
||||
* <p />
|
||||
* <br />
|
||||
* Optionally it is possible to set default {@link TransactionOptions transaction options} defining
|
||||
* {@link com.mongodb.ReadConcern} and {@link com.mongodb.WriteConcern}.
|
||||
*
|
||||
|
||||
@@ -35,7 +35,7 @@ import com.mongodb.session.ClientSession;
|
||||
/**
|
||||
* {@link MethodInterceptor} implementation looking up and invoking an alternative target method having
|
||||
* {@link ClientSession} as its first argument. This allows seamless integration with the existing code base.
|
||||
* <p />
|
||||
* <br />
|
||||
* The {@link MethodInterceptor} is aware of methods on {@code MongoCollection} that my return new instances of itself
|
||||
* like (eg. {@link com.mongodb.reactivestreams.client.MongoCollection#withWriteConcern(WriteConcern)} and decorate them
|
||||
* if not already proxied.
|
||||
|
||||
@@ -15,13 +15,20 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb;
|
||||
|
||||
import org.springframework.data.mongodb.core.MongoTemplate;
|
||||
import org.springframework.data.mongodb.core.ReactiveMongoTemplate;
|
||||
|
||||
/**
|
||||
* {@link SessionSynchronization} is used along with {@link org.springframework.data.mongodb.core.MongoTemplate} to
|
||||
* define in which type of transactions to participate if any.
|
||||
* {@link SessionSynchronization} is used along with {@code MongoTemplate} to define in which type of transactions to
|
||||
* participate if any.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
* @since 2.1
|
||||
* @see MongoTemplate#setSessionSynchronization(SessionSynchronization)
|
||||
* @see MongoDatabaseUtils#getDatabase(MongoDatabaseFactory, SessionSynchronization)
|
||||
* @see ReactiveMongoTemplate#setSessionSynchronization(SessionSynchronization)
|
||||
* @see ReactiveMongoDatabaseUtils#getDatabase(ReactiveMongoDatabaseFactory, SessionSynchronization)
|
||||
*/
|
||||
public enum SessionSynchronization {
|
||||
|
||||
@@ -34,5 +41,12 @@ public enum SessionSynchronization {
|
||||
/**
|
||||
* Synchronize with native MongoDB transactions initiated via {@link MongoTransactionManager}.
|
||||
*/
|
||||
ON_ACTUAL_TRANSACTION;
|
||||
ON_ACTUAL_TRANSACTION,
|
||||
|
||||
/**
|
||||
* Do not participate in ongoing transactions.
|
||||
*
|
||||
* @since 3.2.5
|
||||
*/
|
||||
NEVER;
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ public class SpringDataMongoDB {
|
||||
|
||||
/**
|
||||
* Fetches the "Implementation-Version" manifest attribute from the jar file.
|
||||
* <p />
|
||||
* <br />
|
||||
* Note that some ClassLoaders do not expose the package metadata, hence this class might not be able to determine the
|
||||
* version in all environments. In this case the current Major version is returned as a fallback.
|
||||
*
|
||||
|
||||
@@ -26,7 +26,6 @@ import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.core.type.filter.AnnotationTypeFilter;
|
||||
import org.springframework.data.annotation.Persistent;
|
||||
import org.springframework.data.convert.CustomConversions;
|
||||
import org.springframework.data.mapping.model.CamelCaseAbbreviatingFieldNamingStrategy;
|
||||
import org.springframework.data.mapping.model.FieldNamingStrategy;
|
||||
@@ -140,8 +139,7 @@ public abstract class MongoConfigurationSupport {
|
||||
}
|
||||
|
||||
/**
|
||||
* Scans the given base package for entities, i.e. MongoDB specific types annotated with {@link Document} and
|
||||
* {@link Persistent}.
|
||||
* Scans the given base package for entities, i.e. MongoDB specific types annotated with {@link Document}.
|
||||
*
|
||||
* @param basePackage must not be {@literal null}.
|
||||
* @return
|
||||
@@ -161,7 +159,6 @@ public abstract class MongoConfigurationSupport {
|
||||
ClassPathScanningCandidateComponentProvider componentProvider = new ClassPathScanningCandidateComponentProvider(
|
||||
false);
|
||||
componentProvider.addIncludeFilter(new AnnotationTypeFilter(Document.class));
|
||||
componentProvider.addIncludeFilter(new AnnotationTypeFilter(Persistent.class));
|
||||
|
||||
for (BeanDefinition candidate : componentProvider.findCandidateComponents(basePackage)) {
|
||||
|
||||
@@ -175,8 +172,7 @@ public abstract class MongoConfigurationSupport {
|
||||
|
||||
/**
|
||||
* Configures whether to abbreviate field names for domain objects by configuring a
|
||||
* {@link CamelCaseAbbreviatingFieldNamingStrategy} on the {@link MongoMappingContext} instance created. For advanced
|
||||
* customization needs, consider overriding {@link #mappingMongoConverter()}.
|
||||
* {@link CamelCaseAbbreviatingFieldNamingStrategy} on the {@link MongoMappingContext} instance created.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
|
||||
@@ -22,9 +22,12 @@ import java.util.Map;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.CustomEditorConfigurer;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionValidationException;
|
||||
import org.springframework.beans.factory.support.ManagedMap;
|
||||
import org.springframework.beans.factory.xml.BeanDefinitionParser;
|
||||
import org.springframework.data.mongodb.core.MongoClientSettingsFactoryBean;
|
||||
import org.springframework.data.mongodb.core.MongoServerApiFactoryBean;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.util.xml.DomUtils;
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
@@ -112,6 +115,20 @@ abstract class MongoParsingUtils {
|
||||
// Field level encryption
|
||||
setPropertyReference(clientOptionsDefBuilder, settingsElement, "encryption-settings-ref", "autoEncryptionSettings");
|
||||
|
||||
// ServerAPI
|
||||
if (StringUtils.hasText(settingsElement.getAttribute("server-api-version"))) {
|
||||
|
||||
MongoServerApiFactoryBean serverApiFactoryBean = new MongoServerApiFactoryBean();
|
||||
serverApiFactoryBean.setVersion(settingsElement.getAttribute("server-api-version"));
|
||||
try {
|
||||
clientOptionsDefBuilder.addPropertyValue("serverApi", serverApiFactoryBean.getObject());
|
||||
} catch (Exception exception) {
|
||||
throw new BeanDefinitionValidationException("Non parsable server-api.", exception);
|
||||
}
|
||||
} else {
|
||||
setPropertyReference(clientOptionsDefBuilder, settingsElement, "server-api-ref", "serverApi");
|
||||
}
|
||||
|
||||
// and the rest
|
||||
|
||||
mongoClientBuilder.addPropertyValue("mongoClientSettings", clientOptionsDefBuilder.getBeanDefinition());
|
||||
|
||||
@@ -28,7 +28,7 @@ import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
|
||||
* @author Christoph Strobl
|
||||
* @since 3.1
|
||||
*/
|
||||
class PersistentEntitiesFactoryBean implements FactoryBean<PersistentEntities> {
|
||||
public class PersistentEntitiesFactoryBean implements FactoryBean<PersistentEntities> {
|
||||
|
||||
private final MappingMongoConverter converter;
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ 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.AggregationOptions.DomainTypeMapping;
|
||||
import org.springframework.data.mongodb.core.aggregation.CountOperation;
|
||||
import org.springframework.data.mongodb.core.aggregation.RelaxedTypeBasedAggregationOperationContext;
|
||||
import org.springframework.data.mongodb.core.aggregation.TypeBasedAggregationOperationContext;
|
||||
@@ -36,6 +37,7 @@ 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.data.util.Lazy;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
@@ -52,41 +54,46 @@ class AggregationUtil {
|
||||
|
||||
QueryMapper queryMapper;
|
||||
MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext;
|
||||
Lazy<AggregationOperationContext> untypedMappingContext;
|
||||
|
||||
AggregationUtil(QueryMapper queryMapper,
|
||||
MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext) {
|
||||
|
||||
this.queryMapper = queryMapper;
|
||||
this.mappingContext = mappingContext;
|
||||
this.untypedMappingContext = Lazy
|
||||
.of(() -> new RelaxedTypeBasedAggregationOperationContext(Object.class, mappingContext, queryMapper));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
AggregationOperationContext createAggregationContext(Aggregation aggregation, @Nullable Class<?> inputType) {
|
||||
|
||||
if (context != null) {
|
||||
return context;
|
||||
DomainTypeMapping domainTypeMapping = aggregation.getOptions().getDomainTypeMapping();
|
||||
|
||||
if (domainTypeMapping == DomainTypeMapping.NONE) {
|
||||
return Aggregation.DEFAULT_CONTEXT;
|
||||
}
|
||||
|
||||
if (!(aggregation instanceof TypedAggregation)) {
|
||||
return new RelaxedTypeBasedAggregationOperationContext(Object.class, mappingContext, queryMapper);
|
||||
}
|
||||
|
||||
Class<?> inputType = ((TypedAggregation) aggregation).getInputType();
|
||||
if(inputType == null) {
|
||||
return untypedMappingContext.get();
|
||||
}
|
||||
|
||||
if (domainTypeMapping == DomainTypeMapping.STRICT
|
||||
&& !aggregation.getPipeline().containsUnionWith()) {
|
||||
return new TypeBasedAggregationOperationContext(inputType, mappingContext, queryMapper);
|
||||
}
|
||||
|
||||
if (aggregation.getPipeline().containsUnionWith()) {
|
||||
return new RelaxedTypeBasedAggregationOperationContext(inputType, mappingContext, queryMapper);
|
||||
}
|
||||
|
||||
return new TypeBasedAggregationOperationContext(inputType, mappingContext, queryMapper);
|
||||
inputType = ((TypedAggregation<?>) aggregation).getInputType();
|
||||
if (domainTypeMapping == DomainTypeMapping.STRICT
|
||||
&& !aggregation.getPipeline().containsUnionWith()) {
|
||||
return new TypeBasedAggregationOperationContext(inputType, mappingContext, queryMapper);
|
||||
}
|
||||
|
||||
return new RelaxedTypeBasedAggregationOperationContext(inputType, mappingContext, queryMapper);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -125,53 +132,6 @@ class AggregationUtil {
|
||||
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()))
|
||||
|
||||
@@ -242,13 +242,13 @@ public class ChangeStreamOptions {
|
||||
|
||||
/**
|
||||
* Set the filter to apply.
|
||||
* <p/>
|
||||
* <br />
|
||||
* Fields on aggregation expression root level are prefixed to map to fields contained in
|
||||
* {@link ChangeStreamDocument#getFullDocument() fullDocument}. However {@literal operationType}, {@literal ns},
|
||||
* {@literal documentKey} and {@literal fullDocument} are reserved words that will be omitted, and therefore taken
|
||||
* as given, during the mapping procedure. You may want to have a look at the
|
||||
* <a href="https://docs.mongodb.com/manual/reference/change-events/">structure of Change Events</a>.
|
||||
* <p/>
|
||||
* <br />
|
||||
* Use {@link org.springframework.data.mongodb.core.aggregation.TypedAggregation} to ensure filter expressions are
|
||||
* mapped to domain type fields.
|
||||
*
|
||||
|
||||
@@ -17,8 +17,11 @@ package org.springframework.data.mongodb.core;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.data.mongodb.core.mapping.Field;
|
||||
import org.springframework.data.mongodb.core.query.Collation;
|
||||
import org.springframework.data.mongodb.core.schema.MongoJsonSchema;
|
||||
import org.springframework.data.mongodb.core.timeseries.Granularity;
|
||||
import org.springframework.data.mongodb.core.timeseries.GranularityDefinition;
|
||||
import org.springframework.data.mongodb.core.validation.Validator;
|
||||
import org.springframework.data.util.Optionals;
|
||||
import org.springframework.lang.Nullable;
|
||||
@@ -42,6 +45,7 @@ public class CollectionOptions {
|
||||
private @Nullable Boolean capped;
|
||||
private @Nullable Collation collation;
|
||||
private ValidationOptions validationOptions;
|
||||
private @Nullable TimeSeriesOptions timeSeriesOptions;
|
||||
|
||||
/**
|
||||
* Constructs a new <code>CollectionOptions</code> instance.
|
||||
@@ -54,17 +58,19 @@ public class CollectionOptions {
|
||||
*/
|
||||
@Deprecated
|
||||
public CollectionOptions(@Nullable Long size, @Nullable Long maxDocuments, @Nullable Boolean capped) {
|
||||
this(size, maxDocuments, capped, null, ValidationOptions.none());
|
||||
this(size, maxDocuments, capped, null, ValidationOptions.none(), null);
|
||||
}
|
||||
|
||||
private CollectionOptions(@Nullable Long size, @Nullable Long maxDocuments, @Nullable Boolean capped,
|
||||
@Nullable Collation collation, ValidationOptions validationOptions) {
|
||||
@Nullable Collation collation, ValidationOptions validationOptions,
|
||||
@Nullable TimeSeriesOptions timeSeriesOptions) {
|
||||
|
||||
this.maxDocuments = maxDocuments;
|
||||
this.size = size;
|
||||
this.capped = capped;
|
||||
this.collation = collation;
|
||||
this.validationOptions = validationOptions;
|
||||
this.timeSeriesOptions = timeSeriesOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -78,7 +84,7 @@ public class CollectionOptions {
|
||||
|
||||
Assert.notNull(collation, "Collation must not be null!");
|
||||
|
||||
return new CollectionOptions(null, null, null, collation, ValidationOptions.none());
|
||||
return new CollectionOptions(null, null, null, collation, ValidationOptions.none(), null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -88,7 +94,21 @@ public class CollectionOptions {
|
||||
* @since 2.0
|
||||
*/
|
||||
public static CollectionOptions empty() {
|
||||
return new CollectionOptions(null, null, null, null, ValidationOptions.none());
|
||||
return new CollectionOptions(null, null, null, null, ValidationOptions.none(), null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Quick way to set up {@link CollectionOptions} for a Time Series collection. For more advanced settings use
|
||||
* {@link #timeSeries(TimeSeriesOptions)}.
|
||||
*
|
||||
* @param timeField The name of the property which contains the date in each time series document. Must not be
|
||||
* {@literal null}.
|
||||
* @return new instance of {@link CollectionOptions}.
|
||||
* @see #timeSeries(TimeSeriesOptions)
|
||||
* @since 3.3
|
||||
*/
|
||||
public static CollectionOptions timeSeries(String timeField) {
|
||||
return empty().timeSeries(TimeSeriesOptions.timeSeries(timeField));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -99,7 +119,7 @@ public class CollectionOptions {
|
||||
* @since 2.0
|
||||
*/
|
||||
public CollectionOptions capped() {
|
||||
return new CollectionOptions(size, maxDocuments, true, collation, validationOptions);
|
||||
return new CollectionOptions(size, maxDocuments, true, collation, validationOptions, null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -110,7 +130,7 @@ public class CollectionOptions {
|
||||
* @since 2.0
|
||||
*/
|
||||
public CollectionOptions maxDocuments(long maxDocuments) {
|
||||
return new CollectionOptions(size, maxDocuments, capped, collation, validationOptions);
|
||||
return new CollectionOptions(size, maxDocuments, capped, collation, validationOptions, timeSeriesOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -121,7 +141,7 @@ public class CollectionOptions {
|
||||
* @since 2.0
|
||||
*/
|
||||
public CollectionOptions size(long size) {
|
||||
return new CollectionOptions(size, maxDocuments, capped, collation, validationOptions);
|
||||
return new CollectionOptions(size, maxDocuments, capped, collation, validationOptions, timeSeriesOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -132,7 +152,7 @@ public class CollectionOptions {
|
||||
* @since 2.0
|
||||
*/
|
||||
public CollectionOptions collation(@Nullable Collation collation) {
|
||||
return new CollectionOptions(size, maxDocuments, capped, collation, validationOptions);
|
||||
return new CollectionOptions(size, maxDocuments, capped, collation, validationOptions, timeSeriesOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -252,7 +272,20 @@ public class CollectionOptions {
|
||||
public CollectionOptions validation(ValidationOptions validationOptions) {
|
||||
|
||||
Assert.notNull(validationOptions, "ValidationOptions must not be null!");
|
||||
return new CollectionOptions(size, maxDocuments, capped, collation, validationOptions);
|
||||
return new CollectionOptions(size, maxDocuments, capped, collation, validationOptions, timeSeriesOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new {@link CollectionOptions} with the given {@link TimeSeriesOptions}.
|
||||
*
|
||||
* @param timeSeriesOptions must not be {@literal null}.
|
||||
* @return new instance of {@link CollectionOptions}.
|
||||
* @since 3.3
|
||||
*/
|
||||
public CollectionOptions timeSeries(TimeSeriesOptions timeSeriesOptions) {
|
||||
|
||||
Assert.notNull(timeSeriesOptions, "TimeSeriesOptions must not be null!");
|
||||
return new CollectionOptions(size, maxDocuments, capped, collation, validationOptions, timeSeriesOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -303,6 +336,16 @@ public class CollectionOptions {
|
||||
return validationOptions.isEmpty() ? Optional.empty() : Optional.of(validationOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link TimeSeriesOptions} if available.
|
||||
*
|
||||
* @return {@link Optional#empty()} if not specified.
|
||||
* @since 3.3
|
||||
*/
|
||||
public Optional<TimeSeriesOptions> getTimeSeriesOptions() {
|
||||
return Optional.ofNullable(timeSeriesOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encapsulation of ValidationOptions options.
|
||||
*
|
||||
@@ -385,7 +428,7 @@ public class CollectionOptions {
|
||||
/**
|
||||
* Get the {@code validationAction} to perform.
|
||||
*
|
||||
* @return @return {@link Optional#empty()} if not set.
|
||||
* @return {@link Optional#empty()} if not set.
|
||||
*/
|
||||
public Optional<ValidationAction> getValidationAction() {
|
||||
return Optional.ofNullable(validationAction);
|
||||
@@ -398,4 +441,89 @@ public class CollectionOptions {
|
||||
return !Optionals.isAnyPresent(getValidator(), getValidationAction(), getValidationLevel());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Options applicable to Time Series collections.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 3.3
|
||||
* @see <a href=
|
||||
* "https://docs.mongodb.com/manual/core/timeseries-collections">https://docs.mongodb.com/manual/core/timeseries-collections</a>
|
||||
*/
|
||||
public static class TimeSeriesOptions {
|
||||
|
||||
private final String timeField;
|
||||
|
||||
private @Nullable final String metaField;
|
||||
|
||||
private final GranularityDefinition granularity;
|
||||
|
||||
private TimeSeriesOptions(String timeField, @Nullable String metaField, GranularityDefinition granularity) {
|
||||
|
||||
Assert.hasText(timeField, "Time field must not be empty or null!");
|
||||
|
||||
this.timeField = timeField;
|
||||
this.metaField = metaField;
|
||||
this.granularity = granularity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new instance of {@link TimeSeriesOptions} using the given field as its {@literal timeField}. The one,
|
||||
* that contains the date in each time series document. <br />
|
||||
* {@link Field#name() Annotated fieldnames} will be considered during the mapping process.
|
||||
*
|
||||
* @param timeField must not be {@literal null}.
|
||||
* @return new instance of {@link TimeSeriesOptions}.
|
||||
*/
|
||||
public static TimeSeriesOptions timeSeries(String timeField) {
|
||||
return new TimeSeriesOptions(timeField, null, Granularity.DEFAULT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the name of the field which contains metadata in each time series document. Should not be the {@literal id}
|
||||
* nor {@link TimeSeriesOptions#timeSeries(String)} timeField} nor point to an {@literal array} or
|
||||
* {@link java.util.Collection}. <br />
|
||||
* {@link Field#name() Annotated fieldnames} will be considered during the mapping process.
|
||||
*
|
||||
* @param metaField must not be {@literal null}.
|
||||
* @return new instance of {@link TimeSeriesOptions}.
|
||||
*/
|
||||
public TimeSeriesOptions metaField(String metaField) {
|
||||
return new TimeSeriesOptions(timeField, metaField, granularity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Select the {@link GranularityDefinition} parameter to define how data in the time series collection is organized.
|
||||
* Select one that is closest to the time span between incoming measurements.
|
||||
*
|
||||
* @return new instance of {@link TimeSeriesOptions}.
|
||||
* @see Granularity
|
||||
*/
|
||||
public TimeSeriesOptions granularity(GranularityDefinition granularity) {
|
||||
return new TimeSeriesOptions(timeField, metaField, granularity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
public String getTimeField() {
|
||||
return timeField;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return can be {@literal null}. Might be an {@literal empty} {@link String} as well, so maybe check via
|
||||
* {@link org.springframework.util.StringUtils#hasText(String)}.
|
||||
*/
|
||||
@Nullable
|
||||
public String getMetaField() {
|
||||
return metaField;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
public GranularityDefinition getGranularity() {
|
||||
return granularity;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ class DefaultIndexOperationsProvider implements IndexOperationsProvider {
|
||||
* @see org.springframework.data.mongodb.core.index.IndexOperationsProvider#reactiveIndexOps(java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public IndexOperations indexOps(String collectionName) {
|
||||
return new DefaultIndexOperations(mongoDbFactory, collectionName, mapper);
|
||||
public IndexOperations indexOps(String collectionName, Class<?> type) {
|
||||
return new DefaultIndexOperations(mongoDbFactory, collectionName, mapper, type);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2021 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
|
||||
*
|
||||
* https://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;
|
||||
|
||||
/**
|
||||
* Encryption algorithms supported by MongoDB Client Side Field Level Encryption.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 3.3
|
||||
*/
|
||||
public final class EncryptionAlgorithms {
|
||||
|
||||
public static final String AEAD_AES_256_CBC_HMAC_SHA_512_Deterministic = "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic";
|
||||
public static final String AEAD_AES_256_CBC_HMAC_SHA_512_Random = "AEAD_AES_256_CBC_HMAC_SHA_512-Random";
|
||||
|
||||
}
|
||||
@@ -16,6 +16,7 @@
|
||||
package org.springframework.data.mongodb.core;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
@@ -28,18 +29,23 @@ import org.springframework.data.mapping.PersistentEntity;
|
||||
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.CollectionOptions.TimeSeriesOptions;
|
||||
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.mapping.TimeSeries;
|
||||
import org.springframework.data.mongodb.core.query.Collation;
|
||||
import org.springframework.data.mongodb.core.query.Criteria;
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
import org.springframework.data.mongodb.core.timeseries.Granularity;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Common operations performed on an entity in the context of it's mapping metadata.
|
||||
@@ -107,6 +113,20 @@ class EntityOperations {
|
||||
return AdaptibleMappedEntity.of(entity, context, conversionService);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param source can be {@literal null}.
|
||||
* @return {@literal true} if the given value is an {@literal array}, {@link Collection} or {@link Iterator}.
|
||||
* @since 3.2
|
||||
*/
|
||||
static boolean isCollectionLike(@Nullable Object source) {
|
||||
|
||||
if (source == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ObjectUtils.isArray(source) || source instanceof Collection || source instanceof Iterator;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param entityClass should not be null.
|
||||
* @return the {@link MongoPersistentEntity#getCollection() collection name}.
|
||||
@@ -762,6 +782,24 @@ class EntityOperations {
|
||||
* @return
|
||||
*/
|
||||
Optional<Collation> getCollation(Query query);
|
||||
|
||||
/**
|
||||
* Derive the applicable {@link CollectionOptions} for the given type.
|
||||
*
|
||||
* @return never {@literal null}.
|
||||
* @since 3.3
|
||||
*/
|
||||
CollectionOptions getCollectionOptions();
|
||||
|
||||
/**
|
||||
* Map the fields of a given {@link TimeSeriesOptions} against the target domain type to consider potentially
|
||||
* annotated field names.
|
||||
*
|
||||
* @param options must not be {@literal null}.
|
||||
* @return never {@literal null}.
|
||||
* @since 3.3
|
||||
*/
|
||||
TimeSeriesOptions mapTimeSeriesOptions(TimeSeriesOptions options);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -801,6 +839,16 @@ class EntityOperations {
|
||||
|
||||
return query.getCollation();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CollectionOptions getCollectionOptions() {
|
||||
return CollectionOptions.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TimeSeriesOptions mapTimeSeriesOptions(TimeSeriesOptions options) {
|
||||
return options;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -838,6 +886,58 @@ class EntityOperations {
|
||||
|
||||
return Optional.ofNullable(entity.getCollation());
|
||||
}
|
||||
|
||||
@Override
|
||||
public CollectionOptions getCollectionOptions() {
|
||||
|
||||
CollectionOptions collectionOptions = CollectionOptions.empty();
|
||||
if (entity.hasCollation()) {
|
||||
collectionOptions = collectionOptions.collation(entity.getCollation());
|
||||
}
|
||||
|
||||
if (entity.isAnnotationPresent(TimeSeries.class)) {
|
||||
|
||||
TimeSeries timeSeries = entity.getRequiredAnnotation(TimeSeries.class);
|
||||
|
||||
if (entity.getPersistentProperty(timeSeries.timeField()) == null) {
|
||||
throw new MappingException(String.format("Time series field '%s' does not exist in type %s",
|
||||
timeSeries.timeField(), entity.getName()));
|
||||
}
|
||||
|
||||
TimeSeriesOptions options = TimeSeriesOptions.timeSeries(timeSeries.timeField());
|
||||
if (StringUtils.hasText(timeSeries.metaField())) {
|
||||
|
||||
if (entity.getPersistentProperty(timeSeries.metaField()) == null) {
|
||||
throw new MappingException(
|
||||
String.format("Meta field '%s' does not exist in type %s", timeSeries.metaField(), entity.getName()));
|
||||
}
|
||||
|
||||
options = options.metaField(timeSeries.metaField());
|
||||
}
|
||||
if (!Granularity.DEFAULT.equals(timeSeries.granularity())) {
|
||||
options = options.granularity(timeSeries.granularity());
|
||||
}
|
||||
collectionOptions = collectionOptions.timeSeries(options);
|
||||
}
|
||||
|
||||
return collectionOptions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TimeSeriesOptions mapTimeSeriesOptions(TimeSeriesOptions source) {
|
||||
|
||||
TimeSeriesOptions target = TimeSeriesOptions.timeSeries(mappedNameOrDefault(source.getTimeField()));
|
||||
|
||||
if (StringUtils.hasText(source.getMetaField())) {
|
||||
target = target.metaField(mappedNameOrDefault(source.getMetaField()));
|
||||
}
|
||||
return target.granularity(source.getGranularity());
|
||||
}
|
||||
|
||||
private String mappedNameOrDefault(String name) {
|
||||
MongoPersistentProperty persistentProperty = entity.getPersistentProperty(name);
|
||||
return persistentProperty != null ? persistentProperty.getFieldName() : name;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -125,6 +125,11 @@ public interface ExecutableFindOperation {
|
||||
|
||||
/**
|
||||
* Get the number of matching elements.
|
||||
* <br />
|
||||
* This method uses an {@link com.mongodb.client.MongoCollection#countDocuments(org.bson.conversions.Bson, com.mongodb.client.model.CountOptions) aggregation
|
||||
* execution} even for empty {@link Query queries} which may have an impact on performance, but guarantees shard,
|
||||
* session and transaction compliance. In case an inaccurate count satisfies the applications needs use
|
||||
* {@link MongoOperations#estimatedCount(String)} for empty queries instead.
|
||||
*
|
||||
* @return total number of matching elements.
|
||||
*/
|
||||
|
||||
@@ -89,7 +89,7 @@ public interface ExecutableUpdateOperation {
|
||||
|
||||
/**
|
||||
* Trigger
|
||||
* <a href="https://docs.mongodb.com/manual/reference/method/db.collection.findOneAndReplace/">findOneAndReplace<a/>
|
||||
* <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
|
||||
|
||||
@@ -17,7 +17,7 @@ package org.springframework.data.mongodb.core;
|
||||
|
||||
/**
|
||||
* Options for
|
||||
* <a href="https://docs.mongodb.com/manual/reference/method/db.collection.findOneAndReplace/">findOneAndReplace<a/>.
|
||||
* <a href="https://docs.mongodb.com/manual/reference/method/db.collection.findOneAndReplace/">findOneAndReplace</a>.
|
||||
* <br />
|
||||
* Defaults to
|
||||
* <dl>
|
||||
|
||||
@@ -115,6 +115,10 @@ abstract class IndexConverters {
|
||||
ops = ops.collation(fromDocument(indexOptions.get("collation", Document.class)));
|
||||
}
|
||||
|
||||
if (indexOptions.containsKey("wildcardProjection")) {
|
||||
ops.wildcardProjection(indexOptions.get("wildcardProjection", Document.class));
|
||||
}
|
||||
|
||||
return ops;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -156,5 +156,14 @@ public class MappedDocument {
|
||||
public List<ArrayFilter> getArrayFilters() {
|
||||
return delegate.getArrayFilters();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.query.UpdateDefinition#hasArrayFilters()
|
||||
*/
|
||||
@Override
|
||||
public boolean hasArrayFilters() {
|
||||
return delegate.hasArrayFilters();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,13 +20,20 @@ import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.bson.Document;
|
||||
|
||||
import org.springframework.data.mapping.PersistentProperty;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.mongodb.core.convert.MongoConverter;
|
||||
import org.springframework.data.mongodb.core.mapping.Encrypted;
|
||||
import org.springframework.data.mongodb.core.mapping.Field;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
|
||||
import org.springframework.data.mongodb.core.schema.IdentifiableJsonSchemaProperty.ArrayJsonSchemaProperty;
|
||||
import org.springframework.data.mongodb.core.schema.IdentifiableJsonSchemaProperty.EncryptedJsonSchemaProperty;
|
||||
import org.springframework.data.mongodb.core.schema.IdentifiableJsonSchemaProperty.ObjectJsonSchemaProperty;
|
||||
import org.springframework.data.mongodb.core.schema.JsonSchemaObject;
|
||||
import org.springframework.data.mongodb.core.schema.JsonSchemaObject.Type;
|
||||
@@ -34,10 +41,12 @@ import org.springframework.data.mongodb.core.schema.JsonSchemaProperty;
|
||||
import org.springframework.data.mongodb.core.schema.MongoJsonSchema;
|
||||
import org.springframework.data.mongodb.core.schema.MongoJsonSchema.MongoJsonSchemaBuilder;
|
||||
import org.springframework.data.mongodb.core.schema.TypedJsonSchemaObject;
|
||||
import org.springframework.data.util.ClassTypeInformation;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* {@link MongoJsonSchemaCreator} implementation using both {@link MongoConverter} and {@link MappingContext} to obtain
|
||||
@@ -52,6 +61,7 @@ class MappingMongoJsonSchemaCreator implements MongoJsonSchemaCreator {
|
||||
|
||||
private final MongoConverter converter;
|
||||
private final MappingContext<MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext;
|
||||
private final Predicate<JsonSchemaPropertyContext> filter;
|
||||
|
||||
/**
|
||||
* Create a new instance of {@link MappingMongoJsonSchemaCreator}.
|
||||
@@ -61,10 +71,24 @@ class MappingMongoJsonSchemaCreator implements MongoJsonSchemaCreator {
|
||||
@SuppressWarnings("unchecked")
|
||||
MappingMongoJsonSchemaCreator(MongoConverter converter) {
|
||||
|
||||
this(converter, (MappingContext<MongoPersistentEntity<?>, MongoPersistentProperty>) converter.getMappingContext(),
|
||||
(property) -> true);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
MappingMongoJsonSchemaCreator(MongoConverter converter,
|
||||
MappingContext<MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext,
|
||||
Predicate<JsonSchemaPropertyContext> filter) {
|
||||
|
||||
Assert.notNull(converter, "Converter must not be null!");
|
||||
this.converter = converter;
|
||||
this.mappingContext = (MappingContext<MongoPersistentEntity<?>, MongoPersistentProperty>) converter
|
||||
.getMappingContext();
|
||||
this.mappingContext = mappingContext;
|
||||
this.filter = filter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MongoJsonSchemaCreator filter(Predicate<JsonSchemaPropertyContext> filter) {
|
||||
return new MappingMongoJsonSchemaCreator(converter, mappingContext, filter);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -77,11 +101,29 @@ class MappingMongoJsonSchemaCreator implements MongoJsonSchemaCreator {
|
||||
MongoPersistentEntity<?> entity = mappingContext.getRequiredPersistentEntity(type);
|
||||
MongoJsonSchemaBuilder schemaBuilder = MongoJsonSchema.builder();
|
||||
|
||||
{
|
||||
Encrypted encrypted = entity.findAnnotation(Encrypted.class);
|
||||
if (encrypted != null) {
|
||||
|
||||
Document encryptionMetadata = new Document();
|
||||
|
||||
Collection<Object> encryptionKeyIds = entity.getEncryptionKeyIds();
|
||||
if (!CollectionUtils.isEmpty(encryptionKeyIds)) {
|
||||
encryptionMetadata.append("keyId", encryptionKeyIds);
|
||||
}
|
||||
|
||||
if (StringUtils.hasText(encrypted.algorithm())) {
|
||||
encryptionMetadata.append("algorithm", encrypted.algorithm());
|
||||
}
|
||||
|
||||
schemaBuilder.encryptionMetadata(encryptionMetadata);
|
||||
}
|
||||
}
|
||||
|
||||
List<JsonSchemaProperty> schemaProperties = computePropertiesForEntity(Collections.emptyList(), entity);
|
||||
schemaBuilder.properties(schemaProperties.toArray(new JsonSchemaProperty[0]));
|
||||
|
||||
return schemaBuilder.build();
|
||||
|
||||
}
|
||||
|
||||
private List<JsonSchemaProperty> computePropertiesForEntity(List<MongoPersistentProperty> path,
|
||||
@@ -93,6 +135,11 @@ class MappingMongoJsonSchemaCreator implements MongoJsonSchemaCreator {
|
||||
|
||||
List<MongoPersistentProperty> currentPath = new ArrayList<>(path);
|
||||
|
||||
if (!filter.test(new PropertyContext(
|
||||
currentPath.stream().map(PersistentProperty::getName).collect(Collectors.joining(".")), nested))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (path.contains(nested)) { // cycle guard
|
||||
schemaProperties.add(createSchemaProperty(computePropertyFieldName(CollectionUtils.lastElement(currentPath)),
|
||||
Object.class, false));
|
||||
@@ -114,21 +161,88 @@ class MappingMongoJsonSchemaCreator implements MongoJsonSchemaCreator {
|
||||
Class<?> rawTargetType = computeTargetType(property); // target type before conversion
|
||||
Class<?> targetType = converter.getTypeMapper().getWriteTargetTypeFor(rawTargetType); // conversion target type
|
||||
|
||||
if (property.isEntity() && ObjectUtils.nullSafeEquals(rawTargetType, targetType)) {
|
||||
if (!isCollection(property) && property.isEntity() && ObjectUtils.nullSafeEquals(rawTargetType, targetType)) {
|
||||
return createObjectSchemaPropertyForEntity(path, property, required);
|
||||
}
|
||||
|
||||
String fieldName = computePropertyFieldName(property);
|
||||
|
||||
if (property.isCollectionLike()) {
|
||||
return createSchemaProperty(fieldName, targetType, required);
|
||||
JsonSchemaProperty schemaProperty;
|
||||
if (isCollection(property)) {
|
||||
schemaProperty = createArraySchemaProperty(fieldName, property, required);
|
||||
} else if (property.isMap()) {
|
||||
return createSchemaProperty(fieldName, Type.objectType(), required);
|
||||
schemaProperty = createSchemaProperty(fieldName, Type.objectType(), required);
|
||||
} else if (ClassUtils.isAssignable(Enum.class, targetType)) {
|
||||
return createEnumSchemaProperty(fieldName, targetType, required);
|
||||
schemaProperty = createEnumSchemaProperty(fieldName, targetType, required);
|
||||
} else {
|
||||
schemaProperty = createSchemaProperty(fieldName, targetType, required);
|
||||
}
|
||||
|
||||
return createSchemaProperty(fieldName, targetType, required);
|
||||
return applyEncryptionDataIfNecessary(property, schemaProperty);
|
||||
}
|
||||
|
||||
private JsonSchemaProperty createArraySchemaProperty(String fieldName, MongoPersistentProperty property,
|
||||
boolean required) {
|
||||
|
||||
ArrayJsonSchemaProperty schemaProperty = JsonSchemaProperty.array(fieldName);
|
||||
|
||||
if (isSpecificType(property)) {
|
||||
schemaProperty = potentiallyEnhanceArraySchemaProperty(property, schemaProperty);
|
||||
}
|
||||
|
||||
return createPotentiallyRequiredSchemaProperty(schemaProperty, required);
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
private ArrayJsonSchemaProperty potentiallyEnhanceArraySchemaProperty(MongoPersistentProperty property,
|
||||
ArrayJsonSchemaProperty schemaProperty) {
|
||||
|
||||
MongoPersistentEntity<?> persistentEntity = mappingContext
|
||||
.getPersistentEntity(property.getTypeInformation().getRequiredComponentType());
|
||||
|
||||
if (persistentEntity != null) {
|
||||
|
||||
List<JsonSchemaProperty> nestedProperties = computePropertiesForEntity(Collections.emptyList(), persistentEntity);
|
||||
|
||||
if (nestedProperties.isEmpty()) {
|
||||
return schemaProperty;
|
||||
}
|
||||
|
||||
return schemaProperty
|
||||
.items(JsonSchemaObject.object().properties(nestedProperties.toArray(new JsonSchemaProperty[0])));
|
||||
}
|
||||
|
||||
if (ClassUtils.isAssignable(Enum.class, property.getActualType())) {
|
||||
|
||||
List<Object> possibleValues = getPossibleEnumValues((Class<Enum>) property.getActualType());
|
||||
|
||||
return schemaProperty
|
||||
.items(createSchemaObject(computeTargetType(property.getActualType(), possibleValues), possibleValues));
|
||||
}
|
||||
|
||||
return schemaProperty.items(JsonSchemaObject.of(property.getActualType()));
|
||||
}
|
||||
|
||||
private boolean isSpecificType(MongoPersistentProperty property) {
|
||||
return !ClassTypeInformation.OBJECT.equals(property.getTypeInformation().getActualType());
|
||||
}
|
||||
|
||||
private JsonSchemaProperty applyEncryptionDataIfNecessary(MongoPersistentProperty property,
|
||||
JsonSchemaProperty schemaProperty) {
|
||||
|
||||
Encrypted encrypted = property.findAnnotation(Encrypted.class);
|
||||
if (encrypted == null) {
|
||||
return schemaProperty;
|
||||
}
|
||||
|
||||
EncryptedJsonSchemaProperty enc = new EncryptedJsonSchemaProperty(schemaProperty);
|
||||
if (StringUtils.hasText(encrypted.algorithm())) {
|
||||
enc = enc.algorithm(encrypted.algorithm());
|
||||
}
|
||||
if (!ObjectUtils.isEmpty(encrypted.keyId())) {
|
||||
enc = enc.keys(property.getEncryptionKeyIds());
|
||||
}
|
||||
return enc;
|
||||
}
|
||||
|
||||
private JsonSchemaProperty createObjectSchemaPropertyForEntity(List<MongoPersistentProperty> path,
|
||||
@@ -142,15 +256,12 @@ class MappingMongoJsonSchemaCreator implements MongoJsonSchemaCreator {
|
||||
target.properties(nestedProperties.toArray(new JsonSchemaProperty[0])), required);
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
private JsonSchemaProperty createEnumSchemaProperty(String fieldName, Class<?> targetType, boolean required) {
|
||||
|
||||
List<Object> possibleValues = new ArrayList<>();
|
||||
List<Object> possibleValues = getPossibleEnumValues((Class<Enum>) targetType);
|
||||
|
||||
for (Object enumValue : EnumSet.allOf((Class) targetType)) {
|
||||
possibleValues.add(converter.convertToMongoType(enumValue));
|
||||
}
|
||||
|
||||
targetType = possibleValues.isEmpty() ? targetType : possibleValues.iterator().next().getClass();
|
||||
targetType = computeTargetType(targetType, possibleValues);
|
||||
return createSchemaProperty(fieldName, targetType, required, possibleValues);
|
||||
}
|
||||
|
||||
@@ -161,14 +272,20 @@ class MappingMongoJsonSchemaCreator implements MongoJsonSchemaCreator {
|
||||
JsonSchemaProperty createSchemaProperty(String fieldName, Object type, boolean required,
|
||||
Collection<?> possibleValues) {
|
||||
|
||||
TypedJsonSchemaObject schemaObject = createSchemaObject(type, possibleValues);
|
||||
|
||||
return createPotentiallyRequiredSchemaProperty(JsonSchemaProperty.named(fieldName).with(schemaObject), required);
|
||||
}
|
||||
|
||||
private TypedJsonSchemaObject createSchemaObject(Object type, Collection<?> possibleValues) {
|
||||
|
||||
TypedJsonSchemaObject schemaObject = type instanceof Type ? JsonSchemaObject.of(Type.class.cast(type))
|
||||
: JsonSchemaObject.of(Class.class.cast(type));
|
||||
|
||||
if (!CollectionUtils.isEmpty(possibleValues)) {
|
||||
schemaObject = schemaObject.possibleValues(possibleValues);
|
||||
}
|
||||
|
||||
return createPotentiallyRequiredSchemaProperty(JsonSchemaProperty.named(fieldName).with(schemaObject), required);
|
||||
return schemaObject;
|
||||
}
|
||||
|
||||
private String computePropertyFieldName(PersistentProperty property) {
|
||||
@@ -199,12 +316,53 @@ class MappingMongoJsonSchemaCreator implements MongoJsonSchemaCreator {
|
||||
return mongoProperty.getFieldType() != mongoProperty.getActualType() ? Object.class : mongoProperty.getFieldType();
|
||||
}
|
||||
|
||||
static JsonSchemaProperty createPotentiallyRequiredSchemaProperty(JsonSchemaProperty property, boolean required) {
|
||||
private static Class<?> computeTargetType(Class<?> fallback, List<Object> possibleValues) {
|
||||
return possibleValues.isEmpty() ? fallback : possibleValues.iterator().next().getClass();
|
||||
}
|
||||
|
||||
if (!required) {
|
||||
private <E extends Enum<E>> List<Object> getPossibleEnumValues(Class<E> targetType) {
|
||||
|
||||
EnumSet<E> enumSet = EnumSet.allOf(targetType);
|
||||
List<Object> possibleValues = new ArrayList<>(enumSet.size());
|
||||
|
||||
for (Object enumValue : enumSet) {
|
||||
possibleValues.add(converter.convertToMongoType(enumValue));
|
||||
}
|
||||
|
||||
return possibleValues;
|
||||
}
|
||||
|
||||
private static boolean isCollection(MongoPersistentProperty property) {
|
||||
return property.isCollectionLike() && !property.getType().equals(byte[].class);
|
||||
}
|
||||
|
||||
static JsonSchemaProperty createPotentiallyRequiredSchemaProperty(JsonSchemaProperty property, boolean required) {
|
||||
return required ? JsonSchemaProperty.required(property) : property;
|
||||
}
|
||||
|
||||
class PropertyContext implements JsonSchemaPropertyContext {
|
||||
|
||||
private final String path;
|
||||
private final MongoPersistentProperty property;
|
||||
|
||||
public PropertyContext(String path, MongoPersistentProperty property) {
|
||||
this.path = path;
|
||||
this.property = property;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MongoPersistentProperty getProperty() {
|
||||
return property;
|
||||
}
|
||||
|
||||
return JsonSchemaProperty.required(property);
|
||||
@Override
|
||||
public <T> MongoPersistentEntity<T> resolveEntity(MongoPersistentProperty property) {
|
||||
return (MongoPersistentEntity<T>) mappingContext.getPersistentEntity(property);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ import com.mongodb.MongoClientSettings.Builder;
|
||||
import com.mongodb.ReadConcern;
|
||||
import com.mongodb.ReadPreference;
|
||||
import com.mongodb.ServerAddress;
|
||||
import com.mongodb.ServerApi;
|
||||
import com.mongodb.WriteConcern;
|
||||
import com.mongodb.connection.ClusterConnectionMode;
|
||||
import com.mongodb.connection.ClusterType;
|
||||
@@ -113,6 +114,7 @@ public class MongoClientSettingsFactoryBean extends AbstractFactoryBean<MongoCli
|
||||
// encryption and retry
|
||||
|
||||
private @Nullable AutoEncryptionSettings autoEncryptionSettings;
|
||||
private @Nullable ServerApi serverApi;
|
||||
|
||||
/**
|
||||
* @param socketConnectTimeoutMS in msec
|
||||
@@ -395,6 +397,15 @@ public class MongoClientSettingsFactoryBean extends AbstractFactoryBean<MongoCli
|
||||
this.autoEncryptionSettings = autoEncryptionSettings;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param serverApi can be {@literal null}.
|
||||
* @see MongoClientSettings.Builder#serverApi(ServerApi)
|
||||
* @since 3.3
|
||||
*/
|
||||
public void setServerApi(@Nullable ServerApi serverApi) {
|
||||
this.serverApi = serverApi;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getObjectType() {
|
||||
return MongoClientSettings.class;
|
||||
@@ -476,9 +487,11 @@ public class MongoClientSettingsFactoryBean extends AbstractFactoryBean<MongoCli
|
||||
if (retryWrites != null) {
|
||||
builder = builder.retryWrites(retryWrites);
|
||||
}
|
||||
|
||||
if (uUidRepresentation != null) {
|
||||
builder.uuidRepresentation(uUidRepresentation);
|
||||
builder = builder.uuidRepresentation(uUidRepresentation);
|
||||
}
|
||||
if (serverApi != null) {
|
||||
builder = builder.serverApi(serverApi);
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
|
||||
@@ -33,7 +33,7 @@ import com.mongodb.client.MongoDatabase;
|
||||
/**
|
||||
* Common base class for usage with both {@link com.mongodb.client.MongoClients} defining common properties such as
|
||||
* database name and exception translator.
|
||||
* <p/>
|
||||
* <br />
|
||||
* Not intended to be used directly.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
|
||||
@@ -20,7 +20,7 @@ import org.springframework.dao.support.PersistenceExceptionTranslator;
|
||||
/**
|
||||
* Common base class for usage with both {@link com.mongodb.client.MongoClients} defining common properties such as
|
||||
* database name and exception translator.
|
||||
* <p/>
|
||||
* <br />
|
||||
* Not intended to be used directly.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
|
||||
@@ -21,6 +21,7 @@ import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.bson.BsonInvalidOperationException;
|
||||
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.dao.DataAccessResourceFailureException;
|
||||
import org.springframework.dao.DataIntegrityViolationException;
|
||||
@@ -39,6 +40,7 @@ import org.springframework.util.ClassUtils;
|
||||
import com.mongodb.MongoBulkWriteException;
|
||||
import com.mongodb.MongoException;
|
||||
import com.mongodb.MongoServerException;
|
||||
import com.mongodb.MongoSocketException;
|
||||
import com.mongodb.bulk.BulkWriteError;
|
||||
|
||||
/**
|
||||
@@ -49,6 +51,7 @@ import com.mongodb.bulk.BulkWriteError;
|
||||
* @author Oliver Gierke
|
||||
* @author Michal Vich
|
||||
* @author Christoph Strobl
|
||||
* @author Brice Vandeputte
|
||||
*/
|
||||
public class MongoExceptionTranslator implements PersistenceExceptionTranslator {
|
||||
|
||||
@@ -78,6 +81,10 @@ public class MongoExceptionTranslator implements PersistenceExceptionTranslator
|
||||
throw new InvalidDataAccessApiUsageException(ex.getMessage(), ex);
|
||||
}
|
||||
|
||||
if (ex instanceof MongoSocketException) {
|
||||
return new DataAccessResourceFailureException(ex.getMessage(), ex);
|
||||
}
|
||||
|
||||
String exception = ClassUtils.getShortName(ClassUtils.getUserClass(ex.getClass()));
|
||||
|
||||
if (DUPLICATE_KEY_EXCEPTIONS.contains(exception)) {
|
||||
|
||||
@@ -15,7 +15,23 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.springframework.data.mapping.PersistentProperty;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
|
||||
import org.springframework.data.mongodb.core.convert.MongoConverter;
|
||||
import org.springframework.data.mongodb.core.convert.MongoCustomConversions;
|
||||
import org.springframework.data.mongodb.core.convert.NoOpDbRefResolver;
|
||||
import org.springframework.data.mongodb.core.mapping.Encrypted;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
|
||||
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.mapping.Unwrapped.Nullable;
|
||||
import org.springframework.data.mongodb.core.schema.JsonSchemaProperty;
|
||||
import org.springframework.data.mongodb.core.schema.MongoJsonSchema;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@@ -24,6 +40,7 @@ import org.springframework.util.Assert;
|
||||
* following mapping rules.
|
||||
* <p>
|
||||
* <strong>Required Properties</strong>
|
||||
* </p>
|
||||
* <ul>
|
||||
* <li>Properties of primitive type</li>
|
||||
* </ul>
|
||||
@@ -45,7 +62,8 @@ import org.springframework.util.Assert;
|
||||
* {@link org.springframework.data.annotation.Id _id} properties using types that can be converted into
|
||||
* {@link org.bson.types.ObjectId} like {@link String} will be mapped to {@code type : 'object'} unless there is more
|
||||
* specific information available via the {@link org.springframework.data.mongodb.core.mapping.MongoId} annotation.
|
||||
* </p>
|
||||
|
||||
* {@link Encrypted} properties will contain {@literal encrypt} information.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.2
|
||||
@@ -60,6 +78,88 @@ public interface MongoJsonSchemaCreator {
|
||||
*/
|
||||
MongoJsonSchema createSchemaFor(Class<?> type);
|
||||
|
||||
/**
|
||||
* Filter matching {@link JsonSchemaProperty properties}.
|
||||
*
|
||||
* @param filter the {@link Predicate} to evaluate for inclusion. Must not be {@literal null}.
|
||||
* @return new instance of {@link MongoJsonSchemaCreator}.
|
||||
* @since 3.3
|
||||
*/
|
||||
MongoJsonSchemaCreator filter(Predicate<JsonSchemaPropertyContext> filter);
|
||||
|
||||
/**
|
||||
* The context in which a specific {@link #getProperty()} is encountered during schema creation.
|
||||
*
|
||||
* @since 3.3
|
||||
*/
|
||||
interface JsonSchemaPropertyContext {
|
||||
|
||||
/**
|
||||
* The path to a given field/property in dot notation.
|
||||
*
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
String getPath();
|
||||
|
||||
/**
|
||||
* The current property.
|
||||
*
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
MongoPersistentProperty getProperty();
|
||||
|
||||
/**
|
||||
* Obtain the {@link MongoPersistentEntity} for a given property.
|
||||
*
|
||||
* @param property must not be {@literal null}.
|
||||
* @param <T>
|
||||
* @return {@literal null} if the property is not an entity. It is nevertheless recommend to check
|
||||
* {@link PersistentProperty#isEntity()} first.
|
||||
*/
|
||||
@Nullable
|
||||
<T> MongoPersistentEntity<T> resolveEntity(MongoPersistentProperty property);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A filter {@link Predicate} that matches {@link Encrypted encrypted properties} and those having nested ones.
|
||||
*
|
||||
* @return new instance of {@link Predicate}.
|
||||
* @since 3.3
|
||||
*/
|
||||
static Predicate<JsonSchemaPropertyContext> encryptedOnly() {
|
||||
|
||||
return new Predicate<JsonSchemaPropertyContext>() {
|
||||
|
||||
// cycle guard
|
||||
private final Set<MongoPersistentProperty> seen = new HashSet<>();
|
||||
|
||||
@Override
|
||||
public boolean test(JsonSchemaPropertyContext context) {
|
||||
return extracted(context.getProperty(), context);
|
||||
}
|
||||
|
||||
private boolean extracted(MongoPersistentProperty property, JsonSchemaPropertyContext context) {
|
||||
if (property.isAnnotationPresent(Encrypted.class)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!property.isEntity() || seen.contains(property)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
seen.add(property);
|
||||
|
||||
for (MongoPersistentProperty nested : context.resolveEntity(property)) {
|
||||
if (extracted(nested, context)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link MongoJsonSchemaCreator} that is aware of conversions applied by the given
|
||||
* {@link MongoConverter}.
|
||||
@@ -72,4 +172,41 @@ public interface MongoJsonSchemaCreator {
|
||||
Assert.notNull(mongoConverter, "MongoConverter must not be null!");
|
||||
return new MappingMongoJsonSchemaCreator(mongoConverter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link MongoJsonSchemaCreator} that is aware of type mappings and potential
|
||||
* {@link org.springframework.data.spel.spi.EvaluationContextExtension extensions}.
|
||||
*
|
||||
* @param mappingContext must not be {@literal null}.
|
||||
* @return new instance of {@link MongoJsonSchemaCreator}.
|
||||
* @since 3.3
|
||||
*/
|
||||
static MongoJsonSchemaCreator create(MappingContext mappingContext) {
|
||||
|
||||
MappingMongoConverter converter = new MappingMongoConverter(NoOpDbRefResolver.INSTANCE, mappingContext);
|
||||
converter.setCustomConversions(MongoCustomConversions.create(config -> {}));
|
||||
converter.afterPropertiesSet();
|
||||
|
||||
return create(converter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link MongoJsonSchemaCreator} that does not consider potential extensions - suitable for testing. We
|
||||
* recommend to use {@link #create(MappingContext)}.
|
||||
*
|
||||
* @return new instance of {@link MongoJsonSchemaCreator}.
|
||||
* @since 3.3
|
||||
*/
|
||||
static MongoJsonSchemaCreator create() {
|
||||
|
||||
MongoMappingContext mappingContext = new MongoMappingContext();
|
||||
mappingContext.setSimpleTypeHolder(MongoSimpleTypes.HOLDER);
|
||||
mappingContext.afterPropertiesSet();
|
||||
|
||||
MappingMongoConverter converter = new MappingMongoConverter(NoOpDbRefResolver.INSTANCE, mappingContext);
|
||||
converter.setCustomConversions(MongoCustomConversions.create(config -> {}));
|
||||
converter.afterPropertiesSet();
|
||||
|
||||
return create(converter);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ import com.mongodb.client.result.UpdateResult;
|
||||
* Interface that specifies a basic set of MongoDB operations. Implemented by {@link MongoTemplate}. Not often used but
|
||||
* a useful option for extensibility and testability (as it can be easily mocked, stubbed, or be the target of a JDK
|
||||
* proxy).
|
||||
* <p/>
|
||||
* <br />
|
||||
* <strong>NOTE:</strong> Some operations cannot be executed within a MongoDB transaction. Please refer to the MongoDB
|
||||
* specific documentation to learn more about <a href="https://docs.mongodb.com/manual/core/transactions/">Multi
|
||||
* Document Transactions</a>.
|
||||
@@ -125,7 +125,7 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
|
||||
/**
|
||||
* Executes a {@link DbCallback} translating any exceptions as necessary.
|
||||
* <p/>
|
||||
* <br />
|
||||
* Allows for returning a result object, that is a domain object or a collection of domain objects.
|
||||
*
|
||||
* @param action callback object that specifies the MongoDB actions to perform on the passed in DB instance. Must not
|
||||
@@ -138,7 +138,7 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
|
||||
/**
|
||||
* Executes the given {@link CollectionCallback} on the entity collection of the specified class.
|
||||
* <p/>
|
||||
* <br />
|
||||
* Allows for returning a result object, that is a domain object or a collection of domain objects.
|
||||
*
|
||||
* @param entityClass class that determines the collection to use. Must not be {@literal null}.
|
||||
@@ -151,7 +151,7 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
|
||||
/**
|
||||
* Executes the given {@link CollectionCallback} on the collection of the given name.
|
||||
* <p/>
|
||||
* <br />
|
||||
* Allows for returning a result object, that is a domain object or a collection of domain objects.
|
||||
*
|
||||
* @param collectionName the name of the collection that specifies which {@link MongoCollection} instance will be
|
||||
@@ -176,7 +176,7 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
/**
|
||||
* Obtain a {@link ClientSession session} bound instance of {@link SessionScoped} binding the {@link ClientSession}
|
||||
* provided by the given {@link Supplier} to each and every command issued against MongoDB.
|
||||
* <p/>
|
||||
* <br />
|
||||
* <strong>Note:</strong> It is up to the caller to manage the {@link ClientSession} lifecycle. Use the
|
||||
* {@link SessionScoped#execute(SessionCallback, Consumer)} hook to potentially close the {@link ClientSession}.
|
||||
*
|
||||
@@ -212,7 +212,7 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
|
||||
/**
|
||||
* Obtain a {@link ClientSession} bound instance of {@link MongoOperations}.
|
||||
* <p/>
|
||||
* <br />
|
||||
* <strong>Note:</strong> It is up to the caller to manage the {@link ClientSession} lifecycle.
|
||||
*
|
||||
* @param session must not be {@literal null}.
|
||||
@@ -300,7 +300,7 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
* is created on first interaction with the server. Collections can be explicitly created via
|
||||
* {@link #createCollection(Class)}. Please make sure to check if the collection {@link #collectionExists(Class)
|
||||
* exists} first.
|
||||
* <p/>
|
||||
* <br />
|
||||
* Translate any exceptions as necessary.
|
||||
*
|
||||
* @param collectionName name of the collection. Must not be {@literal null}.
|
||||
@@ -310,7 +310,7 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
|
||||
/**
|
||||
* Check to see if a collection with a name indicated by the entity class exists.
|
||||
* <p/>
|
||||
* <br />
|
||||
* Translate any exceptions as necessary.
|
||||
*
|
||||
* @param entityClass class that determines the name of the collection. Must not be {@literal null}.
|
||||
@@ -320,7 +320,7 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
|
||||
/**
|
||||
* Check to see if a collection with a given name exists.
|
||||
* <p/>
|
||||
* <br />
|
||||
* Translate any exceptions as necessary.
|
||||
*
|
||||
* @param collectionName name of the collection. Must not be {@literal null}.
|
||||
@@ -330,7 +330,7 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
|
||||
/**
|
||||
* Drop the collection with the name indicated by the entity class.
|
||||
* <p/>
|
||||
* <br />
|
||||
* Translate any exceptions as necessary.
|
||||
*
|
||||
* @param entityClass class that determines the collection to drop/delete. Must not be {@literal null}.
|
||||
@@ -339,7 +339,7 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
|
||||
/**
|
||||
* Drop the collection with the given name.
|
||||
* <p/>
|
||||
* <br />
|
||||
* Translate any exceptions as necessary.
|
||||
*
|
||||
* @param collectionName name of the collection to drop/delete.
|
||||
@@ -403,10 +403,10 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
|
||||
/**
|
||||
* Query for a list of objects of type T from the collection used by the entity class.
|
||||
* <p/>
|
||||
* <br />
|
||||
* The object is converted from the MongoDB native representation using an instance of {@see MongoConverter}. Unless
|
||||
* configured otherwise, an instance of {@link MappingMongoConverter} will be used.
|
||||
* <p/>
|
||||
* <br />
|
||||
* If your collection does not contain a homogeneous collection of types, this operation will not be an efficient way
|
||||
* to map objects since the test for class type is done in the client and not on the server.
|
||||
*
|
||||
@@ -417,10 +417,10 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
|
||||
/**
|
||||
* Query for a list of objects of type T from the specified collection.
|
||||
* <p/>
|
||||
* <br />
|
||||
* The object is converted from the MongoDB native representation using an instance of {@see MongoConverter}. Unless
|
||||
* configured otherwise, an instance of {@link MappingMongoConverter} will be used.
|
||||
* <p/>
|
||||
* <br />
|
||||
* If your collection does not contain a homogeneous collection of types, this operation will not be an efficient way
|
||||
* to map objects since the test for class type is done in the client and not on the server.
|
||||
*
|
||||
@@ -539,11 +539,11 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
|
||||
/**
|
||||
* Execute an aggregation operation backed by a Mongo DB {@link com.mongodb.client.AggregateIterable}.
|
||||
* <p/>
|
||||
* <br />
|
||||
* Returns a {@link CloseableIterator} that wraps the a Mongo DB {@link com.mongodb.client.AggregateIterable} that
|
||||
* needs to be closed. The raw results will be mapped to the given entity class and are returned as stream. The name
|
||||
* of the inputCollection is derived from the inputType of the aggregation.
|
||||
* <p/>
|
||||
* <br />
|
||||
* Aggregation streaming can't be used with {@link AggregationOptions#isExplain() aggregation explain}. Enabling
|
||||
* explanation mode will throw an {@link IllegalArgumentException}.
|
||||
*
|
||||
@@ -557,10 +557,10 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
|
||||
/**
|
||||
* Execute an aggregation operation backed by a Mongo DB {@link com.mongodb.client.AggregateIterable}.
|
||||
* <p/>
|
||||
* <br />
|
||||
* Returns a {@link CloseableIterator} that wraps the a Mongo DB {@link com.mongodb.client.AggregateIterable} that
|
||||
* needs to be closed. The raw results will be mapped to the given entity class.
|
||||
* <p/>
|
||||
* <br />
|
||||
* Aggregation streaming can't be used with {@link AggregationOptions#isExplain() aggregation explain}. Enabling
|
||||
* explanation mode will throw an {@link IllegalArgumentException}.
|
||||
*
|
||||
@@ -576,10 +576,10 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
|
||||
/**
|
||||
* Execute an aggregation operation backed by a Mongo DB {@link com.mongodb.client.AggregateIterable}.
|
||||
* <p/>
|
||||
* <br />
|
||||
* Returns a {@link CloseableIterator} that wraps the a Mongo DB {@link com.mongodb.client.AggregateIterable} that
|
||||
* needs to be closed. The raw results will be mapped to the given entity class.
|
||||
* <p/>
|
||||
* <br />
|
||||
* Aggregation streaming can't be used with {@link AggregationOptions#isExplain() aggregation explain}. Enabling
|
||||
* explanation mode will throw an {@link IllegalArgumentException}.
|
||||
*
|
||||
@@ -702,10 +702,10 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
/**
|
||||
* Map the results of an ad-hoc query on the collection for the entity class to a single instance of an object of the
|
||||
* specified type.
|
||||
* <p/>
|
||||
* <br />
|
||||
* The object is converted from the MongoDB native representation using an instance of {@see MongoConverter}. Unless
|
||||
* configured otherwise, an instance of {@link MappingMongoConverter} will be used.
|
||||
* <p/>
|
||||
* <br />
|
||||
* The query is specified as a {@link Query} which can be created either using the {@link BasicQuery} or the more
|
||||
* feature rich {@link Query}.
|
||||
*
|
||||
@@ -720,10 +720,10 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
/**
|
||||
* Map the results of an ad-hoc query on the specified collection to a single instance of an object of the specified
|
||||
* type.
|
||||
* <p/>
|
||||
* <br />
|
||||
* The object is converted from the MongoDB native representation using an instance of {@see MongoConverter}. Unless
|
||||
* configured otherwise, an instance of {@link MappingMongoConverter} will be used.
|
||||
* <p/>
|
||||
* <br />
|
||||
* The query is specified as a {@link Query} which can be created either using the {@link BasicQuery} or the more
|
||||
* feature rich {@link Query}.
|
||||
*
|
||||
@@ -768,10 +768,10 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
|
||||
/**
|
||||
* Map the results of an ad-hoc query on the collection for the entity class to a List of the specified type.
|
||||
* <p/>
|
||||
* <br />
|
||||
* The object is converted from the MongoDB native representation using an instance of {@see MongoConverter}. Unless
|
||||
* configured otherwise, an instance of {@link MappingMongoConverter} will be used.
|
||||
* <p/>
|
||||
* <br />
|
||||
* The query is specified as a {@link Query} which can be created either using the {@link BasicQuery} or the more
|
||||
* feature rich {@link Query}.
|
||||
*
|
||||
@@ -784,10 +784,10 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
|
||||
/**
|
||||
* Map the results of an ad-hoc query on the specified collection to a List of the specified type.
|
||||
* <p/>
|
||||
* <br />
|
||||
* The object is converted from the MongoDB native representation using an instance of {@see MongoConverter}. Unless
|
||||
* configured otherwise, an instance of {@link MappingMongoConverter} will be used.
|
||||
* <p/>
|
||||
* <br />
|
||||
* The query is specified as a {@link Query} which can be created either using the {@link BasicQuery} or the more
|
||||
* feature rich {@link Query}.
|
||||
*
|
||||
@@ -881,7 +881,7 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers <a href="https://docs.mongodb.org/manual/reference/method/db.collection.findAndModify/">findAndModify <a/>
|
||||
* Triggers <a href="https://docs.mongodb.org/manual/reference/method/db.collection.findAndModify/">findAndModify </a>
|
||||
* to apply provided {@link Update} on documents matching {@link Criteria} of given {@link Query}.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional
|
||||
@@ -897,7 +897,7 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
<T> T findAndModify(Query query, UpdateDefinition update, Class<T> entityClass);
|
||||
|
||||
/**
|
||||
* Triggers <a href="https://docs.mongodb.org/manual/reference/method/db.collection.findAndModify/">findAndModify <a/>
|
||||
* Triggers <a href="https://docs.mongodb.org/manual/reference/method/db.collection.findAndModify/">findAndModify </a>
|
||||
* to apply provided {@link Update} on documents matching {@link Criteria} of given {@link Query}.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional
|
||||
@@ -914,7 +914,7 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
<T> T findAndModify(Query query, UpdateDefinition update, Class<T> entityClass, String collectionName);
|
||||
|
||||
/**
|
||||
* Triggers <a href="https://docs.mongodb.org/manual/reference/method/db.collection.findAndModify/">findAndModify <a/>
|
||||
* Triggers <a href="https://docs.mongodb.org/manual/reference/method/db.collection.findAndModify/">findAndModify </a>
|
||||
* to apply provided {@link Update} on documents matching {@link Criteria} of given {@link Query} taking
|
||||
* {@link FindAndModifyOptions} into account.
|
||||
*
|
||||
@@ -934,7 +934,7 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
<T> T findAndModify(Query query, UpdateDefinition update, FindAndModifyOptions options, Class<T> entityClass);
|
||||
|
||||
/**
|
||||
* Triggers <a href="https://docs.mongodb.org/manual/reference/method/db.collection.findAndModify/">findAndModify <a/>
|
||||
* Triggers <a href="https://docs.mongodb.org/manual/reference/method/db.collection.findAndModify/">findAndModify </a>
|
||||
* to apply provided {@link Update} on documents matching {@link Criteria} of given {@link Query} taking
|
||||
* {@link FindAndModifyOptions} into account.
|
||||
*
|
||||
@@ -957,7 +957,7 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
|
||||
/**
|
||||
* Triggers
|
||||
* <a href="https://docs.mongodb.com/manual/reference/method/db.collection.findOneAndReplace/">findOneAndReplace<a/>
|
||||
* <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 />
|
||||
@@ -977,7 +977,7 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
|
||||
/**
|
||||
* Triggers
|
||||
* <a href="https://docs.mongodb.com/manual/reference/method/db.collection.findOneAndReplace/">findOneAndReplace<a/>
|
||||
* <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 />
|
||||
@@ -997,7 +997,7 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
|
||||
/**
|
||||
* Triggers
|
||||
* <a href="https://docs.mongodb.com/manual/reference/method/db.collection.findOneAndReplace/">findOneAndReplace<a/>
|
||||
* <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}.
|
||||
@@ -1018,7 +1018,7 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
|
||||
/**
|
||||
* Triggers
|
||||
* <a href="https://docs.mongodb.com/manual/reference/method/db.collection.findOneAndReplace/">findOneAndReplace<a/>
|
||||
* <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}.
|
||||
@@ -1041,7 +1041,7 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
|
||||
/**
|
||||
* Triggers
|
||||
* <a href="https://docs.mongodb.com/manual/reference/method/db.collection.findOneAndReplace/">findOneAndReplace<a/>
|
||||
* <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}.
|
||||
@@ -1066,7 +1066,7 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
|
||||
/**
|
||||
* Triggers
|
||||
* <a href="https://docs.mongodb.com/manual/reference/method/db.collection.findOneAndReplace/">findOneAndReplace<a/>
|
||||
* <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}.
|
||||
@@ -1094,7 +1094,7 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
|
||||
/**
|
||||
* Triggers
|
||||
* <a href="https://docs.mongodb.com/manual/reference/method/db.collection.findOneAndReplace/">findOneAndReplace<a/>
|
||||
* <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}.
|
||||
@@ -1120,9 +1120,9 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
* 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
|
||||
* database.
|
||||
* <p/>
|
||||
* <br />
|
||||
* The object is converted from the MongoDB native representation using an instance of {@see MongoConverter}.
|
||||
* <p/>
|
||||
* <br />
|
||||
* The query is specified as a {@link Query} which can be created either using the {@link BasicQuery} or the more
|
||||
* feature rich {@link Query}.
|
||||
*
|
||||
@@ -1137,10 +1137,10 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
/**
|
||||
* Map the results of an ad-hoc query on the specified collection 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 database.
|
||||
* <p/>
|
||||
* <br />
|
||||
* The object is converted from the MongoDB native representation using an instance of {@see MongoConverter}. Unless
|
||||
* configured otherwise, an instance of {@link MappingMongoConverter} will be used.
|
||||
* <p/>
|
||||
* <br />
|
||||
* The query is specified as a {@link Query} which can be created either using the {@link BasicQuery} or the more
|
||||
* feature rich {@link Query}.
|
||||
*
|
||||
@@ -1160,6 +1160,12 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
* influence on the resulting number of documents found as those values are passed on to the server and potentially
|
||||
* limit the range and order within which the server performs the count operation. Use an {@literal unpaged} query to
|
||||
* count all matches.
|
||||
* <br />
|
||||
* This method uses an
|
||||
* {@link com.mongodb.client.MongoCollection#countDocuments(org.bson.conversions.Bson, com.mongodb.client.model.CountOptions)
|
||||
* aggregation execution} even for empty {@link Query queries} which may have an impact on performance, but guarantees
|
||||
* shard, session and transaction compliance. In case an inaccurate count satisfies the applications needs use
|
||||
* {@link #estimatedCount(Class)} for empty queries instead.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the criteria used to find documents. Must not be
|
||||
* {@literal null}.
|
||||
@@ -1176,6 +1182,12 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
* influence on the resulting number of documents found as those values are passed on to the server and potentially
|
||||
* limit the range and order within which the server performs the count operation. Use an {@literal unpaged} query to
|
||||
* count all matches.
|
||||
* <br />
|
||||
* This method uses an
|
||||
* {@link com.mongodb.client.MongoCollection#countDocuments(org.bson.conversions.Bson, com.mongodb.client.model.CountOptions)
|
||||
* aggregation execution} even for empty {@link Query queries} which may have an impact on performance, but guarantees
|
||||
* shard, session and transaction compliance. In case an inaccurate count satisfies the applications needs use
|
||||
* {@link #estimatedCount(String)} for empty queries instead.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the criteria used to find documents.
|
||||
* @param collectionName must not be {@literal null} or empty.
|
||||
@@ -1187,6 +1199,9 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
/**
|
||||
* Estimate the number of documents, in the collection {@link #getCollectionName(Class) identified by the given type},
|
||||
* based on collection statistics.
|
||||
* <br />
|
||||
* Please make sure to read the MongoDB reference documentation about limitations on eg. sharded cluster or inside
|
||||
* transactions.
|
||||
*
|
||||
* @param entityClass must not be {@literal null}.
|
||||
* @return the estimated number of documents.
|
||||
@@ -1200,6 +1215,9 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
|
||||
/**
|
||||
* Estimate the number of documents in the given collection based on collection statistics.
|
||||
* <br />
|
||||
* Please make sure to read the MongoDB reference documentation about limitations on eg. sharded cluster or inside
|
||||
* transactions.
|
||||
*
|
||||
* @param collectionName must not be {@literal null}.
|
||||
* @return the estimated number of documents.
|
||||
@@ -1214,6 +1232,12 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
* influence on the resulting number of documents found as those values are passed on to the server and potentially
|
||||
* limit the range and order within which the server performs the count operation. Use an {@literal unpaged} query to
|
||||
* count all matches.
|
||||
* <br />
|
||||
* This method uses an
|
||||
* {@link com.mongodb.client.MongoCollection#countDocuments(org.bson.conversions.Bson, com.mongodb.client.model.CountOptions)
|
||||
* aggregation execution} even for empty {@link Query queries} which may have an impact on performance, but guarantees
|
||||
* shard, session and transaction compliance. In case an inaccurate count satisfies the applications needs use
|
||||
* {@link #estimatedCount(String)} for empty queries instead.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the criteria used to find documents. Must not be
|
||||
* {@literal null}.
|
||||
@@ -1225,34 +1249,39 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
|
||||
/**
|
||||
* Insert the object into the collection for the entity type of the object to save.
|
||||
* <p/>
|
||||
* <br />
|
||||
* The object is converted to the MongoDB native representation using an instance of {@see MongoConverter}.
|
||||
* <p/>
|
||||
* <br />
|
||||
* If your object has an "Id' property, it will be set with the generated Id from MongoDB. If your Id property is a
|
||||
* String then MongoDB ObjectId will be used to populate that string. Otherwise, the conversion from ObjectId to your
|
||||
* property type will be handled by Spring's BeanWrapper class that leverages Type Conversion API. See
|
||||
* <a href="https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#validation" > Spring's
|
||||
* Type Conversion"</a> for more details.
|
||||
* <p/>
|
||||
* <p/>
|
||||
* <br />
|
||||
* Insert is used to initially store the object into the database. To update an existing object use the save method.
|
||||
* <br />
|
||||
* The {@code objectToSave} must not be collection-like.
|
||||
*
|
||||
* @param objectToSave the object to store in the collection. Must not be {@literal null}.
|
||||
* @return the inserted object.
|
||||
* @throws IllegalArgumentException in case the {@code objectToSave} is collection-like.
|
||||
*/
|
||||
<T> T insert(T objectToSave);
|
||||
|
||||
/**
|
||||
* Insert the object into the specified collection.
|
||||
* <p/>
|
||||
* <br />
|
||||
* The object is converted to the MongoDB native representation using an instance of {@see MongoConverter}. Unless
|
||||
* configured otherwise, an instance of {@link MappingMongoConverter} will be used.
|
||||
* <p/>
|
||||
* <br />
|
||||
* Insert is used to initially store the object into the database. To update an existing object use the save method.
|
||||
* <br />
|
||||
* The {@code objectToSave} must not be collection-like.
|
||||
*
|
||||
* @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.
|
||||
* @throws IllegalArgumentException in case the {@code objectToSave} is collection-like.
|
||||
*/
|
||||
<T> T insert(T objectToSave, String collectionName);
|
||||
|
||||
@@ -1286,37 +1315,42 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
/**
|
||||
* Save the object to the collection for the entity type of the object to save. This will perform an insert if the
|
||||
* object is not already present, that is an 'upsert'.
|
||||
* <p/>
|
||||
* <br />
|
||||
* The object is converted to the MongoDB native representation using an instance of {@see MongoConverter}. Unless
|
||||
* configured otherwise, an instance of {@link MappingMongoConverter} will be used.
|
||||
* <p/>
|
||||
* <br />
|
||||
* If your object has an "Id' property, it will be set with the generated Id from MongoDB. If your Id property is a
|
||||
* String then MongoDB ObjectId will be used to populate that string. Otherwise, the conversion from ObjectId to your
|
||||
* property type will be handled by Spring's BeanWrapper class that leverages Type Conversion API. See
|
||||
* <a href="https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#validation" > Spring's
|
||||
* Type Conversion"</a> for more details.
|
||||
* <br />
|
||||
* The {@code objectToSave} must not be collection-like.
|
||||
*
|
||||
* @param objectToSave the object to store in the collection. Must not be {@literal null}.
|
||||
* @return the saved object.
|
||||
* @throws IllegalArgumentException in case the {@code objectToSave} is collection-like.
|
||||
*/
|
||||
<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
|
||||
* is an 'upsert'.
|
||||
* <p/>
|
||||
* <br />
|
||||
* The object is converted to the MongoDB native representation using an instance of {@see MongoConverter}. Unless
|
||||
* configured otherwise, an instance of {@link MappingMongoConverter} will be used.
|
||||
* <p/>
|
||||
* <br />
|
||||
* If your object has an "Id' property, it will be set with the generated Id from MongoDB. If your Id property is a
|
||||
* String then MongoDB ObjectId will be used to populate that string. Otherwise, the conversion from ObjectId to your
|
||||
* property type will be handled by Spring's BeanWrapper class that leverages Type Conversion API. See <a
|
||||
* https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#validation">Spring's Type
|
||||
* Conversion"</a> for more details.
|
||||
* property type will be handled by Spring's BeanWrapper class that leverages Type Conversion API.
|
||||
* See <a href="https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#validation">Spring's Type Conversion</a> for more details.
|
||||
* <br />
|
||||
* The {@code objectToSave} must not be collection-like.
|
||||
*
|
||||
* @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.
|
||||
* @throws IllegalArgumentException in case the {@code objectToSave} is collection-like.
|
||||
*/
|
||||
<T> T save(T objectToSave, String collectionName);
|
||||
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright 2021 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
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.mongodb.core;
|
||||
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
import com.mongodb.ServerApi;
|
||||
import com.mongodb.ServerApi.Builder;
|
||||
import com.mongodb.ServerApiVersion;
|
||||
|
||||
/**
|
||||
* {@link FactoryBean} for creating {@link ServerApi} using the {@link ServerApi.Builder}.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 3.3
|
||||
*/
|
||||
public class MongoServerApiFactoryBean implements FactoryBean<ServerApi> {
|
||||
|
||||
private String version;
|
||||
private @Nullable Boolean deprecationErrors;
|
||||
private @Nullable Boolean strict;
|
||||
|
||||
/**
|
||||
* @param version the version string either as the enum name or the server version value.
|
||||
* @see ServerApiVersion
|
||||
*/
|
||||
public void setVersion(String version) {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param deprecationErrors
|
||||
* @see ServerApi.Builder#deprecationErrors(boolean)
|
||||
*/
|
||||
public void setDeprecationErrors(@Nullable Boolean deprecationErrors) {
|
||||
this.deprecationErrors = deprecationErrors;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param strict
|
||||
* @see ServerApi.Builder#strict(boolean)
|
||||
*/
|
||||
public void setStrict(@Nullable Boolean strict) {
|
||||
this.strict = strict;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public ServerApi getObject() throws Exception {
|
||||
|
||||
Builder builder = ServerApi.builder().version(version());
|
||||
|
||||
if (deprecationErrors != null) {
|
||||
builder = builder.deprecationErrors(deprecationErrors);
|
||||
}
|
||||
if (strict != null) {
|
||||
builder = builder.strict(strict);
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Class<?> getObjectType() {
|
||||
return ServerApi.class;
|
||||
}
|
||||
|
||||
private ServerApiVersion version() {
|
||||
try {
|
||||
// lookup by name eg. 'V1'
|
||||
return ObjectUtils.caseInsensitiveValueOf(ServerApiVersion.values(), version);
|
||||
} catch (IllegalArgumentException e) {
|
||||
// or just the version number, eg. just '1'
|
||||
return ServerApiVersion.findByValue(version);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -47,6 +47,7 @@ import org.springframework.data.geo.Distance;
|
||||
import org.springframework.data.geo.GeoResult;
|
||||
import org.springframework.data.geo.GeoResults;
|
||||
import org.springframework.data.geo.Metric;
|
||||
import org.springframework.data.mapping.MappingException;
|
||||
import org.springframework.data.mapping.callback.EntityCallbacks;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.mongodb.MongoDatabaseFactory;
|
||||
@@ -55,6 +56,7 @@ import org.springframework.data.mongodb.SessionSynchronization;
|
||||
import org.springframework.data.mongodb.core.BulkOperations.BulkMode;
|
||||
import org.springframework.data.mongodb.core.DefaultBulkOperations.BulkOperationContext;
|
||||
import org.springframework.data.mongodb.core.EntityOperations.AdaptibleEntity;
|
||||
import org.springframework.data.mongodb.core.QueryOperations.AggregationDefinition;
|
||||
import org.springframework.data.mongodb.core.QueryOperations.CountContext;
|
||||
import org.springframework.data.mongodb.core.QueryOperations.DeleteContext;
|
||||
import org.springframework.data.mongodb.core.QueryOperations.DistinctQueryContext;
|
||||
@@ -97,12 +99,12 @@ import org.springframework.data.mongodb.core.query.NearQuery;
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
import org.springframework.data.mongodb.core.query.UpdateDefinition;
|
||||
import org.springframework.data.mongodb.core.query.UpdateDefinition.ArrayFilter;
|
||||
import org.springframework.data.mongodb.core.timeseries.Granularity;
|
||||
import org.springframework.data.mongodb.core.validation.Validator;
|
||||
import org.springframework.data.mongodb.util.BsonUtils;
|
||||
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
|
||||
import org.springframework.data.util.CloseableIterator;
|
||||
import org.springframework.data.util.Optionals;
|
||||
import org.springframework.jca.cci.core.ConnectionCallback;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
@@ -157,22 +159,12 @@ import com.mongodb.client.result.UpdateResult;
|
||||
* @author Roman Puchkovskiy
|
||||
* @author Yadhukrishna S Pai
|
||||
* @author Anton Barkan
|
||||
* @author Bartłomiej Mazur
|
||||
*/
|
||||
public class MongoTemplate implements MongoOperations, ApplicationContextAware, IndexOperationsProvider {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(MongoTemplate.class);
|
||||
private static final WriteResultChecking DEFAULT_WRITE_RESULT_CHECKING = WriteResultChecking.NONE;
|
||||
private static final Collection<String> ITERABLE_CLASSES;
|
||||
|
||||
static {
|
||||
|
||||
Set<String> iterableClasses = new HashSet<>();
|
||||
iterableClasses.add(List.class.getName());
|
||||
iterableClasses.add(Collection.class.getName());
|
||||
iterableClasses.add(Iterator.class.getName());
|
||||
|
||||
ITERABLE_CLASSES = Collections.unmodifiableCollection(iterableClasses);
|
||||
}
|
||||
|
||||
private final MongoConverter mongoConverter;
|
||||
private final MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext;
|
||||
@@ -346,7 +338,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
/**
|
||||
* Set the {@link EntityCallbacks} instance to use when invoking
|
||||
* {@link org.springframework.data.mapping.callback.EntityCallback callbacks} like the {@link BeforeSaveCallback}.
|
||||
* <p />
|
||||
* <br />
|
||||
* Overrides potentially existing {@link EntityCallbacks}.
|
||||
*
|
||||
* @param entityCallbacks must not be {@literal null}.
|
||||
@@ -606,7 +598,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
* @see org.springframework.data.mongodb.core.MongoOperations#createCollection(java.lang.Class)
|
||||
*/
|
||||
public <T> MongoCollection<Document> createCollection(Class<T> entityClass) {
|
||||
return createCollection(entityClass, CollectionOptions.empty());
|
||||
return createCollection(entityClass, operations.forType(entityClass).getCollectionOptions());
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -715,12 +707,17 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public IndexOperations indexOps(String collectionName) {
|
||||
return indexOps(collectionName, null);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.ExecutableInsertOperation#indexOps(java.lang.String)
|
||||
*/
|
||||
public IndexOperations indexOps(String collectionName) {
|
||||
return new DefaultIndexOperations(this, collectionName, null);
|
||||
public IndexOperations indexOps(String collectionName, @Nullable Class<?> type) {
|
||||
return new DefaultIndexOperations(this, collectionName, type);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -728,7 +725,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
* @see org.springframework.data.mongodb.core.ExecutableInsertOperation#indexOps(java.lang.Class)
|
||||
*/
|
||||
public IndexOperations indexOps(Class<?> entityClass) {
|
||||
return new DefaultIndexOperations(this, getCollectionName(entityClass), entityClass);
|
||||
return indexOps(getCollectionName(entityClass), entityClass);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -977,7 +974,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
for (Document element : results) {
|
||||
|
||||
GeoResult<T> geoResult = callback.doWith(element);
|
||||
aggregate = aggregate.add(new BigDecimal(geoResult.getDistance().getValue()));
|
||||
aggregate = aggregate.add(BigDecimal.valueOf(geoResult.getDistance().getValue()));
|
||||
result.add(geoResult);
|
||||
}
|
||||
|
||||
@@ -1177,11 +1174,28 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
return (T) doInsert(collectionName, objectToSave, this.mongoConverter);
|
||||
}
|
||||
|
||||
protected void ensureNotIterable(@Nullable Object o) {
|
||||
if (o != null) {
|
||||
if (o.getClass().isArray() || ITERABLE_CLASSES.contains(o.getClass().getName())) {
|
||||
throw new IllegalArgumentException("Cannot use a collection here.");
|
||||
}
|
||||
/**
|
||||
* Ensure the given {@literal source} is not an {@link java.lang.reflect.Array}, {@link Collection} or
|
||||
* {@link Iterator}.
|
||||
*
|
||||
* @param source can be {@literal null}.
|
||||
* @deprecated since 3.2. Call {@link #ensureNotCollectionLike(Object)} instead.
|
||||
*/
|
||||
protected void ensureNotIterable(@Nullable Object source) {
|
||||
ensureNotCollectionLike(source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure the given {@literal source} is not an {@link java.lang.reflect.Array}, {@link Collection} or
|
||||
* {@link Iterator}.
|
||||
*
|
||||
* @param source can be {@literal null}.
|
||||
* @since 3.2.
|
||||
*/
|
||||
protected void ensureNotCollectionLike(@Nullable Object source) {
|
||||
|
||||
if (EntityOperations.isCollectionLike(source)) {
|
||||
throw new IllegalArgumentException("Cannot use a collection here.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1364,13 +1378,13 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
|
||||
Assert.notNull(objectToSave, "Object to save must not be null!");
|
||||
Assert.hasText(collectionName, "Collection name must not be null or empty!");
|
||||
ensureNotCollectionLike(objectToSave);
|
||||
|
||||
AdaptibleEntity<T> source = operations.forEntity(objectToSave, mongoConverter.getConversionService());
|
||||
|
||||
return source.isVersionedEntity() //
|
||||
? doSaveVersioned(source, collectionName) //
|
||||
: (T) doSave(collectionName, objectToSave, this.mongoConverter);
|
||||
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@@ -1988,7 +2002,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
public <O> AggregationResults<O> aggregate(Aggregation aggregation, Class<?> inputType, Class<O> outputType) {
|
||||
|
||||
return aggregate(aggregation, getCollectionName(inputType), outputType,
|
||||
new TypeBasedAggregationOperationContext(inputType, mappingContext, queryMapper));
|
||||
queryOperations.createAggregation(aggregation, inputType).getAggregationOperationContext());
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
@@ -2095,9 +2109,13 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
Assert.notNull(aggregation, "Aggregation pipeline must not be null!");
|
||||
Assert.notNull(outputType, "Output type must not be null!");
|
||||
|
||||
AggregationOperationContext contextToUse = new AggregationUtil(queryMapper, mappingContext)
|
||||
.prepareAggregationContext(aggregation, context);
|
||||
return doAggregate(aggregation, collectionName, outputType, contextToUse);
|
||||
return doAggregate(aggregation, collectionName, outputType,
|
||||
queryOperations.createAggregation(aggregation, context));
|
||||
}
|
||||
|
||||
private <O> AggregationResults<O> doAggregate(Aggregation aggregation, String collectionName, Class<O> outputType,
|
||||
AggregationDefinition context) {
|
||||
return doAggregate(aggregation, collectionName, outputType, context.getAggregationOperationContext());
|
||||
}
|
||||
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
@@ -2185,11 +2203,10 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
Assert.notNull(outputType, "Output type must not be null!");
|
||||
Assert.isTrue(!aggregation.getOptions().isExplain(), "Can't use explain option with streaming!");
|
||||
|
||||
AggregationUtil aggregationUtil = new AggregationUtil(queryMapper, mappingContext);
|
||||
AggregationOperationContext rootContext = aggregationUtil.prepareAggregationContext(aggregation, context);
|
||||
AggregationDefinition aggregationDefinition = queryOperations.createAggregation(aggregation, context);
|
||||
|
||||
AggregationOptions options = aggregation.getOptions();
|
||||
List<Document> pipeline = aggregationUtil.createPipeline(aggregation, rootContext);
|
||||
List<Document> pipeline = aggregationDefinition.getAggregationPipeline();
|
||||
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("Streaming aggregation: {} in collection {}", serializeToJsonSafely(pipeline), collectionName);
|
||||
@@ -2418,6 +2435,20 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
co.validationOptions(options);
|
||||
}
|
||||
|
||||
if (collectionOptions.containsKey("timeseries")) {
|
||||
|
||||
Document timeSeries = collectionOptions.get("timeseries", Document.class);
|
||||
com.mongodb.client.model.TimeSeriesOptions options = new com.mongodb.client.model.TimeSeriesOptions(
|
||||
timeSeries.getString("timeField"));
|
||||
if (timeSeries.containsKey("metaField")) {
|
||||
options.metaField(timeSeries.getString("metaField"));
|
||||
}
|
||||
if (timeSeries.containsKey("granularity")) {
|
||||
options.granularity(TimeSeriesGranularity.valueOf(timeSeries.getString("granularity").toUpperCase()));
|
||||
}
|
||||
co.timeSeriesOptions(options);
|
||||
}
|
||||
|
||||
db.createCollection(collectionName, co);
|
||||
|
||||
MongoCollection<Document> coll = db.getCollection(collectionName, Document.class);
|
||||
@@ -2572,6 +2603,19 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
|
||||
collectionOptions.getValidationOptions().ifPresent(it -> it.getValidator() //
|
||||
.ifPresent(val -> doc.put("validator", getMappedValidator(val, targetType))));
|
||||
|
||||
collectionOptions.getTimeSeriesOptions().map(operations.forType(targetType)::mapTimeSeriesOptions)
|
||||
.ifPresent(it -> {
|
||||
|
||||
Document timeseries = new Document("timeField", it.getTimeField());
|
||||
if (StringUtils.hasText(it.getMetaField())) {
|
||||
timeseries.append("metaField", it.getMetaField());
|
||||
}
|
||||
if (!Granularity.DEFAULT.equals(it.getGranularity())) {
|
||||
timeseries.append("granularity", it.getGranularity().name().toLowerCase());
|
||||
}
|
||||
doc.put("timeseries", timeseries);
|
||||
});
|
||||
}
|
||||
|
||||
return doc;
|
||||
@@ -2619,7 +2663,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
/**
|
||||
* Map the results of an ad-hoc query on the default MongoDB collection to an object using the template's converter.
|
||||
* The first document that matches the query is returned and also removed from the collection in the database.
|
||||
* <p/>
|
||||
* <br />
|
||||
* The query document is specified as a standard Document and so is the fields specification.
|
||||
*
|
||||
* @param collectionName name of the collection to retrieve the objects from
|
||||
@@ -2735,25 +2779,24 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
* Internal method using callbacks to do queries against the datastore that requires reading a single object from a
|
||||
* collection of objects. It will take the following steps
|
||||
* <ol>
|
||||
* <li>Execute the given {@link ConnectionCallback} for a {@link Document}.</li>
|
||||
* <li>Execute the given {@link CollectionCallback} for a {@link Document}.</li>
|
||||
* <li>Apply the given {@link DocumentCallback} to each of the {@link Document}s to obtain the result.</li>
|
||||
* <ol>
|
||||
*
|
||||
* @param <T>
|
||||
* @param collectionCallback the callback to retrieve the {@link Document} with
|
||||
* @param objectCallback the {@link DocumentCallback} to transform {@link Document}s into the actual domain type
|
||||
* @param documentCallback the {@link DocumentCallback} to transform {@link Document}s into the actual domain type
|
||||
* @param collectionName the collection to be queried
|
||||
* @return
|
||||
*/
|
||||
@Nullable
|
||||
private <T> T executeFindOneInternal(CollectionCallback<Document> collectionCallback,
|
||||
DocumentCallback<T> objectCallback, String collectionName) {
|
||||
DocumentCallback<T> documentCallback, String collectionName) {
|
||||
|
||||
try {
|
||||
|
||||
T result = objectCallback
|
||||
.doWith(collectionCallback.doInCollection(getAndPrepareCollection(doGetDatabase(), collectionName)));
|
||||
return result;
|
||||
Document document = collectionCallback.doInCollection(getAndPrepareCollection(doGetDatabase(), collectionName));
|
||||
return document != null ? documentCallback.doWith(document) : null;
|
||||
} catch (RuntimeException e) {
|
||||
throw potentiallyConvertRuntimeException(e, exceptionTranslator);
|
||||
}
|
||||
@@ -2763,7 +2806,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
* Internal method using callback to do queries against the datastore that requires reading a collection of objects.
|
||||
* It will take the following steps
|
||||
* <ol>
|
||||
* <li>Execute the given {@link ConnectionCallback} for a {@link FindIterable}.</li>
|
||||
* <li>Execute the given {@link CollectionCallback} for a {@link FindIterable}.</li>
|
||||
* <li>Prepare that {@link FindIterable} with the given {@link CursorPreparer} (will be skipped if
|
||||
* {@link CursorPreparer} is {@literal null}</li>
|
||||
* <li>Iterate over the {@link FindIterable} and applies the given {@link DocumentCallback} to each of the
|
||||
@@ -2773,36 +2816,27 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
* @param <T>
|
||||
* @param collectionCallback the callback to retrieve the {@link FindIterable} with
|
||||
* @param preparer the {@link CursorPreparer} to potentially modify the {@link FindIterable} before iterating over it
|
||||
* @param objectCallback the {@link DocumentCallback} to transform {@link Document}s into the actual domain type
|
||||
* @param documentCallback the {@link DocumentCallback} to transform {@link Document}s into the actual domain type
|
||||
* @param collectionName the collection to be queried
|
||||
* @return
|
||||
*/
|
||||
private <T> List<T> executeFindMultiInternal(CollectionCallback<FindIterable<Document>> collectionCallback,
|
||||
CursorPreparer preparer, DocumentCallback<T> objectCallback, String collectionName) {
|
||||
CursorPreparer preparer, DocumentCallback<T> documentCallback, String collectionName) {
|
||||
|
||||
try {
|
||||
|
||||
MongoCursor<Document> cursor = null;
|
||||
|
||||
try {
|
||||
|
||||
cursor = preparer
|
||||
.initiateFind(getAndPrepareCollection(doGetDatabase(), collectionName), collectionCallback::doInCollection)
|
||||
.iterator();
|
||||
try (MongoCursor<Document> cursor = preparer
|
||||
.initiateFind(getAndPrepareCollection(doGetDatabase(), collectionName), collectionCallback::doInCollection)
|
||||
.iterator()) {
|
||||
|
||||
List<T> result = new ArrayList<>();
|
||||
|
||||
while (cursor.hasNext()) {
|
||||
Document object = cursor.next();
|
||||
result.add(objectCallback.doWith(object));
|
||||
result.add(documentCallback.doWith(object));
|
||||
}
|
||||
|
||||
return result;
|
||||
} finally {
|
||||
|
||||
if (cursor != null) {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
throw potentiallyConvertRuntimeException(e, exceptionTranslator);
|
||||
@@ -2812,23 +2846,12 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
private void executeQueryInternal(CollectionCallback<FindIterable<Document>> collectionCallback,
|
||||
CursorPreparer preparer, DocumentCallbackHandler callbackHandler, String collectionName) {
|
||||
|
||||
try {
|
||||
try (MongoCursor<Document> cursor = preparer
|
||||
.initiateFind(getAndPrepareCollection(doGetDatabase(), collectionName), collectionCallback::doInCollection)
|
||||
.iterator()) {
|
||||
|
||||
MongoCursor<Document> cursor = null;
|
||||
|
||||
try {
|
||||
|
||||
cursor = preparer
|
||||
.initiateFind(getAndPrepareCollection(doGetDatabase(), collectionName), collectionCallback::doInCollection)
|
||||
.iterator();
|
||||
|
||||
while (cursor.hasNext()) {
|
||||
callbackHandler.processDocument(cursor.next());
|
||||
}
|
||||
} finally {
|
||||
if (cursor != null) {
|
||||
cursor.close();
|
||||
}
|
||||
while (cursor.hasNext()) {
|
||||
callbackHandler.processDocument(cursor.next());
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
throw potentiallyConvertRuntimeException(e, exceptionTranslator);
|
||||
@@ -3127,8 +3150,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
|
||||
interface DocumentCallback<T> {
|
||||
|
||||
@Nullable
|
||||
T doWith(@Nullable Document object);
|
||||
T doWith(Document object);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -3152,22 +3174,19 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
this.collectionName = collectionName;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public T doWith(@Nullable Document document) {
|
||||
public T doWith(Document document) {
|
||||
|
||||
T source = null;
|
||||
maybeEmitEvent(new AfterLoadEvent<>(document, type, collectionName));
|
||||
T entity = reader.read(type, document);
|
||||
|
||||
if (document != null) {
|
||||
maybeEmitEvent(new AfterLoadEvent<>(document, type, collectionName));
|
||||
source = reader.read(type, document);
|
||||
if (entity == null) {
|
||||
throw new MappingException(String.format("EntityReader %s returned null", reader));
|
||||
}
|
||||
|
||||
if (source != null) {
|
||||
maybeEmitEvent(new AfterConvertEvent<>(document, source, collectionName));
|
||||
source = maybeCallAfterConvert(source, document, collectionName);
|
||||
}
|
||||
maybeEmitEvent(new AfterConvertEvent<>(document, entity, collectionName));
|
||||
entity = maybeCallAfterConvert(entity, document, collectionName);
|
||||
|
||||
return source;
|
||||
return entity;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3200,8 +3219,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
* @see org.springframework.data.mongodb.core.MongoTemplate.DocumentCallback#doWith(org.bson.Document)
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@Nullable
|
||||
public T doWith(@Nullable Document document) {
|
||||
public T doWith(Document document) {
|
||||
|
||||
if (document == null) {
|
||||
return null;
|
||||
@@ -3212,15 +3230,16 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
|
||||
maybeEmitEvent(new AfterLoadEvent<>(document, targetType, collectionName));
|
||||
|
||||
Object source = reader.read(typeToRead, document);
|
||||
Object result = targetType.isInterface() ? projectionFactory.createProjection(targetType, source) : source;
|
||||
Object entity = reader.read(typeToRead, document);
|
||||
|
||||
if (result != null) {
|
||||
maybeEmitEvent(new AfterConvertEvent<>(document, result, collectionName));
|
||||
result = maybeCallAfterConvert(result, document, collectionName);
|
||||
if (entity == null) {
|
||||
throw new MappingException(String.format("EntityReader %s returned null", reader));
|
||||
}
|
||||
|
||||
return (T) result;
|
||||
Object result = targetType.isInterface() ? projectionFactory.createProjection(targetType, entity) : entity;
|
||||
|
||||
maybeEmitEvent(new AfterConvertEvent<>(document, result, collectionName));
|
||||
return (T) maybeCallAfterConvert(result, document, collectionName);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3357,8 +3376,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
this.metric = metric;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public GeoResult<T> doWith(@Nullable Document object) {
|
||||
public GeoResult<T> doWith(Document object) {
|
||||
|
||||
double distance = Double.NaN;
|
||||
if (object.containsKey(distanceField)) {
|
||||
@@ -3385,10 +3403,6 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
|
||||
/**
|
||||
* Creates a new {@link CloseableIterableCursorAdapter} backed by the given {@link MongoCollection}.
|
||||
*
|
||||
* @param cursor
|
||||
* @param exceptionTranslator
|
||||
* @param objectReadCallback
|
||||
*/
|
||||
CloseableIterableCursorAdapter(MongoIterable<Document> cursor, PersistenceExceptionTranslator exceptionTranslator,
|
||||
DocumentCallback<T> objectReadCallback) {
|
||||
@@ -3432,8 +3446,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
|
||||
try {
|
||||
Document item = cursor.next();
|
||||
T converted = objectReadCallback.doWith(item);
|
||||
return converted;
|
||||
return objectReadCallback.doWith(item);
|
||||
} catch (RuntimeException ex) {
|
||||
throw potentiallyConvertRuntimeException(ex, exceptionTranslator);
|
||||
}
|
||||
@@ -3459,14 +3472,27 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated since 3.1.4. Use {@link #getMongoDatabaseFactory()} instead.
|
||||
* @return the {@link MongoDatabaseFactory} in use.
|
||||
*/
|
||||
@Deprecated
|
||||
public MongoDatabaseFactory getMongoDbFactory() {
|
||||
return getMongoDatabaseFactory();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the {@link MongoDatabaseFactory} in use.
|
||||
* @since 3.1.4
|
||||
*/
|
||||
public MongoDatabaseFactory getMongoDatabaseFactory() {
|
||||
return mongoDbFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link MongoTemplate} extension bound to a specific {@link ClientSession} that is applied when interacting with the
|
||||
* server through the driver API.
|
||||
* <p />
|
||||
* <br />
|
||||
* The prepare steps for {@link MongoDatabase} and {@link MongoCollection} proxy the target and invoke the desired
|
||||
* target method matching the actual arguments plus a {@link ClientSession}.
|
||||
*
|
||||
|
||||
@@ -17,6 +17,7 @@ package org.springframework.data.mongodb.core;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
@@ -31,11 +32,17 @@ import org.springframework.data.mapping.PropertyPath;
|
||||
import org.springframework.data.mapping.PropertyReferenceException;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.mongodb.CodecRegistryProvider;
|
||||
import org.springframework.data.mongodb.MongoExpression;
|
||||
import org.springframework.data.mongodb.core.MappedDocument.MappedUpdate;
|
||||
import org.springframework.data.mongodb.core.aggregation.Aggregation;
|
||||
import org.springframework.data.mongodb.core.aggregation.AggregationExpression;
|
||||
import org.springframework.data.mongodb.core.aggregation.AggregationOperationContext;
|
||||
import org.springframework.data.mongodb.core.aggregation.AggregationOptions;
|
||||
import org.springframework.data.mongodb.core.aggregation.AggregationPipeline;
|
||||
import org.springframework.data.mongodb.core.aggregation.AggregationUpdate;
|
||||
import org.springframework.data.mongodb.core.aggregation.RelaxedTypeBasedAggregationOperationContext;
|
||||
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.convert.UpdateMapper;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
|
||||
@@ -48,6 +55,7 @@ import org.springframework.data.mongodb.core.query.UpdateDefinition;
|
||||
import org.springframework.data.mongodb.core.query.UpdateDefinition.ArrayFilter;
|
||||
import org.springframework.data.mongodb.util.BsonUtils;
|
||||
import org.springframework.data.projection.ProjectionFactory;
|
||||
import org.springframework.data.util.Lazy;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
@@ -194,9 +202,34 @@ class QueryOperations {
|
||||
return new DeleteContext(query, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link AggregationDefinition} for the given {@link Aggregation}.
|
||||
*
|
||||
* @param aggregation must not be {@literal null}.
|
||||
* @param inputType fallback mapping type in case of untyped aggregation. Can be {@literal null}.
|
||||
* @return new instance of {@link AggregationDefinition}.
|
||||
* @since 3.2
|
||||
*/
|
||||
AggregationDefinition createAggregation(Aggregation aggregation, @Nullable Class<?> inputType) {
|
||||
return new AggregationDefinition(aggregation, inputType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link AggregationDefinition} for the given {@link Aggregation}.
|
||||
*
|
||||
* @param aggregation must not be {@literal null}.
|
||||
* @param aggregationOperationContext the {@link AggregationOperationContext} to use. Can be {@literal null}.
|
||||
* @return new instance of {@link AggregationDefinition}.
|
||||
* @since 3.2
|
||||
*/
|
||||
AggregationDefinition createAggregation(Aggregation aggregation,
|
||||
@Nullable AggregationOperationContext aggregationOperationContext) {
|
||||
return new AggregationDefinition(aggregation, aggregationOperationContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link QueryContext} encapsulates common tasks required to convert a {@link Query} into its MongoDB document
|
||||
* representation, mapping fieldnames, as well as determinging and applying {@link Collation collations}.
|
||||
* representation, mapping field names, as well as determining and applying {@link Collation collations}.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
@@ -205,7 +238,7 @@ class QueryOperations {
|
||||
private final Query query;
|
||||
|
||||
/**
|
||||
* Create new a {@link QueryContext} instance from the given {@literal query} (can be eihter a {@link Query} or a
|
||||
* Create new a {@link QueryContext} instance from the given {@literal query} (can be either a {@link Query} or a
|
||||
* plain {@link Document}.
|
||||
*
|
||||
* @param query can be {@literal null}.
|
||||
@@ -258,7 +291,21 @@ class QueryOperations {
|
||||
Document getMappedFields(@Nullable MongoPersistentEntity<?> entity, Class<?> targetType,
|
||||
ProjectionFactory projectionFactory) {
|
||||
|
||||
Document fields = query.getFieldsObject();
|
||||
Document fields = new Document();
|
||||
|
||||
for (Entry<String, Object> entry : query.getFieldsObject().entrySet()) {
|
||||
|
||||
if (entry.getValue() instanceof MongoExpression) {
|
||||
|
||||
AggregationOperationContext ctx = entity == null ? Aggregation.DEFAULT_CONTEXT
|
||||
: new RelaxedTypeBasedAggregationOperationContext(entity.getType(), mappingContext, queryMapper);
|
||||
|
||||
fields.put(entry.getKey(), AggregationExpression.from((MongoExpression) entry.getValue()).toDocument(ctx));
|
||||
} else {
|
||||
fields.put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
Document mappedFields = fields;
|
||||
|
||||
if (entity == null) {
|
||||
@@ -275,7 +322,7 @@ class QueryOperations {
|
||||
mappingContext.getRequiredPersistentEntity(targetType));
|
||||
}
|
||||
|
||||
if (entity != null && entity.hasTextScoreProperty() && !query.getQueryObject().containsKey("$text")) {
|
||||
if (entity.hasTextScoreProperty() && !query.getQueryObject().containsKey("$text")) {
|
||||
mappedFields.remove(entity.getTextScoreProperty().getFieldName());
|
||||
}
|
||||
|
||||
@@ -341,7 +388,8 @@ class QueryOperations {
|
||||
}
|
||||
|
||||
@Override
|
||||
Document getMappedFields(@Nullable MongoPersistentEntity<?> entity, Class<?> targetType, ProjectionFactory projectionFactory) {
|
||||
Document getMappedFields(@Nullable MongoPersistentEntity<?> entity, Class<?> targetType,
|
||||
ProjectionFactory projectionFactory) {
|
||||
return getMappedFields(entity);
|
||||
}
|
||||
|
||||
@@ -565,7 +613,7 @@ class QueryOperations {
|
||||
|
||||
UpdateContext(MappedDocument update, boolean upsert) {
|
||||
|
||||
super(new BasicQuery(new Document(BsonUtils.asMap(update.getIdFilter()))));
|
||||
super(new BasicQuery(BsonUtils.asDocument(update.getIdFilter())));
|
||||
this.multi = false;
|
||||
this.upsert = upsert;
|
||||
this.mappedDocument = update;
|
||||
@@ -658,7 +706,8 @@ class QueryOperations {
|
||||
: mappedDocument != null ? mappedDocument.getDocument() : getMappedUpdate(domainType);
|
||||
|
||||
Document filterWithShardKey = new Document(filter);
|
||||
getMappedShardKeyFields(domainType).forEach(key -> filterWithShardKey.putIfAbsent(key, shardKeySource.get(key)));
|
||||
getMappedShardKeyFields(domainType)
|
||||
.forEach(key -> filterWithShardKey.putIfAbsent(key, BsonUtils.resolveValue(shardKeySource, key)));
|
||||
|
||||
return filterWithShardKey;
|
||||
}
|
||||
@@ -709,7 +758,8 @@ class QueryOperations {
|
||||
|
||||
Class<?> type = domainType != null ? domainType : Object.class;
|
||||
|
||||
AggregationOperationContext context = new RelaxedTypeBasedAggregationOperationContext(type, mappingContext, queryMapper);
|
||||
AggregationOperationContext context = new RelaxedTypeBasedAggregationOperationContext(type, mappingContext,
|
||||
queryMapper);
|
||||
return aggregationUtil.createPipeline((AggregationUpdate) update, context);
|
||||
}
|
||||
|
||||
@@ -759,4 +809,105 @@ class QueryOperations {
|
||||
return multi;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A value object that encapsulates common tasks required when running {@literal aggregations}.
|
||||
*
|
||||
* @since 3.2
|
||||
*/
|
||||
class AggregationDefinition {
|
||||
|
||||
private final Aggregation aggregation;
|
||||
private final Lazy<AggregationOperationContext> aggregationOperationContext;
|
||||
private final Lazy<List<Document>> pipeline;
|
||||
private final @Nullable Class<?> inputType;
|
||||
|
||||
/**
|
||||
* Creates new instance of {@link AggregationDefinition} extracting the input type from either the
|
||||
* {@link org.springframework.data.mongodb.core.aggregation.Aggregation} in case of a {@link TypedAggregation} or
|
||||
* the given {@literal aggregationOperationContext} if present. <br />
|
||||
* Creates a new {@link AggregationOperationContext} if none given, based on the {@link Aggregation} input type and
|
||||
* the desired {@link AggregationOptions#getDomainTypeMapping() domain type mapping}. <br />
|
||||
* Pipelines are mapped on first access of {@link #getAggregationPipeline()} and cached for reuse.
|
||||
*
|
||||
* @param aggregation the source aggregation.
|
||||
* @param aggregationOperationContext can be {@literal null}.
|
||||
*/
|
||||
AggregationDefinition(Aggregation aggregation, @Nullable AggregationOperationContext aggregationOperationContext) {
|
||||
|
||||
this.aggregation = aggregation;
|
||||
|
||||
if (aggregation instanceof TypedAggregation) {
|
||||
this.inputType = ((TypedAggregation<?>) aggregation).getInputType();
|
||||
} else if (aggregationOperationContext instanceof TypeBasedAggregationOperationContext) {
|
||||
this.inputType = ((TypeBasedAggregationOperationContext) aggregationOperationContext).getType();
|
||||
} else {
|
||||
this.inputType = null;
|
||||
}
|
||||
|
||||
this.aggregationOperationContext = Lazy.of(() -> aggregationOperationContext != null ? aggregationOperationContext
|
||||
: aggregationUtil.createAggregationContext(aggregation, getInputType()));
|
||||
this.pipeline = Lazy.of(() -> aggregationUtil.createPipeline(this.aggregation, getAggregationOperationContext()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new instance of {@link AggregationDefinition} extracting the input type from either the
|
||||
* {@link org.springframework.data.mongodb.core.aggregation.Aggregation} in case of a {@link TypedAggregation} or
|
||||
* the given {@literal aggregationOperationContext} if present. <br />
|
||||
* Creates a new {@link AggregationOperationContext} based on the {@link Aggregation} input type and the desired
|
||||
* {@link AggregationOptions#getDomainTypeMapping() domain type mapping}. <br />
|
||||
* Pipelines are mapped on first access of {@link #getAggregationPipeline()} and cached for reuse.
|
||||
*
|
||||
* @param aggregation the source aggregation.
|
||||
* @param inputType can be {@literal null}.
|
||||
*/
|
||||
AggregationDefinition(Aggregation aggregation, @Nullable Class<?> inputType) {
|
||||
|
||||
this.aggregation = aggregation;
|
||||
|
||||
if (aggregation instanceof TypedAggregation) {
|
||||
this.inputType = ((TypedAggregation<?>) aggregation).getInputType();
|
||||
} else {
|
||||
this.inputType = inputType;
|
||||
}
|
||||
|
||||
this.aggregationOperationContext = Lazy
|
||||
.of(() -> aggregationUtil.createAggregationContext(aggregation, getInputType()));
|
||||
this.pipeline = Lazy.of(() -> aggregationUtil.createPipeline(this.aggregation, getAggregationOperationContext()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain the already mapped pipeline.
|
||||
*
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
List<Document> getAggregationPipeline() {
|
||||
return pipeline.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@literal true} if the last aggregation stage is either {@literal $out} or {@literal $merge}.
|
||||
* @see AggregationPipeline#isOutOrMerge()
|
||||
*/
|
||||
boolean isOutOrMerge() {
|
||||
return aggregation.getPipeline().isOutOrMerge();
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain the {@link AggregationOperationContext} used for mapping the pipeline.
|
||||
*
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
AggregationOperationContext getAggregationOperationContext() {
|
||||
return aggregationOperationContext.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the input type to map the pipeline against. Can be {@literal null}.
|
||||
*/
|
||||
@Nullable
|
||||
Class<?> getInputType() {
|
||||
return inputType;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ public interface ReactiveChangeStreamOperation {
|
||||
/**
|
||||
* Start listening to changes. The stream will not be completed unless the {@link org.reactivestreams.Subscription}
|
||||
* is {@link org.reactivestreams.Subscription#cancel() canceled}.
|
||||
* <p />
|
||||
* <br />
|
||||
* However, the stream may become dead, or invalid, if all watched collections, databases are dropped.
|
||||
*/
|
||||
Flux<ChangeStreamEvent<T>> listen();
|
||||
|
||||
@@ -91,10 +91,10 @@ public interface ReactiveFindOperation {
|
||||
* 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 />
|
||||
* <br />
|
||||
* 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 />
|
||||
* <br />
|
||||
* 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.
|
||||
@@ -106,6 +106,12 @@ public interface ReactiveFindOperation {
|
||||
|
||||
/**
|
||||
* Get the number of matching elements.
|
||||
* <br />
|
||||
* This method uses an
|
||||
* {@link com.mongodb.reactivestreams.client.MongoCollection#countDocuments(org.bson.conversions.Bson, com.mongodb.client.model.CountOptions)
|
||||
* aggregation execution} even for empty {@link Query queries} which may have an impact on performance, but
|
||||
* guarantees shard, session and transaction compliance. In case an inaccurate count satisfies the applications
|
||||
* needs use {@link ReactiveMongoOperations#estimatedCount(String)} for empty queries instead.
|
||||
*
|
||||
* @return {@link Mono} emitting total number of matching elements. Never {@literal null}.
|
||||
*/
|
||||
|
||||
@@ -59,7 +59,7 @@ import com.mongodb.reactivestreams.client.MongoCollection;
|
||||
* Implemented by {@link ReactiveMongoTemplate}. Not often used but a useful option for extensibility and testability
|
||||
* (as it can be easily mocked, stubbed, or be the target of a JDK proxy). Command execution using
|
||||
* {@link ReactiveMongoOperations} is deferred until subscriber subscribes to the {@link Publisher}.
|
||||
* <p />
|
||||
* <br />
|
||||
* <strong>NOTE:</strong> Some operations cannot be executed within a MongoDB transaction. Please refer to the MongoDB
|
||||
* specific documentation to learn more about <a href="https://docs.mongodb.com/manual/core/transactions/">Multi
|
||||
* Document Transactions</a>.
|
||||
@@ -121,7 +121,7 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
|
||||
/**
|
||||
* Executes a {@link ReactiveDatabaseCallback} translating any exceptions as necessary.
|
||||
* <p/>
|
||||
* <br />
|
||||
* Allows for returning a result object, that is a domain object or a collection of domain objects.
|
||||
*
|
||||
* @param action callback object that specifies the MongoDB actions to perform on the passed in DB instance. Must not
|
||||
@@ -133,7 +133,7 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
|
||||
/**
|
||||
* Executes the given {@link ReactiveCollectionCallback} on the entity collection of the specified class.
|
||||
* <p/>
|
||||
* <br />
|
||||
* Allows for returning a result object, that is a domain object or a collection of domain objects.
|
||||
*
|
||||
* @param entityClass class that determines the collection to use. Must not be {@literal null}.
|
||||
@@ -145,7 +145,7 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
|
||||
/**
|
||||
* Executes the given {@link ReactiveCollectionCallback} on the collection of the given name.
|
||||
* <p/>
|
||||
* <br />
|
||||
* Allows for returning a result object, that is a domain object or a collection of domain objects.
|
||||
*
|
||||
* @param collectionName the name of the collection that specifies which {@link MongoCollection} instance will be
|
||||
@@ -159,7 +159,7 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
/**
|
||||
* Obtain a {@link ClientSession session} bound instance of {@link SessionScoped} binding the {@link ClientSession}
|
||||
* provided by the given {@link Supplier} to each and every command issued against MongoDB.
|
||||
* <p />
|
||||
* <br />
|
||||
* <strong>Note:</strong> It is up to the caller to manage the {@link ClientSession} lifecycle. Use
|
||||
* {@link ReactiveSessionScoped#execute(ReactiveSessionCallback, Consumer)} to provide a hook for processing the
|
||||
* {@link ClientSession} when done.
|
||||
@@ -178,7 +178,7 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
/**
|
||||
* Obtain a {@link ClientSession session} bound instance of {@link SessionScoped} binding a new {@link ClientSession}
|
||||
* with given {@literal sessionOptions} to each and every command issued against MongoDB.
|
||||
* <p />
|
||||
* <br />
|
||||
* <strong>Note:</strong> It is up to the caller to manage the {@link ClientSession} lifecycle. Use
|
||||
* {@link ReactiveSessionScoped#execute(ReactiveSessionCallback, Consumer)} to provide a hook for processing the
|
||||
* {@link ClientSession} when done.
|
||||
@@ -192,7 +192,7 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
/**
|
||||
* Obtain a {@link ClientSession session} bound instance of {@link ReactiveSessionScoped} binding the
|
||||
* {@link ClientSession} provided by the given {@link Publisher} to each and every command issued against MongoDB.
|
||||
* <p />
|
||||
* <br />
|
||||
* <strong>Note:</strong> It is up to the caller to manage the {@link ClientSession} lifecycle. Use
|
||||
* {@link ReactiveSessionScoped#execute(ReactiveSessionCallback, Consumer)} to provide a hook for processing the
|
||||
* {@link ClientSession} when done.
|
||||
@@ -205,7 +205,7 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
|
||||
/**
|
||||
* Obtain a {@link ClientSession} bound instance of {@link ReactiveMongoOperations}.
|
||||
* <p />
|
||||
* <br />
|
||||
* <strong>Note:</strong> It is up to the caller to manage the {@link ClientSession} lifecycle.
|
||||
*
|
||||
* @param session must not be {@literal null}.
|
||||
@@ -218,7 +218,7 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
* Initiate a new {@link ClientSession} and obtain a {@link ClientSession session} bound instance of
|
||||
* {@link ReactiveSessionScoped}. Starts the transaction and adds the {@link ClientSession} to each and every command
|
||||
* issued against MongoDB.
|
||||
* <p/>
|
||||
* <br />
|
||||
* Each {@link ReactiveSessionScoped#execute(ReactiveSessionCallback) execution} initiates a new managed transaction
|
||||
* that is {@link ClientSession#commitTransaction() committed} on success. Transactions are
|
||||
* {@link ClientSession#abortTransaction() rolled back} upon errors.
|
||||
@@ -233,7 +233,7 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
* Obtain a {@link ClientSession session} bound instance of {@link ReactiveSessionScoped}, start the transaction and
|
||||
* bind the {@link ClientSession} provided by the given {@link Publisher} to each and every command issued against
|
||||
* MongoDB.
|
||||
* <p/>
|
||||
* <br />
|
||||
* Each {@link ReactiveSessionScoped#execute(ReactiveSessionCallback) execution} initiates a new managed transaction
|
||||
* that is {@link ClientSession#commitTransaction() committed} on success. Transactions are
|
||||
* {@link ClientSession#abortTransaction() rolled back} upon errors.
|
||||
@@ -293,7 +293,7 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
* created on first interaction with the server. Collections can be explicitly created via
|
||||
* {@link #createCollection(Class)}. Please make sure to check if the collection {@link #collectionExists(Class)
|
||||
* exists} first.
|
||||
* <p/>
|
||||
* <br />
|
||||
* Translate any exceptions as necessary.
|
||||
*
|
||||
* @param collectionName name of the collection.
|
||||
@@ -303,7 +303,7 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
|
||||
/**
|
||||
* Check to see if a collection with a name indicated by the entity class exists.
|
||||
* <p/>
|
||||
* <br />
|
||||
* Translate any exceptions as necessary.
|
||||
*
|
||||
* @param entityClass class that determines the name of the collection. Must not be {@literal null}.
|
||||
@@ -313,7 +313,7 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
|
||||
/**
|
||||
* Check to see if a collection with a given name exists.
|
||||
* <p/>
|
||||
* <br />
|
||||
* Translate any exceptions as necessary.
|
||||
*
|
||||
* @param collectionName name of the collection. Must not be {@literal null}.
|
||||
@@ -323,7 +323,7 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
|
||||
/**
|
||||
* Drop the collection with the name indicated by the entity class.
|
||||
* <p/>
|
||||
* <br />
|
||||
* Translate any exceptions as necessary.
|
||||
*
|
||||
* @param entityClass class that determines the collection to drop/delete. Must not be {@literal null}.
|
||||
@@ -332,7 +332,7 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
|
||||
/**
|
||||
* Drop the collection with the given name.
|
||||
* <p/>
|
||||
* <br />
|
||||
* Translate any exceptions as necessary.
|
||||
*
|
||||
* @param collectionName name of the collection to drop/delete.
|
||||
@@ -341,10 +341,10 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
|
||||
/**
|
||||
* Query for a {@link Flux} of objects of type T from the collection used by the entity class.
|
||||
* <p/>
|
||||
* <br />
|
||||
* The object is converted from the MongoDB native representation using an instance of {@see MongoConverter}. Unless
|
||||
* configured otherwise, an instance of {@link MappingMongoConverter} will be used.
|
||||
* <p/>
|
||||
* <br />
|
||||
* If your collection does not contain a homogeneous collection of types, this operation will not be an efficient way
|
||||
* to map objects since the test for class type is done in the client and not on the server.
|
||||
*
|
||||
@@ -355,10 +355,10 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
|
||||
/**
|
||||
* Query for a {@link Flux} of objects of type T from the specified collection.
|
||||
* <p/>
|
||||
* <br />
|
||||
* The object is converted from the MongoDB native representation using an instance of {@see MongoConverter}. Unless
|
||||
* configured otherwise, an instance of {@link MappingMongoConverter} will be used.
|
||||
* <p/>
|
||||
* <br />
|
||||
* If your collection does not contain a homogeneous collection of types, this operation will not be an efficient way
|
||||
* to map objects since the test for class type is done in the client and not on the server.
|
||||
*
|
||||
@@ -371,10 +371,10 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
/**
|
||||
* Map the results of an ad-hoc query on the collection for the entity class to a single instance of an object of the
|
||||
* specified type.
|
||||
* <p/>
|
||||
* <br />
|
||||
* The object is converted from the MongoDB native representation using an instance of {@see MongoConverter}. Unless
|
||||
* configured otherwise, an instance of {@link MappingMongoConverter} will be used.
|
||||
* <p/>
|
||||
* <br />
|
||||
* The query is specified as a {@link Query} which can be created either using the {@link BasicQuery} or the more
|
||||
* feature rich {@link Query}.
|
||||
*
|
||||
@@ -388,10 +388,10 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
/**
|
||||
* Map the results of an ad-hoc query on the specified collection to a single instance of an object of the specified
|
||||
* type.
|
||||
* <p/>
|
||||
* <br />
|
||||
* The object is converted from the MongoDB native representation using an instance of {@see MongoConverter}. Unless
|
||||
* configured otherwise, an instance of {@link MappingMongoConverter} will be used.
|
||||
* <p/>
|
||||
* <br />
|
||||
* The query is specified as a {@link Query} which can be created either using the {@link BasicQuery} or the more
|
||||
* feature rich {@link Query}.
|
||||
*
|
||||
@@ -435,10 +435,10 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
|
||||
/**
|
||||
* Map the results of an ad-hoc query on the collection for the entity class to a {@link Flux} of the specified type.
|
||||
* <p/>
|
||||
* <br />
|
||||
* The object is converted from the MongoDB native representation using an instance of {@see MongoConverter}. Unless
|
||||
* configured otherwise, an instance of {@link MappingMongoConverter} will be used.
|
||||
* <p/>
|
||||
* <br />
|
||||
* The query is specified as a {@link Query} which can be created either using the {@link BasicQuery} or the more
|
||||
* feature rich {@link Query}.
|
||||
*
|
||||
@@ -451,10 +451,10 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
|
||||
/**
|
||||
* Map the results of an ad-hoc query on the specified collection to a {@link Flux} of the specified type.
|
||||
* <p/>
|
||||
* <br />
|
||||
* The object is converted from the MongoDB native representation using an instance of {@see MongoConverter}. Unless
|
||||
* configured otherwise, an instance of {@link MappingMongoConverter} will be used.
|
||||
* <p/>
|
||||
* <br />
|
||||
* The query is specified as a {@link Query} which can be created either using the {@link BasicQuery} or the more
|
||||
* feature rich {@link Query}.
|
||||
*
|
||||
@@ -566,10 +566,10 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
|
||||
/**
|
||||
* Execute an aggregation operation.
|
||||
* <p/>
|
||||
* <br />
|
||||
* The raw results will be mapped to the given entity class and are returned as stream. The name of the
|
||||
* inputCollection is derived from the {@link TypedAggregation#getInputType() aggregation input type}.
|
||||
* <p/>
|
||||
* <br />
|
||||
* Aggregation streaming cannot be used with {@link AggregationOptions#isExplain() aggregation explain} nor with
|
||||
* {@link AggregationOptions#getCursorBatchSize()}. Enabling explanation mode or setting batch size cause
|
||||
* {@link IllegalArgumentException}.
|
||||
@@ -584,10 +584,10 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
|
||||
/**
|
||||
* Execute an aggregation operation.
|
||||
* <p/>
|
||||
* <br />
|
||||
* The raw results will be mapped to the given {@code ouputType}. The name of the inputCollection is derived from the
|
||||
* {@code inputType}.
|
||||
* <p/>
|
||||
* <br />
|
||||
* Aggregation streaming cannot be used with {@link AggregationOptions#isExplain() aggregation explain} nor with
|
||||
* {@link AggregationOptions#getCursorBatchSize()}. Enabling explanation mode or setting batch size cause
|
||||
* {@link IllegalArgumentException}.
|
||||
@@ -604,9 +604,9 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
|
||||
/**
|
||||
* Execute an aggregation operation.
|
||||
* <p/>
|
||||
* <br />
|
||||
* The raw results will be mapped to the given entity class.
|
||||
* <p/>
|
||||
* <br />
|
||||
* Aggregation streaming cannot be used with {@link AggregationOptions#isExplain() aggregation explain} nor with
|
||||
* {@link AggregationOptions#getCursorBatchSize()}. Enabling explanation mode or setting batch size cause
|
||||
* {@link IllegalArgumentException}.
|
||||
@@ -676,7 +676,7 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
<T> Flux<GeoResult<T>> geoNear(NearQuery near, Class<T> entityClass, String collectionName);
|
||||
|
||||
/**
|
||||
* Triggers <a href="https://docs.mongodb.org/manual/reference/method/db.collection.findAndModify/">findAndModify<a/>
|
||||
* Triggers <a href="https://docs.mongodb.org/manual/reference/method/db.collection.findAndModify/">findAndModify</a>
|
||||
* to apply provided {@link Update} on documents matching {@link Criteria} of given {@link Query}.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional
|
||||
@@ -691,7 +691,7 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
<T> Mono<T> findAndModify(Query query, UpdateDefinition update, Class<T> entityClass);
|
||||
|
||||
/**
|
||||
* Triggers <a href="https://docs.mongodb.org/manual/reference/method/db.collection.findAndModify/">findAndModify<a/>
|
||||
* Triggers <a href="https://docs.mongodb.org/manual/reference/method/db.collection.findAndModify/">findAndModify</a>
|
||||
* to apply provided {@link Update} on documents matching {@link Criteria} of given {@link Query}.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional
|
||||
@@ -707,7 +707,7 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
<T> Mono<T> findAndModify(Query query, UpdateDefinition update, Class<T> entityClass, String collectionName);
|
||||
|
||||
/**
|
||||
* Triggers <a href="https://docs.mongodb.org/manual/reference/method/db.collection.findAndModify/">findAndModify<a/>
|
||||
* Triggers <a href="https://docs.mongodb.org/manual/reference/method/db.collection.findAndModify/">findAndModify</a>
|
||||
* to apply provided {@link Update} on documents matching {@link Criteria} of given {@link Query} taking
|
||||
* {@link FindAndModifyOptions} into account.
|
||||
*
|
||||
@@ -725,7 +725,7 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
<T> Mono<T> findAndModify(Query query, UpdateDefinition update, FindAndModifyOptions options, Class<T> entityClass);
|
||||
|
||||
/**
|
||||
* Triggers <a href="https://docs.mongodb.org/manual/reference/method/db.collection.findAndModify/">findAndModify<a/>
|
||||
* Triggers <a href="https://docs.mongodb.org/manual/reference/method/db.collection.findAndModify/">findAndModify</a>
|
||||
* to apply provided {@link Update} on documents matching {@link Criteria} of given {@link Query} taking
|
||||
* {@link FindAndModifyOptions} into account.
|
||||
*
|
||||
@@ -746,7 +746,7 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
|
||||
/**
|
||||
* Triggers
|
||||
* <a href="https://docs.mongodb.com/manual/reference/method/db.collection.findOneAndReplace/">findOneAndReplace<a/>
|
||||
* <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 />
|
||||
@@ -764,7 +764,7 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
|
||||
/**
|
||||
* Triggers
|
||||
* <a href="https://docs.mongodb.com/manual/reference/method/db.collection.findOneAndReplace/">findOneAndReplace<a/>
|
||||
* <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 />
|
||||
@@ -783,7 +783,7 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
|
||||
/**
|
||||
* Triggers
|
||||
* <a href="https://docs.mongodb.com/manual/reference/method/db.collection.findOneAndReplace/">findOneAndReplace<a/>
|
||||
* <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}.
|
||||
@@ -803,7 +803,7 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
|
||||
/**
|
||||
* Triggers
|
||||
* <a href="https://docs.mongodb.com/manual/reference/method/db.collection.findOneAndReplace/">findOneAndReplace<a/>
|
||||
* <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}.
|
||||
@@ -825,7 +825,7 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
|
||||
/**
|
||||
* Triggers
|
||||
* <a href="https://docs.mongodb.com/manual/reference/method/db.collection.findOneAndReplace/">findOneAndReplace<a/>
|
||||
* <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}.
|
||||
@@ -849,7 +849,7 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
|
||||
/**
|
||||
* Triggers
|
||||
* <a href="https://docs.mongodb.com/manual/reference/method/db.collection.findOneAndReplace/">findOneAndReplace<a/>
|
||||
* <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}.
|
||||
@@ -876,7 +876,7 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
|
||||
/**
|
||||
* Triggers
|
||||
* <a href="https://docs.mongodb.com/manual/reference/method/db.collection.findOneAndReplace/">findOneAndReplace<a/>
|
||||
* <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}.
|
||||
@@ -902,9 +902,9 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
* 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
|
||||
* database.
|
||||
* <p/>
|
||||
* <br />
|
||||
* The object is converted from the MongoDB native representation using an instance of {@see MongoConverter}.
|
||||
* <p/>
|
||||
* <br />
|
||||
* The query is specified as a {@link Query} which can be created either using the {@link BasicQuery} or the more
|
||||
* feature rich {@link Query}.
|
||||
*
|
||||
@@ -918,10 +918,10 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
/**
|
||||
* Map the results of an ad-hoc query on the specified collection 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 database.
|
||||
* <p/>
|
||||
* <br />
|
||||
* The object is converted from the MongoDB native representation using an instance of {@see MongoConverter}. Unless
|
||||
* configured otherwise, an instance of {@link MappingMongoConverter} will be used.
|
||||
* <p/>
|
||||
* <br />
|
||||
* The query is specified as a {@link Query} which can be created either using the {@link BasicQuery} or the more
|
||||
* feature rich {@link Query}.
|
||||
*
|
||||
@@ -940,6 +940,12 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
* influence on the resulting number of documents found as those values are passed on to the server and potentially
|
||||
* limit the range and order within which the server performs the count operation. Use an {@literal unpaged} query to
|
||||
* count all matches.
|
||||
* <br />
|
||||
* This method uses an
|
||||
* {@link com.mongodb.reactivestreams.client.MongoCollection#countDocuments(org.bson.conversions.Bson, com.mongodb.client.model.CountOptions)
|
||||
* aggregation execution} even for empty {@link Query queries} which may have an impact on performance, but guarantees
|
||||
* shard, session and transaction compliance. In case an inaccurate count satisfies the applications needs use
|
||||
* {@link #estimatedCount(Class)} for empty queries instead.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the criteria used to find documents. Must not be
|
||||
* {@literal null}.
|
||||
@@ -956,6 +962,12 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
* influence on the resulting number of documents found as those values are passed on to the server and potentially
|
||||
* limit the range and order within which the server performs the count operation. Use an {@literal unpaged} query to
|
||||
* count all matches.
|
||||
* <br />
|
||||
* This method uses an
|
||||
* {@link com.mongodb.reactivestreams.client.MongoCollection#countDocuments(org.bson.conversions.Bson, com.mongodb.client.model.CountOptions)
|
||||
* aggregation execution} even for empty {@link Query queries} which may have an impact on performance, but guarantees
|
||||
* shard, session and transaction compliance. In case an inaccurate count satisfies the applications needs use
|
||||
* {@link #estimatedCount(String)} for empty queries instead.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the criteria used to find documents.
|
||||
* @param collectionName must not be {@literal null} or empty.
|
||||
@@ -971,6 +983,12 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
* influence on the resulting number of documents found as those values are passed on to the server and potentially
|
||||
* limit the range and order within which the server performs the count operation. Use an {@literal unpaged} query to
|
||||
* count all matches.
|
||||
* <br />
|
||||
* This method uses an
|
||||
* {@link com.mongodb.reactivestreams.client.MongoCollection#countDocuments(org.bson.conversions.Bson, com.mongodb.client.model.CountOptions)
|
||||
* aggregation execution} even for empty {@link Query queries} which may have an impact on performance, but guarantees
|
||||
* shard, session and transaction compliance. In case an inaccurate count satisfies the applications needs use
|
||||
* {@link #estimatedCount(String)} for empty queries instead.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the criteria used to find documents. Must not be
|
||||
* {@literal null}.
|
||||
@@ -983,6 +1001,9 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
/**
|
||||
* Estimate the number of documents, in the collection {@link #getCollectionName(Class) identified by the given type},
|
||||
* based on collection statistics.
|
||||
* <br />
|
||||
* Please make sure to read the MongoDB reference documentation about limitations on eg. sharded cluster or inside
|
||||
* transactions.
|
||||
*
|
||||
* @param entityClass must not be {@literal null}.
|
||||
* @return a {@link Mono} emitting the estimated number of documents.
|
||||
@@ -996,6 +1017,9 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
|
||||
/**
|
||||
* Estimate the number of documents in the given collection based on collection statistics.
|
||||
* <br />
|
||||
* Please make sure to read the MongoDB reference documentation about limitations on eg. sharded cluster or inside
|
||||
* transactions.
|
||||
*
|
||||
* @param collectionName must not be {@literal null}.
|
||||
* @return a {@link Mono} emitting the estimated number of documents.
|
||||
@@ -1005,34 +1029,39 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
|
||||
/**
|
||||
* Insert the object into the collection for the entity type of the object to save.
|
||||
* <p/>
|
||||
* <br />
|
||||
* The object is converted to the MongoDB native representation using an instance of {@see MongoConverter}.
|
||||
* <p/>
|
||||
* <br />
|
||||
* If your object has an "Id' property, it will be set with the generated Id from MongoDB. If your Id property is a
|
||||
* String then MongoDB ObjectId will be used to populate that string. Otherwise, the conversion from ObjectId to your
|
||||
* property type will be handled by Spring's BeanWrapper class that leverages Type Conversion API. See
|
||||
* <a href="https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#validation" > Spring's
|
||||
* Type Conversion"</a> for more details.
|
||||
* <p/>
|
||||
* <p/>
|
||||
* <br />
|
||||
* Insert is used to initially store the object into the database. To update an existing object use the save method.
|
||||
* <br />
|
||||
* The {@code objectToSave} must not be collection-like.
|
||||
*
|
||||
* @param objectToSave the object to store in the collection. Must not be {@literal null}.
|
||||
* @return the inserted object.
|
||||
* @throws IllegalArgumentException in case the {@code objectToSave} is collection-like.
|
||||
*/
|
||||
<T> Mono<T> insert(T objectToSave);
|
||||
|
||||
/**
|
||||
* Insert the object into the specified collection.
|
||||
* <p/>
|
||||
* <br />
|
||||
* The object is converted to the MongoDB native representation using an instance of {@see MongoConverter}. Unless
|
||||
* configured otherwise, an instance of {@link MappingMongoConverter} will be used.
|
||||
* <p/>
|
||||
* <br />
|
||||
* Insert is used to initially store the object into the database. To update an existing object use the save method.
|
||||
* <br />
|
||||
* The {@code objectToSave} must not be collection-like.
|
||||
*
|
||||
* @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.
|
||||
* @throws IllegalArgumentException in case the {@code objectToSave} is collection-like.
|
||||
*/
|
||||
<T> Mono<T> insert(T objectToSave, String collectionName);
|
||||
|
||||
@@ -1065,16 +1094,15 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
|
||||
/**
|
||||
* Insert the object into the collection for the entity type of the object to save.
|
||||
* <p/>
|
||||
* <br />
|
||||
* The object is converted to the MongoDB native representation using an instance of {@see MongoConverter}.
|
||||
* <p/>
|
||||
* <br />
|
||||
* If your object has an "Id' property, it will be set with the generated Id from MongoDB. If your Id property is a
|
||||
* String then MongoDB ObjectId will be used to populate that string. Otherwise, the conversion from ObjectId to your
|
||||
* property type will be handled by Spring's BeanWrapper class that leverages Type Conversion API. See
|
||||
* <a href="https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#validation" > Spring's
|
||||
* Type Conversion"</a> for more details.
|
||||
* <p/>
|
||||
* <p/>
|
||||
* <br />
|
||||
* 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}.
|
||||
@@ -1112,52 +1140,54 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
/**
|
||||
* Save the object to the collection for the entity type of the object to save. This will perform an insert if the
|
||||
* object is not already present, that is an 'upsert'.
|
||||
* <p/>
|
||||
* <br />
|
||||
* The object is converted to the MongoDB native representation using an instance of {@see MongoConverter}. Unless
|
||||
* configured otherwise, an instance of {@link MappingMongoConverter} will be used.
|
||||
* <p/>
|
||||
* <br />
|
||||
* If your object has an "Id' property, it will be set with the generated Id from MongoDB. If your Id property is a
|
||||
* String then MongoDB ObjectId will be used to populate that string. Otherwise, the conversion from ObjectId to your
|
||||
* property type will be handled by Spring's BeanWrapper class that leverages Type Conversion API. See
|
||||
* <a href="https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#validation" > Spring's
|
||||
* Type Conversion"</a> for more details.
|
||||
* <br />
|
||||
* The {@code objectToSave} must not be collection-like.
|
||||
*
|
||||
* @param objectToSave the object to store in the collection. Must not be {@literal null}.
|
||||
* @return the saved object.
|
||||
* @throws IllegalArgumentException in case the {@code objectToSave} is collection-like.
|
||||
*/
|
||||
<T> Mono<T> save(T objectToSave);
|
||||
|
||||
/**
|
||||
* Save the object to the specified collection. This will perform an insert if the object is not already present, that
|
||||
* is an 'upsert'.
|
||||
* <p/>
|
||||
* <br />
|
||||
* The object is converted to the MongoDB native representation using an instance of {@see MongoConverter}. Unless
|
||||
* configured otherwise, an instance of {@link MappingMongoConverter} will be used.
|
||||
* <p/>
|
||||
* <br />
|
||||
* If your object has an "Id' property, it will be set with the generated Id from MongoDB. If your Id property is a
|
||||
* String then MongoDB ObjectId will be used to populate that string. Otherwise, the conversion from ObjectId to your
|
||||
* property type will be handled by Spring's BeanWrapper class that leverages Type Conversion API. See <a
|
||||
* https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#validation">Spring's Type
|
||||
* Conversion"</a> for more details.
|
||||
* property type will be handled by Spring's BeanWrapper class that leverages Type Conversion API.
|
||||
* See <a href="https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#validation">Spring's Type Conversion</a> for more details.
|
||||
*
|
||||
* @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.
|
||||
* @throws IllegalArgumentException in case the {@code objectToSave} is collection-like.
|
||||
*/
|
||||
<T> Mono<T> save(T objectToSave, String collectionName);
|
||||
|
||||
/**
|
||||
* Save the object to the collection for the entity type of the object to save. This will perform an insert if the
|
||||
* object is not already present, that is an 'upsert'.
|
||||
* <p/>
|
||||
* <br />
|
||||
* The object is converted to the MongoDB native representation using an instance of {@see MongoConverter}. Unless
|
||||
* configured otherwise, an instance of {@link MappingMongoConverter} will be used.
|
||||
* <p/>
|
||||
* <br />
|
||||
* If your object has an "Id' property, it will be set with the generated Id from MongoDB. If your Id property is a
|
||||
* String then MongoDB ObjectId will be used to populate that string. Otherwise, the conversion from ObjectId to your
|
||||
* property type will be handled by Spring's BeanWrapper class that leverages Type Conversion API. See
|
||||
* <a href="https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#validation" > Spring's
|
||||
* Type Conversion"</a> for more details.
|
||||
* property type will be handled by Spring's BeanWrapper class that leverages Type Conversion API.
|
||||
* See <a href="https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#validation"> Spring's Type Conversion</a> for more details.
|
||||
*
|
||||
* @param objectToSave the object to store in the collection. Must not be {@literal null}.
|
||||
* @return the saved object.
|
||||
@@ -1167,17 +1197,16 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
/**
|
||||
* Save the object to the specified collection. This will perform an insert if the object is not already present, that
|
||||
* is an 'upsert'.
|
||||
* <p/>
|
||||
* <br />
|
||||
* The object is converted to the MongoDB native representation using an instance of {@see MongoConverter}. Unless
|
||||
* configured otherwise, an instance of {@link MappingMongoConverter} will be used.
|
||||
* <p/>
|
||||
* <br />
|
||||
* If your object has an "Id' property, it will be set with the generated Id from MongoDB. If your Id property is a
|
||||
* String then MongoDB ObjectId will be used to populate that string. Otherwise, the conversion from ObjectId to your
|
||||
* property type will be handled by Spring's BeanWrapper class that leverages Type Conversion API. See <a
|
||||
* https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#validation">Spring's Type
|
||||
* Conversion"</a> for more details.
|
||||
* property type will be handled by Spring's BeanWrapper class that leverages Type Conversion API.
|
||||
* See <a href="https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#validation">Spring's Type Conversion</a> for more details.
|
||||
*
|
||||
* @param objectToSave the object to store in the collection. Must not be {@literal null}.
|
||||
* @param objectToSave the object to store in the collReactiveMongoOperationsection. 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.
|
||||
*/
|
||||
@@ -1449,10 +1478,10 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
* type. The stream uses a {@link com.mongodb.CursorType#TailableAwait tailable} cursor that may be an infinite
|
||||
* stream. The stream will not be completed unless the {@link org.reactivestreams.Subscription} is
|
||||
* {@link Subscription#cancel() canceled}.
|
||||
* <p/>
|
||||
* <br />
|
||||
* The object is converted from the MongoDB native representation using an instance of {@see MongoConverter}. Unless
|
||||
* configured otherwise, an instance of {@link MappingMongoConverter} will be used.
|
||||
* <p/>
|
||||
* <br />
|
||||
* The query is specified as a {@link Query} which can be created either using the {@link BasicQuery} or the more
|
||||
* feature rich {@link Query}.
|
||||
*
|
||||
@@ -1468,10 +1497,10 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
* type. The stream uses a {@link com.mongodb.CursorType#TailableAwait tailable} cursor that may be an infinite
|
||||
* stream. The stream will not be completed unless the {@link org.reactivestreams.Subscription} is
|
||||
* {@link Subscription#cancel() canceled}.
|
||||
* <p/>
|
||||
* <br />
|
||||
* The object is converted from the MongoDB native representation using an instance of {@see MongoConverter}. Unless
|
||||
* configured otherwise, an instance of {@link MappingMongoConverter} will be used.
|
||||
* <p/>
|
||||
* <br />
|
||||
* The query is specified as a {@link Query} which can be created either using the {@link BasicQuery} or the more
|
||||
* feature rich {@link Query}.
|
||||
*
|
||||
@@ -1488,10 +1517,10 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
* 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 />
|
||||
* <br />
|
||||
* The {@link ChangeStreamEvent#getBody()} is mapped to the {@literal resultType} while the
|
||||
* {@link ChangeStreamEvent#getRaw()} contains the unmodified payload.
|
||||
* <p />
|
||||
* <br />
|
||||
* Use {@link ChangeStreamOptions} to set arguments like {@link ChangeStreamOptions#getResumeToken() the resumseToken}
|
||||
* for resuming change streams.
|
||||
*
|
||||
@@ -1512,10 +1541,10 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
* 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 />
|
||||
* <br />
|
||||
* The {@link ChangeStreamEvent#getBody()} is mapped to the {@literal resultType} while the
|
||||
* {@link ChangeStreamEvent#getRaw()} contains the unmodified payload.
|
||||
* <p />
|
||||
* <br />
|
||||
* Use {@link ChangeStreamOptions} to set arguments like {@link ChangeStreamOptions#getResumeToken() the resumseToken}
|
||||
* for resuming change streams.
|
||||
*
|
||||
@@ -1537,10 +1566,10 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
* 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 />
|
||||
* <br />
|
||||
* The {@link ChangeStreamEvent#getBody()} is mapped to the {@literal resultType} while the
|
||||
* {@link ChangeStreamEvent#getRaw()} contains the unmodified payload.
|
||||
* <p />
|
||||
* <br />
|
||||
* Use {@link ChangeStreamOptions} to set arguments like {@link ChangeStreamOptions#getResumeToken() the resumseToken}
|
||||
* for resuming change streams.
|
||||
*
|
||||
|
||||
@@ -17,13 +17,20 @@ package org.springframework.data.mongodb.core;
|
||||
|
||||
import static org.springframework.data.mongodb.core.query.SerializationUtils.*;
|
||||
|
||||
import org.springframework.data.mongodb.core.aggregation.RelaxedTypeBasedAggregationOperationContext;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.util.function.Tuple2;
|
||||
import reactor.util.function.Tuples;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
@@ -53,6 +60,7 @@ import org.springframework.data.convert.EntityReader;
|
||||
import org.springframework.data.geo.Distance;
|
||||
import org.springframework.data.geo.GeoResult;
|
||||
import org.springframework.data.geo.Metric;
|
||||
import org.springframework.data.mapping.MappingException;
|
||||
import org.springframework.data.mapping.PersistentEntity;
|
||||
import org.springframework.data.mapping.callback.ReactiveEntityCallbacks;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
@@ -62,6 +70,7 @@ import org.springframework.data.mongodb.ReactiveMongoDatabaseFactory;
|
||||
import org.springframework.data.mongodb.ReactiveMongoDatabaseUtils;
|
||||
import org.springframework.data.mongodb.SessionSynchronization;
|
||||
import org.springframework.data.mongodb.core.EntityOperations.AdaptibleEntity;
|
||||
import org.springframework.data.mongodb.core.QueryOperations.AggregationDefinition;
|
||||
import org.springframework.data.mongodb.core.QueryOperations.CountContext;
|
||||
import org.springframework.data.mongodb.core.QueryOperations.DeleteContext;
|
||||
import org.springframework.data.mongodb.core.QueryOperations.DistinctQueryContext;
|
||||
@@ -71,6 +80,7 @@ import org.springframework.data.mongodb.core.aggregation.Aggregation;
|
||||
import org.springframework.data.mongodb.core.aggregation.AggregationOperationContext;
|
||||
import org.springframework.data.mongodb.core.aggregation.AggregationOptions;
|
||||
import org.springframework.data.mongodb.core.aggregation.PrefixingDelegatingAggregationOperationContext;
|
||||
import org.springframework.data.mongodb.core.aggregation.RelaxedTypeBasedAggregationOperationContext;
|
||||
import org.springframework.data.mongodb.core.aggregation.TypeBasedAggregationOperationContext;
|
||||
import org.springframework.data.mongodb.core.aggregation.TypedAggregation;
|
||||
import org.springframework.data.mongodb.core.convert.DbRefResolver;
|
||||
@@ -100,6 +110,7 @@ import org.springframework.data.mongodb.core.query.NearQuery;
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
import org.springframework.data.mongodb.core.query.UpdateDefinition;
|
||||
import org.springframework.data.mongodb.core.query.UpdateDefinition.ArrayFilter;
|
||||
import org.springframework.data.mongodb.core.timeseries.Granularity;
|
||||
import org.springframework.data.mongodb.core.validation.Validator;
|
||||
import org.springframework.data.mongodb.util.BsonUtils;
|
||||
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
|
||||
@@ -156,18 +167,6 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(ReactiveMongoTemplate.class);
|
||||
private static final WriteResultChecking DEFAULT_WRITE_RESULT_CHECKING = WriteResultChecking.NONE;
|
||||
private static final Collection<Class<?>> ITERABLE_CLASSES;
|
||||
|
||||
static {
|
||||
|
||||
Set<Class<?>> iterableClasses = new HashSet<>();
|
||||
iterableClasses.add(List.class);
|
||||
iterableClasses.add(Collection.class);
|
||||
iterableClasses.add(Iterator.class);
|
||||
iterableClasses.add(Publisher.class);
|
||||
|
||||
ITERABLE_CLASSES = Collections.unmodifiableCollection(iterableClasses);
|
||||
}
|
||||
|
||||
private final MongoConverter mongoConverter;
|
||||
private final MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext;
|
||||
@@ -363,7 +362,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
* Set the {@link ReactiveEntityCallbacks} instance to use when invoking
|
||||
* {@link org.springframework.data.mapping.callback.EntityCallback callbacks} like the
|
||||
* {@link ReactiveBeforeSaveCallback}.
|
||||
* <p />
|
||||
* <br />
|
||||
* Overrides potentially existing {@link ReactiveEntityCallbacks}.
|
||||
*
|
||||
* @param entityCallbacks must not be {@literal null}.
|
||||
@@ -667,7 +666,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
* @see org.springframework.data.mongodb.core.ReactiveMongoOperations#createCollection(java.lang.Class)
|
||||
*/
|
||||
public <T> Mono<MongoCollection<Document>> createCollection(Class<T> entityClass) {
|
||||
return createCollection(entityClass, CollectionOptions.empty());
|
||||
return createCollection(entityClass, operations.forType(entityClass).getCollectionOptions());
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -946,9 +945,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
|
||||
Assert.notNull(aggregation, "Aggregation pipeline must not be null!");
|
||||
|
||||
AggregationOperationContext context = new TypeBasedAggregationOperationContext(aggregation.getInputType(),
|
||||
mappingContext, queryMapper);
|
||||
return aggregate(aggregation, inputCollectionName, outputType, context);
|
||||
return doAggregate(aggregation, inputCollectionName, aggregation.getInputType(), outputType);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -966,9 +963,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
*/
|
||||
@Override
|
||||
public <O> Flux<O> aggregate(Aggregation aggregation, Class<?> inputType, Class<O> outputType) {
|
||||
|
||||
return aggregate(aggregation, getCollectionName(inputType), outputType,
|
||||
new TypeBasedAggregationOperationContext(inputType, mappingContext, queryMapper));
|
||||
return doAggregate(aggregation, getCollectionName(inputType), inputType, outputType);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -977,45 +972,34 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
*/
|
||||
@Override
|
||||
public <O> Flux<O> aggregate(Aggregation aggregation, String collectionName, Class<O> outputType) {
|
||||
return aggregate(aggregation, collectionName, outputType, null);
|
||||
return doAggregate(aggregation, collectionName, null, outputType);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param aggregation must not be {@literal null}.
|
||||
* @param collectionName must not be {@literal null}.
|
||||
* @param outputType must not be {@literal null}.
|
||||
* @param context can be {@literal null} and will be defaulted to {@link Aggregation#DEFAULT_CONTEXT}.
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
protected <O> Flux<O> aggregate(Aggregation aggregation, String collectionName, Class<O> outputType,
|
||||
@Nullable AggregationOperationContext context) {
|
||||
protected <O> Flux<O> doAggregate(Aggregation aggregation, String collectionName, @Nullable Class<?> inputType,
|
||||
Class<O> outputType) {
|
||||
|
||||
Assert.notNull(aggregation, "Aggregation pipeline must not be null!");
|
||||
Assert.hasText(collectionName, "Collection name must not be null or empty!");
|
||||
Assert.notNull(outputType, "Output type must not be null!");
|
||||
|
||||
AggregationUtil aggregationUtil = new AggregationUtil(queryMapper, mappingContext);
|
||||
AggregationOperationContext rootContext = aggregationUtil.prepareAggregationContext(aggregation, context);
|
||||
|
||||
AggregationOptions options = aggregation.getOptions();
|
||||
List<Document> pipeline = aggregationUtil.createPipeline(aggregation, rootContext);
|
||||
|
||||
Assert.isTrue(!options.isExplain(), "Cannot use explain option with streaming!");
|
||||
|
||||
AggregationDefinition ctx = queryOperations.createAggregation(aggregation, inputType);
|
||||
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("Streaming aggregation: {} in collection {}", serializeToJsonSafely(pipeline), collectionName);
|
||||
LOGGER.debug("Streaming aggregation: {} in collection {}", serializeToJsonSafely(ctx.getAggregationPipeline()),
|
||||
collectionName);
|
||||
}
|
||||
|
||||
ReadDocumentCallback<O> readCallback = new ReadDocumentCallback<>(mongoConverter, outputType, collectionName);
|
||||
return execute(collectionName,
|
||||
collection -> aggregateAndMap(collection, pipeline, aggregation.getPipeline().isOutOrMerge(), options,
|
||||
readCallback,
|
||||
aggregation instanceof TypedAggregation ? ((TypedAggregation<?>) aggregation).getInputType() : null));
|
||||
return execute(collectionName, collection -> aggregateAndMap(collection, ctx.getAggregationPipeline(),
|
||||
ctx.isOutOrMerge(), options, readCallback, ctx.getInputType()));
|
||||
}
|
||||
|
||||
private <O> Flux<O> aggregateAndMap(MongoCollection<Document> collection, List<Document> pipeline,
|
||||
boolean isOutOrMerge,
|
||||
AggregationOptions options, ReadDocumentCallback<O> readCallback, @Nullable Class<?> inputType) {
|
||||
boolean isOutOrMerge, AggregationOptions options, ReadDocumentCallback<O> readCallback,
|
||||
@Nullable Class<?> inputType) {
|
||||
|
||||
AggregatePublisher<Document> cursor = collection.aggregate(pipeline, Document.class)
|
||||
.allowDiskUse(options.isAllowDiskUse());
|
||||
@@ -2522,6 +2506,20 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
result.validationOptions(validationOptions);
|
||||
});
|
||||
|
||||
collectionOptions.getTimeSeriesOptions().map(operations.forType(entityType)::mapTimeSeriesOptions).ifPresent(it -> {
|
||||
|
||||
TimeSeriesOptions options = new TimeSeriesOptions(it.getTimeField());
|
||||
|
||||
if (StringUtils.hasText(it.getMetaField())) {
|
||||
options.metaField(it.getMetaField());
|
||||
}
|
||||
if (!Granularity.DEFAULT.equals(it.getGranularity())) {
|
||||
options.granularity(TimeSeriesGranularity.valueOf(it.getGranularity().name().toUpperCase()));
|
||||
}
|
||||
|
||||
result.timeSeriesOptions(options);
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -2539,7 +2537,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
/**
|
||||
* Map the results of an ad-hoc query on the default MongoDB collection to an object using the template's converter.
|
||||
* The first document that matches the query is returned and also removed from the collection in the database.
|
||||
* <p/>
|
||||
* <br />
|
||||
* The query document is specified as a standard Document and so is the fields specification.
|
||||
*
|
||||
* @param collectionName name of the collection to retrieve the objects from
|
||||
@@ -2682,13 +2680,27 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
}
|
||||
}
|
||||
|
||||
protected void ensureNotIterable(Object o) {
|
||||
/**
|
||||
* Ensure the given {@literal source} is not an {@link java.lang.reflect.Array}, {@link Collection} or
|
||||
* {@link Iterator}.
|
||||
*
|
||||
* @param source can be {@literal null}.
|
||||
* @deprecated since 3.2. Call {@link #ensureNotCollectionLike(Object)} instead.
|
||||
*/
|
||||
protected void ensureNotIterable(@Nullable Object source) {
|
||||
ensureNotCollectionLike(source);
|
||||
}
|
||||
|
||||
boolean isIterable = o.getClass().isArray()
|
||||
|| ITERABLE_CLASSES.stream().anyMatch(iterableClass -> iterableClass.isAssignableFrom(o.getClass())
|
||||
|| o.getClass().getName().equals(iterableClass.getName()));
|
||||
/**
|
||||
* Ensure the given {@literal source} is not an {@link java.lang.reflect.Array}, {@link Collection} or
|
||||
* {@link Iterator}.
|
||||
*
|
||||
* @param source can be {@literal null}.
|
||||
* @since 3.2.
|
||||
*/
|
||||
protected void ensureNotCollectionLike(@Nullable Object source) {
|
||||
|
||||
if (isIterable) {
|
||||
if (EntityOperations.isCollectionLike(source) || source instanceof Publisher) {
|
||||
throw new IllegalArgumentException("Cannot use a collection here.");
|
||||
}
|
||||
}
|
||||
@@ -2730,6 +2742,14 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
return potentiallyForceAcknowledgedWrite(wc);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the {@link MongoDatabaseFactory} in use.
|
||||
* @since 3.1.4
|
||||
*/
|
||||
public ReactiveMongoDatabaseFactory getMongoDatabaseFactory() {
|
||||
return mongoDatabaseFactory;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private WriteConcern potentiallyForceAcknowledgedWrite(@Nullable WriteConcern wc) {
|
||||
|
||||
@@ -3157,13 +3177,14 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
|
||||
maybeEmitEvent(new AfterLoadEvent<>(document, type, collectionName));
|
||||
|
||||
T source = reader.read(type, document);
|
||||
if (source != null) {
|
||||
maybeEmitEvent(new AfterConvertEvent<>(document, source, collectionName));
|
||||
return maybeCallAfterConvert(source, document, collectionName);
|
||||
T entity = reader.read(type, document);
|
||||
|
||||
if (entity == null) {
|
||||
throw new MappingException(String.format("EntityReader %s returned null", reader));
|
||||
}
|
||||
|
||||
return Mono.empty();
|
||||
maybeEmitEvent(new AfterConvertEvent<>(document, entity, collectionName));
|
||||
return maybeCallAfterConvert(entity, document, collectionName);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3201,16 +3222,17 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
|
||||
maybeEmitEvent(new AfterLoadEvent<>(document, typeToRead, collectionName));
|
||||
|
||||
Object source = reader.read(typeToRead, document);
|
||||
Object result = targetType.isInterface() ? projectionFactory.createProjection(targetType, source) : source;
|
||||
Object entity = reader.read(typeToRead, document);
|
||||
|
||||
T castEntity = (T) result;
|
||||
if (castEntity != null) {
|
||||
maybeEmitEvent(new AfterConvertEvent<>(document, castEntity, collectionName));
|
||||
return maybeCallAfterConvert(castEntity, document, collectionName);
|
||||
if (entity == null) {
|
||||
throw new MappingException(String.format("EntityReader %s returned null", reader));
|
||||
}
|
||||
|
||||
return Mono.empty();
|
||||
Object result = targetType.isInterface() ? projectionFactory.createProjection(targetType, entity) : entity;
|
||||
|
||||
T castEntity = (T) result;
|
||||
maybeEmitEvent(new AfterConvertEvent<>(document, castEntity, collectionName));
|
||||
return maybeCallAfterConvert(castEntity, document, collectionName);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3368,7 +3390,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
/**
|
||||
* {@link MongoTemplate} extension bound to a specific {@link ClientSession} that is applied when interacting with the
|
||||
* server through the driver API.
|
||||
* <p />
|
||||
* <br />
|
||||
* The prepare steps for {@link MongoDatabase} and {@link MongoCollection} proxy the target and invoke the desired
|
||||
* target method matching the actual arguments plus a {@link ClientSession}.
|
||||
*
|
||||
|
||||
@@ -32,7 +32,7 @@ public interface ReactiveSessionCallback<T> {
|
||||
/**
|
||||
* Execute operations against a MongoDB instance via session bound {@link ReactiveMongoOperations}. The session is
|
||||
* inferred directly into the operation so that no further interaction is necessary.
|
||||
* <p />
|
||||
* <br />
|
||||
* Please note that only Spring Data-specific abstractions like {@link ReactiveMongoOperations#find(Query, Class)} and
|
||||
* others are enhanced with the {@link com.mongodb.session.ClientSession}. When obtaining plain MongoDB gateway
|
||||
* objects like {@link com.mongodb.reactivestreams.client.MongoCollection} or
|
||||
|
||||
@@ -33,7 +33,7 @@ public interface ReactiveSessionScoped {
|
||||
|
||||
/**
|
||||
* Executes the given {@link ReactiveSessionCallback} within the {@link com.mongodb.session.ClientSession}.
|
||||
* <p/>
|
||||
* <br />
|
||||
* It is up to the caller to make sure the {@link com.mongodb.session.ClientSession} is {@link ClientSession#close()
|
||||
* closed} when done.
|
||||
*
|
||||
@@ -47,7 +47,7 @@ public interface ReactiveSessionScoped {
|
||||
|
||||
/**
|
||||
* Executes the given {@link ReactiveSessionCallback} within the {@link com.mongodb.session.ClientSession}.
|
||||
* <p/>
|
||||
* <br />
|
||||
* It is up to the caller to make sure the {@link com.mongodb.session.ClientSession} is {@link ClientSession#close()
|
||||
* closed} when done.
|
||||
*
|
||||
|
||||
@@ -23,7 +23,7 @@ import org.springframework.lang.Nullable;
|
||||
|
||||
|
||||
/**
|
||||
* Script operations on {@link com.mongodb.DB} level. Allows interaction with server side JavaScript functions.
|
||||
* Script operations on {@link com.mongodb.client.MongoDatabase} level. Allows interaction with server side JavaScript functions.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author Oliver Gierke
|
||||
@@ -72,10 +72,10 @@ public interface ScriptOperations {
|
||||
Object call(String scriptName, Object... args);
|
||||
|
||||
/**
|
||||
* Checks {@link DB} for existence of {@link ServerSideJavaScript} with given name.
|
||||
* Checks {@link com.mongodb.client.MongoDatabase} for existence of {@literal ServerSideJavaScript} with given name.
|
||||
*
|
||||
* @param scriptName must not be {@literal null} or empty.
|
||||
* @return false if no {@link ServerSideJavaScript} with given name exists.
|
||||
* @return false if no {@literal ServerSideJavaScript} with given name exists.
|
||||
*/
|
||||
boolean exists(String scriptName);
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ public interface SessionCallback<T> {
|
||||
/**
|
||||
* Execute operations against a MongoDB instance via session bound {@link MongoOperations}. The session is inferred
|
||||
* directly into the operation so that no further interaction is necessary.
|
||||
* <p />
|
||||
* <br />
|
||||
* Please note that only Spring Data-specific abstractions like {@link MongoOperations#find(Query, Class)} and others
|
||||
* are enhanced with the {@link com.mongodb.session.ClientSession}. When obtaining plain MongoDB gateway objects like
|
||||
* {@link com.mongodb.client.MongoCollection} or {@link com.mongodb.client.MongoDatabase} via eg.
|
||||
|
||||
@@ -23,7 +23,7 @@ import com.mongodb.client.ClientSession;
|
||||
|
||||
/**
|
||||
* Gateway interface to execute {@link ClientSession} bound operations against MongoDB via a {@link SessionCallback}.
|
||||
* <p />
|
||||
* <br />
|
||||
* The very same bound {@link ClientSession} is used for all invocations of {@code execute} on the instance.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
@@ -34,7 +34,7 @@ public interface SessionScoped {
|
||||
|
||||
/**
|
||||
* Executes the given {@link SessionCallback} within the {@link com.mongodb.session.ClientSession}.
|
||||
* <p/>
|
||||
* <br />
|
||||
* It is up to the caller to make sure the {@link com.mongodb.session.ClientSession} is {@link ClientSession#close()
|
||||
* closed} when done.
|
||||
*
|
||||
@@ -49,7 +49,7 @@ public interface SessionScoped {
|
||||
|
||||
/**
|
||||
* Executes the given {@link SessionCallback} within the {@link com.mongodb.session.ClientSession}.
|
||||
* <p/>
|
||||
* <br />
|
||||
* It is up to the caller to make sure the {@link com.mongodb.session.ClientSession} is {@link ClientSession#close()
|
||||
* closed} when done.
|
||||
*
|
||||
|
||||
@@ -142,11 +142,118 @@ public class AccumulatorOperators {
|
||||
return usesFieldRef() ? StdDevSamp.stdDevSampOf(fieldReference) : StdDevSamp.stdDevSampOf(expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link AggregationExpression} that uses the previous input (field/expression) and the value of the
|
||||
* given field to calculate the population covariance of the two.
|
||||
*
|
||||
* @param fieldReference must not be {@literal null}.
|
||||
* @return new instance of {@link CovariancePop}.
|
||||
* @since 3.3
|
||||
*/
|
||||
public CovariancePop covariancePop(String fieldReference) {
|
||||
return covariancePop().and(fieldReference);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link AggregationExpression} that uses the previous input (field/expression) and the result of the
|
||||
* given {@link AggregationExpression expression} to calculate the population covariance of the two.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return new instance of {@link CovariancePop}.
|
||||
* @since 3.3
|
||||
*/
|
||||
public CovariancePop covariancePop(AggregationExpression expression) {
|
||||
return covariancePop().and(expression);
|
||||
}
|
||||
|
||||
private CovariancePop covariancePop() {
|
||||
return usesFieldRef() ? CovariancePop.covariancePopOf(fieldReference) : CovariancePop.covariancePopOf(expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link AggregationExpression} that uses the previous input (field/expression) and the value of the
|
||||
* given field to calculate the sample covariance of the two.
|
||||
*
|
||||
* @param fieldReference must not be {@literal null}.
|
||||
* @return new instance of {@link CovariancePop}.
|
||||
* @since 3.3
|
||||
*/
|
||||
public CovarianceSamp covarianceSamp(String fieldReference) {
|
||||
return covarianceSamp().and(fieldReference);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link AggregationExpression} that uses the previous input (field/expression) and the result of the
|
||||
* given {@link AggregationExpression expression} to calculate the sample covariance of the two.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return new instance of {@link CovariancePop}.
|
||||
* @since 3.3
|
||||
*/
|
||||
public CovarianceSamp covarianceSamp(AggregationExpression expression) {
|
||||
return covarianceSamp().and(expression);
|
||||
}
|
||||
|
||||
private CovarianceSamp covarianceSamp() {
|
||||
return usesFieldRef() ? CovarianceSamp.covarianceSampOf(fieldReference)
|
||||
: CovarianceSamp.covarianceSampOf(expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link ExpMovingAvgBuilder} that to build {@link AggregationExpression expMovingAvg} that calculates
|
||||
* the exponential moving average of numeric values
|
||||
*
|
||||
* @return new instance of {@link ExpMovingAvg}.
|
||||
* @since 3.3
|
||||
*/
|
||||
public ExpMovingAvgBuilder expMovingAvg() {
|
||||
|
||||
ExpMovingAvg expMovingAvg = usesFieldRef() ? ExpMovingAvg.expMovingAvgOf(fieldReference)
|
||||
: ExpMovingAvg.expMovingAvgOf(expression);
|
||||
return new ExpMovingAvgBuilder() {
|
||||
|
||||
@Override
|
||||
public ExpMovingAvg historicalDocuments(int numberOfHistoricalDocuments) {
|
||||
return expMovingAvg.n(numberOfHistoricalDocuments);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExpMovingAvg alpha(double exponentialDecayValue) {
|
||||
return expMovingAvg.alpha(exponentialDecayValue);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private boolean usesFieldRef() {
|
||||
return fieldReference != null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder for {@link ExpMovingAvg}.
|
||||
*
|
||||
* @since 3.3
|
||||
*/
|
||||
public interface ExpMovingAvgBuilder {
|
||||
|
||||
/**
|
||||
* Define the number of historical documents with significant mathematical weight.
|
||||
*
|
||||
* @param numberOfHistoricalDocuments
|
||||
* @return new instance of {@link ExpMovingAvg}.
|
||||
*/
|
||||
ExpMovingAvg historicalDocuments(int numberOfHistoricalDocuments);
|
||||
|
||||
/**
|
||||
* Define the exponential decay value.
|
||||
*
|
||||
* @param exponentialDecayValue
|
||||
* @return new instance of {@link ExpMovingAvg}.
|
||||
*/
|
||||
ExpMovingAvg alpha(double exponentialDecayValue);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link AggregationExpression} for {@code $sum}.
|
||||
*
|
||||
@@ -658,4 +765,185 @@ public class AccumulatorOperators {
|
||||
return super.toDocument(value, context);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link AggregationExpression} for {@code $covariancePop}.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 3.3
|
||||
*/
|
||||
public static class CovariancePop extends AbstractAggregationExpression {
|
||||
|
||||
private CovariancePop(Object value) {
|
||||
super(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link CovariancePop}.
|
||||
*
|
||||
* @param fieldReference must not be {@literal null}.
|
||||
* @return new instance of {@link CovariancePop}.
|
||||
*/
|
||||
public static CovariancePop covariancePopOf(String fieldReference) {
|
||||
|
||||
Assert.notNull(fieldReference, "FieldReference must not be null!");
|
||||
return new CovariancePop(asFields(fieldReference));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link CovariancePop}.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return new instance of {@link CovariancePop}.
|
||||
*/
|
||||
public static CovariancePop covariancePopOf(AggregationExpression expression) {
|
||||
return new CovariancePop(Collections.singletonList(expression));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link CovariancePop} with all previously added arguments appending the given one.
|
||||
*
|
||||
* @param fieldReference must not be {@literal null}.
|
||||
* @return new instance of {@link CovariancePop}.
|
||||
*/
|
||||
public CovariancePop and(String fieldReference) {
|
||||
return new CovariancePop(append(asFields(fieldReference)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link CovariancePop} with all previously added arguments appending the given one.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return new instance of {@link CovariancePop}.
|
||||
*/
|
||||
public CovariancePop and(AggregationExpression expression) {
|
||||
return new CovariancePop(append(expression));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getMongoMethod() {
|
||||
return "$covariancePop";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link AggregationExpression} for {@code $covarianceSamp}.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 3.3
|
||||
*/
|
||||
public static class CovarianceSamp extends AbstractAggregationExpression {
|
||||
|
||||
private CovarianceSamp(Object value) {
|
||||
super(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link CovarianceSamp}.
|
||||
*
|
||||
* @param fieldReference must not be {@literal null}.
|
||||
* @return new instance of {@link CovarianceSamp}.
|
||||
*/
|
||||
public static CovarianceSamp covarianceSampOf(String fieldReference) {
|
||||
|
||||
Assert.notNull(fieldReference, "FieldReference must not be null!");
|
||||
return new CovarianceSamp(asFields(fieldReference));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link CovarianceSamp}.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return new instance of {@link CovarianceSamp}.
|
||||
*/
|
||||
public static CovarianceSamp covarianceSampOf(AggregationExpression expression) {
|
||||
return new CovarianceSamp(Collections.singletonList(expression));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link CovarianceSamp} with all previously added arguments appending the given one.
|
||||
*
|
||||
* @param fieldReference must not be {@literal null}.
|
||||
* @return new instance of {@link CovarianceSamp}.
|
||||
*/
|
||||
public CovarianceSamp and(String fieldReference) {
|
||||
return new CovarianceSamp(append(asFields(fieldReference)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link CovarianceSamp} with all previously added arguments appending the given one.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return new instance of {@link CovarianceSamp}.
|
||||
*/
|
||||
public CovarianceSamp and(AggregationExpression expression) {
|
||||
return new CovarianceSamp(append(expression));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getMongoMethod() {
|
||||
return "$covarianceSamp";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link ExpMovingAvg} calculates the exponential moving average of numeric values.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 3.3
|
||||
*/
|
||||
public static class ExpMovingAvg extends AbstractAggregationExpression {
|
||||
|
||||
private ExpMovingAvg(Object value) {
|
||||
super(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link ExpMovingAvg} by defining the field holding the value to be used as input.
|
||||
*
|
||||
* @param fieldReference must not be {@literal null}.
|
||||
* @return new instance of {@link ExpMovingAvg}.
|
||||
*/
|
||||
public static ExpMovingAvg expMovingAvgOf(String fieldReference) {
|
||||
return new ExpMovingAvg(Collections.singletonMap("input", Fields.field(fieldReference)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link ExpMovingAvg} by defining the {@link AggregationExpression expression} to compute the value
|
||||
* to be used as input.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return new instance of {@link ExpMovingAvg}.
|
||||
*/
|
||||
public static ExpMovingAvg expMovingAvgOf(AggregationExpression expression) {
|
||||
return new ExpMovingAvg(Collections.singletonMap("input", expression));
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the number of historical documents with significant mathematical weight. <br />
|
||||
* Specify either {@link #n(int) N} or {@link #alpha(double) aplha}. Not both!
|
||||
*
|
||||
* @param numberOfHistoricalDocuments
|
||||
* @return new instance of {@link ExpMovingAvg}.
|
||||
*/
|
||||
public ExpMovingAvg n/*umber of historical documents*/(int numberOfHistoricalDocuments) {
|
||||
return new ExpMovingAvg(append("N", numberOfHistoricalDocuments));
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the exponential decay value. <br />
|
||||
* Specify either {@link #alpha(double) aplha} or {@link #n(int) N}. Not both!
|
||||
*
|
||||
* @param exponentialDecayValue
|
||||
* @return new instance of {@link ExpMovingAvg}.
|
||||
*/
|
||||
public ExpMovingAvg alpha(double exponentialDecayValue) {
|
||||
return new ExpMovingAvg(append("alpha", exponentialDecayValue));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getMongoMethod() {
|
||||
return "$expMovingAvg";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -201,4 +201,5 @@ public class AddFieldsOperation extends DocumentEnhancingOperation {
|
||||
AddFieldsOperationBuilder withValueOfExpression(String operation, Object... values);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -227,7 +227,7 @@ public class Aggregation {
|
||||
|
||||
/**
|
||||
* Obtain an {@link AddFieldsOperationBuilder builder} instance to create a new {@link AddFieldsOperation}.
|
||||
* <p/>
|
||||
* <br />
|
||||
* Starting in version 4.2, MongoDB adds a new aggregation pipeline stage {@link AggregationUpdate#set $set} that is
|
||||
* an alias for {@code $addFields}.
|
||||
*
|
||||
@@ -499,6 +499,17 @@ public class Aggregation {
|
||||
return new MatchOperation(criteria);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link MatchOperation} using the given {@link AggregationExpression}.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return new instance of {@link MatchOperation}.
|
||||
* @since 3.3
|
||||
*/
|
||||
public static MatchOperation match(AggregationExpression expression) {
|
||||
return new MatchOperation(expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link GeoNearOperation} instance from the given {@link NearQuery} and the {@code distanceField}. The
|
||||
* {@code distanceField} defines output field that contains the calculated distance.
|
||||
@@ -715,7 +726,7 @@ public class Aggregation {
|
||||
|
||||
/**
|
||||
* Converts this {@link Aggregation} specification to a {@link Document}.
|
||||
* <p/>
|
||||
* <br />
|
||||
* MongoDB requires as of 3.6 cursor-based aggregation. Use {@link #toPipeline(AggregationOperationContext)} to render
|
||||
* an aggregation pipeline.
|
||||
*
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
package org.springframework.data.mongodb.core.aggregation;
|
||||
|
||||
import org.bson.Document;
|
||||
import org.springframework.data.mongodb.MongoExpression;
|
||||
|
||||
/**
|
||||
* An {@link AggregationExpression} can be used with field expressions in aggregation pipeline stages like
|
||||
@@ -25,7 +26,37 @@ import org.bson.Document;
|
||||
* @author Oliver Gierke
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public interface AggregationExpression {
|
||||
public interface AggregationExpression extends MongoExpression {
|
||||
|
||||
/**
|
||||
* Create an {@link AggregationExpression} out of a given {@link MongoExpression} to ensure the resulting
|
||||
* {@link MongoExpression#toDocument() Document} is mapped against the {@link AggregationOperationContext}. <br />
|
||||
* If the given expression is already an {@link AggregationExpression} the very same instance is returned.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return never {@literal null}.
|
||||
* @since 3.2
|
||||
*/
|
||||
static AggregationExpression from(MongoExpression expression) {
|
||||
|
||||
if (expression instanceof AggregationExpression) {
|
||||
return AggregationExpression.class.cast(expression);
|
||||
}
|
||||
|
||||
return (context) -> context.getMappedObject(expression.toDocument());
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain the as is (unmapped) representation of the {@link AggregationExpression}. Use
|
||||
* {@link #toDocument(AggregationOperationContext)} with a matching {@link AggregationOperationContext context} to
|
||||
* engage domain type mapping including field name resolution.
|
||||
*
|
||||
* @see org.springframework.data.mongodb.MongoExpression#toDocument()
|
||||
*/
|
||||
@Override
|
||||
default Document toDocument() {
|
||||
return toDocument(Aggregation.DEFAULT_CONTEXT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns the {@link AggregationExpression} into a {@link Document} within the given
|
||||
|
||||
@@ -56,6 +56,7 @@ public class AggregationOptions {
|
||||
private final Optional<Document> hint;
|
||||
private Duration maxTime = Duration.ZERO;
|
||||
private ResultOptions resultOptions = ResultOptions.READ;
|
||||
private DomainTypeMapping domainTypeMapping = DomainTypeMapping.RELAXED;
|
||||
|
||||
/**
|
||||
* Creates a new {@link AggregationOptions}.
|
||||
@@ -261,6 +262,14 @@ public class AggregationOptions {
|
||||
return ResultOptions.SKIP.equals(resultOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the domain type mapping strategy do apply. Never {@literal null}.
|
||||
* @since 3.2
|
||||
*/
|
||||
public DomainTypeMapping getDomainTypeMapping() {
|
||||
return domainTypeMapping;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new potentially adjusted copy for the given {@code aggregationCommandObject} with the configuration
|
||||
* applied.
|
||||
@@ -358,6 +367,7 @@ public class AggregationOptions {
|
||||
private @Nullable Document hint;
|
||||
private @Nullable Duration maxTime;
|
||||
private @Nullable ResultOptions resultOptions;
|
||||
private @Nullable DomainTypeMapping domainTypeMapping;
|
||||
|
||||
/**
|
||||
* Defines whether to off-load intensive sort-operations to disk.
|
||||
@@ -475,6 +485,44 @@ public class AggregationOptions {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply a strict domain type mapping considering {@link org.springframework.data.mongodb.core.mapping.Field}
|
||||
* annotations throwing errors for non-existent, but referenced fields.
|
||||
*
|
||||
* @return this.
|
||||
* @since 3.2
|
||||
*/
|
||||
public Builder strictMapping() {
|
||||
|
||||
this.domainTypeMapping = DomainTypeMapping.STRICT;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply a relaxed domain type mapping considering {@link org.springframework.data.mongodb.core.mapping.Field}
|
||||
* annotations using the user provided name if a referenced field does not exist.
|
||||
*
|
||||
* @return this.
|
||||
* @since 3.2
|
||||
*/
|
||||
public Builder relaxedMapping() {
|
||||
|
||||
this.domainTypeMapping = DomainTypeMapping.RELAXED;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply no domain type mapping at all taking the pipeline as-is.
|
||||
*
|
||||
* @return this.
|
||||
* @since 3.2
|
||||
*/
|
||||
public Builder noMapping() {
|
||||
|
||||
this.domainTypeMapping = DomainTypeMapping.NONE;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new {@link AggregationOptions} instance with the given configuration.
|
||||
*
|
||||
@@ -489,6 +537,9 @@ public class AggregationOptions {
|
||||
if (resultOptions != null) {
|
||||
options.resultOptions = resultOptions;
|
||||
}
|
||||
if (domainTypeMapping != null) {
|
||||
options.domainTypeMapping = domainTypeMapping;
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
@@ -508,4 +559,27 @@ public class AggregationOptions {
|
||||
*/
|
||||
READ;
|
||||
}
|
||||
|
||||
/**
|
||||
* Aggregation pipeline Domain type mappings supported by the mapping layer.
|
||||
*
|
||||
* @since 3.2
|
||||
*/
|
||||
public enum DomainTypeMapping {
|
||||
|
||||
/**
|
||||
* Mapping throws errors for non-existent, but referenced fields.
|
||||
*/
|
||||
STRICT,
|
||||
|
||||
/**
|
||||
* Fields that do not exist in the model are treated as-is.
|
||||
*/
|
||||
RELAXED,
|
||||
|
||||
/**
|
||||
* Do not attempt to map fields against the model and treat the entire pipeline as-is.
|
||||
*/
|
||||
NONE
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,15 +24,15 @@ import org.springframework.util.Assert;
|
||||
* expression</a>. <br />
|
||||
* <br />
|
||||
* <strong>Samples:</strong> <br />
|
||||
* <code>
|
||||
* <pre>
|
||||
* <code>
|
||||
* // { $and: [ { $gt: [ "$qty", 100 ] }, { $lt: [ "$qty", 250 ] } ] }
|
||||
* expressionOf("qty > 100 && qty < 250);
|
||||
*
|
||||
* // { $cond : { if : { $gte : [ "$a", 42 ]}, then : "answer", else : "no-answer" } }
|
||||
* expressionOf("cond(a >= 42, 'answer', 'no-answer')");
|
||||
* </pre>
|
||||
* </code>
|
||||
* </pre>
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
|
||||
@@ -71,8 +71,7 @@ import org.springframework.util.Assert;
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
* @see <a href=
|
||||
* "https://docs.mongodb.com/manual/reference/method/db.collection.update/#update-with-aggregation-pipeline">MongoDB
|
||||
* @see <a href="https://docs.mongodb.com/manual/reference/method/db.collection.update/#update-with-aggregation-pipeline">MongoDB
|
||||
* Reference Documentation</a>
|
||||
* @since 3.0
|
||||
*/
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -28,8 +28,7 @@ import org.springframework.util.Assert;
|
||||
* We recommend to use the static factory method {@link Aggregation#bucketAuto(String, int)} instead of creating
|
||||
* instances of this class directly.
|
||||
*
|
||||
* @see <a href=
|
||||
* "https://docs.mongodb.org/manual/reference/aggregation/bucketAuto/">https://docs.mongodb.org/manual/reference/aggregation/bucketAuto/</a>
|
||||
* @see <a href="https://docs.mongodb.org/manual/reference/aggregation/bucketAuto/">https://docs.mongodb.org/manual/reference/aggregation/bucketAuto/</a>
|
||||
* @see BucketOperationSupport
|
||||
* @author Mark Paluch
|
||||
* @author Christoph Strobl
|
||||
@@ -248,8 +247,7 @@ public class BucketAutoOperation extends BucketOperationSupport<BucketAutoOperat
|
||||
/**
|
||||
* Supported MongoDB granularities.
|
||||
*
|
||||
* @see <a
|
||||
* href="https://docs.mongodb.com/manual/reference/operator/aggregation/bucketAuto/#granularity>https://docs.mongodb.com/manual/reference/operator/aggregation/bucketAuto/#granularity</a>
|
||||
* @see <a href="https://docs.mongodb.com/manual/reference/operator/aggregation/bucketAuto/#granularity">https://docs.mongodb.com/manual/reference/operator/aggregation/bucketAuto/#granularity</a>
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public enum Granularities implements Granularity {
|
||||
|
||||
@@ -31,8 +31,7 @@ import org.springframework.util.Assert;
|
||||
* We recommend to use the static factory method {@link Aggregation#bucket(String)} instead of creating instances of
|
||||
* this class directly.
|
||||
*
|
||||
* @see <a href=
|
||||
* "https://docs.mongodb.org/manual/reference/aggregation/bucket/">https://docs.mongodb.org/manual/reference/aggregation/bucket/</a>
|
||||
* @see <a href="https://docs.mongodb.org/manual/reference/aggregation/bucket/">https://docs.mongodb.org/manual/reference/aggregation/bucket/</a>
|
||||
* @see BucketOperationSupport
|
||||
* @author Mark Paluch
|
||||
* @since 1.10
|
||||
|
||||
@@ -17,6 +17,7 @@ package org.springframework.data.mongodb.core.aggregation;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@@ -235,7 +236,7 @@ public class ConditionalOperators {
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @see <a href=
|
||||
* "https://docs.mongodb.com/manual/reference/operator/aggregation/ifNull/">https://docs.mongodb.com/manual/reference/operator/aggregation/ifNull/</a>
|
||||
* "https://docs.mongodb.com/manual/reference/operator/aggregation/ifNull/">https://docs.mongodb.com/manual/reference/operator/aggregation/ifNull/</a>
|
||||
*/
|
||||
public static class IfNull implements AggregationExpression {
|
||||
|
||||
@@ -251,7 +252,8 @@ public class ConditionalOperators {
|
||||
/**
|
||||
* Creates new {@link IfNull}.
|
||||
*
|
||||
* @param fieldReference the field to check for a {@literal null} value, field reference must not be {@literal null}.
|
||||
* @param fieldReference the field to check for a {@literal null} value, field reference must not be
|
||||
* {@literal null}.
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
public static ThenBuilder ifNull(String fieldReference) {
|
||||
@@ -264,7 +266,7 @@ public class ConditionalOperators {
|
||||
* Creates new {@link IfNull}.
|
||||
*
|
||||
* @param expression the expression to check for a {@literal null} value, field reference must not be
|
||||
* {@literal null}.
|
||||
* {@literal null}.
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
public static ThenBuilder ifNull(AggregationExpression expression) {
|
||||
@@ -282,19 +284,29 @@ public class ConditionalOperators {
|
||||
|
||||
List<Object> list = new ArrayList<Object>();
|
||||
|
||||
if (condition instanceof Field) {
|
||||
list.add(context.getReference((Field) condition).toString());
|
||||
} else if (condition instanceof AggregationExpression) {
|
||||
list.add(((AggregationExpression) condition).toDocument(context));
|
||||
if (condition instanceof Collection) {
|
||||
for (Object val : ((Collection) this.condition)) {
|
||||
list.add(mapCondition(val, context));
|
||||
}
|
||||
} else {
|
||||
list.add(condition);
|
||||
list.add(mapCondition(condition, context));
|
||||
}
|
||||
|
||||
list.add(resolve(value, context));
|
||||
|
||||
return new Document("$ifNull", list);
|
||||
}
|
||||
|
||||
private Object mapCondition(Object condition, AggregationOperationContext context) {
|
||||
|
||||
if (condition instanceof Field) {
|
||||
return context.getReference((Field) condition).toString();
|
||||
} else if (condition instanceof AggregationExpression) {
|
||||
return ((AggregationExpression) condition).toDocument(context);
|
||||
} else {
|
||||
return condition;
|
||||
}
|
||||
}
|
||||
|
||||
private Object resolve(Object value, AggregationOperationContext context) {
|
||||
|
||||
if (value instanceof Field) {
|
||||
@@ -315,28 +327,48 @@ public class ConditionalOperators {
|
||||
|
||||
/**
|
||||
* @param fieldReference the field to check for a {@literal null} value, field reference must not be
|
||||
* {@literal null}.
|
||||
* {@literal null}.
|
||||
* @return the {@link ThenBuilder}
|
||||
*/
|
||||
ThenBuilder ifNull(String fieldReference);
|
||||
|
||||
/**
|
||||
* @param expression the expression to check for a {@literal null} value, field name must not be {@literal null}
|
||||
* or empty.
|
||||
* @return the {@link ThenBuilder}
|
||||
* or empty.
|
||||
* @return the {@link ThenBuilder}.
|
||||
*/
|
||||
ThenBuilder ifNull(AggregationExpression expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
* @since 3.3
|
||||
*/
|
||||
public interface OrBuilder {
|
||||
|
||||
/**
|
||||
* @param fieldReference the field to check for a {@literal null} value, field reference must not be
|
||||
* {@literal null}.
|
||||
* @return the {@link ThenBuilder}
|
||||
*/
|
||||
ThenBuilder orIfNull(String fieldReference);
|
||||
|
||||
/**
|
||||
* @param expression the expression to check for a {@literal null} value,
|
||||
* @return the {@link ThenBuilder}.
|
||||
*/
|
||||
ThenBuilder orIfNull(AggregationExpression expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public interface ThenBuilder {
|
||||
public interface ThenBuilder extends OrBuilder {
|
||||
|
||||
/**
|
||||
* @param value the value to be used if the {@code $ifNull} condition evaluates {@literal true}. Can be a
|
||||
* {@link Document}, a value that is supported by MongoDB or a value that can be converted to a MongoDB
|
||||
* representation but must not be {@literal null}.
|
||||
* {@link Document}, a value that is supported by MongoDB or a value that can be converted to a MongoDB
|
||||
* representation but must not be {@literal null}.
|
||||
* @return new instance of {@link IfNull}.
|
||||
*/
|
||||
IfNull then(Object value);
|
||||
@@ -361,9 +393,10 @@ public class ConditionalOperators {
|
||||
*/
|
||||
static final class IfNullOperatorBuilder implements IfNullBuilder, ThenBuilder {
|
||||
|
||||
private @Nullable Object condition;
|
||||
private @Nullable List<Object> conditions;
|
||||
|
||||
private IfNullOperatorBuilder() {
|
||||
conditions = new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -381,7 +414,7 @@ public class ConditionalOperators {
|
||||
public ThenBuilder ifNull(String fieldReference) {
|
||||
|
||||
Assert.hasText(fieldReference, "FieldReference name must not be null or empty!");
|
||||
this.condition = Fields.field(fieldReference);
|
||||
this.conditions.add(Fields.field(fieldReference));
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -392,15 +425,25 @@ public class ConditionalOperators {
|
||||
public ThenBuilder ifNull(AggregationExpression expression) {
|
||||
|
||||
Assert.notNull(expression, "AggregationExpression name must not be null or empty!");
|
||||
this.condition = expression;
|
||||
this.conditions.add(expression);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ThenBuilder orIfNull(String fieldReference) {
|
||||
return ifNull(fieldReference);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ThenBuilder orIfNull(AggregationExpression expression) {
|
||||
return ifNull(expression);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.aggregation.ConditionalOperators.IfNull.ThenBuilder#then(java.lang.Object)
|
||||
*/
|
||||
public IfNull then(Object value) {
|
||||
return new IfNull(condition, value);
|
||||
return new IfNull(conditions, value);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
@@ -409,7 +452,7 @@ public class ConditionalOperators {
|
||||
public IfNull thenValueOf(String fieldReference) {
|
||||
|
||||
Assert.notNull(fieldReference, "FieldReference must not be null!");
|
||||
return new IfNull(condition, Fields.field(fieldReference));
|
||||
return new IfNull(conditions, Fields.field(fieldReference));
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
@@ -418,7 +461,7 @@ public class ConditionalOperators {
|
||||
public IfNull thenValueOf(AggregationExpression expression) {
|
||||
|
||||
Assert.notNull(expression, "Expression must not be null!");
|
||||
return new IfNull(condition, expression);
|
||||
return new IfNull(conditions, expression);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -458,7 +501,7 @@ public class ConditionalOperators {
|
||||
public static Switch switchCases(List<CaseOperator> conditions) {
|
||||
|
||||
Assert.notNull(conditions, "Conditions must not be null!");
|
||||
return new Switch(Collections.<String, Object>singletonMap("branches", new ArrayList<CaseOperator>(conditions)));
|
||||
return new Switch(Collections.<String, Object> singletonMap("branches", new ArrayList<CaseOperator>(conditions)));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -545,7 +588,7 @@ public class ConditionalOperators {
|
||||
* @author Mark Paluch
|
||||
* @author Christoph Strobl
|
||||
* @see <a href=
|
||||
* "https://docs.mongodb.com/manual/reference/operator/aggregation/cond/">https://docs.mongodb.com/manual/reference/operator/aggregation/cond/</a>
|
||||
* "https://docs.mongodb.com/manual/reference/operator/aggregation/cond/">https://docs.mongodb.com/manual/reference/operator/aggregation/cond/</a>
|
||||
*/
|
||||
public static class Cond implements AggregationExpression {
|
||||
|
||||
@@ -806,8 +849,8 @@ public class ConditionalOperators {
|
||||
|
||||
/**
|
||||
* @param value the value to be used if the condition evaluates {@literal true}. Can be a {@link Document}, a
|
||||
* value that is supported by MongoDB or a value that can be converted to a MongoDB representation but
|
||||
* must not be {@literal null}.
|
||||
* value that is supported by MongoDB or a value that can be converted to a MongoDB representation but
|
||||
* must not be {@literal null}.
|
||||
* @return the {@link OtherwiseBuilder}
|
||||
*/
|
||||
OtherwiseBuilder then(Object value);
|
||||
@@ -832,8 +875,8 @@ public class ConditionalOperators {
|
||||
|
||||
/**
|
||||
* @param value the value to be used if the condition evaluates {@literal false}. Can be a {@link Document}, a
|
||||
* value that is supported by MongoDB or a value that can be converted to a MongoDB representation but
|
||||
* must not be {@literal null}.
|
||||
* value that is supported by MongoDB or a value that can be converted to a MongoDB representation but
|
||||
* must not be {@literal null}.
|
||||
* @return the {@link Cond}
|
||||
*/
|
||||
Cond otherwise(Object value);
|
||||
@@ -861,8 +904,7 @@ public class ConditionalOperators {
|
||||
private @Nullable Object condition;
|
||||
private @Nullable Object thenValue;
|
||||
|
||||
private ConditionalExpressionBuilder() {
|
||||
}
|
||||
private ConditionalExpressionBuilder() {}
|
||||
|
||||
/**
|
||||
* Creates a new builder for {@link Cond}.
|
||||
|
||||
@@ -231,6 +231,17 @@ public class ConvertOperators {
|
||||
return ToString.toString(valueObject());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link AggregationExpression} for {@code $degreesToRadians} that converts an input value measured in degrees to
|
||||
* radians.
|
||||
*
|
||||
* @return new instance of {@link DegreesToRadians}.
|
||||
* @since 3.3
|
||||
*/
|
||||
public DegreesToRadians convertDegreesToRadians() {
|
||||
return DegreesToRadians.degreesToRadians(valueObject());
|
||||
}
|
||||
|
||||
private Convert createConvert() {
|
||||
return usesFieldRef() ? Convert.convertValueOf(fieldReference) : Convert.convertValueOf(expression);
|
||||
}
|
||||
@@ -317,9 +328,9 @@ public class ConvertOperators {
|
||||
* <dt>1</dt>
|
||||
* <dd>double</dd>
|
||||
* <dt>2</dt>
|
||||
* <dd>string</li>
|
||||
* <dd>string</dd>
|
||||
* <dt>7</dt>
|
||||
* <dd>objectId</li>
|
||||
* <dd>objectId</dd>
|
||||
* <dt>8</dt>
|
||||
* <dd>bool</dd>
|
||||
* <dt>9</dt>
|
||||
@@ -692,4 +703,52 @@ public class ConvertOperators {
|
||||
return "$toString";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link AggregationExpression} for {@code $degreesToRadians} that converts an input value measured in degrees to radians.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 3.3
|
||||
*/
|
||||
public static class DegreesToRadians extends AbstractAggregationExpression {
|
||||
|
||||
private DegreesToRadians(Object value) {
|
||||
super(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new instance of {@link DegreesToRadians} that converts the value of the given field, measured in degrees, to radians.
|
||||
*
|
||||
* @param fieldName must not be {@literal null}.
|
||||
* @return new instance of {@link DegreesToRadians}.
|
||||
*/
|
||||
public static DegreesToRadians degreesToRadiansOf(String fieldName) {
|
||||
return degreesToRadians(Fields.field(fieldName));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new instance of {@link DegreesToRadians} that converts the result of the given {@link AggregationExpression expression}, measured in degrees, to radians.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return new instance of {@link DegreesToRadians}.
|
||||
*/
|
||||
public static DegreesToRadians degreesToRadiansOf(AggregationExpression expression) {
|
||||
return degreesToRadians(expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new instance of {@link DegreesToRadians} that converts the given value, measured in degrees, to radians.
|
||||
*
|
||||
* @param value must not be {@literal null}.
|
||||
* @return new instance of {@link DegreesToRadians}.
|
||||
*/
|
||||
public static DegreesToRadians degreesToRadians(Object value) {
|
||||
return new DegreesToRadians(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getMongoMethod() {
|
||||
return "$degreesToRadians";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,8 +24,7 @@ import org.springframework.util.Assert;
|
||||
* We recommend to use the static factory method {@link Aggregation#count()} instead of creating instances of this class
|
||||
* directly.
|
||||
*
|
||||
* @see <a href=
|
||||
* "https://docs.mongodb.com/manual/reference/operator/aggregation/count/#pipe._S_count">https://docs.mongodb.com/manual/reference/operator/aggregation/count/</a>
|
||||
* @see <a href="https://docs.mongodb.com/manual/reference/operator/aggregation/count/#pipe._S_count">https://docs.mongodb.com/manual/reference/operator/aggregation/count/</a>
|
||||
* @author Mark Paluch
|
||||
* @since 1.10
|
||||
*/
|
||||
|
||||
@@ -15,9 +15,16 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core.aggregation;
|
||||
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.TimeZone;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -45,6 +52,19 @@ public class DateOperators {
|
||||
return new DateOperatorFactory(fieldReference);
|
||||
}
|
||||
|
||||
/**
|
||||
* Take the date referenced by given {@literal fieldReference}.
|
||||
*
|
||||
* @param fieldReference must not be {@literal null}.
|
||||
* @return new instance of {@link DateOperatorFactory}.
|
||||
* @since 3.3
|
||||
*/
|
||||
public static DateOperatorFactory zonedDateOf(String fieldReference, Timezone timezone) {
|
||||
|
||||
Assert.notNull(fieldReference, "FieldReference must not be null!");
|
||||
return new DateOperatorFactory(fieldReference).withTimezone(timezone);
|
||||
}
|
||||
|
||||
/**
|
||||
* Take the date resulting from the given {@link AggregationExpression}.
|
||||
*
|
||||
@@ -57,9 +77,22 @@ public class DateOperators {
|
||||
return new DateOperatorFactory(expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* Take the date resulting from the given {@link AggregationExpression}.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return new instance of {@link DateOperatorFactory}.
|
||||
* @since 3.3
|
||||
*/
|
||||
public static DateOperatorFactory zonedDateOf(AggregationExpression expression, Timezone timezone) {
|
||||
|
||||
Assert.notNull(expression, "Expression must not be null!");
|
||||
return new DateOperatorFactory(expression).withTimezone(timezone);
|
||||
}
|
||||
|
||||
/**
|
||||
* Take the given value as date.
|
||||
* <p/>
|
||||
* <br />
|
||||
* This can be one of:
|
||||
* <ul>
|
||||
* <li>{@link java.util.Date}</li>
|
||||
@@ -109,7 +142,7 @@ public class DateOperators {
|
||||
* Timezone represents a MongoDB timezone abstraction which can be represented with a timezone ID or offset as a
|
||||
* {@link String}. Also accepts a {@link AggregationExpression} or {@link Field} that resolves to a {@link String} of
|
||||
* either Olson Timezone Identifier or a UTC Offset.<br />
|
||||
* <table valign="top">
|
||||
* <table>
|
||||
* <tr>
|
||||
* <th>Format</th>
|
||||
* <th>Example</th>
|
||||
@@ -130,6 +163,7 @@ public class DateOperators {
|
||||
* <strong>NOTE: </strong>Support for timezones in aggregations Requires MongoDB 3.6 or later.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
* @since 2.1
|
||||
*/
|
||||
public static class Timezone {
|
||||
@@ -156,7 +190,7 @@ public class DateOperators {
|
||||
* representing an Olson Timezone Identifier or UTC Offset.
|
||||
*
|
||||
* @param value the plain timezone {@link String}, a {@link Field} holding the timezone or an
|
||||
* {@link AggregationExpression} resulting in the timezone.
|
||||
* {@link AggregationExpression} resulting in the timezone.
|
||||
* @return new instance of {@link Timezone}.
|
||||
*/
|
||||
public static Timezone valueOf(Object value) {
|
||||
@@ -165,6 +199,61 @@ public class DateOperators {
|
||||
return new Timezone(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link Timezone} for the given {@link TimeZone} rendering the offset as UTC offset.
|
||||
*
|
||||
* @param timeZone {@link TimeZone} rendering the offset as UTC offset.
|
||||
* @return new instance of {@link Timezone}.
|
||||
* @since 3.3
|
||||
*/
|
||||
public static Timezone fromOffset(TimeZone timeZone) {
|
||||
|
||||
Assert.notNull(timeZone, "TimeZone must not be null!");
|
||||
|
||||
return fromOffset(
|
||||
ZoneOffset.ofTotalSeconds(Math.toIntExact(TimeUnit.MILLISECONDS.toSeconds(timeZone.getRawOffset()))));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link Timezone} for the given {@link ZoneOffset} rendering the offset as UTC offset.
|
||||
*
|
||||
* @param offset {@link ZoneOffset} rendering the offset as UTC offset.
|
||||
* @return new instance of {@link Timezone}.
|
||||
* @since 3.3
|
||||
*/
|
||||
public static Timezone fromOffset(ZoneOffset offset) {
|
||||
|
||||
Assert.notNull(offset, "ZoneOffset must not be null!");
|
||||
return new Timezone(offset.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link Timezone} for the given {@link TimeZone} rendering the offset as UTC offset.
|
||||
*
|
||||
* @param timeZone {@link Timezone} rendering the offset as zone identifier.
|
||||
* @return new instance of {@link Timezone}.
|
||||
* @since 3.3
|
||||
*/
|
||||
public static Timezone fromZone(TimeZone timeZone) {
|
||||
|
||||
Assert.notNull(timeZone, "TimeZone must not be null!");
|
||||
|
||||
return valueOf(timeZone.getID());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link Timezone} for the given {@link java.time.ZoneId} rendering the offset as UTC offset.
|
||||
*
|
||||
* @param zoneId {@link ZoneId} rendering the offset as zone identifier.
|
||||
* @return new instance of {@link Timezone}.
|
||||
* @since 3.3
|
||||
*/
|
||||
public static Timezone fromZone(ZoneId zoneId) {
|
||||
|
||||
Assert.notNull(zoneId, "ZoneId must not be null!");
|
||||
return new Timezone(zoneId.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link Timezone} for the {@link Field} reference holding the Olson Timezone Identifier or UTC Offset.
|
||||
*
|
||||
@@ -185,6 +274,11 @@ public class DateOperators {
|
||||
public static Timezone ofExpression(AggregationExpression expression) {
|
||||
return valueOf(expression);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
Object getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -240,7 +334,7 @@ public class DateOperators {
|
||||
|
||||
/**
|
||||
* Creates new {@link DateOperatorFactory} for given {@code value} that resolves to a Date.
|
||||
* <p/>
|
||||
* <br />
|
||||
* <ul>
|
||||
* <li>{@link java.util.Date}</li>
|
||||
* <li>{@link java.util.Calendar}</li>
|
||||
@@ -274,6 +368,89 @@ public class DateOperators {
|
||||
return new DateOperatorFactory(fieldReference, expression, dateValue, timezone);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link AggregationExpression} that adds the value of the given {@link AggregationExpression
|
||||
* expression} (in {@literal units}).
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @param unit the unit of measure. Must not be {@literal null}.
|
||||
* @return new instance of {@link DateAdd}. @since 3.3
|
||||
*/
|
||||
public DateAdd addValueOf(AggregationExpression expression, String unit) {
|
||||
return applyTimezone(DateAdd.addValueOf(expression, unit).toDate(dateReference()), timezone);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link AggregationExpression} that adds the value of the given {@link AggregationExpression
|
||||
* expression} (in {@literal units}).
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @param unit the unit of measure. Must not be {@literal null}.
|
||||
* @return new instance of {@link DateAdd}. @since 3.3
|
||||
*/
|
||||
public DateAdd addValueOf(AggregationExpression expression, TemporalUnit unit) {
|
||||
|
||||
Assert.notNull(unit, "TemporalUnit must not be null");
|
||||
return applyTimezone(DateAdd.addValueOf(expression, unit.name().toLowerCase(Locale.ROOT)).toDate(dateReference()),
|
||||
timezone);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link AggregationExpression} that adds the value stored at the given {@literal field} (in
|
||||
* {@literal units}).
|
||||
*
|
||||
* @param fieldReference must not be {@literal null}.
|
||||
* @param unit the unit of measure. Must not be {@literal null}.
|
||||
* @return new instance of {@link DateAdd}. @since 3.3
|
||||
*/
|
||||
public DateAdd addValueOf(String fieldReference, String unit) {
|
||||
return applyTimezone(DateAdd.addValueOf(fieldReference, unit).toDate(dateReference()), timezone);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link AggregationExpression} that adds the value stored at the given {@literal field} (in
|
||||
* {@literal units}).
|
||||
*
|
||||
* @param fieldReference must not be {@literal null}.
|
||||
* @param unit the unit of measure. Must not be {@literal null}.
|
||||
* @return new instance of {@link DateAdd}. @since 3.3
|
||||
*/
|
||||
public DateAdd addValueOf(String fieldReference, TemporalUnit unit) {
|
||||
|
||||
Assert.notNull(unit, "TemporalUnit must not be null");
|
||||
|
||||
return applyTimezone(
|
||||
DateAdd.addValueOf(fieldReference, unit.name().toLowerCase(Locale.ROOT)).toDate(dateReference()), timezone);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link AggregationExpression} that adds the given value (in {@literal units}).
|
||||
*
|
||||
* @param value must not be {@literal null}.
|
||||
* @param unit the unit of measure. Must not be {@literal null}.
|
||||
* @return
|
||||
* @since 3.3 new instance of {@link DateAdd}.
|
||||
*/
|
||||
public DateAdd add(Object value, String unit) {
|
||||
return applyTimezone(DateAdd.addValue(value, unit).toDate(dateReference()), timezone);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link AggregationExpression} that adds the given value (in {@literal units}).
|
||||
*
|
||||
* @param value must not be {@literal null}.
|
||||
* @param unit the unit of measure. Must not be {@literal null}.
|
||||
* @return
|
||||
* @since 3.3 new instance of {@link DateAdd}.
|
||||
*/
|
||||
public DateAdd add(Object value, TemporalUnit unit) {
|
||||
|
||||
Assert.notNull(unit, "TemporalUnit must not be null");
|
||||
|
||||
return applyTimezone(DateAdd.addValue(value, unit.name().toLowerCase(Locale.ROOT)).toDate(dateReference()),
|
||||
timezone);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link AggregationExpression} that returns the day of the year for a date as a number between 1 and
|
||||
* 366.
|
||||
@@ -304,6 +481,90 @@ public class DateOperators {
|
||||
return applyTimezone(DayOfWeek.dayOfWeek(dateReference()), timezone);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link AggregationExpression} that calculates the difference (in {@literal units}) to the date
|
||||
* computed by the given {@link AggregationExpression expression}.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @param unit the unit of measure. Must not be {@literal null}.
|
||||
* @return new instance of {@link DateAdd}. @since 3.3
|
||||
*/
|
||||
public DateDiff diffValueOf(AggregationExpression expression, String unit) {
|
||||
return applyTimezone(DateDiff.diffValueOf(expression, unit).toDate(dateReference()), timezone);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link AggregationExpression} that calculates the difference (in {@literal units}) to the date
|
||||
* computed by the given {@link AggregationExpression expression}.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @param unit the unit of measure. Must not be {@literal null}.
|
||||
* @return new instance of {@link DateAdd}. @since 3.3
|
||||
*/
|
||||
public DateDiff diffValueOf(AggregationExpression expression, TemporalUnit unit) {
|
||||
|
||||
Assert.notNull(unit, "TemporalUnit must not be null");
|
||||
|
||||
return applyTimezone(
|
||||
DateDiff.diffValueOf(expression, unit.name().toLowerCase(Locale.ROOT)).toDate(dateReference()), timezone);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link AggregationExpression} that calculates the difference (in {@literal units}) to the date stored
|
||||
* at the given {@literal field}.
|
||||
*
|
||||
* @param fieldReference must not be {@literal null}.
|
||||
* @param unit the unit of measure. Must not be {@literal null}.
|
||||
* @return new instance of {@link DateAdd}. @since 3.3
|
||||
*/
|
||||
public DateDiff diffValueOf(String fieldReference, String unit) {
|
||||
return applyTimezone(DateDiff.diffValueOf(fieldReference, unit).toDate(dateReference()), timezone);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link AggregationExpression} that calculates the difference (in {@literal units}) to the date stored
|
||||
* at the given {@literal field}.
|
||||
*
|
||||
* @param fieldReference must not be {@literal null}.
|
||||
* @param unit the unit of measure. Must not be {@literal null}.
|
||||
* @return new instance of {@link DateAdd}. @since 3.3
|
||||
*/
|
||||
public DateDiff diffValueOf(String fieldReference, TemporalUnit unit) {
|
||||
|
||||
Assert.notNull(unit, "TemporalUnit must not be null");
|
||||
|
||||
return applyTimezone(
|
||||
DateDiff.diffValueOf(fieldReference, unit.name().toLowerCase(Locale.ROOT)).toDate(dateReference()), timezone);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link AggregationExpression} that calculates the difference (in {@literal units}) to the date given
|
||||
* {@literal value}.
|
||||
*
|
||||
* @param value anything the resolves to a valid date. Must not be {@literal null}.
|
||||
* @param unit the unit of measure. Must not be {@literal null}.
|
||||
* @return new instance of {@link DateAdd}. @since 3.3
|
||||
*/
|
||||
public DateDiff diff(Object value, String unit) {
|
||||
return applyTimezone(DateDiff.diffValue(value, unit).toDate(dateReference()), timezone);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link AggregationExpression} that calculates the difference (in {@literal units}) to the date given
|
||||
* {@literal value}.
|
||||
*
|
||||
* @param value anything the resolves to a valid date. Must not be {@literal null}.
|
||||
* @param unit the unit of measure. Must not be {@literal null}.
|
||||
* @return new instance of {@link DateAdd}. @since 3.3
|
||||
*/
|
||||
public DateDiff diff(Object value, TemporalUnit unit) {
|
||||
|
||||
Assert.notNull(unit, "TemporalUnit must not be null");
|
||||
|
||||
return applyTimezone(DateDiff.diffValue(value, unit.name().toLowerCase(Locale.ROOT)).toDate(dateReference()),
|
||||
timezone);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link AggregationExpression} that returns the year portion of a date.
|
||||
*
|
||||
@@ -1480,7 +1741,6 @@ public class DateOperators {
|
||||
} else {
|
||||
clone.put("timezone", ((Timezone) value).value);
|
||||
}
|
||||
|
||||
} else {
|
||||
clone.put(key, value);
|
||||
}
|
||||
@@ -1911,7 +2171,7 @@ public class DateOperators {
|
||||
* @author Matt Morrissette
|
||||
* @author Christoph Strobl
|
||||
* @see <a href=
|
||||
* "https://docs.mongodb.com/manual/reference/operator/aggregation/dateFromParts/">https://docs.mongodb.com/manual/reference/operator/aggregation/dateFromParts/</a>
|
||||
* "https://docs.mongodb.com/manual/reference/operator/aggregation/dateFromParts/">https://docs.mongodb.com/manual/reference/operator/aggregation/dateFromParts/</a>
|
||||
* @since 2.1
|
||||
*/
|
||||
public static class DateFromParts extends TimezonedDateAggregationExpression implements DateParts<DateFromParts> {
|
||||
@@ -2086,7 +2346,7 @@ public class DateOperators {
|
||||
* @author Matt Morrissette
|
||||
* @author Christoph Strobl
|
||||
* @see <a href=
|
||||
* "https://docs.mongodb.com/manual/reference/operator/aggregation/dateFromParts/">https://docs.mongodb.com/manual/reference/operator/aggregation/dateFromParts/</a>
|
||||
* "https://docs.mongodb.com/manual/reference/operator/aggregation/dateFromParts/">https://docs.mongodb.com/manual/reference/operator/aggregation/dateFromParts/</a>
|
||||
* @since 2.1
|
||||
*/
|
||||
public static class IsoDateFromParts extends TimezonedDateAggregationExpression
|
||||
@@ -2262,7 +2522,7 @@ public class DateOperators {
|
||||
* @author Matt Morrissette
|
||||
* @author Christoph Strobl
|
||||
* @see <a href=
|
||||
* "https://docs.mongodb.com/manual/reference/operator/aggregation/dateToParts/">https://docs.mongodb.com/manual/reference/operator/aggregation/dateToParts/</a>
|
||||
* "https://docs.mongodb.com/manual/reference/operator/aggregation/dateToParts/">https://docs.mongodb.com/manual/reference/operator/aggregation/dateToParts/</a>
|
||||
* @since 2.1
|
||||
*/
|
||||
public static class DateToParts extends TimezonedDateAggregationExpression {
|
||||
@@ -2343,7 +2603,7 @@ public class DateOperators {
|
||||
* @author Matt Morrissette
|
||||
* @author Christoph Strobl
|
||||
* @see <a href=
|
||||
* "https://docs.mongodb.com/manual/reference/operator/aggregation/dateFromString/">https://docs.mongodb.com/manual/reference/operator/aggregation/dateFromString/</a>
|
||||
* "https://docs.mongodb.com/manual/reference/operator/aggregation/dateFromString/">https://docs.mongodb.com/manual/reference/operator/aggregation/dateFromString/</a>
|
||||
* @since 2.1
|
||||
*/
|
||||
public static class DateFromString extends TimezonedDateAggregationExpression {
|
||||
@@ -2418,6 +2678,290 @@ public class DateOperators {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link AggregationExpression} for {@code $dateAdd}.<br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 5.0 or later.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 3.3
|
||||
*/
|
||||
public static class DateAdd extends TimezonedDateAggregationExpression {
|
||||
|
||||
private DateAdd(Object value) {
|
||||
super(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the number of {@literal units} of the result of the given {@link AggregationExpression expression} to a
|
||||
* {@link #toDate(Object) start date}.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @param unit must not be {@literal null}.
|
||||
* @return new instance of {@link DateAdd}.
|
||||
*/
|
||||
public static DateAdd addValueOf(AggregationExpression expression, String unit) {
|
||||
return addValue(expression, unit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the number of {@literal units} from a {@literal field} to a {@link #toDate(Object) start date}.
|
||||
*
|
||||
* @param fieldReference must not be {@literal null}.
|
||||
* @param unit must not be {@literal null}.
|
||||
* @return new instance of {@link DateAdd}.
|
||||
*/
|
||||
public static DateAdd addValueOf(String fieldReference, String unit) {
|
||||
return addValue(Fields.field(fieldReference), unit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the number of {@literal units} to a {@link #toDate(Object) start date}.
|
||||
*
|
||||
* @param value must not be {@literal null}.
|
||||
* @param unit must not be {@literal null}.
|
||||
* @return new instance of {@link DateAdd}.
|
||||
*/
|
||||
public static DateAdd addValue(Object value, String unit) {
|
||||
|
||||
Map<String, Object> args = new HashMap<>();
|
||||
args.put("unit", unit);
|
||||
args.put("amount", value);
|
||||
return new DateAdd(args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the start date, in UTC, for the addition operation.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return new instance of {@link DateAdd}.
|
||||
*/
|
||||
public DateAdd toDateOf(AggregationExpression expression) {
|
||||
return toDate(expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the start date, in UTC, for the addition operation.
|
||||
*
|
||||
* @param fieldReference must not be {@literal null}.
|
||||
* @return new instance of {@link DateAdd}.
|
||||
*/
|
||||
public DateAdd toDateOf(String fieldReference) {
|
||||
return toDate(Fields.field(fieldReference));
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the start date, in UTC, for the addition operation.
|
||||
*
|
||||
* @param dateExpression anything that evaluates to a valid date. Must not be {@literal null}.
|
||||
* @return new instance of {@link DateAdd}.
|
||||
*/
|
||||
public DateAdd toDate(Object dateExpression) {
|
||||
return new DateAdd(append("startDate", dateExpression));
|
||||
}
|
||||
|
||||
/**
|
||||
* Optionally set the {@link Timezone} to use. If not specified {@literal UTC} is used.
|
||||
*
|
||||
* @param timezone must not be {@literal null}. Consider {@link Timezone#none()} instead.
|
||||
* @return new instance of {@link DateAdd}.
|
||||
*/
|
||||
public DateAdd withTimezone(Timezone timezone) {
|
||||
return new DateAdd(appendTimezone(argumentMap(), timezone));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getMongoMethod() {
|
||||
return "$dateAdd";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link AggregationExpression} for {@code $dateDiff}.<br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 5.0 or later.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 3.3
|
||||
*/
|
||||
public static class DateDiff extends TimezonedDateAggregationExpression {
|
||||
|
||||
private DateDiff(Object value) {
|
||||
super(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the number of {@literal units} of the result of the given {@link AggregationExpression expression} to a
|
||||
* {@link #toDate(Object) start date}.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @param unit must not be {@literal null}.
|
||||
* @return new instance of {@link DateAdd}.
|
||||
*/
|
||||
public static DateDiff diffValueOf(AggregationExpression expression, String unit) {
|
||||
return diffValue(expression, unit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the number of {@literal units} from a {@literal field} to a {@link #toDate(Object) start date}.
|
||||
*
|
||||
* @param fieldReference must not be {@literal null}.
|
||||
* @param unit must not be {@literal null}.
|
||||
* @return new instance of {@link DateAdd}.
|
||||
*/
|
||||
public static DateDiff diffValueOf(String fieldReference, String unit) {
|
||||
return diffValue(Fields.field(fieldReference), unit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the number of {@literal units} to a {@link #toDate(Object) start date}.
|
||||
*
|
||||
* @param value must not be {@literal null}.
|
||||
* @param unit must not be {@literal null}.
|
||||
* @return new instance of {@link DateAdd}.
|
||||
*/
|
||||
public static DateDiff diffValue(Object value, String unit) {
|
||||
|
||||
Map<String, Object> args = new HashMap<>();
|
||||
args.put("unit", unit);
|
||||
args.put("endDate", value);
|
||||
return new DateDiff(args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the start date, in UTC, for the addition operation.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return new instance of {@link DateAdd}.
|
||||
*/
|
||||
public DateDiff toDateOf(AggregationExpression expression) {
|
||||
return toDate(expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the start date, in UTC, for the addition operation.
|
||||
*
|
||||
* @param fieldReference must not be {@literal null}.
|
||||
* @return new instance of {@link DateAdd}.
|
||||
*/
|
||||
public DateDiff toDateOf(String fieldReference) {
|
||||
return toDate(Fields.field(fieldReference));
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the start date, in UTC, for the addition operation.
|
||||
*
|
||||
* @param dateExpression anything that evaluates to a valid date. Must not be {@literal null}.
|
||||
* @return new instance of {@link DateAdd}.
|
||||
*/
|
||||
public DateDiff toDate(Object dateExpression) {
|
||||
return new DateDiff(append("startDate", dateExpression));
|
||||
}
|
||||
|
||||
/**
|
||||
* Optionally set the {@link Timezone} to use. If not specified {@literal UTC} is used.
|
||||
*
|
||||
* @param timezone must not be {@literal null}. Consider {@link Timezone#none()} instead.
|
||||
* @return new instance of {@link DateAdd}.
|
||||
*/
|
||||
public DateDiff withTimezone(Timezone timezone) {
|
||||
return new DateDiff(appendTimezone(argumentMap(), timezone));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the start day of the week if the unit if measure is set to {@literal week}. Uses {@literal Sunday} by
|
||||
* default.
|
||||
*
|
||||
* @param day must not be {@literal null}.
|
||||
* @return new instance of {@link DateDiff}.
|
||||
*/
|
||||
public DateDiff startOfWeek(Object day) {
|
||||
return new DateDiff(append("startOfWeek", day));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getMongoMethod() {
|
||||
return "$dateDiff";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface defining a temporal unit for date operators.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 3.3
|
||||
*/
|
||||
public interface TemporalUnit {
|
||||
|
||||
String name();
|
||||
|
||||
/**
|
||||
* Converts the given time unit into a {@link TemporalUnit}. Supported units are: days, hours, minutes, seconds, and
|
||||
* milliseconds.
|
||||
*
|
||||
* @param timeUnit the time unit to convert, must not be {@literal null}.
|
||||
* @return
|
||||
* @throws IllegalArgumentException if the {@link TimeUnit} is {@literal null} or not supported for conversion.
|
||||
*/
|
||||
static TemporalUnit from(TimeUnit timeUnit) {
|
||||
|
||||
Assert.notNull(timeUnit, "TimeUnit must not be null");
|
||||
|
||||
switch (timeUnit) {
|
||||
case DAYS:
|
||||
return TemporalUnits.DAY;
|
||||
case HOURS:
|
||||
return TemporalUnits.HOUR;
|
||||
case MINUTES:
|
||||
return TemporalUnits.MINUTE;
|
||||
case SECONDS:
|
||||
return TemporalUnits.SECOND;
|
||||
case MILLISECONDS:
|
||||
return TemporalUnits.MILLISECOND;
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException(String.format("Cannot create TemporalUnit from %s", timeUnit));
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the given chrono unit into a {@link TemporalUnit}. Supported units are: years, weeks, months, days,
|
||||
* hours, minutes, seconds, and millis.
|
||||
*
|
||||
* @param chronoUnit the chrono unit to convert, must not be {@literal null}.
|
||||
* @return
|
||||
* @throws IllegalArgumentException if the {@link TimeUnit} is {@literal null} or not supported for conversion.
|
||||
*/
|
||||
static TemporalUnit from(ChronoUnit chronoUnit) {
|
||||
|
||||
switch (chronoUnit) {
|
||||
case YEARS:
|
||||
return TemporalUnits.YEAR;
|
||||
case WEEKS:
|
||||
return TemporalUnits.WEEK;
|
||||
case MONTHS:
|
||||
return TemporalUnits.MONTH;
|
||||
case DAYS:
|
||||
return TemporalUnits.DAY;
|
||||
case HOURS:
|
||||
return TemporalUnits.HOUR;
|
||||
case MINUTES:
|
||||
return TemporalUnits.MINUTE;
|
||||
case SECONDS:
|
||||
return TemporalUnits.SECOND;
|
||||
case MILLIS:
|
||||
return TemporalUnits.MILLISECOND;
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException(String.format("Cannot create TemporalUnit from %s", chronoUnit));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Supported temporal units.
|
||||
*/
|
||||
enum TemporalUnits implements TemporalUnit {
|
||||
YEAR, QUARTER, WEEK, MONTH, DAY, HOUR, MINUTE, SECOND, MILLISECOND
|
||||
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static <T extends TimezonedDateAggregationExpression> T applyTimezone(T instance, Timezone timezone) {
|
||||
return !ObjectUtils.nullSafeEquals(Timezone.none(), timezone) && !instance.hasTimezone()
|
||||
|
||||
@@ -0,0 +1,222 @@
|
||||
/*
|
||||
* Copyright 2021 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
|
||||
*
|
||||
* https://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.bson.Document;
|
||||
|
||||
/**
|
||||
* Gateway to {@literal document expressions} such as {@literal $rank, $documentNumber, etc.}
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 3.3
|
||||
*/
|
||||
public class DocumentOperators {
|
||||
|
||||
/**
|
||||
* Obtain the document position (including gaps) relative to others (rank).
|
||||
*
|
||||
* @return new instance of {@link Rank}.
|
||||
* @since 3.3
|
||||
*/
|
||||
public static Rank rank() {
|
||||
return new Rank();
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain the document position (without gaps) relative to others (rank).
|
||||
*
|
||||
* @return new instance of {@link DenseRank}.
|
||||
* @since 3.3
|
||||
*/
|
||||
public static DenseRank denseRank() {
|
||||
return new DenseRank();
|
||||
}
|
||||
|
||||
/**
|
||||
* Take the field referenced by given {@literal fieldReference}.
|
||||
*
|
||||
* @param fieldReference must not be {@literal null}.
|
||||
* @return new instance of {@link DocumentOperatorsFactory}.
|
||||
*/
|
||||
public static DocumentOperatorsFactory valueOf(String fieldReference) {
|
||||
return new DocumentOperatorsFactory(fieldReference);
|
||||
}
|
||||
|
||||
/**
|
||||
* Take the value resulting from the given {@link AggregationExpression}.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return new instance of {@link DocumentOperatorsFactory}.
|
||||
*/
|
||||
public static DocumentOperatorsFactory valueOf(AggregationExpression expression) {
|
||||
return new DocumentOperatorsFactory(expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain the current document position.
|
||||
*
|
||||
* @return new instance of {@link DocumentNumber}.
|
||||
* @since 3.3
|
||||
*/
|
||||
public static DocumentNumber documentNumber() {
|
||||
return new DocumentNumber();
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public static class DocumentOperatorsFactory {
|
||||
|
||||
private final Object target;
|
||||
|
||||
public DocumentOperatorsFactory(Object target) {
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link AggregationExpression} that applies the expression to a document at specified position
|
||||
* relative to the current document.
|
||||
*
|
||||
* @param by the value to add to the current position.
|
||||
* @return new instance of {@link Shift}.
|
||||
*/
|
||||
public Shift shift(int by) {
|
||||
|
||||
Shift shift = usesExpression() ? Shift.shift((AggregationExpression) target) : Shift.shift(target.toString());
|
||||
return shift.by(by);
|
||||
}
|
||||
|
||||
private boolean usesExpression() {
|
||||
return target instanceof AggregationExpression;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link Rank} resolves the current document position (the rank) relative to other documents. If multiple documents
|
||||
* occupy the same rank, {@literal $rank} places the document with the subsequent value at a rank with a gap.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 3.3
|
||||
*/
|
||||
public static class Rank implements AggregationExpression {
|
||||
|
||||
@Override
|
||||
public Document toDocument(AggregationOperationContext context) {
|
||||
return new Document("$rank", new Document());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link DenseRank} resolves the current document position (the rank) relative to other documents. If multiple
|
||||
* documents occupy the same rank, {@literal $denseRank} places the document with the subsequent value at the next
|
||||
* rank without any gaps.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 3.3
|
||||
*/
|
||||
public static class DenseRank implements AggregationExpression {
|
||||
|
||||
@Override
|
||||
public Document toDocument(AggregationOperationContext context) {
|
||||
return new Document("$denseRank", new Document());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link DocumentNumber} resolves the current document position.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 3.3
|
||||
*/
|
||||
public static class DocumentNumber implements AggregationExpression {
|
||||
|
||||
@Override
|
||||
public Document toDocument(AggregationOperationContext context) {
|
||||
return new Document("$documentNumber", new Document());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shift applies an expression to a document in a specified position relative to the current document.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 3.3
|
||||
*/
|
||||
public static class Shift extends AbstractAggregationExpression {
|
||||
|
||||
private Shift(Object value) {
|
||||
super(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies the field to evaluate and return.
|
||||
*
|
||||
* @param fieldReference must not be {@literal null}.
|
||||
* @return new instance of {@link Shift}.
|
||||
*/
|
||||
public static Shift shift(String fieldReference) {
|
||||
return new Shift(Collections.singletonMap("output", Fields.field(fieldReference)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies the {@link AggregationExpression expression} to evaluate and return.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return new instance of {@link Shift}.
|
||||
*/
|
||||
public static Shift shift(AggregationExpression expression) {
|
||||
return new Shift(Collections.singletonMap("output", expression));
|
||||
}
|
||||
|
||||
/**
|
||||
* Shift the document position relative to the current. Use a positive value for follow up documents (eg. 1 for the
|
||||
* next) or a negative value for the predecessor documents (eg. -1 for the previous).
|
||||
*
|
||||
* @param shiftBy value to add to the current position.
|
||||
* @return new instance of {@link Shift}.
|
||||
*/
|
||||
public Shift by(int shiftBy) {
|
||||
return new Shift(append("by", shiftBy));
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the default value if the target document is out of range.
|
||||
*
|
||||
* @param value must not be {@literal null}.
|
||||
* @return new instance of {@link Shift}.
|
||||
*/
|
||||
public Shift defaultTo(Object value) {
|
||||
return new Shift(append("default", value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the {@link AggregationExpression expression} to evaluate if the target document is out of range.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return new instance of {@link Shift}.
|
||||
*/
|
||||
public Shift defaultToValueOf(AggregationExpression expression) {
|
||||
return defaultTo(expression);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getMongoMethod() {
|
||||
return "$shift";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* Copyright 2021 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
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.mongodb.core.aggregation;
|
||||
|
||||
import org.bson.Document;
|
||||
|
||||
import org.springframework.data.mongodb.core.query.CriteriaDefinition;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Gateway to {@literal evaluation operators} such as {@literal $expr}.
|
||||
*
|
||||
* @author Divya Srivastava
|
||||
* @since 3.3
|
||||
*/
|
||||
public class EvaluationOperators {
|
||||
|
||||
/**
|
||||
* Take the value resulting from the given fieldReference.
|
||||
*
|
||||
* @param fieldReference must not be {@literal null}.
|
||||
* @return new instance of {@link EvaluationOperatorFactory}.
|
||||
*/
|
||||
public static EvaluationOperatorFactory valueOf(String fieldReference) {
|
||||
return new EvaluationOperatorFactory(fieldReference);
|
||||
}
|
||||
|
||||
/**
|
||||
* Take the value resulting from the given {@link AggregationExpression}.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return new instance of {@link EvaluationOperatorFactory}.
|
||||
*/
|
||||
public static EvaluationOperatorFactory valueOf(AggregationExpression expression) {
|
||||
return new EvaluationOperatorFactory(expression);
|
||||
}
|
||||
|
||||
public static class EvaluationOperatorFactory {
|
||||
|
||||
private final String fieldReference;
|
||||
private final AggregationExpression expression;
|
||||
|
||||
/**
|
||||
* Creates new {@link EvaluationOperatorFactory} for given {@literal fieldReference}.
|
||||
*
|
||||
* @param fieldReference must not be {@literal null}.
|
||||
*/
|
||||
public EvaluationOperatorFactory(String fieldReference) {
|
||||
|
||||
Assert.notNull(fieldReference, "FieldReference must not be null!");
|
||||
this.fieldReference = fieldReference;
|
||||
this.expression = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link EvaluationOperatorFactory} for given {@link AggregationExpression}.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
*/
|
||||
public EvaluationOperatorFactory(AggregationExpression expression) {
|
||||
|
||||
Assert.notNull(expression, "Expression must not be null!");
|
||||
this.fieldReference = null;
|
||||
this.expression = expression;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link AggregationExpression} that is a valid aggregation expression.
|
||||
*
|
||||
* @return new instance of {@link Expr}.
|
||||
*/
|
||||
public Expr expr() {
|
||||
return usesFieldRef() ? Expr.valueOf(fieldReference) : Expr.valueOf(expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows the use of aggregation expressions within the query language.
|
||||
*/
|
||||
public static class Expr extends AbstractAggregationExpression {
|
||||
|
||||
private Expr(Object value) {
|
||||
super(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getMongoMethod() {
|
||||
return "$expr";
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link Expr}.
|
||||
*
|
||||
* @param fieldReference must not be {@literal null}.
|
||||
* @return new instance of {@link Expr}.
|
||||
*/
|
||||
public static Expr valueOf(String fieldReference) {
|
||||
|
||||
Assert.notNull(fieldReference, "FieldReference must not be null!");
|
||||
return new Expr(Fields.field(fieldReference));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link Expr}.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return new instance of {@link Expr}.
|
||||
*/
|
||||
public static Expr valueOf(AggregationExpression expression) {
|
||||
|
||||
Assert.notNull(expression, "Expression must not be null!");
|
||||
return new Expr(expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates {@code $expr} as {@link CriteriaDefinition}.
|
||||
*
|
||||
* @return the {@link CriteriaDefinition} from this expression.
|
||||
*/
|
||||
public CriteriaDefinition toCriteriaDefinition(AggregationOperationContext context) {
|
||||
|
||||
Document criteriaObject = toDocument(context);
|
||||
|
||||
return new CriteriaDefinition() {
|
||||
@Override
|
||||
public Document getCriteriaObject() {
|
||||
return criteriaObject;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getKey() {
|
||||
return getMongoMethod();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private boolean usesFieldRef() {
|
||||
return fieldReference != null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -36,8 +36,7 @@ import org.springframework.util.ClassUtils;
|
||||
* We recommend to use the static factory method {@link Aggregation#graphLookup(String)} instead of creating instances
|
||||
* of this class directly.
|
||||
*
|
||||
* @see <a href=
|
||||
* "https://docs.mongodb.org/manual/reference/aggregation/graphLookup/">https://docs.mongodb.org/manual/reference/aggregation/graphLookup/</a>
|
||||
* @see <a href="https://docs.mongodb.org/manual/reference/aggregation/graphLookup/">https://docs.mongodb.org/manual/reference/aggregation/graphLookup/</a>
|
||||
* @author Mark Paluch
|
||||
* @author Christoph Strobl
|
||||
* @since 1.10
|
||||
|
||||
@@ -139,7 +139,7 @@ public class GroupOperation implements FieldsExposingAggregationOperation {
|
||||
* Generates an {@link GroupOperationBuilder} for a {@code $sum}-expression.
|
||||
* <p>
|
||||
* Count expressions are emulated via {@code $sum: 1}.
|
||||
* <p>
|
||||
* </p>
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
package org.springframework.data.mongodb.core.aggregation;
|
||||
|
||||
import org.bson.Document;
|
||||
|
||||
import org.springframework.data.mongodb.core.query.CriteriaDefinition;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@@ -29,6 +30,7 @@ import org.springframework.util.Assert;
|
||||
* @author Sebastian Herold
|
||||
* @author Thomas Darimont
|
||||
* @author Oliver Gierke
|
||||
* @author Divya Srivastava
|
||||
* @since 1.3
|
||||
* @see <a href="https://docs.mongodb.com/manual/reference/operator/aggregation/match/">MongoDB Aggregation Framework:
|
||||
* $match</a>
|
||||
@@ -36,6 +38,7 @@ import org.springframework.util.Assert;
|
||||
public class MatchOperation implements AggregationOperation {
|
||||
|
||||
private final CriteriaDefinition criteriaDefinition;
|
||||
private final AggregationExpression expression;
|
||||
|
||||
/**
|
||||
* Creates a new {@link MatchOperation} for the given {@link CriteriaDefinition}.
|
||||
@@ -45,7 +48,23 @@ public class MatchOperation implements AggregationOperation {
|
||||
public MatchOperation(CriteriaDefinition criteriaDefinition) {
|
||||
|
||||
Assert.notNull(criteriaDefinition, "Criteria must not be null!");
|
||||
|
||||
this.criteriaDefinition = criteriaDefinition;
|
||||
this.expression = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link MatchOperation} for the given {@link AggregationExpression}.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @since 3.3
|
||||
*/
|
||||
public MatchOperation(AggregationExpression expression) {
|
||||
|
||||
Assert.notNull(expression, "Expression must not be null!");
|
||||
|
||||
this.criteriaDefinition = null;
|
||||
this.expression = expression;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -54,7 +73,9 @@ public class MatchOperation implements AggregationOperation {
|
||||
*/
|
||||
@Override
|
||||
public Document toDocument(AggregationOperationContext context) {
|
||||
return new Document(getOperator(), context.getMappedObject(criteriaDefinition.getCriteriaObject()));
|
||||
|
||||
return new Document(getOperator(),
|
||||
context.getMappedObject(expression != null ? expression.toDocument() : criteriaDefinition.getCriteriaObject()));
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -30,7 +30,7 @@ import org.springframework.lang.Nullable;
|
||||
/**
|
||||
* {@link AggregationOperationContext} implementation prefixing non-command keys on root level with the given prefix.
|
||||
* Useful when mapping fields to domain specific types while having to prefix keys for query purpose.
|
||||
* <p />
|
||||
* <br />
|
||||
* Fields to be excluded from prefixing my be added to a {@literal denylist}.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
|
||||
@@ -33,8 +33,7 @@ import org.springframework.util.Assert;
|
||||
* </pre>
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @see <a href=
|
||||
* "https://docs.mongodb.com/manual/reference/operator/aggregation/redact/">https://docs.mongodb.com/manual/reference/operator/aggregation/redact/</a>
|
||||
* @see <a href="https://docs.mongodb.com/manual/reference/operator/aggregation/redact/">https://docs.mongodb.com/manual/reference/operator/aggregation/redact/</a>
|
||||
* @since 3.0
|
||||
*/
|
||||
public class RedactOperation implements AggregationOperation {
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core.aggregation;
|
||||
|
||||
import org.springframework.data.mapping.MappingException;
|
||||
import org.springframework.data.mapping.context.InvalidPersistentPropertyPath;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.mongodb.core.aggregation.ExposedFields.DirectFieldReference;
|
||||
@@ -55,7 +56,7 @@ public class RelaxedTypeBasedAggregationOperationContext extends TypeBasedAggreg
|
||||
|
||||
try {
|
||||
return super.getReferenceFor(field);
|
||||
} catch (InvalidPersistentPropertyPath e) {
|
||||
} catch (MappingException e) {
|
||||
return new DirectFieldReference(new ExposedField(field, true));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.bson.Document;
|
||||
|
||||
import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExposedField;
|
||||
import org.springframework.expression.spel.ast.Projection;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@@ -30,7 +30,7 @@ import org.springframework.util.CollectionUtils;
|
||||
|
||||
/**
|
||||
* Gateway to {@literal $function} and {@literal $accumulator} aggregation operations.
|
||||
* <p />
|
||||
* <br />
|
||||
* Using {@link ScriptOperators} as part of the {@link Aggregation} requires MongoDB server to have
|
||||
* <a href="https://docs.mongodb.com/master/core/server-side-javascript/">server-side JavaScript</a> execution
|
||||
* <a href="https://docs.mongodb.com/master/reference/configuration-options/#security.javascriptEnabled">enabled</a>.
|
||||
@@ -43,7 +43,7 @@ public class ScriptOperators {
|
||||
|
||||
/**
|
||||
* Create a custom aggregation
|
||||
* <a href="https://docs.mongodb.com/master/reference/operator/aggregation/function/">$function<a /> in JavaScript.
|
||||
* <a href="https://docs.mongodb.com/master/reference/operator/aggregation/function/">$function</a> in JavaScript.
|
||||
*
|
||||
* @param body The function definition. Must not be {@literal null}.
|
||||
* @return new instance of {@link Function}.
|
||||
@@ -53,8 +53,8 @@ public class ScriptOperators {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a custom <a href="https://docs.mongodb.com/master/reference/operator/aggregation/accumulator/">$accumulator
|
||||
* operator</a> in Javascript.
|
||||
* Create a custom <a href="https://docs.mongodb.com/master/reference/operator/aggregation/accumulator/">$accumulator operator</a>
|
||||
* in Javascript.
|
||||
*
|
||||
* @return new instance of {@link AccumulatorInitBuilder}.
|
||||
*/
|
||||
@@ -65,7 +65,7 @@ public class ScriptOperators {
|
||||
/**
|
||||
* {@link Function} defines a custom aggregation
|
||||
* <a href="https://docs.mongodb.com/master/reference/operator/aggregation/function/">$function</a> in JavaScript.
|
||||
* <p />
|
||||
* <br />
|
||||
* <code class="java">
|
||||
* {
|
||||
* $function: {
|
||||
@@ -75,7 +75,7 @@ public class ScriptOperators {
|
||||
* }
|
||||
* }
|
||||
* </code>
|
||||
* <p />
|
||||
* <br />
|
||||
* {@link Function} cannot be used as part of {@link org.springframework.data.mongodb.core.schema.MongoJsonSchema
|
||||
* schema} validation query expression. <br />
|
||||
* <b>NOTE:</b> <a href="https://docs.mongodb.com/master/core/server-side-javascript/">Server-Side JavaScript</a>
|
||||
@@ -179,7 +179,7 @@ public class ScriptOperators {
|
||||
* <a href="https://docs.mongodb.com/master/reference/operator/aggregation/accumulator/">$accumulator operator</a>,
|
||||
* one that maintains its state (e.g. totals, maximums, minimums, and related data) as documents progress through the
|
||||
* pipeline, in JavaScript.
|
||||
* <p />
|
||||
* <br />
|
||||
* <code class="java">
|
||||
* {
|
||||
* $accumulator: {
|
||||
@@ -193,7 +193,7 @@ public class ScriptOperators {
|
||||
* }
|
||||
* }
|
||||
* </code>
|
||||
* <p />
|
||||
* <br />
|
||||
* {@link Accumulator} can be used as part of {@link GroupOperation $group}, {@link BucketOperation $bucket} and
|
||||
* {@link BucketAutoOperation $bucketAuto} pipeline stages. <br />
|
||||
* <b>NOTE:</b> <a href="https://docs.mongodb.com/master/core/server-side-javascript/">Server-Side JavaScript</a>
|
||||
@@ -241,7 +241,7 @@ public class ScriptOperators {
|
||||
/**
|
||||
* Define the {@code init} {@link Function} for the {@link Accumulator accumulators} initial state. The function
|
||||
* receives its arguments from the {@link Function#args(Object...) initArgs} array expression.
|
||||
* <p />
|
||||
* <br />
|
||||
* <code class="java">
|
||||
* function(initArg1, initArg2, ...) {
|
||||
* ...
|
||||
@@ -259,7 +259,7 @@ public class ScriptOperators {
|
||||
/**
|
||||
* Define the {@code init} function for the {@link Accumulator accumulators} initial state. The function receives
|
||||
* its arguments from the {@link AccumulatorInitArgsBuilder#initArgs(Object...)} array expression.
|
||||
* <p />
|
||||
* <br />
|
||||
* <code class="java">
|
||||
* function(initArg1, initArg2, ...) {
|
||||
* ...
|
||||
@@ -308,7 +308,7 @@ public class ScriptOperators {
|
||||
* Set the {@code accumulate} {@link Function} that updates the state for each document. The functions first
|
||||
* argument is the current {@code state}, additional arguments can be defined via {@link Function#args(Object...)
|
||||
* accumulateArgs}.
|
||||
* <p />
|
||||
* <br />
|
||||
* <code class="java">
|
||||
* function(state, accumArg1, accumArg2, ...) {
|
||||
* ...
|
||||
@@ -327,7 +327,7 @@ public class ScriptOperators {
|
||||
* Set the {@code accumulate} function that updates the state for each document. The functions first argument is
|
||||
* the current {@code state}, additional arguments can be defined via
|
||||
* {@link AccumulatorAccumulateArgsBuilder#accumulateArgs(Object...)}.
|
||||
* <p />
|
||||
* <br />
|
||||
* <code class="java">
|
||||
* function(state, accumArg1, accumArg2, ...) {
|
||||
* ...
|
||||
@@ -370,7 +370,7 @@ public class ScriptOperators {
|
||||
* Set the {@code merge} function used to merge two internal states. <br />
|
||||
* This might be required because the operation is run on a sharded cluster or when the operator exceeds its
|
||||
* memory limit.
|
||||
* <p />
|
||||
* <br />
|
||||
* <code class="java">
|
||||
* function(state1, state2) {
|
||||
* ...
|
||||
@@ -389,7 +389,7 @@ public class ScriptOperators {
|
||||
/**
|
||||
* Set the {@code finalize} function used to update the result of the accumulation when all documents have been
|
||||
* processed.
|
||||
* <p />
|
||||
* <br />
|
||||
* <code class="java">
|
||||
* function(state) {
|
||||
* ...
|
||||
@@ -425,7 +425,7 @@ public class ScriptOperators {
|
||||
/**
|
||||
* Define the {@code init} function for the {@link Accumulator accumulators} initial state. The function receives
|
||||
* its arguments from the {@link #initArgs(Object...)} array expression.
|
||||
* <p />
|
||||
* <br />
|
||||
* <code class="java">
|
||||
* function(initArg1, initArg2, ...) {
|
||||
* ...
|
||||
@@ -461,7 +461,7 @@ public class ScriptOperators {
|
||||
/**
|
||||
* Set the {@code accumulate} function that updates the state for each document. The functions first argument is
|
||||
* the current {@code state}, additional arguments can be defined via {@link #accumulateArgs(Object...)}.
|
||||
* <p />
|
||||
* <br />
|
||||
* <code class="java">
|
||||
* function(state, accumArg1, accumArg2, ...) {
|
||||
* ...
|
||||
@@ -500,7 +500,7 @@ public class ScriptOperators {
|
||||
* Set the {@code merge} function used to merge two internal states. <br />
|
||||
* This might be required because the operation is run on a sharded cluster or when the operator exceeds its
|
||||
* memory limit.
|
||||
* <p />
|
||||
* <br />
|
||||
* <code class="java">
|
||||
* function(state1, state2) {
|
||||
* ...
|
||||
@@ -537,7 +537,7 @@ public class ScriptOperators {
|
||||
/**
|
||||
* Set the {@code finalize} function used to update the result of the accumulation when all documents have been
|
||||
* processed.
|
||||
* <p />
|
||||
* <br />
|
||||
* <code class="java">
|
||||
* function(state) {
|
||||
* ...
|
||||
|
||||
@@ -193,5 +193,6 @@ public class SetOperation extends DocumentEnhancingOperation {
|
||||
*/
|
||||
SetOperation withValueOfExpression(String operation, Object... values);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,873 @@
|
||||
/*
|
||||
* Copyright 2021 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
|
||||
*
|
||||
* https://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.time.temporal.ChronoUnit;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.bson.Document;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Encapsulates the {@code setWindowFields}-operation.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 3.3
|
||||
* @see <a href="https://docs.mongodb.com/manual/reference/operator/aggregation/setWindowFields/">https://docs.mongodb.com/manual/reference/operator/aggregation/setWindowFields/</a>
|
||||
*/
|
||||
public class SetWindowFieldsOperation
|
||||
implements AggregationOperation, FieldsExposingAggregationOperation.InheritsFieldsAggregationOperation {
|
||||
|
||||
private static final String CURRENT = "current";
|
||||
private static final String UNBOUNDED = "unbounded";
|
||||
|
||||
private final @Nullable Object partitionBy;
|
||||
private final @Nullable AggregationOperation sortBy;
|
||||
private final WindowOutput output;
|
||||
|
||||
/**
|
||||
* Create a new {@link SetWindowFieldsOperation} with given args.
|
||||
*
|
||||
* @param partitionBy The field or {@link AggregationExpression} to group by.
|
||||
* @param sortBy the {@link SortOperation operation} to sort the documents by in the partition.
|
||||
* @param output the {@link WindowOutput} containing the fields to add and the rules to calculate their respective
|
||||
* values.
|
||||
*/
|
||||
protected SetWindowFieldsOperation(@Nullable Object partitionBy, @Nullable AggregationOperation sortBy,
|
||||
WindowOutput output) {
|
||||
|
||||
this.partitionBy = partitionBy;
|
||||
this.sortBy = sortBy;
|
||||
this.output = output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a {@link SetWindowFieldsOperationBuilder builder} to create a {@link SetWindowFieldsOperation}.
|
||||
*
|
||||
* @return new instance of {@link SetWindowFieldsOperationBuilder}.
|
||||
*/
|
||||
public static SetWindowFieldsOperationBuilder builder() {
|
||||
return new SetWindowFieldsOperationBuilder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExposedFields getFields() {
|
||||
return ExposedFields.nonSynthetic(Fields.from(output.fields.toArray(new Field[0])));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Document toDocument(AggregationOperationContext context) {
|
||||
|
||||
Document $setWindowFields = new Document();
|
||||
if (partitionBy != null) {
|
||||
if (partitionBy instanceof AggregationExpression) {
|
||||
$setWindowFields.append("partitionBy", ((AggregationExpression) partitionBy).toDocument(context));
|
||||
} else if (partitionBy instanceof Field) {
|
||||
$setWindowFields.append("partitionBy", context.getReference((Field) partitionBy).toString());
|
||||
} else {
|
||||
$setWindowFields.append("partitionBy", partitionBy);
|
||||
}
|
||||
}
|
||||
|
||||
if (sortBy != null) {
|
||||
$setWindowFields.append("sortBy", sortBy.toDocument(context).get(sortBy.getOperator()));
|
||||
}
|
||||
|
||||
Document output = new Document();
|
||||
for (ComputedField field : this.output.fields) {
|
||||
|
||||
Document fieldOperation = field.getWindowOperator().toDocument(context);
|
||||
if (field.window != null) {
|
||||
fieldOperation.put("window", field.window.toDocument(context));
|
||||
}
|
||||
output.append(field.getName(), fieldOperation);
|
||||
}
|
||||
$setWindowFields.append("output", output);
|
||||
|
||||
return new Document(getOperator(), $setWindowFields);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.aggregation.AggregationOperation#getOperator()
|
||||
*/
|
||||
@Override
|
||||
public String getOperator() {
|
||||
return "$setWindowFields";
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link WindowOutput} defines output of {@literal $setWindowFields} stage by defining the {@link ComputedField
|
||||
* field(s)} to append to the documents in the output.
|
||||
*/
|
||||
public static class WindowOutput {
|
||||
|
||||
private final List<ComputedField> fields;
|
||||
|
||||
/**
|
||||
* Create a new output containing the single given {@link ComputedField field}.
|
||||
*
|
||||
* @param outputField must not be {@literal null}.
|
||||
*/
|
||||
public WindowOutput(ComputedField outputField) {
|
||||
|
||||
Assert.notNull(outputField, "OutputField must not be null!");
|
||||
|
||||
this.fields = new ArrayList<>();
|
||||
this.fields.add(outputField);
|
||||
}
|
||||
|
||||
/**
|
||||
* Append the given {@link ComputedField field} to the outptut.
|
||||
*
|
||||
* @param field must not be {@literal null}.
|
||||
* @return this.
|
||||
*/
|
||||
public WindowOutput append(ComputedField field) {
|
||||
|
||||
Assert.notNull(field, "Field must not be null!");
|
||||
|
||||
fields.add(field);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Append the given {@link AggregationExpression} as a {@link ComputedField field} in a fluent way.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return new instance of {@link ComputedFieldAppender}.
|
||||
* @see #append(ComputedField)
|
||||
*/
|
||||
public ComputedFieldAppender append(AggregationExpression expression) {
|
||||
|
||||
return new ComputedFieldAppender() {
|
||||
|
||||
@Nullable private Window window;
|
||||
|
||||
@Override
|
||||
public WindowOutput as(String fieldname) {
|
||||
|
||||
return WindowOutput.this.append(new ComputedField(fieldname, expression, window));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ComputedFieldAppender within(Window window) {
|
||||
this.window = window;
|
||||
return this;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Tiny little helper to allow fluent API usage for {@link #append(ComputedField)}.
|
||||
*/
|
||||
interface ComputedFieldAppender {
|
||||
|
||||
/**
|
||||
* Specify the target field name.
|
||||
*
|
||||
* @param fieldname the name of field to add to the target document.
|
||||
* @return the {@link WindowOutput} that started the append operation.
|
||||
*/
|
||||
WindowOutput as(String fieldname);
|
||||
|
||||
/**
|
||||
* Specify the window boundaries.
|
||||
*
|
||||
* @param window must not be {@literal null}.
|
||||
* @return this.
|
||||
*/
|
||||
ComputedFieldAppender within(Window window);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link Field} that the result of a computation done via an {@link AggregationExpression}.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public static class ComputedField implements Field {
|
||||
|
||||
private final String name;
|
||||
private final AggregationExpression windowOperator;
|
||||
private final @Nullable Window window;
|
||||
|
||||
/**
|
||||
* Create a new {@link ComputedField}.
|
||||
*
|
||||
* @param name the target field name.
|
||||
* @param windowOperator the expression to calculate the field value.
|
||||
*/
|
||||
public ComputedField(String name, AggregationExpression windowOperator) {
|
||||
this(name, windowOperator, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link ComputedField}.
|
||||
*
|
||||
* @param name the target field name.
|
||||
* @param windowOperator the expression to calculate the field value.
|
||||
* @param window the boundaries to operate within. Can be {@literal null}.
|
||||
*/
|
||||
public ComputedField(String name, AggregationExpression windowOperator, @Nullable Window window) {
|
||||
|
||||
this.name = name;
|
||||
this.windowOperator = windowOperator;
|
||||
this.window = window;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTarget() {
|
||||
return getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAliased() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public AggregationExpression getWindowOperator() {
|
||||
return windowOperator;
|
||||
}
|
||||
|
||||
public Window getWindow() {
|
||||
return window;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Quick access to {@link DocumentWindow documents} and {@literal RangeWindow range} {@link Window windows}.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public interface Windows {
|
||||
|
||||
/**
|
||||
* Create a document window relative to the position of the current document.
|
||||
*
|
||||
* @param lower an integer for a position relative to the current document, {@literal current} or
|
||||
* {@literal unbounded}.
|
||||
* @param upper an integer for a position relative to the current document, {@literal current} or
|
||||
* {@literal unbounded}.
|
||||
* @return new instance of {@link DocumentWindow}.
|
||||
*/
|
||||
static DocumentWindow documents(Object lower, Object upper) {
|
||||
return new DocumentWindow(lower, upper);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a range window defined based on sort expression.
|
||||
*
|
||||
* @param lower a numeric value to add the sort by field value of the current document, {@literal current} or
|
||||
* {@literal unbounded}.
|
||||
* @param upper a numeric value to add the sort by field value of the current document, {@literal current} or
|
||||
* {@literal unbounded}.
|
||||
* @return new instance of {@link RangeWindow}.
|
||||
*/
|
||||
static RangeWindow range(Object lower, Object upper, @Nullable WindowUnit unit) {
|
||||
return new RangeWindow(lower, upper, unit == null ? WindowUnits.DEFAULT : unit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a range window based on the {@link Sort sort value} of the current document via a fluent API.
|
||||
*
|
||||
* @return new instance of {@link RangeWindowBuilder}.
|
||||
*/
|
||||
static RangeWindowBuilder range() {
|
||||
return new RangeWindowBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a document window relative to the position of the current document via a fluent API.
|
||||
*
|
||||
* @return new instance of {@link DocumentWindowBuilder}.
|
||||
*/
|
||||
static DocumentWindowBuilder documents() {
|
||||
return new DocumentWindowBuilder();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link Window} to be used for {@link ComputedField#getWindow() ComputedField}.
|
||||
*/
|
||||
public interface Window {
|
||||
|
||||
/**
|
||||
* The lower (inclusive) boundary.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
Object getLower();
|
||||
|
||||
/**
|
||||
* The upper (inclusive) boundary.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
Object getUpper();
|
||||
|
||||
/**
|
||||
* Obtain the document representation of the window in a default {@link AggregationOperationContext context}.
|
||||
*
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
default Document toDocument() {
|
||||
return toDocument(Aggregation.DEFAULT_CONTEXT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain the document representation of the window in the given {@link AggregationOperationContext context}.
|
||||
*
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
Document toDocument(AggregationOperationContext ctx);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder API for a {@link RangeWindow}.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public static class RangeWindowBuilder {
|
||||
|
||||
private @Nullable Object lower;
|
||||
private @Nullable Object upper;
|
||||
private @Nullable WindowUnit unit;
|
||||
|
||||
/**
|
||||
* The lower (inclusive) range limit based on the sortBy field.
|
||||
*
|
||||
* @param lower eg. {@literal current} or {@literal unbounded}.
|
||||
* @return this.
|
||||
*/
|
||||
public RangeWindowBuilder from(String lower) {
|
||||
|
||||
this.lower = lower;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The upper (inclusive) range limit based on the sortBy field.
|
||||
*
|
||||
* @param upper eg. {@literal current} or {@literal unbounded}.
|
||||
* @return this.
|
||||
*/
|
||||
public RangeWindowBuilder to(String upper) {
|
||||
|
||||
this.upper = upper;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The lower (inclusive) range limit value to add to the value based on the sortBy field. Use a negative integer for
|
||||
* a position before the current document. Use a positive integer for a position after the current document.
|
||||
* {@code 0} is the current document position.
|
||||
*
|
||||
* @param lower
|
||||
* @return this.
|
||||
*/
|
||||
public RangeWindowBuilder from(Number lower) {
|
||||
|
||||
this.lower = lower;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The upper (inclusive) range limit value to add to the value based on the sortBy field. Use a negative integer for
|
||||
* a position before the current document. Use a positive integer for a position after the current document.
|
||||
* {@code 0} is the current document position.
|
||||
*
|
||||
* @param upper
|
||||
* @return this.
|
||||
*/
|
||||
public RangeWindowBuilder to(Number upper) {
|
||||
|
||||
this.upper = upper;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use {@literal current} as {@link #from(String) lower} limit.
|
||||
*
|
||||
* @return this.
|
||||
*/
|
||||
public RangeWindowBuilder fromCurrent() {
|
||||
return from(CURRENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use {@literal unbounded} as {@link #from(String) lower} limit.
|
||||
*
|
||||
* @return this.
|
||||
*/
|
||||
public RangeWindowBuilder fromUnbounded() {
|
||||
return from(UNBOUNDED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use {@literal current} as {@link #to(String) upper} limit.
|
||||
*
|
||||
* @return this.
|
||||
*/
|
||||
public RangeWindowBuilder toCurrent() {
|
||||
return to(CURRENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use {@literal unbounded} as {@link #to(String) upper} limit.
|
||||
*
|
||||
* @return this.
|
||||
*/
|
||||
public RangeWindowBuilder toUnbounded() {
|
||||
return to(UNBOUNDED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link WindowUnit unit} or measure for the given {@link Window}.
|
||||
*
|
||||
* @param windowUnit must not be {@literal null}. Can be on of {@link Windows}.
|
||||
* @return this.
|
||||
*/
|
||||
public RangeWindowBuilder unit(WindowUnit windowUnit) {
|
||||
|
||||
Assert.notNull(windowUnit, "WindowUnit must not be null");
|
||||
this.unit = windowUnit;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the {@link RangeWindow}.
|
||||
*
|
||||
* @return new instance of {@link RangeWindow}.
|
||||
*/
|
||||
public RangeWindow build() {
|
||||
|
||||
Assert.notNull(lower, "Lower bound must not be null");
|
||||
Assert.notNull(upper, "Upper bound must not be null");
|
||||
Assert.notNull(unit, "WindowUnit bound must not be null");
|
||||
|
||||
return new RangeWindow(lower, upper, unit);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder API for a {@link RangeWindow}.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public static class DocumentWindowBuilder {
|
||||
|
||||
private @Nullable Object lower;
|
||||
private @Nullable Object upper;
|
||||
|
||||
/**
|
||||
* The lower (inclusive) range limit based on current document. Use a negative integer for a position before the
|
||||
* current document. Use a positive integer for a position after the current document. {@code 0} is the current
|
||||
* document position.
|
||||
*
|
||||
* @param lower
|
||||
* @return this.
|
||||
*/
|
||||
public DocumentWindowBuilder from(Number lower) {
|
||||
|
||||
this.lower = lower;
|
||||
return this;
|
||||
}
|
||||
|
||||
public DocumentWindowBuilder fromCurrent() {
|
||||
return from(CURRENT);
|
||||
}
|
||||
|
||||
public DocumentWindowBuilder fromUnbounded() {
|
||||
return from(UNBOUNDED);
|
||||
}
|
||||
|
||||
public DocumentWindowBuilder to(String upper) {
|
||||
|
||||
this.upper = upper;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The lower (inclusive) range limit based on current document.
|
||||
*
|
||||
* @param lower eg. {@literal current} or {@literal unbounded}.
|
||||
* @return this.
|
||||
*/
|
||||
public DocumentWindowBuilder from(String lower) {
|
||||
|
||||
this.lower = lower;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The upper (inclusive) range limit based on current document. Use a negative integer for a position before the
|
||||
* current document. Use a positive integer for a position after the current document. {@code 0} is the current
|
||||
* document position.
|
||||
*
|
||||
* @param upper
|
||||
* @return this.
|
||||
*/
|
||||
public DocumentWindowBuilder to(Number upper) {
|
||||
|
||||
this.upper = upper;
|
||||
return this;
|
||||
}
|
||||
|
||||
public DocumentWindowBuilder toCurrent() {
|
||||
return to(CURRENT);
|
||||
}
|
||||
|
||||
public DocumentWindowBuilder toUnbounded() {
|
||||
return to(UNBOUNDED);
|
||||
}
|
||||
|
||||
public DocumentWindow build() {
|
||||
|
||||
Assert.notNull(lower, "Lower bound must not be null");
|
||||
Assert.notNull(upper, "Upper bound must not be null");
|
||||
|
||||
return new DocumentWindow(lower, upper);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Common base class for {@link Window} implementation.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
static abstract class WindowImpl implements Window {
|
||||
|
||||
private final Object lower;
|
||||
private final Object upper;
|
||||
|
||||
protected WindowImpl(Object lower, Object upper) {
|
||||
this.lower = lower;
|
||||
this.upper = upper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getLower() {
|
||||
return lower;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getUpper() {
|
||||
return upper;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link Window} implementation based on the current document.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public static class DocumentWindow extends WindowImpl {
|
||||
|
||||
DocumentWindow(Object lower, Object upper) {
|
||||
super(lower, upper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Document toDocument(AggregationOperationContext ctx) {
|
||||
return new Document("documents", Arrays.asList(getLower(), getUpper()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link Window} implementation based on the sort fields.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public static class RangeWindow extends WindowImpl {
|
||||
|
||||
private final WindowUnit unit;
|
||||
|
||||
protected RangeWindow(Object lower, Object upper, WindowUnit unit) {
|
||||
|
||||
super(lower, upper);
|
||||
this.unit = unit;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Document toDocument(AggregationOperationContext ctx) {
|
||||
|
||||
Document range = new Document("range", new Object[] { getLower(), getUpper() });
|
||||
if (unit != null && !WindowUnits.DEFAULT.equals(unit)) {
|
||||
range.append("unit", unit.name().toLowerCase());
|
||||
}
|
||||
return range;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The actual time unit to apply to a {@link Window}.
|
||||
*/
|
||||
public interface WindowUnit {
|
||||
|
||||
String name();
|
||||
|
||||
/**
|
||||
* Converts the given time unit into a {@link WindowUnit}. Supported units are: days, hours, minutes, seconds, and
|
||||
* milliseconds.
|
||||
*
|
||||
* @param timeUnit the time unit to convert, must not be {@literal null}.
|
||||
* @return
|
||||
* @throws IllegalArgumentException if the {@link TimeUnit} is {@literal null} or not supported for conversion.
|
||||
*/
|
||||
static WindowUnit from(TimeUnit timeUnit) {
|
||||
|
||||
Assert.notNull(timeUnit, "TimeUnit must not be null");
|
||||
|
||||
switch (timeUnit) {
|
||||
case DAYS:
|
||||
return WindowUnits.DAY;
|
||||
case HOURS:
|
||||
return WindowUnits.HOUR;
|
||||
case MINUTES:
|
||||
return WindowUnits.MINUTE;
|
||||
case SECONDS:
|
||||
return WindowUnits.SECOND;
|
||||
case MILLISECONDS:
|
||||
return WindowUnits.MILLISECOND;
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException(String.format("Cannot create WindowUnit from %s", timeUnit));
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the given chrono unit into a {@link WindowUnit}. Supported units are: years, weeks, months, days, hours,
|
||||
* minutes, seconds, and millis.
|
||||
*
|
||||
* @param chronoUnit the chrono unit to convert, must not be {@literal null}.
|
||||
* @return
|
||||
* @throws IllegalArgumentException if the {@link TimeUnit} is {@literal null} or not supported for conversion.
|
||||
*/
|
||||
static WindowUnit from(ChronoUnit chronoUnit) {
|
||||
|
||||
switch (chronoUnit) {
|
||||
case YEARS:
|
||||
return WindowUnits.YEAR;
|
||||
case WEEKS:
|
||||
return WindowUnits.WEEK;
|
||||
case MONTHS:
|
||||
return WindowUnits.MONTH;
|
||||
case DAYS:
|
||||
return WindowUnits.DAY;
|
||||
case HOURS:
|
||||
return WindowUnits.HOUR;
|
||||
case MINUTES:
|
||||
return WindowUnits.MINUTE;
|
||||
case SECONDS:
|
||||
return WindowUnits.SECOND;
|
||||
case MILLIS:
|
||||
return WindowUnits.MILLISECOND;
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException(String.format("Cannot create WindowUnit from %s", chronoUnit));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Quick access to available {@link WindowUnit units}.
|
||||
*/
|
||||
public enum WindowUnits implements WindowUnit {
|
||||
DEFAULT, YEAR, QUARTER, MONTH, WEEK, DAY, HOUR, MINUTE, SECOND, MILLISECOND
|
||||
}
|
||||
|
||||
/**
|
||||
* A fluent builder to create a {@link SetWindowFieldsOperation}.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public static class SetWindowFieldsOperationBuilder {
|
||||
|
||||
private Object partitionBy;
|
||||
private SortOperation sortOperation;
|
||||
private WindowOutput output;
|
||||
|
||||
/**
|
||||
* Specify the field to group by.
|
||||
*
|
||||
* @param fieldName must not be {@literal null} or null.
|
||||
* @return this.
|
||||
*/
|
||||
public SetWindowFieldsOperationBuilder partitionByField(String fieldName) {
|
||||
|
||||
Assert.hasText(fieldName, "Field name must not be empty or null");
|
||||
return partitionBy(Fields.field("$" + fieldName, fieldName));
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the {@link AggregationExpression expression} to group by.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return this.
|
||||
*/
|
||||
public SetWindowFieldsOperationBuilder partitionByExpression(AggregationExpression expression) {
|
||||
return partitionBy(expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort {@link Sort.Direction#ASC ascending} by the given fields.
|
||||
*
|
||||
* @param fields must not be {@literal null}.
|
||||
* @return this.
|
||||
*/
|
||||
public SetWindowFieldsOperationBuilder sortBy(String... fields) {
|
||||
return sortBy(Sort.by(fields));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the sort order.
|
||||
*
|
||||
* @param sort must not be {@literal null}.
|
||||
* @return this.
|
||||
*/
|
||||
public SetWindowFieldsOperationBuilder sortBy(Sort sort) {
|
||||
return sortBy(new SortOperation(sort));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link SortOperation} to use.
|
||||
*
|
||||
* @param sort must not be {@literal null}.
|
||||
* @return this.
|
||||
*/
|
||||
public SetWindowFieldsOperationBuilder sortBy(SortOperation sort) {
|
||||
|
||||
Assert.notNull(sort, "SortOperation must not be null");
|
||||
|
||||
this.sortOperation = sort;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the actual output computation.
|
||||
*
|
||||
* @param output must not be {@literal null}.
|
||||
* @return this.
|
||||
*/
|
||||
public SetWindowFieldsOperationBuilder output(WindowOutput output) {
|
||||
|
||||
Assert.notNull(output, "WindowOutput must not be null");
|
||||
|
||||
this.output = output;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a field capturing the result of the given {@link AggregationExpression expression} to the output.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return new instance of {@link WindowChoice}.
|
||||
*/
|
||||
public WindowChoice output(AggregationExpression expression) {
|
||||
|
||||
return new WindowChoice() {
|
||||
|
||||
@Nullable private Window window;
|
||||
|
||||
@Override
|
||||
public As within(Window window) {
|
||||
|
||||
Assert.notNull(window, "Window must not be null");
|
||||
|
||||
this.window = window;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SetWindowFieldsOperationBuilder as(String targetFieldName) {
|
||||
|
||||
Assert.hasText(targetFieldName, "Target field name must not be empty or null");
|
||||
|
||||
ComputedField computedField = new ComputedField(targetFieldName, expression, window);
|
||||
|
||||
if (SetWindowFieldsOperationBuilder.this.output == null) {
|
||||
SetWindowFieldsOperationBuilder.this.output = new WindowOutput(computedField);
|
||||
} else {
|
||||
SetWindowFieldsOperationBuilder.this.output.append(computedField);
|
||||
}
|
||||
|
||||
return SetWindowFieldsOperationBuilder.this;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface to capture field name used to capture the computation result.
|
||||
*/
|
||||
public interface As {
|
||||
|
||||
/**
|
||||
* Define the target name field name to hold the computation result.
|
||||
*
|
||||
* @param targetFieldName must not be {@literal null} or empty.
|
||||
* @return the starting point {@link SetWindowFieldsOperationBuilder builder} instance.
|
||||
*/
|
||||
SetWindowFieldsOperationBuilder as(String targetFieldName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface to capture an optional {@link Window} applicable to the field computation.
|
||||
*/
|
||||
public interface WindowChoice extends As {
|
||||
|
||||
/**
|
||||
* Specify calculation boundaries.
|
||||
*
|
||||
* @param window must not be {@literal null}.
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
As within(Window window);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Partition by a value that translates to a valid mongodb expression.
|
||||
*
|
||||
* @param value must not be {@literal null}.
|
||||
* @return this.
|
||||
*/
|
||||
public SetWindowFieldsOperationBuilder partitionBy(Object value) {
|
||||
|
||||
Assert.notNull(value, "Partition By must not be null");
|
||||
|
||||
partitionBy = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a new instance of {@link SetWindowFieldsOperation} with previously set arguments.
|
||||
*
|
||||
* @return new instance of {@link SetWindowFieldsOperation}.
|
||||
*/
|
||||
public SetWindowFieldsOperation build() {
|
||||
return new SetWindowFieldsOperation(partitionBy, sortOperation, output);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,17 +21,16 @@ import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Encapsulates the aggregation framework {@code $sortByCount}-operation.
|
||||
* <p/>
|
||||
* <br />
|
||||
* {@code $sortByCount} stage is typically used with {@link Aggregation} and {@code $facet}. Groups incoming documents
|
||||
* based on the value of a specified expression and computes the count of documents in each distinct group.
|
||||
* {@link SortByCountOperation} is equivalent to {@code { $group: { _id: <expression>, count: { $sum: 1 } } }, { $sort:
|
||||
* { count: -1 } }}.
|
||||
* <p/>
|
||||
* <br />
|
||||
* We recommend to use the static factory method {@link Aggregation#sortByCount(String)} instead of creating instances
|
||||
* of this class directly.
|
||||
*
|
||||
* @see <a href=
|
||||
* "https://docs.mongodb.com/manual/reference/operator/aggregation/sortByCount/">https://docs.mongodb.com/manual/reference/operator/aggregation/sortByCount/</a>
|
||||
* @see <a href="https://docs.mongodb.com/manual/reference/operator/aggregation/sortByCount/">https://docs.mongodb.com/manual/reference/operator/aggregation/sortByCount/</a>
|
||||
* @author Jérôme Guyon
|
||||
* @author Mark Paluch
|
||||
* @since 2.1
|
||||
|
||||
@@ -102,7 +102,7 @@ class SpelExpressionTransformer implements AggregationExpressionTransformer {
|
||||
ExpressionState state = new ExpressionState(new StandardEvaluationContext(params), CONFIG);
|
||||
ExpressionNode node = ExpressionNode.from(spelExpression.getAST(), state);
|
||||
|
||||
return transform(new AggregationExpressionTransformationContext<ExpressionNode>(node, null, null, context));
|
||||
return transform(new AggregationExpressionTransformationContext<>(node, null, null, context));
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -500,7 +500,10 @@ class SpelExpressionTransformer implements AggregationExpressionTransformer {
|
||||
dbo.put(methodReference.getArgumentMap()[i++], transform(child, context));
|
||||
}
|
||||
args = dbo;
|
||||
} else {
|
||||
} else if (ObjectUtils.nullSafeEquals(methodReference.getArgumentType(), ArgumentType.EMPTY_DOCUMENT)) {
|
||||
args = new Document();
|
||||
}
|
||||
else {
|
||||
|
||||
List<Object> argList = new ArrayList<Object>();
|
||||
|
||||
|
||||
@@ -18,8 +18,11 @@ package org.springframework.data.mongodb.core.aggregation;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.springframework.data.domain.Range;
|
||||
import org.springframework.data.mongodb.util.RegexFlags;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
@@ -27,6 +30,7 @@ import org.springframework.util.Assert;
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
* @author Divya Srivastava
|
||||
* @since 1.10
|
||||
*/
|
||||
public class StringOperators {
|
||||
@@ -516,6 +520,173 @@ public class StringOperators {
|
||||
return usesFieldRef() ? RTrim.valueOf(fieldReference) : RTrim.valueOf(expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link AggregationExpression} that takes the associated string representation and applies the given
|
||||
* regular expression to find the document with the first match.<br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @param regex must not be {@literal null}.
|
||||
* @return new instance of {@link RegexFind}.
|
||||
* @since 3.3
|
||||
*/
|
||||
public RegexFind regexFind(String regex) {
|
||||
return createRegexFind().regex(regex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link AggregationExpression} that takes the associated string representation and applies the regular
|
||||
* expression resulting from the given {@link AggregationExpression} to find the document with the first
|
||||
* match.<br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return new instance of {@link RegexFind}.
|
||||
* @since 3.3
|
||||
*/
|
||||
public RegexFind regexFind(AggregationExpression expression) {
|
||||
return createRegexFind().regexOf(expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link AggregationExpression} that takes the {@link Pattern} and applies the regular expression with
|
||||
* the options specified in the argument to find the document with the first match.
|
||||
*
|
||||
* @param pattern the pattern object to apply.
|
||||
* @return new instance of {@link RegexFind}.
|
||||
* @since 3.3
|
||||
*/
|
||||
public RegexFind regexFind(Pattern pattern) {
|
||||
return createRegexFind().pattern(pattern);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link AggregationExpression} that takes the associated string representation and applies the regular
|
||||
* expression with the options specified in the argument to find the document with the first match.
|
||||
*
|
||||
* @param regex the regular expression to apply.
|
||||
* @param options the options to use.
|
||||
* @return new instance of {@link RegexFind}.
|
||||
* @since 3.3
|
||||
*/
|
||||
public RegexFind regexFind(String regex, String options) {
|
||||
return createRegexFind().regex(regex).options(options);
|
||||
}
|
||||
|
||||
private RegexFind createRegexFind() {
|
||||
return usesFieldRef() ? RegexFind.valueOf(fieldReference) : RegexFind.valueOf(expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link AggregationExpression} that takes the associated string representation and applies the given
|
||||
* regular expression to find all the documents with the match.<br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @param regex must not be {@literal null}.
|
||||
* @return new instance of {@link RegexFindAll}.
|
||||
* @since 3.3
|
||||
*/
|
||||
public RegexFindAll regexFindAll(String regex) {
|
||||
return createRegexFindAll().regex(regex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link AggregationExpression} that takes the associated string representation and applies the regular
|
||||
* expression resulting from the given {@link AggregationExpression} to find all the documents with the
|
||||
* match..<br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return new instance of {@link RegexFindAll}.
|
||||
* @since 3.3
|
||||
*/
|
||||
public RegexFindAll regexFindAll(AggregationExpression expression) {
|
||||
return createRegexFindAll().regexOf(expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link AggregationExpression} that takes a {@link Pattern} and applies the regular expression with
|
||||
* the options specified in the argument to find all the documents with the match.
|
||||
*
|
||||
* @param pattern the pattern object to apply.
|
||||
* @return new instance of {@link RegexFindAll}.
|
||||
* @since 3.3
|
||||
*/
|
||||
public RegexFindAll regexFindAll(Pattern pattern) {
|
||||
return createRegexFindAll().pattern(pattern);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link AggregationExpression} that takes the associated string representation and applies the regular
|
||||
* expression with the options specified in the argument to find all the documents with the match.
|
||||
*
|
||||
* @param regex the regular expression to apply.
|
||||
* @param options the options to use.
|
||||
* @return new instance of {@link RegexFindAll}.
|
||||
* @since 3.3
|
||||
*/
|
||||
public RegexFindAll regexFindAll(String regex, String options) {
|
||||
return createRegexFindAll().regex(regex).options(options);
|
||||
}
|
||||
|
||||
private RegexFindAll createRegexFindAll() {
|
||||
return usesFieldRef() ? RegexFindAll.valueOf(fieldReference) : RegexFindAll.valueOf(expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link AggregationExpression} that takes the associated string representation and applies the given
|
||||
* regular expression to find if a match is found or not.<br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @param regex must not be {@literal null}.
|
||||
* @return new instance of {@link RegexMatch}.
|
||||
* @since 3.3
|
||||
*/
|
||||
public RegexMatch regexMatch(String regex) {
|
||||
return createRegexMatch().regex(regex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link AggregationExpression} that takes the associated string representation and applies the regular
|
||||
* expression resulting from the given {@link AggregationExpression} to find if a match is found or not.<br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return new instance of {@link RegexMatch}.
|
||||
* @since 3.3
|
||||
*/
|
||||
public RegexMatch regexMatch(AggregationExpression expression) {
|
||||
return createRegexMatch().regexOf(expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link AggregationExpression} that takes a {@link Pattern} and applies the regular expression with
|
||||
* the options specified in the argument to find if a match is found or not.
|
||||
*
|
||||
* @param pattern the pattern object to apply.
|
||||
* @return new instance of {@link RegexMatch}.
|
||||
* @since 3.3
|
||||
*/
|
||||
public RegexMatch regexMatch(Pattern pattern) {
|
||||
return createRegexMatch().pattern(pattern);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link AggregationExpression} that takes the associated string representation and applies the regular
|
||||
* expression with the options specified in the argument to find if a match is found or not.
|
||||
*
|
||||
* @param regex the regular expression to apply.
|
||||
* @param options the options to use.
|
||||
* @return new instance of {@link RegexMatch}.
|
||||
* @since 3.3
|
||||
*/
|
||||
public RegexMatch regexMatch(String regex, String options) {
|
||||
return createRegexMatch().regex(regex).options(options);
|
||||
}
|
||||
|
||||
private RegexMatch createRegexMatch() {
|
||||
return usesFieldRef() ? RegexMatch.valueOf(fieldReference) : RegexMatch.valueOf(expression);
|
||||
}
|
||||
|
||||
private boolean usesFieldRef() {
|
||||
return fieldReference != null;
|
||||
}
|
||||
@@ -1477,4 +1648,434 @@ public class StringOperators {
|
||||
return "$rtrim";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link AggregationExpression} for {@code $regexFind} which applies a regular expression (regex) to a string and
|
||||
* returns information on the first matched substring. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @author Divya Srivastava
|
||||
* @since 3.3
|
||||
*/
|
||||
public static class RegexFind extends AbstractAggregationExpression {
|
||||
|
||||
protected RegexFind(Object value) {
|
||||
super(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link RegexFind} 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 RegexFind}.
|
||||
*/
|
||||
public static RegexFind valueOf(String fieldReference) {
|
||||
|
||||
Assert.notNull(fieldReference, "FieldReference must not be null!");
|
||||
|
||||
return new RegexFind(Collections.singletonMap("input", Fields.field(fieldReference)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link RegexFind} using the result of the provided {@link AggregationExpression} as {@literal input}
|
||||
* value.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return new instance of {@link RegexFind}.
|
||||
*/
|
||||
public static RegexFind valueOf(AggregationExpression expression) {
|
||||
|
||||
Assert.notNull(expression, "Expression must not be null!");
|
||||
|
||||
return new RegexFind(Collections.singletonMap("input", expression));
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional specify the options to use with the regular expression.
|
||||
*
|
||||
* @param options must not be {@literal null}.
|
||||
* @return new instance of {@link RegexFind}.
|
||||
*/
|
||||
public RegexFind options(String options) {
|
||||
|
||||
Assert.notNull(options, "Options must not be null!");
|
||||
|
||||
return new RegexFind(append("options", options));
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional specify the reference to the {@link Field field} holding the options values to use with the regular
|
||||
* expression.
|
||||
*
|
||||
* @param fieldReference must not be {@literal null}.
|
||||
* @return new instance of {@link RegexFind}.
|
||||
*/
|
||||
public RegexFind optionsOf(String fieldReference) {
|
||||
|
||||
Assert.notNull(fieldReference, "FieldReference must not be null!");
|
||||
|
||||
return new RegexFind(append("options", Fields.field(fieldReference)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional specify the {@link AggregationExpression} evaluating to the options values to use with the regular
|
||||
* expression.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return new instance of {@link RegexFind}.
|
||||
*/
|
||||
public RegexFind optionsOf(AggregationExpression expression) {
|
||||
|
||||
Assert.notNull(expression, "Expression must not be null!");
|
||||
|
||||
return new RegexFind(append("options", expression));
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the regular expression to apply.
|
||||
*
|
||||
* @param regex must not be {@literal null}.
|
||||
* @return new instance of {@link RegexFind}.
|
||||
*/
|
||||
public RegexFind regex(String regex) {
|
||||
|
||||
Assert.notNull(regex, "Regex must not be null!");
|
||||
|
||||
return new RegexFind(append("regex", regex));
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply a {@link Pattern} into {@code regex} and {@code options} fields.
|
||||
*
|
||||
* @param pattern must not be {@literal null}.
|
||||
* @return new instance of {@link RegexFind}.
|
||||
*/
|
||||
public RegexFind pattern(Pattern pattern) {
|
||||
|
||||
Assert.notNull(pattern, "Pattern must not be null!");
|
||||
|
||||
Map<String, Object> regex = append("regex", pattern.pattern());
|
||||
regex.put("options", RegexFlags.toRegexOptions(pattern.flags()));
|
||||
|
||||
return new RegexFind(regex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the reference to the {@link Field field} holding the regular expression to apply.
|
||||
*
|
||||
* @param fieldReference must not be {@literal null}.
|
||||
* @return new instance of {@link RegexFind}.
|
||||
*/
|
||||
public RegexFind regexOf(String fieldReference) {
|
||||
|
||||
Assert.notNull(fieldReference, "fieldReference must not be null!");
|
||||
|
||||
return new RegexFind(append("regex", Fields.field(fieldReference)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the {@link AggregationExpression} evaluating to the regular expression to apply.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return new instance of {@link RegexFind}.
|
||||
*/
|
||||
public RegexFind regexOf(AggregationExpression expression) {
|
||||
|
||||
Assert.notNull(expression, "Expression must not be null!");
|
||||
|
||||
return new RegexFind(append("regex", expression));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getMongoMethod() {
|
||||
return "$regexFind";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link AggregationExpression} for {@code $regexFindAll} which applies a regular expression (regex) to a string and
|
||||
* returns information on all the matched substrings. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @author Divya Srivastava
|
||||
* @since 3.3
|
||||
*/
|
||||
public static class RegexFindAll extends AbstractAggregationExpression {
|
||||
|
||||
protected RegexFindAll(Object value) {
|
||||
super(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link RegexFindAll} 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 RegexFindAll}.
|
||||
*/
|
||||
public static RegexFindAll valueOf(String fieldReference) {
|
||||
Assert.notNull(fieldReference, "FieldReference must not be null!");
|
||||
return new RegexFindAll(Collections.singletonMap("input", Fields.field(fieldReference)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link RegexFindAll} using the result of the provided {@link AggregationExpression} as
|
||||
* {@literal input} value.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return new instance of {@link RegexFindAll}.
|
||||
*/
|
||||
public static RegexFindAll valueOf(AggregationExpression expression) {
|
||||
|
||||
Assert.notNull(expression, "Expression must not be null!");
|
||||
|
||||
return new RegexFindAll(Collections.singletonMap("input", expression));
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional specify the options to use with the regular expression.
|
||||
*
|
||||
* @param options must not be {@literal null}.
|
||||
* @return new instance of {@link RegexFindAll}.
|
||||
*/
|
||||
public RegexFindAll options(String options) {
|
||||
|
||||
Assert.notNull(options, "Options must not be null!");
|
||||
|
||||
return new RegexFindAll(append("options", options));
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional specify the reference to the {@link Field field} holding the options values to use with the regular
|
||||
* expression.
|
||||
*
|
||||
* @param fieldReference must not be {@literal null}.
|
||||
* @return new instance of {@link RegexFindAll}.
|
||||
*/
|
||||
public RegexFindAll optionsOf(String fieldReference) {
|
||||
|
||||
Assert.notNull(fieldReference, "fieldReference must not be null!");
|
||||
|
||||
return new RegexFindAll(append("options", Fields.field(fieldReference)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional specify the {@link AggregationExpression} evaluating to the options values to use with the regular
|
||||
* expression.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return new instance of {@link RegexFindAll}.
|
||||
*/
|
||||
public RegexFindAll optionsOf(AggregationExpression expression) {
|
||||
|
||||
Assert.notNull(expression, "Expression must not be null!");
|
||||
|
||||
return new RegexFindAll(append("options", expression));
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply a {@link Pattern} into {@code regex} and {@code options} fields.
|
||||
*
|
||||
* @param pattern must not be {@literal null}.
|
||||
* @return new instance of {@link RegexFindAll}.
|
||||
*/
|
||||
public RegexFindAll pattern(Pattern pattern) {
|
||||
|
||||
Assert.notNull(pattern, "Pattern must not be null!");
|
||||
|
||||
Map<String, Object> regex = append("regex", pattern.pattern());
|
||||
regex.put("options", RegexFlags.toRegexOptions(pattern.flags()));
|
||||
|
||||
return new RegexFindAll(regex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the regular expression to apply.
|
||||
*
|
||||
* @param regex must not be {@literal null}.
|
||||
* @return new instance of {@link RegexFindAll}.
|
||||
*/
|
||||
public RegexFindAll regex(String regex) {
|
||||
|
||||
Assert.notNull(regex, "Regex must not be null!");
|
||||
|
||||
return new RegexFindAll(append("regex", regex));
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the reference to the {@link Field field} holding the regular expression to apply.
|
||||
*
|
||||
* @param fieldReference must not be {@literal null}.
|
||||
* @return new instance of {@link RegexFindAll}.
|
||||
*/
|
||||
public RegexFindAll regexOf(String fieldReference) {
|
||||
|
||||
Assert.notNull(fieldReference, "fieldReference must not be null!");
|
||||
|
||||
return new RegexFindAll(append("regex", Fields.field(fieldReference)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the {@link AggregationExpression} evaluating to the regular expression to apply.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return new instance of {@link RegexFindAll}.
|
||||
*/
|
||||
public RegexFindAll regexOf(AggregationExpression expression) {
|
||||
|
||||
Assert.notNull(expression, "Expression must not be null!");
|
||||
|
||||
return new RegexFindAll(append("regex", expression));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getMongoMethod() {
|
||||
return "$regexFindAll";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link AggregationExpression} for {@code $regexMatch} which applies a regular expression (regex) to a string and
|
||||
* returns a boolean that indicates if a match is found or not. <br />
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.0 or later.
|
||||
*
|
||||
* @author Divya Srivastava
|
||||
* @since 3.3
|
||||
*/
|
||||
public static class RegexMatch extends AbstractAggregationExpression {
|
||||
|
||||
protected RegexMatch(Object value) {
|
||||
super(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link RegexMatch} 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 RegexMatch}.
|
||||
*/
|
||||
public static RegexMatch valueOf(String fieldReference) {
|
||||
|
||||
Assert.notNull(fieldReference, "FieldReference must not be null!");
|
||||
|
||||
return new RegexMatch(Collections.singletonMap("input", Fields.field(fieldReference)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link RegexMatch} using the result of the provided {@link AggregationExpression} as {@literal input}
|
||||
* value.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return new instance of {@link RegexMatch}.
|
||||
*/
|
||||
public static RegexMatch valueOf(AggregationExpression expression) {
|
||||
|
||||
Assert.notNull(expression, "Expression must not be null!");
|
||||
|
||||
return new RegexMatch(Collections.singletonMap("input", expression));
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional specify the options to use with the regular expression.
|
||||
*
|
||||
* @param options must not be {@literal null}.
|
||||
* @return new instance of {@link RegexMatch}.
|
||||
*/
|
||||
public RegexMatch options(String options) {
|
||||
|
||||
Assert.notNull(options, "Options must not be null!");
|
||||
|
||||
return new RegexMatch(append("options", options));
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional specify the reference to the {@link Field field} holding the options values to use with the regular
|
||||
* expression.
|
||||
*
|
||||
* @param fieldReference must not be {@literal null}.
|
||||
* @return new instance of {@link RegexMatch}.
|
||||
*/
|
||||
public RegexMatch optionsOf(String fieldReference) {
|
||||
|
||||
Assert.notNull(fieldReference, "FieldReference must not be null!");
|
||||
|
||||
return new RegexMatch(append("options", Fields.field(fieldReference)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional specify the {@link AggregationExpression} evaluating to the options values to use with the regular
|
||||
* expression.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return new instance of {@link RegexMatch}.
|
||||
*/
|
||||
public RegexMatch optionsOf(AggregationExpression expression) {
|
||||
|
||||
Assert.notNull(expression, "Expression must not be null!");
|
||||
|
||||
return new RegexMatch(append("options", expression));
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply a {@link Pattern} into {@code regex} and {@code options} fields.
|
||||
*
|
||||
* @param pattern must not be {@literal null}.
|
||||
* @return new instance of {@link RegexMatch}.
|
||||
*/
|
||||
public RegexMatch pattern(Pattern pattern) {
|
||||
|
||||
Assert.notNull(pattern, "Pattern must not be null!");
|
||||
|
||||
Map<String, Object> regex = append("regex", pattern.pattern());
|
||||
regex.put("options", RegexFlags.toRegexOptions(pattern.flags()));
|
||||
|
||||
return new RegexMatch(regex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the regular expression to apply.
|
||||
*
|
||||
* @param regex must not be {@literal null}.
|
||||
* @return new instance of {@link RegexMatch}.
|
||||
*/
|
||||
public RegexMatch regex(String regex) {
|
||||
|
||||
Assert.notNull(regex, "Regex must not be null!");
|
||||
|
||||
return new RegexMatch(append("regex", regex));
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the reference to the {@link Field field} holding the regular expression to apply.
|
||||
*
|
||||
* @param fieldReference must not be {@literal null}.
|
||||
* @return new instance of {@link RegexMatch}.
|
||||
*/
|
||||
public RegexMatch regexOf(String fieldReference) {
|
||||
|
||||
Assert.notNull(fieldReference, "FieldReference must not be null!");
|
||||
|
||||
return new RegexMatch(append("regex", Fields.field(fieldReference)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional specify the {@link AggregationExpression} evaluating to the regular expression to apply.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return new instance of {@link RegexMatch}.
|
||||
*/
|
||||
public RegexMatch regexOf(AggregationExpression expression) {
|
||||
|
||||
Assert.notNull(expression, "Expression must not be null!");
|
||||
|
||||
return new RegexMatch(append("regex", expression));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getMongoMethod() {
|
||||
return "$regexMatch";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,4 +166,8 @@ public class TypeBasedAggregationOperationContext implements AggregationOperatio
|
||||
|
||||
return new DirectFieldReference(new ExposedField(mappedField, true));
|
||||
}
|
||||
|
||||
public Class<?> getType() {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ import org.springframework.util.Assert;
|
||||
* containing duplicates, into a single result set that is handed over to the next stage. <br />
|
||||
* In order to remove duplicates it is possible to append a {@link GroupOperation} right after
|
||||
* {@link UnionWithOperation}.
|
||||
* <p />
|
||||
* <br />
|
||||
* If the {@link UnionWithOperation} uses a
|
||||
* <a href="https://docs.mongodb.com/master/reference/operator/aggregation/unionWith/#unionwith-pipeline">pipeline</a>
|
||||
* to process documents, field names within the pipeline will be treated as is. In order to map domain type property
|
||||
|
||||
@@ -35,7 +35,7 @@ import com.mongodb.DBRef;
|
||||
* @author Mark Paluch
|
||||
* @since 1.4
|
||||
*/
|
||||
public interface DbRefResolver {
|
||||
public interface DbRefResolver extends ReferenceResolver {
|
||||
|
||||
/**
|
||||
* Resolves the given {@link DBRef} into an object of the given {@link MongoPersistentProperty}'s type. The method
|
||||
|
||||
@@ -15,13 +15,6 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core.convert;
|
||||
|
||||
import static org.springframework.util.ReflectionUtils.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
@@ -29,28 +22,18 @@ import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.aopalliance.intercept.MethodInterceptor;
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
import org.bson.Document;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.aop.framework.ProxyFactory;
|
||||
import org.springframework.cglib.proxy.Callback;
|
||||
import org.springframework.cglib.proxy.Enhancer;
|
||||
import org.springframework.cglib.proxy.Factory;
|
||||
import org.springframework.cglib.proxy.MethodProxy;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
|
||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
import org.springframework.dao.support.PersistenceExceptionTranslator;
|
||||
import org.springframework.data.mongodb.ClientSessionException;
|
||||
import org.springframework.data.mongodb.LazyLoadingException;
|
||||
import org.springframework.data.mongodb.MongoDatabaseFactory;
|
||||
import org.springframework.data.mongodb.MongoDatabaseUtils;
|
||||
import org.springframework.data.mongodb.core.convert.ReferenceLoader.DocumentReferenceQuery;
|
||||
import org.springframework.data.mongodb.core.mapping.BasicMongoPersistentProperty;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.objenesis.ObjenesisStd;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.mongodb.DBRef;
|
||||
@@ -67,13 +50,11 @@ import com.mongodb.client.model.Filters;
|
||||
* @author Mark Paluch
|
||||
* @since 1.4
|
||||
*/
|
||||
public class DefaultDbRefResolver implements DbRefResolver {
|
||||
public class DefaultDbRefResolver extends DefaultReferenceResolver implements DbRefResolver, ReferenceResolver {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(DefaultDbRefResolver.class);
|
||||
|
||||
private final MongoDatabaseFactory mongoDbFactory;
|
||||
private final PersistenceExceptionTranslator exceptionTranslator;
|
||||
private final ObjenesisStd objenesis;
|
||||
|
||||
/**
|
||||
* Creates a new {@link DefaultDbRefResolver} with the given {@link MongoDatabaseFactory}.
|
||||
@@ -82,11 +63,11 @@ public class DefaultDbRefResolver implements DbRefResolver {
|
||||
*/
|
||||
public DefaultDbRefResolver(MongoDatabaseFactory mongoDbFactory) {
|
||||
|
||||
super(new MongoDatabaseFactoryReferenceLoader(mongoDbFactory), mongoDbFactory.getExceptionTranslator());
|
||||
|
||||
Assert.notNull(mongoDbFactory, "MongoDbFactory translator must not be null!");
|
||||
|
||||
this.mongoDbFactory = mongoDbFactory;
|
||||
this.exceptionTranslator = mongoDbFactory.getExceptionTranslator();
|
||||
this.objenesis = new ObjenesisStd(true);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -114,17 +95,8 @@ public class DefaultDbRefResolver implements DbRefResolver {
|
||||
*/
|
||||
@Override
|
||||
public Document fetch(DBRef dbRef) {
|
||||
|
||||
MongoCollection<Document> mongoCollection = getCollection(dbRef);
|
||||
|
||||
if (LOGGER.isTraceEnabled()) {
|
||||
LOGGER.trace("Fetching DBRef '{}' from {}.{}.", dbRef.getId(),
|
||||
StringUtils.hasText(dbRef.getDatabaseName()) ? dbRef.getDatabaseName()
|
||||
: mongoCollection.getNamespace().getDatabaseName(),
|
||||
dbRef.getCollectionName());
|
||||
}
|
||||
|
||||
return mongoCollection.find(Filters.eq("_id", dbRef.getId())).first();
|
||||
return getReferenceLoader().fetchOne(DocumentReferenceQuery.forSingleDocument(Filters.eq("_id", dbRef.getId())),
|
||||
ReferenceCollection.fromDBRef(dbRef));
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -165,7 +137,7 @@ public class DefaultDbRefResolver implements DbRefResolver {
|
||||
}
|
||||
|
||||
List<Document> result = mongoCollection //
|
||||
.find(new Document("_id", new Document("$in", ids))) //
|
||||
.find(new Document(BasicMongoPersistentProperty.ID_FIELD_NAME, new Document("$in", ids))) //
|
||||
.into(new ArrayList<>());
|
||||
|
||||
return ids.stream() //
|
||||
@@ -185,44 +157,9 @@ public class DefaultDbRefResolver implements DbRefResolver {
|
||||
private Object createLazyLoadingProxy(MongoPersistentProperty property, @Nullable DBRef dbref,
|
||||
DbRefResolverCallback callback, DbRefProxyHandler handler) {
|
||||
|
||||
Class<?> propertyType = property.getType();
|
||||
LazyLoadingInterceptor interceptor = new LazyLoadingInterceptor(property, dbref, exceptionTranslator, callback);
|
||||
Object lazyLoadingProxy = getProxyFactory().createLazyLoadingProxy(property, callback, dbref);
|
||||
|
||||
if (!propertyType.isInterface()) {
|
||||
|
||||
Factory factory = (Factory) objenesis.newInstance(getEnhancedTypeFor(propertyType));
|
||||
factory.setCallbacks(new Callback[] { interceptor });
|
||||
|
||||
return handler.populateId(property, dbref, factory);
|
||||
}
|
||||
|
||||
ProxyFactory proxyFactory = new ProxyFactory();
|
||||
|
||||
for (Class<?> type : propertyType.getInterfaces()) {
|
||||
proxyFactory.addInterface(type);
|
||||
}
|
||||
|
||||
proxyFactory.addInterface(LazyLoadingProxy.class);
|
||||
proxyFactory.addInterface(propertyType);
|
||||
proxyFactory.addAdvice(interceptor);
|
||||
|
||||
return handler.populateId(property, dbref, proxyFactory.getProxy(LazyLoadingProxy.class.getClassLoader()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the CGLib enhanced type for the given source type.
|
||||
*
|
||||
* @param type
|
||||
* @return
|
||||
*/
|
||||
private Class<?> getEnhancedTypeFor(Class<?> type) {
|
||||
|
||||
Enhancer enhancer = new Enhancer();
|
||||
enhancer.setSuperclass(type);
|
||||
enhancer.setCallbackType(org.springframework.cglib.proxy.MethodInterceptor.class);
|
||||
enhancer.setInterfaces(new Class[] { LazyLoadingProxy.class });
|
||||
|
||||
return enhancer.createClass();
|
||||
return handler.populateId(property, dbref, lazyLoadingProxy);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -245,253 +182,10 @@ public class DefaultDbRefResolver implements DbRefResolver {
|
||||
private static Stream<Document> documentWithId(Object identifier, Collection<Document> documents) {
|
||||
|
||||
return documents.stream() //
|
||||
.filter(it -> it.get("_id").equals(identifier)) //
|
||||
.filter(it -> it.get(BasicMongoPersistentProperty.ID_FIELD_NAME).equals(identifier)) //
|
||||
.limit(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link MethodInterceptor} that is used within a lazy loading proxy. The property resolving is delegated to a
|
||||
* {@link DbRefResolverCallback}. The resolving process is triggered by a method invocation on the proxy and is
|
||||
* guaranteed to be performed only once.
|
||||
*
|
||||
* @author Thomas Darimont
|
||||
* @author Oliver Gierke
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
static class LazyLoadingInterceptor
|
||||
implements MethodInterceptor, org.springframework.cglib.proxy.MethodInterceptor, Serializable {
|
||||
|
||||
private static final Method INITIALIZE_METHOD, TO_DBREF_METHOD, FINALIZE_METHOD;
|
||||
|
||||
private final DbRefResolverCallback callback;
|
||||
private final MongoPersistentProperty property;
|
||||
private final PersistenceExceptionTranslator exceptionTranslator;
|
||||
|
||||
private volatile boolean resolved;
|
||||
private final @Nullable DBRef dbref;
|
||||
private @Nullable Object result;
|
||||
|
||||
static {
|
||||
try {
|
||||
INITIALIZE_METHOD = LazyLoadingProxy.class.getMethod("getTarget");
|
||||
TO_DBREF_METHOD = LazyLoadingProxy.class.getMethod("toDBRef");
|
||||
FINALIZE_METHOD = Object.class.getDeclaredMethod("finalize");
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link LazyLoadingInterceptor} for the given {@link MongoPersistentProperty},
|
||||
* {@link PersistenceExceptionTranslator} and {@link DbRefResolverCallback}.
|
||||
*
|
||||
* @param property must not be {@literal null}.
|
||||
* @param dbref can be {@literal null}.
|
||||
* @param callback must not be {@literal null}.
|
||||
*/
|
||||
public LazyLoadingInterceptor(MongoPersistentProperty property, @Nullable DBRef dbref,
|
||||
PersistenceExceptionTranslator exceptionTranslator, DbRefResolverCallback callback) {
|
||||
|
||||
Assert.notNull(property, "Property must not be null!");
|
||||
Assert.notNull(exceptionTranslator, "Exception translator must not be null!");
|
||||
Assert.notNull(callback, "Callback must not be null!");
|
||||
|
||||
this.dbref = dbref;
|
||||
this.callback = callback;
|
||||
this.exceptionTranslator = exceptionTranslator;
|
||||
this.property = property;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.aopalliance.intercept.MethodInterceptor#invoke(org.aopalliance.intercept.MethodInvocation)
|
||||
*/
|
||||
@Override
|
||||
public Object invoke(@Nullable MethodInvocation invocation) throws Throwable {
|
||||
return intercept(invocation.getThis(), invocation.getMethod(), invocation.getArguments(), null);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.cglib.proxy.MethodInterceptor#intercept(java.lang.Object, java.lang.reflect.Method, java.lang.Object[], org.springframework.cglib.proxy.MethodProxy)
|
||||
*/
|
||||
@Nullable
|
||||
@Override
|
||||
public Object intercept(Object obj, Method method, Object[] args, @Nullable MethodProxy proxy) throws Throwable {
|
||||
|
||||
if (INITIALIZE_METHOD.equals(method)) {
|
||||
return ensureResolved();
|
||||
}
|
||||
|
||||
if (TO_DBREF_METHOD.equals(method)) {
|
||||
return this.dbref;
|
||||
}
|
||||
|
||||
if (isObjectMethod(method) && Object.class.equals(method.getDeclaringClass())) {
|
||||
|
||||
if (ReflectionUtils.isToStringMethod(method)) {
|
||||
return proxyToString(proxy);
|
||||
}
|
||||
|
||||
if (ReflectionUtils.isEqualsMethod(method)) {
|
||||
return proxyEquals(proxy, args[0]);
|
||||
}
|
||||
|
||||
if (ReflectionUtils.isHashCodeMethod(method)) {
|
||||
return proxyHashCode(proxy);
|
||||
}
|
||||
|
||||
// DATAMONGO-1076 - finalize methods should not trigger proxy initialization
|
||||
if (FINALIZE_METHOD.equals(method)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Object target = ensureResolved();
|
||||
|
||||
if (target == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
ReflectionUtils.makeAccessible(method);
|
||||
|
||||
return method.invoke(target, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a to string representation for the given {@code proxy}.
|
||||
*
|
||||
* @param proxy
|
||||
* @return
|
||||
*/
|
||||
private String proxyToString(@Nullable Object proxy) {
|
||||
|
||||
StringBuilder description = new StringBuilder();
|
||||
if (dbref != null) {
|
||||
description.append(dbref.getCollectionName());
|
||||
description.append(":");
|
||||
description.append(dbref.getId());
|
||||
} else {
|
||||
description.append(System.identityHashCode(proxy));
|
||||
}
|
||||
description.append("$").append(LazyLoadingProxy.class.getSimpleName());
|
||||
|
||||
return description.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the hashcode for the given {@code proxy}.
|
||||
*
|
||||
* @param proxy
|
||||
* @return
|
||||
*/
|
||||
private int proxyHashCode(@Nullable Object proxy) {
|
||||
return proxyToString(proxy).hashCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs an equality check for the given {@code proxy}.
|
||||
*
|
||||
* @param proxy
|
||||
* @param that
|
||||
* @return
|
||||
*/
|
||||
private boolean proxyEquals(@Nullable Object proxy, Object that) {
|
||||
|
||||
if (!(that instanceof LazyLoadingProxy)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (that == proxy) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return proxyToString(proxy).equals(that.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Will trigger the resolution if the proxy is not resolved already or return a previously resolved result.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Nullable
|
||||
private Object ensureResolved() {
|
||||
|
||||
if (!resolved) {
|
||||
this.result = resolve();
|
||||
this.resolved = true;
|
||||
}
|
||||
|
||||
return this.result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback method for serialization.
|
||||
*
|
||||
* @param out
|
||||
* @throws IOException
|
||||
*/
|
||||
private void writeObject(ObjectOutputStream out) throws IOException {
|
||||
|
||||
ensureResolved();
|
||||
out.writeObject(this.result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback method for deserialization.
|
||||
*
|
||||
* @param in
|
||||
* @throws IOException
|
||||
*/
|
||||
private void readObject(ObjectInputStream in) throws IOException {
|
||||
|
||||
try {
|
||||
this.resolved = true;
|
||||
this.result = in.readObject();
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new LazyLoadingException("Could not deserialize result", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the proxy into its backing object.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Nullable
|
||||
private synchronized Object resolve() {
|
||||
|
||||
if (resolved) {
|
||||
|
||||
if (LOGGER.isTraceEnabled()) {
|
||||
LOGGER.trace("Accessing already resolved lazy loading property {}.{}",
|
||||
property.getOwner() != null ? property.getOwner().getName() : "unknown", property.getName());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
try {
|
||||
if (LOGGER.isTraceEnabled()) {
|
||||
LOGGER.trace("Resolving lazy loading property {}.{}",
|
||||
property.getOwner() != null ? property.getOwner().getName() : "unknown", property.getName());
|
||||
}
|
||||
|
||||
return callback.resolve(property);
|
||||
|
||||
} catch (RuntimeException ex) {
|
||||
|
||||
DataAccessException translatedException = this.exceptionTranslator.translateExceptionIfPossible(ex);
|
||||
|
||||
if (translatedException instanceof ClientSessionException) {
|
||||
throw new LazyLoadingException("Unable to lazily resolve DBRef! Invalid session state.", ex);
|
||||
}
|
||||
|
||||
throw new LazyLoadingException("Unable to lazily resolve DBRef!",
|
||||
translatedException != null ? translatedException : ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Customization hook for obtaining the {@link MongoCollection} for a given {@link DBRef}.
|
||||
*
|
||||
@@ -504,4 +198,10 @@ public class DefaultDbRefResolver implements DbRefResolver {
|
||||
return MongoDatabaseUtils.getDatabase(dbref.getDatabaseName(), mongoDbFactory)
|
||||
.getCollection(dbref.getCollectionName(), Document.class);
|
||||
}
|
||||
|
||||
protected MongoCollection<Document> getCollection(ReferenceCollection context) {
|
||||
|
||||
return MongoDatabaseUtils.getDatabase(context.getDatabase(), mongoDbFactory).getCollection(context.getCollection(),
|
||||
Document.class);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* Copyright 2021 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
|
||||
*
|
||||
* https://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 static org.springframework.data.mongodb.core.convert.ReferenceLookupDelegate.*;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import org.springframework.dao.support.PersistenceExceptionTranslator;
|
||||
import org.springframework.data.mongodb.core.mapping.DBRef;
|
||||
import org.springframework.data.mongodb.core.mapping.DocumentReference;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* {@link ReferenceResolver} implementation that uses a given {@link ReferenceLookupDelegate} to load and convert entity
|
||||
* associations expressed via a {@link MongoPersistentProperty persitent property}. Creates {@link LazyLoadingProxy
|
||||
* proxies} for associations that should be lazily loaded.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
* @since 3.3
|
||||
*/
|
||||
public class DefaultReferenceResolver implements ReferenceResolver {
|
||||
|
||||
private final ReferenceLoader referenceLoader;
|
||||
private final LazyLoadingProxyFactory proxyFactory;
|
||||
|
||||
private final LookupFunction collectionLookupFunction = (filter, ctx) -> getReferenceLoader().fetchMany(filter, ctx);
|
||||
private final LookupFunction singleValueLookupFunction = (filter, ctx) -> {
|
||||
Object target = getReferenceLoader().fetchOne(filter, ctx);
|
||||
return target == null ? Collections.emptyList() : Collections.singleton(getReferenceLoader().fetchOne(filter, ctx));
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a new instance of {@link DefaultReferenceResolver}.
|
||||
*
|
||||
* @param referenceLoader must not be {@literal null}.
|
||||
* @param exceptionTranslator must not be {@literal null}.
|
||||
*/
|
||||
public DefaultReferenceResolver(ReferenceLoader referenceLoader, PersistenceExceptionTranslator exceptionTranslator) {
|
||||
|
||||
Assert.notNull(referenceLoader, "ReferenceLoader must not be null!");
|
||||
Assert.notNull(exceptionTranslator, "ExceptionTranslator must not be null!");
|
||||
|
||||
this.referenceLoader = referenceLoader;
|
||||
this.proxyFactory = new LazyLoadingProxyFactory(exceptionTranslator);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object resolveReference(MongoPersistentProperty property, Object source,
|
||||
ReferenceLookupDelegate referenceLookupDelegate, MongoEntityReader entityReader) {
|
||||
|
||||
LookupFunction lookupFunction = (property.isCollectionLike() || property.isMap()) ? collectionLookupFunction
|
||||
: singleValueLookupFunction;
|
||||
|
||||
if (isLazyReference(property)) {
|
||||
return createLazyLoadingProxy(property, source, referenceLookupDelegate, lookupFunction, entityReader);
|
||||
}
|
||||
|
||||
return referenceLookupDelegate.readReference(property, source, lookupFunction, entityReader);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the association expressed by the given {@link MongoPersistentProperty property} should be resolved lazily.
|
||||
*
|
||||
* @param property
|
||||
* @return return {@literal true} if the defined association is lazy.
|
||||
* @see DBRef#lazy()
|
||||
* @see DocumentReference#lazy()
|
||||
*/
|
||||
protected boolean isLazyReference(MongoPersistentProperty property) {
|
||||
|
||||
if (property.isDocumentReference()) {
|
||||
return property.getDocumentReference().lazy();
|
||||
}
|
||||
|
||||
return property.getDBRef() != null && property.getDBRef().lazy();
|
||||
}
|
||||
|
||||
/**
|
||||
* The {@link ReferenceLoader} executing the lookup.
|
||||
*
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
protected ReferenceLoader getReferenceLoader() {
|
||||
return referenceLoader;
|
||||
}
|
||||
|
||||
LazyLoadingProxyFactory getProxyFactory() {
|
||||
return proxyFactory;
|
||||
}
|
||||
|
||||
private Object createLazyLoadingProxy(MongoPersistentProperty property, Object source,
|
||||
ReferenceLookupDelegate referenceLookupDelegate, LookupFunction lookupFunction, MongoEntityReader entityReader) {
|
||||
return proxyFactory.createLazyLoadingProxy(property, it -> {
|
||||
return referenceLookupDelegate.readReference(it, source, lookupFunction, entityReader);
|
||||
}, source instanceof DocumentReferenceSource ? ((DocumentReferenceSource)source).getTargetSource() : source);
|
||||
}
|
||||
}
|
||||
@@ -21,13 +21,13 @@ 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;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.mongodb.BasicDBObject;
|
||||
import com.mongodb.DBObject;
|
||||
|
||||
/**
|
||||
@@ -67,6 +67,19 @@ class DocumentAccessor {
|
||||
return this.document;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies all of the mappings from the given {@link Document} to the underlying target {@link Document}. These
|
||||
* mappings will replace any mappings that the target document had for any of the keys currently in the specified map.
|
||||
*
|
||||
* @param source
|
||||
*/
|
||||
public void putAll(Document source) {
|
||||
|
||||
Map<String, Object> target = BsonUtils.asMap(document);
|
||||
|
||||
target.putAll(source);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
@@ -110,28 +123,7 @@ class DocumentAccessor {
|
||||
*/
|
||||
@Nullable
|
||||
public Object get(MongoPersistentProperty property) {
|
||||
|
||||
String fieldName = property.getFieldName();
|
||||
Map<String, Object> map = BsonUtils.asMap(document);
|
||||
|
||||
if (!fieldName.contains(".")) {
|
||||
return map.get(fieldName);
|
||||
}
|
||||
|
||||
Iterator<String> parts = Arrays.asList(fieldName.split("\\.")).iterator();
|
||||
Map<String, Object> source = map;
|
||||
Object result = null;
|
||||
|
||||
while (source != null && parts.hasNext()) {
|
||||
|
||||
result = source.get(parts.next());
|
||||
|
||||
if (parts.hasNext()) {
|
||||
source = getAsMap(result);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
return BsonUtils.resolveValue(document, property.getFieldName());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -141,8 +133,9 @@ class DocumentAccessor {
|
||||
* @param entity must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
@Nullable
|
||||
public Object getRawId(MongoPersistentEntity<?> entity) {
|
||||
return entity.hasIdProperty() ? get(entity.getRequiredIdProperty()) : BsonUtils.asMap(document).get("_id");
|
||||
return entity.hasIdProperty() ? get(entity.getRequiredIdProperty()) : BsonUtils.get(document, "_id");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -157,71 +150,7 @@ class DocumentAccessor {
|
||||
|
||||
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(".")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String[] parts = fieldName.split("\\.");
|
||||
Map<String, Object> source;
|
||||
|
||||
if (this.document instanceof Document) {
|
||||
source = ((Document) this.document);
|
||||
} else {
|
||||
source = ((DBObject) this.document).toMap();
|
||||
}
|
||||
|
||||
Object result = null;
|
||||
|
||||
for (int i = 1; i < parts.length; i++) {
|
||||
|
||||
result = source.get(parts[i - 1]);
|
||||
source = getAsMap(result);
|
||||
|
||||
if (source == null) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return source.containsKey(parts[parts.length - 1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the given source object as map, i.e. {@link Document}s and maps as is or {@literal null} otherwise.
|
||||
*
|
||||
* @param source can be {@literal null}.
|
||||
* @return can be {@literal null}.
|
||||
*/
|
||||
@Nullable
|
||||
@SuppressWarnings("unchecked")
|
||||
private static Map<String, Object> getAsMap(Object source) {
|
||||
|
||||
if (source instanceof Document) {
|
||||
return (Document) source;
|
||||
}
|
||||
|
||||
if (source instanceof BasicDBObject) {
|
||||
return (BasicDBObject) source;
|
||||
}
|
||||
|
||||
if (source instanceof Map) {
|
||||
return (Map<String, Object>) source;
|
||||
}
|
||||
|
||||
return null;
|
||||
return BsonUtils.hasValue(document, property.getFieldName());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user