Compare commits
146 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5b502a095f | ||
|
|
7552329598 | ||
|
|
fa4f8bfe9f | ||
|
|
58fde292d5 | ||
|
|
e71f8b34be | ||
|
|
152d84a200 | ||
|
|
ddf77d55ff | ||
|
|
2acf1bc5d4 | ||
|
|
26f230036a | ||
|
|
3ee696dab2 | ||
|
|
7ee12e9637 | ||
|
|
414d716c07 | ||
|
|
5f9e25cd8a | ||
|
|
caa2dd4e85 | ||
|
|
28fc1e4823 | ||
|
|
d62639d11c | ||
|
|
b13e47d8ac | ||
|
|
3743ca56df | ||
|
|
0f974130d1 | ||
|
|
020da42800 | ||
|
|
4307b4cd91 | ||
|
|
af874b635c | ||
|
|
017e01810d | ||
|
|
05b46779cb | ||
|
|
ba56d247c9 | ||
|
|
4484dac574 | ||
|
|
af4b1788fc | ||
|
|
af40f15a36 | ||
|
|
193b7de2d9 | ||
|
|
f5204e859f | ||
|
|
16818324d8 | ||
|
|
a9269c0086 | ||
|
|
d3624b9af4 | ||
|
|
3711ed5b50 | ||
|
|
c20feabada | ||
|
|
f93e40fdee | ||
|
|
dca508f3dc | ||
|
|
742448a281 | ||
|
|
1a4749e97c | ||
|
|
a3cbe829b2 | ||
|
|
4ca6f29666 | ||
|
|
df65644d59 | ||
|
|
05a169c858 | ||
|
|
e49119038d | ||
|
|
f2df84e8c4 | ||
|
|
1504fd0529 | ||
|
|
f63610c925 | ||
|
|
06eb52cf13 | ||
|
|
c9a4a8a370 | ||
|
|
8d17ddb92c | ||
|
|
0e96ef2d0c | ||
|
|
4d8b965176 | ||
|
|
37bb390447 | ||
|
|
5c79e1e4ef | ||
|
|
9b41d70f75 | ||
|
|
b2e52ab28f | ||
|
|
fed33444e9 | ||
|
|
a4ad37703f | ||
|
|
42e95e9600 | ||
|
|
ed894aa51a | ||
|
|
693ff04294 | ||
|
|
b018b31e20 | ||
|
|
bacb75d098 | ||
|
|
ec0fe6f994 | ||
|
|
d3d690b908 | ||
|
|
b98b5125f1 | ||
|
|
d68bd4b44a | ||
|
|
a73ae9a1a5 | ||
|
|
ac3c578e93 | ||
|
|
9872f8cb07 | ||
|
|
1b1ab2c495 | ||
|
|
6554274dde | ||
|
|
34f91acbfe | ||
|
|
8e7d508cfe | ||
|
|
04dd78cb36 | ||
|
|
52c554cfe1 | ||
|
|
c1bb8c4ba5 | ||
|
|
ed9a367bb8 | ||
|
|
b88ce46239 | ||
|
|
42ec94d321 | ||
|
|
53f35e185f | ||
|
|
e08bfd253c | ||
|
|
caaafa275d | ||
|
|
1f082abc7f | ||
|
|
df77fcc19d | ||
|
|
007b965673 | ||
|
|
1b9680cece | ||
|
|
13f1d21919 | ||
|
|
4c10bf30bc | ||
|
|
8f78d0e0d8 | ||
|
|
56115a263c | ||
|
|
66b809318a | ||
|
|
bfff60d915 | ||
|
|
051e973226 | ||
|
|
eb61629f10 | ||
|
|
85783e5354 | ||
|
|
433b012b91 | ||
|
|
8dca0049ca | ||
|
|
635f3b82be | ||
|
|
e69c7e1134 | ||
|
|
1f94e74b75 | ||
|
|
98858e0f5f | ||
|
|
90f311de51 | ||
|
|
7f7015fd86 | ||
|
|
ab9c5d73a0 | ||
|
|
13caa162db | ||
|
|
d5d620d777 | ||
|
|
21d50f2a72 | ||
|
|
4912d62be6 | ||
|
|
2bf6f226d6 | ||
|
|
1a6f7e371a | ||
|
|
2ef5d795ce | ||
|
|
e3d2f16202 | ||
|
|
c44232ff39 | ||
|
|
385e911708 | ||
|
|
171d8b2b1e | ||
|
|
7ac7eefad6 | ||
|
|
7a39e94e4b | ||
|
|
25733664b3 | ||
|
|
81da10f499 | ||
|
|
bde114ed19 | ||
|
|
a2403f58ec | ||
|
|
875b8eda9f | ||
|
|
fc0a021937 | ||
|
|
a6aa174ff5 | ||
|
|
8b36617752 | ||
|
|
56e8799c22 | ||
|
|
88e60070d6 | ||
|
|
6c7039580f | ||
|
|
fba003f215 | ||
|
|
1a3239554c | ||
|
|
37b541931d | ||
|
|
9038280f68 | ||
|
|
75935a2bdb | ||
|
|
d9ca3d7eb3 | ||
|
|
04e77ad5ab | ||
|
|
0ab39a17a7 | ||
|
|
49a6f13797 | ||
|
|
b0fd6f691b | ||
|
|
b5778772d9 | ||
|
|
0f55fb305d | ||
|
|
5ae7547465 | ||
|
|
cf4e04a30e | ||
|
|
89c1dc77d9 | ||
|
|
a2c842b59b | ||
|
|
0cd0be9478 |
1
.github/PULL_REQUEST_TEMPLATE.md
vendored
1
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -6,6 +6,7 @@ Make sure that:
|
||||
-->
|
||||
|
||||
- [ ] You have read the [Spring Data contribution guidelines](https://github.com/spring-projects/spring-data-build/blob/master/CONTRIBUTING.adoc).
|
||||
- [ ] There is a ticket in the bug tracker for the project in our [JIRA](https://jira.spring.io/browse/DATAMONGO).
|
||||
- [ ] You use the code formatters provided [here](https://github.com/spring-projects/spring-data-build/tree/master/etc/ide) and have them applied to your changes. Don’t submit any formatting related changes.
|
||||
- [ ] You submit test cases (unit or integration tests) that back your changes.
|
||||
- [ ] You added yourself as author in the headers of the classes you touched. Amend the date range in the Apache license header if needed. For new types, add the license header (copy from another file and set the current year only).
|
||||
|
||||
47
.github/workflows/project.yml
vendored
47
.github/workflows/project.yml
vendored
@@ -1,47 +0,0 @@
|
||||
# GitHub Actions to automate GitHub issues for Spring Data Project Management
|
||||
|
||||
name: Spring Data GitHub Issues
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened, edited, reopened]
|
||||
issue_comment:
|
||||
types: [created]
|
||||
pull_request_target:
|
||||
types: [opened, edited, reopened]
|
||||
|
||||
jobs:
|
||||
Inbox:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository_owner == 'spring-projects' && (github.event.action == 'opened' || github.event.action == 'reopened') && github.event.pull_request == null
|
||||
steps:
|
||||
- name: Create or Update Issue Card
|
||||
uses: peter-evans/create-or-update-project-card@v1.1.2
|
||||
with:
|
||||
project-name: 'Spring Data'
|
||||
column-name: 'Inbox'
|
||||
project-location: 'spring-projects'
|
||||
token: ${{ secrets.GH_ISSUES_TOKEN_SPRING_DATA }}
|
||||
Pull-Request:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository_owner == 'spring-projects' && (github.event.action == 'opened' || github.event.action == 'reopened') && github.event.pull_request != null
|
||||
steps:
|
||||
- name: Create or Update Pull Request Card
|
||||
uses: peter-evans/create-or-update-project-card@v1.1.2
|
||||
with:
|
||||
project-name: 'Spring Data'
|
||||
column-name: 'Review pending'
|
||||
project-location: 'spring-projects'
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
token: ${{ secrets.GH_ISSUES_TOKEN_SPRING_DATA }}
|
||||
Feedback-Provided:
|
||||
runs-on: ubuntu-latest
|
||||
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
|
||||
with:
|
||||
project-name: 'Spring Data'
|
||||
column-name: 'Feedback provided'
|
||||
project-location: 'spring-projects'
|
||||
token: ${{ secrets.GH_ISSUES_TOKEN_SPRING_DATA }}
|
||||
2
CI.adoc
2
CI.adoc
@@ -1,6 +1,6 @@
|
||||
= Continuous Integration
|
||||
|
||||
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%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%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/]
|
||||
|
||||
|
||||
27
CODE_OF_CONDUCT.adoc
Normal file
27
CODE_OF_CONDUCT.adoc
Normal file
@@ -0,0 +1,27 @@
|
||||
= Contributor Code of Conduct
|
||||
|
||||
As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
|
||||
|
||||
We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, or nationality.
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery
|
||||
* Personal attacks
|
||||
* Trolling or insulting/derogatory comments
|
||||
* Public or private harassment
|
||||
* Publishing other's private information, such as physical or electronic addresses,
|
||||
without explicit permission
|
||||
* Other unethical or unprofessional conduct
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
|
||||
|
||||
By adopting this Code of Conduct, project maintainers commit themselves to fairly and consistently applying these principles to every aspect of managing this project. Project maintainers who do not follow or enforce the Code of Conduct may be permanently removed from the project team.
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community.
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting a project maintainer at spring-code-of-conduct@pivotal.io.
|
||||
All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances.
|
||||
Maintainers are obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
|
||||
This Code of Conduct is adapted from the https://contributor-covenant.org[Contributor Covenant], version 1.3.0, available at https://contributor-covenant.org/version/1/3/0/[contributor-covenant.org/version/1/3/0/].
|
||||
@@ -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/main/CONTRIBUTING.adoc[here].
|
||||
You find the contribution guidelines for Spring Data projects https://github.com/spring-projects/spring-data-build/blob/master/CONTRIBUTING.adoc[here].
|
||||
|
||||
38
Jenkinsfile
vendored
38
Jenkinsfile
vendored
@@ -3,7 +3,7 @@ pipeline {
|
||||
|
||||
triggers {
|
||||
pollSCM 'H/10 * * * *'
|
||||
upstream(upstreamProjects: "spring-data-commons/main", threshold: hudson.model.Result.SUCCESS)
|
||||
upstream(upstreamProjects: "spring-data-commons/2.3.x", threshold: hudson.model.Result.SUCCESS)
|
||||
}
|
||||
|
||||
options {
|
||||
@@ -23,39 +23,39 @@ pipeline {
|
||||
|
||||
steps {
|
||||
script {
|
||||
def image = docker.build("springci/spring-data-openjdk8-with-mongodb-4.0.23", "ci/openjdk8-mongodb-4.0/")
|
||||
def image = docker.build("springci/spring-data-openjdk8-with-mongodb-4.0", "ci/openjdk8-mongodb-4.0/")
|
||||
docker.withRegistry('', 'hub.docker.com-springbuildmaster') {
|
||||
image.push()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
stage('Publish JDK 8 + MongoDB 4.4') {
|
||||
stage('Publish JDK 8 + MongoDB 4.2') {
|
||||
when {
|
||||
changeset "ci/openjdk8-mongodb-4.4/**"
|
||||
changeset "ci/openjdk8-mongodb-4.2/**"
|
||||
}
|
||||
agent { label 'data' }
|
||||
options { timeout(time: 30, unit: 'MINUTES') }
|
||||
|
||||
steps {
|
||||
script {
|
||||
def image = docker.build("springci/spring-data-openjdk8-with-mongodb-4.4.4", "ci/openjdk8-mongodb-4.4/")
|
||||
def image = docker.build("springci/spring-data-openjdk8-with-mongodb-4.2.0", "ci/openjdk8-mongodb-4.2/")
|
||||
docker.withRegistry('', 'hub.docker.com-springbuildmaster') {
|
||||
image.push()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
stage('Publish JDK 16 + MongoDB 4.4') {
|
||||
stage('Publish JDK 14 + MongoDB 4.2') {
|
||||
when {
|
||||
changeset "ci/openjdk16-mongodb-4.4/**"
|
||||
changeset "ci/openjdk14-mongodb-4.2/**"
|
||||
}
|
||||
agent { label 'data' }
|
||||
options { timeout(time: 30, unit: 'MINUTES') }
|
||||
|
||||
steps {
|
||||
script {
|
||||
def image = docker.build("springci/spring-data-openjdk16-with-mongodb-4.4.4", "ci/openjdk16-mongodb-4.4/")
|
||||
def image = docker.build("springci/spring-data-openjdk14-with-mongodb-4.2.0", "ci/openjdk14-mongodb-4.2/")
|
||||
docker.withRegistry('', 'hub.docker.com-springbuildmaster') {
|
||||
image.push()
|
||||
}
|
||||
@@ -68,7 +68,7 @@ pipeline {
|
||||
stage("test: baseline (jdk8)") {
|
||||
when {
|
||||
anyOf {
|
||||
branch 'main'
|
||||
branch '3.0.x'
|
||||
not { triggeredBy 'UpstreamCause' }
|
||||
}
|
||||
}
|
||||
@@ -82,7 +82,7 @@ pipeline {
|
||||
steps {
|
||||
script {
|
||||
docker.withRegistry('', 'hub.docker.com-springbuildmaster') {
|
||||
docker.image('springci/spring-data-openjdk8-with-mongodb-4.0.23:latest').inside('-v $HOME:/tmp/jenkins-home') {
|
||||
docker.image('springci/spring-data-openjdk8-with-mongodb-4.2.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'
|
||||
@@ -97,8 +97,8 @@ pipeline {
|
||||
|
||||
stage("Test other configurations") {
|
||||
when {
|
||||
allOf {
|
||||
branch 'main'
|
||||
anyOf {
|
||||
branch '3.0.x'
|
||||
not { triggeredBy 'UpstreamCause' }
|
||||
}
|
||||
}
|
||||
@@ -114,7 +114,7 @@ pipeline {
|
||||
steps {
|
||||
script {
|
||||
docker.withRegistry('', 'hub.docker.com-springbuildmaster') {
|
||||
docker.image('springci/spring-data-openjdk8-with-mongodb-4.0.23:latest').inside('-v $HOME:/tmp/jenkins-home') {
|
||||
docker.image('springci/spring-data-openjdk8-with-mongodb-4.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'
|
||||
@@ -127,7 +127,7 @@ pipeline {
|
||||
}
|
||||
}
|
||||
|
||||
stage("test: mongodb 4.4 (jdk8)") {
|
||||
stage("test: mongodb 4.2 (jdk8)") {
|
||||
agent {
|
||||
label 'data'
|
||||
}
|
||||
@@ -138,7 +138,7 @@ pipeline {
|
||||
steps {
|
||||
script {
|
||||
docker.withRegistry('', 'hub.docker.com-springbuildmaster') {
|
||||
docker.image('springci/spring-data-openjdk8-with-mongodb-4.4.4:latest').inside('-v $HOME:/tmp/jenkins-home') {
|
||||
docker.image('springci/spring-data-openjdk8-with-mongodb-4.2.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'
|
||||
@@ -151,7 +151,7 @@ pipeline {
|
||||
}
|
||||
}
|
||||
|
||||
stage("test: baseline (jdk16)") {
|
||||
stage("test: baseline (jdk14)") {
|
||||
agent {
|
||||
label 'data'
|
||||
}
|
||||
@@ -162,7 +162,7 @@ pipeline {
|
||||
steps {
|
||||
script {
|
||||
docker.withRegistry('', 'hub.docker.com-springbuildmaster') {
|
||||
docker.image('springci/spring-data-openjdk16-with-mongodb-4.4.4:latest').inside('-v $HOME:/tmp/jenkins-home') {
|
||||
docker.image('springci/spring-data-openjdk15-with-mongodb-4.2.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'
|
||||
@@ -180,7 +180,7 @@ pipeline {
|
||||
stage('Release to artifactory') {
|
||||
when {
|
||||
anyOf {
|
||||
branch 'main'
|
||||
branch '3.0.x'
|
||||
not { triggeredBy 'UpstreamCause' }
|
||||
}
|
||||
}
|
||||
@@ -213,7 +213,7 @@ pipeline {
|
||||
|
||||
stage('Publish documentation') {
|
||||
when {
|
||||
branch 'main'
|
||||
branch '3.0.x'
|
||||
}
|
||||
agent {
|
||||
label 'data'
|
||||
|
||||
@@ -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%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]]
|
||||
= 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]]
|
||||
|
||||
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.
|
||||
|
||||
@@ -10,7 +10,7 @@ Key functional areas of Spring Data MongoDB are a POJO centric model for interac
|
||||
|
||||
== 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.
|
||||
This project is governed by the link:CODE_OF_CONDUCT.adoc[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
|
||||
|
||||
|
||||
@@ -5,11 +5,11 @@ 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 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; \
|
||||
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.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 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/*;
|
||||
@@ -1,15 +1,15 @@
|
||||
FROM adoptopenjdk/openjdk16:latest
|
||||
FROM adoptopenjdk/openjdk14: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 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; \
|
||||
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.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 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/*;
|
||||
@@ -10,6 +10,6 @@ RUN RUN set -eux; \
|
||||
echo ${TZ} > /etc/timezone;
|
||||
|
||||
RUN apt-get update ; \
|
||||
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 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 clean; \
|
||||
rm -rf /var/lib/apt/lists/*;
|
||||
|
||||
@@ -5,13 +5,11 @@ 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 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; \
|
||||
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 ; \
|
||||
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 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/*;
|
||||
12
pom.xml
12
pom.xml
@@ -5,7 +5,7 @@
|
||||
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-mongodb-parent</artifactId>
|
||||
<version>3.3.0-M2</version>
|
||||
<version>3.0.10.BUILD-SNAPSHOT</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.6.0-M2</version>
|
||||
<version>2.3.10.BUILD-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<modules>
|
||||
@@ -26,8 +26,8 @@
|
||||
<properties>
|
||||
<project.type>multi</project.type>
|
||||
<dist.id>spring-data-mongodb</dist.id>
|
||||
<springdata.commons>2.6.0-M2</springdata.commons>
|
||||
<mongo>4.3.0</mongo>
|
||||
<springdata.commons>2.3.10.BUILD-SNAPSHOT</springdata.commons>
|
||||
<mongo>4.0.6</mongo>
|
||||
<mongo.reactivestreams>${mongo}</mongo.reactivestreams>
|
||||
<jmh.version>1.19</jmh.version>
|
||||
</properties>
|
||||
@@ -134,8 +134,8 @@
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>spring-libs-milestone</id>
|
||||
<url>https://repo.spring.io/libs-milestone</url>
|
||||
<id>spring-libs-snapshot</id>
|
||||
<url>https://repo.spring.io/libs-snapshot</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>sonatype-libs-snapshot</id>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-mongodb-parent</artifactId>
|
||||
<version>3.3.0-M2</version>
|
||||
<version>3.0.10.BUILD-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-mongodb-parent</artifactId>
|
||||
<version>3.3.0-M2</version>
|
||||
<version>3.0.10.BUILD-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-mongodb-parent</artifactId>
|
||||
<version>3.3.0-M2</version>
|
||||
<version>3.0.10.BUILD-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -87,13 +87,6 @@
|
||||
<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>
|
||||
@@ -143,13 +136,6 @@
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.reactivex.rxjava3</groupId>
|
||||
<artifactId>rxjava</artifactId>
|
||||
<version>${rxjava3}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- CDI -->
|
||||
<!-- Dependency order required to build against CDI 1.0 and test with CDI 2.0 -->
|
||||
<dependency>
|
||||
@@ -206,14 +192,7 @@
|
||||
<dependency>
|
||||
<groupId>org.hibernate</groupId>
|
||||
<artifactId>hibernate-validator</artifactId>
|
||||
<version>5.4.3.Final</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.glassfish</groupId>
|
||||
<artifactId>javax.el</artifactId>
|
||||
<version>3.0.1-b11</version>
|
||||
<version>5.2.4.Final</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
|
||||
@@ -1,152 +0,0 @@
|
||||
/*
|
||||
* 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()}.
|
||||
* <p />
|
||||
*
|
||||
* <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 + "}";
|
||||
}
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
* <p />
|
||||
* 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();
|
||||
}
|
||||
@@ -61,8 +61,8 @@ public @interface EnableMongoAuditing {
|
||||
boolean modifyOnCreate() default true;
|
||||
|
||||
/**
|
||||
* Configures a {@link DateTimeProvider} bean name that allows customizing the timestamp to be used for setting
|
||||
* creation and modification dates.
|
||||
* Configures a {@link DateTimeProvider} bean name that allows customizing the {@link org.joda.time.DateTime} to be
|
||||
* used for setting creation and modification dates.
|
||||
*
|
||||
* @return empty {@link String} by default.
|
||||
*/
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020-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.config;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.data.auditing.DateTimeProvider;
|
||||
import org.springframework.data.domain.ReactiveAuditorAware;
|
||||
|
||||
/**
|
||||
* Annotation to enable auditing in MongoDB using reactive infrastructure via annotation configuration.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 3.1
|
||||
*/
|
||||
@Inherited
|
||||
@Documented
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Import(ReactiveMongoAuditingRegistrar.class)
|
||||
public @interface EnableReactiveMongoAuditing {
|
||||
|
||||
/**
|
||||
* Configures the {@link ReactiveAuditorAware} bean to be used to lookup the current principal.
|
||||
*
|
||||
* @return empty {@link String} by default.
|
||||
*/
|
||||
String auditorAwareRef() default "";
|
||||
|
||||
/**
|
||||
* Configures whether the creation and modification dates are set. Defaults to {@literal true}.
|
||||
*
|
||||
* @return {@literal true} by default.
|
||||
*/
|
||||
boolean setDates() default true;
|
||||
|
||||
/**
|
||||
* Configures whether the entity shall be marked as modified on creation. Defaults to {@literal true}.
|
||||
*
|
||||
* @return {@literal true} by default.
|
||||
*/
|
||||
boolean modifyOnCreate() default true;
|
||||
|
||||
/**
|
||||
* Configures a {@link DateTimeProvider} bean name that allows customizing the timestamp to be used for setting
|
||||
* creation and modification dates.
|
||||
*
|
||||
* @return empty {@link String} by default.
|
||||
*/
|
||||
String dateTimeProviderRef() default "";
|
||||
}
|
||||
@@ -17,6 +17,7 @@ package org.springframework.data.mongodb.config;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.support.AbstractBeanDefinition;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
@@ -27,8 +28,14 @@ import org.springframework.data.auditing.IsNewAwareAuditingHandler;
|
||||
import org.springframework.data.auditing.config.AuditingBeanDefinitionRegistrarSupport;
|
||||
import org.springframework.data.auditing.config.AuditingConfiguration;
|
||||
import org.springframework.data.config.ParsingUtils;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
|
||||
import org.springframework.data.mongodb.core.mapping.event.AuditingEntityCallback;
|
||||
import org.springframework.data.mongodb.core.mapping.event.ReactiveAuditingEntityCallback;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* {@link ImportBeanDefinitionRegistrar} to enable {@link EnableMongoAuditing} annotation.
|
||||
@@ -39,6 +46,9 @@ import org.springframework.util.Assert;
|
||||
*/
|
||||
class MongoAuditingRegistrar extends AuditingBeanDefinitionRegistrarSupport {
|
||||
|
||||
private static boolean PROJECT_REACTOR_AVAILABLE = ClassUtils.isPresent("reactor.core.publisher.Mono",
|
||||
MongoAuditingRegistrar.class.getClassLoader());
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.auditing.config.AuditingBeanDefinitionRegistrarSupport#getAnnotation()
|
||||
@@ -81,7 +91,7 @@ class MongoAuditingRegistrar extends AuditingBeanDefinitionRegistrarSupport {
|
||||
|
||||
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(IsNewAwareAuditingHandler.class);
|
||||
|
||||
BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(PersistentEntitiesFactoryBean.class);
|
||||
BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(MongoMappingContextLookup.class);
|
||||
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR);
|
||||
|
||||
builder.addConstructorArgValue(definition.getBeanDefinition());
|
||||
@@ -106,6 +116,68 @@ class MongoAuditingRegistrar extends AuditingBeanDefinitionRegistrarSupport {
|
||||
|
||||
registerInfrastructureBeanWithId(listenerBeanDefinitionBuilder.getBeanDefinition(),
|
||||
AuditingEntityCallback.class.getName(), registry);
|
||||
|
||||
if (PROJECT_REACTOR_AVAILABLE) {
|
||||
registerReactiveAuditingEntityCallback(registry, auditingHandlerDefinition.getSource());
|
||||
}
|
||||
}
|
||||
|
||||
private void registerReactiveAuditingEntityCallback(BeanDefinitionRegistry registry, Object source) {
|
||||
|
||||
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(ReactiveAuditingEntityCallback.class);
|
||||
|
||||
builder.addConstructorArgValue(ParsingUtils.getObjectFactoryBeanDefinition(getAuditingHandlerBeanName(), registry));
|
||||
builder.getRawBeanDefinition().setSource(source);
|
||||
|
||||
registerInfrastructureBeanWithId(builder.getBeanDefinition(), ReactiveAuditingEntityCallback.class.getName(),
|
||||
registry);
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple helper to be able to wire the {@link MappingContext} from a {@link MappingMongoConverter} bean available in
|
||||
* the application context.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
static class MongoMappingContextLookup
|
||||
implements FactoryBean<MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty>> {
|
||||
|
||||
private final MappingMongoConverter converter;
|
||||
|
||||
/**
|
||||
* Creates a new {@link MongoMappingContextLookup} for the given {@link MappingMongoConverter}.
|
||||
*
|
||||
* @param converter must not be {@literal null}.
|
||||
*/
|
||||
public MongoMappingContextLookup(MappingMongoConverter converter) {
|
||||
this.converter = converter;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.beans.factory.FactoryBean#getObject()
|
||||
*/
|
||||
@Override
|
||||
public MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> getObject() throws Exception {
|
||||
return converter.getMappingContext();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.beans.factory.FactoryBean#getObjectType()
|
||||
*/
|
||||
@Override
|
||||
public Class<?> getObjectType() {
|
||||
return MappingContext.class;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.beans.factory.FactoryBean#isSingleton()
|
||||
*/
|
||||
@Override
|
||||
public boolean isSingleton() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ 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;
|
||||
@@ -139,7 +140,8 @@ public abstract class MongoConfigurationSupport {
|
||||
}
|
||||
|
||||
/**
|
||||
* Scans the given base package for entities, i.e. MongoDB specific types annotated with {@link Document}.
|
||||
* Scans the given base package for entities, i.e. MongoDB specific types annotated with {@link Document} and
|
||||
* {@link Persistent}.
|
||||
*
|
||||
* @param basePackage must not be {@literal null}.
|
||||
* @return
|
||||
@@ -159,6 +161,7 @@ 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)) {
|
||||
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020-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.config;
|
||||
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.data.mapping.context.PersistentEntities;
|
||||
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
|
||||
|
||||
/**
|
||||
* Simple helper to be able to wire the {@link PersistentEntities} from a {@link MappingMongoConverter} bean available
|
||||
* in the application context.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Mark Paluch
|
||||
* @author Christoph Strobl
|
||||
* @since 3.1
|
||||
*/
|
||||
class PersistentEntitiesFactoryBean implements FactoryBean<PersistentEntities> {
|
||||
|
||||
private final MappingMongoConverter converter;
|
||||
|
||||
/**
|
||||
* Creates a new {@link PersistentEntitiesFactoryBean} for the given {@link MappingMongoConverter}.
|
||||
*
|
||||
* @param converter must not be {@literal null}.
|
||||
*/
|
||||
public PersistentEntitiesFactoryBean(MappingMongoConverter converter) {
|
||||
this.converter = converter;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.beans.factory.FactoryBean#getObject()
|
||||
*/
|
||||
@Override
|
||||
public PersistentEntities getObject() {
|
||||
return PersistentEntities.of(converter.getMappingContext());
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.beans.factory.FactoryBean#getObjectType()
|
||||
*/
|
||||
@Override
|
||||
public Class<?> getObjectType() {
|
||||
return PersistentEntities.class;
|
||||
}
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020-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.config;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.support.AbstractBeanDefinition;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
import org.springframework.data.auditing.ReactiveIsNewAwareAuditingHandler;
|
||||
import org.springframework.data.auditing.config.AuditingBeanDefinitionRegistrarSupport;
|
||||
import org.springframework.data.auditing.config.AuditingConfiguration;
|
||||
import org.springframework.data.config.ParsingUtils;
|
||||
import org.springframework.data.mongodb.core.mapping.event.ReactiveAuditingEntityCallback;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* {@link ImportBeanDefinitionRegistrar} to enable {@link EnableReactiveMongoAuditing} annotation.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 3.1
|
||||
*/
|
||||
class ReactiveMongoAuditingRegistrar extends AuditingBeanDefinitionRegistrarSupport {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.auditing.config.AuditingBeanDefinitionRegistrarSupport#getAnnotation()
|
||||
*/
|
||||
@Override
|
||||
protected Class<? extends Annotation> getAnnotation() {
|
||||
return EnableReactiveMongoAuditing.class;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.auditing.config.AuditingBeanDefinitionRegistrarSupport#getAuditingHandlerBeanName()
|
||||
*/
|
||||
@Override
|
||||
protected String getAuditingHandlerBeanName() {
|
||||
return "reactiveMongoAuditingHandler";
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.auditing.config.AuditingBeanDefinitionRegistrarSupport#getAuditHandlerBeanDefinitionBuilder(org.springframework.data.auditing.config.AuditingConfiguration)
|
||||
*/
|
||||
@Override
|
||||
protected BeanDefinitionBuilder getAuditHandlerBeanDefinitionBuilder(AuditingConfiguration configuration) {
|
||||
|
||||
Assert.notNull(configuration, "AuditingConfiguration must not be null!");
|
||||
|
||||
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(ReactiveIsNewAwareAuditingHandler.class);
|
||||
|
||||
BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(PersistentEntitiesFactoryBean.class);
|
||||
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR);
|
||||
|
||||
builder.addConstructorArgValue(definition.getBeanDefinition());
|
||||
return configureDefaultAuditHandlerAttributes(configuration, builder);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.auditing.config.AuditingBeanDefinitionRegistrarSupport#registerAuditListener(org.springframework.beans.factory.config.BeanDefinition, org.springframework.beans.factory.support.BeanDefinitionRegistry)
|
||||
*/
|
||||
@Override
|
||||
protected void registerAuditListenerBeanDefinition(BeanDefinition auditingHandlerDefinition,
|
||||
BeanDefinitionRegistry registry) {
|
||||
|
||||
Assert.notNull(auditingHandlerDefinition, "BeanDefinition must not be null!");
|
||||
Assert.notNull(registry, "BeanDefinitionRegistry must not be null!");
|
||||
|
||||
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(ReactiveAuditingEntityCallback.class);
|
||||
|
||||
builder.addConstructorArgValue(ParsingUtils.getObjectFactoryBeanDefinition(getAuditingHandlerBeanName(), registry));
|
||||
builder.getRawBeanDefinition().setSource(auditingHandlerDefinition.getSource());
|
||||
|
||||
registerInfrastructureBeanWithId(builder.getBeanDefinition(), ReactiveAuditingEntityCallback.class.getName(),
|
||||
registry);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -15,6 +15,8 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@@ -27,9 +29,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;
|
||||
import org.springframework.data.mongodb.core.aggregation.TypedAggregation;
|
||||
import org.springframework.data.mongodb.core.convert.QueryMapper;
|
||||
@@ -37,7 +37,6 @@ 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;
|
||||
@@ -50,50 +49,34 @@ import org.springframework.util.ObjectUtils;
|
||||
* @author Mark Paluch
|
||||
* @since 2.1
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
class AggregationUtil {
|
||||
|
||||
QueryMapper queryMapper;
|
||||
MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext;
|
||||
Lazy<AggregationOperationContext> untypedMappingContext;
|
||||
|
||||
AggregationUtil(QueryMapper queryMapper,
|
||||
MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext) {
|
||||
/**
|
||||
* Prepare the {@link AggregationOperationContext} for a given aggregation by either returning the context itself it
|
||||
* is not {@literal null}, create a {@link TypeBasedAggregationOperationContext} if the aggregation contains type
|
||||
* information (is a {@link TypedAggregation}) or use the {@link Aggregation#DEFAULT_CONTEXT}.
|
||||
*
|
||||
* @param aggregation must not be {@literal null}.
|
||||
* @param context can be {@literal null}.
|
||||
* @return the root {@link AggregationOperationContext} to use.
|
||||
*/
|
||||
AggregationOperationContext prepareAggregationContext(Aggregation aggregation,
|
||||
@Nullable AggregationOperationContext context) {
|
||||
|
||||
this.queryMapper = queryMapper;
|
||||
this.mappingContext = mappingContext;
|
||||
this.untypedMappingContext = Lazy
|
||||
.of(() -> new RelaxedTypeBasedAggregationOperationContext(Object.class, mappingContext, queryMapper));
|
||||
}
|
||||
|
||||
AggregationOperationContext createAggregationContext(Aggregation aggregation, @Nullable Class<?> inputType) {
|
||||
|
||||
DomainTypeMapping domainTypeMapping = aggregation.getOptions().getDomainTypeMapping();
|
||||
|
||||
if (domainTypeMapping == DomainTypeMapping.NONE) {
|
||||
return Aggregation.DEFAULT_CONTEXT;
|
||||
if (context != null) {
|
||||
return context;
|
||||
}
|
||||
|
||||
if (!(aggregation instanceof TypedAggregation)) {
|
||||
|
||||
if(inputType == null) {
|
||||
return untypedMappingContext.get();
|
||||
}
|
||||
|
||||
if (domainTypeMapping == DomainTypeMapping.STRICT
|
||||
&& !aggregation.getPipeline().containsUnionWith()) {
|
||||
return new TypeBasedAggregationOperationContext(inputType, mappingContext, queryMapper);
|
||||
}
|
||||
|
||||
return new RelaxedTypeBasedAggregationOperationContext(inputType, mappingContext, queryMapper);
|
||||
if (aggregation instanceof TypedAggregation) {
|
||||
return new TypeBasedAggregationOperationContext(((TypedAggregation) aggregation).getInputType(), 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);
|
||||
return Aggregation.DEFAULT_CONTEXT;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -105,7 +88,7 @@ class AggregationUtil {
|
||||
*/
|
||||
List<Document> createPipeline(Aggregation aggregation, AggregationOperationContext context) {
|
||||
|
||||
if (ObjectUtils.nullSafeEquals(context, Aggregation.DEFAULT_CONTEXT)) {
|
||||
if (!ObjectUtils.nullSafeEquals(context, Aggregation.DEFAULT_CONTEXT)) {
|
||||
return aggregation.toPipeline(context);
|
||||
}
|
||||
|
||||
@@ -132,6 +115,53 @@ 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()))
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
|
||||
|
||||
@@ -25,7 +27,6 @@ import org.springframework.data.mongodb.core.convert.MongoConverter;
|
||||
import org.springframework.data.mongodb.core.messaging.Message;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
import com.mongodb.client.model.changestream.ChangeStreamDocument;
|
||||
import com.mongodb.client.model.changestream.OperationType;
|
||||
@@ -38,6 +39,7 @@ import com.mongodb.client.model.changestream.OperationType;
|
||||
* @author Mark Paluch
|
||||
* @since 2.1
|
||||
*/
|
||||
@EqualsAndHashCode
|
||||
public class ChangeStreamEvent<T> {
|
||||
|
||||
@SuppressWarnings("rawtypes") //
|
||||
@@ -185,8 +187,8 @@ public class ChangeStreamEvent<T> {
|
||||
return CONVERTED_UPDATER.compareAndSet(this, null, result) ? result : CONVERTED_UPDATER.get(this);
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException(
|
||||
String.format("No converter found capable of converting %s to %s", fullDocument.getClass(), targetType));
|
||||
throw new IllegalArgumentException(String.format("No converter found capable of converting %s to %s",
|
||||
fullDocument.getClass(), targetType));
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -197,27 +199,4 @@ public class ChangeStreamEvent<T> {
|
||||
public String toString() {
|
||||
return "ChangeStreamEvent {" + "raw=" + raw + ", targetType=" + targetType + '}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
|
||||
if (this == o)
|
||||
return true;
|
||||
if (o == null || getClass() != o.getClass())
|
||||
return false;
|
||||
|
||||
ChangeStreamEvent<?> that = (ChangeStreamEvent<?>) o;
|
||||
|
||||
if (!ObjectUtils.nullSafeEquals(this.raw, that.raw)) {
|
||||
return false;
|
||||
}
|
||||
return ObjectUtils.nullSafeEquals(this.targetType, that.targetType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = raw != null ? raw.hashCode() : 0;
|
||||
result = 31 * result + ObjectUtils.nullSafeHashCode(targetType);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Arrays;
|
||||
import java.util.Optional;
|
||||
@@ -23,6 +25,7 @@ import org.bson.BsonDocument;
|
||||
import org.bson.BsonTimestamp;
|
||||
import org.bson.BsonValue;
|
||||
import org.bson.Document;
|
||||
|
||||
import org.springframework.data.mongodb.core.aggregation.Aggregation;
|
||||
import org.springframework.data.mongodb.core.query.Collation;
|
||||
import org.springframework.lang.Nullable;
|
||||
@@ -42,6 +45,7 @@ import com.mongodb.client.model.changestream.FullDocument;
|
||||
* @author Mark Paluch
|
||||
* @since 2.1
|
||||
*/
|
||||
@EqualsAndHashCode
|
||||
public class ChangeStreamOptions {
|
||||
|
||||
private @Nullable Object filter;
|
||||
@@ -152,44 +156,6 @@ public class ChangeStreamOptions {
|
||||
+ ObjectUtils.nullSafeClassName(timestamp));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o)
|
||||
return true;
|
||||
if (o == null || getClass() != o.getClass())
|
||||
return false;
|
||||
|
||||
ChangeStreamOptions that = (ChangeStreamOptions) o;
|
||||
|
||||
if (!ObjectUtils.nullSafeEquals(this.filter, that.filter)) {
|
||||
return false;
|
||||
}
|
||||
if (!ObjectUtils.nullSafeEquals(this.resumeToken, that.resumeToken)) {
|
||||
return false;
|
||||
}
|
||||
if (!ObjectUtils.nullSafeEquals(this.fullDocumentLookup, that.fullDocumentLookup)) {
|
||||
return false;
|
||||
}
|
||||
if (!ObjectUtils.nullSafeEquals(this.collation, that.collation)) {
|
||||
return false;
|
||||
}
|
||||
if (!ObjectUtils.nullSafeEquals(this.resumeTimestamp, that.resumeTimestamp)) {
|
||||
return false;
|
||||
}
|
||||
return resume == that.resume;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = ObjectUtils.nullSafeHashCode(filter);
|
||||
result = 31 * result + ObjectUtils.nullSafeHashCode(resumeToken);
|
||||
result = 31 * result + ObjectUtils.nullSafeHashCode(fullDocumentLookup);
|
||||
result = 31 * result + ObjectUtils.nullSafeHashCode(collation);
|
||||
result = 31 * result + ObjectUtils.nullSafeHashCode(resumeTimestamp);
|
||||
result = 31 * result + ObjectUtils.nullSafeHashCode(resume);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
* @since 2.2
|
||||
|
||||
@@ -15,13 +15,12 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
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;
|
||||
@@ -45,7 +44,6 @@ 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.
|
||||
@@ -58,19 +56,17 @@ public class CollectionOptions {
|
||||
*/
|
||||
@Deprecated
|
||||
public CollectionOptions(@Nullable Long size, @Nullable Long maxDocuments, @Nullable Boolean capped) {
|
||||
this(size, maxDocuments, capped, null, ValidationOptions.none(), null);
|
||||
this(size, maxDocuments, capped, null, ValidationOptions.none());
|
||||
}
|
||||
|
||||
private CollectionOptions(@Nullable Long size, @Nullable Long maxDocuments, @Nullable Boolean capped,
|
||||
@Nullable Collation collation, ValidationOptions validationOptions,
|
||||
@Nullable TimeSeriesOptions timeSeriesOptions) {
|
||||
@Nullable Collation collation, ValidationOptions validationOptions) {
|
||||
|
||||
this.maxDocuments = maxDocuments;
|
||||
this.size = size;
|
||||
this.capped = capped;
|
||||
this.collation = collation;
|
||||
this.validationOptions = validationOptions;
|
||||
this.timeSeriesOptions = timeSeriesOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -84,7 +80,7 @@ public class CollectionOptions {
|
||||
|
||||
Assert.notNull(collation, "Collation must not be null!");
|
||||
|
||||
return new CollectionOptions(null, null, null, collation, ValidationOptions.none(), null);
|
||||
return new CollectionOptions(null, null, null, collation, ValidationOptions.none());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -94,21 +90,7 @@ public class CollectionOptions {
|
||||
* @since 2.0
|
||||
*/
|
||||
public static CollectionOptions empty() {
|
||||
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));
|
||||
return new CollectionOptions(null, null, null, null, ValidationOptions.none());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -119,7 +101,7 @@ public class CollectionOptions {
|
||||
* @since 2.0
|
||||
*/
|
||||
public CollectionOptions capped() {
|
||||
return new CollectionOptions(size, maxDocuments, true, collation, validationOptions, null);
|
||||
return new CollectionOptions(size, maxDocuments, true, collation, validationOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -130,7 +112,7 @@ public class CollectionOptions {
|
||||
* @since 2.0
|
||||
*/
|
||||
public CollectionOptions maxDocuments(long maxDocuments) {
|
||||
return new CollectionOptions(size, maxDocuments, capped, collation, validationOptions, timeSeriesOptions);
|
||||
return new CollectionOptions(size, maxDocuments, capped, collation, validationOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -141,7 +123,7 @@ public class CollectionOptions {
|
||||
* @since 2.0
|
||||
*/
|
||||
public CollectionOptions size(long size) {
|
||||
return new CollectionOptions(size, maxDocuments, capped, collation, validationOptions, timeSeriesOptions);
|
||||
return new CollectionOptions(size, maxDocuments, capped, collation, validationOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -152,7 +134,7 @@ public class CollectionOptions {
|
||||
* @since 2.0
|
||||
*/
|
||||
public CollectionOptions collation(@Nullable Collation collation) {
|
||||
return new CollectionOptions(size, maxDocuments, capped, collation, validationOptions, timeSeriesOptions);
|
||||
return new CollectionOptions(size, maxDocuments, capped, collation, validationOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -272,20 +254,7 @@ public class CollectionOptions {
|
||||
public CollectionOptions validation(ValidationOptions validationOptions) {
|
||||
|
||||
Assert.notNull(validationOptions, "ValidationOptions must not be null!");
|
||||
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);
|
||||
return new CollectionOptions(size, maxDocuments, capped, collation, validationOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -336,16 +305,6 @@ 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.
|
||||
*
|
||||
@@ -353,6 +312,7 @@ public class CollectionOptions {
|
||||
* @author Andreas Zink
|
||||
* @since 2.1
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public static class ValidationOptions {
|
||||
|
||||
private static final ValidationOptions NONE = new ValidationOptions(null, null, null);
|
||||
@@ -361,13 +321,6 @@ public class CollectionOptions {
|
||||
private final @Nullable ValidationLevel validationLevel;
|
||||
private final @Nullable ValidationAction validationAction;
|
||||
|
||||
public ValidationOptions(Validator validator, ValidationLevel validationLevel, ValidationAction validationAction) {
|
||||
|
||||
this.validator = validator;
|
||||
this.validationLevel = validationLevel;
|
||||
this.validationAction = validationAction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an empty {@link ValidationOptions}.
|
||||
*
|
||||
@@ -441,89 +394,4 @@ 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,9 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.Value;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@@ -24,9 +27,8 @@ import java.util.stream.Collectors;
|
||||
import org.bson.Document;
|
||||
import org.bson.conversions.Bson;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.dao.DataIntegrityViolationException;
|
||||
import org.springframework.dao.support.PersistenceExceptionTranslator;
|
||||
import org.springframework.data.mapping.callback.EntityCallbacks;
|
||||
import org.springframework.data.mongodb.BulkOperationException;
|
||||
import org.springframework.data.mongodb.core.convert.QueryMapper;
|
||||
import org.springframework.data.mongodb.core.convert.UpdateMapper;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
|
||||
@@ -45,9 +47,7 @@ import org.springframework.data.mongodb.core.query.UpdateDefinition.ArrayFilter;
|
||||
import org.springframework.data.util.Pair;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
import com.mongodb.MongoBulkWriteException;
|
||||
import com.mongodb.WriteConcern;
|
||||
import com.mongodb.bulk.BulkWriteResult;
|
||||
import com.mongodb.client.MongoCollection;
|
||||
@@ -64,7 +64,6 @@ import com.mongodb.client.model.*;
|
||||
* @author Jens Schauder
|
||||
* @author Michail Nikolaev
|
||||
* @author Roman Puchkovskiy
|
||||
* @author Jacob Botuck
|
||||
* @since 1.9
|
||||
*/
|
||||
class DefaultBulkOperations implements BulkOperations {
|
||||
@@ -74,6 +73,7 @@ class DefaultBulkOperations implements BulkOperations {
|
||||
private final BulkOperationContext bulkOperationContext;
|
||||
private final List<SourceAwareWriteModelHolder> models = new ArrayList<>();
|
||||
|
||||
private PersistenceExceptionTranslator exceptionTranslator;
|
||||
private @Nullable WriteConcern defaultWriteConcern;
|
||||
|
||||
private BulkWriteOptions bulkOptions;
|
||||
@@ -97,9 +97,19 @@ class DefaultBulkOperations implements BulkOperations {
|
||||
this.mongoOperations = mongoOperations;
|
||||
this.collectionName = collectionName;
|
||||
this.bulkOperationContext = bulkOperationContext;
|
||||
this.exceptionTranslator = new MongoExceptionTranslator();
|
||||
this.bulkOptions = getBulkWriteOptions(bulkOperationContext.getBulkMode());
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the {@link PersistenceExceptionTranslator} to be used. Defaults to {@link MongoExceptionTranslator}.
|
||||
*
|
||||
* @param exceptionTranslator can be {@literal null}.
|
||||
*/
|
||||
public void setExceptionTranslator(@Nullable PersistenceExceptionTranslator exceptionTranslator) {
|
||||
this.exceptionTranslator = exceptionTranslator == null ? new MongoExceptionTranslator() : exceptionTranslator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the default {@link WriteConcern} to be used. Defaults to {@literal null}.
|
||||
*
|
||||
@@ -306,26 +316,11 @@ class DefaultBulkOperations implements BulkOperations {
|
||||
collection = collection.withWriteConcern(defaultWriteConcern);
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
return collection.bulkWrite( //
|
||||
models.stream() //
|
||||
.map(this::extractAndMapWriteModel) //
|
||||
.collect(Collectors.toList()), //
|
||||
bulkOptions);
|
||||
} catch (RuntimeException ex) {
|
||||
|
||||
if (ex instanceof MongoBulkWriteException) {
|
||||
|
||||
MongoBulkWriteException mongoBulkWriteException = (MongoBulkWriteException) ex;
|
||||
if (mongoBulkWriteException.getWriteConcernError() != null) {
|
||||
throw new DataIntegrityViolationException(ex.getMessage(), ex);
|
||||
}
|
||||
throw new BulkOperationException(ex.getMessage(), mongoBulkWriteException);
|
||||
}
|
||||
|
||||
throw ex;
|
||||
}
|
||||
return collection.bulkWrite( //
|
||||
models.stream() //
|
||||
.map(this::extractAndMapWriteModel) //
|
||||
.collect(Collectors.toList()), //
|
||||
bulkOptions);
|
||||
}
|
||||
|
||||
private WriteModel<Document> extractAndMapWriteModel(SourceAwareWriteModelHolder it) {
|
||||
@@ -552,93 +547,15 @@ class DefaultBulkOperations implements BulkOperations {
|
||||
* @author Christoph Strobl
|
||||
* @since 2.0
|
||||
*/
|
||||
static final class BulkOperationContext {
|
||||
@Value
|
||||
static class BulkOperationContext {
|
||||
|
||||
private final BulkMode bulkMode;
|
||||
private final Optional<? extends MongoPersistentEntity<?>> entity;
|
||||
private final QueryMapper queryMapper;
|
||||
private final UpdateMapper updateMapper;
|
||||
private final ApplicationEventPublisher eventPublisher;
|
||||
private final EntityCallbacks entityCallbacks;
|
||||
|
||||
BulkOperationContext(BulkOperations.BulkMode bulkMode, Optional<? extends MongoPersistentEntity<?>> entity,
|
||||
QueryMapper queryMapper, UpdateMapper updateMapper, ApplicationEventPublisher eventPublisher,
|
||||
EntityCallbacks entityCallbacks) {
|
||||
|
||||
this.bulkMode = bulkMode;
|
||||
this.entity = entity;
|
||||
this.queryMapper = queryMapper;
|
||||
this.updateMapper = updateMapper;
|
||||
this.eventPublisher = eventPublisher;
|
||||
this.entityCallbacks = entityCallbacks;
|
||||
}
|
||||
|
||||
public BulkMode getBulkMode() {
|
||||
return this.bulkMode;
|
||||
}
|
||||
|
||||
public Optional<? extends MongoPersistentEntity<?>> getEntity() {
|
||||
return this.entity;
|
||||
}
|
||||
|
||||
public QueryMapper getQueryMapper() {
|
||||
return this.queryMapper;
|
||||
}
|
||||
|
||||
public UpdateMapper getUpdateMapper() {
|
||||
return this.updateMapper;
|
||||
}
|
||||
|
||||
public ApplicationEventPublisher getEventPublisher() {
|
||||
return this.eventPublisher;
|
||||
}
|
||||
|
||||
public EntityCallbacks getEntityCallbacks() {
|
||||
return this.entityCallbacks;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o)
|
||||
return true;
|
||||
if (o == null || getClass() != o.getClass())
|
||||
return false;
|
||||
|
||||
BulkOperationContext that = (BulkOperationContext) o;
|
||||
|
||||
if (bulkMode != that.bulkMode)
|
||||
return false;
|
||||
if (!ObjectUtils.nullSafeEquals(this.entity, that.entity)) {
|
||||
return false;
|
||||
}
|
||||
if (!ObjectUtils.nullSafeEquals(this.queryMapper, that.queryMapper)) {
|
||||
return false;
|
||||
}
|
||||
if (!ObjectUtils.nullSafeEquals(this.updateMapper, that.updateMapper)) {
|
||||
return false;
|
||||
}
|
||||
if (!ObjectUtils.nullSafeEquals(this.eventPublisher, that.eventPublisher)) {
|
||||
return false;
|
||||
}
|
||||
return ObjectUtils.nullSafeEquals(this.entityCallbacks, that.entityCallbacks);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = bulkMode != null ? bulkMode.hashCode() : 0;
|
||||
result = 31 * result + ObjectUtils.nullSafeHashCode(entity);
|
||||
result = 31 * result + ObjectUtils.nullSafeHashCode(queryMapper);
|
||||
result = 31 * result + ObjectUtils.nullSafeHashCode(updateMapper);
|
||||
result = 31 * result + ObjectUtils.nullSafeHashCode(eventPublisher);
|
||||
result = 31 * result + ObjectUtils.nullSafeHashCode(entityCallbacks);
|
||||
return result;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "DefaultBulkOperations.BulkOperationContext(bulkMode=" + this.getBulkMode() + ", entity="
|
||||
+ this.getEntity() + ", queryMapper=" + this.getQueryMapper() + ", updateMapper=" + this.getUpdateMapper()
|
||||
+ ", eventPublisher=" + this.getEventPublisher() + ", entityCallbacks=" + this.getEntityCallbacks() + ")";
|
||||
}
|
||||
@NonNull BulkMode bulkMode;
|
||||
@NonNull Optional<? extends MongoPersistentEntity<?>> entity;
|
||||
@NonNull QueryMapper queryMapper;
|
||||
@NonNull UpdateMapper updateMapper;
|
||||
ApplicationEventPublisher eventPublisher;
|
||||
EntityCallbacks entityCallbacks;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -647,50 +564,10 @@ class DefaultBulkOperations implements BulkOperations {
|
||||
* @since 2.2
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
private static final class SourceAwareWriteModelHolder {
|
||||
@Value
|
||||
private static class SourceAwareWriteModelHolder {
|
||||
|
||||
private final Object source;
|
||||
private final WriteModel<Document> model;
|
||||
|
||||
SourceAwareWriteModelHolder(Object source, WriteModel<Document> model) {
|
||||
|
||||
this.source = source;
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
public Object getSource() {
|
||||
return this.source;
|
||||
}
|
||||
|
||||
public WriteModel<Document> getModel() {
|
||||
return this.model;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o)
|
||||
return true;
|
||||
if (o == null || getClass() != o.getClass())
|
||||
return false;
|
||||
|
||||
SourceAwareWriteModelHolder that = (SourceAwareWriteModelHolder) o;
|
||||
|
||||
if (!ObjectUtils.nullSafeEquals(this.source, that.source)) {
|
||||
return false;
|
||||
}
|
||||
return ObjectUtils.nullSafeEquals(this.model, that.model);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = ObjectUtils.nullSafeHashCode(model);
|
||||
result = 31 * result + ObjectUtils.nullSafeHashCode(source);
|
||||
return result;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "DefaultBulkOperations.SourceAwareWriteModelHolder(source=" + this.getSource() + ", model="
|
||||
+ this.getModel() + ")";
|
||||
}
|
||||
Object source;
|
||||
WriteModel<Document> model;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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, Class<?> type) {
|
||||
return new DefaultIndexOperations(mongoDbFactory, collectionName, mapper, type);
|
||||
public IndexOperations indexOps(String collectionName) {
|
||||
return new DefaultIndexOperations(mongoDbFactory, collectionName, mapper);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,8 +15,11 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
@@ -29,23 +32,18 @@ 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.
|
||||
@@ -57,15 +55,12 @@ import org.springframework.util.StringUtils;
|
||||
* @see MongoTemplate
|
||||
* @see ReactiveMongoTemplate
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
class EntityOperations {
|
||||
|
||||
private static final String ID_FIELD = "_id";
|
||||
|
||||
private final MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> context;
|
||||
|
||||
EntityOperations(MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> context) {
|
||||
this.context = context;
|
||||
}
|
||||
private final @NonNull MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> context;
|
||||
|
||||
/**
|
||||
* Creates a new {@link Entity} for the given bean.
|
||||
@@ -74,7 +69,7 @@ class EntityOperations {
|
||||
* @return new instance of {@link Entity}.
|
||||
*/
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
<T> Entity<T> forEntity(T entity) {
|
||||
public <T> Entity<T> forEntity(T entity) {
|
||||
|
||||
Assert.notNull(entity, "Bean must not be null!");
|
||||
|
||||
@@ -97,7 +92,7 @@ class EntityOperations {
|
||||
* @return new instance of {@link AdaptibleEntity}.
|
||||
*/
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
<T> AdaptibleEntity<T> forEntity(T entity, ConversionService conversionService) {
|
||||
public <T> AdaptibleEntity<T> forEntity(T entity, ConversionService conversionService) {
|
||||
|
||||
Assert.notNull(entity, "Bean must not be null!");
|
||||
Assert.notNull(conversionService, "ConversionService must not be null!");
|
||||
@@ -113,20 +108,6 @@ 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}.
|
||||
@@ -365,14 +346,11 @@ class EntityOperations {
|
||||
Number getVersion();
|
||||
}
|
||||
|
||||
@RequiredArgsConstructor
|
||||
private static class UnmappedEntity<T extends Map<String, Object>> implements AdaptibleEntity<T> {
|
||||
|
||||
private final T map;
|
||||
|
||||
protected UnmappedEntity(T map) {
|
||||
this.map = map;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.EntityOperations.PersistableSource#getIdPropertyName()
|
||||
@@ -482,7 +460,7 @@ class EntityOperations {
|
||||
|
||||
private static class SimpleMappedEntity<T extends Map<String, Object>> extends UnmappedEntity<T> {
|
||||
|
||||
protected SimpleMappedEntity(T map) {
|
||||
SimpleMappedEntity(T map) {
|
||||
super(map);
|
||||
}
|
||||
|
||||
@@ -505,19 +483,12 @@ class EntityOperations {
|
||||
}
|
||||
}
|
||||
|
||||
@RequiredArgsConstructor(access = AccessLevel.PROTECTED)
|
||||
private static class MappedEntity<T> implements Entity<T> {
|
||||
|
||||
private final MongoPersistentEntity<?> entity;
|
||||
private final IdentifierAccessor idAccessor;
|
||||
private final PersistentPropertyAccessor<T> propertyAccessor;
|
||||
|
||||
protected MappedEntity(MongoPersistentEntity<?> entity, IdentifierAccessor idAccessor,
|
||||
PersistentPropertyAccessor<T> propertyAccessor) {
|
||||
|
||||
this.entity = entity;
|
||||
this.idAccessor = idAccessor;
|
||||
this.propertyAccessor = propertyAccessor;
|
||||
}
|
||||
private final @NonNull MongoPersistentEntity<?> entity;
|
||||
private final @NonNull IdentifierAccessor idAccessor;
|
||||
private final @NonNull PersistentPropertyAccessor<T> propertyAccessor;
|
||||
|
||||
private static <T> MappedEntity<T> of(T bean,
|
||||
MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> context) {
|
||||
@@ -782,36 +753,17 @@ 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link TypedOperations} for generic entities that are not represented with {@link PersistentEntity} (e.g. custom
|
||||
* conversions).
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
enum UntypedOperations implements TypedOperations<Object> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
UntypedOperations() {}
|
||||
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
public static <T> TypedOperations<T> instance() {
|
||||
return (TypedOperations) INSTANCE;
|
||||
@@ -839,16 +791,6 @@ class EntityOperations {
|
||||
|
||||
return query.getCollation();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CollectionOptions getCollectionOptions() {
|
||||
return CollectionOptions.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TimeSeriesOptions mapTimeSeriesOptions(TimeSeriesOptions options) {
|
||||
return options;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -856,13 +798,10 @@ class EntityOperations {
|
||||
*
|
||||
* @param <T>
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
static class TypedEntityOperations<T> implements TypedOperations<T> {
|
||||
|
||||
private final MongoPersistentEntity<T> entity;
|
||||
|
||||
protected TypedEntityOperations(MongoPersistentEntity<T> entity) {
|
||||
this.entity = entity;
|
||||
}
|
||||
private final @NonNull MongoPersistentEntity<T> entity;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
@@ -886,58 +825,6 @@ 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -15,10 +15,16 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.experimental.FieldDefaults;
|
||||
|
||||
import org.springframework.data.mongodb.core.aggregation.Aggregation;
|
||||
import org.springframework.data.mongodb.core.aggregation.AggregationResults;
|
||||
import org.springframework.data.mongodb.core.aggregation.TypedAggregation;
|
||||
import org.springframework.data.util.CloseableIterator;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
@@ -29,13 +35,10 @@ import org.springframework.util.StringUtils;
|
||||
* @author Mark Paluch
|
||||
* @since 2.0
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
class ExecutableAggregationOperationSupport implements ExecutableAggregationOperation {
|
||||
|
||||
private final MongoTemplate template;
|
||||
|
||||
ExecutableAggregationOperationSupport(MongoTemplate template) {
|
||||
this.template = template;
|
||||
}
|
||||
private final @NonNull MongoTemplate template;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
@@ -53,21 +56,15 @@ class ExecutableAggregationOperationSupport implements ExecutableAggregationOper
|
||||
* @author Christoph Strobl
|
||||
* @since 2.0
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
|
||||
static class ExecutableAggregationSupport<T>
|
||||
implements AggregationWithAggregation<T>, ExecutableAggregation<T>, TerminatingAggregation<T> {
|
||||
|
||||
private final MongoTemplate template;
|
||||
private final Class<T> domainType;
|
||||
private final Aggregation aggregation;
|
||||
private final String collection;
|
||||
|
||||
public ExecutableAggregationSupport(MongoTemplate template, Class<T> domainType, Aggregation aggregation,
|
||||
String collection) {
|
||||
this.template = template;
|
||||
this.domainType = domainType;
|
||||
this.aggregation = aggregation;
|
||||
this.collection = collection;
|
||||
}
|
||||
@NonNull MongoTemplate template;
|
||||
@NonNull Class<T> domainType;
|
||||
@Nullable Aggregation aggregation;
|
||||
@Nullable String collection;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
|
||||
@@ -125,11 +125,6 @@ public interface ExecutableFindOperation {
|
||||
|
||||
/**
|
||||
* Get the number of matching elements.
|
||||
* <p />
|
||||
* 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.
|
||||
*/
|
||||
|
||||
@@ -15,6 +15,12 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core;
|
||||
|
||||
import com.mongodb.ReadPreference;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.experimental.FieldDefaults;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
@@ -31,7 +37,6 @@ import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.mongodb.ReadPreference;
|
||||
import com.mongodb.client.FindIterable;
|
||||
|
||||
/**
|
||||
@@ -41,15 +46,12 @@ import com.mongodb.client.FindIterable;
|
||||
* @author Mark Paluch
|
||||
* @since 2.0
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
class ExecutableFindOperationSupport implements ExecutableFindOperation {
|
||||
|
||||
private static final Query ALL_QUERY = new Query();
|
||||
|
||||
private final MongoTemplate template;
|
||||
|
||||
ExecutableFindOperationSupport(MongoTemplate template) {
|
||||
this.template = template;
|
||||
}
|
||||
private final @NonNull MongoTemplate template;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
@@ -68,23 +70,16 @@ class ExecutableFindOperationSupport implements ExecutableFindOperation {
|
||||
* @author Christoph Strobl
|
||||
* @since 2.0
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
|
||||
static class ExecutableFindSupport<T>
|
||||
implements ExecutableFind<T>, FindWithCollection<T>, FindWithProjection<T>, FindWithQuery<T> {
|
||||
|
||||
private final MongoTemplate template;
|
||||
private final Class<?> domainType;
|
||||
private final Class<T> returnType;
|
||||
@Nullable private final String collection;
|
||||
private final Query query;
|
||||
|
||||
ExecutableFindSupport(MongoTemplate template, Class<?> domainType, Class<T> returnType,
|
||||
String collection, Query query) {
|
||||
this.template = template;
|
||||
this.domainType = domainType;
|
||||
this.returnType = returnType;
|
||||
this.collection = collection;
|
||||
this.query = query;
|
||||
}
|
||||
@NonNull MongoTemplate template;
|
||||
@NonNull Class<?> domainType;
|
||||
Class<T> returnType;
|
||||
@Nullable String collection;
|
||||
Query query;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
|
||||
@@ -15,6 +15,11 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.experimental.FieldDefaults;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
|
||||
@@ -32,13 +37,10 @@ import com.mongodb.bulk.BulkWriteResult;
|
||||
* @author Mark Paluch
|
||||
* @since 2.0
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
class ExecutableInsertOperationSupport implements ExecutableInsertOperation {
|
||||
|
||||
private final MongoTemplate template;
|
||||
|
||||
ExecutableInsertOperationSupport(MongoTemplate template) {
|
||||
this.template = template;
|
||||
}
|
||||
private final @NonNull MongoTemplate template;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
@@ -56,20 +58,14 @@ class ExecutableInsertOperationSupport implements ExecutableInsertOperation {
|
||||
* @author Christoph Strobl
|
||||
* @since 2.0
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
|
||||
static class ExecutableInsertSupport<T> implements ExecutableInsert<T> {
|
||||
|
||||
private final MongoTemplate template;
|
||||
private final Class<T> domainType;
|
||||
@Nullable private final String collection;
|
||||
@Nullable private final BulkMode bulkMode;
|
||||
|
||||
ExecutableInsertSupport(MongoTemplate template, Class<T> domainType, String collection, BulkMode bulkMode) {
|
||||
|
||||
this.template = template;
|
||||
this.domainType = domainType;
|
||||
this.collection = collection;
|
||||
this.bulkMode = bulkMode;
|
||||
}
|
||||
@NonNull MongoTemplate template;
|
||||
@NonNull Class<T> domainType;
|
||||
@Nullable String collection;
|
||||
@Nullable BulkMode bulkMode;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
|
||||
@@ -15,6 +15,9 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.mongodb.core.mapreduce.MapReduceOptions;
|
||||
@@ -29,17 +32,12 @@ import org.springframework.util.StringUtils;
|
||||
* @author Christoph Strobl
|
||||
* @since 2.1
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
class ExecutableMapReduceOperationSupport implements ExecutableMapReduceOperation {
|
||||
|
||||
private static final Query ALL_QUERY = new Query();
|
||||
|
||||
private final MongoTemplate template;
|
||||
|
||||
ExecutableMapReduceOperationSupport(MongoTemplate template) {
|
||||
|
||||
Assert.notNull(template, "Template must not be null!");
|
||||
this.template = template;
|
||||
}
|
||||
private final @NonNull MongoTemplate template;
|
||||
|
||||
/*
|
||||
* (non-Javascript)
|
||||
|
||||
@@ -15,6 +15,11 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.experimental.FieldDefaults;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
@@ -31,15 +36,12 @@ import com.mongodb.client.result.DeleteResult;
|
||||
* @author Mark Paluch
|
||||
* @since 2.0
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
class ExecutableRemoveOperationSupport implements ExecutableRemoveOperation {
|
||||
|
||||
private static final Query ALL_QUERY = new Query();
|
||||
|
||||
private final MongoTemplate tempate;
|
||||
|
||||
public ExecutableRemoveOperationSupport(MongoTemplate tempate) {
|
||||
this.tempate = tempate;
|
||||
}
|
||||
private final @NonNull MongoTemplate tempate;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
@@ -57,19 +59,14 @@ class ExecutableRemoveOperationSupport implements ExecutableRemoveOperation {
|
||||
* @author Christoph Strobl
|
||||
* @since 2.0
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
|
||||
static class ExecutableRemoveSupport<T> implements ExecutableRemove<T>, RemoveWithCollection<T> {
|
||||
|
||||
private final MongoTemplate template;
|
||||
private final Class<T> domainType;
|
||||
private final Query query;
|
||||
@Nullable private final String collection;
|
||||
|
||||
public ExecutableRemoveSupport(MongoTemplate template, Class<T> domainType, Query query, String collection) {
|
||||
this.template = template;
|
||||
this.domainType = domainType;
|
||||
this.query = query;
|
||||
this.collection = collection;
|
||||
}
|
||||
@NonNull MongoTemplate template;
|
||||
@NonNull Class<T> domainType;
|
||||
Query query;
|
||||
@Nullable String collection;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
|
||||
@@ -15,6 +15,11 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.experimental.FieldDefaults;
|
||||
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
import org.springframework.data.mongodb.core.query.UpdateDefinition;
|
||||
import org.springframework.lang.Nullable;
|
||||
@@ -30,15 +35,12 @@ import com.mongodb.client.result.UpdateResult;
|
||||
* @author Mark Paluch
|
||||
* @since 2.0
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
class ExecutableUpdateOperationSupport implements ExecutableUpdateOperation {
|
||||
|
||||
private static final Query ALL_QUERY = new Query();
|
||||
|
||||
private final MongoTemplate template;
|
||||
|
||||
ExecutableUpdateOperationSupport(MongoTemplate template) {
|
||||
this.template = template;
|
||||
}
|
||||
private final @NonNull MongoTemplate template;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
@@ -56,34 +58,21 @@ class ExecutableUpdateOperationSupport implements ExecutableUpdateOperation {
|
||||
* @author Christoph Strobl
|
||||
* @since 2.0
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
|
||||
static class ExecutableUpdateSupport<T>
|
||||
implements ExecutableUpdate<T>, UpdateWithCollection<T>, UpdateWithQuery<T>, TerminatingUpdate<T>,
|
||||
FindAndReplaceWithOptions<T>, TerminatingFindAndReplace<T>, FindAndReplaceWithProjection<T> {
|
||||
|
||||
private final MongoTemplate template;
|
||||
private final Class domainType;
|
||||
private final Query query;
|
||||
@Nullable private final UpdateDefinition update;
|
||||
@Nullable private final String collection;
|
||||
@Nullable private final FindAndModifyOptions findAndModifyOptions;
|
||||
@Nullable private final FindAndReplaceOptions findAndReplaceOptions;
|
||||
@Nullable private final Object replacement;
|
||||
private final Class<T> targetType;
|
||||
|
||||
ExecutableUpdateSupport(MongoTemplate template, Class domainType, Query query, UpdateDefinition update,
|
||||
String collection, FindAndModifyOptions findAndModifyOptions, FindAndReplaceOptions findAndReplaceOptions,
|
||||
Object replacement, Class<T> targetType) {
|
||||
|
||||
this.template = template;
|
||||
this.domainType = domainType;
|
||||
this.query = query;
|
||||
this.update = update;
|
||||
this.collection = collection;
|
||||
this.findAndModifyOptions = findAndModifyOptions;
|
||||
this.findAndReplaceOptions = findAndReplaceOptions;
|
||||
this.replacement = replacement;
|
||||
this.targetType = targetType;
|
||||
}
|
||||
@NonNull MongoTemplate template;
|
||||
@NonNull Class domainType;
|
||||
Query query;
|
||||
@Nullable UpdateDefinition update;
|
||||
@Nullable String collection;
|
||||
@Nullable FindAndModifyOptions findAndModifyOptions;
|
||||
@Nullable FindAndReplaceOptions findAndReplaceOptions;
|
||||
@Nullable Object replacement;
|
||||
@NonNull Class<T> targetType;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
|
||||
@@ -115,10 +115,6 @@ 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;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -15,6 +15,9 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
@@ -24,6 +27,8 @@ import org.springframework.data.mongodb.core.query.Update;
|
||||
import org.springframework.data.mongodb.core.query.UpdateDefinition;
|
||||
import org.springframework.data.util.StreamUtils;
|
||||
|
||||
import com.mongodb.client.model.Filters;
|
||||
|
||||
/**
|
||||
* A MongoDB document in its mapped state. I.e. after a source document has been mapped using mapping information of the
|
||||
* entity the source document was supposed to represent.
|
||||
@@ -31,20 +36,13 @@ import org.springframework.data.util.StreamUtils;
|
||||
* @author Oliver Gierke
|
||||
* @since 2.1
|
||||
*/
|
||||
@RequiredArgsConstructor(staticName = "of")
|
||||
public class MappedDocument {
|
||||
|
||||
private static final String ID_FIELD = "_id";
|
||||
private static final Document ID_ONLY_PROJECTION = new Document(ID_FIELD, 1);
|
||||
|
||||
private final Document document;
|
||||
|
||||
private MappedDocument(Document document) {
|
||||
this.document = document;
|
||||
}
|
||||
|
||||
public static MappedDocument of(Document document) {
|
||||
return new MappedDocument(document);
|
||||
}
|
||||
private final @Getter Document document;
|
||||
|
||||
public static Document getIdOnlyProjection() {
|
||||
return ID_ONLY_PROJECTION;
|
||||
@@ -93,10 +91,6 @@ public class MappedDocument {
|
||||
return new MappedUpdate(Update.fromDocument(document, ID_FIELD));
|
||||
}
|
||||
|
||||
public Document getDocument() {
|
||||
return this.document;
|
||||
}
|
||||
|
||||
/**
|
||||
* An {@link UpdateDefinition} that indicates that the {@link #getUpdateObject() update object} has already been
|
||||
* mapped to the specific domain type.
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core;
|
||||
|
||||
import lombok.Value;
|
||||
|
||||
import org.springframework.aop.framework.ProxyFactory;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.dao.support.PersistenceExceptionTranslator;
|
||||
@@ -22,7 +24,6 @@ import org.springframework.data.mongodb.MongoDatabaseFactory;
|
||||
import org.springframework.data.mongodb.SessionAwareMethodInterceptor;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
import com.mongodb.ClientSessionOptions;
|
||||
import com.mongodb.WriteConcern;
|
||||
@@ -170,15 +171,11 @@ public abstract class MongoDatabaseFactorySupport<C> implements MongoDatabaseFac
|
||||
* @author Christoph Strobl
|
||||
* @since 2.1
|
||||
*/
|
||||
static final class ClientSessionBoundMongoDbFactory implements MongoDatabaseFactory {
|
||||
@Value
|
||||
static class ClientSessionBoundMongoDbFactory implements MongoDatabaseFactory {
|
||||
|
||||
private final ClientSession session;
|
||||
private final MongoDatabaseFactory delegate;
|
||||
|
||||
public ClientSessionBoundMongoDbFactory(ClientSession session, MongoDatabaseFactory delegate) {
|
||||
this.session = session;
|
||||
this.delegate = delegate;
|
||||
}
|
||||
ClientSession session;
|
||||
MongoDatabaseFactory delegate;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
@@ -259,40 +256,5 @@ public abstract class MongoDatabaseFactorySupport<C> implements MongoDatabaseFac
|
||||
|
||||
return targetType.cast(factory.getProxy(target.getClass().getClassLoader()));
|
||||
}
|
||||
|
||||
public ClientSession getSession() {
|
||||
return this.session;
|
||||
}
|
||||
|
||||
public MongoDatabaseFactory getDelegate() {
|
||||
return this.delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o)
|
||||
return true;
|
||||
if (o == null || getClass() != o.getClass())
|
||||
return false;
|
||||
|
||||
ClientSessionBoundMongoDbFactory that = (ClientSessionBoundMongoDbFactory) o;
|
||||
|
||||
if (!ObjectUtils.nullSafeEquals(this.session, that.session)) {
|
||||
return false;
|
||||
}
|
||||
return ObjectUtils.nullSafeEquals(this.delegate, that.delegate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = ObjectUtils.nullSafeHashCode(this.session);
|
||||
result = 31 * result + ObjectUtils.nullSafeHashCode(this.delegate);
|
||||
return result;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "MongoDatabaseFactorySupport.ClientSessionBoundMongoDbFactory(session=" + this.getSession() + ", delegate="
|
||||
+ this.getDelegate() + ")";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1160,12 +1160,6 @@ 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.
|
||||
* <p />
|
||||
* 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}.
|
||||
@@ -1182,12 +1176,6 @@ 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.
|
||||
* <p />
|
||||
* 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.
|
||||
@@ -1196,35 +1184,6 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
*/
|
||||
long count(Query query, String collectionName);
|
||||
|
||||
/**
|
||||
* Estimate the number of documents, in the collection {@link #getCollectionName(Class) identified by the given type},
|
||||
* based on collection statistics.
|
||||
* <p />
|
||||
* 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.
|
||||
* @since 3.1
|
||||
*/
|
||||
default long estimatedCount(Class<?> entityClass) {
|
||||
|
||||
Assert.notNull(entityClass, "Entity class must not be null!");
|
||||
return estimatedCount(getCollectionName(entityClass));
|
||||
}
|
||||
|
||||
/**
|
||||
* Estimate the number of documents in the given collection based on collection statistics.
|
||||
* <p />
|
||||
* 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.
|
||||
* @since 3.1
|
||||
*/
|
||||
long estimatedCount(String collectionName);
|
||||
|
||||
/**
|
||||
* Returns the number of documents for the given {@link Query} by querying the given collection using the given entity
|
||||
* class to map the given {@link Query}. <br />
|
||||
@@ -1232,12 +1191,6 @@ 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.
|
||||
* <p />
|
||||
* 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}.
|
||||
@@ -1258,13 +1211,11 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
* <a href="https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#validation" > Spring's
|
||||
* Type Conversion"</a> for more details.
|
||||
* <p/>
|
||||
* Insert is used to initially store the object into the database. To update an existing object use the save method.
|
||||
* <p/>
|
||||
* The {@code objectToSave} must not be collection-like.
|
||||
* Insert is used to initially store the object into the database. To update an existing object use the save method.
|
||||
*
|
||||
* @param objectToSave the object to store in the collection. Must not be {@literal null}.
|
||||
* @return the inserted object.
|
||||
* @throws IllegalArgumentException in case the {@code objectToSave} is collection-like.
|
||||
*/
|
||||
<T> T insert(T objectToSave);
|
||||
|
||||
@@ -1275,13 +1226,10 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
* configured otherwise, an instance of {@link MappingMongoConverter} will be used.
|
||||
* <p/>
|
||||
* Insert is used to initially store the object into the database. To update an existing object use the save method.
|
||||
* <p/>
|
||||
* 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);
|
||||
|
||||
@@ -1324,12 +1272,9 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
* 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 />
|
||||
* 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);
|
||||
|
||||
@@ -1345,13 +1290,10 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
* 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.
|
||||
* <p />
|
||||
* 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);
|
||||
|
||||
|
||||
@@ -17,6 +17,11 @@ package org.springframework.data.mongodb.core;
|
||||
|
||||
import static org.springframework.data.mongodb.core.query.SerializationUtils.*;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
@@ -47,7 +52,6 @@ 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;
|
||||
@@ -56,7 +60,6 @@ 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;
|
||||
@@ -99,12 +102,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,14 +160,22 @@ import com.mongodb.client.result.UpdateResult;
|
||||
* @author Cimon Lucas
|
||||
* @author Michael J. Simons
|
||||
* @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;
|
||||
@@ -598,7 +609,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, operations.forType(entityClass).getCollectionOptions());
|
||||
return createCollection(entityClass, CollectionOptions.empty());
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -707,17 +718,12 @@ 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, @Nullable Class<?> type) {
|
||||
return new DefaultIndexOperations(this, collectionName, type);
|
||||
public IndexOperations indexOps(String collectionName) {
|
||||
return new DefaultIndexOperations(this, collectionName, null);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -725,7 +731,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
* @see org.springframework.data.mongodb.core.ExecutableInsertOperation#indexOps(java.lang.Class)
|
||||
*/
|
||||
public IndexOperations indexOps(Class<?> entityClass) {
|
||||
return indexOps(getCollectionName(entityClass), entityClass);
|
||||
return new DefaultIndexOperations(this, getCollectionName(entityClass), entityClass);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -757,6 +763,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
new BulkOperationContext(mode, Optional.ofNullable(getPersistentEntity(entityType)), queryMapper, updateMapper,
|
||||
eventPublisher, entityCallbacks));
|
||||
|
||||
operations.setExceptionTranslator(exceptionTranslator);
|
||||
operations.setDefaultWriteConcern(writeConcern);
|
||||
|
||||
return operations;
|
||||
@@ -974,7 +981,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
for (Document element : results) {
|
||||
|
||||
GeoResult<T> geoResult = callback.doWith(element);
|
||||
aggregate = aggregate.add(BigDecimal.valueOf(geoResult.getDistance().getValue()));
|
||||
aggregate = aggregate.add(new BigDecimal(geoResult.getDistance().getValue()));
|
||||
result.add(geoResult);
|
||||
}
|
||||
|
||||
@@ -1133,19 +1140,6 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
collection -> collection.countDocuments(CountQuery.of(filter).toQueryDocument(), options));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.MongoOperations#estimatedCount(java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public long estimatedCount(String collectionName) {
|
||||
return doEstimatedCount(collectionName, new EstimatedDocumentCountOptions());
|
||||
}
|
||||
|
||||
protected long doEstimatedCount(String collectionName, EstimatedDocumentCountOptions options) {
|
||||
return execute(collectionName, collection -> collection.estimatedDocumentCount(options));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.MongoOperations#insert(java.lang.Object)
|
||||
@@ -1174,28 +1168,11 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
return (T) doInsert(collectionName, objectToSave, this.mongoConverter);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.");
|
||||
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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1378,7 +1355,6 @@ 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());
|
||||
|
||||
@@ -1993,7 +1969,9 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
|
||||
Assert.notNull(aggregation, "Aggregation pipeline must not be null!");
|
||||
|
||||
return aggregate(aggregation, inputCollectionName, outputType, null);
|
||||
AggregationOperationContext context = new TypeBasedAggregationOperationContext(aggregation.getInputType(),
|
||||
mappingContext, queryMapper);
|
||||
return aggregate(aggregation, inputCollectionName, outputType, context);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
@@ -2003,7 +1981,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,
|
||||
queryOperations.createAggregation(aggregation, inputType).getAggregationOperationContext());
|
||||
new TypeBasedAggregationOperationContext(inputType, mappingContext, queryMapper));
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
@@ -2110,13 +2088,9 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
Assert.notNull(aggregation, "Aggregation pipeline must not be null!");
|
||||
Assert.notNull(outputType, "Output type must not be null!");
|
||||
|
||||
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());
|
||||
AggregationOperationContext contextToUse = new AggregationUtil(queryMapper, mappingContext)
|
||||
.prepareAggregationContext(aggregation, context);
|
||||
return doAggregate(aggregation, collectionName, outputType, contextToUse);
|
||||
}
|
||||
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
@@ -2167,7 +2141,6 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
}
|
||||
|
||||
options.getComment().ifPresent(aggregateIterable::comment);
|
||||
options.getHint().ifPresent(aggregateIterable::hint);
|
||||
|
||||
if (options.hasExecutionTimeLimit()) {
|
||||
aggregateIterable = aggregateIterable.maxTime(options.getMaxTime().toMillis(), TimeUnit.MILLISECONDS);
|
||||
@@ -2204,10 +2177,11 @@ 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!");
|
||||
|
||||
AggregationDefinition aggregationDefinition = queryOperations.createAggregation(aggregation, context);
|
||||
AggregationUtil aggregationUtil = new AggregationUtil(queryMapper, mappingContext);
|
||||
AggregationOperationContext rootContext = aggregationUtil.prepareAggregationContext(aggregation, context);
|
||||
|
||||
AggregationOptions options = aggregation.getOptions();
|
||||
List<Document> pipeline = aggregationDefinition.getAggregationPipeline();
|
||||
List<Document> pipeline = aggregationUtil.createPipeline(aggregation, rootContext);
|
||||
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("Streaming aggregation: {} in collection {}", serializeToJsonSafely(pipeline), collectionName);
|
||||
@@ -2225,7 +2199,6 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
}
|
||||
|
||||
options.getComment().ifPresent(cursor::comment);
|
||||
options.getHint().ifPresent(cursor::hint);
|
||||
|
||||
Class<?> domainType = aggregation instanceof TypedAggregation ? ((TypedAggregation) aggregation).getInputType()
|
||||
: null;
|
||||
@@ -2436,20 +2409,6 @@ 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);
|
||||
@@ -2604,19 +2563,6 @@ 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;
|
||||
@@ -2780,24 +2726,25 @@ 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 CollectionCallback} for a {@link Document}.</li>
|
||||
* <li>Execute the given {@link ConnectionCallback} 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 documentCallback the {@link DocumentCallback} to transform {@link Document}s into the actual domain type
|
||||
* @param objectCallback 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> documentCallback, String collectionName) {
|
||||
DocumentCallback<T> objectCallback, String collectionName) {
|
||||
|
||||
try {
|
||||
|
||||
Document document = collectionCallback.doInCollection(getAndPrepareCollection(doGetDatabase(), collectionName));
|
||||
return document != null ? documentCallback.doWith(document) : null;
|
||||
T result = objectCallback
|
||||
.doWith(collectionCallback.doInCollection(getAndPrepareCollection(doGetDatabase(), collectionName)));
|
||||
return result;
|
||||
} catch (RuntimeException e) {
|
||||
throw potentiallyConvertRuntimeException(e, exceptionTranslator);
|
||||
}
|
||||
@@ -2807,7 +2754,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 CollectionCallback} for a {@link FindIterable}.</li>
|
||||
* <li>Execute the given {@link ConnectionCallback} 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
|
||||
@@ -2817,27 +2764,36 @@ 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 documentCallback the {@link DocumentCallback} to transform {@link Document}s into the actual domain type
|
||||
* @param objectCallback 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> documentCallback, String collectionName) {
|
||||
CursorPreparer preparer, DocumentCallback<T> objectCallback, 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();
|
||||
|
||||
List<T> result = new ArrayList<>();
|
||||
|
||||
while (cursor.hasNext()) {
|
||||
Document object = cursor.next();
|
||||
result.add(documentCallback.doWith(object));
|
||||
result.add(objectCallback.doWith(object));
|
||||
}
|
||||
|
||||
return result;
|
||||
} finally {
|
||||
|
||||
if (cursor != null) {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
throw potentiallyConvertRuntimeException(e, exceptionTranslator);
|
||||
@@ -2847,12 +2803,23 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
private void executeQueryInternal(CollectionCallback<FindIterable<Document>> collectionCallback,
|
||||
CursorPreparer preparer, DocumentCallbackHandler callbackHandler, String collectionName) {
|
||||
|
||||
try (MongoCursor<Document> cursor = preparer
|
||||
.initiateFind(getAndPrepareCollection(doGetDatabase(), collectionName), collectionCallback::doInCollection)
|
||||
.iterator()) {
|
||||
try {
|
||||
|
||||
while (cursor.hasNext()) {
|
||||
callbackHandler.processDocument(cursor.next());
|
||||
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();
|
||||
}
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
throw potentiallyConvertRuntimeException(e, exceptionTranslator);
|
||||
@@ -2994,17 +2961,12 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
* @author Christoph Strobl
|
||||
* @since 2.0
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
private class ExistsCallback implements CollectionCallback<Boolean> {
|
||||
|
||||
private final Document mappedQuery;
|
||||
private final com.mongodb.client.model.Collation collation;
|
||||
|
||||
ExistsCallback(Document mappedQuery, com.mongodb.client.model.Collation collation) {
|
||||
|
||||
this.mappedQuery = mappedQuery;
|
||||
this.collation = collation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean doInCollection(MongoCollection<Document> collection) throws MongoException, DataAccessException {
|
||||
|
||||
@@ -3026,7 +2988,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
private final Document sort;
|
||||
private final Optional<Collation> collation;
|
||||
|
||||
FindAndRemoveCallback(Document query, Document fields, Document sort, @Nullable Collation collation) {
|
||||
public FindAndRemoveCallback(Document query, Document fields, Document sort, @Nullable Collation collation) {
|
||||
|
||||
this.query = query;
|
||||
this.fields = fields;
|
||||
@@ -3052,9 +3014,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
private final List<Document> arrayFilters;
|
||||
private final FindAndModifyOptions options;
|
||||
|
||||
FindAndModifyCallback(Document query, Document fields, Document sort, Object update, List<Document> arrayFilters,
|
||||
FindAndModifyOptions options) {
|
||||
|
||||
public FindAndModifyCallback(Document query, Document fields, Document sort, Object update,
|
||||
List<Document> arrayFilters, FindAndModifyOptions options) {
|
||||
this.query = query;
|
||||
this.fields = fields;
|
||||
this.sort = sort;
|
||||
@@ -3151,7 +3112,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
|
||||
interface DocumentCallback<T> {
|
||||
|
||||
T doWith(Document object);
|
||||
@Nullable
|
||||
T doWith(@Nullable Document object);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -3162,32 +3124,29 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
* @author Christoph Strobl
|
||||
* @author Roman Puchkovskiy
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
private class ReadDocumentCallback<T> implements DocumentCallback<T> {
|
||||
|
||||
private final EntityReader<? super T, Bson> reader;
|
||||
private final Class<T> type;
|
||||
private final @NonNull EntityReader<? super T, Bson> reader;
|
||||
private final @NonNull Class<T> type;
|
||||
private final String collectionName;
|
||||
|
||||
ReadDocumentCallback(EntityReader<? super T, Bson> reader, Class<T> type, String collectionName) {
|
||||
@Nullable
|
||||
public T doWith(@Nullable Document document) {
|
||||
|
||||
this.reader = reader;
|
||||
this.type = type;
|
||||
this.collectionName = collectionName;
|
||||
}
|
||||
T source = null;
|
||||
|
||||
public T doWith(Document document) {
|
||||
|
||||
maybeEmitEvent(new AfterLoadEvent<>(document, type, collectionName));
|
||||
T entity = reader.read(type, document);
|
||||
|
||||
if (entity == null) {
|
||||
throw new MappingException(String.format("EntityReader %s returned null", reader));
|
||||
if (document != null) {
|
||||
maybeEmitEvent(new AfterLoadEvent<>(document, type, collectionName));
|
||||
source = reader.read(type, document);
|
||||
}
|
||||
|
||||
maybeEmitEvent(new AfterConvertEvent<>(document, entity, collectionName));
|
||||
entity = maybeCallAfterConvert(entity, document, collectionName);
|
||||
if (source != null) {
|
||||
maybeEmitEvent(new AfterConvertEvent<>(document, source, collectionName));
|
||||
source = maybeCallAfterConvert(source, document, collectionName);
|
||||
}
|
||||
|
||||
return entity;
|
||||
return source;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3199,28 +3158,21 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
* @param <T>
|
||||
* @since 2.0
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
private class ProjectingReadCallback<S, T> implements DocumentCallback<T> {
|
||||
|
||||
private final EntityReader<Object, Bson> reader;
|
||||
private final Class<S> entityType;
|
||||
private final Class<T> targetType;
|
||||
private final String collectionName;
|
||||
|
||||
ProjectingReadCallback(EntityReader<Object, Bson> reader, Class<S> entityType, Class<T> targetType,
|
||||
String collectionName) {
|
||||
|
||||
this.reader = reader;
|
||||
this.entityType = entityType;
|
||||
this.targetType = targetType;
|
||||
this.collectionName = collectionName;
|
||||
}
|
||||
private final @NonNull EntityReader<Object, Bson> reader;
|
||||
private final @NonNull Class<S> entityType;
|
||||
private final @NonNull Class<T> targetType;
|
||||
private final @NonNull String collectionName;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.MongoTemplate.DocumentCallback#doWith(org.bson.Document)
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public T doWith(Document document) {
|
||||
@Nullable
|
||||
public T doWith(@Nullable Document document) {
|
||||
|
||||
if (document == null) {
|
||||
return null;
|
||||
@@ -3231,16 +3183,15 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
|
||||
maybeEmitEvent(new AfterLoadEvent<>(document, targetType, collectionName));
|
||||
|
||||
Object entity = reader.read(typeToRead, document);
|
||||
Object source = reader.read(typeToRead, document);
|
||||
Object result = targetType.isInterface() ? projectionFactory.createProjection(targetType, source) : source;
|
||||
|
||||
if (entity == null) {
|
||||
throw new MappingException(String.format("EntityReader %s returned null", reader));
|
||||
if (result != null) {
|
||||
maybeEmitEvent(new AfterConvertEvent<>(document, result, collectionName));
|
||||
result = maybeCallAfterConvert(result, document, collectionName);
|
||||
}
|
||||
|
||||
Object result = targetType.isInterface() ? projectionFactory.createProjection(targetType, entity) : entity;
|
||||
|
||||
maybeEmitEvent(new AfterConvertEvent<>(document, result, collectionName));
|
||||
return (T) maybeCallAfterConvert(result, document, collectionName);
|
||||
return (T) result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3249,7 +3200,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
private final Query query;
|
||||
private final @Nullable Class<?> type;
|
||||
|
||||
QueryCursorPreparer(Query query, @Nullable Class<?> type) {
|
||||
public QueryCursorPreparer(Query query, @Nullable Class<?> type) {
|
||||
|
||||
this.query = query;
|
||||
this.type = type;
|
||||
@@ -3310,10 +3261,6 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
cursorToUse = cursorToUse.batchSize(meta.getCursorBatchSize());
|
||||
}
|
||||
|
||||
if (meta.getAllowDiskUse() != null) {
|
||||
cursorToUse = cursorToUse.allowDiskUse(meta.getAllowDiskUse());
|
||||
}
|
||||
|
||||
for (Meta.CursorOption option : meta.getFlags()) {
|
||||
|
||||
switch (option) {
|
||||
@@ -3377,7 +3324,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
this.metric = metric;
|
||||
}
|
||||
|
||||
public GeoResult<T> doWith(Document object) {
|
||||
@Nullable
|
||||
public GeoResult<T> doWith(@Nullable Document object) {
|
||||
|
||||
double distance = Double.NaN;
|
||||
if (object.containsKey(distanceField)) {
|
||||
@@ -3396,6 +3344,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
* @author Thomas Darimont
|
||||
* @since 1.7
|
||||
*/
|
||||
@AllArgsConstructor(access = AccessLevel.PACKAGE)
|
||||
static class CloseableIterableCursorAdapter<T> implements CloseableIterator<T> {
|
||||
|
||||
private volatile @Nullable MongoCursor<Document> cursor;
|
||||
@@ -3404,23 +3353,19 @@ 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) {
|
||||
public CloseableIterableCursorAdapter(MongoIterable<Document> cursor,
|
||||
PersistenceExceptionTranslator exceptionTranslator, DocumentCallback<T> objectReadCallback) {
|
||||
|
||||
this.cursor = cursor.iterator();
|
||||
this.exceptionTranslator = exceptionTranslator;
|
||||
this.objectReadCallback = objectReadCallback;
|
||||
}
|
||||
|
||||
CloseableIterableCursorAdapter(MongoCursor<Document> cursor, PersistenceExceptionTranslator exceptionTranslator,
|
||||
DocumentCallback<T> objectReadCallback) {
|
||||
|
||||
this.cursor = cursor;
|
||||
this.exceptionTranslator = exceptionTranslator;
|
||||
this.objectReadCallback = objectReadCallback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
|
||||
@@ -3447,7 +3392,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
|
||||
try {
|
||||
Document item = cursor.next();
|
||||
return objectReadCallback.doWith(item);
|
||||
T converted = objectReadCallback.doWith(item);
|
||||
return converted;
|
||||
} catch (RuntimeException ex) {
|
||||
throw potentiallyConvertRuntimeException(ex, exceptionTranslator);
|
||||
}
|
||||
@@ -3473,20 +3419,7 @@ 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;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,9 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import org.bson.Document;
|
||||
import org.springframework.data.mapping.SimplePropertyHandler;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
@@ -30,14 +33,11 @@ import org.springframework.util.ClassUtils;
|
||||
* @author Christoph Strobl
|
||||
* @since 2.1
|
||||
*/
|
||||
@RequiredArgsConstructor(access = AccessLevel.PACKAGE)
|
||||
class PropertyOperations {
|
||||
|
||||
private final MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext;
|
||||
|
||||
PropertyOperations(MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext) {
|
||||
this.mappingContext = mappingContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* For cases where {@code fields} is {@link Document#isEmpty() empty} include only fields that are required for
|
||||
* creating the projection (target) type if the {@code targetType} is a {@literal DTO projection} or a
|
||||
|
||||
@@ -17,7 +17,6 @@ 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;
|
||||
@@ -32,17 +31,11 @@ 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;
|
||||
@@ -55,7 +48,6 @@ 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;
|
||||
@@ -202,34 +194,9 @@ 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 field names, as well as determining and applying {@link Collation collations}.
|
||||
* representation, mapping fieldnames, as well as determinging and applying {@link Collation collations}.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
@@ -238,7 +205,7 @@ class QueryOperations {
|
||||
private final Query query;
|
||||
|
||||
/**
|
||||
* Create new a {@link QueryContext} instance from the given {@literal query} (can be either a {@link Query} or a
|
||||
* Create new a {@link QueryContext} instance from the given {@literal query} (can be eihter a {@link Query} or a
|
||||
* plain {@link Document}.
|
||||
*
|
||||
* @param query can be {@literal null}.
|
||||
@@ -291,21 +258,7 @@ class QueryOperations {
|
||||
Document getMappedFields(@Nullable MongoPersistentEntity<?> entity, Class<?> targetType,
|
||||
ProjectionFactory projectionFactory) {
|
||||
|
||||
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 fields = query.getFieldsObject();
|
||||
Document mappedFields = fields;
|
||||
|
||||
if (entity == null) {
|
||||
@@ -322,7 +275,7 @@ class QueryOperations {
|
||||
mappingContext.getRequiredPersistentEntity(targetType));
|
||||
}
|
||||
|
||||
if (entity.hasTextScoreProperty() && !query.getQueryObject().containsKey("$text")) {
|
||||
if (entity != null && entity.hasTextScoreProperty() && !query.getQueryObject().containsKey("$text")) {
|
||||
mappedFields.remove(entity.getTextScoreProperty().getFieldName());
|
||||
}
|
||||
|
||||
@@ -388,8 +341,7 @@ class QueryOperations {
|
||||
}
|
||||
|
||||
@Override
|
||||
Document getMappedFields(@Nullable MongoPersistentEntity<?> entity, Class<?> targetType,
|
||||
ProjectionFactory projectionFactory) {
|
||||
Document getMappedFields(@Nullable MongoPersistentEntity<?> entity, Class<?> targetType, ProjectionFactory projectionFactory) {
|
||||
return getMappedFields(entity);
|
||||
}
|
||||
|
||||
@@ -756,10 +708,10 @@ class QueryOperations {
|
||||
*/
|
||||
List<Document> getUpdatePipeline(@Nullable Class<?> domainType) {
|
||||
|
||||
Class<?> type = domainType != null ? domainType : Object.class;
|
||||
AggregationOperationContext context = domainType != null
|
||||
? new RelaxedTypeBasedAggregationOperationContext(domainType, mappingContext, queryMapper)
|
||||
: Aggregation.DEFAULT_CONTEXT;
|
||||
|
||||
AggregationOperationContext context = new RelaxedTypeBasedAggregationOperationContext(type, mappingContext,
|
||||
queryMapper);
|
||||
return aggregationUtil.createPipeline((AggregationUpdate) update, context);
|
||||
}
|
||||
|
||||
@@ -809,105 +761,4 @@ 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,10 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.experimental.FieldDefaults;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import org.springframework.data.mongodb.core.aggregation.Aggregation;
|
||||
@@ -58,22 +62,15 @@ class ReactiveAggregationOperationSupport implements ReactiveAggregationOperatio
|
||||
return new ReactiveAggregationSupport<>(template, domainType, null, null);
|
||||
}
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
|
||||
static class ReactiveAggregationSupport<T>
|
||||
implements AggregationOperationWithAggregation<T>, ReactiveAggregation<T>, TerminatingAggregationOperation<T> {
|
||||
|
||||
private final ReactiveMongoTemplate template;
|
||||
private final Class<T> domainType;
|
||||
private final Aggregation aggregation;
|
||||
private final String collection;
|
||||
|
||||
ReactiveAggregationSupport(ReactiveMongoTemplate template, Class<T> domainType, Aggregation aggregation,
|
||||
String collection) {
|
||||
|
||||
this.template = template;
|
||||
this.domainType = domainType;
|
||||
this.aggregation = aggregation;
|
||||
this.collection = collection;
|
||||
}
|
||||
@NonNull ReactiveMongoTemplate template;
|
||||
@NonNull Class<T> domainType;
|
||||
Aggregation aggregation;
|
||||
String collection;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
|
||||
@@ -106,12 +106,6 @@ public interface ReactiveFindOperation {
|
||||
|
||||
/**
|
||||
* Get the number of matching elements.
|
||||
* <p />
|
||||
* 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}.
|
||||
*/
|
||||
|
||||
@@ -15,10 +15,15 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.experimental.FieldDefaults;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.bson.Document;
|
||||
|
||||
import org.springframework.dao.IncorrectResultSizeDataAccessException;
|
||||
import org.springframework.data.mongodb.core.query.NearQuery;
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
@@ -34,15 +39,12 @@ import org.springframework.util.StringUtils;
|
||||
* @author Christoph Strobl
|
||||
* @since 2.0
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
class ReactiveFindOperationSupport implements ReactiveFindOperation {
|
||||
|
||||
private static final Query ALL_QUERY = new Query();
|
||||
|
||||
private final ReactiveMongoTemplate template;
|
||||
|
||||
ReactiveFindOperationSupport(ReactiveMongoTemplate template) {
|
||||
this.template = template;
|
||||
}
|
||||
private final @NonNull ReactiveMongoTemplate template;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
@@ -62,24 +64,16 @@ class ReactiveFindOperationSupport implements ReactiveFindOperation {
|
||||
* @author Christoph Strobl
|
||||
* @since 2.0
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
|
||||
static class ReactiveFindSupport<T>
|
||||
implements ReactiveFind<T>, FindWithCollection<T>, FindWithProjection<T>, FindWithQuery<T> {
|
||||
|
||||
private final ReactiveMongoTemplate template;
|
||||
private final Class<?> domainType;
|
||||
private final Class<T> returnType;
|
||||
private final String collection;
|
||||
private final Query query;
|
||||
|
||||
ReactiveFindSupport(ReactiveMongoTemplate template, Class<?> domainType, Class<T> returnType,
|
||||
String collection, Query query) {
|
||||
|
||||
this.template = template;
|
||||
this.domainType = domainType;
|
||||
this.returnType = returnType;
|
||||
this.collection = collection;
|
||||
this.query = query;
|
||||
}
|
||||
@NonNull ReactiveMongoTemplate template;
|
||||
@NonNull Class<?> domainType;
|
||||
Class<T> returnType;
|
||||
String collection;
|
||||
Query query;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
|
||||
@@ -15,6 +15,10 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.experimental.FieldDefaults;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@@ -30,13 +34,10 @@ import org.springframework.util.StringUtils;
|
||||
* @author Christoph Strobl
|
||||
* @since 2.0
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
class ReactiveInsertOperationSupport implements ReactiveInsertOperation {
|
||||
|
||||
private final ReactiveMongoTemplate template;
|
||||
|
||||
ReactiveInsertOperationSupport(ReactiveMongoTemplate template) {
|
||||
this.template = template;
|
||||
}
|
||||
private final @NonNull ReactiveMongoTemplate template;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
@@ -50,18 +51,13 @@ class ReactiveInsertOperationSupport implements ReactiveInsertOperation {
|
||||
return new ReactiveInsertSupport<>(template, domainType, null);
|
||||
}
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
|
||||
static class ReactiveInsertSupport<T> implements ReactiveInsert<T> {
|
||||
|
||||
private final ReactiveMongoTemplate template;
|
||||
private final Class<T> domainType;
|
||||
private final String collection;
|
||||
|
||||
ReactiveInsertSupport(ReactiveMongoTemplate template, Class<T> domainType, String collection) {
|
||||
|
||||
this.template = template;
|
||||
this.domainType = domainType;
|
||||
this.collection = collection;
|
||||
}
|
||||
@NonNull ReactiveMongoTemplate template;
|
||||
@NonNull Class<T> domainType;
|
||||
String collection;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import org.springframework.data.mongodb.core.mapreduce.MapReduceOptions;
|
||||
@@ -29,15 +31,12 @@ import org.springframework.util.StringUtils;
|
||||
* @author Christoph Strobl
|
||||
* @since 2.1
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
class ReactiveMapReduceOperationSupport implements ReactiveMapReduceOperation {
|
||||
|
||||
private static final Query ALL_QUERY = new Query();
|
||||
|
||||
private final ReactiveMongoTemplate template;
|
||||
|
||||
ReactiveMapReduceOperationSupport(ReactiveMongoTemplate template) {
|
||||
this.template = template;
|
||||
}
|
||||
private final @NonNull ReactiveMongoTemplate template;
|
||||
|
||||
/*
|
||||
* (non-Javascript)
|
||||
|
||||
@@ -15,15 +15,11 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core;
|
||||
|
||||
import org.reactivestreams.Publisher;
|
||||
import org.springframework.util.Assert;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.util.context.Context;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.reactivestreams.Publisher;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.mongodb.reactivestreams.client.ClientSession;
|
||||
|
||||
/**
|
||||
@@ -33,7 +29,7 @@ import com.mongodb.reactivestreams.client.ClientSession;
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
* @since 2.1
|
||||
* @see Mono#deferContextual(Function)
|
||||
* @see Mono#subscriberContext()
|
||||
* @see Context
|
||||
*/
|
||||
public class ReactiveMongoContext {
|
||||
@@ -50,14 +46,8 @@ public class ReactiveMongoContext {
|
||||
*/
|
||||
public static Mono<ClientSession> getSession() {
|
||||
|
||||
return Mono.deferContextual(ctx -> {
|
||||
|
||||
if (ctx.hasKey(SESSION_KEY)) {
|
||||
return ctx.<Mono<ClientSession>> get(SESSION_KEY);
|
||||
}
|
||||
|
||||
return Mono.empty();
|
||||
});
|
||||
return Mono.subscriberContext().filter(ctx -> ctx.hasKey(SESSION_KEY))
|
||||
.flatMap(ctx -> ctx.<Mono<ClientSession>> get(SESSION_KEY));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -940,12 +940,6 @@ 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.
|
||||
* <p />
|
||||
* 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}.
|
||||
@@ -962,12 +956,6 @@ 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.
|
||||
* <p />
|
||||
* 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.
|
||||
@@ -983,12 +971,6 @@ 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.
|
||||
* <p />
|
||||
* 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}.
|
||||
@@ -998,35 +980,6 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
*/
|
||||
Mono<Long> count(Query query, @Nullable Class<?> entityClass, String collectionName);
|
||||
|
||||
/**
|
||||
* Estimate the number of documents, in the collection {@link #getCollectionName(Class) identified by the given type},
|
||||
* based on collection statistics.
|
||||
* <p />
|
||||
* 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.
|
||||
* @since 3.1
|
||||
*/
|
||||
default Mono<Long> estimatedCount(Class<?> entityClass) {
|
||||
|
||||
Assert.notNull(entityClass, "Entity class must not be null!");
|
||||
return estimatedCount(getCollectionName(entityClass));
|
||||
}
|
||||
|
||||
/**
|
||||
* Estimate the number of documents in the given collection based on collection statistics.
|
||||
* <p />
|
||||
* 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.
|
||||
* @since 3.1
|
||||
*/
|
||||
Mono<Long> estimatedCount(String collectionName);
|
||||
|
||||
/**
|
||||
* Insert the object into the collection for the entity type of the object to save.
|
||||
* <p/>
|
||||
@@ -1038,13 +991,11 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
* <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/>
|
||||
* Insert is used to initially store the object into the database. To update an existing object use the save method.
|
||||
* <p />
|
||||
* 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);
|
||||
|
||||
@@ -1055,13 +1006,10 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
* configured otherwise, an instance of {@link MappingMongoConverter} will be used.
|
||||
* <p/>
|
||||
* Insert is used to initially store the object into the database. To update an existing object use the save method.
|
||||
* <p />
|
||||
* 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);
|
||||
|
||||
@@ -1103,6 +1051,7 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
* <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/>
|
||||
* 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}.
|
||||
@@ -1149,12 +1098,9 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
* 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 />
|
||||
* 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);
|
||||
|
||||
@@ -1174,7 +1120,6 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
* @param objectToSave the object to store in the collection. Must not be {@literal null}.
|
||||
* @param collectionName name of the collection to store the object in. Must not be {@literal null}.
|
||||
* @return the saved object.
|
||||
* @throws IllegalArgumentException in case the {@code objectToSave} is collection-like.
|
||||
*/
|
||||
<T> Mono<T> save(T objectToSave, String collectionName);
|
||||
|
||||
|
||||
@@ -17,20 +17,15 @@ package org.springframework.data.mongodb.core;
|
||||
|
||||
import static org.springframework.data.mongodb.core.query.SerializationUtils.*;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.util.function.Tuple2;
|
||||
import reactor.util.function.Tuples;
|
||||
|
||||
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.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
@@ -60,7 +55,6 @@ 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;
|
||||
@@ -70,7 +64,6 @@ 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;
|
||||
@@ -80,7 +73,6 @@ 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;
|
||||
@@ -110,7 +102,6 @@ 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;
|
||||
@@ -129,7 +120,16 @@ import com.mongodb.CursorType;
|
||||
import com.mongodb.MongoException;
|
||||
import com.mongodb.ReadPreference;
|
||||
import com.mongodb.WriteConcern;
|
||||
import com.mongodb.client.model.*;
|
||||
import com.mongodb.client.model.CountOptions;
|
||||
import com.mongodb.client.model.CreateCollectionOptions;
|
||||
import com.mongodb.client.model.DeleteOptions;
|
||||
import com.mongodb.client.model.FindOneAndDeleteOptions;
|
||||
import com.mongodb.client.model.FindOneAndReplaceOptions;
|
||||
import com.mongodb.client.model.FindOneAndUpdateOptions;
|
||||
import com.mongodb.client.model.ReplaceOptions;
|
||||
import com.mongodb.client.model.ReturnDocument;
|
||||
import com.mongodb.client.model.UpdateOptions;
|
||||
import com.mongodb.client.model.ValidationOptions;
|
||||
import com.mongodb.client.model.changestream.FullDocument;
|
||||
import com.mongodb.client.result.DeleteResult;
|
||||
import com.mongodb.client.result.InsertOneResult;
|
||||
@@ -158,7 +158,6 @@ import com.mongodb.reactivestreams.client.MongoDatabase;
|
||||
* @author Christoph Strobl
|
||||
* @author Roman Puchkovskiy
|
||||
* @author Mathieu Ouellet
|
||||
* @author Yadhukrishna S Pai
|
||||
* @since 2.0
|
||||
*/
|
||||
public class ReactiveMongoTemplate implements ReactiveMongoOperations, ApplicationContextAware {
|
||||
@@ -167,6 +166,18 @@ 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;
|
||||
@@ -573,7 +584,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
ReactiveMongoTemplate.this);
|
||||
|
||||
return Flux.from(action.doInSession(operations)) //
|
||||
.contextWrite(ctx -> ReactiveMongoContext.setSession(ctx, Mono.just(session)));
|
||||
.subscriberContext(ctx -> ReactiveMongoContext.setSession(ctx, Mono.just(session)));
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -666,7 +677,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, operations.forType(entityClass).getCollectionOptions());
|
||||
return createCollection(entityClass, CollectionOptions.empty());
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -945,7 +956,9 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
|
||||
Assert.notNull(aggregation, "Aggregation pipeline must not be null!");
|
||||
|
||||
return doAggregate(aggregation, inputCollectionName, aggregation.getInputType(), outputType);
|
||||
AggregationOperationContext context = new TypeBasedAggregationOperationContext(aggregation.getInputType(),
|
||||
mappingContext, queryMapper);
|
||||
return aggregate(aggregation, inputCollectionName, outputType, context);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -963,7 +976,9 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
*/
|
||||
@Override
|
||||
public <O> Flux<O> aggregate(Aggregation aggregation, Class<?> inputType, Class<O> outputType) {
|
||||
return doAggregate(aggregation, getCollectionName(inputType), inputType, outputType);
|
||||
|
||||
return aggregate(aggregation, getCollectionName(inputType), outputType,
|
||||
new TypeBasedAggregationOperationContext(inputType, mappingContext, queryMapper));
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -972,34 +987,45 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
*/
|
||||
@Override
|
||||
public <O> Flux<O> aggregate(Aggregation aggregation, String collectionName, Class<O> outputType) {
|
||||
return doAggregate(aggregation, collectionName, null, outputType);
|
||||
return aggregate(aggregation, collectionName, outputType, null);
|
||||
}
|
||||
|
||||
protected <O> Flux<O> doAggregate(Aggregation aggregation, String collectionName, @Nullable Class<?> inputType,
|
||||
Class<O> 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) {
|
||||
|
||||
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(ctx.getAggregationPipeline()),
|
||||
collectionName);
|
||||
LOGGER.debug("Streaming aggregation: {} in collection {}", serializeToJsonSafely(pipeline), collectionName);
|
||||
}
|
||||
|
||||
ReadDocumentCallback<O> readCallback = new ReadDocumentCallback<>(mongoConverter, outputType, collectionName);
|
||||
return execute(collectionName, collection -> aggregateAndMap(collection, ctx.getAggregationPipeline(),
|
||||
ctx.isOutOrMerge(), options, readCallback, ctx.getInputType()));
|
||||
return execute(collectionName,
|
||||
collection -> aggregateAndMap(collection, pipeline, aggregation.getPipeline().isOutOrMerge(), options,
|
||||
readCallback,
|
||||
aggregation instanceof TypedAggregation ? ((TypedAggregation<?>) aggregation).getInputType() : null));
|
||||
}
|
||||
|
||||
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());
|
||||
@@ -1009,7 +1035,6 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
}
|
||||
|
||||
options.getComment().ifPresent(cursor::comment);
|
||||
options.getHint().ifPresent(cursor::hint);
|
||||
|
||||
Optionals.firstNonEmpty(options::getCollation, () -> operations.forType(inputType).getCollation()) //
|
||||
.map(Collation::toMongoCollation) //
|
||||
@@ -1233,15 +1258,6 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.ReactiveMongoOperations#estimatedCount(java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public Mono<Long> estimatedCount(String collectionName) {
|
||||
return doEstimatedCount(collectionName, new EstimatedDocumentCountOptions());
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the actual count operation against the collection with given name.
|
||||
*
|
||||
@@ -1256,11 +1272,6 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
collection -> collection.countDocuments(CountQuery.of(filter).toQueryDocument(), options));
|
||||
}
|
||||
|
||||
protected Mono<Long> doEstimatedCount(String collectionName, EstimatedDocumentCountOptions options) {
|
||||
|
||||
return createMono(collectionName, collection -> collection.estimatedDocumentCount(options));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.ReactiveMongoOperations#insert(reactor.core.publisher.Mono)
|
||||
@@ -2097,7 +2108,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
AggregationOperationContext context = agg instanceof TypedAggregation
|
||||
? new TypeBasedAggregationOperationContext(((TypedAggregation<?>) agg).getInputType(),
|
||||
getConverter().getMappingContext(), queryMapper)
|
||||
: new RelaxedTypeBasedAggregationOperationContext(Object.class, mappingContext, queryMapper);
|
||||
: Aggregation.DEFAULT_CONTEXT;
|
||||
|
||||
return agg.toPipeline(new PrefixingDelegatingAggregationOperationContext(context, "fullDocument",
|
||||
Arrays.asList("operationType", "fullDocument", "documentKey", "updateDescription", "ns")));
|
||||
@@ -2407,8 +2418,8 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
* @param query the query document that specifies the criteria used to find a record.
|
||||
* @param fields the document that specifies the fields to be returned.
|
||||
* @param entityClass the parameterized type of the returned list.
|
||||
* @param preparer allows for customization of the {@link com.mongodb.client.FindIterable} used when iterating over
|
||||
* the result set, (apply limits, skips and so on).
|
||||
* @param preparer allows for customization of the {@link com.mongodb.client.FindIterable} used when iterating over the result set, (apply
|
||||
* limits, skips and so on).
|
||||
* @return the {@link List} of converted objects.
|
||||
*/
|
||||
protected <T> Flux<T> doFind(String collectionName, Document query, Document fields, Class<T> entityClass,
|
||||
@@ -2506,20 +2517,6 @@ 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;
|
||||
}
|
||||
|
||||
@@ -2680,27 +2677,13 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
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}.
|
||||
* @since 3.2.
|
||||
*/
|
||||
protected void ensureNotCollectionLike(@Nullable Object source) {
|
||||
boolean isIterable = o.getClass().isArray()
|
||||
|| ITERABLE_CLASSES.stream().anyMatch(iterableClass -> iterableClass.isAssignableFrom(o.getClass())
|
||||
|| o.getClass().getName().equals(iterableClass.getName()));
|
||||
|
||||
if (EntityOperations.isCollectionLike(source) || source instanceof Publisher) {
|
||||
if (isIterable) {
|
||||
throw new IllegalArgumentException("Cannot use a collection here.");
|
||||
}
|
||||
}
|
||||
@@ -2742,14 +2725,6 @@ 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) {
|
||||
|
||||
@@ -2918,6 +2893,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
private static class FindCallback implements ReactiveCollectionQueryCallback<Document> {
|
||||
|
||||
private final @Nullable Document query;
|
||||
@@ -2927,12 +2903,6 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
this(query, null);
|
||||
}
|
||||
|
||||
FindCallback(Document query, Document fields) {
|
||||
|
||||
this.query = query;
|
||||
this.fields = fields;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FindPublisher<Document> doInCollection(MongoCollection<Document> collection) {
|
||||
|
||||
@@ -2986,6 +2956,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
/**
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
private static class FindAndModifyCallback implements ReactiveCollectionCallback<Document> {
|
||||
|
||||
private final Document query;
|
||||
@@ -2995,17 +2966,6 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
private final List<Document> arrayFilters;
|
||||
private final FindAndModifyOptions options;
|
||||
|
||||
FindAndModifyCallback(Document query, Document fields, Document sort, Object update, List<Document> arrayFilters,
|
||||
FindAndModifyOptions options) {
|
||||
|
||||
this.query = query;
|
||||
this.fields = fields;
|
||||
this.sort = sort;
|
||||
this.update = update;
|
||||
this.arrayFilters = arrayFilters;
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Publisher<Document> doInCollection(MongoCollection<Document> collection)
|
||||
throws MongoException, DataAccessException {
|
||||
@@ -3061,6 +3021,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
* @author Christoph Strobl
|
||||
* @since 2.1
|
||||
*/
|
||||
@RequiredArgsConstructor(access = AccessLevel.PACKAGE)
|
||||
private static class FindAndReplaceCallback implements ReactiveCollectionCallback<Document> {
|
||||
|
||||
private final Document query;
|
||||
@@ -3070,17 +3031,6 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
private final @Nullable com.mongodb.client.model.Collation collation;
|
||||
private final FindAndReplaceOptions options;
|
||||
|
||||
FindAndReplaceCallback(Document query, Document fields, Document sort, Document update,
|
||||
com.mongodb.client.model.Collation collation, FindAndReplaceOptions options) {
|
||||
|
||||
this.query = query;
|
||||
this.fields = fields;
|
||||
this.sort = sort;
|
||||
this.update = update;
|
||||
this.collation = collation;
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.ReactiveCollectionCallback#doInCollection(com.mongodb.reactivestreams.client.MongoCollection)
|
||||
@@ -3177,14 +3127,13 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
|
||||
maybeEmitEvent(new AfterLoadEvent<>(document, type, collectionName));
|
||||
|
||||
T entity = reader.read(type, document);
|
||||
|
||||
if (entity == null) {
|
||||
throw new MappingException(String.format("EntityReader %s returned null", reader));
|
||||
T source = reader.read(type, document);
|
||||
if (source != null) {
|
||||
maybeEmitEvent(new AfterConvertEvent<>(document, source, collectionName));
|
||||
return maybeCallAfterConvert(source, document, collectionName);
|
||||
}
|
||||
|
||||
maybeEmitEvent(new AfterConvertEvent<>(document, entity, collectionName));
|
||||
return maybeCallAfterConvert(entity, document, collectionName);
|
||||
return Mono.empty();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3198,20 +3147,13 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
* @author Roman Puchkovskiy
|
||||
* @since 2.0
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
private class ProjectingReadCallback<S, T> implements DocumentCallback<T> {
|
||||
|
||||
private final EntityReader<Object, Bson> reader;
|
||||
private final Class<S> entityType;
|
||||
private final Class<T> targetType;
|
||||
private final String collectionName;
|
||||
|
||||
ProjectingReadCallback(EntityReader<Object, Bson> reader, Class<S> entityType, Class<T> targetType,
|
||||
String collectionName) {
|
||||
this.reader = reader;
|
||||
this.entityType = entityType;
|
||||
this.targetType = targetType;
|
||||
this.collectionName = collectionName;
|
||||
}
|
||||
private final @NonNull EntityReader<Object, Bson> reader;
|
||||
private final @NonNull Class<S> entityType;
|
||||
private final @NonNull Class<T> targetType;
|
||||
private final @NonNull String collectionName;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public Mono<T> doWith(Document document) {
|
||||
@@ -3222,17 +3164,16 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
|
||||
maybeEmitEvent(new AfterLoadEvent<>(document, typeToRead, collectionName));
|
||||
|
||||
Object entity = reader.read(typeToRead, document);
|
||||
|
||||
if (entity == null) {
|
||||
throw new MappingException(String.format("EntityReader %s returned null", reader));
|
||||
}
|
||||
|
||||
Object result = targetType.isInterface() ? projectionFactory.createProjection(targetType, entity) : entity;
|
||||
Object source = reader.read(typeToRead, document);
|
||||
Object result = targetType.isInterface() ? projectionFactory.createProjection(targetType, source) : source;
|
||||
|
||||
T castEntity = (T) result;
|
||||
maybeEmitEvent(new AfterConvertEvent<>(document, castEntity, collectionName));
|
||||
return maybeCallAfterConvert(castEntity, document, collectionName);
|
||||
if (castEntity != null) {
|
||||
maybeEmitEvent(new AfterConvertEvent<>(document, castEntity, collectionName));
|
||||
return maybeCallAfterConvert(castEntity, document, collectionName);
|
||||
}
|
||||
|
||||
return Mono.empty();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3351,10 +3292,6 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
if (meta.getCursorBatchSize() != null) {
|
||||
findPublisherToUse = findPublisherToUse.batchSize(meta.getCursorBatchSize());
|
||||
}
|
||||
|
||||
if (meta.getAllowDiskUse() != null) {
|
||||
findPublisherToUse = findPublisherToUse.allowDiskUse(meta.getAllowDiskUse());
|
||||
}
|
||||
}
|
||||
|
||||
} catch (RuntimeException e) {
|
||||
@@ -3437,14 +3374,11 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
}
|
||||
}
|
||||
|
||||
@RequiredArgsConstructor
|
||||
class IndexCreatorEventListener implements ApplicationListener<MappingContextEvent<?, ?>> {
|
||||
|
||||
final Consumer<Throwable> subscriptionExceptionHandler;
|
||||
|
||||
public IndexCreatorEventListener(Consumer<Throwable> subscriptionExceptionHandler) {
|
||||
this.subscriptionExceptionHandler = subscriptionExceptionHandler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(MappingContextEvent<?, ?> event) {
|
||||
|
||||
|
||||
@@ -15,6 +15,10 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.experimental.FieldDefaults;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@@ -31,15 +35,12 @@ import com.mongodb.client.result.DeleteResult;
|
||||
* @author Christoph Strobl
|
||||
* @since 2.0
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
class ReactiveRemoveOperationSupport implements ReactiveRemoveOperation {
|
||||
|
||||
private static final Query ALL_QUERY = new Query();
|
||||
|
||||
private final ReactiveMongoTemplate tempate;
|
||||
|
||||
ReactiveRemoveOperationSupport(ReactiveMongoTemplate tempate) {
|
||||
this.tempate = tempate;
|
||||
}
|
||||
private final @NonNull ReactiveMongoTemplate tempate;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
@@ -53,20 +54,14 @@ class ReactiveRemoveOperationSupport implements ReactiveRemoveOperation {
|
||||
return new ReactiveRemoveSupport<>(tempate, domainType, ALL_QUERY, null);
|
||||
}
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
|
||||
static class ReactiveRemoveSupport<T> implements ReactiveRemove<T>, RemoveWithCollection<T> {
|
||||
|
||||
private final ReactiveMongoTemplate template;
|
||||
private final Class<T> domainType;
|
||||
private final Query query;
|
||||
private final String collection;
|
||||
|
||||
ReactiveRemoveSupport(ReactiveMongoTemplate template, Class<T> domainType, Query query, String collection) {
|
||||
|
||||
this.template = template;
|
||||
this.domainType = domainType;
|
||||
this.query = query;
|
||||
this.collection = collection;
|
||||
}
|
||||
@NonNull ReactiveMongoTemplate template;
|
||||
@NonNull Class<T> domainType;
|
||||
Query query;
|
||||
String collection;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
|
||||
@@ -15,10 +15,13 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.experimental.FieldDefaults;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
import org.springframework.data.mongodb.core.query.UpdateDefinition;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
@@ -32,15 +35,12 @@ import com.mongodb.client.result.UpdateResult;
|
||||
* @author Christoph Strobl
|
||||
* @since 2.0
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
class ReactiveUpdateOperationSupport implements ReactiveUpdateOperation {
|
||||
|
||||
private static final Query ALL_QUERY = new Query();
|
||||
|
||||
private final ReactiveMongoTemplate template;
|
||||
|
||||
ReactiveUpdateOperationSupport(ReactiveMongoTemplate template) {
|
||||
this.template = template;
|
||||
}
|
||||
private final @NonNull ReactiveMongoTemplate template;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
@@ -54,34 +54,21 @@ class ReactiveUpdateOperationSupport implements ReactiveUpdateOperation {
|
||||
return new ReactiveUpdateSupport<>(template, domainType, ALL_QUERY, null, null, null, null, null, domainType);
|
||||
}
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
|
||||
static class ReactiveUpdateSupport<T>
|
||||
implements ReactiveUpdate<T>, UpdateWithCollection<T>, UpdateWithQuery<T>, TerminatingUpdate<T>,
|
||||
FindAndReplaceWithOptions<T>, FindAndReplaceWithProjection<T>, TerminatingFindAndReplace<T> {
|
||||
|
||||
private final ReactiveMongoTemplate template;
|
||||
private final Class<?> domainType;
|
||||
private final Query query;
|
||||
private final org.springframework.data.mongodb.core.query.UpdateDefinition update;
|
||||
@Nullable private final String collection;
|
||||
@Nullable private final FindAndModifyOptions findAndModifyOptions;
|
||||
@Nullable private final FindAndReplaceOptions findAndReplaceOptions;
|
||||
@Nullable private final Object replacement;
|
||||
private final Class<T> targetType;
|
||||
|
||||
ReactiveUpdateSupport(ReactiveMongoTemplate template, Class<?> domainType, Query query, UpdateDefinition update,
|
||||
String collection, FindAndModifyOptions findAndModifyOptions, FindAndReplaceOptions findAndReplaceOptions,
|
||||
Object replacement, Class<T> targetType) {
|
||||
|
||||
this.template = template;
|
||||
this.domainType = domainType;
|
||||
this.query = query;
|
||||
this.update = update;
|
||||
this.collection = collection;
|
||||
this.findAndModifyOptions = findAndModifyOptions;
|
||||
this.findAndReplaceOptions = findAndReplaceOptions;
|
||||
this.replacement = replacement;
|
||||
this.targetType = targetType;
|
||||
}
|
||||
@NonNull ReactiveMongoTemplate template;
|
||||
@NonNull Class<?> domainType;
|
||||
Query query;
|
||||
org.springframework.data.mongodb.core.query.UpdateDefinition update;
|
||||
@Nullable String collection;
|
||||
@Nullable FindAndModifyOptions findAndModifyOptions;
|
||||
@Nullable FindAndReplaceOptions findAndReplaceOptions;
|
||||
@Nullable Object replacement;
|
||||
@NonNull Class<T> targetType;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
@@ -136,9 +123,7 @@ class ReactiveUpdateOperationSupport implements ReactiveUpdateOperation {
|
||||
|
||||
String collectionName = getCollectionName();
|
||||
|
||||
return template.findAndModify(query, update,
|
||||
findAndModifyOptions != null ? findAndModifyOptions : FindAndModifyOptions.none(), targetType,
|
||||
collectionName);
|
||||
return template.findAndModify(query, update, findAndModifyOptions != null ? findAndModifyOptions : FindAndModifyOptions.none(), targetType, collectionName);
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core;
|
||||
|
||||
import lombok.Value;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.bson.codecs.configuration.CodecRegistry;
|
||||
@@ -26,7 +27,6 @@ import org.springframework.data.mongodb.ReactiveMongoDatabaseFactory;
|
||||
import org.springframework.data.mongodb.SessionAwareMethodInterceptor;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
import com.mongodb.ClientSessionOptions;
|
||||
import com.mongodb.ConnectionString;
|
||||
@@ -175,16 +175,11 @@ public class SimpleReactiveMongoDatabaseFactory implements DisposableBean, React
|
||||
* @author Christoph Strobl
|
||||
* @since 2.1
|
||||
*/
|
||||
static final class ClientSessionBoundMongoDbFactory implements ReactiveMongoDatabaseFactory {
|
||||
@Value
|
||||
static class ClientSessionBoundMongoDbFactory implements ReactiveMongoDatabaseFactory {
|
||||
|
||||
private final ClientSession session;
|
||||
private final ReactiveMongoDatabaseFactory delegate;
|
||||
|
||||
ClientSessionBoundMongoDbFactory(ClientSession session, ReactiveMongoDatabaseFactory delegate) {
|
||||
|
||||
this.session = session;
|
||||
this.delegate = delegate;
|
||||
}
|
||||
ClientSession session;
|
||||
ReactiveMongoDatabaseFactory delegate;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
@@ -273,40 +268,5 @@ public class SimpleReactiveMongoDatabaseFactory implements DisposableBean, React
|
||||
|
||||
return targetType.cast(factory.getProxy(target.getClass().getClassLoader()));
|
||||
}
|
||||
|
||||
public ClientSession getSession() {
|
||||
return this.session;
|
||||
}
|
||||
|
||||
public ReactiveMongoDatabaseFactory getDelegate() {
|
||||
return this.delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o)
|
||||
return true;
|
||||
if (o == null || getClass() != o.getClass())
|
||||
return false;
|
||||
|
||||
ClientSessionBoundMongoDbFactory that = (ClientSessionBoundMongoDbFactory) o;
|
||||
|
||||
if (!ObjectUtils.nullSafeEquals(this.session, that.session)) {
|
||||
return false;
|
||||
}
|
||||
return ObjectUtils.nullSafeEquals(this.delegate, that.delegate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = ObjectUtils.nullSafeHashCode(this.session);
|
||||
result = 31 * result + ObjectUtils.nullSafeHashCode(this.delegate);
|
||||
return result;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "SimpleReactiveMongoDatabaseFactory.ClientSessionBoundMongoDbFactory(session=" + this.getSession()
|
||||
+ ", delegate=" + this.getDelegate() + ")";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,11 +29,8 @@ import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
/**
|
||||
* Support class for {@link AggregationExpression} implementations.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author Matt Morrissette
|
||||
* @author Mark Paluch
|
||||
* @since 1.10
|
||||
*/
|
||||
abstract class AbstractAggregationExpression implements AggregationExpression {
|
||||
@@ -52,6 +49,7 @@ abstract class AbstractAggregationExpression implements AggregationExpression {
|
||||
return toDocument(this.value, context);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public Document toDocument(Object value, AggregationOperationContext context) {
|
||||
return new Document(getMongoMethod(), unpack(value, context));
|
||||
}
|
||||
@@ -103,19 +101,17 @@ abstract class AbstractAggregationExpression implements AggregationExpression {
|
||||
return value;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected List<Object> append(Object value, Expand expandList) {
|
||||
|
||||
if (this.value instanceof List) {
|
||||
|
||||
List<Object> clone = new ArrayList<>((List<Object>) this.value);
|
||||
List<Object> clone = new ArrayList<Object>((List) this.value);
|
||||
|
||||
if (value instanceof Collection && Expand.EXPAND_VALUES.equals(expandList)) {
|
||||
clone.addAll((Collection<?>) value);
|
||||
} else {
|
||||
clone.add(value);
|
||||
}
|
||||
|
||||
return clone;
|
||||
}
|
||||
|
||||
@@ -133,72 +129,25 @@ abstract class AbstractAggregationExpression implements AggregationExpression {
|
||||
return append(value, Expand.EXPAND_VALUES);
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
protected Map<String, Object> append(String key, Object value) {
|
||||
@SuppressWarnings("unchecked")
|
||||
protected java.util.Map<String, Object> append(String key, Object value) {
|
||||
|
||||
Assert.isInstanceOf(Map.class, this.value, "Value must be a type of Map!");
|
||||
|
||||
Map<String, Object> clone = new LinkedHashMap<>((java.util.Map) this.value);
|
||||
java.util.Map<String, Object> clone = new LinkedHashMap<>((java.util.Map) this.value);
|
||||
clone.put(key, value);
|
||||
return clone;
|
||||
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
protected Map<String, Object> remove(String key) {
|
||||
|
||||
Assert.isInstanceOf(Map.class, this.value, "Value must be a type of Map!");
|
||||
|
||||
Map<String, Object> clone = new LinkedHashMap<>((java.util.Map) this.value);
|
||||
clone.remove(key);
|
||||
return clone;
|
||||
}
|
||||
|
||||
/**
|
||||
* Append the given key at the position in the underlying {@link LinkedHashMap}.
|
||||
*
|
||||
* @param index
|
||||
* @param key
|
||||
* @param value
|
||||
* @return
|
||||
* @since 3.1
|
||||
*/
|
||||
@SuppressWarnings({ "unchecked" })
|
||||
protected Map<String, Object> appendAt(int index, String key, Object value) {
|
||||
|
||||
Assert.isInstanceOf(Map.class, this.value, "Value must be a type of Map!");
|
||||
|
||||
Map<String, Object> clone = new LinkedHashMap<>();
|
||||
|
||||
int i = 0;
|
||||
for (Map.Entry<String, Object> entry : ((Map<String, Object>) this.value).entrySet()) {
|
||||
|
||||
if (i == index) {
|
||||
clone.put(key, value);
|
||||
}
|
||||
if (!entry.getKey().equals(key)) {
|
||||
clone.put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
i++;
|
||||
}
|
||||
if (i <= index) {
|
||||
clone.put(key, value);
|
||||
}
|
||||
return clone;
|
||||
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "rawtypes" })
|
||||
protected List<Object> values() {
|
||||
|
||||
if (value instanceof List) {
|
||||
return new ArrayList<Object>((List) value);
|
||||
}
|
||||
|
||||
if (value instanceof java.util.Map) {
|
||||
return new ArrayList<Object>(((java.util.Map) value).values());
|
||||
}
|
||||
|
||||
return new ArrayList<>(Collections.singletonList(value));
|
||||
}
|
||||
|
||||
@@ -228,7 +177,7 @@ abstract class AbstractAggregationExpression implements AggregationExpression {
|
||||
|
||||
Assert.isInstanceOf(Map.class, this.value, "Value must be a type of Map!");
|
||||
|
||||
return (T) ((Map<String, Object>) this.value).get(key);
|
||||
return (T) ((java.util.Map<String, Object>) this.value).get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -238,11 +187,11 @@ abstract class AbstractAggregationExpression implements AggregationExpression {
|
||||
* @return
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
protected Map<String, Object> argumentMap() {
|
||||
protected java.util.Map<String, Object> argumentMap() {
|
||||
|
||||
Assert.isInstanceOf(Map.class, this.value, "Value must be a type of Map!");
|
||||
|
||||
return Collections.unmodifiableMap((java.util.Map<String, Object>) value);
|
||||
return Collections.unmodifiableMap((java.util.Map) value);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -259,7 +208,7 @@ abstract class AbstractAggregationExpression implements AggregationExpression {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ((Map<String, Object>) this.value).containsKey(key);
|
||||
return ((java.util.Map<String, Object>) this.value).containsKey(key);
|
||||
}
|
||||
|
||||
protected abstract String getMongoMethod();
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
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
|
||||
@@ -26,37 +25,7 @@ import org.springframework.data.mongodb.MongoExpression;
|
||||
* @author Oliver Gierke
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
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);
|
||||
}
|
||||
public interface AggregationExpression {
|
||||
|
||||
/**
|
||||
* Turns the {@link AggregationExpression} into a {@link Document} within the given
|
||||
|
||||
@@ -32,7 +32,6 @@ import org.springframework.util.Assert;
|
||||
* @author Oliver Gierke
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
* @author Yadhukrishna S Pai
|
||||
* @see Aggregation#withOptions(AggregationOptions)
|
||||
* @see TypedAggregation#withOptions(AggregationOptions)
|
||||
* @since 1.6
|
||||
@@ -46,17 +45,14 @@ public class AggregationOptions {
|
||||
private static final String COLLATION = "collation";
|
||||
private static final String COMMENT = "comment";
|
||||
private static final String MAX_TIME = "maxTimeMS";
|
||||
private static final String HINT = "hint";
|
||||
|
||||
private final boolean allowDiskUse;
|
||||
private final boolean explain;
|
||||
private final Optional<Document> cursor;
|
||||
private final Optional<Collation> collation;
|
||||
private final Optional<String> comment;
|
||||
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}.
|
||||
@@ -75,13 +71,13 @@ public class AggregationOptions {
|
||||
* @param allowDiskUse whether to off-load intensive sort-operations to disk.
|
||||
* @param explain whether to get the execution plan for the aggregation instead of the actual results.
|
||||
* @param cursor can be {@literal null}, used to pass additional options (such as {@code batchSize}) to the
|
||||
* aggregation.
|
||||
* aggregation.
|
||||
* @param collation collation for string comparison. Can be {@literal null}.
|
||||
* @since 2.0
|
||||
*/
|
||||
public AggregationOptions(boolean allowDiskUse, boolean explain, @Nullable Document cursor,
|
||||
@Nullable Collation collation) {
|
||||
this(allowDiskUse, explain, cursor, collation, null, null);
|
||||
this(allowDiskUse, explain, cursor, collation, null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -90,37 +86,19 @@ public class AggregationOptions {
|
||||
* @param allowDiskUse whether to off-load intensive sort-operations to disk.
|
||||
* @param explain whether to get the execution plan for the aggregation instead of the actual results.
|
||||
* @param cursor can be {@literal null}, used to pass additional options (such as {@code batchSize}) to the
|
||||
* aggregation.
|
||||
* aggregation.
|
||||
* @param collation collation for string comparison. Can be {@literal null}.
|
||||
* @param comment execution comment. Can be {@literal null}.
|
||||
* @since 2.2
|
||||
*/
|
||||
public AggregationOptions(boolean allowDiskUse, boolean explain, @Nullable Document cursor,
|
||||
@Nullable Collation collation, @Nullable String comment) {
|
||||
this(allowDiskUse, explain, cursor, collation, comment, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link AggregationOptions}.
|
||||
*
|
||||
* @param allowDiskUse whether to off-load intensive sort-operations to disk.
|
||||
* @param explain whether to get the execution plan for the aggregation instead of the actual results.
|
||||
* @param cursor can be {@literal null}, used to pass additional options (such as {@code batchSize}) to the
|
||||
* aggregation.
|
||||
* @param collation collation for string comparison. Can be {@literal null}.
|
||||
* @param comment execution comment. Can be {@literal null}.
|
||||
* @param hint can be {@literal null}, used to provide an index that would be forcibly used by query optimizer.
|
||||
* @since 3.1
|
||||
*/
|
||||
private AggregationOptions(boolean allowDiskUse, boolean explain, @Nullable Document cursor,
|
||||
@Nullable Collation collation, @Nullable String comment, @Nullable Document hint) {
|
||||
|
||||
this.allowDiskUse = allowDiskUse;
|
||||
this.explain = explain;
|
||||
this.cursor = Optional.ofNullable(cursor);
|
||||
this.collation = Optional.ofNullable(collation);
|
||||
this.comment = Optional.ofNullable(comment);
|
||||
this.hint = Optional.ofNullable(hint);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -152,9 +130,8 @@ public class AggregationOptions {
|
||||
Collation collation = document.containsKey(COLLATION) ? Collation.from(document.get(COLLATION, Document.class))
|
||||
: null;
|
||||
String comment = document.getString(COMMENT);
|
||||
Document hint = document.get(HINT, Document.class);
|
||||
|
||||
AggregationOptions options = new AggregationOptions(allowDiskUse, explain, cursor, collation, comment, hint);
|
||||
AggregationOptions options = new AggregationOptions(allowDiskUse, explain, cursor, collation, comment);
|
||||
if (document.containsKey(MAX_TIME)) {
|
||||
options.maxTime = Duration.ofMillis(document.getLong(MAX_TIME));
|
||||
}
|
||||
@@ -235,16 +212,6 @@ public class AggregationOptions {
|
||||
return comment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the hint used to to fulfill the aggregation.
|
||||
*
|
||||
* @return never {@literal null}.
|
||||
* @since 3.1
|
||||
*/
|
||||
public Optional<Document> getHint() {
|
||||
return hint;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the time limit for processing. {@link Duration#ZERO} is used for the default unbounded behavior.
|
||||
* @since 3.0
|
||||
@@ -262,14 +229,6 @@ 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.
|
||||
@@ -289,10 +248,6 @@ public class AggregationOptions {
|
||||
result.put(EXPLAIN, explain);
|
||||
}
|
||||
|
||||
if (result.containsKey(HINT)) {
|
||||
hint.ifPresent(val -> result.append(HINT, val));
|
||||
}
|
||||
|
||||
if (!result.containsKey(CURSOR)) {
|
||||
cursor.ifPresent(val -> result.put(CURSOR, val));
|
||||
}
|
||||
@@ -322,7 +277,6 @@ public class AggregationOptions {
|
||||
cursor.ifPresent(val -> document.put(CURSOR, val));
|
||||
collation.ifPresent(val -> document.append(COLLATION, val.toDocument()));
|
||||
comment.ifPresent(val -> document.append(COMMENT, val));
|
||||
hint.ifPresent(val -> document.append(HINT, val));
|
||||
|
||||
if (hasExecutionTimeLimit()) {
|
||||
document.append(MAX_TIME, maxTime.toMillis());
|
||||
@@ -364,10 +318,8 @@ public class AggregationOptions {
|
||||
private @Nullable Document cursor;
|
||||
private @Nullable Collation collation;
|
||||
private @Nullable String comment;
|
||||
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.
|
||||
@@ -444,24 +396,11 @@ public class AggregationOptions {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Define a hint that is used by query optimizer to to fulfill the aggregation.
|
||||
*
|
||||
* @param hint can be {@literal null}.
|
||||
* @return this.
|
||||
* @since 3.1
|
||||
*/
|
||||
public Builder hint(@Nullable Document hint) {
|
||||
|
||||
this.hint = hint;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the time limit for processing.
|
||||
*
|
||||
* @param maxTime {@link Duration#ZERO} is used for the default unbounded behavior. {@link Duration#isNegative()
|
||||
* Negative} values will be ignored.
|
||||
* Negative} values will be ignored.
|
||||
* @return this.
|
||||
* @since 3.0
|
||||
*/
|
||||
@@ -485,44 +424,6 @@ 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.
|
||||
*
|
||||
@@ -530,16 +431,13 @@ public class AggregationOptions {
|
||||
*/
|
||||
public AggregationOptions build() {
|
||||
|
||||
AggregationOptions options = new AggregationOptions(allowDiskUse, explain, cursor, collation, comment, hint);
|
||||
AggregationOptions options = new AggregationOptions(allowDiskUse, explain, cursor, collation, comment);
|
||||
if (maxTime != null) {
|
||||
options.maxTime = maxTime;
|
||||
}
|
||||
if (resultOptions != null) {
|
||||
options.resultOptions = resultOptions;
|
||||
}
|
||||
if (domainTypeMapping != null) {
|
||||
options.domainTypeMapping = domainTypeMapping;
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
@@ -559,27 +457,4 @@ 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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ package org.springframework.data.mongodb.core.aggregation;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.bson.Document;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -27,7 +26,6 @@ import org.springframework.util.Assert;
|
||||
* The {@link AggregationPipeline} holds the collection of {@link AggregationOperation aggregation stages}.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
* @since 3.0.2
|
||||
*/
|
||||
public class AggregationPipeline {
|
||||
@@ -47,8 +45,6 @@ public class AggregationPipeline {
|
||||
* @param aggregationOperations must not be {@literal null}.
|
||||
*/
|
||||
public AggregationPipeline(List<AggregationOperation> aggregationOperations) {
|
||||
|
||||
Assert.notNull(aggregationOperations, "AggregationOperations must not be null!");
|
||||
pipeline = new ArrayList<>(aggregationOperations);
|
||||
}
|
||||
|
||||
@@ -86,77 +82,30 @@ public class AggregationPipeline {
|
||||
*/
|
||||
public boolean isOutOrMerge() {
|
||||
|
||||
if (isEmpty()) {
|
||||
if (pipeline.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
AggregationOperation operation = pipeline.get(pipeline.size() - 1);
|
||||
return isOut(operation) || isMerge(operation);
|
||||
String operator = pipeline.get(pipeline.size() - 1).getOperator();
|
||||
return operator.equals("$out") || operator.equals("$merge");
|
||||
}
|
||||
|
||||
void verify() {
|
||||
|
||||
// check $out/$merge is the last operation if it exists
|
||||
for (AggregationOperation operation : pipeline) {
|
||||
for (AggregationOperation aggregationOperation : pipeline) {
|
||||
|
||||
if (isOut(operation) && !isLast(operation)) {
|
||||
if (aggregationOperation instanceof OutOperation && !isLast(aggregationOperation)) {
|
||||
throw new IllegalArgumentException("The $out operator must be the last stage in the pipeline.");
|
||||
}
|
||||
|
||||
if (isMerge(operation) && !isLast(operation)) {
|
||||
if (aggregationOperation instanceof MergeOperation && !isLast(aggregationOperation)) {
|
||||
throw new IllegalArgumentException("The $merge operator must be the last stage in the pipeline.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether this aggregation pipeline defines a {@code $unionWith} stage that may contribute documents from
|
||||
* other collections. Checking for presence of union stages is useful when attempting to determine the aggregation
|
||||
* element type for mapping metadata computation.
|
||||
*
|
||||
* @return {@literal true} the aggregation pipeline makes use of {@code $unionWith}.
|
||||
* @since 3.1
|
||||
*/
|
||||
public boolean containsUnionWith() {
|
||||
return containsOperation(AggregationPipeline::isUnionWith);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@literal true} if the pipeline does not contain any stages.
|
||||
* @since 3.1
|
||||
*/
|
||||
public boolean isEmpty() {
|
||||
return pipeline.isEmpty();
|
||||
}
|
||||
|
||||
private boolean containsOperation(Predicate<AggregationOperation> predicate) {
|
||||
|
||||
if (isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (AggregationOperation element : pipeline) {
|
||||
if (predicate.test(element)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isLast(AggregationOperation aggregationOperation) {
|
||||
return pipeline.indexOf(aggregationOperation) == pipeline.size() - 1;
|
||||
}
|
||||
|
||||
private static boolean isUnionWith(AggregationOperation operator) {
|
||||
return operator instanceof UnionWithOperation || operator.getOperator().equals("$unionWith");
|
||||
}
|
||||
|
||||
private static boolean isMerge(AggregationOperation operator) {
|
||||
return operator instanceof MergeOperation || operator.getOperator().equals("$merge");
|
||||
}
|
||||
|
||||
private static boolean isOut(AggregationOperation operator) {
|
||||
return operator instanceof OutOperation || operator.getOperator().equals("$out");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1835,7 +1835,7 @@ public class DateOperators {
|
||||
* @param millisecond must not be {@literal null}.
|
||||
* @return new instance.
|
||||
* @throws IllegalArgumentException if given {@literal millisecond} is {@literal null}
|
||||
* @deprecated since 3.2, use {@link #millisecond(Object)} instead.
|
||||
* @deprecated since 3.0.7, use {@link #millisecond(Object)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
default T milliseconds(Object millisecond) {
|
||||
@@ -1849,7 +1849,7 @@ public class DateOperators {
|
||||
* @param millisecond must not be {@literal null}.
|
||||
* @return new instance.
|
||||
* @throws IllegalArgumentException if given {@literal millisecond} is {@literal null}
|
||||
* @since 3.2
|
||||
* @since 3.0.7
|
||||
*/
|
||||
T millisecond(Object millisecond);
|
||||
|
||||
@@ -1859,7 +1859,7 @@ public class DateOperators {
|
||||
* @param fieldReference must not be {@literal null}.
|
||||
* @return new instance.
|
||||
* @throws IllegalArgumentException if given {@literal fieldReference} is {@literal null}.
|
||||
* @deprecated since 3.2,use {@link #millisecondOf(String)} instead.
|
||||
* @deprecated since 3.0.7,use {@link #millisecondOf(String)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
default T millisecondsOf(String fieldReference) {
|
||||
@@ -1872,7 +1872,7 @@ public class DateOperators {
|
||||
* @param fieldReference must not be {@literal null}.
|
||||
* @return new instance.
|
||||
* @throws IllegalArgumentException if given {@literal fieldReference} is {@literal null}.
|
||||
* @since 3.2
|
||||
* @since 3.0.7
|
||||
*/
|
||||
default T millisecondOf(String fieldReference) {
|
||||
return milliseconds(Fields.field(fieldReference));
|
||||
@@ -1884,7 +1884,7 @@ public class DateOperators {
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return new instance.
|
||||
* @throws IllegalArgumentException if given {@literal expression} is {@literal null}.
|
||||
* @deprecated since 3.2, use {@link #millisecondOf(AggregationExpression)} instead.
|
||||
* @deprecated since 3.0.7, use {@link #millisecondOf(AggregationExpression)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
default T millisecondsOf(AggregationExpression expression) {
|
||||
@@ -1897,7 +1897,7 @@ public class DateOperators {
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return new instance.
|
||||
* @throws IllegalArgumentException if given {@literal expression} is {@literal null}.
|
||||
* @since 3.2
|
||||
* @since 3.0.7
|
||||
*/
|
||||
default T millisecondOf(AggregationExpression expression) {
|
||||
return milliseconds(expression);
|
||||
|
||||
@@ -149,12 +149,4 @@ class ExposedFieldsAggregationOperationContext implements AggregationOperationCo
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return obtain the root context used to resolve references.
|
||||
* @since 3.1
|
||||
*/
|
||||
AggregationOperationContext getRootContext() {
|
||||
return rootContext;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@ import java.util.List;
|
||||
import org.bson.Document;
|
||||
import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExposedField;
|
||||
import org.springframework.data.mongodb.core.aggregation.ExposedFields.FieldReference;
|
||||
import org.springframework.data.mongodb.core.aggregation.ScriptOperators.Accumulator;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@@ -376,17 +375,6 @@ public class GroupOperation implements FieldsExposingAggregationOperation {
|
||||
return newBuilder(GroupOps.STD_DEV_POP, null, expr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an {@link GroupOperationBuilder} for an {@code $accumulator}-expression.
|
||||
*
|
||||
* @param accumulator must not be {@literal null}.
|
||||
* @return never {@literal null}.
|
||||
* @since 3.2
|
||||
*/
|
||||
public GroupOperationBuilder accumulate(Accumulator accumulator) {
|
||||
return new GroupOperationBuilder(this, new Operation(accumulator));
|
||||
}
|
||||
|
||||
private GroupOperationBuilder newBuilder(Keyword keyword, @Nullable String reference, @Nullable Object value) {
|
||||
return new GroupOperationBuilder(this, new Operation(keyword, null, reference, value));
|
||||
}
|
||||
@@ -477,16 +465,12 @@ public class GroupOperation implements FieldsExposingAggregationOperation {
|
||||
|
||||
static class Operation implements AggregationOperation {
|
||||
|
||||
private final @Nullable Keyword op;
|
||||
private final Keyword op;
|
||||
private final @Nullable String key;
|
||||
private final @Nullable String reference;
|
||||
private final @Nullable Object value;
|
||||
|
||||
Operation(AggregationExpression expression) {
|
||||
this(null, null, null, expression);
|
||||
}
|
||||
|
||||
public Operation(@Nullable Keyword op, @Nullable String key, @Nullable String reference, @Nullable Object value) {
|
||||
public Operation(Keyword op, @Nullable String key, @Nullable String reference, @Nullable Object value) {
|
||||
|
||||
this.op = op;
|
||||
this.key = key;
|
||||
@@ -503,12 +487,7 @@ public class GroupOperation implements FieldsExposingAggregationOperation {
|
||||
}
|
||||
|
||||
public Document toDocument(AggregationOperationContext context) {
|
||||
|
||||
Object value = getValue(context);
|
||||
if(op == null && value instanceof Document) {
|
||||
return new Document(key, value);
|
||||
}
|
||||
return new Document(key, new Document(op.toString(), value));
|
||||
return new Document(key, new Document(op.toString(), getValue(context)));
|
||||
}
|
||||
|
||||
public Object getValue(AggregationOperationContext context) {
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
*/
|
||||
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;
|
||||
@@ -56,7 +55,7 @@ public class RelaxedTypeBasedAggregationOperationContext extends TypeBasedAggreg
|
||||
|
||||
try {
|
||||
return super.getReferenceFor(field);
|
||||
} catch (MappingException e) {
|
||||
} catch (InvalidPersistentPropertyPath e) {
|
||||
return new DirectFieldReference(new ExposedField(field, true));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,587 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020-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.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.data.mongodb.core.aggregation.ScriptOperators.Accumulator.AccumulatorBuilder;
|
||||
import org.springframework.data.mongodb.core.aggregation.ScriptOperators.Accumulator.AccumulatorInitBuilder;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
/**
|
||||
* Gateway to {@literal $function} and {@literal $accumulator} aggregation operations.
|
||||
* <p />
|
||||
* 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>.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
* @since 3.1
|
||||
*/
|
||||
public class ScriptOperators {
|
||||
|
||||
/**
|
||||
* Create a custom aggregation
|
||||
* <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}.
|
||||
*/
|
||||
public static Function function(String body) {
|
||||
return Function.function(body);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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}.
|
||||
*/
|
||||
public static AccumulatorInitBuilder accumulatorBuilder() {
|
||||
return new AccumulatorBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link Function} defines a custom aggregation
|
||||
* <a href="https://docs.mongodb.com/master/reference/operator/aggregation/function/">$function</a> in JavaScript.
|
||||
* <p />
|
||||
* <code class="java">
|
||||
* {
|
||||
* $function: {
|
||||
* body: ...,
|
||||
* args: ...,
|
||||
* lang: "js"
|
||||
* }
|
||||
* }
|
||||
* </code>
|
||||
* <p />
|
||||
* {@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>
|
||||
* execution must be
|
||||
* <a href="https://docs.mongodb.com/master/reference/configuration-options/#security.javascriptEnabled">enabled</a>
|
||||
*
|
||||
* @see <a href="https://docs.mongodb.com/master/reference/operator/aggregation/function/">MongoDB Documentation:
|
||||
* $function</a>
|
||||
*/
|
||||
public static class Function extends AbstractAggregationExpression {
|
||||
|
||||
private Function(Map<String, Object> values) {
|
||||
super(values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link Function} with the given function definition.
|
||||
*
|
||||
* @param body must not be {@literal null}.
|
||||
* @return new instance of {@link Function}.
|
||||
*/
|
||||
public static Function function(String body) {
|
||||
|
||||
Assert.notNull(body, "Function body must not be null!");
|
||||
|
||||
Map<String, Object> function = new LinkedHashMap<>(2);
|
||||
function.put(Fields.BODY.toString(), body);
|
||||
function.put(Fields.ARGS.toString(), Collections.emptyList());
|
||||
function.put(Fields.LANG.toString(), "js");
|
||||
|
||||
return new Function(function);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the arguments passed to the function body.
|
||||
*
|
||||
* @param args the arguments passed to the function body. Leave empty if the function does not take any arguments.
|
||||
* @return new instance of {@link Function}.
|
||||
*/
|
||||
public Function args(Object... args) {
|
||||
return args(Arrays.asList(args));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the arguments passed to the function body.
|
||||
*
|
||||
* @param args the arguments passed to the function body. Leave empty if the function does not take any arguments.
|
||||
* @return new instance of {@link Function}.
|
||||
*/
|
||||
public Function args(List<Object> args) {
|
||||
|
||||
Assert.notNull(args, "Args must not be null! Use an empty list instead.");
|
||||
|
||||
return new Function(appendAt(1, Fields.ARGS.toString(), args));
|
||||
}
|
||||
|
||||
/**
|
||||
* The language used in the body.
|
||||
*
|
||||
* @param lang must not be {@literal null} nor empty.
|
||||
* @return new instance of {@link Function}.
|
||||
*/
|
||||
public Function lang(String lang) {
|
||||
|
||||
Assert.hasText(lang, "Lang must not be null nor empty! The default would be 'js'.");
|
||||
|
||||
return new Function(appendAt(2, Fields.LANG.toString(), lang));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
List<Object> getArgs() {
|
||||
return get(Fields.ARGS.toString());
|
||||
}
|
||||
|
||||
String getBody() {
|
||||
return get(Fields.BODY.toString());
|
||||
}
|
||||
|
||||
String getLang() {
|
||||
return get(Fields.LANG.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getMongoMethod() {
|
||||
return "$function";
|
||||
}
|
||||
|
||||
enum Fields {
|
||||
|
||||
BODY, ARGS, LANG;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name().toLowerCase();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link Accumulator} defines a custom aggregation
|
||||
* <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 />
|
||||
* <code class="java">
|
||||
* {
|
||||
* $accumulator: {
|
||||
* init: ...,
|
||||
* intArgs: ...,
|
||||
* accumulate: ...,
|
||||
* accumulateArgs: ...,
|
||||
* merge: ...,
|
||||
* finalize: ...,
|
||||
* lang: "js"
|
||||
* }
|
||||
* }
|
||||
* </code>
|
||||
* <p />
|
||||
* {@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>
|
||||
* execution must be
|
||||
* <a href="https://docs.mongodb.com/master/reference/configuration-options/#security.javascriptEnabled">enabled</a>
|
||||
*
|
||||
* @see <a href="https://docs.mongodb.com/master/reference/operator/aggregation/accumulator/">MongoDB Documentation:
|
||||
* $accumulator</a>
|
||||
*/
|
||||
public static class Accumulator extends AbstractAggregationExpression {
|
||||
|
||||
private Accumulator(Map<String, Object> value) {
|
||||
super(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getMongoMethod() {
|
||||
return "$accumulator";
|
||||
}
|
||||
|
||||
enum Fields {
|
||||
|
||||
ACCUMULATE("accumulate"), //
|
||||
ACCUMULATE_ARGS("accumulateArgs"), //
|
||||
FINALIZE("finalize"), //
|
||||
INIT("init"), //
|
||||
INIT_ARGS("initArgs"), //
|
||||
LANG("lang"), //
|
||||
MERGE("merge"); //
|
||||
|
||||
private String field;
|
||||
|
||||
Fields(String field) {
|
||||
this.field = field;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return field;
|
||||
}
|
||||
}
|
||||
|
||||
public interface AccumulatorInitBuilder {
|
||||
|
||||
/**
|
||||
* 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 />
|
||||
* <code class="java">
|
||||
* function(initArg1, initArg2, ...) {
|
||||
* ...
|
||||
* return initialState
|
||||
* }
|
||||
* </code>
|
||||
*
|
||||
* @param function must not be {@literal null}.
|
||||
* @return this.
|
||||
*/
|
||||
default AccumulatorAccumulateBuilder init(Function function) {
|
||||
return init(function.getBody()).initArgs(function.getArgs());
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 />
|
||||
* <code class="java">
|
||||
* function(initArg1, initArg2, ...) {
|
||||
* ...
|
||||
* return initialState
|
||||
* }
|
||||
* </code>
|
||||
*
|
||||
* @param function must not be {@literal null}.
|
||||
* @return this.
|
||||
*/
|
||||
AccumulatorInitArgsBuilder init(String function);
|
||||
|
||||
/**
|
||||
* The language used in the {@code $accumulator} code.
|
||||
*
|
||||
* @param lang must not be {@literal null}. Default is {@literal js}.
|
||||
* @return this.
|
||||
*/
|
||||
AccumulatorInitBuilder lang(String lang);
|
||||
}
|
||||
|
||||
public interface AccumulatorInitArgsBuilder extends AccumulatorAccumulateBuilder {
|
||||
|
||||
/**
|
||||
* Define the optional {@code initArgs} for the {@link AccumulatorInitBuilder#init(String)} function.
|
||||
*
|
||||
* @param args must not be {@literal null}.
|
||||
* @return this.
|
||||
*/
|
||||
default AccumulatorAccumulateBuilder initArgs(Object... args) {
|
||||
return initArgs(Arrays.asList(args));
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the optional {@code initArgs} for the {@link AccumulatorInitBuilder#init(String)} function.
|
||||
*
|
||||
* @param args must not be {@literal null}.
|
||||
* @return this.
|
||||
*/
|
||||
AccumulatorAccumulateBuilder initArgs(List<Object> args);
|
||||
}
|
||||
|
||||
public interface AccumulatorAccumulateBuilder {
|
||||
|
||||
/**
|
||||
* 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 />
|
||||
* <code class="java">
|
||||
* function(state, accumArg1, accumArg2, ...) {
|
||||
* ...
|
||||
* return newState
|
||||
* }
|
||||
* </code>
|
||||
*
|
||||
* @param function must not be {@literal null}.
|
||||
* @return this.
|
||||
*/
|
||||
default AccumulatorMergeBuilder accumulate(Function function) {
|
||||
return accumulate(function.getBody()).accumulateArgs(function.getArgs());
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 />
|
||||
* <code class="java">
|
||||
* function(state, accumArg1, accumArg2, ...) {
|
||||
* ...
|
||||
* return newState
|
||||
* }
|
||||
* </code>
|
||||
*
|
||||
* @param function must not be {@literal null}.
|
||||
* @return this.
|
||||
*/
|
||||
AccumulatorAccumulateArgsBuilder accumulate(String function);
|
||||
}
|
||||
|
||||
public interface AccumulatorAccumulateArgsBuilder extends AccumulatorMergeBuilder {
|
||||
|
||||
/**
|
||||
* Define additional {@code accumulateArgs} for the {@link AccumulatorAccumulateBuilder#accumulate(String)}
|
||||
* function.
|
||||
*
|
||||
* @param args must not be {@literal null}.
|
||||
* @return this.
|
||||
*/
|
||||
default AccumulatorMergeBuilder accumulateArgs(Object... args) {
|
||||
return accumulateArgs(Arrays.asList(args));
|
||||
}
|
||||
|
||||
/**
|
||||
* Define additional {@code accumulateArgs} for the {@link AccumulatorAccumulateBuilder#accumulate(String)}
|
||||
* function.
|
||||
*
|
||||
* @param args must not be {@literal null}.
|
||||
* @return this.
|
||||
*/
|
||||
AccumulatorMergeBuilder accumulateArgs(List<Object> args);
|
||||
}
|
||||
|
||||
public interface AccumulatorMergeBuilder {
|
||||
|
||||
/**
|
||||
* 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 />
|
||||
* <code class="java">
|
||||
* function(state1, state2) {
|
||||
* ...
|
||||
* return newState
|
||||
* }
|
||||
* </code>
|
||||
*
|
||||
* @param function must not be {@literal null}.
|
||||
* @return this.
|
||||
*/
|
||||
AccumulatorFinalizeBuilder merge(String function);
|
||||
}
|
||||
|
||||
public interface AccumulatorFinalizeBuilder {
|
||||
|
||||
/**
|
||||
* Set the {@code finalize} function used to update the result of the accumulation when all documents have been
|
||||
* processed.
|
||||
* <p />
|
||||
* <code class="java">
|
||||
* function(state) {
|
||||
* ...
|
||||
* return finalState
|
||||
* }
|
||||
* </code>
|
||||
*
|
||||
* @param function must not be {@literal null}.
|
||||
* @return new instance of {@link Accumulator}.
|
||||
*/
|
||||
Accumulator finalize(String function);
|
||||
|
||||
/**
|
||||
* Build the {@link Accumulator} object without specifying a {@link #finalize(String) finalize function}.
|
||||
*
|
||||
* @return new instance of {@link Accumulator}.
|
||||
*/
|
||||
Accumulator build();
|
||||
}
|
||||
|
||||
static class AccumulatorBuilder
|
||||
implements AccumulatorInitBuilder, AccumulatorInitArgsBuilder, AccumulatorAccumulateBuilder,
|
||||
AccumulatorAccumulateArgsBuilder, AccumulatorMergeBuilder, AccumulatorFinalizeBuilder {
|
||||
|
||||
private List<Object> initArgs;
|
||||
private String initFunction;
|
||||
private List<Object> accumulateArgs;
|
||||
private String accumulateFunction;
|
||||
private String mergeFunction;
|
||||
private String finalizeFunction;
|
||||
private String lang = "js";
|
||||
|
||||
/**
|
||||
* 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 />
|
||||
* <code class="java">
|
||||
* function(initArg1, initArg2, ...) {
|
||||
* ...
|
||||
* return initialState
|
||||
* }
|
||||
* </code>
|
||||
*
|
||||
* @param function must not be {@literal null}.
|
||||
* @return this.
|
||||
*/
|
||||
@Override
|
||||
public AccumulatorBuilder init(String function) {
|
||||
|
||||
this.initFunction = function;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the optional {@code initArgs} for the {@link #init(String)} function.
|
||||
*
|
||||
* @param function must not be {@literal null}.
|
||||
* @return this.
|
||||
*/
|
||||
@Override
|
||||
public AccumulatorBuilder initArgs(List<Object> args) {
|
||||
|
||||
Assert.notNull(args, "Args must not be null");
|
||||
|
||||
this.initArgs = new ArrayList<>(args);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 />
|
||||
* <code class="java">
|
||||
* function(state, accumArg1, accumArg2, ...) {
|
||||
* ...
|
||||
* return newState
|
||||
* }
|
||||
* </code>
|
||||
*
|
||||
* @param function must not be {@literal null}.
|
||||
* @return this.
|
||||
*/
|
||||
@Override
|
||||
public AccumulatorBuilder accumulate(String function) {
|
||||
|
||||
Assert.notNull(function, "Accumulate function must not be null");
|
||||
|
||||
this.accumulateFunction = function;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Define additional {@code accumulateArgs} for the {@link #accumulate(String)} function.
|
||||
*
|
||||
* @param args must not be {@literal null}.
|
||||
* @return this.
|
||||
*/
|
||||
@Override
|
||||
public AccumulatorBuilder accumulateArgs(List<Object> args) {
|
||||
|
||||
Assert.notNull(args, "Args must not be null");
|
||||
|
||||
this.accumulateArgs = new ArrayList<>(args);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 />
|
||||
* <code class="java">
|
||||
* function(state1, state2) {
|
||||
* ...
|
||||
* return newState
|
||||
* }
|
||||
* </code>
|
||||
*
|
||||
* @param function must not be {@literal null}.
|
||||
* @return this.
|
||||
*/
|
||||
@Override
|
||||
public AccumulatorBuilder merge(String function) {
|
||||
|
||||
Assert.notNull(function, "Merge function must not be null");
|
||||
|
||||
this.mergeFunction = function;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The language used in the {@code $accumulator} code.
|
||||
*
|
||||
* @param lang must not be {@literal null}. Default is {@literal js}.
|
||||
* @return this.
|
||||
*/
|
||||
public AccumulatorBuilder lang(String lang) {
|
||||
|
||||
Assert.hasText(lang, "Lang must not be null nor empty! The default would be 'js'.");
|
||||
|
||||
this.lang = lang;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@code finalize} function used to update the result of the accumulation when all documents have been
|
||||
* processed.
|
||||
* <p />
|
||||
* <code class="java">
|
||||
* function(state) {
|
||||
* ...
|
||||
* return finalState
|
||||
* }
|
||||
* </code>
|
||||
*
|
||||
* @param function must not be {@literal null}.
|
||||
* @return new instance of {@link Accumulator}.
|
||||
*/
|
||||
@Override
|
||||
public Accumulator finalize(String function) {
|
||||
|
||||
Assert.notNull(function, "Finalize function must not be null");
|
||||
|
||||
this.finalizeFunction = function;
|
||||
|
||||
Map<String, Object> args = createArgumentMap();
|
||||
args.put(Fields.FINALIZE.toString(), finalizeFunction);
|
||||
|
||||
return new Accumulator(args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Accumulator build() {
|
||||
return new Accumulator(createArgumentMap());
|
||||
}
|
||||
|
||||
private Map<String, Object> createArgumentMap() {
|
||||
|
||||
Map<String, Object> args = new LinkedHashMap<>();
|
||||
args.put(Fields.INIT.toString(), initFunction);
|
||||
if (!CollectionUtils.isEmpty(initArgs)) {
|
||||
args.put(Fields.INIT_ARGS.toString(), initArgs);
|
||||
}
|
||||
args.put(Fields.ACCUMULATE.toString(), accumulateFunction);
|
||||
if (!CollectionUtils.isEmpty(accumulateArgs)) {
|
||||
args.put(Fields.ACCUMULATE_ARGS.toString(), accumulateArgs);
|
||||
}
|
||||
args.put(Fields.MERGE.toString(), mergeFunction);
|
||||
args.put(Fields.LANG.toString(), lang);
|
||||
|
||||
return args;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,6 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.bson.Document;
|
||||
import org.springframework.data.mapping.PersistentEntity;
|
||||
import org.springframework.data.mapping.PersistentPropertyPath;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.mongodb.core.aggregation.ExposedFields.DirectFieldReference;
|
||||
@@ -30,7 +29,6 @@ import org.springframework.data.mongodb.core.aggregation.ExposedFields.FieldRefe
|
||||
import org.springframework.data.mongodb.core.convert.QueryMapper;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
|
||||
import org.springframework.data.util.Lazy;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@@ -48,7 +46,6 @@ public class TypeBasedAggregationOperationContext implements AggregationOperatio
|
||||
private final Class<?> type;
|
||||
private final MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext;
|
||||
private final QueryMapper mapper;
|
||||
private final Lazy<MongoPersistentEntity<?>> entity;
|
||||
|
||||
/**
|
||||
* Creates a new {@link TypeBasedAggregationOperationContext} for the given type, {@link MappingContext} and
|
||||
@@ -68,7 +65,6 @@ public class TypeBasedAggregationOperationContext implements AggregationOperatio
|
||||
this.type = type;
|
||||
this.mappingContext = mappingContext;
|
||||
this.mapper = mapper;
|
||||
this.entity = Lazy.of(() -> mappingContext.getPersistentEntity(type));
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -137,37 +133,16 @@ public class TypeBasedAggregationOperationContext implements AggregationOperatio
|
||||
*/
|
||||
@Override
|
||||
public AggregationOperationContext continueOnMissingFieldReference() {
|
||||
return continueOnMissingFieldReference(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* This toggle allows the {@link AggregationOperationContext context} to use any given field name without checking for
|
||||
* its existence. Typically the {@link AggregationOperationContext} fails when referencing unknown fields, those that
|
||||
* are not present in one of the previous stages or the input source, throughout the pipeline.
|
||||
*
|
||||
* @param type The domain type to map fields to.
|
||||
* @return a more relaxed {@link AggregationOperationContext}.
|
||||
* @since 3.1
|
||||
*/
|
||||
public AggregationOperationContext continueOnMissingFieldReference(Class<?> type) {
|
||||
return new RelaxedTypeBasedAggregationOperationContext(type, mappingContext, mapper);
|
||||
}
|
||||
|
||||
protected FieldReference getReferenceFor(Field field) {
|
||||
|
||||
if(entity.getNullable() == null) {
|
||||
return new DirectFieldReference(new ExposedField(field, true));
|
||||
}
|
||||
|
||||
PersistentPropertyPath<MongoPersistentProperty> propertyPath = mappingContext
|
||||
.getPersistentPropertyPath(field.getTarget(), type);
|
||||
.getPersistentPropertyPath(field.getTarget(), type);
|
||||
Field mappedField = field(field.getName(),
|
||||
propertyPath.toDotPath(MongoPersistentProperty.PropertyToFieldNameConverter.INSTANCE));
|
||||
propertyPath.toDotPath(MongoPersistentProperty.PropertyToFieldNameConverter.INSTANCE));
|
||||
|
||||
return new DirectFieldReference(new ExposedField(mappedField, true));
|
||||
}
|
||||
|
||||
public Class<?> getType() {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,164 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020-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.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.bson.Document;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* The <a href="https://docs.mongodb.com/master/reference/operator/aggregation/unionWith/">$unionWith</a> aggregation
|
||||
* stage (available since MongoDB 4.4) performs a union of two collections by combining pipeline results, potentially
|
||||
* 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 />
|
||||
* 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
|
||||
* names to actual field names (considering potential {@link org.springframework.data.mongodb.core.mapping.Field}
|
||||
* annotations) make sure the enclosing aggregation is a {@link TypedAggregation} and provide the target type for the
|
||||
* {@code $unionWith} stage via {@link #mapFieldsTo(Class)}.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @see <a href="https://docs.mongodb.com/master/reference/operator/aggregation/unionWith/">Aggregation Pipeline Stage:
|
||||
* $unionWith</a>
|
||||
* @since 3.1
|
||||
*/
|
||||
public class UnionWithOperation implements AggregationOperation {
|
||||
|
||||
private final String collection;
|
||||
|
||||
private final @Nullable AggregationPipeline pipeline;
|
||||
|
||||
private final @Nullable Class<?> domainType;
|
||||
|
||||
public UnionWithOperation(String collection, @Nullable AggregationPipeline pipeline, @Nullable Class<?> domainType) {
|
||||
|
||||
Assert.notNull(collection, "Collection must not be null!");
|
||||
|
||||
this.collection = collection;
|
||||
this.pipeline = pipeline;
|
||||
this.domainType = domainType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the name of the collection from which pipeline results should be included in the result set.<br />
|
||||
* The collection name is used to set the {@code coll} parameter of {@code $unionWith}.
|
||||
*
|
||||
* @param collection the MongoDB collection name. Must not be {@literal null}.
|
||||
* @return new instance of {@link UnionWithOperation}.
|
||||
* @throws IllegalArgumentException if the required argument is {@literal null}.
|
||||
*/
|
||||
public static UnionWithOperation unionWith(String collection) {
|
||||
return new UnionWithOperation(collection, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link AggregationPipeline} to apply to the specified collection. The pipeline corresponds to the optional
|
||||
* {@code pipeline} field of the {@code $unionWith} aggregation stage and is used to compute the documents going into
|
||||
* the result set.
|
||||
*
|
||||
* @param pipeline the {@link AggregationPipeline} that computes the documents. Must not be {@literal null}.
|
||||
* @return new instance of {@link UnionWithOperation}.
|
||||
* @throws IllegalArgumentException if the required argument is {@literal null}.
|
||||
*/
|
||||
public UnionWithOperation pipeline(AggregationPipeline pipeline) {
|
||||
return new UnionWithOperation(collection, pipeline, domainType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the aggregation pipeline stages to apply to the specified collection. The pipeline corresponds to the optional
|
||||
* {@code pipeline} field of the {@code $unionWith} aggregation stage and is used to compute the documents going into
|
||||
* the result set.
|
||||
*
|
||||
* @param aggregationStages the aggregation pipeline stages that compute the documents. Must not be {@literal null}.
|
||||
* @return new instance of {@link UnionWithOperation}.
|
||||
* @throws IllegalArgumentException if the required argument is {@literal null}.
|
||||
*/
|
||||
public UnionWithOperation pipeline(List<AggregationOperation> aggregationStages) {
|
||||
return new UnionWithOperation(collection, new AggregationPipeline(aggregationStages), domainType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the aggregation pipeline stages to apply to the specified collection. The pipeline corresponds to the optional
|
||||
* {@code pipeline} field of the {@code $unionWith} aggregation stage and is used to compute the documents going into
|
||||
* the result set.
|
||||
*
|
||||
* @param aggregationStages the aggregation pipeline stages that compute the documents. Must not be {@literal null}.
|
||||
* @return new instance of {@link UnionWithOperation}.
|
||||
* @throws IllegalArgumentException if the required argument is {@literal null}.
|
||||
*/
|
||||
public UnionWithOperation pipeline(AggregationOperation... aggregationStages) {
|
||||
return new UnionWithOperation(collection, new AggregationPipeline(Arrays.asList(aggregationStages)), domainType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set domain type used for field name mapping of property references used by the {@link AggregationPipeline}.
|
||||
* Remember to also use a {@link TypedAggregation} in the outer pipeline.<br />
|
||||
* If not set, field names used within {@link AggregationOperation pipeline operations} are taken as is.
|
||||
*
|
||||
* @param domainType the domain type to map field names used in pipeline operations to. Must not be {@literal null}.
|
||||
* @return new instance of {@link UnionWithOperation}.
|
||||
* @throws IllegalArgumentException if the required argument is {@literal null}.
|
||||
*/
|
||||
public UnionWithOperation mapFieldsTo(Class<?> domainType) {
|
||||
|
||||
Assert.notNull(domainType, "DomainType must not be null!");
|
||||
return new UnionWithOperation(collection, pipeline, domainType);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.aggregation.AggregationOperation#toDocument(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext)
|
||||
*/
|
||||
@Override
|
||||
public Document toDocument(AggregationOperationContext context) {
|
||||
|
||||
Document $unionWith = new Document("coll", collection);
|
||||
if (pipeline == null || pipeline.isEmpty()) {
|
||||
return new Document(getOperator(), $unionWith);
|
||||
}
|
||||
|
||||
$unionWith.append("pipeline", pipeline.toDocuments(computeContext(context)));
|
||||
return new Document(getOperator(), $unionWith);
|
||||
}
|
||||
|
||||
private AggregationOperationContext computeContext(AggregationOperationContext source) {
|
||||
|
||||
if (source instanceof TypeBasedAggregationOperationContext) {
|
||||
return ((TypeBasedAggregationOperationContext) source).continueOnMissingFieldReference(domainType != null ? domainType : Object.class);
|
||||
}
|
||||
|
||||
if (source instanceof ExposedFieldsAggregationOperationContext) {
|
||||
return computeContext(((ExposedFieldsAggregationOperationContext) source).getRootContext());
|
||||
}
|
||||
|
||||
return source;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.aggregation.AggregationOperation#getOperator()
|
||||
*/
|
||||
@Override
|
||||
public String getOperator() {
|
||||
return "$unionWith";
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,7 @@ import com.mongodb.DBRef;
|
||||
* @author Mark Paluch
|
||||
* @since 1.4
|
||||
*/
|
||||
public interface DbRefResolver extends ReferenceResolver {
|
||||
public interface DbRefResolver {
|
||||
|
||||
/**
|
||||
* Resolves the given {@link DBRef} into an object of the given {@link MongoPersistentProperty}'s type. The method
|
||||
|
||||
@@ -15,6 +15,13 @@
|
||||
*/
|
||||
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;
|
||||
@@ -22,18 +29,28 @@ 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;
|
||||
@@ -50,11 +67,13 @@ import com.mongodb.client.model.Filters;
|
||||
* @author Mark Paluch
|
||||
* @since 1.4
|
||||
*/
|
||||
public class DefaultDbRefResolver extends DefaultReferenceResolver implements DbRefResolver, ReferenceResolver {
|
||||
public class DefaultDbRefResolver implements DbRefResolver {
|
||||
|
||||
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}.
|
||||
@@ -63,11 +82,11 @@ public class DefaultDbRefResolver extends DefaultReferenceResolver implements Db
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -95,8 +114,17 @@ public class DefaultDbRefResolver extends DefaultReferenceResolver implements Db
|
||||
*/
|
||||
@Override
|
||||
public Document fetch(DBRef dbRef) {
|
||||
return getReferenceLoader().fetchOne(DocumentReferenceQuery.forSingleDocument(Filters.eq("_id", dbRef.getId())),
|
||||
ReferenceCollection.fromDBRef(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();
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -137,7 +165,7 @@ public class DefaultDbRefResolver extends DefaultReferenceResolver implements Db
|
||||
}
|
||||
|
||||
List<Document> result = mongoCollection //
|
||||
.find(new Document(BasicMongoPersistentProperty.ID_FIELD_NAME, new Document("$in", ids))) //
|
||||
.find(new Document("_id", new Document("$in", ids))) //
|
||||
.into(new ArrayList<>());
|
||||
|
||||
return ids.stream() //
|
||||
@@ -157,9 +185,44 @@ public class DefaultDbRefResolver extends DefaultReferenceResolver implements Db
|
||||
private Object createLazyLoadingProxy(MongoPersistentProperty property, @Nullable DBRef dbref,
|
||||
DbRefResolverCallback callback, DbRefProxyHandler handler) {
|
||||
|
||||
Object lazyLoadingProxy = getProxyFactory().createLazyLoadingProxy(property, callback, dbref);
|
||||
Class<?> propertyType = property.getType();
|
||||
LazyLoadingInterceptor interceptor = new LazyLoadingInterceptor(property, dbref, exceptionTranslator, callback);
|
||||
|
||||
return handler.populateId(property, dbref, lazyLoadingProxy);
|
||||
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();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -182,10 +245,253 @@ public class DefaultDbRefResolver extends DefaultReferenceResolver implements Db
|
||||
private static Stream<Document> documentWithId(Object identifier, Collection<Document> documents) {
|
||||
|
||||
return documents.stream() //
|
||||
.filter(it -> it.get(BasicMongoPersistentProperty.ID_FIELD_NAME).equals(identifier)) //
|
||||
.filter(it -> it.get("_id").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}.
|
||||
*
|
||||
@@ -198,10 +504,4 @@ public class DefaultDbRefResolver extends DefaultReferenceResolver implements Db
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,113 +0,0 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,6 @@ package org.springframework.data.mongodb.core.convert;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
|
||||
import org.bson.Document;
|
||||
import org.bson.conversions.Bson;
|
||||
@@ -67,19 +66,6 @@ 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
|
||||
@@ -133,7 +119,6 @@ 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");
|
||||
}
|
||||
|
||||
@@ -1,237 +0,0 @@
|
||||
/*
|
||||
* 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 java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.WeakHashMap;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.bson.Document;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
import org.springframework.data.annotation.Reference;
|
||||
import org.springframework.data.mapping.PersistentPropertyAccessor;
|
||||
import org.springframework.data.mapping.PersistentPropertyPath;
|
||||
import org.springframework.data.mapping.PropertyPath;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.mapping.model.BeanWrapperPropertyAccessorFactory;
|
||||
import org.springframework.data.mongodb.core.mapping.DocumentPointer;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
|
||||
|
||||
/**
|
||||
* Internal API to construct {@link DocumentPointer} for a given property. Considers {@link LazyLoadingProxy},
|
||||
* registered {@link Object} to {@link DocumentPointer} {@link org.springframework.core.convert.converter.Converter},
|
||||
* simple {@literal _id} lookups and cases where the {@link DocumentPointer} needs to be computed via a lookup query.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 3.3
|
||||
*/
|
||||
class DocumentPointerFactory {
|
||||
|
||||
private final ConversionService conversionService;
|
||||
private final MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext;
|
||||
private final Map<String, LinkageDocument> cache;
|
||||
|
||||
/**
|
||||
* A {@link Pattern} matching quoted and unquoted variants (with/out whitespaces) of
|
||||
* <code>{'_id' : ?#{#target} }</code>.
|
||||
*/
|
||||
private static final Pattern DEFAULT_LOOKUP_PATTERN = Pattern.compile("\\{\\s?" + // document start (whitespace opt)
|
||||
"['\"]?_id['\"]?" + // followed by an optionally quoted _id. Like: _id, '_id' or "_id"
|
||||
"?\\s?:\\s?" + // then a colon optionally wrapped inside whitespaces
|
||||
"['\"]?\\?#\\{#target\\}['\"]?" + // leading to the potentially quoted ?#{#target} expression
|
||||
"\\s*}"); // some optional whitespaces and document close
|
||||
|
||||
DocumentPointerFactory(ConversionService conversionService,
|
||||
MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext) {
|
||||
|
||||
this.conversionService = conversionService;
|
||||
this.mappingContext = mappingContext;
|
||||
this.cache = new WeakHashMap<>();
|
||||
}
|
||||
|
||||
DocumentPointer<?> computePointer(
|
||||
MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext,
|
||||
MongoPersistentProperty property, Object value, Class<?> typeHint) {
|
||||
|
||||
if (value instanceof LazyLoadingProxy) {
|
||||
return () -> ((LazyLoadingProxy) value).getSource();
|
||||
}
|
||||
|
||||
if (conversionService.canConvert(typeHint, DocumentPointer.class)) {
|
||||
return conversionService.convert(value, DocumentPointer.class);
|
||||
}
|
||||
|
||||
MongoPersistentEntity<?> persistentEntity = mappingContext
|
||||
.getRequiredPersistentEntity(property.getAssociationTargetType());
|
||||
|
||||
if (usesDefaultLookup(property)) {
|
||||
return () -> persistentEntity.getIdentifierAccessor(value).getIdentifier();
|
||||
}
|
||||
|
||||
MongoPersistentEntity<?> valueEntity = mappingContext.getPersistentEntity(value.getClass());
|
||||
PersistentPropertyAccessor<Object> propertyAccessor;
|
||||
if (valueEntity == null) {
|
||||
propertyAccessor = BeanWrapperPropertyAccessorFactory.INSTANCE.getPropertyAccessor(property.getOwner(), value);
|
||||
} else {
|
||||
propertyAccessor = valueEntity.getPropertyPathAccessor(value);
|
||||
}
|
||||
|
||||
return cache.computeIfAbsent(property.getDocumentReference().lookup(), LinkageDocument::from)
|
||||
.getDocumentPointer(mappingContext, persistentEntity, propertyAccessor);
|
||||
}
|
||||
|
||||
private boolean usesDefaultLookup(MongoPersistentProperty property) {
|
||||
|
||||
if (property.isDocumentReference()) {
|
||||
return DEFAULT_LOOKUP_PATTERN.matcher(property.getDocumentReference().lookup()).matches();
|
||||
}
|
||||
|
||||
Reference atReference = property.findAnnotation(Reference.class);
|
||||
if (atReference != null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
throw new IllegalStateException(String.format("%s does not seem to be define Reference", property));
|
||||
}
|
||||
|
||||
/**
|
||||
* Value object that computes a document pointer from a given lookup query by identifying SpEL expressions and
|
||||
* inverting it.
|
||||
*
|
||||
* <pre class="code">
|
||||
* // source
|
||||
* { 'firstname' : ?#{fn}, 'lastname' : '?#{ln} }
|
||||
*
|
||||
* // target
|
||||
* { 'fn' : ..., 'ln' : ... }
|
||||
* </pre>
|
||||
*
|
||||
* The actual pointer is the computed via
|
||||
* {@link #getDocumentPointer(MappingContext, MongoPersistentEntity, PersistentPropertyAccessor)} applying values from
|
||||
* the provided {@link PersistentPropertyAccessor} to the target document by looking at the keys of the expressions
|
||||
* from the source.
|
||||
*/
|
||||
static class LinkageDocument {
|
||||
|
||||
static final Pattern EXPRESSION_PATTERN = Pattern.compile("\\?#\\{#?(?<fieldName>[\\w\\d\\.\\-)]*)\\}");
|
||||
static final Pattern PLACEHOLDER_PATTERN = Pattern.compile("###_(?<index>\\d*)_###");
|
||||
|
||||
private final String lookup;
|
||||
private final org.bson.Document documentPointer;
|
||||
private final Map<String, String> placeholderMap;
|
||||
|
||||
static LinkageDocument from(String lookup) {
|
||||
return new LinkageDocument(lookup);
|
||||
}
|
||||
|
||||
private LinkageDocument(String lookup) {
|
||||
|
||||
this.lookup = lookup;
|
||||
this.placeholderMap = new LinkedHashMap<>();
|
||||
|
||||
int index = 0;
|
||||
Matcher matcher = EXPRESSION_PATTERN.matcher(lookup);
|
||||
String targetLookup = lookup;
|
||||
|
||||
while (matcher.find()) {
|
||||
|
||||
String expression = matcher.group();
|
||||
String fieldName = matcher.group("fieldName").replace("target.", "");
|
||||
|
||||
String placeholder = placeholder(index);
|
||||
placeholderMap.put(placeholder, fieldName);
|
||||
targetLookup = targetLookup.replace(expression, "'" + placeholder + "'");
|
||||
index++;
|
||||
}
|
||||
|
||||
this.documentPointer = org.bson.Document.parse(targetLookup);
|
||||
}
|
||||
|
||||
private String placeholder(int index) {
|
||||
return "###_" + index + "_###";
|
||||
}
|
||||
|
||||
private boolean isPlaceholder(String key) {
|
||||
return PLACEHOLDER_PATTERN.matcher(key).matches();
|
||||
}
|
||||
|
||||
DocumentPointer<Object> getDocumentPointer(
|
||||
MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext,
|
||||
MongoPersistentEntity<?> persistentEntity, PersistentPropertyAccessor<?> propertyAccessor) {
|
||||
return () -> updatePlaceholders(documentPointer, new Document(), mappingContext, persistentEntity,
|
||||
propertyAccessor);
|
||||
}
|
||||
|
||||
Document updatePlaceholders(org.bson.Document source, org.bson.Document target,
|
||||
MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext,
|
||||
MongoPersistentEntity<?> persistentEntity, PersistentPropertyAccessor<?> propertyAccessor) {
|
||||
|
||||
for (Entry<String, Object> entry : source.entrySet()) {
|
||||
|
||||
if (entry.getKey().startsWith("$")) {
|
||||
throw new InvalidDataAccessApiUsageException(String.format(
|
||||
"Cannot derive document pointer from lookup '%s' using query operator (%s). Please consider registering a custom converter.",
|
||||
lookup, entry.getKey()));
|
||||
}
|
||||
|
||||
if (entry.getValue() instanceof Document) {
|
||||
|
||||
MongoPersistentProperty persistentProperty = persistentEntity.getPersistentProperty(entry.getKey());
|
||||
if (persistentProperty != null && persistentProperty.isEntity()) {
|
||||
|
||||
MongoPersistentEntity<?> nestedEntity = mappingContext.getPersistentEntity(persistentProperty.getType());
|
||||
target.put(entry.getKey(), updatePlaceholders((Document) entry.getValue(), new Document(), mappingContext,
|
||||
nestedEntity, nestedEntity.getPropertyAccessor(propertyAccessor.getProperty(persistentProperty))));
|
||||
} else {
|
||||
target.put(entry.getKey(), updatePlaceholders((Document) entry.getValue(), new Document(), mappingContext,
|
||||
persistentEntity, propertyAccessor));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (placeholderMap.containsKey(entry.getValue())) {
|
||||
|
||||
String attribute = placeholderMap.get(entry.getValue());
|
||||
if (attribute.contains(".")) {
|
||||
attribute = attribute.substring(attribute.lastIndexOf('.') + 1);
|
||||
}
|
||||
|
||||
String fieldName = entry.getKey().equals("_id") ? "id" : entry.getKey();
|
||||
if (!fieldName.contains(".")) {
|
||||
|
||||
Object targetValue = propertyAccessor.getProperty(persistentEntity.getPersistentProperty(fieldName));
|
||||
target.put(attribute, targetValue);
|
||||
continue;
|
||||
}
|
||||
|
||||
PersistentPropertyPath<?> path = mappingContext
|
||||
.getPersistentPropertyPath(PropertyPath.from(fieldName, persistentEntity.getTypeInformation()));
|
||||
Object targetValue = propertyAccessor.getProperty(path);
|
||||
target.put(attribute, targetValue);
|
||||
continue;
|
||||
}
|
||||
|
||||
target.put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
return target;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,18 +15,18 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core.convert;
|
||||
|
||||
import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver.LazyLoadingInterceptor;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
import com.mongodb.DBRef;
|
||||
|
||||
/**
|
||||
* Allows direct interaction with the underlying {@code LazyLoadingInterceptor}.
|
||||
* Allows direct interaction with the underlying {@link LazyLoadingInterceptor}.
|
||||
*
|
||||
* @author Thomas Darimont
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
* @since 1.5
|
||||
* @see LazyLoadingProxyFactory
|
||||
*/
|
||||
public interface LazyLoadingProxy {
|
||||
|
||||
@@ -46,15 +46,4 @@ public interface LazyLoadingProxy {
|
||||
*/
|
||||
@Nullable
|
||||
DBRef toDBRef();
|
||||
|
||||
/**
|
||||
* Returns the raw {@literal source} object that defines the reference.
|
||||
*
|
||||
* @return can be {@literal null}.
|
||||
* @since 3.3
|
||||
*/
|
||||
@Nullable
|
||||
default Object getSource() {
|
||||
return toDBRef();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,303 +0,0 @@
|
||||
/*
|
||||
* 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.util.ReflectionUtils.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.aopalliance.intercept.MethodInterceptor;
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
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.support.PersistenceExceptionTranslator;
|
||||
import org.springframework.data.mongodb.ClientSessionException;
|
||||
import org.springframework.data.mongodb.LazyLoadingException;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.objenesis.ObjenesisStd;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
import com.mongodb.DBRef;
|
||||
|
||||
/**
|
||||
* {@link ProxyFactory} to create a proxy for {@link MongoPersistentProperty#getType()} to resolve a reference lazily.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
class LazyLoadingProxyFactory {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(LazyLoadingProxyFactory.class);
|
||||
|
||||
private final ObjenesisStd objenesis;
|
||||
|
||||
private final PersistenceExceptionTranslator exceptionTranslator;
|
||||
|
||||
public LazyLoadingProxyFactory(PersistenceExceptionTranslator exceptionTranslator) {
|
||||
this.exceptionTranslator = exceptionTranslator;
|
||||
this.objenesis = new ObjenesisStd(true);
|
||||
}
|
||||
|
||||
public Object createLazyLoadingProxy(MongoPersistentProperty property, DbRefResolverCallback callback,
|
||||
Object source) {
|
||||
|
||||
Class<?> propertyType = property.getType();
|
||||
LazyLoadingInterceptor interceptor = new LazyLoadingInterceptor(property, callback, source, exceptionTranslator);
|
||||
|
||||
if (!propertyType.isInterface()) {
|
||||
|
||||
Factory factory = (Factory) objenesis.newInstance(getEnhancedTypeFor(propertyType));
|
||||
factory.setCallbacks(new Callback[] { interceptor });
|
||||
|
||||
return factory;
|
||||
}
|
||||
|
||||
ProxyFactory proxyFactory = new ProxyFactory();
|
||||
|
||||
for (Class<?> type : propertyType.getInterfaces()) {
|
||||
proxyFactory.addInterface(type);
|
||||
}
|
||||
|
||||
proxyFactory.addInterface(LazyLoadingProxy.class);
|
||||
proxyFactory.addInterface(propertyType);
|
||||
proxyFactory.addAdvice(interceptor);
|
||||
|
||||
return 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();
|
||||
}
|
||||
|
||||
public static class LazyLoadingInterceptor
|
||||
implements MethodInterceptor, org.springframework.cglib.proxy.MethodInterceptor, Serializable {
|
||||
|
||||
private static final Method INITIALIZE_METHOD, TO_DBREF_METHOD, FINALIZE_METHOD, GET_SOURCE_METHOD;
|
||||
|
||||
static {
|
||||
try {
|
||||
INITIALIZE_METHOD = LazyLoadingProxy.class.getMethod("getTarget");
|
||||
TO_DBREF_METHOD = LazyLoadingProxy.class.getMethod("toDBRef");
|
||||
FINALIZE_METHOD = Object.class.getDeclaredMethod("finalize");
|
||||
GET_SOURCE_METHOD = LazyLoadingProxy.class.getMethod("getSource");
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private final MongoPersistentProperty property;
|
||||
private final DbRefResolverCallback callback;
|
||||
private final Object source;
|
||||
private final PersistenceExceptionTranslator exceptionTranslator;
|
||||
private volatile boolean resolved;
|
||||
private @Nullable Object result;
|
||||
|
||||
public LazyLoadingInterceptor(MongoPersistentProperty property, DbRefResolverCallback callback, Object source,
|
||||
PersistenceExceptionTranslator exceptionTranslator) {
|
||||
|
||||
this.property = property;
|
||||
this.callback = callback;
|
||||
this.source = source;
|
||||
this.exceptionTranslator = exceptionTranslator;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Object invoke(MethodInvocation invocation) throws Throwable {
|
||||
return intercept(invocation.getThis(), invocation.getMethod(), invocation.getArguments(), null);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Object intercept(Object o, Method method, Object[] args, MethodProxy proxy) throws Throwable {
|
||||
|
||||
if (INITIALIZE_METHOD.equals(method)) {
|
||||
return ensureResolved();
|
||||
}
|
||||
|
||||
if (TO_DBREF_METHOD.equals(method)) {
|
||||
return source instanceof DBRef ? source : null;
|
||||
}
|
||||
|
||||
if (GET_SOURCE_METHOD.equals(method)) {
|
||||
return source;
|
||||
}
|
||||
|
||||
if (isObjectMethod(method) && Object.class.equals(method.getDeclaringClass())) {
|
||||
|
||||
if (ReflectionUtils.isToStringMethod(method)) {
|
||||
return proxyToString(source);
|
||||
}
|
||||
|
||||
if (ReflectionUtils.isEqualsMethod(method)) {
|
||||
return proxyEquals(o, args[0]);
|
||||
}
|
||||
|
||||
if (ReflectionUtils.isHashCodeMethod(method)) {
|
||||
return proxyHashCode();
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Object ensureResolved() {
|
||||
|
||||
if (!resolved) {
|
||||
this.result = resolve();
|
||||
this.resolved = true;
|
||||
}
|
||||
|
||||
return this.result;
|
||||
}
|
||||
|
||||
private String proxyToString(@Nullable Object source) {
|
||||
|
||||
StringBuilder description = new StringBuilder();
|
||||
if (source != null) {
|
||||
if (source instanceof DBRef) {
|
||||
description.append(((DBRef) source).getCollectionName());
|
||||
description.append(":");
|
||||
description.append(((DBRef) source).getId());
|
||||
} else {
|
||||
description.append(source);
|
||||
}
|
||||
} else {
|
||||
description.append(System.identityHashCode(source));
|
||||
}
|
||||
description.append("$").append(LazyLoadingProxy.class.getSimpleName());
|
||||
|
||||
return description.toString();
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
private int proxyHashCode() {
|
||||
return proxyToString(source).hashCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
|
||||
@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 = 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -26,7 +26,6 @@ import org.springframework.data.convert.TypeMapper;
|
||||
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.data.util.TypeInformation;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
@@ -145,9 +144,9 @@ public interface MongoConverter
|
||||
try {
|
||||
return getConversionService().canConvert(id.getClass(), targetType)
|
||||
? getConversionService().convert(id, targetType)
|
||||
: convertToMongoType(id, (TypeInformation<?>) null);
|
||||
: convertToMongoType(id, null);
|
||||
} catch (ConversionException o_O) {
|
||||
return convertToMongoType(id,(TypeInformation<?>) null);
|
||||
return convertToMongoType(id, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,18 +27,11 @@ import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Currency;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import org.bson.BsonReader;
|
||||
import org.bson.BsonTimestamp;
|
||||
import org.bson.BsonWriter;
|
||||
import org.bson.Document;
|
||||
import org.bson.codecs.Codec;
|
||||
import org.bson.codecs.DecoderContext;
|
||||
import org.bson.codecs.EncoderContext;
|
||||
import org.bson.codecs.configuration.CodecRegistries;
|
||||
import org.bson.types.Binary;
|
||||
import org.bson.types.Code;
|
||||
import org.bson.types.Decimal128;
|
||||
@@ -52,12 +45,11 @@ import org.springframework.data.convert.ReadingConverter;
|
||||
import org.springframework.data.convert.WritingConverter;
|
||||
import org.springframework.data.mongodb.core.query.Term;
|
||||
import org.springframework.data.mongodb.core.script.NamedMongoScript;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.NumberUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.mongodb.MongoClientSettings;
|
||||
|
||||
/**
|
||||
* Wrapper class to contain useful converters for the usage with Mongo.
|
||||
*
|
||||
@@ -244,27 +236,9 @@ abstract class MongoConverters {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
private final Codec<Document> codec = CodecRegistries.fromRegistries(CodecRegistries.fromCodecs(new Codec<UUID>() {
|
||||
|
||||
@Override
|
||||
public void encode(BsonWriter writer, UUID value, EncoderContext encoderContext) {
|
||||
writer.writeString(value.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<UUID> getEncoderClass() {
|
||||
return UUID.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID decode(BsonReader reader, DecoderContext decoderContext) {
|
||||
throw new IllegalStateException("decode not supported");
|
||||
}
|
||||
}), MongoClientSettings.getDefaultCodecRegistry()).get(Document.class);
|
||||
|
||||
@Override
|
||||
public String convert(Document source) {
|
||||
return source.toJson(codec);
|
||||
return source.toJson();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -294,7 +268,7 @@ abstract class MongoConverters {
|
||||
@Override
|
||||
public NamedMongoScript convert(Document source) {
|
||||
|
||||
if (source.isEmpty()) {
|
||||
if(source.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
/*
|
||||
* 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 org.bson.Document;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.data.mongodb.MongoDatabaseFactory;
|
||||
import org.springframework.data.mongodb.MongoDatabaseUtils;
|
||||
import org.springframework.data.mongodb.core.convert.ReferenceResolver.ReferenceCollection;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.mongodb.client.MongoCollection;
|
||||
|
||||
/**
|
||||
* {@link ReferenceLoader} implementation using a {@link MongoDatabaseFactory} to obtain raw {@link Document documents}
|
||||
* for linked entities via a {@link ReferenceLoader.DocumentReferenceQuery}.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 3.3
|
||||
*/
|
||||
public class MongoDatabaseFactoryReferenceLoader implements ReferenceLoader {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(MongoDatabaseFactoryReferenceLoader.class);
|
||||
|
||||
private final MongoDatabaseFactory mongoDbFactory;
|
||||
|
||||
/**
|
||||
* @param mongoDbFactory must not be {@literal null}.
|
||||
*/
|
||||
public MongoDatabaseFactoryReferenceLoader(MongoDatabaseFactory mongoDbFactory) {
|
||||
|
||||
Assert.notNull(mongoDbFactory, "MongoDbFactory translator must not be null!");
|
||||
|
||||
this.mongoDbFactory = mongoDbFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<Document> fetchMany(DocumentReferenceQuery referenceQuery, ReferenceCollection context) {
|
||||
|
||||
MongoCollection<Document> collection = getCollection(context);
|
||||
|
||||
if (LOGGER.isTraceEnabled()) {
|
||||
LOGGER.trace("Bulk fetching {} from {}.{}.", referenceQuery,
|
||||
StringUtils.hasText(context.getDatabase()) ? context.getDatabase()
|
||||
: collection.getNamespace().getDatabaseName(),
|
||||
context.getCollection());
|
||||
}
|
||||
|
||||
return referenceQuery.apply(collection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain the {@link MongoCollection} for a given {@link ReferenceCollection} from the underlying
|
||||
* {@link MongoDatabaseFactory}.
|
||||
*
|
||||
* @param context must not be {@literal null}.
|
||||
* @return the {@link MongoCollection} targeted by the {@link ReferenceCollection}.
|
||||
*/
|
||||
protected MongoCollection<Document> getCollection(ReferenceCollection context) {
|
||||
|
||||
return MongoDatabaseUtils.getDatabase(context.getDatabase(), mongoDbFactory).getCollection(context.getCollection(),
|
||||
Document.class);
|
||||
}
|
||||
}
|
||||
@@ -40,7 +40,6 @@ import org.springframework.data.mongodb.core.query.MongoRegexCreator;
|
||||
import org.springframework.data.mongodb.core.query.MongoRegexCreator.MatchMode;
|
||||
import org.springframework.data.mongodb.core.query.SerializationUtils;
|
||||
import org.springframework.data.mongodb.core.query.UntypedExampleMatcher;
|
||||
import org.springframework.data.mongodb.util.DotPath;
|
||||
import org.springframework.data.support.ExampleMatcherAccessor;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -135,7 +134,7 @@ public class MongoExampleMapper {
|
||||
while (iter.hasNext()) {
|
||||
|
||||
Map.Entry<String, Object> entry = iter.next();
|
||||
String propertyPath = DotPath.from(path).append(entry.getKey()).toString();
|
||||
String propertyPath = StringUtils.hasText(path) ? path + "." + entry.getKey() : entry.getKey();
|
||||
String mappedPropertyPath = getMappedPropertyPath(propertyPath, probeType);
|
||||
|
||||
if (isEmptyIdProperty(entry)) {
|
||||
|
||||
@@ -17,9 +17,6 @@ package org.springframework.data.mongodb.core.convert;
|
||||
|
||||
import org.bson.conversions.Bson;
|
||||
import org.springframework.data.convert.EntityWriter;
|
||||
import org.springframework.data.mongodb.core.mapping.DocumentPointer;
|
||||
import org.springframework.data.mongodb.core.mapping.DocumentReference;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.lang.Nullable;
|
||||
@@ -46,7 +43,7 @@ public interface MongoWriter<T> extends EntityWriter<T, Bson> {
|
||||
*/
|
||||
@Nullable
|
||||
default Object convertToMongoType(@Nullable Object obj) {
|
||||
return convertToMongoType(obj, (TypeInformation<?>) null);
|
||||
return convertToMongoType(obj, null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -60,10 +57,6 @@ public interface MongoWriter<T> extends EntityWriter<T, Bson> {
|
||||
@Nullable
|
||||
Object convertToMongoType(@Nullable Object obj, @Nullable TypeInformation<?> typeInformation);
|
||||
|
||||
default Object convertToMongoType(@Nullable Object obj, MongoPersistentEntity<?> entity) {
|
||||
return convertToMongoType(obj, entity.getTypeInformation());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link DBRef} to refer to the given object.
|
||||
*
|
||||
@@ -73,17 +66,4 @@ public interface MongoWriter<T> extends EntityWriter<T, Bson> {
|
||||
* @return will never be {@literal null}.
|
||||
*/
|
||||
DBRef toDBRef(Object object, @Nullable MongoPersistentProperty referingProperty);
|
||||
|
||||
/**
|
||||
* Creates a the {@link DocumentPointer} representing the link to another entity.
|
||||
*
|
||||
* @param source the object to create a document link to.
|
||||
* @param referringProperty the client-side property referring to the object which might carry additional metadata for
|
||||
* the {@link DBRef} object to create. Can be {@literal null}.
|
||||
* @return will never be {@literal null}.
|
||||
* @since 3.3
|
||||
*/
|
||||
default DocumentPointer<?> toDocumentPointer(Object source, @Nullable MongoPersistentProperty referringProperty) {
|
||||
return () -> toDBRef(source, referringProperty);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ package org.springframework.data.mongodb.core.convert;
|
||||
import java.util.List;
|
||||
|
||||
import org.bson.Document;
|
||||
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
@@ -70,11 +69,4 @@ public enum NoOpDbRefResolver implements DbRefResolver {
|
||||
private <T> T handle() throws UnsupportedOperationException {
|
||||
throw new UnsupportedOperationException("DBRef resolution is not supported!");
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Object resolveReference(MongoPersistentProperty property, Object source,
|
||||
ReferenceLookupDelegate referenceLookupDelegate, MongoEntityReader entityReader) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,17 +19,13 @@ import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.bson.BsonValue;
|
||||
import org.bson.Document;
|
||||
import org.bson.conversions.Bson;
|
||||
import org.bson.types.ObjectId;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.data.annotation.Reference;
|
||||
import org.springframework.data.domain.Example;
|
||||
import org.springframework.data.mapping.Association;
|
||||
import org.springframework.data.mapping.MappingException;
|
||||
@@ -40,14 +36,12 @@ import org.springframework.data.mapping.PropertyPath;
|
||||
import org.springframework.data.mapping.PropertyReferenceException;
|
||||
import org.springframework.data.mapping.context.InvalidPersistentPropertyPath;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.mongodb.MongoExpression;
|
||||
import org.springframework.data.mongodb.core.convert.MappingMongoConverter.NestedDocument;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty.PropertyToFieldNameConverter;
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
import org.springframework.data.mongodb.util.BsonUtils;
|
||||
import org.springframework.data.mongodb.util.DotPath;
|
||||
import org.springframework.data.util.ClassTypeInformation;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.lang.Nullable;
|
||||
@@ -69,12 +63,9 @@ import com.mongodb.DBRef;
|
||||
* @author Thomas Darimont
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
* @author David Julia
|
||||
*/
|
||||
public class QueryMapper {
|
||||
|
||||
protected static final Logger LOGGER = LoggerFactory.getLogger(QueryMapper.class);
|
||||
|
||||
private static final List<String> DEFAULT_ID_NAMES = Arrays.asList("id", "_id");
|
||||
private static final Document META_TEXT_SCORE = new Document("$meta", "textScore");
|
||||
static final ClassTypeInformation<?> NESTED_DOCUMENT = ClassTypeInformation.from(NestedDocument.class);
|
||||
@@ -149,23 +140,9 @@ public class QueryMapper {
|
||||
try {
|
||||
|
||||
Field field = createPropertyField(entity, key, mappingContext);
|
||||
Entry<String, Object> entry = getMappedObjectForField(field, BsonUtils.get(query, key));
|
||||
|
||||
// TODO: move to dedicated method
|
||||
if (field.getProperty() != null && field.getProperty().isUnwrapped()) {
|
||||
|
||||
Object theNestedObject = BsonUtils.get(query, key);
|
||||
Document mappedValue = (Document) getMappedValue(field, theNestedObject);
|
||||
if (!StringUtils.hasText(field.getMappedKey())) {
|
||||
result.putAll(mappedValue);
|
||||
} else {
|
||||
result.put(field.getMappedKey(), mappedValue);
|
||||
}
|
||||
} else {
|
||||
|
||||
Entry<String, Object> entry = getMappedObjectForField(field, BsonUtils.get(query, key));
|
||||
|
||||
result.put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
result.put(entry.getKey(), entry.getValue());
|
||||
} catch (InvalidPersistentPropertyPath invalidPathException) {
|
||||
|
||||
// in case the object has not already been mapped
|
||||
@@ -196,7 +173,13 @@ public class QueryMapper {
|
||||
return new Document();
|
||||
}
|
||||
|
||||
Document mappedSort = mapFieldsToPropertyNames(sortObject, entity);
|
||||
Document mappedSort = new Document();
|
||||
for (Map.Entry<String, Object> entry : BsonUtils.asMap(sortObject).entrySet()) {
|
||||
|
||||
Field field = createPropertyField(entity, entry.getKey(), mappingContext);
|
||||
mappedSort.put(field.getMappedKey(), entry.getValue());
|
||||
}
|
||||
|
||||
mapMetaAttributes(mappedSort, entity, MetaMapping.WHEN_PRESENT);
|
||||
return mappedSort;
|
||||
}
|
||||
@@ -214,30 +197,11 @@ public class QueryMapper {
|
||||
|
||||
Assert.notNull(fieldsObject, "FieldsObject must not be null!");
|
||||
|
||||
Document mappedFields = mapFieldsToPropertyNames(fieldsObject, entity);
|
||||
Document mappedFields = fieldsObject.isEmpty() ? new Document() : getMappedObject(fieldsObject, entity);
|
||||
mapMetaAttributes(mappedFields, entity, MetaMapping.FORCE);
|
||||
return mappedFields;
|
||||
}
|
||||
|
||||
private Document mapFieldsToPropertyNames(Document fields, @Nullable MongoPersistentEntity<?> entity) {
|
||||
|
||||
if (fields.isEmpty()) {
|
||||
return new Document();
|
||||
|
||||
}
|
||||
Document target = new Document();
|
||||
for (Map.Entry<String, Object> entry : BsonUtils.asMap(filterUnwrappedObjects(fields, entity)).entrySet()) {
|
||||
|
||||
Field field = createPropertyField(entity, entry.getKey(), mappingContext);
|
||||
if (field.getProperty() != null && field.getProperty().isUnwrapped()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
target.put(field.getMappedKey(), entry.getValue());
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
private void mapMetaAttributes(Document source, @Nullable MongoPersistentEntity<?> entity, MetaMapping metaMapping) {
|
||||
|
||||
if (entity == null) {
|
||||
@@ -253,44 +217,6 @@ public class QueryMapper {
|
||||
}
|
||||
}
|
||||
|
||||
private Document filterUnwrappedObjects(Document fieldsObject, @Nullable MongoPersistentEntity<?> entity) {
|
||||
|
||||
if (fieldsObject.isEmpty() || entity == null) {
|
||||
return fieldsObject;
|
||||
}
|
||||
|
||||
Document target = new Document();
|
||||
|
||||
for (Entry<String, Object> field : fieldsObject.entrySet()) {
|
||||
|
||||
try {
|
||||
|
||||
PropertyPath path = PropertyPath.from(field.getKey(), entity.getTypeInformation());
|
||||
PersistentPropertyPath<MongoPersistentProperty> persistentPropertyPath = mappingContext
|
||||
.getPersistentPropertyPath(path);
|
||||
MongoPersistentProperty property = mappingContext.getPersistentPropertyPath(path).getRequiredLeafProperty();
|
||||
|
||||
if (property.isUnwrapped() && property.isEntity()) {
|
||||
|
||||
MongoPersistentEntity<?> unwrappedEntity = mappingContext.getRequiredPersistentEntity(property);
|
||||
|
||||
for (MongoPersistentProperty unwrappedProperty : unwrappedEntity) {
|
||||
|
||||
DotPath dotPath = DotPath.from(persistentPropertyPath.toDotPath()).append(unwrappedProperty.getName());
|
||||
target.put(dotPath.toString(), field.getValue());
|
||||
}
|
||||
|
||||
} else {
|
||||
target.put(field.getKey(), field.getValue());
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
target.put(field.getKey(), field.getValue());
|
||||
}
|
||||
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
private Document getMappedTextScoreField(MongoPersistentProperty property) {
|
||||
return new Document(property.getFieldName(), META_TEXT_SCORE);
|
||||
}
|
||||
@@ -307,10 +233,6 @@ public class QueryMapper {
|
||||
String key = field.getMappedKey();
|
||||
Object value;
|
||||
|
||||
if (rawValue instanceof MongoExpression) {
|
||||
return createMapEntry(key, getMappedObject(((MongoExpression) rawValue).toDocument(), field.getEntity()));
|
||||
}
|
||||
|
||||
if (isNestedKeyword(rawValue) && !field.isIdField()) {
|
||||
Keyword keyword = new Keyword((Document) rawValue);
|
||||
value = getMappedKeyword(field, keyword);
|
||||
@@ -456,10 +378,6 @@ public class QueryMapper {
|
||||
}
|
||||
}
|
||||
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isNestedKeyword(value)) {
|
||||
return getMappedKeyword(new Keyword((Bson) value), documentField.getPropertyEntity());
|
||||
}
|
||||
@@ -579,11 +497,6 @@ public class QueryMapper {
|
||||
*/
|
||||
@Nullable
|
||||
protected Object delegateConvertToMongoType(Object source, @Nullable MongoPersistentEntity<?> entity) {
|
||||
|
||||
if (entity != null && entity.isUnwrapped()) {
|
||||
return converter.convertToMongoType(source, entity);
|
||||
}
|
||||
|
||||
return converter.convertToMongoType(source, entity == null ? null : entity.getTypeInformation());
|
||||
}
|
||||
|
||||
@@ -621,7 +534,7 @@ public class QueryMapper {
|
||||
if (source instanceof Iterable) {
|
||||
BasicDBList result = new BasicDBList();
|
||||
for (Object element : (Iterable<?>) source) {
|
||||
result.add(createReferenceFor(element, property));
|
||||
result.add(createDbRefFor(element, property));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -630,12 +543,12 @@ public class QueryMapper {
|
||||
Document result = new Document();
|
||||
Document dbObject = (Document) source;
|
||||
for (String key : dbObject.keySet()) {
|
||||
result.put(key, createReferenceFor(dbObject.get(key), property));
|
||||
result.put(key, createDbRefFor(dbObject.get(key), property));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
return createReferenceFor(source, property);
|
||||
return createDbRefFor(source, property);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -682,17 +595,12 @@ public class QueryMapper {
|
||||
return Collections.singletonMap(key, value).entrySet().iterator().next();
|
||||
}
|
||||
|
||||
private Object createReferenceFor(Object source, MongoPersistentProperty property) {
|
||||
private DBRef createDbRefFor(Object source, MongoPersistentProperty property) {
|
||||
|
||||
if (source instanceof DBRef) {
|
||||
return (DBRef) source;
|
||||
}
|
||||
|
||||
if (property != null && (property.isDocumentReference()
|
||||
|| (!property.isDbReference() && property.findAnnotation(Reference.class) != null))) {
|
||||
return converter.toDocumentPointer(source, property).getPointer();
|
||||
}
|
||||
|
||||
return converter.toDBRef(source, property);
|
||||
}
|
||||
|
||||
@@ -727,7 +635,7 @@ public class QueryMapper {
|
||||
* @param candidate
|
||||
* @return
|
||||
*/
|
||||
protected boolean isNestedKeyword(@Nullable Object candidate) {
|
||||
protected boolean isNestedKeyword(Object candidate) {
|
||||
|
||||
if (!(candidate instanceof Document)) {
|
||||
return false;
|
||||
@@ -772,13 +680,12 @@ public class QueryMapper {
|
||||
* converted one by one.
|
||||
*
|
||||
* @param documentField the field and its meta data
|
||||
* @param value the actual value. Can be {@literal null}.
|
||||
* @param value the actual value
|
||||
* @return the potentially converted target value.
|
||||
*/
|
||||
@Nullable
|
||||
private Object applyFieldTargetTypeHintToValue(Field documentField, @Nullable Object value) {
|
||||
private Object applyFieldTargetTypeHintToValue(Field documentField, Object value) {
|
||||
|
||||
if (value == null || documentField.getProperty() == null || !documentField.getProperty().hasExplicitWriteTarget()) {
|
||||
if (documentField.getProperty() == null || !documentField.getProperty().hasExplicitWriteTarget()) {
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -809,6 +716,7 @@ public class QueryMapper {
|
||||
*/
|
||||
static class Keyword {
|
||||
|
||||
private static final String N_OR_PATTERN = "\\$.*or";
|
||||
private static final Set<String> NON_DBREF_CONVERTING_KEYWORDS = new HashSet<>(
|
||||
Arrays.asList("$", "$size", "$slice", "$gt", "$lt"));
|
||||
|
||||
@@ -839,7 +747,7 @@ public class QueryMapper {
|
||||
}
|
||||
|
||||
public boolean isOrOrNor() {
|
||||
return key.equalsIgnoreCase("$or") || key.equalsIgnoreCase("$nor");
|
||||
return key.matches(N_OR_PATTERN);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -959,11 +867,6 @@ public class QueryMapper {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
MongoPersistentEntity<?> getEntity() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the field represents an association.
|
||||
*
|
||||
@@ -1009,7 +912,6 @@ public class QueryMapper {
|
||||
public TypeInformation<?> getTypeHint() {
|
||||
return ClassTypeInformation.OBJECT;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1116,12 +1018,6 @@ public class QueryMapper {
|
||||
return property == null ? null : mappingContext.getPersistentEntity(property);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public MongoPersistentEntity<?> getEntity() {
|
||||
return entity;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.convert.QueryMapper.Field#isAssociation()
|
||||
@@ -1190,8 +1086,8 @@ public class QueryMapper {
|
||||
removePlaceholders(DOT_POSITIONAL_PATTERN, pathExpression));
|
||||
|
||||
if (sourceProperty != null && sourceProperty.getOwner().equals(entity)) {
|
||||
return mappingContext.getPersistentPropertyPath(
|
||||
PropertyPath.from(Pattern.quote(sourceProperty.getName()), entity.getTypeInformation()));
|
||||
return mappingContext
|
||||
.getPersistentPropertyPath(PropertyPath.from(sourceProperty.getName(), entity.getTypeInformation()));
|
||||
}
|
||||
|
||||
PropertyPath path = forName(rawPath);
|
||||
@@ -1199,47 +1095,29 @@ public class QueryMapper {
|
||||
return null;
|
||||
}
|
||||
|
||||
PersistentPropertyPath<MongoPersistentProperty> propertyPath = tryToResolvePersistentPropertyPath(path);
|
||||
|
||||
if (propertyPath == null) {
|
||||
|
||||
if (QueryMapper.LOGGER.isInfoEnabled()) {
|
||||
|
||||
String types = StringUtils.collectionToDelimitedString(
|
||||
path.stream().map(it -> it.getType().getSimpleName()).collect(Collectors.toList()), " -> ");
|
||||
QueryMapper.LOGGER.info(
|
||||
"Could not map '{}'. Maybe a fragment in '{}' is considered a simple type. Mapper continues with {}.",
|
||||
path, types, pathExpression);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Iterator<MongoPersistentProperty> iterator = propertyPath.iterator();
|
||||
boolean associationDetected = false;
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
|
||||
MongoPersistentProperty property = iterator.next();
|
||||
|
||||
if (property.isAssociation()) {
|
||||
associationDetected = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (associationDetected && !property.isIdProperty()) {
|
||||
throw new MappingException(String.format(INVALID_ASSOCIATION_REFERENCE, pathExpression));
|
||||
}
|
||||
}
|
||||
|
||||
return propertyPath;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private PersistentPropertyPath<MongoPersistentProperty> tryToResolvePersistentPropertyPath(PropertyPath path) {
|
||||
|
||||
try {
|
||||
return mappingContext.getPersistentPropertyPath(path);
|
||||
} catch (MappingException e) {
|
||||
|
||||
PersistentPropertyPath<MongoPersistentProperty> propertyPath = mappingContext.getPersistentPropertyPath(path);
|
||||
|
||||
Iterator<MongoPersistentProperty> iterator = propertyPath.iterator();
|
||||
boolean associationDetected = false;
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
|
||||
MongoPersistentProperty property = iterator.next();
|
||||
|
||||
if (property.isAssociation()) {
|
||||
associationDetected = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (associationDetected && !property.isIdProperty()) {
|
||||
throw new MappingException(String.format(INVALID_ASSOCIATION_REFERENCE, pathExpression));
|
||||
}
|
||||
}
|
||||
|
||||
return propertyPath;
|
||||
} catch (InvalidPersistentPropertyPath e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1268,13 +1146,6 @@ public class QueryMapper {
|
||||
return forName(path.substring(0, path.length() - 3) + "id");
|
||||
}
|
||||
|
||||
// Ok give it another try quoting
|
||||
try {
|
||||
return PropertyPath.from(Pattern.quote(path), entity.getTypeInformation());
|
||||
} catch (PropertyReferenceException | InvalidPersistentPropertyPath ex) {
|
||||
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1368,17 +1239,12 @@ public class QueryMapper {
|
||||
static class KeyMapper {
|
||||
|
||||
private final Iterator<String> iterator;
|
||||
private int currentIndex;
|
||||
private String currentPropertyRoot;
|
||||
private final List<String> pathParts;
|
||||
|
||||
public KeyMapper(String key,
|
||||
MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext) {
|
||||
|
||||
this.pathParts = Arrays.asList(key.split("\\."));
|
||||
this.iterator = pathParts.iterator();
|
||||
this.currentPropertyRoot = iterator.next();
|
||||
this.currentIndex = 0;
|
||||
this.iterator = Arrays.asList(key.split("\\.")).iterator();
|
||||
this.iterator.next();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1390,31 +1256,21 @@ public class QueryMapper {
|
||||
protected String mapPropertyName(MongoPersistentProperty property) {
|
||||
|
||||
StringBuilder mappedName = new StringBuilder(PropertyToFieldNameConverter.INSTANCE.convert(property));
|
||||
|
||||
boolean inspect = iterator.hasNext();
|
||||
|
||||
while (inspect) {
|
||||
|
||||
String partial = iterator.next();
|
||||
currentIndex++;
|
||||
|
||||
boolean isPositional = isPositionalParameter(partial) && property.isCollectionLike() ;
|
||||
if(property.isMap() && currentPropertyRoot.equals(partial) && iterator.hasNext()){
|
||||
partial = iterator.next();
|
||||
currentIndex++;
|
||||
}
|
||||
boolean isPositional = isPositionalParameter(partial) && property.isCollectionLike();
|
||||
|
||||
if (isPositional || property.isMap() && !currentPropertyRoot.equals(partial)) {
|
||||
if (isPositional || property.isMap()) {
|
||||
mappedName.append(".").append(partial);
|
||||
}
|
||||
|
||||
inspect = isPositional && iterator.hasNext();
|
||||
}
|
||||
|
||||
if(currentIndex + 1 < pathParts.size()) {
|
||||
currentIndex++;
|
||||
currentPropertyRoot = pathParts.get(currentIndex);
|
||||
}
|
||||
return mappedName.toString();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,130 +0,0 @@
|
||||
/*
|
||||
* 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 java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
|
||||
import org.bson.Document;
|
||||
import org.bson.conversions.Bson;
|
||||
import org.springframework.data.mongodb.core.convert.ReferenceResolver.ReferenceCollection;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
import com.mongodb.client.MongoCollection;
|
||||
|
||||
/**
|
||||
* The {@link ReferenceLoader} obtains raw {@link Document documents} for linked entities via a
|
||||
* {@link ReferenceLoader.DocumentReferenceQuery}.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 3.3
|
||||
*/
|
||||
public interface ReferenceLoader {
|
||||
|
||||
/**
|
||||
* Obtain a single {@link Document} matching the given {@literal referenceQuery} in the {@literal context}.
|
||||
*
|
||||
* @param referenceQuery must not be {@literal null}.
|
||||
* @param context must not be {@literal null}.
|
||||
* @return the matching {@link Document} or {@literal null} if none found.
|
||||
*/
|
||||
@Nullable
|
||||
default Document fetchOne(DocumentReferenceQuery referenceQuery, ReferenceCollection context) {
|
||||
|
||||
Iterator<Document> it = fetchMany(referenceQuery, context).iterator();
|
||||
return it.hasNext() ? it.next() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain multiple {@link Document} matching the given {@literal referenceQuery} in the {@literal context}.
|
||||
*
|
||||
* @param referenceQuery must not be {@literal null}.
|
||||
* @param context must not be {@literal null}.
|
||||
* @return the matching {@link Document} or {@literal null} if none found.
|
||||
*/
|
||||
Iterable<Document> fetchMany(DocumentReferenceQuery referenceQuery, ReferenceCollection context);
|
||||
|
||||
/**
|
||||
* The {@link DocumentReferenceQuery} defines the criteria by which {@link Document documents} should be matched
|
||||
* applying potentially given order criteria.
|
||||
*/
|
||||
interface DocumentReferenceQuery {
|
||||
|
||||
/**
|
||||
* Get the query to obtain matching {@link Document documents}.
|
||||
*
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
Bson getQuery();
|
||||
|
||||
/**
|
||||
* Get the sort criteria for ordering results.
|
||||
*
|
||||
* @return an empty {@link Document} by default. Never {@literal null}.
|
||||
*/
|
||||
default Bson getSort() {
|
||||
return new Document();
|
||||
}
|
||||
|
||||
default Iterable<Document> apply(MongoCollection<Document> collection) {
|
||||
return restoreOrder(collection.find(getQuery()).sort(getSort()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore the order of fetched documents.
|
||||
*
|
||||
* @param documents must not be {@literal null}.
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
default Iterable<Document> restoreOrder(Iterable<Document> documents) {
|
||||
return documents;
|
||||
}
|
||||
|
||||
static DocumentReferenceQuery forSingleDocument(Bson bson) {
|
||||
|
||||
return new DocumentReferenceQuery() {
|
||||
|
||||
@Override
|
||||
public Bson getQuery() {
|
||||
return bson;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<Document> apply(MongoCollection<Document> collection) {
|
||||
|
||||
Document result = collection.find(getQuery()).sort(getSort()).limit(1).first();
|
||||
return result != null ? Collections.singleton(result) : Collections.emptyList();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
static DocumentReferenceQuery forManyDocuments(Bson bson) {
|
||||
|
||||
return new DocumentReferenceQuery() {
|
||||
|
||||
@Override
|
||||
public Bson getQuery() {
|
||||
return bson;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<Document> apply(MongoCollection<Document> collection) {
|
||||
return collection.find(getQuery()).sort(getSort());
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,451 +0,0 @@
|
||||
/*
|
||||
* 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 java.lang.annotation.Annotation;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.bson.Document;
|
||||
import org.bson.conversions.Bson;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.mapping.model.SpELContext;
|
||||
import org.springframework.data.mongodb.core.convert.ReferenceLoader.DocumentReferenceQuery;
|
||||
import org.springframework.data.mongodb.core.convert.ReferenceResolver.MongoEntityReader;
|
||||
import org.springframework.data.mongodb.core.convert.ReferenceResolver.ReferenceCollection;
|
||||
import org.springframework.data.mongodb.core.mapping.DocumentReference;
|
||||
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.data.mongodb.util.json.ParameterBindingContext;
|
||||
import org.springframework.data.mongodb.util.json.ParameterBindingDocumentCodec;
|
||||
import org.springframework.data.mongodb.util.json.ValueProvider;
|
||||
import org.springframework.data.util.Streamable;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.mongodb.DBRef;
|
||||
import com.mongodb.client.MongoCollection;
|
||||
|
||||
/**
|
||||
* A common delegate for {@link ReferenceResolver} implementations to resolve a reference to one/many target documents
|
||||
* that are converted to entities.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
* @since 3.3
|
||||
*/
|
||||
public final class ReferenceLookupDelegate {
|
||||
|
||||
private final MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext;
|
||||
private final SpELContext spELContext;
|
||||
private final ParameterBindingDocumentCodec codec;
|
||||
|
||||
/**
|
||||
* Create a new {@link ReferenceLookupDelegate}.
|
||||
*
|
||||
* @param mappingContext must not be {@literal null}.
|
||||
* @param spELContext must not be {@literal null}.
|
||||
*/
|
||||
public ReferenceLookupDelegate(
|
||||
MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext,
|
||||
SpELContext spELContext) {
|
||||
|
||||
Assert.notNull(mappingContext, "MappingContext must not be null");
|
||||
Assert.notNull(spELContext, "SpELContext must not be null");
|
||||
|
||||
this.mappingContext = mappingContext;
|
||||
this.spELContext = spELContext;
|
||||
this.codec = new ParameterBindingDocumentCodec();
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the reference expressed by the given property.
|
||||
*
|
||||
* @param property the reference defining property. Must not be {@literal null}. THe
|
||||
* @param value the source value identifying to the referenced entity. Must not be {@literal null}.
|
||||
* @param lookupFunction to execute a lookup query. Must not be {@literal null}.
|
||||
* @param entityReader the callback to convert raw source values into actual domain types. Must not be
|
||||
* {@literal null}.
|
||||
* @return can be {@literal null}.
|
||||
*/
|
||||
@Nullable
|
||||
public Object readReference(MongoPersistentProperty property, Object value, LookupFunction lookupFunction,
|
||||
MongoEntityReader entityReader) {
|
||||
|
||||
DocumentReferenceQuery filter = computeFilter(property, value, spELContext);
|
||||
ReferenceCollection referenceCollection = computeReferenceContext(property, value, spELContext);
|
||||
|
||||
Iterable<Document> result = lookupFunction.apply(filter, referenceCollection);
|
||||
|
||||
if (property.isCollectionLike()) {
|
||||
return entityReader.read(result, property.getTypeInformation());
|
||||
}
|
||||
|
||||
if (!result.iterator().hasNext()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Object resultValue = result.iterator().next();
|
||||
return resultValue != null ? entityReader.read(resultValue, property.getTypeInformation()) : null;
|
||||
}
|
||||
|
||||
private ReferenceCollection computeReferenceContext(MongoPersistentProperty property, Object value,
|
||||
SpELContext spELContext) {
|
||||
|
||||
// Use the first value as a reference for others in case of collection like
|
||||
if (value instanceof Iterable) {
|
||||
value = ((Iterable<?>) value).iterator().next();
|
||||
}
|
||||
|
||||
// handle DBRef value
|
||||
if (value instanceof DBRef) {
|
||||
return ReferenceCollection.fromDBRef((DBRef) value);
|
||||
}
|
||||
|
||||
String collection = mappingContext.getRequiredPersistentEntity(property.getAssociationTargetType()).getCollection();
|
||||
|
||||
if (value instanceof Document) {
|
||||
|
||||
Document documentPointer = (Document) value;
|
||||
|
||||
if (property.isDocumentReference()) {
|
||||
|
||||
ParameterBindingContext bindingContext = bindingContext(property, value, spELContext);
|
||||
DocumentReference documentReference = property.getDocumentReference();
|
||||
|
||||
String targetDatabase = parseValueOrGet(documentReference.db(), bindingContext,
|
||||
() -> documentPointer.get("db", String.class));
|
||||
String targetCollection = parseValueOrGet(documentReference.collection(), bindingContext,
|
||||
() -> documentPointer.get("collection", collection));
|
||||
return new ReferenceCollection(targetDatabase, targetCollection);
|
||||
}
|
||||
|
||||
return new ReferenceCollection(documentPointer.getString("db"), documentPointer.get("collection", collection));
|
||||
}
|
||||
|
||||
if (property.isDocumentReference()) {
|
||||
|
||||
ParameterBindingContext bindingContext = bindingContext(property, value, spELContext);
|
||||
DocumentReference documentReference = property.getDocumentReference();
|
||||
|
||||
String targetDatabase = parseValueOrGet(documentReference.db(), bindingContext, () -> null);
|
||||
String targetCollection = parseValueOrGet(documentReference.collection(), bindingContext, () -> collection);
|
||||
|
||||
return new ReferenceCollection(targetDatabase, targetCollection);
|
||||
}
|
||||
|
||||
return new ReferenceCollection(null, collection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use the given {@link ParameterBindingContext} to compute potential expressions against the value.
|
||||
*
|
||||
* @param value must not be {@literal null}.
|
||||
* @param bindingContext must not be {@literal null}.
|
||||
* @param defaultValue
|
||||
* @param <T>
|
||||
* @return can be {@literal null}.
|
||||
*/
|
||||
@Nullable
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> T parseValueOrGet(String value, ParameterBindingContext bindingContext, Supplier<T> defaultValue) {
|
||||
|
||||
if (!StringUtils.hasText(value)) {
|
||||
return defaultValue.get();
|
||||
}
|
||||
|
||||
// parameter binding requires a document, since we do not have one, construct it.
|
||||
if (!BsonUtils.isJsonDocument(value) && value.contains("?#{")) {
|
||||
String s = "{ 'target-value' : " + value + "}";
|
||||
T evaluated = (T) codec.decode(s, bindingContext).get("target-value");
|
||||
return evaluated != null ? evaluated : defaultValue.get();
|
||||
}
|
||||
|
||||
if (BsonUtils.isJsonDocument(value)) {
|
||||
return (T) codec.decode(value, bindingContext);
|
||||
}
|
||||
|
||||
T evaluated = (T) bindingContext.evaluateExpression(value);
|
||||
return evaluated != null ? evaluated : defaultValue.get();
|
||||
}
|
||||
|
||||
ParameterBindingContext bindingContext(MongoPersistentProperty property, Object source, SpELContext spELContext) {
|
||||
|
||||
return new ParameterBindingContext(valueProviderFor(source), spELContext.getParser(),
|
||||
() -> evaluationContextFor(property, source, spELContext));
|
||||
}
|
||||
|
||||
ValueProvider valueProviderFor(Object source) {
|
||||
|
||||
return (index) -> {
|
||||
if (source instanceof Document) {
|
||||
return Streamable.of(((Document) source).values()).toList().get(index);
|
||||
}
|
||||
return source;
|
||||
};
|
||||
}
|
||||
|
||||
EvaluationContext evaluationContextFor(MongoPersistentProperty property, Object source, SpELContext spELContext) {
|
||||
|
||||
EvaluationContext ctx = spELContext.getEvaluationContext(source);
|
||||
ctx.setVariable("target", source);
|
||||
ctx.setVariable(property.getName(), source);
|
||||
|
||||
return ctx;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the query to retrieve linked documents.
|
||||
*
|
||||
* @param property must not be {@literal null}.
|
||||
* @param value must not be {@literal null}.
|
||||
* @param spELContext must not be {@literal null}.
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
DocumentReferenceQuery computeFilter(MongoPersistentProperty property, Object value, SpELContext spELContext) {
|
||||
|
||||
DocumentReference documentReference = property.isDocumentReference() ? property.getDocumentReference()
|
||||
: ReferenceEmulatingDocumentReference.INSTANCE;
|
||||
|
||||
String lookup = documentReference.lookup();
|
||||
|
||||
Document sort = parseValueOrGet(documentReference.sort(), bindingContext(property, value, spELContext),
|
||||
() -> new Document());
|
||||
|
||||
if (property.isCollectionLike() && value instanceof Collection) {
|
||||
|
||||
List<Document> ors = new ArrayList<>();
|
||||
for (Object entry : (Collection<Object>) value) {
|
||||
|
||||
Document decoded = codec.decode(lookup, bindingContext(property, entry, spELContext));
|
||||
ors.add(decoded);
|
||||
}
|
||||
|
||||
return new ListDocumentReferenceQuery(new Document("$or", ors), sort);
|
||||
}
|
||||
|
||||
if (property.isMap() && value instanceof Map) {
|
||||
|
||||
Map<Object, Document> filterMap = new LinkedHashMap<>();
|
||||
|
||||
for (Entry<Object, Object> entry : ((Map<Object, Object>) value).entrySet()) {
|
||||
|
||||
Document decoded = codec.decode(lookup, bindingContext(property, entry.getValue(), spELContext));
|
||||
filterMap.put(entry.getKey(), decoded);
|
||||
}
|
||||
|
||||
return new MapDocumentReferenceQuery(new Document("$or", filterMap.values()), sort, filterMap);
|
||||
}
|
||||
|
||||
return new SingleDocumentReferenceQuery(codec.decode(lookup, bindingContext(property, value, spELContext)), sort);
|
||||
}
|
||||
|
||||
enum ReferenceEmulatingDocumentReference implements DocumentReference {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public Class<? extends Annotation> annotationType() {
|
||||
return DocumentReference.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String db() {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String collection() {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String lookup() {
|
||||
return "{ '_id' : ?#{#target} }";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String sort() {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean lazy() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link DocumentReferenceQuery} implementation fetching a single {@link Document}.
|
||||
*/
|
||||
static class SingleDocumentReferenceQuery implements DocumentReferenceQuery {
|
||||
|
||||
private final Document query;
|
||||
private final Document sort;
|
||||
|
||||
public SingleDocumentReferenceQuery(Document query, Document sort) {
|
||||
|
||||
this.query = query;
|
||||
this.sort = sort;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bson getQuery() {
|
||||
return query;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Document getSort() {
|
||||
return sort;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<Document> apply(MongoCollection<Document> collection) {
|
||||
|
||||
Document result = collection.find(getQuery()).sort(getSort()).limit(1).first();
|
||||
return result != null ? Collections.singleton(result) : Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link DocumentReferenceQuery} implementation to retrieve linked {@link Document documents} stored inside a
|
||||
* {@link Map} structure. Restores the original map order by matching individual query documents against the actual
|
||||
* values.
|
||||
*/
|
||||
static class MapDocumentReferenceQuery implements DocumentReferenceQuery {
|
||||
|
||||
private final Document query;
|
||||
private final Document sort;
|
||||
private final Map<Object, Document> filterOrderMap;
|
||||
|
||||
public MapDocumentReferenceQuery(Document query, Document sort, Map<Object, Document> filterOrderMap) {
|
||||
|
||||
this.query = query;
|
||||
this.sort = sort;
|
||||
this.filterOrderMap = filterOrderMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bson getQuery() {
|
||||
return query;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bson getSort() {
|
||||
return sort;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<Document> restoreOrder(Iterable<Document> documents) {
|
||||
|
||||
Map<String, Object> targetMap = new LinkedHashMap<>();
|
||||
List<Document> collected = documents instanceof List ? (List<Document>) documents
|
||||
: Streamable.of(documents).toList();
|
||||
|
||||
for (Entry<Object, Document> filterMapping : filterOrderMap.entrySet()) {
|
||||
|
||||
Optional<Document> first = collected.stream()
|
||||
.filter(it -> it.entrySet().containsAll(filterMapping.getValue().entrySet())).findFirst();
|
||||
|
||||
targetMap.put(filterMapping.getKey().toString(), first.orElse(null));
|
||||
}
|
||||
return Collections.singleton(new Document(targetMap));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link DocumentReferenceQuery} implementation to retrieve linked {@link Document documents} stored inside a
|
||||
* {@link Collection} like structure. Restores the original order by matching individual query documents against the
|
||||
* actual values.
|
||||
*/
|
||||
static class ListDocumentReferenceQuery implements DocumentReferenceQuery {
|
||||
|
||||
private final Document query;
|
||||
private final Document sort;
|
||||
|
||||
public ListDocumentReferenceQuery(Document query, Document sort) {
|
||||
|
||||
this.query = query;
|
||||
this.sort = sort;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<Document> restoreOrder(Iterable<Document> documents) {
|
||||
|
||||
List<Document> target = documents instanceof List ? (List<Document>) documents
|
||||
: Streamable.of(documents).toList();
|
||||
|
||||
if (!sort.isEmpty() || !query.containsKey("$or")) {
|
||||
return target;
|
||||
}
|
||||
|
||||
List<Document> ors = query.get("$or", List.class);
|
||||
return target.stream().sorted((o1, o2) -> compareAgainstReferenceIndex(ors, o1, o2)).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public Document getQuery() {
|
||||
return query;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Document getSort() {
|
||||
return sort;
|
||||
}
|
||||
|
||||
int compareAgainstReferenceIndex(List<Document> referenceList, Document document1, Document document2) {
|
||||
|
||||
for (Document document : referenceList) {
|
||||
|
||||
Set<Entry<String, Object>> entries = document.entrySet();
|
||||
if (document1.entrySet().containsAll(entries)) {
|
||||
return -1;
|
||||
}
|
||||
if (document2.entrySet().containsAll(entries)) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return referenceList.size();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The function that can execute a given {@link DocumentReferenceQuery} within the {@link ReferenceCollection} to
|
||||
* obtain raw results.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
interface LookupFunction {
|
||||
|
||||
/**
|
||||
* @param referenceQuery never {@literal null}.
|
||||
* @param referenceCollection never {@literal null}.
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
Iterable<Document> apply(DocumentReferenceQuery referenceQuery, ReferenceCollection referenceCollection);
|
||||
}
|
||||
}
|
||||
@@ -1,119 +0,0 @@
|
||||
/*
|
||||
* 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 org.springframework.data.mapping.PersistentProperty;
|
||||
import org.springframework.data.mongodb.MongoDatabaseFactory;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.mongodb.DBRef;
|
||||
|
||||
/**
|
||||
* The {@link ReferenceResolver} allows to load and convert linked entities.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 3.3
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface ReferenceResolver {
|
||||
|
||||
/**
|
||||
* Resolve the association defined via the given property from a given source value. May return a
|
||||
* {@link LazyLoadingProxy proxy instance} in case of a lazy loading association. The resolved value is assignable to
|
||||
* {@link PersistentProperty#getType()}.
|
||||
*
|
||||
* @param property the association defining property.
|
||||
* @param source the association source value.
|
||||
* @param referenceLookupDelegate the lookup executing component.
|
||||
* @param entityReader conversion function capable of constructing entities from raw source.
|
||||
* @return can be {@literal null}.
|
||||
*/
|
||||
@Nullable
|
||||
Object resolveReference(MongoPersistentProperty property, Object source,
|
||||
ReferenceLookupDelegate referenceLookupDelegate, MongoEntityReader entityReader);
|
||||
|
||||
/**
|
||||
* {@link ReferenceCollection} is a value object that contains information about the target database and collection
|
||||
* name of an association.
|
||||
*/
|
||||
class ReferenceCollection {
|
||||
|
||||
@Nullable //
|
||||
private final String database;
|
||||
private final String collection;
|
||||
|
||||
/**
|
||||
* @param database can be {@literal null} to indicate the configured default
|
||||
* {@link MongoDatabaseFactory#getMongoDatabase() database} should be used.
|
||||
* @param collection the target collection name. Must not be {@literal null}.
|
||||
*/
|
||||
public ReferenceCollection(@Nullable String database, String collection) {
|
||||
|
||||
Assert.hasText(collection, "Collection must not be empty or null!");
|
||||
|
||||
this.database = database;
|
||||
this.collection = collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new instance of {@link ReferenceCollection} from the given {@link DBRef}.
|
||||
*
|
||||
* @param dbRef must not be {@literal null}.
|
||||
* @return new instance of {@link ReferenceCollection}.
|
||||
*/
|
||||
public static ReferenceCollection fromDBRef(DBRef dbRef) {
|
||||
return new ReferenceCollection(dbRef.getDatabaseName(), dbRef.getCollectionName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the target collection name.
|
||||
*
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
public String getCollection() {
|
||||
return collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the target database name. If {@literal null} the default database should be used.
|
||||
*
|
||||
* @return can be {@literal null}.
|
||||
*/
|
||||
@Nullable
|
||||
public String getDatabase() {
|
||||
return database;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Domain type conversion callback interface that allows to read the {@code source} object into a mapped object.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
interface MongoEntityReader {
|
||||
|
||||
/**
|
||||
* Read values from the given source into an object defined via the given {@link TypeInformation}.
|
||||
*
|
||||
* @param source never {@literal null}.
|
||||
* @param typeInformation information about the desired target type.
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
Object read(Object source, TypeInformation<?> typeInformation);
|
||||
}
|
||||
}
|
||||
@@ -132,11 +132,6 @@ public class UpdateMapper extends QueryMapper {
|
||||
*/
|
||||
@Override
|
||||
protected Object delegateConvertToMongoType(Object source, @Nullable MongoPersistentEntity<?> entity) {
|
||||
|
||||
if(entity != null && entity.isUnwrapped()) {
|
||||
return converter.convertToMongoType(source, entity);
|
||||
}
|
||||
|
||||
return converter.convertToMongoType(source,
|
||||
entity == null ? ClassTypeInformation.OBJECT : getTypeHintForEntity(source, entity));
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ import org.springframework.data.geo.Point;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.core.Version;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
@@ -34,10 +34,7 @@ import com.fasterxml.jackson.databind.module.SimpleModule;
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
|
||||
/**
|
||||
* A Jackson {@link Module} to register custom {@link JsonDeserializer}s for GeoJSON types.
|
||||
* <p />
|
||||
* Use {@link #geoJsonModule()} to obtain a {@link Module} containing both {@link JsonSerializer serializers} and
|
||||
* {@link JsonDeserializer deserializers}.
|
||||
* A Jackson {@link Module} to register custom {@link JsonSerializer} and {@link JsonDeserializer}s for GeoJSON types.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author Oliver Gierke
|
||||
@@ -50,87 +47,12 @@ public class GeoJsonModule extends SimpleModule {
|
||||
|
||||
public GeoJsonModule() {
|
||||
|
||||
registerDeserializersIn(this);
|
||||
// TODO: add serializers as of next major version (4.0).
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a {@link Module} containing {@link JsonDeserializer deserializers} for the following {@link GeoJson} types:
|
||||
* <ul>
|
||||
* <li>{@link GeoJsonPoint}</li>
|
||||
* <li>{@link GeoJsonMultiPoint}</li>
|
||||
* <li>{@link GeoJsonLineString}</li>
|
||||
* <li>{@link GeoJsonMultiLineString}</li>
|
||||
* <li>{@link GeoJsonPolygon}</li>
|
||||
* <li>{@link GeoJsonMultiPolygon}</li>
|
||||
* </ul>
|
||||
*
|
||||
* @return a {@link Module} containing {@link JsonDeserializer deserializers} for {@link GeoJson} types.
|
||||
* @since 3.2
|
||||
*/
|
||||
public static Module deserializers() {
|
||||
|
||||
SimpleModule module = new SimpleModule("Spring Data MongoDB GeoJson - Deserializers",
|
||||
new Version(3, 2, 0, null, "org.springframework.data", "spring-data-mongodb-geojson"));
|
||||
registerDeserializersIn(module);
|
||||
return module;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a {@link Module} containing {@link JsonSerializer serializers} for the following {@link GeoJson} types:
|
||||
* <ul>
|
||||
* <li>{@link GeoJsonPoint}</li>
|
||||
* <li>{@link GeoJsonMultiPoint}</li>
|
||||
* <li>{@link GeoJsonLineString}</li>
|
||||
* <li>{@link GeoJsonMultiLineString}</li>
|
||||
* <li>{@link GeoJsonPolygon}</li>
|
||||
* <li>{@link GeoJsonMultiPolygon}</li>
|
||||
* </ul>
|
||||
*
|
||||
* @return a {@link Module} containing {@link JsonSerializer serializers} for {@link GeoJson} types.
|
||||
* @since 3.2
|
||||
*/
|
||||
public static Module serializers() {
|
||||
|
||||
SimpleModule module = new SimpleModule("Spring Data MongoDB GeoJson - Serializers",
|
||||
new Version(3, 2, 0, null, "org.springframework.data", "spring-data-mongodb-geojson"));
|
||||
GeoJsonSerializersModule.registerSerializersIn(module);
|
||||
return module;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a {@link Module} containing {@link JsonSerializer serializers} and {@link JsonDeserializer deserializers}
|
||||
* for the following {@link GeoJson} types:
|
||||
* <ul>
|
||||
* <li>{@link GeoJsonPoint}</li>
|
||||
* <li>{@link GeoJsonMultiPoint}</li>
|
||||
* <li>{@link GeoJsonLineString}</li>
|
||||
* <li>{@link GeoJsonMultiLineString}</li>
|
||||
* <li>{@link GeoJsonPolygon}</li>
|
||||
* <li>{@link GeoJsonMultiPolygon}</li>
|
||||
* </ul>
|
||||
*
|
||||
* @return a {@link Module} containing {@link JsonSerializer serializers} and {@link JsonDeserializer deserializers}
|
||||
* for {@link GeoJson} types.
|
||||
* @since 3.2
|
||||
*/
|
||||
public static Module geoJsonModule() {
|
||||
|
||||
SimpleModule module = new SimpleModule("Spring Data MongoDB GeoJson",
|
||||
new Version(3, 2, 0, null, "org.springframework.data", "spring-data-mongodb-geojson"));
|
||||
GeoJsonSerializersModule.registerSerializersIn(module);
|
||||
registerDeserializersIn(module);
|
||||
return module;
|
||||
}
|
||||
|
||||
private static void registerDeserializersIn(SimpleModule module) {
|
||||
|
||||
module.addDeserializer(GeoJsonPoint.class, new GeoJsonPointDeserializer());
|
||||
module.addDeserializer(GeoJsonMultiPoint.class, new GeoJsonMultiPointDeserializer());
|
||||
module.addDeserializer(GeoJsonLineString.class, new GeoJsonLineStringDeserializer());
|
||||
module.addDeserializer(GeoJsonMultiLineString.class, new GeoJsonMultiLineStringDeserializer());
|
||||
module.addDeserializer(GeoJsonPolygon.class, new GeoJsonPolygonDeserializer());
|
||||
module.addDeserializer(GeoJsonMultiPolygon.class, new GeoJsonMultiPolygonDeserializer());
|
||||
addDeserializer(GeoJsonPoint.class, new GeoJsonPointDeserializer());
|
||||
addDeserializer(GeoJsonMultiPoint.class, new GeoJsonMultiPointDeserializer());
|
||||
addDeserializer(GeoJsonLineString.class, new GeoJsonLineStringDeserializer());
|
||||
addDeserializer(GeoJsonMultiLineString.class, new GeoJsonMultiLineStringDeserializer());
|
||||
addDeserializer(GeoJsonPolygon.class, new GeoJsonPolygonDeserializer());
|
||||
addDeserializer(GeoJsonMultiPolygon.class, new GeoJsonMultiPolygonDeserializer());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -145,7 +67,8 @@ public class GeoJsonModule extends SimpleModule {
|
||||
*/
|
||||
@Nullable
|
||||
@Override
|
||||
public T deserialize(@Nullable JsonParser jp, @Nullable DeserializationContext ctxt) throws IOException {
|
||||
public T deserialize(@Nullable JsonParser jp, @Nullable DeserializationContext ctxt)
|
||||
throws IOException, JsonProcessingException {
|
||||
|
||||
JsonNode node = jp.readValueAsTree();
|
||||
JsonNode coordinates = node.get("coordinates");
|
||||
@@ -211,7 +134,7 @@ public class GeoJsonModule extends SimpleModule {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<Point> points = new ArrayList<>(node.size());
|
||||
List<Point> points = new ArrayList<Point>(node.size());
|
||||
|
||||
for (JsonNode coordinatePair : node) {
|
||||
if (coordinatePair.isArray()) {
|
||||
@@ -222,7 +145,7 @@ public class GeoJsonModule extends SimpleModule {
|
||||
}
|
||||
|
||||
protected GeoJsonLineString toLineString(ArrayNode node) {
|
||||
return new GeoJsonLineString(toPoints(node));
|
||||
return new GeoJsonLineString(toPoints((ArrayNode) node));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -336,7 +259,7 @@ public class GeoJsonModule extends SimpleModule {
|
||||
@Override
|
||||
protected GeoJsonMultiLineString doDeserialize(ArrayNode coordinates) {
|
||||
|
||||
List<GeoJsonLineString> lines = new ArrayList<>(coordinates.size());
|
||||
List<GeoJsonLineString> lines = new ArrayList<GeoJsonLineString>(coordinates.size());
|
||||
|
||||
for (JsonNode lineString : coordinates) {
|
||||
if (lineString.isArray()) {
|
||||
@@ -413,7 +336,7 @@ public class GeoJsonModule extends SimpleModule {
|
||||
@Override
|
||||
protected GeoJsonMultiPolygon doDeserialize(ArrayNode coordinates) {
|
||||
|
||||
List<GeoJsonPolygon> polygones = new ArrayList<>(coordinates.size());
|
||||
List<GeoJsonPolygon> polygones = new ArrayList<GeoJsonPolygon>(coordinates.size());
|
||||
|
||||
for (JsonNode polygon : coordinates) {
|
||||
for (JsonNode ring : polygon) {
|
||||
|
||||
@@ -1,313 +0,0 @@
|
||||
/*
|
||||
* 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.geo;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.springframework.data.geo.Point;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.databind.JsonSerializer;
|
||||
import com.fasterxml.jackson.databind.Module;
|
||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||
import com.fasterxml.jackson.databind.module.SimpleModule;
|
||||
|
||||
/**
|
||||
* A Jackson {@link Module} to register custom {@link JsonSerializer}s for GeoJSON types.
|
||||
*
|
||||
* @author Bjorn Harvold
|
||||
* @author Christoph Strobl
|
||||
* @since 3.2
|
||||
*/
|
||||
class GeoJsonSerializersModule extends SimpleModule {
|
||||
|
||||
private static final long serialVersionUID = 1340494654898895610L;
|
||||
|
||||
GeoJsonSerializersModule() {
|
||||
registerSerializersIn(this);
|
||||
}
|
||||
|
||||
|
||||
static void registerSerializersIn(SimpleModule module) {
|
||||
|
||||
module.addSerializer(GeoJsonPoint.class, new GeoJsonPointSerializer());
|
||||
module.addSerializer(GeoJsonMultiPoint.class, new GeoJsonMultiPointSerializer());
|
||||
module.addSerializer(GeoJsonLineString.class, new GeoJsonLineStringSerializer());
|
||||
module.addSerializer(GeoJsonMultiLineString.class, new GeoJsonMultiLineStringSerializer());
|
||||
module.addSerializer(GeoJsonPolygon.class, new GeoJsonPolygonSerializer());
|
||||
module.addSerializer(GeoJsonMultiPolygon.class, new GeoJsonMultiPolygonSerializer());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param <T>
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
private static abstract class GeoJsonSerializer<T extends GeoJson<? extends Iterable>> extends JsonSerializer<T> {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.fasterxml.jackson.databind.JsonSerializer#serialize(java.lang.Object, com.fasterxml.jackson.core.JsonGenerator, com.fasterxml.jackson.databind.SerializerProvider)
|
||||
*/
|
||||
@Override
|
||||
public void serialize(T shape, JsonGenerator jsonGenerator, SerializerProvider serializers) throws IOException {
|
||||
|
||||
jsonGenerator.writeStartObject();
|
||||
jsonGenerator.writeStringField("type", shape.getType());
|
||||
jsonGenerator.writeArrayFieldStart("coordinates");
|
||||
|
||||
doSerialize(shape, jsonGenerator);
|
||||
|
||||
jsonGenerator.writeEndArray();
|
||||
jsonGenerator.writeEndObject();
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the actual serialization given the {@literal shape} as {@link GeoJson}.
|
||||
*
|
||||
* @param shape
|
||||
* @param jsonGenerator
|
||||
* @return
|
||||
*/
|
||||
protected abstract void doSerialize(T shape, JsonGenerator jsonGenerator) throws IOException;
|
||||
|
||||
/**
|
||||
* Write a {@link Point} as array. <br />
|
||||
* {@code [10.0, 20.0]}
|
||||
*
|
||||
* @param point
|
||||
* @param jsonGenerator
|
||||
* @throws IOException
|
||||
*/
|
||||
protected void writePoint(Point point, JsonGenerator jsonGenerator) throws IOException {
|
||||
|
||||
jsonGenerator.writeStartArray();
|
||||
writeRawCoordinates(point, jsonGenerator);
|
||||
jsonGenerator.writeEndArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the {@link Point} coordinates. <br />
|
||||
* {@code 10.0, 20.0}
|
||||
*
|
||||
* @param point
|
||||
* @param jsonGenerator
|
||||
* @throws IOException
|
||||
*/
|
||||
protected void writeRawCoordinates(Point point, JsonGenerator jsonGenerator) throws IOException {
|
||||
|
||||
jsonGenerator.writeNumber(point.getX());
|
||||
jsonGenerator.writeNumber(point.getY());
|
||||
}
|
||||
|
||||
/**
|
||||
* Write an {@link Iterable} of {@link Point} as array. <br />
|
||||
* {@code [ [10.0, 20.0], [30.0, 40.0], [50.0, 60.0] ]}
|
||||
*
|
||||
* @param points
|
||||
* @param jsonGenerator
|
||||
* @throws IOException
|
||||
*/
|
||||
protected void writeLine(Iterable<Point> points, JsonGenerator jsonGenerator) throws IOException {
|
||||
|
||||
jsonGenerator.writeStartArray();
|
||||
writeRawLine(points, jsonGenerator);
|
||||
jsonGenerator.writeEndArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Write an {@link Iterable} of {@link Point}. <br />
|
||||
* {@code [10.0, 20.0], [30.0, 40.0], [50.0, 60.0]}
|
||||
*
|
||||
* @param points
|
||||
* @param jsonGenerator
|
||||
* @throws IOException
|
||||
*/
|
||||
protected void writeRawLine(Iterable<Point> points, JsonGenerator jsonGenerator) throws IOException {
|
||||
|
||||
for (Point point : points) {
|
||||
writePoint(point, jsonGenerator);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link JsonSerializer} converting {@link GeoJsonPoint} to:
|
||||
*
|
||||
* <pre>
|
||||
* <code>
|
||||
* { "type": "Point", "coordinates": [10.0, 20.0] }
|
||||
* </code>
|
||||
* </pre>
|
||||
*
|
||||
* @author Bjorn Harvold
|
||||
* @author Christoph Strobl
|
||||
* @since 3.2
|
||||
*/
|
||||
static class GeoJsonPointSerializer extends GeoJsonSerializer<GeoJsonPoint> {
|
||||
|
||||
@Override
|
||||
protected void doSerialize(GeoJsonPoint value, JsonGenerator jsonGenerator) throws IOException {
|
||||
writeRawCoordinates(value, jsonGenerator);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link JsonSerializer} converting {@link GeoJsonLineString} to:
|
||||
*
|
||||
* <pre>
|
||||
* <code>
|
||||
* {
|
||||
* "type": "LineString",
|
||||
* "coordinates": [
|
||||
* [10.0, 20.0], [30.0, 40.0], [50.0, 60.0]
|
||||
* ]
|
||||
* }
|
||||
* </code>
|
||||
* </pre>
|
||||
*
|
||||
* @author Bjorn Harvold
|
||||
* @author Christoph Strobl
|
||||
* @since 3.2
|
||||
*/
|
||||
static class GeoJsonLineStringSerializer extends GeoJsonSerializer<GeoJsonLineString> {
|
||||
|
||||
@Override
|
||||
protected void doSerialize(GeoJsonLineString value, JsonGenerator jsonGenerator) throws IOException {
|
||||
writeRawLine(value.getCoordinates(), jsonGenerator);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link JsonSerializer} converting {@link GeoJsonMultiPoint} to:
|
||||
*
|
||||
* <pre>
|
||||
* <code>
|
||||
* {
|
||||
* "type": "MultiPoint",
|
||||
* "coordinates": [
|
||||
* [10.0, 20.0], [30.0, 40.0], [50.0, 60.0]
|
||||
* ]
|
||||
* }
|
||||
* </code>
|
||||
* </pre>
|
||||
*
|
||||
* @author Bjorn Harvold
|
||||
* @author Christoph Strobl
|
||||
* @since 3.2
|
||||
*/
|
||||
static class GeoJsonMultiPointSerializer extends GeoJsonSerializer<GeoJsonMultiPoint> {
|
||||
|
||||
@Override
|
||||
protected void doSerialize(GeoJsonMultiPoint value, JsonGenerator jsonGenerator) throws IOException {
|
||||
writeRawLine(value.getCoordinates(), jsonGenerator);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link JsonSerializer} converting {@link GeoJsonMultiLineString} to:
|
||||
*
|
||||
* <pre>
|
||||
* <code>
|
||||
* {
|
||||
* "type": "MultiLineString",
|
||||
* "coordinates": [
|
||||
* [ [10.0, 20.0], [30.0, 40.0] ],
|
||||
* [ [50.0, 60.0] , [70.0, 80.0] ]
|
||||
* ]
|
||||
* }
|
||||
* </code>
|
||||
* </pre>
|
||||
*
|
||||
* @author Bjorn Harvold
|
||||
* @author Christoph Strobl
|
||||
* @since 3.2
|
||||
*/
|
||||
static class GeoJsonMultiLineStringSerializer extends GeoJsonSerializer<GeoJsonMultiLineString> {
|
||||
|
||||
@Override
|
||||
protected void doSerialize(GeoJsonMultiLineString value, JsonGenerator jsonGenerator) throws IOException {
|
||||
|
||||
for (GeoJsonLineString lineString : value.getCoordinates()) {
|
||||
writeLine(lineString.getCoordinates(), jsonGenerator);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link JsonSerializer} converting {@link GeoJsonPolygon} to:
|
||||
*
|
||||
* <pre>
|
||||
* <code>
|
||||
* {
|
||||
* "type": "Polygon",
|
||||
* "coordinates": [
|
||||
* [ [100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0] ]
|
||||
* ]
|
||||
* }
|
||||
* </code>
|
||||
* </pre>
|
||||
*
|
||||
* @author Bjorn Harvold
|
||||
* @author Christoph Strobl
|
||||
* @since 3.2
|
||||
*/
|
||||
static class GeoJsonPolygonSerializer extends GeoJsonSerializer<GeoJsonPolygon> {
|
||||
|
||||
@Override
|
||||
protected void doSerialize(GeoJsonPolygon value, JsonGenerator jsonGenerator) throws IOException {
|
||||
|
||||
for (GeoJsonLineString lineString : value.getCoordinates()) {
|
||||
writeLine(lineString.getCoordinates(), jsonGenerator);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link JsonSerializer} converting {@link GeoJsonMultiPolygon} to:
|
||||
*
|
||||
* <pre>
|
||||
* <code>
|
||||
* {
|
||||
* "type": "MultiPolygon",
|
||||
* "coordinates": [
|
||||
* [[[102.0, 2.0], [103.0, 2.0], [103.0, 3.0], [102.0, 3.0], [102.0, 2.0]]],
|
||||
* [[[100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0]],
|
||||
* [[100.2, 0.2], [100.8, 0.2], [100.8, 0.8], [100.2, 0.8], [100.2, 0.2]]]
|
||||
* ]
|
||||
* }
|
||||
* </code>
|
||||
* </pre>
|
||||
*
|
||||
* @author Bjorn Harvold
|
||||
* @author Christoph Strobl
|
||||
* @since 3.2
|
||||
*/
|
||||
static class GeoJsonMultiPolygonSerializer extends GeoJsonSerializer<GeoJsonMultiPolygon> {
|
||||
|
||||
@Override
|
||||
protected void doSerialize(GeoJsonMultiPolygon value, JsonGenerator jsonGenerator) throws IOException {
|
||||
|
||||
for (GeoJsonPolygon polygon : value.getCoordinates()) {
|
||||
|
||||
jsonGenerator.writeStartArray();
|
||||
for (GeoJsonLineString lineString : polygon.getCoordinates()) {
|
||||
writeLine(lineString.getCoordinates(), jsonGenerator);
|
||||
}
|
||||
jsonGenerator.writeEndArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -46,7 +46,6 @@ import java.lang.annotation.Target;
|
||||
* @author Philipp Schneider
|
||||
* @author Johno Crawford
|
||||
* @author Christoph Strobl
|
||||
* @author Dave Perryman
|
||||
*/
|
||||
@Target({ ElementType.TYPE })
|
||||
@Documented
|
||||
@@ -96,8 +95,7 @@ public @interface CompoundIndex {
|
||||
boolean unique() default false;
|
||||
|
||||
/**
|
||||
* If set to true index will skip over any document that is missing the indexed field. <br />
|
||||
* Must not be used with {@link #partialFilter()}.
|
||||
* If set to true index will skip over any document that is missing the indexed field.
|
||||
*
|
||||
* @return {@literal false} by default.
|
||||
* @see <a href=
|
||||
@@ -172,14 +170,4 @@ public @interface CompoundIndex {
|
||||
*/
|
||||
boolean background() default false;
|
||||
|
||||
/**
|
||||
* Only index the documents in a collection that meet a specified {@link IndexFilter filter expression}. <br />
|
||||
* Must not be used with {@link #sparse() sparse = true}.
|
||||
*
|
||||
* @return empty by default.
|
||||
* @see <a href=
|
||||
* "https://docs.mongodb.com/manual/core/index-partial/">https://docs.mongodb.com/manual/core/index-partial/</a>
|
||||
* @since 3.1
|
||||
*/
|
||||
String partialFilter() default "";
|
||||
}
|
||||
|
||||
@@ -29,17 +29,7 @@ import org.springframework.util.ObjectUtils;
|
||||
public final class IndexField {
|
||||
|
||||
enum Type {
|
||||
GEO, TEXT, DEFAULT,
|
||||
|
||||
/**
|
||||
* @since 2.2
|
||||
*/
|
||||
HASH,
|
||||
|
||||
/**
|
||||
* @since 3.3
|
||||
*/
|
||||
WILDCARD;
|
||||
GEO, TEXT, DEFAULT, HASH;
|
||||
}
|
||||
|
||||
private final String key;
|
||||
@@ -58,7 +48,7 @@ public final class IndexField {
|
||||
if (Type.GEO.equals(type) || Type.TEXT.equals(type)) {
|
||||
Assert.isNull(direction, "Geo/Text indexes must not have a direction!");
|
||||
} else {
|
||||
if (!(Type.HASH.equals(type) || Type.WILDCARD.equals(type))) {
|
||||
if (!Type.HASH.equals(type)) {
|
||||
Assert.notNull(direction, "Default indexes require a direction");
|
||||
}
|
||||
}
|
||||
@@ -87,18 +77,6 @@ public final class IndexField {
|
||||
return new IndexField(key, null, Type.HASH);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@literal wildcard} {@link IndexField} for the given key. The {@code key} must follow the
|
||||
* {@code fieldName.$**} notation.
|
||||
*
|
||||
* @param key must not be {@literal null} or empty.
|
||||
* @return new instance of {@link IndexField}.
|
||||
* @since 3.3
|
||||
*/
|
||||
static IndexField wildcard(String key) {
|
||||
return new IndexField(key, null, Type.WILDCARD);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a geo {@link IndexField} for the given key.
|
||||
*
|
||||
@@ -164,16 +142,6 @@ public final class IndexField {
|
||||
return Type.HASH.equals(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the {@link IndexField} is contains a {@literal wildcard} expression.
|
||||
*
|
||||
* @return {@literal true} if {@link IndexField} contains a wildcard {@literal $**}.
|
||||
* @since 3.3
|
||||
*/
|
||||
public boolean isWildcard() {
|
||||
return Type.WILDCARD.equals(type);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#equals(java.lang.Object)
|
||||
|
||||
@@ -55,7 +55,6 @@ public class IndexInfo {
|
||||
private @Nullable Duration expireAfter;
|
||||
private @Nullable String partialFilterExpression;
|
||||
private @Nullable Document collation;
|
||||
private @Nullable Document wildcardProjection;
|
||||
|
||||
public IndexInfo(List<IndexField> indexFields, String name, boolean unique, boolean sparse, String language) {
|
||||
|
||||
@@ -100,8 +99,6 @@ public class IndexInfo {
|
||||
|
||||
if (ObjectUtils.nullSafeEquals("hashed", value)) {
|
||||
indexFields.add(IndexField.hashed(key));
|
||||
} else if (key.endsWith("$**")) {
|
||||
indexFields.add(IndexField.wildcard(key));
|
||||
} else {
|
||||
|
||||
Double keyValue = new Double(value.toString());
|
||||
@@ -134,10 +131,6 @@ public class IndexInfo {
|
||||
info.expireAfter = Duration.ofSeconds(NumberUtils.convertNumberToTargetClass(expireAfterSeconds, Long.class));
|
||||
}
|
||||
|
||||
if (sourceDocument.containsKey("wildcardProjection")) {
|
||||
info.wildcardProjection = sourceDocument.get("wildcardProjection", Document.class);
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
@@ -223,16 +216,6 @@ public class IndexInfo {
|
||||
return Optional.ofNullable(collation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get {@literal wildcardProjection} information.
|
||||
*
|
||||
* @return {@link Optional#empty() empty} if not set.
|
||||
* @since 3.3
|
||||
*/
|
||||
public Optional<Document> getWildcardProjection() {
|
||||
return Optional.ofNullable(wildcardProjection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the duration after which documents within the index expire.
|
||||
*
|
||||
@@ -251,14 +234,6 @@ public class IndexInfo {
|
||||
return getIndexFields().stream().anyMatch(IndexField::isHashed);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@literal true} if a wildcard index field is present.
|
||||
* @since 3.3
|
||||
*/
|
||||
public boolean isWildcard() {
|
||||
return getIndexFields().stream().anyMatch(IndexField::isWildcard);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
|
||||
@@ -328,5 +303,4 @@ public class IndexInfo {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -15,8 +15,6 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core.index;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Provider interface to obtain {@link IndexOperations} by MongoDB collection name.
|
||||
*
|
||||
@@ -28,22 +26,10 @@ import org.springframework.lang.Nullable;
|
||||
public interface IndexOperationsProvider {
|
||||
|
||||
/**
|
||||
* Returns the operations that can be performed on indexes.
|
||||
* Returns the operations that can be performed on indexes
|
||||
*
|
||||
* @param collectionName name of the MongoDB collection, must not be {@literal null}.
|
||||
* @return index operations on the named collection
|
||||
*/
|
||||
default IndexOperations indexOps(String collectionName) {
|
||||
return indexOps(collectionName, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the operations that can be performed on indexes.
|
||||
*
|
||||
* @param collectionName name of the MongoDB collection, must not be {@literal null}.
|
||||
* @param type the type used for field mapping. Can be {@literal null}.
|
||||
* @return index operations on the named collection
|
||||
* @since 3.2
|
||||
*/
|
||||
IndexOperations indexOps(String collectionName, @Nullable Class<?> type);
|
||||
IndexOperations indexOps(String collectionName);
|
||||
}
|
||||
|
||||
@@ -53,8 +53,7 @@ public @interface Indexed {
|
||||
IndexDirection direction() default IndexDirection.ASCENDING;
|
||||
|
||||
/**
|
||||
* If set to true index will skip over any document that is missing the indexed field. <br />
|
||||
* Must not be used with {@link #partialFilter()}.
|
||||
* If set to true index will skip over any document that is missing the indexed field.
|
||||
*
|
||||
* @return {@literal false} by default.
|
||||
* @see <a href=
|
||||
@@ -171,15 +170,4 @@ public @interface Indexed {
|
||||
* @since 2.2
|
||||
*/
|
||||
String expireAfter() default "";
|
||||
|
||||
/**
|
||||
* Only index the documents in a collection that meet a specified {@link IndexFilter filter expression}. <br />
|
||||
* Must not be used with {@link #sparse() sparse = true}.
|
||||
*
|
||||
* @return empty by default.
|
||||
* @see <a href=
|
||||
* "https://docs.mongodb.com/manual/core/index-partial/">https://docs.mongodb.com/manual/core/index-partial/</a>
|
||||
* @since 3.1
|
||||
*/
|
||||
String partialFilter() default "";
|
||||
}
|
||||
|
||||
@@ -15,6 +15,10 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core.index;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@@ -29,6 +33,7 @@ import java.util.stream.Collectors;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.mapping.Association;
|
||||
@@ -46,9 +51,6 @@ import org.springframework.data.mongodb.core.mapping.BasicMongoPersistentEntity;
|
||||
import org.springframework.data.mongodb.core.mapping.Document;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
|
||||
import org.springframework.data.mongodb.core.query.Collation;
|
||||
import org.springframework.data.mongodb.util.BsonUtils;
|
||||
import org.springframework.data.mongodb.util.DotPath;
|
||||
import org.springframework.data.spel.EvaluationContextProvider;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
@@ -72,7 +74,6 @@ import org.springframework.util.StringUtils;
|
||||
* @author Thomas Darimont
|
||||
* @author Martin Macko
|
||||
* @author Mark Paluch
|
||||
* @author Dave Perryman
|
||||
* @since 1.5
|
||||
*/
|
||||
public class MongoPersistentEntityIndexResolver implements IndexResolver {
|
||||
@@ -119,12 +120,9 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
|
||||
Assert.notNull(document, () -> String
|
||||
.format("Entity %s is not a collection root. Make sure to annotate it with @Document!", root.getName()));
|
||||
|
||||
verifyWildcardIndexedProjection(root);
|
||||
|
||||
List<IndexDefinitionHolder> indexInformation = new ArrayList<>();
|
||||
String collection = root.getCollection();
|
||||
indexInformation.addAll(potentiallyCreateCompoundIndexDefinitions("", collection, root));
|
||||
indexInformation.addAll(potentiallyCreateWildcardIndexDefinitions("", collection, root));
|
||||
indexInformation.addAll(potentiallyCreateTextIndexDefinition(root, collection));
|
||||
|
||||
root.doWithProperties((PropertyHandler<MongoPersistentProperty>) property -> this
|
||||
@@ -135,32 +133,13 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
|
||||
return indexInformation;
|
||||
}
|
||||
|
||||
private void verifyWildcardIndexedProjection(MongoPersistentEntity<?> entity) {
|
||||
|
||||
entity.doWithAll(it -> {
|
||||
|
||||
if (it.isAnnotationPresent(WildcardIndexed.class)) {
|
||||
|
||||
WildcardIndexed indexed = it.getRequiredAnnotation(WildcardIndexed.class);
|
||||
|
||||
if (!ObjectUtils.isEmpty(indexed.wildcardProjection())) {
|
||||
|
||||
throw new MappingException(String.format(
|
||||
"WildcardIndexed.wildcardProjection cannot be used on nested paths. Offending property: %s.%s",
|
||||
entity.getName(), it.getName()));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void potentiallyAddIndexForProperty(MongoPersistentEntity<?> root, MongoPersistentProperty persistentProperty,
|
||||
List<IndexDefinitionHolder> indexes, CycleGuard guard) {
|
||||
|
||||
try {
|
||||
if (persistentProperty.isEntity()) {
|
||||
indexes.addAll(resolveIndexForEntity(mappingContext.getPersistentEntity(persistentProperty),
|
||||
persistentProperty.isUnwrapped() ? "" : persistentProperty.getFieldName(), Path.of(persistentProperty),
|
||||
root.getCollection(), guard));
|
||||
indexes.addAll(resolveIndexForClass(persistentProperty.getTypeInformation().getActualType(),
|
||||
persistentProperty.getFieldName(), Path.of(persistentProperty), root.getCollection(), guard));
|
||||
}
|
||||
|
||||
List<IndexDefinitionHolder> indexDefinitions = createIndexDefinitionHolderForProperty(
|
||||
@@ -184,18 +163,13 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
|
||||
* @return List of {@link IndexDefinitionHolder} representing indexes for given type and its referenced property
|
||||
* types. Will never be {@code null}.
|
||||
*/
|
||||
private List<IndexDefinitionHolder> resolveIndexForClass(TypeInformation<?> type, String dotPath, Path path,
|
||||
String collection, CycleGuard guard) {
|
||||
private List<IndexDefinitionHolder> resolveIndexForClass(final TypeInformation<?> type, final String dotPath,
|
||||
final Path path, final String collection, final CycleGuard guard) {
|
||||
|
||||
return resolveIndexForEntity(mappingContext.getRequiredPersistentEntity(type), dotPath, path, collection, guard);
|
||||
}
|
||||
MongoPersistentEntity<?> entity = mappingContext.getRequiredPersistentEntity(type);
|
||||
|
||||
private List<IndexDefinitionHolder> resolveIndexForEntity(MongoPersistentEntity<?> entity, String dotPath, Path path,
|
||||
String collection, CycleGuard guard) {
|
||||
|
||||
List<IndexDefinitionHolder> indexInformation = new ArrayList<>();
|
||||
final List<IndexDefinitionHolder> indexInformation = new ArrayList<>();
|
||||
indexInformation.addAll(potentiallyCreateCompoundIndexDefinitions(dotPath, collection, entity));
|
||||
indexInformation.addAll(potentiallyCreateWildcardIndexDefinitions(dotPath, collection, entity));
|
||||
|
||||
entity.doWithProperties((PropertyHandler<MongoPersistentProperty>) property -> this
|
||||
.guardAndPotentiallyAddIndexForProperty(property, dotPath, path, collection, indexInformation, guard));
|
||||
@@ -208,26 +182,22 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
|
||||
private void guardAndPotentiallyAddIndexForProperty(MongoPersistentProperty persistentProperty, String dotPath,
|
||||
Path path, String collection, List<IndexDefinitionHolder> indexes, CycleGuard guard) {
|
||||
|
||||
DotPath propertyDotPath = DotPath.from(dotPath);
|
||||
|
||||
if (!persistentProperty.isUnwrapped()) {
|
||||
propertyDotPath = propertyDotPath.append(persistentProperty.getFieldName());
|
||||
}
|
||||
String propertyDotPath = (StringUtils.hasText(dotPath) ? dotPath + "." : "") + persistentProperty.getFieldName();
|
||||
|
||||
Path propertyPath = path.append(persistentProperty);
|
||||
guard.protect(persistentProperty, propertyPath);
|
||||
|
||||
if (persistentProperty.isEntity()) {
|
||||
try {
|
||||
indexes.addAll(resolveIndexForEntity(mappingContext.getPersistentEntity(persistentProperty),
|
||||
propertyDotPath.toString(), propertyPath, collection, guard));
|
||||
indexes.addAll(resolveIndexForClass(persistentProperty.getTypeInformation().getActualType(), propertyDotPath,
|
||||
propertyPath, collection, guard));
|
||||
} catch (CyclicPropertyReferenceException e) {
|
||||
LOGGER.info(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
List<IndexDefinitionHolder> indexDefinitions = createIndexDefinitionHolderForProperty(propertyDotPath.toString(),
|
||||
collection, persistentProperty);
|
||||
List<IndexDefinitionHolder> indexDefinitions = createIndexDefinitionHolderForProperty(propertyDotPath, collection,
|
||||
persistentProperty);
|
||||
|
||||
if (!indexDefinitions.isEmpty()) {
|
||||
indexes.addAll(indexDefinitions);
|
||||
@@ -239,13 +209,6 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
|
||||
|
||||
List<IndexDefinitionHolder> indices = new ArrayList<>(2);
|
||||
|
||||
if (persistentProperty.isUnwrapped() && (persistentProperty.isAnnotationPresent(Indexed.class)
|
||||
|| persistentProperty.isAnnotationPresent(HashIndexed.class)
|
||||
|| persistentProperty.isAnnotationPresent(GeoSpatialIndexed.class))) {
|
||||
throw new InvalidDataAccessApiUsageException(
|
||||
String.format("Index annotation not allowed on unwrapped object for path '%s'.", dotPath));
|
||||
}
|
||||
|
||||
if (persistentProperty.isAnnotationPresent(Indexed.class)) {
|
||||
indices.add(createIndexDefinition(dotPath, collection, persistentProperty));
|
||||
} else if (persistentProperty.isAnnotationPresent(GeoSpatialIndexed.class)) {
|
||||
@@ -255,11 +218,6 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
|
||||
if (persistentProperty.isAnnotationPresent(HashIndexed.class)) {
|
||||
indices.add(createHashedIndexDefinition(dotPath, collection, persistentProperty));
|
||||
}
|
||||
if (persistentProperty.isAnnotationPresent(WildcardIndexed.class)) {
|
||||
indices.add(createWildcardIndexDefinition(dotPath, collection,
|
||||
persistentProperty.getRequiredAnnotation(WildcardIndexed.class),
|
||||
mappingContext.getPersistentEntity(persistentProperty)));
|
||||
}
|
||||
|
||||
return indices;
|
||||
}
|
||||
@@ -274,18 +232,6 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
|
||||
return createCompoundIndexDefinitions(dotPath, collection, entity);
|
||||
}
|
||||
|
||||
private List<IndexDefinitionHolder> potentiallyCreateWildcardIndexDefinitions(String dotPath, String collection,
|
||||
MongoPersistentEntity<?> entity) {
|
||||
|
||||
if (!entity.isAnnotationPresent(WildcardIndexed.class)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
return Collections.singletonList(new IndexDefinitionHolder(dotPath,
|
||||
createWildcardIndexDefinition(dotPath, collection, entity.getRequiredAnnotation(WildcardIndexed.class), entity),
|
||||
collection));
|
||||
}
|
||||
|
||||
private Collection<? extends IndexDefinitionHolder> potentiallyCreateTextIndexDefinition(
|
||||
MongoPersistentEntity<?> root, String collection) {
|
||||
|
||||
@@ -311,7 +257,7 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
|
||||
}
|
||||
|
||||
try {
|
||||
appendTextIndexInformation(DotPath.empty(), Path.empty(), indexDefinitionBuilder, root,
|
||||
appendTextIndexInformation("", Path.empty(), indexDefinitionBuilder, root,
|
||||
new TextIndexIncludeOptions(IncludeStrategy.DEFAULT), new CycleGuard());
|
||||
} catch (CyclicPropertyReferenceException e) {
|
||||
LOGGER.info(e.getMessage());
|
||||
@@ -332,8 +278,9 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
|
||||
|
||||
}
|
||||
|
||||
private void appendTextIndexInformation(DotPath dotPath, Path path, TextIndexDefinitionBuilder indexDefinitionBuilder,
|
||||
MongoPersistentEntity<?> entity, TextIndexIncludeOptions includeOptions, CycleGuard guard) {
|
||||
private void appendTextIndexInformation(final String dotPath, final Path path,
|
||||
final TextIndexDefinitionBuilder indexDefinitionBuilder, final MongoPersistentEntity<?> entity,
|
||||
final TextIndexIncludeOptions includeOptions, final CycleGuard guard) {
|
||||
|
||||
entity.doWithProperties(new PropertyHandler<MongoPersistentProperty>() {
|
||||
|
||||
@@ -342,7 +289,7 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
|
||||
|
||||
guard.protect(persistentProperty, path);
|
||||
|
||||
if (persistentProperty.isExplicitLanguageProperty() && dotPath.isEmpty()) {
|
||||
if (persistentProperty.isExplicitLanguageProperty() && !StringUtils.hasText(dotPath)) {
|
||||
indexDefinitionBuilder.withLanguageOverride(persistentProperty.getFieldName());
|
||||
}
|
||||
|
||||
@@ -350,7 +297,8 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
|
||||
|
||||
if (includeOptions.isForce() || indexed != null || persistentProperty.isEntity()) {
|
||||
|
||||
DotPath propertyDotPath = dotPath.append(persistentProperty.getFieldName());
|
||||
String propertyDotPath = (StringUtils.hasText(dotPath) ? dotPath + "." : "")
|
||||
+ persistentProperty.getFieldName();
|
||||
|
||||
Path propertyPath = path.append(persistentProperty);
|
||||
|
||||
@@ -363,7 +311,7 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
|
||||
TextIndexIncludeOptions optionsForNestedType = includeOptions;
|
||||
if (!IncludeStrategy.FORCE.equals(includeOptions.getStrategy()) && indexed != null) {
|
||||
optionsForNestedType = new TextIndexIncludeOptions(IncludeStrategy.FORCE,
|
||||
new TextIndexedFieldSpec(propertyDotPath.toString(), weight));
|
||||
new TextIndexedFieldSpec(propertyDotPath, weight));
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -376,7 +324,7 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
|
||||
entity.getName()), e);
|
||||
}
|
||||
} else if (includeOptions.isForce() || indexed != null) {
|
||||
indexDefinitionBuilder.onField(propertyDotPath.toString(), weight);
|
||||
indexDefinitionBuilder.onField(propertyDotPath, weight);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -437,36 +385,6 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
|
||||
indexDefinition.background();
|
||||
}
|
||||
|
||||
if (StringUtils.hasText(index.partialFilter())) {
|
||||
indexDefinition.partial(evaluatePartialFilter(index.partialFilter(), entity));
|
||||
}
|
||||
|
||||
return new IndexDefinitionHolder(dotPath, indexDefinition, collection);
|
||||
}
|
||||
|
||||
protected IndexDefinitionHolder createWildcardIndexDefinition(String dotPath, String collection,
|
||||
WildcardIndexed index, @Nullable MongoPersistentEntity<?> entity) {
|
||||
|
||||
WildcardIndex indexDefinition = new WildcardIndex(dotPath);
|
||||
|
||||
if (StringUtils.hasText(index.wildcardProjection()) && ObjectUtils.isEmpty(dotPath)) {
|
||||
indexDefinition.wildcardProjection(evaluateWildcardProjection(index.wildcardProjection(), entity));
|
||||
}
|
||||
|
||||
if (!index.useGeneratedName()) {
|
||||
indexDefinition.named(pathAwareIndexName(index.name(), dotPath, entity, null));
|
||||
}
|
||||
|
||||
if (StringUtils.hasText(index.partialFilter())) {
|
||||
indexDefinition.partial(evaluatePartialFilter(index.partialFilter(), entity));
|
||||
}
|
||||
|
||||
if (StringUtils.hasText(index.collation())) {
|
||||
indexDefinition.collation(evaluateCollation(index.collation(), entity));
|
||||
} else if (entity != null && entity.hasCollation()) {
|
||||
indexDefinition.collation(entity.getCollation());
|
||||
}
|
||||
|
||||
return new IndexDefinitionHolder(dotPath, indexDefinition, collection);
|
||||
}
|
||||
|
||||
@@ -556,51 +474,9 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
|
||||
}
|
||||
}
|
||||
|
||||
if (StringUtils.hasText(index.partialFilter())) {
|
||||
indexDefinition.partial(evaluatePartialFilter(index.partialFilter(), persistentProperty.getOwner()));
|
||||
}
|
||||
|
||||
return new IndexDefinitionHolder(dotPath, indexDefinition, collection);
|
||||
}
|
||||
|
||||
private PartialIndexFilter evaluatePartialFilter(String filterExpression, PersistentEntity<?, ?> entity) {
|
||||
|
||||
Object result = evaluate(filterExpression, getEvaluationContextForProperty(entity));
|
||||
|
||||
if (result instanceof org.bson.Document) {
|
||||
return PartialIndexFilter.of((org.bson.Document) result);
|
||||
}
|
||||
|
||||
return PartialIndexFilter.of(BsonUtils.parse(filterExpression, null));
|
||||
}
|
||||
|
||||
private org.bson.Document evaluateWildcardProjection(String projectionExpression, PersistentEntity<?, ?> entity) {
|
||||
|
||||
Object result = evaluate(projectionExpression, getEvaluationContextForProperty(entity));
|
||||
|
||||
if (result instanceof org.bson.Document) {
|
||||
return (org.bson.Document) result;
|
||||
}
|
||||
|
||||
return BsonUtils.parse(projectionExpression, null);
|
||||
}
|
||||
|
||||
private Collation evaluateCollation(String collationExpression, PersistentEntity<?, ?> entity) {
|
||||
|
||||
Object result = evaluate(collationExpression, getEvaluationContextForProperty(entity));
|
||||
if (result instanceof org.bson.Document) {
|
||||
return Collation.from((org.bson.Document) result);
|
||||
}
|
||||
if (result instanceof Collation) {
|
||||
return (Collation) result;
|
||||
}
|
||||
if (result instanceof String) {
|
||||
return Collation.parse(result.toString());
|
||||
}
|
||||
throw new IllegalStateException("Cannot parse collation " + result);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates {@link HashedIndex} wrapped in {@link IndexDefinitionHolder} out of {@link HashIndexed} for a given
|
||||
* {@link MongoPersistentProperty}.
|
||||
@@ -740,7 +616,7 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
|
||||
|
||||
MongoPersistentProperty property = association.getInverse();
|
||||
|
||||
DotPath propertyDotPath = DotPath.from(path).append(property.getFieldName());
|
||||
String propertyDotPath = (StringUtils.hasText(path) ? path + "." : "") + property.getFieldName();
|
||||
|
||||
if (property.isAnnotationPresent(GeoSpatialIndexed.class) || property.isAnnotationPresent(TextIndexed.class)) {
|
||||
throw new MappingException(
|
||||
@@ -748,8 +624,8 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
|
||||
propertyDotPath));
|
||||
}
|
||||
|
||||
List<IndexDefinitionHolder> indexDefinitions = createIndexDefinitionHolderForProperty(propertyDotPath.toString(),
|
||||
collection, property);
|
||||
List<IndexDefinitionHolder> indexDefinitions = createIndexDefinitionHolderForProperty(propertyDotPath, collection,
|
||||
property);
|
||||
|
||||
if (!indexDefinitions.isEmpty()) {
|
||||
indexes.addAll(indexDefinitions);
|
||||
@@ -857,6 +733,8 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
@EqualsAndHashCode
|
||||
static class Path {
|
||||
|
||||
private static final Path EMPTY = new Path(Collections.emptyList(), false);
|
||||
@@ -864,11 +742,6 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
|
||||
private final List<PersistentProperty<?>> elements;
|
||||
private final boolean cycle;
|
||||
|
||||
private Path(List<PersistentProperty<?>> elements, boolean cycle) {
|
||||
this.elements = elements;
|
||||
this.cycle = cycle;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return an empty {@link Path}.
|
||||
* @since 1.10.8
|
||||
@@ -970,28 +843,6 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o)
|
||||
return true;
|
||||
if (o == null || getClass() != o.getClass())
|
||||
return false;
|
||||
|
||||
Path that = (Path) o;
|
||||
|
||||
if (this.cycle != that.cycle) {
|
||||
return false;
|
||||
}
|
||||
return ObjectUtils.nullSafeEquals(this.elements, that.elements);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = ObjectUtils.nullSafeHashCode(elements);
|
||||
result = 31 * result + (cycle ? 1 : 0);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1089,11 +940,6 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
|
||||
public org.bson.Document getIndexOptions() {
|
||||
return indexDefinition.getIndexOptions();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "IndexDefinitionHolder{" + "indexKeys=" + getIndexKeys() + '}';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -16,8 +16,14 @@
|
||||
package org.springframework.data.mongodb.core.index;
|
||||
|
||||
import org.bson.Document;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import org.springframework.data.mongodb.core.query.CriteriaDefinition;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.mongodb.DBObject;
|
||||
|
||||
/**
|
||||
* {@link IndexFilter} implementation for usage with plain {@link Document} as well as {@link CriteriaDefinition} filter
|
||||
@@ -26,16 +32,10 @@ import org.springframework.util.Assert;
|
||||
* @author Christoph Strobl
|
||||
* @since 1.10
|
||||
*/
|
||||
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public class PartialIndexFilter implements IndexFilter {
|
||||
|
||||
private final Object filterExpression;
|
||||
|
||||
private PartialIndexFilter(Object filterExpression) {
|
||||
|
||||
Assert.notNull(filterExpression, "FilterExpression must not be null!");
|
||||
|
||||
this.filterExpression = filterExpression;
|
||||
}
|
||||
private final @NonNull Object filterExpression;
|
||||
|
||||
/**
|
||||
* Create new {@link PartialIndexFilter} for given {@link Document filter expression}.
|
||||
|
||||
@@ -1,198 +0,0 @@
|
||||
/*
|
||||
* 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.index;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.bson.Document;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* {@link WildcardIndex} is a specific {@link Index} that can be used to include all fields into an index based on the
|
||||
* {@code $**" : 1} pattern on a root object (the one typically carrying the
|
||||
* {@link org.springframework.data.mongodb.core.mapping.Document} annotation). On those it is possible to use
|
||||
* {@link #wildcardProjectionInclude(String...)} and {@link #wildcardProjectionExclude(String...)} to define specific
|
||||
* paths for in-/exclusion.
|
||||
* <p />
|
||||
* It can also be used to define an index on a specific field path and its subfields, e.g.
|
||||
* {@code "path.to.field.$**" : 1}. <br />
|
||||
* Note that {@literal wildcardProjections} are not allowed in this case.
|
||||
* <p />
|
||||
* <strong>LIMITATIONS</strong><br />
|
||||
* <ul>
|
||||
* <li>{@link #unique() Unique} and {@link #expire(long) ttl} options are not supported.</li>
|
||||
* <li>Keys used for sharding must not be included</li>
|
||||
* <li>Cannot be used to generate any type of geo index.</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @see <a href= "https://docs.mongodb.com/manual/core/index-wildcard/">MongoDB Reference Documentation: Wildcard
|
||||
* Indexes/</a>
|
||||
* @since 3.3
|
||||
*/
|
||||
public class WildcardIndex extends Index {
|
||||
|
||||
private @Nullable String fieldName;
|
||||
private final Map<String, Object> wildcardProjection = new LinkedHashMap<>();
|
||||
|
||||
/**
|
||||
* Create a new instance of {@link WildcardIndex} using {@code $**}.
|
||||
*/
|
||||
public WildcardIndex() {}
|
||||
|
||||
/**
|
||||
* Create a new instance of {@link WildcardIndex} for the given {@literal path}. If no {@literal path} is provided the
|
||||
* index will be considered a root one using {@code $**}. <br />
|
||||
* <strong>NOTE</strong> {@link #wildcardProjectionInclude(String...)}, {@link #wildcardProjectionExclude(String...)}
|
||||
* can only be used for top level index definitions having an {@literal empty} or {@literal null} path.
|
||||
*
|
||||
* @param path can be {@literal null}. If {@literal null} all fields will be indexed.
|
||||
*/
|
||||
public WildcardIndex(@Nullable String path) {
|
||||
this.fieldName = path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Include the {@code _id} field in {@literal wildcardProjection}.
|
||||
*
|
||||
* @return this.
|
||||
*/
|
||||
public WildcardIndex includeId() {
|
||||
|
||||
wildcardProjection.put("_id", 1);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the index name to use.
|
||||
*
|
||||
* @param name
|
||||
* @return this.
|
||||
*/
|
||||
@Override
|
||||
public WildcardIndex named(String name) {
|
||||
|
||||
super.named(name);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unique option is not supported.
|
||||
*
|
||||
* @throws UnsupportedOperationException not supported for wildcard indexes.
|
||||
*/
|
||||
@Override
|
||||
public Index unique() {
|
||||
throw new UnsupportedOperationException("Wildcard Index does not support 'unique'.");
|
||||
}
|
||||
|
||||
/**
|
||||
* ttl option is not supported.
|
||||
*
|
||||
* @throws UnsupportedOperationException not supported for wildcard indexes.
|
||||
*/
|
||||
@Override
|
||||
public Index expire(long seconds) {
|
||||
throw new UnsupportedOperationException("Wildcard Index does not support 'ttl'.");
|
||||
}
|
||||
|
||||
/**
|
||||
* ttl option is not supported.
|
||||
*
|
||||
* @throws UnsupportedOperationException not supported for wildcard indexes.
|
||||
*/
|
||||
@Override
|
||||
public Index expire(long value, TimeUnit timeUnit) {
|
||||
throw new UnsupportedOperationException("Wildcard Index does not support 'ttl'.");
|
||||
}
|
||||
|
||||
/**
|
||||
* ttl option is not supported.
|
||||
*
|
||||
* @throws UnsupportedOperationException not supported for wildcard indexes.
|
||||
*/
|
||||
@Override
|
||||
public Index expire(Duration duration) {
|
||||
throw new UnsupportedOperationException("Wildcard Index does not support 'ttl'.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Add fields to be included from indexing via {@code wildcardProjection}. <br />
|
||||
* This option is only allowed on {@link WildcardIndex#WildcardIndex() top level} wildcard indexes.
|
||||
*
|
||||
* @param paths must not be {@literal null}.
|
||||
* @return this.
|
||||
*/
|
||||
public WildcardIndex wildcardProjectionInclude(String... paths) {
|
||||
|
||||
for (String path : paths) {
|
||||
wildcardProjection.put(path, 1);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add fields to be excluded from indexing via {@code wildcardProjection}. <br />
|
||||
* This option is only allowed on {@link WildcardIndex#WildcardIndex() top level} wildcard indexes.
|
||||
*
|
||||
* @param paths must not be {@literal null}.
|
||||
* @return this.
|
||||
*/
|
||||
public WildcardIndex wildcardProjectionExclude(String... paths) {
|
||||
|
||||
for (String path : paths) {
|
||||
wildcardProjection.put(path, 0);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the fields to be in-/excluded from indexing via {@code wildcardProjection}. <br />
|
||||
* This option is only allowed on {@link WildcardIndex#WildcardIndex() top level} wildcard indexes.
|
||||
*
|
||||
* @param includeExclude must not be {@literal null}.
|
||||
* @return this.
|
||||
*/
|
||||
public WildcardIndex wildcardProjection(Map<String, Object> includeExclude) {
|
||||
|
||||
wildcardProjection.putAll(includeExclude);
|
||||
return this;
|
||||
}
|
||||
|
||||
private String getTargetFieldName() {
|
||||
return StringUtils.hasText(fieldName) ? (fieldName + ".$**") : "$**";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Document getIndexKeys() {
|
||||
return new Document(getTargetFieldName(), 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Document getIndexOptions() {
|
||||
|
||||
Document options = new Document(super.getIndexOptions());
|
||||
if (!CollectionUtils.isEmpty(wildcardProjection)) {
|
||||
options.put("wildcardProjection", new Document(wildcardProjection));
|
||||
}
|
||||
return options;
|
||||
}
|
||||
}
|
||||
@@ -1,130 +0,0 @@
|
||||
/*
|
||||
* 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.index;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Annotation for an entity or property that should be used as key for a
|
||||
* <a href="https://docs.mongodb.com/manual/core/index-wildcard/">Wildcard Index</a>. <br />
|
||||
* If placed on a {@link ElementType#TYPE type} that is a root level domain entity (one having an
|
||||
* {@link org.springframework.data.mongodb.core.mapping.Document} annotation) will advise the index creator to create a
|
||||
* wildcard index for it.
|
||||
*
|
||||
* <pre class="code">
|
||||
*
|
||||
* @Document
|
||||
* @WildcardIndexed
|
||||
* public class Product {
|
||||
* ...
|
||||
* }
|
||||
*
|
||||
* db.product.createIndex({ "$**" : 1 } , {})
|
||||
* </pre>
|
||||
*
|
||||
* {@literal wildcardProjection} can be used to specify keys to in-/exclude in the index.
|
||||
*
|
||||
* <pre class="code">
|
||||
*
|
||||
* @Document
|
||||
* @WildcardIndexed(wildcardProjection = "{ 'userMetadata.age' : 0 }")
|
||||
* public class User {
|
||||
* private @Id String id;
|
||||
* private UserMetadata userMetadata;
|
||||
* }
|
||||
*
|
||||
*
|
||||
* db.user.createIndex(
|
||||
* { "$**" : 1 },
|
||||
* { "wildcardProjection" :
|
||||
* { "userMetadata.age" : 0 }
|
||||
* }
|
||||
* )
|
||||
* </pre>
|
||||
*
|
||||
* Wildcard indexes can also be expressed by adding the annotation directly to the field. Please note that
|
||||
* {@literal wildcardProjection} is not allowed on nested paths.
|
||||
*
|
||||
* <pre class="code">
|
||||
* @Document
|
||||
* public class User {
|
||||
*
|
||||
* private @Id String id;
|
||||
*
|
||||
* @WildcardIndexed
|
||||
* private UserMetadata userMetadata;
|
||||
* }
|
||||
*
|
||||
*
|
||||
* db.user.createIndex({ "userMetadata.$**" : 1 }, {})
|
||||
* </pre>
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 3.3
|
||||
*/
|
||||
@Documented
|
||||
@Target({ ElementType.TYPE, ElementType.FIELD })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface WildcardIndexed {
|
||||
|
||||
/**
|
||||
* Index name either as plain value or as {@link org.springframework.expression.spel.standard.SpelExpression template
|
||||
* expression}. <br />
|
||||
* <br />
|
||||
* The name will only be applied as is when defined on root level. For usage on nested or embedded structures the
|
||||
* provided name will be prefixed with the path leading to the entity.
|
||||
*
|
||||
* @return empty by default.
|
||||
*/
|
||||
String name() default "";
|
||||
|
||||
/**
|
||||
* If set to {@literal true} then MongoDB will ignore the given index name and instead generate a new name. Defaults
|
||||
* to {@literal false}.
|
||||
*
|
||||
* @return {@literal false} by default.
|
||||
*/
|
||||
boolean useGeneratedName() default false;
|
||||
|
||||
/**
|
||||
* Only index the documents in a collection that meet a specified {@link IndexFilter filter expression}. <br />
|
||||
*
|
||||
* @return empty by default.
|
||||
* @see <a href=
|
||||
* "https://docs.mongodb.com/manual/core/index-partial/">https://docs.mongodb.com/manual/core/index-partial/</a>
|
||||
*/
|
||||
String partialFilter() default "";
|
||||
|
||||
/**
|
||||
* Explicitly specify sub fields to be in-/excluded as a {@link org.bson.Document#parse(String) prasable} String.
|
||||
* <br />
|
||||
* <strong>NOTE: </strong>Can only be applied on root level documents.
|
||||
*
|
||||
* @return empty by default.
|
||||
*/
|
||||
String wildcardProjection() default "";
|
||||
|
||||
/**
|
||||
* Defines the collation to apply.
|
||||
*
|
||||
* @return an empty {@link String} by default.
|
||||
*/
|
||||
String collation() default "";
|
||||
}
|
||||
@@ -164,8 +164,7 @@ public class BasicMongoPersistentEntity<T> extends BasicPersistentEntity<T, Mong
|
||||
@Override
|
||||
public org.springframework.data.mongodb.core.query.Collation getCollation() {
|
||||
|
||||
Object collationValue = collationExpression != null
|
||||
? collationExpression.getValue(getEvaluationContext(null), String.class)
|
||||
Object collationValue = collationExpression != null ? collationExpression.getValue(getEvaluationContext(null), String.class)
|
||||
: this.collation;
|
||||
|
||||
if (collationValue == null) {
|
||||
|
||||
@@ -41,14 +41,13 @@ import org.springframework.util.StringUtils;
|
||||
* @author Thomas Darimont
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
* @author Divya Srivastava
|
||||
*/
|
||||
public class BasicMongoPersistentProperty extends AnnotationBasedPersistentProperty<MongoPersistentProperty>
|
||||
implements MongoPersistentProperty {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(BasicMongoPersistentProperty.class);
|
||||
|
||||
public static final String ID_FIELD_NAME = "_id";
|
||||
private static final String ID_FIELD_NAME = "_id";
|
||||
private static final String LANGUAGE_FIELD_NAME = "language";
|
||||
private static final Set<Class<?>> SUPPORTED_ID_TYPES = new HashSet<Class<?>>();
|
||||
private static final Set<String> SUPPORTED_ID_PROPERTY_NAMES = new HashSet<String>();
|
||||
@@ -215,19 +214,6 @@ public class BasicMongoPersistentProperty extends AnnotationBasedPersistentPrope
|
||||
return annotation != null ? annotation.order() : Integer.MAX_VALUE;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.mapping.MongoPersistentProperty#skipNullValues()
|
||||
*/
|
||||
@Override
|
||||
public boolean writeNullValues() {
|
||||
|
||||
org.springframework.data.mongodb.core.mapping.Field annotation = findAnnotation(
|
||||
org.springframework.data.mongodb.core.mapping.Field.class);
|
||||
|
||||
return annotation != null && annotation.write() == Field.Write.ALWAYS;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mapping.model.AbstractPersistentProperty#createAssociation()
|
||||
@@ -245,15 +231,6 @@ public class BasicMongoPersistentProperty extends AnnotationBasedPersistentPrope
|
||||
return isAnnotationPresent(DBRef.class);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.mapping.MongoPersistentProperty#isDocumentReference()
|
||||
*/
|
||||
@Override
|
||||
public boolean isDocumentReference() {
|
||||
return isAnnotationPresent(DocumentReference.class);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.mapping.MongoPersistentProperty#getDBRef()
|
||||
@@ -263,16 +240,6 @@ public class BasicMongoPersistentProperty extends AnnotationBasedPersistentPrope
|
||||
return findAnnotation(DBRef.class);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.mapping.MongoPersistentProperty#getDocumentReference()
|
||||
*/
|
||||
@Nullable
|
||||
@Override
|
||||
public DocumentReference getDocumentReference() {
|
||||
return findAnnotation(DocumentReference.class);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.mapping.MongoPersistentProperty#isLanguageProperty()
|
||||
@@ -299,5 +266,4 @@ public class BasicMongoPersistentProperty extends AnnotationBasedPersistentPrope
|
||||
public boolean isTextScoreProperty() {
|
||||
return isAnnotationPresent(TextScore.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -33,7 +33,6 @@ public class CachingMongoPersistentProperty extends BasicMongoPersistentProperty
|
||||
private boolean dbRefResolved;
|
||||
private @Nullable DBRef dbref;
|
||||
private @Nullable String fieldName;
|
||||
private @Nullable Boolean writeNullValues;
|
||||
private @Nullable Class<?> fieldType;
|
||||
private @Nullable Boolean usePropertyAccess;
|
||||
private @Nullable Boolean isTransient;
|
||||
@@ -91,20 +90,6 @@ public class CachingMongoPersistentProperty extends BasicMongoPersistentProperty
|
||||
return this.fieldName;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.mapping.BasicMongoPersistentProperty#skipNullValues()
|
||||
*/
|
||||
@Override
|
||||
public boolean writeNullValues() {
|
||||
|
||||
if (this.writeNullValues == null) {
|
||||
this.writeNullValues = super.writeNullValues();
|
||||
}
|
||||
|
||||
return this.writeNullValues;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.mapping.BasicMongoPersistentProperty#getFieldType()
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
/*
|
||||
* 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.mapping;
|
||||
|
||||
/**
|
||||
* A custom pointer to a linked document to be used along with {@link DocumentReference} for storing the linkage value.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 3.3
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface DocumentPointer<T> {
|
||||
|
||||
/**
|
||||
* The actual pointer value. This can be any simple type, like a {@link String} or {@link org.bson.types.ObjectId} or
|
||||
* a {@link org.bson.Document} holding more information like the target collection, multiple fields forming the key,
|
||||
* etc.
|
||||
*
|
||||
* @return the value stored in MongoDB and used for constructing the {@link DocumentReference#lookup() lookup query}.
|
||||
*/
|
||||
T getPointer();
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user