diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 6c5d559a8a..e8f632af23 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -6,7 +6,6 @@ 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). diff --git a/.github/workflows/project.yml b/.github/workflows/project.yml new file mode 100644 index 0000000000..606226523e --- /dev/null +++ b/.github/workflows/project.yml @@ -0,0 +1,47 @@ +# 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 }} diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties index 00d32aab1d..dd2d8ef1ee 100755 --- a/.mvn/wrapper/maven-wrapper.properties +++ b/.mvn/wrapper/maven-wrapper.properties @@ -1 +1,2 @@ -distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.5.4/apache-maven-3.5.4-bin.zip \ No newline at end of file +#Fri Jun 03 09:42:19 CEST 2022 +distributionUrl=https\://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.5/apache-maven-3.8.5-bin.zip diff --git a/CI.adoc b/CI.adoc index c6cb467f2b..4e95939a34 100644 --- a/CI.adoc +++ b/CI.adoc @@ -1,6 +1,6 @@ = Continuous Integration -image:https://jenkins.spring.io/buildStatus/icon?job=spring-data-mongodb%2Fmaster&subject=Moore%20(master)[link=https://jenkins.spring.io/view/SpringData/job/spring-data-mongodb/] +image:https://jenkins.spring.io/buildStatus/icon?job=spring-data-mongodb%2Fmain&subject=Moore%20(main)[link=https://jenkins.spring.io/view/SpringData/job/spring-data-mongodb/] image:https://jenkins.spring.io/buildStatus/icon?job=spring-data-mongodb%2F2.1.x&subject=Lovelace%20(2.1.x)[link=https://jenkins.spring.io/view/SpringData/job/spring-data-mongodb/] image:https://jenkins.spring.io/buildStatus/icon?job=spring-data-mongodb%2F1.10.x&subject=Ingalls%20(1.10.x)[link=https://jenkins.spring.io/view/SpringData/job/spring-data-mongodb/] diff --git a/CODE_OF_CONDUCT.adoc b/CODE_OF_CONDUCT.adoc deleted file mode 100644 index 33ae7bc9f1..0000000000 --- a/CODE_OF_CONDUCT.adoc +++ /dev/null @@ -1,27 +0,0 @@ -= 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/]. \ No newline at end of file diff --git a/CONTRIBUTING.adoc b/CONTRIBUTING.adoc index f007591467..740e8bd0bb 100644 --- a/CONTRIBUTING.adoc +++ b/CONTRIBUTING.adoc @@ -1,3 +1,3 @@ = Spring Data contribution guidelines -You find the contribution guidelines for Spring Data projects https://github.com/spring-projects/spring-data-build/blob/master/CONTRIBUTING.adoc[here]. +You find the contribution guidelines for Spring Data projects https://github.com/spring-projects/spring-data-build/blob/main/CONTRIBUTING.adoc[here]. diff --git a/Jenkinsfile b/Jenkinsfile index 62ea203246..565b61da56 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,9 +1,15 @@ +def p = [:] +node { + checkout scm + p = readProperties interpolate: true, file: 'ci/pipeline.properties' +} + pipeline { agent none triggers { pollSCM 'H/10 * * * *' - upstream(upstreamProjects: "spring-data-commons/master", threshold: hudson.model.Result.SUCCESS) + upstream(upstreamProjects: "spring-data-commons/2.6.x", threshold: hudson.model.Result.SUCCESS) } options { @@ -14,49 +20,77 @@ pipeline { stages { stage("Docker images") { parallel { - stage('Publish JDK 8 + MongoDB 4.0') { + stage('Publish JDK (main) + MongoDB 4.0') { when { - changeset "ci/openjdk8-mongodb-4.0/**" + anyOf { + changeset "ci/openjdk8-mongodb-4.0/**" + changeset "ci/pipeline.properties" + } } agent { label 'data' } options { timeout(time: 30, unit: 'MINUTES') } steps { script { - def image = docker.build("springci/spring-data-openjdk8-with-mongodb-4.0", "ci/openjdk8-mongodb-4.0/") - docker.withRegistry('', 'hub.docker.com-springbuildmaster') { + def image = docker.build("springci/spring-data-with-mongodb-4.0:${p['java.main.tag']}", "--build-arg BASE=${p['docker.java.main.image']} --build-arg MONGODB=${p['docker.mongodb.4.0.version']} ci/openjdk8-mongodb-4.0/") + docker.withRegistry(p['docker.registry'], p['docker.credentials']) { image.push() } } } } - stage('Publish JDK 8 + MongoDB 4.2') { + stage('Publish JDK (main) + MongoDB 4.4') { when { - changeset "ci/openjdk8-mongodb-4.2/**" + anyOf { + changeset "ci/openjdk8-mongodb-4.4/**" + changeset "ci/pipeline.properties" + } } agent { label 'data' } options { timeout(time: 30, unit: 'MINUTES') } steps { script { - def image = docker.build("springci/spring-data-openjdk8-with-mongodb-4.2.0", "ci/openjdk8-mongodb-4.2/") - docker.withRegistry('', 'hub.docker.com-springbuildmaster') { + def image = docker.build("springci/spring-data-with-mongodb-4.4:${p['java.main.tag']}", "--build-arg BASE=${p['docker.java.main.image']} --build-arg MONGODB=${p['docker.mongodb.4.4.version']} ci/openjdk8-mongodb-4.4/") + docker.withRegistry(p['docker.registry'], p['docker.credentials']) { image.push() } } } } - stage('Publish JDK 14 + MongoDB 4.2') { + stage('Publish JDK (main) + MongoDB 5.0') { when { - changeset "ci/openjdk14-mongodb-4.2/**" + anyOf { + changeset "ci/openjdk8-mongodb-5.0/**" + changeset "ci/pipeline.properties" + } } agent { label 'data' } options { timeout(time: 30, unit: 'MINUTES') } steps { script { - def image = docker.build("springci/spring-data-openjdk14-with-mongodb-4.2.0", "ci/openjdk14-mongodb-4.2/") - docker.withRegistry('', 'hub.docker.com-springbuildmaster') { + def image = docker.build("springci/spring-data-with-mongodb-5.0:${p['java.main.tag']}", "--build-arg BASE=${p['docker.java.main.image']} --build-arg MONGODB=${p['docker.mongodb.5.0.version']} ci/openjdk8-mongodb-5.0/") + docker.withRegistry(p['docker.registry'], p['docker.credentials']) { + image.push() + } + } + } + } + stage('Publish JDK (LTS) + MongoDB 4.4') { + when { + anyOf { + changeset "ci/openjdk17-mongodb-4.4/**" + changeset "ci/pipeline.properties" + } + } + agent { label 'data' } + options { timeout(time: 30, unit: 'MINUTES') } + + steps { + script { + def image = docker.build("springci/spring-data-with-mongodb-4.4:${p['java.lts.tag']}", "--build-arg BASE=${p['docker.java.lts.image']} --build-arg MONGODB=${p['docker.mongodb.4.4.version']} ci/openjdk17-mongodb-4.4/") + docker.withRegistry(p['docker.registry'], p['docker.credentials']) { image.push() } } @@ -65,97 +99,107 @@ pipeline { } } - stage("test: baseline (jdk8)") { + stage("test: baseline (main)") { when { + beforeAgent(true) anyOf { - branch 'master' + branch(pattern: "main|(\\d\\.\\d\\.x)", comparator: "REGEXP") not { triggeredBy 'UpstreamCause' } } } agent { - docker { - image 'springci/spring-data-openjdk8-with-mongodb-4.2.0:latest' - label 'data' - args '-v $HOME:/tmp/jenkins-home' - } + label 'data' } options { timeout(time: 30, unit: 'MINUTES') } + environment { + ARTIFACTORY = credentials("${p['artifactory.credentials']}") + } steps { - sh 'rm -rf ?' - sh 'mkdir -p /tmp/mongodb/db /tmp/mongodb/log' - sh 'mongod --setParameter transactionLifetimeLimitSeconds=90 --setParameter maxTransactionLockRequestTimeoutMillis=10000 --dbpath /tmp/mongodb/db --replSet rs0 --fork --logpath /tmp/mongodb/log/mongod.log &' - sh 'sleep 10' - sh 'mongo --eval "rs.initiate({_id: \'rs0\', members:[{_id: 0, host: \'127.0.0.1:27017\'}]});"' - sh 'sleep 15' - sh 'MAVEN_OPTS="-Duser.name=jenkins -Duser.home=/tmp/jenkins-home" ./mvnw clean dependency:list test -Duser.name=jenkins -Dsort -U -B' + script { + docker.image("harbor-repo.vmware.com/dockerhub-proxy-cache/springci/spring-data-with-mongodb-4.0:${p['java.main.tag']}").inside(p['docker.java.inside.basic']) { + sh 'mkdir -p /tmp/mongodb/db /tmp/mongodb/log' + sh 'mongod --setParameter transactionLifetimeLimitSeconds=90 --setParameter maxTransactionLockRequestTimeoutMillis=10000 --dbpath /tmp/mongodb/db --replSet rs0 --fork --logpath /tmp/mongodb/log/mongod.log &' + sh 'sleep 10' + sh 'mongo --eval "rs.initiate({_id: \'rs0\', members:[{_id: 0, host: \'127.0.0.1:27017\'}]});"' + sh 'sleep 15' + sh 'MAVEN_OPTS="-Duser.name=jenkins -Duser.home=/tmp/jenkins-home" ./mvnw -s settings.xml clean dependency:list test -Duser.name=jenkins -Dsort -U -B' + } + } } } stage("Test other configurations") { when { - anyOf { - branch 'master' + beforeAgent(true) + allOf { + branch(pattern: "main|(\\d\\.\\d\\.x)", comparator: "REGEXP") not { triggeredBy 'UpstreamCause' } } } parallel { - stage("test: mongodb 4.0 (jdk8)") { + stage("test: mongodb 4.4 (main)") { agent { - docker { - image 'springci/spring-data-openjdk8-with-mongodb-4.0:latest' - label 'data' - args '-v $HOME:/tmp/jenkins-home' - } + label 'data' } options { timeout(time: 30, unit: 'MINUTES') } + environment { + ARTIFACTORY = credentials("${p['artifactory.credentials']}") + } steps { - sh 'rm -rf ?' - sh 'mkdir -p /tmp/mongodb/db /tmp/mongodb/log' - sh 'mongod --setParameter transactionLifetimeLimitSeconds=90 --setParameter maxTransactionLockRequestTimeoutMillis=10000 --dbpath /tmp/mongodb/db --replSet rs0 --fork --logpath /tmp/mongodb/log/mongod.log &' - sh 'sleep 10' - sh 'mongo --eval "rs.initiate({_id: \'rs0\', members:[{_id: 0, host: \'127.0.0.1:27017\'}]});"' - sh 'sleep 15' - sh 'MAVEN_OPTS="-Duser.name=jenkins -Duser.home=/tmp/jenkins-home" ./mvnw clean dependency:list test -Duser.name=jenkins -Dsort -U -B' + script { + docker.image("harbor-repo.vmware.com/dockerhub-proxy-cache/springci/spring-data-with-mongodb-4.4:${p['java.main.tag']}").inside(p['docker.java.inside.basic']) { + sh 'mkdir -p /tmp/mongodb/db /tmp/mongodb/log' + sh 'mongod --setParameter transactionLifetimeLimitSeconds=90 --setParameter maxTransactionLockRequestTimeoutMillis=10000 --dbpath /tmp/mongodb/db --replSet rs0 --fork --logpath /tmp/mongodb/log/mongod.log &' + sh 'sleep 10' + sh 'mongo --eval "rs.initiate({_id: \'rs0\', members:[{_id: 0, host: \'127.0.0.1:27017\'}]});"' + sh 'sleep 15' + sh 'MAVEN_OPTS="-Duser.name=jenkins -Duser.home=/tmp/jenkins-home" ./mvnw -s settings.xml clean dependency:list test -Duser.name=jenkins -Dsort -U -B' + } + } } } - stage("test: mongodb 4.2 (jdk8)") { + stage("test: mongodb 5.0 (main)") { agent { - docker { - image 'springci/spring-data-openjdk8-with-mongodb-4.2.0:latest' - label 'data' - args '-v $HOME:/tmp/jenkins-home' - } + label 'data' } options { timeout(time: 30, unit: 'MINUTES') } + environment { + ARTIFACTORY = credentials("${p['artifactory.credentials']}") + } steps { - sh 'rm -rf ?' - sh 'mkdir -p /tmp/mongodb/db /tmp/mongodb/log' - sh 'mongod --setParameter transactionLifetimeLimitSeconds=90 --setParameter maxTransactionLockRequestTimeoutMillis=10000 --dbpath /tmp/mongodb/db --replSet rs0 --fork --logpath /tmp/mongodb/log/mongod.log &' - sh 'sleep 10' - sh 'mongo --eval "rs.initiate({_id: \'rs0\', members:[{_id: 0, host: \'127.0.0.1:27017\'}]});"' - sh 'sleep 15' - sh 'MAVEN_OPTS="-Duser.name=jenkins -Duser.home=/tmp/jenkins-home" ./mvnw clean dependency:list test -Duser.name=jenkins -Dsort -U -B' + script { + docker.image("harbor-repo.vmware.com/dockerhub-proxy-cache/springci/spring-data-with-mongodb-5.0:${p['java.main.tag']}").inside(p['docker.java.inside.basic']) { + sh 'mkdir -p /tmp/mongodb/db /tmp/mongodb/log' + sh 'mongod --setParameter transactionLifetimeLimitSeconds=90 --setParameter maxTransactionLockRequestTimeoutMillis=10000 --dbpath /tmp/mongodb/db --replSet rs0 --fork --logpath /tmp/mongodb/log/mongod.log &' + sh 'sleep 10' + sh 'mongo --eval "rs.initiate({_id: \'rs0\', members:[{_id: 0, host: \'127.0.0.1:27017\'}]});"' + sh 'sleep 15' + sh 'MAVEN_OPTS="-Duser.name=jenkins -Duser.home=/tmp/jenkins-home" ./mvnw -s settings.xml clean dependency:list test -Duser.name=jenkins -Dsort -U -B' + } + } } } - stage("test: baseline (jdk14)") { + stage("test: baseline (LTS)") { agent { - docker { - image 'springci/spring-data-openjdk14-with-mongodb-4.2.0:latest' - label 'data' - args '-v $HOME:/tmp/jenkins-home' - } + label 'data' } options { timeout(time: 30, unit: 'MINUTES') } + environment { + ARTIFACTORY = credentials("${p['artifactory.credentials']}") + } steps { - sh 'rm -rf ?' - sh 'mkdir -p /tmp/mongodb/db /tmp/mongodb/log' - sh 'mongod --setParameter transactionLifetimeLimitSeconds=90 --setParameter maxTransactionLockRequestTimeoutMillis=10000 --dbpath /tmp/mongodb/db --replSet rs0 --fork --logpath /tmp/mongodb/log/mongod.log &' - sh 'sleep 10' - sh 'mongo --eval "rs.initiate({_id: \'rs0\', members:[{_id: 0, host: \'127.0.0.1:27017\'}]});"' - sh 'sleep 15' - sh 'MAVEN_OPTS="-Duser.name=jenkins -Duser.home=/tmp/jenkins-home" ./mvnw -Pjava11 clean dependency:list test -Duser.name=jenkins -Dsort -U -B' + script { + docker.image("harbor-repo.vmware.com/dockerhub-proxy-cache/springci/spring-data-with-mongodb-4.4:${p['java.lts.tag']}").inside(p['docker.java.inside.basic']) { + sh 'mkdir -p /tmp/mongodb/db /tmp/mongodb/log' + sh 'mongod --setParameter transactionLifetimeLimitSeconds=90 --setParameter maxTransactionLockRequestTimeoutMillis=10000 --dbpath /tmp/mongodb/db --replSet rs0 --fork --logpath /tmp/mongodb/log/mongod.log &' + sh 'sleep 10' + sh 'mongo --eval "rs.initiate({_id: \'rs0\', members:[{_id: 0, host: \'127.0.0.1:27017\'}]});"' + sh 'sleep 15' + sh 'MAVEN_OPTS="-Duser.name=jenkins -Duser.home=/tmp/jenkins-home" ./mvnw -s settings.xml clean dependency:list test -Duser.name=jenkins -Dsort -U -B' + } + } } } } @@ -163,62 +207,35 @@ pipeline { stage('Release to artifactory') { when { + beforeAgent(true) anyOf { - branch 'master' + branch(pattern: "main|(\\d\\.\\d\\.x)", comparator: "REGEXP") not { triggeredBy 'UpstreamCause' } } } agent { - docker { - image 'adoptopenjdk/openjdk8:latest' - label 'data' - args '-v $HOME:/tmp/jenkins-home' - } + label 'data' } options { timeout(time: 20, unit: 'MINUTES') } environment { - ARTIFACTORY = credentials('02bd1690-b54f-4c9f-819d-a77cb7a9822c') + ARTIFACTORY = credentials("${p['artifactory.credentials']}") } steps { - sh 'rm -rf ?' - sh 'MAVEN_OPTS="-Duser.name=jenkins -Duser.home=/tmp/jenkins-home" ./mvnw -Pci,artifactory ' + - '-Dartifactory.server=https://repo.spring.io ' + - "-Dartifactory.username=${ARTIFACTORY_USR} " + - "-Dartifactory.password=${ARTIFACTORY_PSW} " + - "-Dartifactory.staging-repository=libs-snapshot-local " + - "-Dartifactory.build-name=spring-data-mongodb " + - "-Dartifactory.build-number=${BUILD_NUMBER} " + - '-Dmaven.test.skip=true clean deploy -U -B' - } - } - - stage('Publish documentation') { - when { - branch 'master' - } - agent { - docker { - image 'adoptopenjdk/openjdk8:latest' - label 'data' - args '-v $HOME:/tmp/jenkins-home' + script { + docker.image(p['docker.java.main.image']).inside(p['docker.java.inside.basic']) { + sh 'MAVEN_OPTS="-Duser.name=jenkins -Duser.home=/tmp/jenkins-home" ./mvnw -s settings.xml -Pci,artifactory ' + + '-Dartifactory.server=https://repo.spring.io ' + + "-Dartifactory.username=${ARTIFACTORY_USR} " + + "-Dartifactory.password=${ARTIFACTORY_PSW} " + + "-Dartifactory.staging-repository=libs-snapshot-local " + + "-Dartifactory.build-name=spring-data-mongodb " + + "-Dartifactory.build-number=${BUILD_NUMBER} " + + '-Dmaven.test.skip=true clean deploy -U -B' + } } } - options { timeout(time: 20, unit: 'MINUTES') } - - environment { - ARTIFACTORY = credentials('02bd1690-b54f-4c9f-819d-a77cb7a9822c') - } - - steps { - sh 'MAVEN_OPTS="-Duser.name=jenkins -Duser.home=/tmp/jenkins-home" ./mvnw -Pci,distribute ' + - '-Dartifactory.server=https://repo.spring.io ' + - "-Dartifactory.username=${ARTIFACTORY_USR} " + - "-Dartifactory.password=${ARTIFACTORY_PSW} " + - "-Dartifactory.distribution-repository=temp-private-local " + - '-Dmaven.test.skip=true clean deploy -U -B' - } } } diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000000..ff77379631 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + https://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.adoc b/README.adoc index a6b69f9747..f526d0d4cc 100644 --- a/README.adoc +++ b/README.adoc @@ -1,17 +1,19 @@ -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] +image:https://spring.io/badges/spring-data-mongodb/ga.svg[Spring Data MongoDB,link=https://spring.io/projects/spring-data-mongodb#quick-start] image:https://spring.io/badges/spring-data-mongodb/snapshot.svg[Spring Data MongoDB,link=https://spring.io/projects/spring-data-mongodb#quick-start] -= Spring Data MongoDB image:https://jenkins.spring.io/buildStatus/icon?job=spring-data-mongodb%2Fmaster&subject=Build[link=https://jenkins.spring.io/view/SpringData/job/spring-data-mongodb/] https://gitter.im/spring-projects/spring-data[image:https://badges.gitter.im/spring-projects/spring-data.svg[Gitter]] += Spring Data MongoDB image:https://jenkins.spring.io/buildStatus/icon?job=spring-data-mongodb%2Fmain&subject=Build[link=https://jenkins.spring.io/view/SpringData/job/spring-data-mongodb/] https://gitter.im/spring-projects/spring-data[image:https://badges.gitter.im/spring-projects/spring-data.svg[Gitter]] -The primary goal of the https://projects.spring.io/spring-data[Spring Data] project is to make it easier to build Spring-powered applications that use new data access technologies such as non-relational databases, map-reduce frameworks, and cloud based data services. +The primary goal of the https://spring.io/projects/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. The Spring Data MongoDB project aims to provide a familiar and consistent Spring-based programming model for new datastores while retaining store-specific features and capabilities. The Spring Data MongoDB project provides integration with the MongoDB document database. Key functional areas of Spring Data MongoDB are a POJO centric model for interacting with a MongoDB `+Document+` and easily writing a repository style data access layer. +[[code-of-conduct]] == Code of Conduct -This project is governed by the 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. +This project is governed by the https://github.com/spring-projects/.github/blob/e3cc2ff230d8f1dca06535aa6b5a4a23815861d4/CODE_OF_CONDUCT.md[Spring Code of Conduct]. By participating, you are expected to uphold this code of conduct. Please report unacceptable behavior to spring-code-of-conduct@pivotal.io. +[[getting-started]] == Getting Started Here is a quick teaser of an application using Spring Data Repositories in Java: @@ -59,6 +61,7 @@ class ApplicationConfig extends AbstractMongoClientConfiguration { } ---- +[[maven-configuration]] === Maven configuration Add the Maven dependency: @@ -68,24 +71,25 @@ Add the Maven dependency: org.springframework.data spring-data-mongodb - ${version}.RELEASE + ${version} ---- -If you'd rather like the latest snapshots of the upcoming major version, use our Maven snapshot repository and declare the appropriate dependency version. +If you'd rather like the latest snapshots of the upcoming major version, use our Maven snapshot repository +and declare the appropriate dependency version. [source,xml] ---- org.springframework.data spring-data-mongodb - ${version}.BUILD-SNAPSHOT + ${version}-SNAPSHOT - spring-libs-snapshot + spring-snapshot Spring Snapshot Repository - https://repo.spring.io/libs-snapshot + https://repo.spring.io/snapshot ---- @@ -98,7 +102,7 @@ Some of the changes affect the initial setup configuration as well as compile/ru .Changed XML Namespace Elements and Attributes: |=== -Element / Attribute | 2.x | 3.x +| Element / Attribute | 2.x | 3.x | `` | Used to create a `com.mongodb.MongoClient` @@ -116,7 +120,7 @@ Use `` instead .Removed XML Namespace Elements and Attributes: |=== -Element / Attribute | Replacement in 3.x | Comment +| Element / Attribute | Replacement in 3.x | Comment | `` | `` @@ -133,7 +137,7 @@ Element / Attribute | Replacement in 3.x | Comment .New XML Namespace Elements and Attributes: |=== -Element | Comment +| Element | Comment | `` | Replacement for `` @@ -153,7 +157,7 @@ Element | Comment .Java API changes |=== -Type | Comment +| Type | Comment | `MongoClientFactoryBean` | Creates `com.mongodb.client.MongoClient` instead of `com.mongodb.MongoClient` + @@ -174,7 +178,7 @@ Uses `MongoClientSettings` instead of `MongoClientOptions`. .Removed Java API: |=== -2.x | Replacement in 3.x | Comment +| 2.x | Replacement in 3.x | Comment | `MongoClientOptionsFactoryBean` | `MongoClientSettingsFactoryBean` @@ -226,6 +230,7 @@ static class Config extends AbstractMongoClientConfiguration { ---- ==== +[[getting-help]] == Getting Help Having trouble with Spring Data? We’d love to help! @@ -237,23 +242,98 @@ If you are just starting out with Spring, try one of the https://spring.io/guide * If you are upgrading, check out the https://docs.spring.io/spring-data/mongodb/docs/current/changelog.txt[changelog] for "`new and noteworthy`" features. * Ask a question - we monitor https://stackoverflow.com[stackoverflow.com] for questions tagged with https://stackoverflow.com/tags/spring-data[`spring-data-mongodb`]. You can also chat with the community on https://gitter.im/spring-projects/spring-data[Gitter]. -* Report bugs with Spring Data MongoDB at https://jira.spring.io/browse/DATAMONGO[jira.spring.io/browse/DATAMONGO]. +* Report bugs with Spring Data MongoDB at https://github.com/spring-projects/spring-data-mongodb/issues[github.com/spring-projects/spring-data-mongodb/issues]. +[[reporting-issues]] == Reporting Issues -Spring Data uses JIRA as issue tracking system to record bugs and feature requests. If you want to raise an issue, please follow the recommendations below: +Spring Data uses Github as issue tracking system to record bugs and feature requests. +If you want to raise an issue, please follow the recommendations below: -* Before you log a bug, please search the -https://jira.spring.io/browse/DATAMONGO[issue tracker] to see if someone has already reported the problem. -* If the issue doesn’t already exist, https://jira.spring.io/browse/DATAMONGO[create a new issue]. -* Please provide as much information as possible with the issue report, we like to know the version of Spring Data that you are using and JVM version. -* If you need to paste code, or include a stack trace use JIRA `{code}…{code}` escapes before and after your text. -* If possible try to create a test-case or project that replicates the issue. Attach a link to your code or a compressed file containing your code. +* Before you log a bug, please search the https://github.com/spring-projects/spring-data-mongodb/issues[issue tracker] to see if someone has already reported the problem. +* If the issue does not already exist, https://github.com/spring-projects/spring-data-mongodb/issues/new[create a new issue]. +* Please provide as much information as possible with the issue report, we like to know the version of Spring Data that you are using, the JVM version, Stacktrace, etc. +* If you need to paste code, or include a stack trace use https://guides.github.com/features/mastering-markdown/[Markdown] code fences +++```+++. +[[guides]] +== Guides + +The https://spring.io/[spring.io] site contains several guides that show how to use Spring Data step-by-step: + +* https://spring.io/guides/gs/accessing-data-mongodb/[Accessing Data with MongoDB] is a very basic guide that shows you how to create a simple application and how to access data using repositories. +* https://spring.io/guides/gs/accessing-mongodb-data-rest/[Accessing MongoDB Data with REST] is a guide to creating a REST web service exposing data stored in MongoDB through repositories. + +[[examples]] +== Examples + +* https://github.com/spring-projects/spring-data-examples/[Spring Data Examples] contains example projects that explain specific features in more detail. + +[[building-from-source]] == Building from Source -You don’t need to build from source to use Spring Data (binaries in https://repo.spring.io[repo.spring.io]), but if you want to try out the latest and greatest, Spring Data can be easily built with the https://github.com/takari/maven-wrapper[maven wrapper]. -You also need JDK 1.8. +You do not need to build from source to use Spring Data. Binaries are available in https://repo.spring.io[repo.spring.io]. +and accessible from Maven using the Maven configuration noted <>. + +NOTE: Configuration for Gradle is similar to Maven. + +The best way to get started is by creating a Spring Boot project using MongoDB on https://start.spring.io[start.spring.io]. +Follow this https://start.spring.io/#type=maven-project&language=java&platformVersion=2.5.4&packaging=jar&jvmVersion=1.8&groupId=com.example&artifactId=demo&name=demo&description=Demo%20project%20for%20Spring%20Boot&packageName=com.example.demo&dependencies=data-mongodb[link] +to build an imperative application and this https://start.spring.io/#type=maven-project&language=java&platformVersion=2.5.4&packaging=jar&jvmVersion=1.8&groupId=com.example&artifactId=demo&name=demo&description=Demo%20project%20for%20Spring%20Boot&packageName=com.example.demo&dependencies=data-mongodb-reactive[link] +to build a reactive one. + +However, if you want to try out the latest and greatest, Spring Data can be easily built with the https://github.com/takari/maven-wrapper[maven wrapper] +and minimally JDK 8 (https://www.oracle.com/java/technologies/downloads/[JDK downloads]). + +In order to build Spring Data MongoDB, first you will need to https://www.mongodb.com/try/download/community[download] +and https://docs.mongodb.com/manual/installation/[install a MongoDB distribution]. + +Once you have installed MongoDB, you need to start a MongoDB server. It is convenient to set an environment variable to +your MongoDB installation (e.g. `MONGODB_HOME`). + +To run the full test suite a https://docs.mongodb.com/manual/tutorial/deploy-replica-set/[MongoDB Replica Set] is required. + +To run the MongoDB server enter the following command from a command-line: + +[source,bash] +---- +$ $MONGODB_HOME/bin/mongod --dbpath $MONGODB_HOME/runtime/data --ipv6 --port 27017 --replSet rs0 +... +"msg":"Successfully connected to host" +---- + +Once the MongoDB server starts up, you should see the message (`msg`), "_Successfully connected to host_". + +Notice the `--dbpath` option to the `mongod` command. You can set this to anything you like, but in this case, we set +the absolute path to a sub-directory (`runtime/data/`) under the MongoDB installation directory (in `$MONGODB_HOME`). + +You need to initialize the MongoDB replica set only once on the first time the MongoDB server is started. +To initialize the replica set, start a mongo client: + +[source,bash] +---- +$ $MONGODB_HOME/bin/mongo +MongoDB server version: 5.0.0 +... +---- + +Then enter the following command: + +[source,bash] +---- +mongo> rs.initiate({ _id: 'rs0', members: [ { _id: 0, host: '127.0.0.1:27017' } ] }) +---- + +Finally, on UNIX-based system (for example, Linux or Mac OS X) you may need to adjust the `ulimit`. +In case you need to, you can adjust the `ulimit` with the following command (32768 is just a recommendation): + +[source,bash] +---- +$ ulimit -n 32768 +---- + +You can use `ulimit -a` again to verify the `ulimit` on "_open files_" was set appropriately. + +Now you are ready to build Spring Data MongoDB. Simply enter the following `mvnw` (Maven Wrapper) command: [source,bash] ---- @@ -262,7 +342,8 @@ You also need JDK 1.8. If you want to build with the regular `mvn` command, you will need https://maven.apache.org/run-maven/index.html[Maven v3.5.0 or above]. -_Also see link:CONTRIBUTING.adoc[CONTRIBUTING.adoc] if you wish to submit pull requests, and in particular please sign the https://cla.pivotal.io/sign/spring[Contributor’s Agreement] before your first non-trivial change._ +_Also see link:CONTRIBUTING.adoc[CONTRIBUTING.adoc] if you wish to submit pull requests, and in particular, please sign +the https://cla.pivotal.io/sign/spring[Contributor’s Agreement] before your first non-trivial change._ === Building reference documentation @@ -275,17 +356,7 @@ Building the documentation builds also the project without running tests. The generated documentation is available from `target/site/reference/html/index.html`. -== Guides - -The https://spring.io/[spring.io] site contains several guides that show how to use Spring Data step-by-step: - -* https://spring.io/guides/gs/accessing-data-mongodb/[Accessing Data with MongoDB] is a very basic guide that shows you how to create a simple application and how to access data using repositories. -* https://spring.io/guides/gs/accessing-mongodb-data-rest/[Accessing MongoDB Data with REST] is a guide to creating a REST web service exposing data stored in MongoDB through repositories. - -== Examples - -* https://github.com/spring-projects/spring-data-examples/[Spring Data Examples] contains example projects that explain specific features in more detail. - +[[license]] == License Spring Data MongoDB is Open Source software released under the https://www.apache.org/licenses/LICENSE-2.0.html[Apache 2.0 license]. diff --git a/ci/openjdk11-mongodb-4.2/Dockerfile b/ci/openjdk11-mongodb-4.2/Dockerfile deleted file mode 100644 index 0d92eba78d..0000000000 --- a/ci/openjdk11-mongodb-4.2/Dockerfile +++ /dev/null @@ -1,15 +0,0 @@ -FROM adoptopenjdk/openjdk11:latest - -ENV TZ=Etc/UTC -ENV DEBIAN_FRONTEND=noninteractive - -RUN set -eux; \ - apt-get update && apt-get install -y apt-transport-https apt-utils gnupg2 ; \ - apt-key adv --keyserver hkps://keyserver.ubuntu.com:443 --recv e162f504a20cdf15827f718d4b7c549a058f8b6b ; \ - echo "deb [ arch=amd64 ] https://repo.mongodb.org/apt/ubuntu bionic/mongodb-org/4.2 multiverse" | tee /etc/apt/sources.list.d/mongodb-org-4.2.list; \ - echo ${TZ} > /etc/timezone; - -RUN apt-get update ; \ - apt-get install -y mongodb-org=4.2.0 mongodb-org-server=4.2.0 mongodb-org-shell=4.2.0 mongodb-org-mongos=4.2.0 mongodb-org-tools=4.2.0 ; \ - apt-get clean; \ - rm -rf /var/lib/apt/lists/*; diff --git a/ci/openjdk11-mongodb-4.4/Dockerfile b/ci/openjdk11-mongodb-4.4/Dockerfile new file mode 100644 index 0000000000..abacb005e5 --- /dev/null +++ b/ci/openjdk11-mongodb-4.4/Dockerfile @@ -0,0 +1,21 @@ +ARG BASE +FROM ${BASE} +# Any ARG statements before FROM are cleared. +ARG MONGODB + +ENV TZ=Etc/UTC +ENV DEBIAN_FRONTEND=noninteractive + +RUN set -eux; \ + sed -i -e 's/archive.ubuntu.com/mirror.one.com/g' /etc/apt/sources.list; \ + sed -i -e 's/security.ubuntu.com/mirror.one.com/g' /etc/apt/sources.list; \ + sed -i -e 's/http/https/g' /etc/apt/sources.list ; \ + apt-get update && apt-get install -y apt-transport-https apt-utils gnupg2 ; \ + apt-key adv --keyserver hkps://keyserver.ubuntu.com:443 --recv 656408E390CFB1F5 ; \ + echo "deb [ arch=amd64 ] https://repo.mongodb.org/apt/ubuntu bionic/mongodb-org/4.4 multiverse" | tee /etc/apt/sources.list.d/mongodb-org-4.4.list; \ + echo ${TZ} > /etc/timezone; + +RUN apt-get update ; \ + apt-get install -y mongodb-org=${MONGODB} mongodb-org-server=${MONGODB} mongodb-org-shell=${MONGODB} mongodb-org-mongos=${MONGODB} mongodb-org-tools=${MONGODB} ; \ + apt-get clean; \ + rm -rf /var/lib/apt/lists/*; diff --git a/ci/openjdk14-mongodb-4.2/Dockerfile b/ci/openjdk14-mongodb-4.2/Dockerfile deleted file mode 100644 index 5f7d26c929..0000000000 --- a/ci/openjdk14-mongodb-4.2/Dockerfile +++ /dev/null @@ -1,15 +0,0 @@ -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 e162f504a20cdf15827f718d4b7c549a058f8b6b ; \ - echo "deb [ arch=amd64 ] https://repo.mongodb.org/apt/ubuntu bionic/mongodb-org/4.2 multiverse" | tee /etc/apt/sources.list.d/mongodb-org-4.2.list; \ - echo ${TZ} > /etc/timezone; - -RUN apt-get update ; \ - apt-get install -y mongodb-org=4.2.0 mongodb-org-server=4.2.0 mongodb-org-shell=4.2.0 mongodb-org-mongos=4.2.0 mongodb-org-tools=4.2.0 ; \ - apt-get clean; \ - rm -rf /var/lib/apt/lists/*; diff --git a/ci/openjdk17-mongodb-4.4/Dockerfile b/ci/openjdk17-mongodb-4.4/Dockerfile new file mode 100644 index 0000000000..abacb005e5 --- /dev/null +++ b/ci/openjdk17-mongodb-4.4/Dockerfile @@ -0,0 +1,21 @@ +ARG BASE +FROM ${BASE} +# Any ARG statements before FROM are cleared. +ARG MONGODB + +ENV TZ=Etc/UTC +ENV DEBIAN_FRONTEND=noninteractive + +RUN set -eux; \ + sed -i -e 's/archive.ubuntu.com/mirror.one.com/g' /etc/apt/sources.list; \ + sed -i -e 's/security.ubuntu.com/mirror.one.com/g' /etc/apt/sources.list; \ + sed -i -e 's/http/https/g' /etc/apt/sources.list ; \ + apt-get update && apt-get install -y apt-transport-https apt-utils gnupg2 ; \ + apt-key adv --keyserver hkps://keyserver.ubuntu.com:443 --recv 656408E390CFB1F5 ; \ + echo "deb [ arch=amd64 ] https://repo.mongodb.org/apt/ubuntu bionic/mongodb-org/4.4 multiverse" | tee /etc/apt/sources.list.d/mongodb-org-4.4.list; \ + echo ${TZ} > /etc/timezone; + +RUN apt-get update ; \ + apt-get install -y mongodb-org=${MONGODB} mongodb-org-server=${MONGODB} mongodb-org-shell=${MONGODB} mongodb-org-mongos=${MONGODB} mongodb-org-tools=${MONGODB} ; \ + apt-get clean; \ + rm -rf /var/lib/apt/lists/*; diff --git a/ci/openjdk8-mongodb-4.0/Dockerfile b/ci/openjdk8-mongodb-4.0/Dockerfile index 050a1797f5..99586b7961 100644 --- a/ci/openjdk8-mongodb-4.0/Dockerfile +++ b/ci/openjdk8-mongodb-4.0/Dockerfile @@ -1,15 +1,21 @@ -FROM adoptopenjdk/openjdk8:latest +ARG BASE +FROM ${BASE} +# Any ARG statements before FROM are cleared. +ARG MONGODB ENV TZ=Etc/UTC ENV DEBIAN_FRONTEND=noninteractive RUN RUN set -eux; \ + sed -i -e 's/archive.ubuntu.com/mirror.one.com/g' /etc/apt/sources.list; \ + sed -i -e 's/security.ubuntu.com/mirror.one.com/g' /etc/apt/sources.list; \ + sed -i -e 's/http/https/g' /etc/apt/sources.list ; \ apt-get update && apt-get install -y apt-transport-https apt-utils gnupg2 ; \ apt-key adv --keyserver hkps://keyserver.ubuntu.com:443 --recv 9DA31620334BD75D9DCB49F368818C72E52529D4 ; \ echo "deb [ arch=amd64 ] https://repo.mongodb.org/apt/ubuntu bionic/mongodb-org/4.0 multiverse" | tee /etc/apt/sources.list.d/mongodb-org-4.0.list; \ echo ${TZ} > /etc/timezone; RUN apt-get update ; \ - apt-get install -y mongodb-org=4.0.14 mongodb-org-server=4.0.14 mongodb-org-shell=4.0.14 mongodb-org-mongos=4.0.14 mongodb-org-tools=4.0.14 ; \ + apt-get install -y mongodb-org=${MONGODB} mongodb-org-server=${MONGODB} mongodb-org-shell=${MONGODB} mongodb-org-mongos=${MONGODB} mongodb-org-tools=${MONGODB} ; \ apt-get clean; \ rm -rf /var/lib/apt/lists/*; diff --git a/ci/openjdk8-mongodb-4.2/Dockerfile b/ci/openjdk8-mongodb-4.2/Dockerfile deleted file mode 100644 index 846a10423b..0000000000 --- a/ci/openjdk8-mongodb-4.2/Dockerfile +++ /dev/null @@ -1,15 +0,0 @@ -FROM adoptopenjdk/openjdk8:latest - -ENV TZ=Etc/UTC -ENV DEBIAN_FRONTEND=noninteractive - -RUN set -eux; \ - apt-get update && apt-get install -y apt-transport-https apt-utils gnupg2 ; \ - apt-key adv --keyserver hkps://keyserver.ubuntu.com:443 --recv e162f504a20cdf15827f718d4b7c549a058f8b6b ; \ - echo "deb [ arch=amd64 ] https://repo.mongodb.org/apt/ubuntu bionic/mongodb-org/4.2 multiverse" | tee /etc/apt/sources.list.d/mongodb-org-4.2.list; \ - echo ${TZ} > /etc/timezone; - -RUN apt-get update ; \ - apt-get install -y mongodb-org=4.2.0 mongodb-org-server=4.2.0 mongodb-org-shell=4.2.0 mongodb-org-mongos=4.2.0 mongodb-org-tools=4.2.0 ; \ - apt-get clean; \ - rm -rf /var/lib/apt/lists/*; diff --git a/ci/openjdk8-mongodb-4.4/Dockerfile b/ci/openjdk8-mongodb-4.4/Dockerfile new file mode 100644 index 0000000000..87e212dbf6 --- /dev/null +++ b/ci/openjdk8-mongodb-4.4/Dockerfile @@ -0,0 +1,23 @@ +ARG BASE +FROM ${BASE} +# Any ARG statements before FROM are cleared. +ARG MONGODB + +ENV TZ=Etc/UTC +ENV DEBIAN_FRONTEND=noninteractive + +RUN set -eux; \ + sed -i -e 's/archive.ubuntu.com/mirror.one.com/g' /etc/apt/sources.list; \ + sed -i -e 's/security.ubuntu.com/mirror.one.com/g' /etc/apt/sources.list; \ + sed -i -e 's/http/https/g' /etc/apt/sources.list ; \ + apt-get update && apt-get install -y apt-transport-https apt-utils gnupg2 ; \ + apt-key adv --keyserver hkps://keyserver.ubuntu.com:443 --recv 656408E390CFB1F5 ; \ + echo "deb [ arch=amd64 ] https://repo.mongodb.org/apt/ubuntu bionic/mongodb-org/4.4 multiverse" | tee /etc/apt/sources.list.d/mongodb-org-4.4.list; \ + echo ${TZ} > /etc/timezone; + +RUN apt-get update ; \ + ln -T /bin/true /usr/bin/systemctl ; \ + apt-get install -y mongodb-org=${MONGODB} mongodb-org-server=${MONGODB} mongodb-org-shell=${MONGODB} mongodb-org-mongos=${MONGODB} mongodb-org-tools=${MONGODB} ; \ + rm /usr/bin/systemctl ; \ + apt-get clean ; \ + rm -rf /var/lib/apt/lists/* ; diff --git a/ci/openjdk8-mongodb-5.0/Dockerfile b/ci/openjdk8-mongodb-5.0/Dockerfile new file mode 100644 index 0000000000..127d2693bc --- /dev/null +++ b/ci/openjdk8-mongodb-5.0/Dockerfile @@ -0,0 +1,23 @@ +ARG BASE +FROM ${BASE} +# Any ARG statements before FROM are cleared. +ARG MONGODB + +ENV TZ=Etc/UTC +ENV DEBIAN_FRONTEND=noninteractive + +RUN set -eux; \ + sed -i -e 's/archive.ubuntu.com/mirror.one.com/g' /etc/apt/sources.list; \ + sed -i -e 's/security.ubuntu.com/mirror.one.com/g' /etc/apt/sources.list; \ + sed -i -e 's/http/https/g' /etc/apt/sources.list ; \ + apt-get update && apt-get install -y apt-transport-https apt-utils gnupg2 wget ; \ + # MongoDB 5.0 release signing key + apt-key adv --keyserver hkps://keyserver.ubuntu.com:443 --recv B00A0BD1E2C63C11 ; \ + # Needed when MongoDB creates a 5.0 folder. + echo "deb [ arch=amd64 ] https://repo.mongodb.org/apt/ubuntu bionic/mongodb-org/5.0 multiverse" | tee /etc/apt/sources.list.d/mongodb-org-5.0.list; \ + echo ${TZ} > /etc/timezone; + +RUN apt-get update; \ + apt-get install -y mongodb-org=${MONGODB} mongodb-org-server=${MONGODB} mongodb-org-shell=${MONGODB} mongodb-org-mongos=${MONGODB} mongodb-org-tools=${MONGODB} ; \ + apt-get clean; \ + rm -rf /var/lib/apt/lists/*; diff --git a/ci/pipeline.properties b/ci/pipeline.properties new file mode 100644 index 0000000000..f3c84e0527 --- /dev/null +++ b/ci/pipeline.properties @@ -0,0 +1,29 @@ +# Java versions +java.main.tag=8u322-b06-jdk +java.next.tag=11.0.14.1_1-jdk +java.lts.tag=17.0.2_8-jdk + +# Docker container images - standard +docker.java.main.image=harbor-repo.vmware.com/dockerhub-proxy-cache/library/eclipse-temurin:${java.main.tag} +docker.java.next.image=harbor-repo.vmware.com/dockerhub-proxy-cache/library/eclipse-temurin:${java.next.tag} +docker.java.lts.image=harbor-repo.vmware.com/dockerhub-proxy-cache/library/eclipse-temurin:${java.lts.tag} + +# Supported versions of MongoDB +docker.mongodb.4.0.version=4.0.28 +docker.mongodb.4.4.version=4.4.12 +docker.mongodb.5.0.version=5.0.6 + +# Supported versions of Redis +docker.redis.6.version=6.2.6 + +# Supported versions of Cassandra +docker.cassandra.3.version=3.11.12 + +# Docker environment settings +docker.java.inside.basic=-v $HOME:/tmp/jenkins-home +docker.java.inside.docker=-u root -v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker -v $HOME:/tmp/jenkins-home + +# Credentials +docker.registry= +docker.credentials=hub.docker.com-springbuildmaster +artifactory.credentials=02bd1690-b54f-4c9f-819d-a77cb7a9822c diff --git a/pom.xml b/pom.xml index ce0fa5e15d..4232689e67 100644 --- a/pom.xml +++ b/pom.xml @@ -5,17 +5,17 @@ org.springframework.data spring-data-mongodb-parent - 3.0.0.RELEASE + 3.3.5 pom Spring Data MongoDB MongoDB support for Spring Data - https://projects.spring.io/spring-data-mongodb + https://spring.io/projects/spring-data-mongodb org.springframework.data.build spring-data-parent - 2.3.0.RELEASE + 2.6.5 @@ -26,8 +26,8 @@ multi spring-data-mongodb - 2.3.0.RELEASE - 4.0.3 + 2.6.5 + 4.4.2 ${mongo} 1.19 @@ -112,6 +112,17 @@ + + scm:git:https://github.com/spring-projects/spring-data-mongodb.git + scm:git:git@github.com:spring-projects/spring-data-mongodb.git + https://github.com/spring-projects/spring-data-mongodb + + + + GitHub + https://github.com/spring-projects/spring-data-mongodb/issues + + benchmarks @@ -141,11 +152,11 @@ sonatype-libs-snapshot https://oss.sonatype.org/content/repositories/snapshots - false - + false + - true - + true + @@ -158,7 +169,6 @@ spring-libs-milestone https://repo.spring.io/libs-milestone - diff --git a/settings.xml b/settings.xml new file mode 100644 index 0000000000..b3227cc110 --- /dev/null +++ b/settings.xml @@ -0,0 +1,29 @@ + + + + + spring-plugins-release + ${env.ARTIFACTORY_USR} + ${env.ARTIFACTORY_PSW} + + + spring-libs-snapshot + ${env.ARTIFACTORY_USR} + ${env.ARTIFACTORY_PSW} + + + spring-libs-milestone + ${env.ARTIFACTORY_USR} + ${env.ARTIFACTORY_PSW} + + + spring-libs-release + ${env.ARTIFACTORY_USR} + ${env.ARTIFACTORY_PSW} + + + + \ No newline at end of file diff --git a/spring-data-mongodb-benchmarks/pom.xml b/spring-data-mongodb-benchmarks/pom.xml index fa6beb5778..8d083e47c0 100644 --- a/spring-data-mongodb-benchmarks/pom.xml +++ b/spring-data-mongodb-benchmarks/pom.xml @@ -7,7 +7,7 @@ org.springframework.data spring-data-mongodb-parent - 3.0.0.RELEASE + 3.3.5 ../pom.xml diff --git a/spring-data-mongodb-benchmarks/src/main/java/org/springframework/data/mongodb/core/ProjectionsBenchmark.java b/spring-data-mongodb-benchmarks/src/main/java/org/springframework/data/mongodb/core/ProjectionsBenchmark.java index c7df157f63..ac7932b6e8 100644 --- a/spring-data-mongodb-benchmarks/src/main/java/org/springframework/data/mongodb/core/ProjectionsBenchmark.java +++ b/spring-data-mongodb-benchmarks/src/main/java/org/springframework/data/mongodb/core/ProjectionsBenchmark.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2020 the original author or authors. + * Copyright 2017-2022 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. diff --git a/spring-data-mongodb-benchmarks/src/main/java/org/springframework/data/mongodb/core/convert/DbRefMappingBenchmark.java b/spring-data-mongodb-benchmarks/src/main/java/org/springframework/data/mongodb/core/convert/DbRefMappingBenchmark.java index 5694416f3d..a6c3105fe4 100644 --- a/spring-data-mongodb-benchmarks/src/main/java/org/springframework/data/mongodb/core/convert/DbRefMappingBenchmark.java +++ b/spring-data-mongodb-benchmarks/src/main/java/org/springframework/data/mongodb/core/convert/DbRefMappingBenchmark.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2020 the original author or authors. + * Copyright 2017-2022 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. diff --git a/spring-data-mongodb-benchmarks/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterBenchmark.java b/spring-data-mongodb-benchmarks/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterBenchmark.java index a29fb51e3e..196cdcbdc4 100644 --- a/spring-data-mongodb-benchmarks/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterBenchmark.java +++ b/spring-data-mongodb-benchmarks/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterBenchmark.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2020 the original author or authors. + * Copyright 2017-2022 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. diff --git a/spring-data-mongodb-benchmarks/src/main/java/org/springframework/data/mongodb/microbenchmark/AbstractMicrobenchmark.java b/spring-data-mongodb-benchmarks/src/main/java/org/springframework/data/mongodb/microbenchmark/AbstractMicrobenchmark.java index 4c09f1a166..ea940b3c8e 100644 --- a/spring-data-mongodb-benchmarks/src/main/java/org/springframework/data/mongodb/microbenchmark/AbstractMicrobenchmark.java +++ b/spring-data-mongodb-benchmarks/src/main/java/org/springframework/data/mongodb/microbenchmark/AbstractMicrobenchmark.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2020 the original author or authors. + * Copyright 2017-2022 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. diff --git a/spring-data-mongodb-benchmarks/src/main/java/org/springframework/data/mongodb/microbenchmark/HttpResultsWriter.java b/spring-data-mongodb-benchmarks/src/main/java/org/springframework/data/mongodb/microbenchmark/HttpResultsWriter.java index e6d728dbdc..ba34f38b1c 100644 --- a/spring-data-mongodb-benchmarks/src/main/java/org/springframework/data/mongodb/microbenchmark/HttpResultsWriter.java +++ b/spring-data-mongodb-benchmarks/src/main/java/org/springframework/data/mongodb/microbenchmark/HttpResultsWriter.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2020 the original author or authors. + * Copyright 2017-2022 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. diff --git a/spring-data-mongodb-benchmarks/src/main/java/org/springframework/data/mongodb/microbenchmark/MongoResultsWriter.java b/spring-data-mongodb-benchmarks/src/main/java/org/springframework/data/mongodb/microbenchmark/MongoResultsWriter.java index 19e7987ee2..ee12058f5f 100644 --- a/spring-data-mongodb-benchmarks/src/main/java/org/springframework/data/mongodb/microbenchmark/MongoResultsWriter.java +++ b/spring-data-mongodb-benchmarks/src/main/java/org/springframework/data/mongodb/microbenchmark/MongoResultsWriter.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2020 the original author or authors. + * Copyright 2017-2022 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. diff --git a/spring-data-mongodb-benchmarks/src/main/java/org/springframework/data/mongodb/microbenchmark/ResultsWriter.java b/spring-data-mongodb-benchmarks/src/main/java/org/springframework/data/mongodb/microbenchmark/ResultsWriter.java index bc43cb5ded..30cbaf6c08 100644 --- a/spring-data-mongodb-benchmarks/src/main/java/org/springframework/data/mongodb/microbenchmark/ResultsWriter.java +++ b/spring-data-mongodb-benchmarks/src/main/java/org/springframework/data/mongodb/microbenchmark/ResultsWriter.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2020 the original author or authors. + * Copyright 2017-2022 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. diff --git a/spring-data-mongodb-distribution/pom.xml b/spring-data-mongodb-distribution/pom.xml index f0cb34d5f3..9784bffcc5 100644 --- a/spring-data-mongodb-distribution/pom.xml +++ b/spring-data-mongodb-distribution/pom.xml @@ -14,7 +14,7 @@ org.springframework.data spring-data-mongodb-parent - 3.0.0.RELEASE + 3.3.5 ../pom.xml diff --git a/spring-data-mongodb/pom.xml b/spring-data-mongodb/pom.xml index 6ede5b361b..eae8fe722a 100644 --- a/spring-data-mongodb/pom.xml +++ b/spring-data-mongodb/pom.xml @@ -11,7 +11,7 @@ org.springframework.data spring-data-mongodb-parent - 3.0.0.RELEASE + 3.3.5 ../pom.xml @@ -87,6 +87,13 @@ true + + com.google.code.findbugs + jsr305 + 3.0.2 + true + + @@ -136,6 +143,13 @@ true + + io.reactivex.rxjava3 + rxjava + ${rxjava3} + true + + @@ -192,7 +206,14 @@ org.hibernate hibernate-validator - 5.2.4.Final + 5.4.3.Final + test + + + + org.glassfish + javax.el + 3.0.1-b11 test @@ -296,6 +317,15 @@ test + + + + org.jmolecules + jmolecules-ddd + ${jmolecules} + test + + diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/BindableMongoExpression.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/BindableMongoExpression.java new file mode 100644 index 0000000000..304ff63d25 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/BindableMongoExpression.java @@ -0,0 +1,152 @@ +/* + * Copyright 2021-2022 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 { ... } 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()}. + *
+ * + *
+ * $toUpper : $name                -> { '$toUpper' : '$name' }
+ *
+ * { '$toUpper' : '$name' }        -> { '$toUpper' : '$name' }
+ *
+ * { '$toUpper' : '?0' }, "$name"  -> { '$toUpper' : '$name' }
+ * 
+ * + * 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 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 + "}"; + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/BulkOperationException.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/BulkOperationException.java index eee31a1e67..3ed4675017 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/BulkOperationException.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/BulkOperationException.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-2022 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. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/ClientSessionException.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/ClientSessionException.java index 46c1c43546..76e73e2394 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/ClientSessionException.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/ClientSessionException.java @@ -1,5 +1,5 @@ /* - * Copyright 2018-2020 the original author or authors. + * Copyright 2018-2022 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. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/CodecRegistryProvider.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/CodecRegistryProvider.java index 81d0955f0a..0bed30ccea 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/CodecRegistryProvider.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/CodecRegistryProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2020 the original author or authors. + * Copyright 2017-2022 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. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/InvalidMongoDbApiUsageException.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/InvalidMongoDbApiUsageException.java index 4d5831dd4d..f380871d25 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/InvalidMongoDbApiUsageException.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/InvalidMongoDbApiUsageException.java @@ -1,5 +1,5 @@ /* - * Copyright 2010-2020 the original author or authors. + * Copyright 2010-2022 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. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/LazyLoadingException.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/LazyLoadingException.java index 0ec364f6c8..158b7e12cf 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/LazyLoadingException.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/LazyLoadingException.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2020 the original author or authors. + * Copyright 2013-2022 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. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoCollectionUtils.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoCollectionUtils.java index 4167250634..903bb71101 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoCollectionUtils.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoCollectionUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2020 the original author or authors. + * Copyright 2011-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,8 +20,8 @@ /** * Helper class featuring helper methods for working with MongoDb collections. - *

- *

+ *
+ *
* Mainly intended for internal use within the framework. * * @author Thomas Risberg diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoDatabaseFactory.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoDatabaseFactory.java index 96620d8f8a..8272e126b0 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoDatabaseFactory.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoDatabaseFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2019 the original author or authors. + * Copyright 2011-2022 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. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoDatabaseUtils.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoDatabaseUtils.java index 2c7d3903cd..f80a278057 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoDatabaseUtils.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoDatabaseUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2018-2020 the original author or authors. + * Copyright 2018-2022 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. @@ -30,7 +30,7 @@ * Helper class for managing a {@link MongoDatabase} instances via {@link MongoDatabaseFactory}. Used for obtaining * {@link ClientSession session bound} resources, such as {@link MongoDatabase} and * {@link com.mongodb.client.MongoCollection} suitable for transactional usage. - *

+ *
* Note: Intended for internal usage only. * * @author Christoph Strobl @@ -43,7 +43,7 @@ public class MongoDatabaseUtils { /** * Obtain the default {@link MongoDatabase database} form the given {@link MongoDatabaseFactory factory} using * {@link SessionSynchronization#ON_ACTUAL_TRANSACTION native session synchronization}. - *

+ *
* Registers a {@link MongoSessionSynchronization MongoDB specific transaction synchronization} within the current * {@link Thread} if {@link TransactionSynchronizationManager#isSynchronizationActive() synchronization is active}. * @@ -56,7 +56,7 @@ public static MongoDatabase getDatabase(MongoDatabaseFactory factory) { /** * Obtain the default {@link MongoDatabase database} form the given {@link MongoDatabaseFactory factory}. - *

+ *
* Registers a {@link MongoSessionSynchronization MongoDB specific transaction synchronization} within the current * {@link Thread} if {@link TransactionSynchronizationManager#isSynchronizationActive() synchronization is active}. * @@ -71,7 +71,7 @@ public static MongoDatabase getDatabase(MongoDatabaseFactory factory, SessionSyn /** * Obtain the {@link MongoDatabase database} with given name form the given {@link MongoDatabaseFactory factory} using * {@link SessionSynchronization#ON_ACTUAL_TRANSACTION native session synchronization}. - *

+ *
* Registers a {@link MongoSessionSynchronization MongoDB specific transaction synchronization} within the current * {@link Thread} if {@link TransactionSynchronizationManager#isSynchronizationActive() synchronization is active}. * @@ -79,13 +79,13 @@ public static MongoDatabase getDatabase(MongoDatabaseFactory factory, SessionSyn * @param factory the {@link MongoDatabaseFactory} to get the {@link MongoDatabase} from. * @return the {@link MongoDatabase} that is potentially associated with a transactional {@link ClientSession}. */ - public static MongoDatabase getDatabase(String dbName, MongoDatabaseFactory factory) { + public static MongoDatabase getDatabase(@Nullable String dbName, MongoDatabaseFactory factory) { return doGetMongoDatabase(dbName, factory, SessionSynchronization.ON_ACTUAL_TRANSACTION); } /** * Obtain the {@link MongoDatabase database} with given name form the given {@link MongoDatabaseFactory factory}. - *

+ *
* Registers a {@link MongoSessionSynchronization MongoDB specific transaction synchronization} within the current * {@link Thread} if {@link TransactionSynchronizationManager#isSynchronizationActive() synchronization is active}. * @@ -94,7 +94,7 @@ public static MongoDatabase getDatabase(String dbName, MongoDatabaseFactory fact * @param sessionSynchronization the synchronization to use. Must not be {@literal null}. * @return the {@link MongoDatabase} that is potentially associated with a transactional {@link ClientSession}. */ - public static MongoDatabase getDatabase(String dbName, MongoDatabaseFactory factory, + public static MongoDatabase getDatabase(@Nullable String dbName, MongoDatabaseFactory factory, SessionSynchronization sessionSynchronization) { return doGetMongoDatabase(dbName, factory, sessionSynchronization); } @@ -104,7 +104,8 @@ private static MongoDatabase doGetMongoDatabase(@Nullable String dbName, MongoDa Assert.notNull(factory, "Factory must not be null!"); - if (!TransactionSynchronizationManager.isSynchronizationActive()) { + if (sessionSynchronization == SessionSynchronization.NEVER + || !TransactionSynchronizationManager.isSynchronizationActive()) { return StringUtils.hasText(dbName) ? factory.getMongoDatabase(dbName) : factory.getMongoDatabase(); } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoDbFactory.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoDbFactory.java index aa46fbc882..1190f9470e 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoDbFactory.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoDbFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2020 the original author or authors. + * Copyright 2011-2022 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. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoExpression.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoExpression.java new file mode 100644 index 0000000000..498f835ac1 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoExpression.java @@ -0,0 +1,73 @@ +/* + * Copyright 2021-2022 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. + *
+ * 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}).
+ * The given expression will be wrapped with { ... } 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()}.
+ * The given expression will be wrapped with { ... } 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(); +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoResourceHolder.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoResourceHolder.java index c1b80f814f..5528dce38c 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoResourceHolder.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoResourceHolder.java @@ -1,5 +1,5 @@ /* - * Copyright 2018-2020 the original author or authors. + * Copyright 2018-2022 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. @@ -24,7 +24,7 @@ /** * MongoDB specific {@link ResourceHolderSupport resource holder}, wrapping a {@link ClientSession}. * {@link MongoTransactionManager} binds instances of this class to the thread. - *

+ *
* Note: Intended for internal usage only. * * @author Christoph Strobl diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoSessionProvider.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoSessionProvider.java index 47cd65a1c8..202089dab3 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoSessionProvider.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoSessionProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2018-2020 the original author or authors. + * Copyright 2018-2022 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. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoTransactionException.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoTransactionException.java index 746a769e22..62a3664dc4 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoTransactionException.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoTransactionException.java @@ -1,5 +1,5 @@ /* - * Copyright 2018-2020 the original author or authors. + * Copyright 2018-2022 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. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoTransactionManager.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoTransactionManager.java index b0c8d640f7..7aa54472c8 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoTransactionManager.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoTransactionManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2018-2020 the original author or authors. + * Copyright 2018-2022 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. @@ -37,18 +37,18 @@ /** * A {@link org.springframework.transaction.PlatformTransactionManager} implementation that manages * {@link ClientSession} based transactions for a single {@link MongoDatabaseFactory}. - *

+ *
* Binds a {@link ClientSession} from the specified {@link MongoDatabaseFactory} to the thread. - *

+ *
* {@link TransactionDefinition#isReadOnly() Readonly} transactions operate on a {@link ClientSession} and enable causal * consistency, and also {@link ClientSession#startTransaction() start}, {@link ClientSession#commitTransaction() * commit} or {@link ClientSession#abortTransaction() abort} a transaction. - *

+ *
* Application code is required to retrieve the {@link com.mongodb.client.MongoDatabase} via * {@link MongoDatabaseUtils#getDatabase(MongoDatabaseFactory)} instead of a standard * {@link MongoDatabaseFactory#getMongoDatabase()} call. Spring classes such as * {@link org.springframework.data.mongodb.core.MongoTemplate} use this strategy implicitly. - *

+ *
* By default failure of a {@literal commit} operation raises a {@link TransactionSystemException}. One may override * {@link #doCommit(MongoTransactionObject)} to implement the * Retry Commit Operation @@ -69,11 +69,11 @@ public class MongoTransactionManager extends AbstractPlatformTransactionManager /** * Create a new {@link MongoTransactionManager} for bean-style usage. - *

+ *
* Note:The {@link MongoDatabaseFactory db factory} has to be * {@link #setDbFactory(MongoDatabaseFactory) set} before using the instance. Use this constructor to prepare a * {@link MongoTransactionManager} via a {@link org.springframework.beans.factory.BeanFactory}. - *

+ *
* Optionally it is possible to set default {@link TransactionOptions transaction options} defining * {@link com.mongodb.ReadConcern} and {@link com.mongodb.WriteConcern}. * @@ -212,8 +212,8 @@ protected final void doCommit(DefaultTransactionStatus status) throws Transactio * By default those labels are ignored, nevertheless one might check for * {@link MongoException#UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL transient commit errors labels} and retry the the * commit.
+ *

 	 * 
-	 *     
 	 * int retries = 3;
 	 * do {
 	 *     try {
@@ -226,8 +226,8 @@ protected final void doCommit(DefaultTransactionStatus status) throws Transactio
 	 *     }
 	 *     Thread.sleep(500);
 	 * } while (--retries > 0);
-	 *     
*
+ *
* * @param transactionObject never {@literal null}. * @throws Exception in case of transaction errors. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/ReactiveMongoDatabaseFactory.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/ReactiveMongoDatabaseFactory.java index 6a54e94e65..77674a41e3 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/ReactiveMongoDatabaseFactory.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/ReactiveMongoDatabaseFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2020 the original author or authors. + * Copyright 2016-2022 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. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/ReactiveMongoDatabaseUtils.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/ReactiveMongoDatabaseUtils.java index 6138d71a57..98b7c197b0 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/ReactiveMongoDatabaseUtils.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/ReactiveMongoDatabaseUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 the original author or authors. + * Copyright 2019-2022 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. @@ -36,7 +36,7 @@ * Helper class for managing reactive {@link MongoDatabase} instances via {@link ReactiveMongoDatabaseFactory}. Used for * obtaining {@link ClientSession session bound} resources, such as {@link MongoDatabase} and {@link MongoCollection} * suitable for transactional usage. - *

+ *
* Note: Intended for internal usage only. * * @author Mark Paluch @@ -75,7 +75,7 @@ public static Mono isTransactionActive(ReactiveMongoDatabaseFactory dat /** * Obtain the default {@link MongoDatabase database} form the given {@link ReactiveMongoDatabaseFactory factory} using * {@link SessionSynchronization#ON_ACTUAL_TRANSACTION native session synchronization}. - *

+ *
* Registers a {@link MongoSessionSynchronization MongoDB specific transaction synchronization} within the subscriber * {@link Context} if {@link TransactionSynchronizationManager#isSynchronizationActive() synchronization is active}. * @@ -88,7 +88,7 @@ public static Mono getDatabase(ReactiveMongoDatabaseFactory facto /** * Obtain the default {@link MongoDatabase database} form the given {@link ReactiveMongoDatabaseFactory factory}. - *

+ *
* Registers a {@link MongoSessionSynchronization MongoDB specific transaction synchronization} within the subscriber * {@link Context} if {@link TransactionSynchronizationManager#isSynchronizationActive() synchronization is active}. * @@ -104,7 +104,7 @@ public static Mono getDatabase(ReactiveMongoDatabaseFactory facto /** * Obtain the {@link MongoDatabase database} with given name form the given {@link ReactiveMongoDatabaseFactory * factory} using {@link SessionSynchronization#ON_ACTUAL_TRANSACTION native session synchronization}. - *

+ *
* Registers a {@link MongoSessionSynchronization MongoDB specific transaction synchronization} within the subscriber * {@link Context} if {@link TransactionSynchronizationManager#isSynchronizationActive() synchronization is active}. * @@ -119,7 +119,7 @@ public static Mono getDatabase(String dbName, ReactiveMongoDataba /** * Obtain the {@link MongoDatabase database} with given name form the given {@link ReactiveMongoDatabaseFactory * factory}. - *

+ *
* Registers a {@link MongoSessionSynchronization MongoDB specific transaction synchronization} within the subscriber * {@link Context} if {@link TransactionSynchronizationManager#isSynchronizationActive() synchronization is active}. * @@ -138,6 +138,10 @@ private static Mono doGetMongoDatabase(@Nullable String dbName, R Assert.notNull(factory, "DatabaseFactory must not be null!"); + if (sessionSynchronization == SessionSynchronization.NEVER) { + return getMongoDatabaseOrDefault(dbName, factory); + } + return TransactionSynchronizationManager.forCurrentTransaction() .filter(TransactionSynchronizationManager::isSynchronizationActive) // .flatMap(synchronizationManager -> { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/ReactiveMongoResourceHolder.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/ReactiveMongoResourceHolder.java index 34301bdc6f..fdcf4d520c 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/ReactiveMongoResourceHolder.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/ReactiveMongoResourceHolder.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 the original author or authors. + * Copyright 2019-2022 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. @@ -24,7 +24,7 @@ /** * MongoDB specific resource holder, wrapping a {@link ClientSession}. {@link ReactiveMongoTransactionManager} binds * instances of this class to the subscriber context. - *

+ *
* Note: Intended for internal usage only. * * @author Mark Paluch diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/ReactiveMongoTransactionManager.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/ReactiveMongoTransactionManager.java index 8792e169cc..d0d9d59c6f 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/ReactiveMongoTransactionManager.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/ReactiveMongoTransactionManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 the original author or authors. + * Copyright 2019-2022 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. @@ -38,21 +38,21 @@ * A {@link org.springframework.transaction.ReactiveTransactionManager} implementation that manages * {@link com.mongodb.reactivestreams.client.ClientSession} based transactions for a single * {@link org.springframework.data.mongodb.ReactiveMongoDatabaseFactory}. - *

+ *
* Binds a {@link ClientSession} from the specified * {@link org.springframework.data.mongodb.ReactiveMongoDatabaseFactory} to the subscriber * {@link reactor.util.context.Context}. - *

+ *
* {@link org.springframework.transaction.TransactionDefinition#isReadOnly() Readonly} transactions operate on a * {@link ClientSession} and enable causal consistency, and also {@link ClientSession#startTransaction() start}, * {@link com.mongodb.reactivestreams.client.ClientSession#commitTransaction() commit} or * {@link ClientSession#abortTransaction() abort} a transaction. - *

+ *
* Application code is required to retrieve the {@link com.mongodb.reactivestreams.client.MongoDatabase} via * {@link org.springframework.data.mongodb.ReactiveMongoDatabaseUtils#getDatabase(ReactiveMongoDatabaseFactory)} instead * of a standard {@link org.springframework.data.mongodb.ReactiveMongoDatabaseFactory#getMongoDatabase()} call. Spring * classes such as {@link org.springframework.data.mongodb.core.ReactiveMongoTemplate} use this strategy implicitly. - *

+ *
* By default failure of a {@literal commit} operation raises a {@link TransactionSystemException}. You can override * {@link #doCommit(TransactionSynchronizationManager, ReactiveMongoTransactionObject)} to implement the * Retry Commit Operation @@ -71,11 +71,11 @@ public class ReactiveMongoTransactionManager extends AbstractReactiveTransaction /** * Create a new {@link ReactiveMongoTransactionManager} for bean-style usage. - *

+ *
* Note:The {@link org.springframework.data.mongodb.ReactiveMongoDatabaseFactory db factory} has to * be {@link #setDatabaseFactory(ReactiveMongoDatabaseFactory)} set} before using the instance. Use this constructor * to prepare a {@link ReactiveMongoTransactionManager} via a {@link org.springframework.beans.factory.BeanFactory}. - *

+ *
* Optionally it is possible to set default {@link TransactionOptions transaction options} defining * {@link com.mongodb.ReadConcern} and {@link com.mongodb.WriteConcern}. * @@ -110,7 +110,7 @@ public ReactiveMongoTransactionManager(ReactiveMongoDatabaseFactory databaseFact this.options = options; } - /* + /* * (non-Javadoc) * @see org.springframework.transaction.reactive.AbstractReactiveTransactionManager#doGetTransaction(org.springframework.transaction.reactive.TransactionSynchronizationManager) */ @@ -123,7 +123,7 @@ protected Object doGetTransaction(TransactionSynchronizationManager synchronizat return new ReactiveMongoTransactionObject(resourceHolder); } - /* + /* * (non-Javadoc) * @see org.springframework.transaction.reactive.AbstractReactiveTransactionManager#isExistingTransaction(java.lang.Object) */ @@ -132,7 +132,7 @@ protected boolean isExistingTransaction(Object transaction) throws TransactionEx return extractMongoTransaction(transaction).hasResourceHolder(); } - /* + /* * (non-Javadoc) * @see org.springframework.transaction.reactive.AbstractReactiveTransactionManager#doBegin(org.springframework.transaction.reactive.TransactionSynchronizationManager, java.lang.Object, org.springframework.transaction.TransactionDefinition) */ @@ -175,7 +175,7 @@ protected Mono doBegin(TransactionSynchronizationManager synchronizationMa }); } - /* + /* * (non-Javadoc) * @see org.springframework.transaction.reactive.AbstractReactiveTransactionManager#doSuspend(org.springframework.transaction.reactive.TransactionSynchronizationManager, java.lang.Object) */ @@ -192,7 +192,7 @@ protected Mono doSuspend(TransactionSynchronizationManager synchronizati }); } - /* + /* * (non-Javadoc) * @see org.springframework.transaction.reactive.AbstractReactiveTransactionManager#doResume(org.springframework.transaction.reactive.TransactionSynchronizationManager, java.lang.Object, java.lang.Object) */ @@ -203,7 +203,7 @@ protected Mono doResume(TransactionSynchronizationManager synchronizationM .fromRunnable(() -> synchronizationManager.bindResource(getRequiredDatabaseFactory(), suspendedResources)); } - /* + /* * (non-Javadoc) * @see org.springframework.transaction.reactive.AbstractReactiveTransactionManager#doCommit(org.springframework.transaction.reactive.TransactionSynchronizationManager, org.springframework.transaction.reactive.GenericReactiveTransaction) */ @@ -243,7 +243,7 @@ protected Mono doCommit(TransactionSynchronizationManager synchronizationM return transactionObject.commitTransaction(); } - /* + /* * (non-Javadoc) * @see org.springframework.transaction.reactive.AbstractReactiveTransactionManager#doRollback(org.springframework.transaction.reactive.TransactionSynchronizationManager, org.springframework.transaction.reactive.GenericReactiveTransaction) */ @@ -268,7 +268,7 @@ protected Mono doRollback(TransactionSynchronizationManager synchronizatio }); } - /* + /* * (non-Javadoc) * @see org.springframework.transaction.reactive.AbstractReactiveTransactionManager#doSetRollbackOnly(org.springframework.transaction.reactive.TransactionSynchronizationManager, org.springframework.transaction.reactive.GenericReactiveTransaction) */ @@ -282,7 +282,7 @@ protected Mono doSetRollbackOnly(TransactionSynchronizationManager synchro }); } - /* + /* * (non-Javadoc) * @see org.springframework.transaction.reactive.AbstractReactiveTransactionManager#doCleanupAfterCompletion(org.springframework.transaction.reactive.TransactionSynchronizationManager, java.lang.Object) */ @@ -509,7 +509,7 @@ private ClientSession getRequiredSession() { return session; } - /* + /* * (non-Javadoc) * @see org.springframework.transaction.support.SmartTransactionObject#isRollbackOnly() */ @@ -518,7 +518,7 @@ public boolean isRollbackOnly() { return this.resourceHolder != null && this.resourceHolder.isRollbackOnly(); } - /* + /* * (non-Javadoc) * @see org.springframework.transaction.support.SmartTransactionObject#flush() */ diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/SessionAwareMethodInterceptor.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/SessionAwareMethodInterceptor.java index 621a5389e3..08bf587b20 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/SessionAwareMethodInterceptor.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/SessionAwareMethodInterceptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2018-2020 the original author or authors. + * Copyright 2018-2022 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. @@ -35,7 +35,7 @@ /** * {@link MethodInterceptor} implementation looking up and invoking an alternative target method having * {@link ClientSession} as its first argument. This allows seamless integration with the existing code base. - *

+ *
* The {@link MethodInterceptor} is aware of methods on {@code MongoCollection} that my return new instances of itself * like (eg. {@link com.mongodb.reactivestreams.client.MongoCollection#withWriteConcern(WriteConcern)} and decorate them * if not already proxied. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/SessionSynchronization.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/SessionSynchronization.java index f32d0f593e..a773b91399 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/SessionSynchronization.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/SessionSynchronization.java @@ -1,5 +1,5 @@ /* - * Copyright 2018-2020 the original author or authors. + * Copyright 2018-2022 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. @@ -15,13 +15,20 @@ */ package org.springframework.data.mongodb; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.ReactiveMongoTemplate; + /** - * {@link SessionSynchronization} is used along with {@link org.springframework.data.mongodb.core.MongoTemplate} to - * define in which type of transactions to participate if any. + * {@link SessionSynchronization} is used along with {@code MongoTemplate} to define in which type of transactions to + * participate if any. * * @author Christoph Strobl * @author Mark Paluch * @since 2.1 + * @see MongoTemplate#setSessionSynchronization(SessionSynchronization) + * @see MongoDatabaseUtils#getDatabase(MongoDatabaseFactory, SessionSynchronization) + * @see ReactiveMongoTemplate#setSessionSynchronization(SessionSynchronization) + * @see ReactiveMongoDatabaseUtils#getDatabase(ReactiveMongoDatabaseFactory, SessionSynchronization) */ public enum SessionSynchronization { @@ -34,5 +41,12 @@ public enum SessionSynchronization { /** * Synchronize with native MongoDB transactions initiated via {@link MongoTransactionManager}. */ - ON_ACTUAL_TRANSACTION; + ON_ACTUAL_TRANSACTION, + + /** + * Do not participate in ongoing transactions. + * + * @since 3.2.5 + */ + NEVER; } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/SpringDataMongoDB.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/SpringDataMongoDB.java index cc3004644f..45d71eb8c1 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/SpringDataMongoDB.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/SpringDataMongoDB.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 the original author or authors. + * Copyright 2020-2022 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. @@ -48,7 +48,7 @@ public static MongoDriverInformation driverInformation() { /** * Fetches the "Implementation-Version" manifest attribute from the jar file. - *

+ *
* Note that some ClassLoaders do not expose the package metadata, hence this class might not be able to determine the * version in all environments. In this case the current Major version is returned as a fallback. * diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/UncategorizedMongoDbException.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/UncategorizedMongoDbException.java index fc2079ba8c..5497f9109e 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/UncategorizedMongoDbException.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/UncategorizedMongoDbException.java @@ -1,5 +1,5 @@ /* - * Copyright 2010-2020 the original author or authors. + * Copyright 2010-2022 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. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/AbstractMongoClientConfiguration.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/AbstractMongoClientConfiguration.java index a9635c9743..55a5b5316c 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/AbstractMongoClientConfiguration.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/AbstractMongoClientConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2018-2020 the original author or authors. + * Copyright 2018-2022 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. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/AbstractReactiveMongoConfiguration.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/AbstractReactiveMongoConfiguration.java index f02fbaabf0..b745205660 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/AbstractReactiveMongoConfiguration.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/AbstractReactiveMongoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2020 the original author or authors. + * Copyright 2016-2022 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. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/BeanNames.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/BeanNames.java index 3bb71c86ab..a24448ccb9 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/BeanNames.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/BeanNames.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2020 the original author or authors. + * Copyright 2011-2022 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. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/ConnectionStringPropertyEditor.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/ConnectionStringPropertyEditor.java index 3fd9017ef9..64fcb03a4e 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/ConnectionStringPropertyEditor.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/ConnectionStringPropertyEditor.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 the original author or authors. + * Copyright 2019-2022 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. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/EnableMongoAuditing.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/EnableMongoAuditing.java index 98bd186da2..c1d8b3941d 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/EnableMongoAuditing.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/EnableMongoAuditing.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2020 the original author or authors. + * Copyright 2013-2022 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. @@ -61,8 +61,8 @@ boolean modifyOnCreate() default true; /** - * Configures a {@link DateTimeProvider} bean name that allows customizing the {@link org.joda.time.DateTime} to be - * used for setting creation and modification dates. + * 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. */ diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/EnableReactiveMongoAuditing.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/EnableReactiveMongoAuditing.java new file mode 100644 index 0000000000..2b79df6121 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/EnableReactiveMongoAuditing.java @@ -0,0 +1,70 @@ +/* + * Copyright 2020-2022 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 ""; +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/GeoJsonConfiguration.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/GeoJsonConfiguration.java index d93c11466d..1288de8564 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/GeoJsonConfiguration.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/GeoJsonConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-2022 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. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/GridFsTemplateParser.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/GridFsTemplateParser.java index 1b03d777bc..336e479f46 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/GridFsTemplateParser.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/GridFsTemplateParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2020 the original author or authors. + * Copyright 2013-2022 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. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/MappingMongoConverterParser.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/MappingMongoConverterParser.java index 767c03e3af..ecad529cd5 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/MappingMongoConverterParser.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/MappingMongoConverterParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2020 the original author or authors. + * Copyright 2011-2022 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. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/MongoAuditingBeanDefinitionParser.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/MongoAuditingBeanDefinitionParser.java index fb3ac7408e..2ddbbad2ba 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/MongoAuditingBeanDefinitionParser.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/MongoAuditingBeanDefinitionParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2022 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. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/MongoAuditingRegistrar.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/MongoAuditingRegistrar.java index f842b9b738..7a5db867e2 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/MongoAuditingRegistrar.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/MongoAuditingRegistrar.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2020 the original author or authors. + * Copyright 2013-2022 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. @@ -17,7 +17,6 @@ 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; @@ -28,14 +27,8 @@ 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. @@ -46,9 +39,6 @@ */ 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() @@ -91,7 +81,7 @@ protected BeanDefinitionBuilder getAuditHandlerBeanDefinitionBuilder(AuditingCon BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(IsNewAwareAuditingHandler.class); - BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(MongoMappingContextLookup.class); + BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(PersistentEntitiesFactoryBean.class); definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR); builder.addConstructorArgValue(definition.getBeanDefinition()); @@ -116,68 +106,6 @@ protected void registerAuditListenerBeanDefinition(BeanDefinition auditingHandle 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, 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, 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; - } - } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/MongoClientParser.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/MongoClientParser.java index b3dcfdabce..15f8aa5104 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/MongoClientParser.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/MongoClientParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-2022 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. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/MongoConfigurationSupport.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/MongoConfigurationSupport.java index a96617de46..2da2984687 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/MongoConfigurationSupport.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/MongoConfigurationSupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2020 the original author or authors. + * Copyright 2016-2022 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. @@ -26,7 +26,6 @@ import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider; import org.springframework.core.convert.converter.Converter; import org.springframework.core.type.filter.AnnotationTypeFilter; -import org.springframework.data.annotation.Persistent; import org.springframework.data.convert.CustomConversions; import org.springframework.data.mapping.model.CamelCaseAbbreviatingFieldNamingStrategy; import org.springframework.data.mapping.model.FieldNamingStrategy; @@ -140,8 +139,7 @@ protected Set> getInitialEntitySet() throws ClassNotFoundException { } /** - * Scans the given base package for entities, i.e. MongoDB specific types annotated with {@link Document} and - * {@link Persistent}. + * Scans the given base package for entities, i.e. MongoDB specific types annotated with {@link Document}. * * @param basePackage must not be {@literal null}. * @return @@ -161,7 +159,6 @@ protected Set> scanForEntities(String basePackage) throws ClassNotFound ClassPathScanningCandidateComponentProvider componentProvider = new ClassPathScanningCandidateComponentProvider( false); componentProvider.addIncludeFilter(new AnnotationTypeFilter(Document.class)); - componentProvider.addIncludeFilter(new AnnotationTypeFilter(Persistent.class)); for (BeanDefinition candidate : componentProvider.findCandidateComponents(basePackage)) { @@ -175,8 +172,7 @@ protected Set> scanForEntities(String basePackage) throws ClassNotFound /** * Configures whether to abbreviate field names for domain objects by configuring a - * {@link CamelCaseAbbreviatingFieldNamingStrategy} on the {@link MongoMappingContext} instance created. For advanced - * customization needs, consider overriding {@link #mappingMongoConverter()}. + * {@link CamelCaseAbbreviatingFieldNamingStrategy} on the {@link MongoMappingContext} instance created. * * @return */ diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/MongoCredentialPropertyEditor.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/MongoCredentialPropertyEditor.java index 39f17c4142..8cbe3b1afc 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/MongoCredentialPropertyEditor.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/MongoCredentialPropertyEditor.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-2022 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. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/MongoDbFactoryParser.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/MongoDbFactoryParser.java index c929b5b3a6..d59b57e722 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/MongoDbFactoryParser.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/MongoDbFactoryParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2020 the original author or authors. + * Copyright 2011-2022 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. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/MongoJmxParser.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/MongoJmxParser.java index 93248e2f3f..fcda5986b8 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/MongoJmxParser.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/MongoJmxParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2020 the original author or authors. + * Copyright 2011-2022 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. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/MongoNamespaceHandler.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/MongoNamespaceHandler.java index 2516ca70e4..13fafa32aa 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/MongoNamespaceHandler.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/MongoNamespaceHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2020 the original author or authors. + * Copyright 2011-2022 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. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/MongoParsingUtils.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/MongoParsingUtils.java index 7ed91b27f9..a224e2e592 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/MongoParsingUtils.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/MongoParsingUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2020 the original author or authors. + * Copyright 2011-2022 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. @@ -22,9 +22,12 @@ import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.CustomEditorConfigurer; import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.BeanDefinitionValidationException; import org.springframework.beans.factory.support.ManagedMap; import org.springframework.beans.factory.xml.BeanDefinitionParser; import org.springframework.data.mongodb.core.MongoClientSettingsFactoryBean; +import org.springframework.data.mongodb.core.MongoServerApiFactoryBean; +import org.springframework.util.StringUtils; import org.springframework.util.xml.DomUtils; import org.w3c.dom.Element; @@ -45,7 +48,7 @@ private MongoParsingUtils() {} /** * Parses the {@code mongo:client-settings} sub-element. Populates the given attribute factory with the proper * attributes. - * + * * @param element * @param mongoClientBuilder * @return @@ -112,6 +115,20 @@ public static boolean parseMongoClientSettings(Element element, BeanDefinitionBu // Field level encryption setPropertyReference(clientOptionsDefBuilder, settingsElement, "encryption-settings-ref", "autoEncryptionSettings"); + // ServerAPI + if (StringUtils.hasText(settingsElement.getAttribute("server-api-version"))) { + + MongoServerApiFactoryBean serverApiFactoryBean = new MongoServerApiFactoryBean(); + serverApiFactoryBean.setVersion(settingsElement.getAttribute("server-api-version")); + try { + clientOptionsDefBuilder.addPropertyValue("serverApi", serverApiFactoryBean.getObject()); + } catch (Exception exception) { + throw new BeanDefinitionValidationException("Non parsable server-api.", exception); + } + } else { + setPropertyReference(clientOptionsDefBuilder, settingsElement, "server-api-ref", "serverApi"); + } + // and the rest mongoClientBuilder.addPropertyValue("mongoClientSettings", clientOptionsDefBuilder.getBeanDefinition()); diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/MongoTemplateParser.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/MongoTemplateParser.java index 65aa04745f..1d90ac2837 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/MongoTemplateParser.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/MongoTemplateParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2020 the original author or authors. + * Copyright 2011-2022 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. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/PersistentEntitiesFactoryBean.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/PersistentEntitiesFactoryBean.java new file mode 100644 index 0000000000..920d334b79 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/PersistentEntitiesFactoryBean.java @@ -0,0 +1,61 @@ +/* + * Copyright 2020-2022 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 + */ +public class PersistentEntitiesFactoryBean implements FactoryBean { + + 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; + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/ReactiveMongoAuditingRegistrar.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/ReactiveMongoAuditingRegistrar.java new file mode 100644 index 0000000000..342a54e47b --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/ReactiveMongoAuditingRegistrar.java @@ -0,0 +1,97 @@ +/* + * Copyright 2020-2022 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 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); + } + +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/ReadConcernPropertyEditor.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/ReadConcernPropertyEditor.java index ed5d73d6e6..238c984cff 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/ReadConcernPropertyEditor.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/ReadConcernPropertyEditor.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 the original author or authors. + * Copyright 2019-2022 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. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/ReadPreferencePropertyEditor.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/ReadPreferencePropertyEditor.java index 87174f28c0..9e5568a903 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/ReadPreferencePropertyEditor.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/ReadPreferencePropertyEditor.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-2022 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. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/ServerAddressPropertyEditor.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/ServerAddressPropertyEditor.java index 2865248eac..d8c0c5145e 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/ServerAddressPropertyEditor.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/ServerAddressPropertyEditor.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2020 the original author or authors. + * Copyright 2011-2022 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. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/StringToWriteConcernConverter.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/StringToWriteConcernConverter.java index 141c874ffc..2d5f957927 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/StringToWriteConcernConverter.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/StringToWriteConcernConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2022 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. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/UUidRepresentationPropertyEditor.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/UUidRepresentationPropertyEditor.java index 2499ecc48b..4a9d9b47e2 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/UUidRepresentationPropertyEditor.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/UUidRepresentationPropertyEditor.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 the original author or authors. + * Copyright 2020-2022 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. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/WriteConcernPropertyEditor.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/WriteConcernPropertyEditor.java index e1b1531e15..9041a58577 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/WriteConcernPropertyEditor.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/WriteConcernPropertyEditor.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2020 the original author or authors. + * Copyright 2011-2022 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. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/AggregationUtil.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/AggregationUtil.java index e67773c1fc..3873a67fc0 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/AggregationUtil.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/AggregationUtil.java @@ -1,5 +1,5 @@ /* - * Copyright 2018-2020 the original author or authors. + * Copyright 2018-2022 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. @@ -15,8 +15,6 @@ */ package org.springframework.data.mongodb.core; -import lombok.AllArgsConstructor; - import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -29,7 +27,9 @@ 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,6 +37,7 @@ 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; @@ -49,34 +50,50 @@ * @author Mark Paluch * @since 2.1 */ -@AllArgsConstructor class AggregationUtil { QueryMapper queryMapper; MappingContext, MongoPersistentProperty> mappingContext; + Lazy untypedMappingContext; - /** - * 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) { + AggregationUtil(QueryMapper queryMapper, + MappingContext, MongoPersistentProperty> mappingContext) { + + 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 (!(aggregation instanceof TypedAggregation)) { - if (context != null) { - return context; + 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 Aggregation.DEFAULT_CONTEXT; + return new RelaxedTypeBasedAggregationOperationContext(inputType, mappingContext, queryMapper); } /** @@ -88,7 +105,7 @@ AggregationOperationContext prepareAggregationContext(Aggregation aggregation, */ List createPipeline(Aggregation aggregation, AggregationOperationContext context) { - if (!ObjectUtils.nullSafeEquals(context, Aggregation.DEFAULT_CONTEXT)) { + if (ObjectUtils.nullSafeEquals(context, Aggregation.DEFAULT_CONTEXT)) { return aggregation.toPipeline(context); } @@ -115,53 +132,6 @@ Document createCommand(String collection, Aggregation aggregation, AggregationOp 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 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 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 mapAggregationPipeline(List pipeline) { return pipeline.stream().map(val -> queryMapper.getMappedObject(val, Optional.empty())) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/BulkOperations.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/BulkOperations.java index bbc85032ec..2808b769d6 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/BulkOperations.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/BulkOperations.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-2022 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. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ChangeStreamEvent.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ChangeStreamEvent.java index 8e1d3523c5..ad584d7a46 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ChangeStreamEvent.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ChangeStreamEvent.java @@ -1,5 +1,5 @@ /* - * Copyright 2018-2020 the original author or authors. + * Copyright 2018-2022 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. @@ -15,8 +15,6 @@ */ package org.springframework.data.mongodb.core; -import lombok.EqualsAndHashCode; - import java.time.Instant; import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; @@ -27,6 +25,7 @@ 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; @@ -39,7 +38,6 @@ * @author Mark Paluch * @since 2.1 */ -@EqualsAndHashCode public class ChangeStreamEvent { @SuppressWarnings("rawtypes") // @@ -187,8 +185,8 @@ private Object doGetConverted(Document fullDocument) { 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)); } /* @@ -199,4 +197,27 @@ private Object doGetConverted(Document fullDocument) { 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; + } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ChangeStreamOptions.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ChangeStreamOptions.java index 535e40a216..583d958686 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ChangeStreamOptions.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ChangeStreamOptions.java @@ -1,5 +1,5 @@ /* - * Copyright 2018-2020 the original author or authors. + * Copyright 2018-2022 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. @@ -15,8 +15,6 @@ */ package org.springframework.data.mongodb.core; -import lombok.EqualsAndHashCode; - import java.time.Instant; import java.util.Arrays; import java.util.Optional; @@ -25,7 +23,6 @@ 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; @@ -45,7 +42,6 @@ * @author Mark Paluch * @since 2.1 */ -@EqualsAndHashCode public class ChangeStreamOptions { private @Nullable Object filter; @@ -156,6 +152,44 @@ private static Object doGetTimestamp(Object timestamp, Class targetType) + 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 @@ -208,13 +242,13 @@ public ChangeStreamOptionsBuilder collation(Collation collation) { /** * Set the filter to apply. - *

+ *
* Fields on aggregation expression root level are prefixed to map to fields contained in * {@link ChangeStreamDocument#getFullDocument() fullDocument}. However {@literal operationType}, {@literal ns}, * {@literal documentKey} and {@literal fullDocument} are reserved words that will be omitted, and therefore taken * as given, during the mapping procedure. You may want to have a look at the * structure of Change Events. - *

+ *
* Use {@link org.springframework.data.mongodb.core.aggregation.TypedAggregation} to ensure filter expressions are * mapped to domain type fields. * diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/CollectionCallback.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/CollectionCallback.java index d6b9bcc6e3..9b976b6467 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/CollectionCallback.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/CollectionCallback.java @@ -1,5 +1,5 @@ /* - * Copyright 2010-2020 the original author or authors. + * Copyright 2010-2022 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. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/CollectionOptions.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/CollectionOptions.java index 77833c6ada..059808bd07 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/CollectionOptions.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/CollectionOptions.java @@ -1,5 +1,5 @@ /* - * Copyright 2010-2020 the original author or authors. + * Copyright 2010-2022 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. @@ -15,12 +15,13 @@ */ 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; @@ -44,6 +45,7 @@ public class CollectionOptions { private @Nullable Boolean capped; private @Nullable Collation collation; private ValidationOptions validationOptions; + private @Nullable TimeSeriesOptions timeSeriesOptions; /** * Constructs a new CollectionOptions instance. @@ -56,17 +58,19 @@ public class CollectionOptions { */ @Deprecated public CollectionOptions(@Nullable Long size, @Nullable Long maxDocuments, @Nullable Boolean capped) { - this(size, maxDocuments, capped, null, ValidationOptions.none()); + this(size, maxDocuments, capped, null, ValidationOptions.none(), null); } private CollectionOptions(@Nullable Long size, @Nullable Long maxDocuments, @Nullable Boolean capped, - @Nullable Collation collation, ValidationOptions validationOptions) { + @Nullable Collation collation, ValidationOptions validationOptions, + @Nullable TimeSeriesOptions timeSeriesOptions) { this.maxDocuments = maxDocuments; this.size = size; this.capped = capped; this.collation = collation; this.validationOptions = validationOptions; + this.timeSeriesOptions = timeSeriesOptions; } /** @@ -80,7 +84,7 @@ public static CollectionOptions just(Collation collation) { Assert.notNull(collation, "Collation must not be null!"); - return new CollectionOptions(null, null, null, collation, ValidationOptions.none()); + return new CollectionOptions(null, null, null, collation, ValidationOptions.none(), null); } /** @@ -90,7 +94,21 @@ public static CollectionOptions just(Collation collation) { * @since 2.0 */ public static CollectionOptions empty() { - return new CollectionOptions(null, null, null, null, ValidationOptions.none()); + return new CollectionOptions(null, null, null, null, ValidationOptions.none(), null); + } + + /** + * Quick way to set up {@link CollectionOptions} for a Time Series collection. For more advanced settings use + * {@link #timeSeries(TimeSeriesOptions)}. + * + * @param timeField The name of the property which contains the date in each time series document. Must not be + * {@literal null}. + * @return new instance of {@link CollectionOptions}. + * @see #timeSeries(TimeSeriesOptions) + * @since 3.3 + */ + public static CollectionOptions timeSeries(String timeField) { + return empty().timeSeries(TimeSeriesOptions.timeSeries(timeField)); } /** @@ -101,7 +119,7 @@ public static CollectionOptions empty() { * @since 2.0 */ public CollectionOptions capped() { - return new CollectionOptions(size, maxDocuments, true, collation, validationOptions); + return new CollectionOptions(size, maxDocuments, true, collation, validationOptions, null); } /** @@ -112,7 +130,7 @@ public CollectionOptions capped() { * @since 2.0 */ public CollectionOptions maxDocuments(long maxDocuments) { - return new CollectionOptions(size, maxDocuments, capped, collation, validationOptions); + return new CollectionOptions(size, maxDocuments, capped, collation, validationOptions, timeSeriesOptions); } /** @@ -123,7 +141,7 @@ public CollectionOptions maxDocuments(long maxDocuments) { * @since 2.0 */ public CollectionOptions size(long size) { - return new CollectionOptions(size, maxDocuments, capped, collation, validationOptions); + return new CollectionOptions(size, maxDocuments, capped, collation, validationOptions, timeSeriesOptions); } /** @@ -134,7 +152,7 @@ public CollectionOptions size(long size) { * @since 2.0 */ public CollectionOptions collation(@Nullable Collation collation) { - return new CollectionOptions(size, maxDocuments, capped, collation, validationOptions); + return new CollectionOptions(size, maxDocuments, capped, collation, validationOptions, timeSeriesOptions); } /** @@ -254,7 +272,20 @@ public CollectionOptions schemaValidationAction(ValidationAction validationActio public CollectionOptions validation(ValidationOptions validationOptions) { Assert.notNull(validationOptions, "ValidationOptions must not be null!"); - return new CollectionOptions(size, maxDocuments, capped, collation, validationOptions); + return new CollectionOptions(size, maxDocuments, capped, collation, validationOptions, timeSeriesOptions); + } + + /** + * Create new {@link CollectionOptions} with the given {@link TimeSeriesOptions}. + * + * @param timeSeriesOptions must not be {@literal null}. + * @return new instance of {@link CollectionOptions}. + * @since 3.3 + */ + public CollectionOptions timeSeries(TimeSeriesOptions timeSeriesOptions) { + + Assert.notNull(timeSeriesOptions, "TimeSeriesOptions must not be null!"); + return new CollectionOptions(size, maxDocuments, capped, collation, validationOptions, timeSeriesOptions); } /** @@ -305,6 +336,16 @@ public Optional getValidationOptions() { 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 getTimeSeriesOptions() { + return Optional.ofNullable(timeSeriesOptions); + } + /** * Encapsulation of ValidationOptions options. * @@ -312,7 +353,6 @@ public Optional getValidationOptions() { * @author Andreas Zink * @since 2.1 */ - @RequiredArgsConstructor public static class ValidationOptions { private static final ValidationOptions NONE = new ValidationOptions(null, null, null); @@ -321,6 +361,13 @@ public static class ValidationOptions { 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}. * @@ -381,7 +428,7 @@ public Optional getValidationLevel() { /** * Get the {@code validationAction} to perform. * - * @return @return {@link Optional#empty()} if not set. + * @return {@link Optional#empty()} if not set. */ public Optional getValidationAction() { return Optional.ofNullable(validationAction); @@ -394,4 +441,89 @@ boolean isEmpty() { return !Optionals.isAnyPresent(getValidator(), getValidationAction(), getValidationLevel()); } } + + /** + * Options applicable to Time Series collections. + * + * @author Christoph Strobl + * @since 3.3 + * @see https://docs.mongodb.com/manual/core/timeseries-collections + */ + 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.
+ * {@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}.
+ * {@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; + } + } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/CountQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/CountQuery.java index 66b6a37b85..debfb78e59 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/CountQuery.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/CountQuery.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 the original author or authors. + * Copyright 2019-2022 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. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/CursorPreparer.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/CursorPreparer.java index bb98bb96bd..6b4d8909b2 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/CursorPreparer.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/CursorPreparer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DbCallback.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DbCallback.java index d828e962e5..1fdd99a969 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DbCallback.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DbCallback.java @@ -1,5 +1,5 @@ /* - * Copyright 2010-2020 the original author or authors. + * Copyright 2010-2022 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. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultBulkOperations.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultBulkOperations.java index bbd99d3d8d..243c376fc0 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultBulkOperations.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultBulkOperations.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-2022 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. @@ -15,9 +15,6 @@ */ package org.springframework.data.mongodb.core; -import lombok.NonNull; -import lombok.Value; - import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -27,8 +24,9 @@ import org.bson.Document; import org.bson.conversions.Bson; import org.springframework.context.ApplicationEventPublisher; -import org.springframework.dao.support.PersistenceExceptionTranslator; +import org.springframework.dao.DataIntegrityViolationException; 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; @@ -47,7 +45,9 @@ 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,6 +64,7 @@ * @author Jens Schauder * @author Michail Nikolaev * @author Roman Puchkovskiy + * @author Jacob Botuck * @since 1.9 */ class DefaultBulkOperations implements BulkOperations { @@ -73,7 +74,6 @@ class DefaultBulkOperations implements BulkOperations { private final BulkOperationContext bulkOperationContext; private final List models = new ArrayList<>(); - private PersistenceExceptionTranslator exceptionTranslator; private @Nullable WriteConcern defaultWriteConcern; private BulkWriteOptions bulkOptions; @@ -97,19 +97,9 @@ 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}. * @@ -316,11 +306,26 @@ private BulkWriteResult bulkWriteTo(MongoCollection collection) { collection = collection.withWriteConcern(defaultWriteConcern); } - return collection.bulkWrite( // - models.stream() // - .map(this::extractAndMapWriteModel) // - .collect(Collectors.toList()), // - bulkOptions); + 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; + } } private WriteModel extractAndMapWriteModel(SourceAwareWriteModelHolder it) { @@ -547,15 +552,93 @@ private static UpdateOptions computeUpdateOptions(Query filterQuery, UpdateDefin * @author Christoph Strobl * @since 2.0 */ - @Value - static class BulkOperationContext { + static final class BulkOperationContext { + + private final BulkMode bulkMode; + private final Optional> entity; + private final QueryMapper queryMapper; + private final UpdateMapper updateMapper; + private final ApplicationEventPublisher eventPublisher; + private final EntityCallbacks entityCallbacks; + + BulkOperationContext(BulkOperations.BulkMode bulkMode, Optional> 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> 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; - @NonNull BulkMode bulkMode; - @NonNull Optional> entity; - @NonNull QueryMapper queryMapper; - @NonNull UpdateMapper updateMapper; - ApplicationEventPublisher eventPublisher; - EntityCallbacks entityCallbacks; + 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() + ")"; + } } /** @@ -564,10 +647,50 @@ static class BulkOperationContext { * @since 2.2 * @author Christoph Strobl */ - @Value - private static class SourceAwareWriteModelHolder { + private static final class SourceAwareWriteModelHolder { + + private final Object source; + private final WriteModel model; + + SourceAwareWriteModelHolder(Object source, WriteModel model) { + + this.source = source; + this.model = model; + } - Object source; - WriteModel model; + public Object getSource() { + return this.source; + } + + public WriteModel 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() + ")"; + } } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultIndexOperations.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultIndexOperations.java index 62ad1f46ee..958d838cd1 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultIndexOperations.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultIndexOperations.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2020 the original author or authors. + * Copyright 2011-2022 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. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultIndexOperationsProvider.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultIndexOperationsProvider.java index 3888b0549b..c2c799d4a2 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultIndexOperationsProvider.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultIndexOperationsProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2020 the original author or authors. + * Copyright 2016-2022 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. @@ -47,7 +47,7 @@ class DefaultIndexOperationsProvider implements IndexOperationsProvider { * @see org.springframework.data.mongodb.core.index.IndexOperationsProvider#reactiveIndexOps(java.lang.String) */ @Override - public IndexOperations indexOps(String collectionName) { - return new DefaultIndexOperations(mongoDbFactory, collectionName, mapper); + public IndexOperations indexOps(String collectionName, Class type) { + return new DefaultIndexOperations(mongoDbFactory, collectionName, mapper, type); } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultReactiveIndexOperations.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultReactiveIndexOperations.java index 2ec7c14c95..5ad8555157 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultReactiveIndexOperations.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultReactiveIndexOperations.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2020 the original author or authors. + * Copyright 2016-2022 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. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultScriptOperations.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultScriptOperations.java index 53e217f3eb..0f86975be5 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultScriptOperations.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultScriptOperations.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2020 the original author or authors. + * Copyright 2014-2022 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. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultWriteConcernResolver.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultWriteConcernResolver.java index 459792e11a..3991f55468 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultWriteConcernResolver.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultWriteConcernResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-2022 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. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DocumentCallbackHandler.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DocumentCallbackHandler.java index aa97063703..4a79f3075a 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DocumentCallbackHandler.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DocumentCallbackHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2010-2020 the original author or authors. + * Copyright 2010-2022 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. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/EncryptionAlgorithms.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/EncryptionAlgorithms.java new file mode 100644 index 0000000000..2597101f81 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/EncryptionAlgorithms.java @@ -0,0 +1,29 @@ +/* + * Copyright 2021-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.mongodb.core; + +/** + * Encryption algorithms supported by MongoDB Client Side Field Level Encryption. + * + * @author Christoph Strobl + * @since 3.3 + */ +public final class EncryptionAlgorithms { + + public static final String AEAD_AES_256_CBC_HMAC_SHA_512_Deterministic = "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic"; + public static final String AEAD_AES_256_CBC_HMAC_SHA_512_Random = "AEAD_AES_256_CBC_HMAC_SHA_512-Random"; + +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/EntityOperations.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/EntityOperations.java index a7cbf879ed..02186505af 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/EntityOperations.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/EntityOperations.java @@ -1,5 +1,5 @@ /* - * Copyright 2018-2020 the original author or authors. + * Copyright 2018-2022 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. @@ -15,11 +15,8 @@ */ 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; @@ -32,18 +29,23 @@ 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. @@ -55,12 +57,15 @@ * @see MongoTemplate * @see ReactiveMongoTemplate */ -@RequiredArgsConstructor class EntityOperations { private static final String ID_FIELD = "_id"; - private final @NonNull MappingContext, MongoPersistentProperty> context; + private final MappingContext, MongoPersistentProperty> context; + + EntityOperations(MappingContext, MongoPersistentProperty> context) { + this.context = context; + } /** * Creates a new {@link Entity} for the given bean. @@ -69,7 +74,7 @@ class EntityOperations { * @return new instance of {@link Entity}. */ @SuppressWarnings({ "unchecked", "rawtypes" }) - public Entity forEntity(T entity) { + Entity forEntity(T entity) { Assert.notNull(entity, "Bean must not be null!"); @@ -92,7 +97,7 @@ public Entity forEntity(T entity) { * @return new instance of {@link AdaptibleEntity}. */ @SuppressWarnings({ "unchecked", "rawtypes" }) - public AdaptibleEntity forEntity(T entity, ConversionService conversionService) { + AdaptibleEntity forEntity(T entity, ConversionService conversionService) { Assert.notNull(entity, "Bean must not be null!"); Assert.notNull(conversionService, "ConversionService must not be null!"); @@ -108,6 +113,20 @@ public AdaptibleEntity forEntity(T entity, ConversionService conversionSe 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}. @@ -346,11 +365,14 @@ interface AdaptibleEntity extends Entity { Number getVersion(); } - @RequiredArgsConstructor private static class UnmappedEntity> implements AdaptibleEntity { private final T map; + protected UnmappedEntity(T map) { + this.map = map; + } + /* * (non-Javadoc) * @see org.springframework.data.mongodb.core.EntityOperations.PersistableSource#getIdPropertyName() @@ -460,7 +482,7 @@ public boolean isNew() { private static class SimpleMappedEntity> extends UnmappedEntity { - SimpleMappedEntity(T map) { + protected SimpleMappedEntity(T map) { super(map); } @@ -483,12 +505,19 @@ public MappedDocument toMappedDocument(MongoWriter writer) { } } - @RequiredArgsConstructor(access = AccessLevel.PROTECTED) private static class MappedEntity implements Entity { - private final @NonNull MongoPersistentEntity entity; - private final @NonNull IdentifierAccessor idAccessor; - private final @NonNull PersistentPropertyAccessor propertyAccessor; + private final MongoPersistentEntity entity; + private final IdentifierAccessor idAccessor; + private final PersistentPropertyAccessor propertyAccessor; + + protected MappedEntity(MongoPersistentEntity entity, IdentifierAccessor idAccessor, + PersistentPropertyAccessor propertyAccessor) { + + this.entity = entity; + this.idAccessor = idAccessor; + this.propertyAccessor = propertyAccessor; + } private static MappedEntity of(T bean, MappingContext, MongoPersistentProperty> context) { @@ -753,17 +782,36 @@ interface TypedOperations { * @return */ Optional 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 { INSTANCE; + UntypedOperations() {} + @SuppressWarnings({ "unchecked", "rawtypes" }) public static TypedOperations instance() { return (TypedOperations) INSTANCE; @@ -791,6 +839,16 @@ public Optional getCollation(Query query) { return query.getCollation(); } + + @Override + public CollectionOptions getCollectionOptions() { + return CollectionOptions.empty(); + } + + @Override + public TimeSeriesOptions mapTimeSeriesOptions(TimeSeriesOptions options) { + return options; + } } /** @@ -798,10 +856,13 @@ public Optional getCollation(Query query) { * * @param */ - @RequiredArgsConstructor static class TypedEntityOperations implements TypedOperations { - private final @NonNull MongoPersistentEntity entity; + private final MongoPersistentEntity entity; + + protected TypedEntityOperations(MongoPersistentEntity entity) { + this.entity = entity; + } /* * (non-Javadoc) @@ -825,6 +886,58 @@ public Optional getCollation(Query query) { 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; + } } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableAggregationOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableAggregationOperation.java index 4e9bec35e3..228e06e1ec 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableAggregationOperation.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableAggregationOperation.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2020 the original author or authors. + * Copyright 2017-2022 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. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableAggregationOperationSupport.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableAggregationOperationSupport.java index 76ff13a02a..858beeba85 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableAggregationOperationSupport.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableAggregationOperationSupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2020 the original author or authors. + * Copyright 2017-2022 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. @@ -15,16 +15,10 @@ */ 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; @@ -35,10 +29,13 @@ * @author Mark Paluch * @since 2.0 */ -@RequiredArgsConstructor class ExecutableAggregationOperationSupport implements ExecutableAggregationOperation { - private final @NonNull MongoTemplate template; + private final MongoTemplate template; + + ExecutableAggregationOperationSupport(MongoTemplate template) { + this.template = template; + } /* * (non-Javadoc) @@ -56,15 +53,21 @@ public ExecutableAggregation aggregateAndReturn(Class domainType) { * @author Christoph Strobl * @since 2.0 */ - @RequiredArgsConstructor - @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) static class ExecutableAggregationSupport implements AggregationWithAggregation, ExecutableAggregation, TerminatingAggregation { - @NonNull MongoTemplate template; - @NonNull Class domainType; - @Nullable Aggregation aggregation; - @Nullable String collection; + private final MongoTemplate template; + private final Class domainType; + private final Aggregation aggregation; + private final String collection; + + public ExecutableAggregationSupport(MongoTemplate template, Class domainType, Aggregation aggregation, + String collection) { + this.template = template; + this.domainType = domainType; + this.aggregation = aggregation; + this.collection = collection; + } /* * (non-Javadoc) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableFindOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableFindOperation.java index 3bd63bb4f9..50c1192018 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableFindOperation.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableFindOperation.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2020 the original author or authors. + * Copyright 2017-2022 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. @@ -125,6 +125,11 @@ default Optional first() { /** * Get the number of matching elements. + *
+ * 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. */ diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableFindOperationSupport.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableFindOperationSupport.java index eac01e5298..562681dc54 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableFindOperationSupport.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableFindOperationSupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2020 the original author or authors. + * Copyright 2017-2022 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. @@ -15,12 +15,6 @@ */ 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; @@ -37,6 +31,7 @@ import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; +import com.mongodb.ReadPreference; import com.mongodb.client.FindIterable; /** @@ -46,12 +41,15 @@ * @author Mark Paluch * @since 2.0 */ -@RequiredArgsConstructor class ExecutableFindOperationSupport implements ExecutableFindOperation { private static final Query ALL_QUERY = new Query(); - private final @NonNull MongoTemplate template; + private final MongoTemplate template; + + ExecutableFindOperationSupport(MongoTemplate template) { + this.template = template; + } /* * (non-Javadoc) @@ -70,16 +68,23 @@ public ExecutableFind query(Class domainType) { * @author Christoph Strobl * @since 2.0 */ - @RequiredArgsConstructor - @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) static class ExecutableFindSupport implements ExecutableFind, FindWithCollection, FindWithProjection, FindWithQuery { - @NonNull MongoTemplate template; - @NonNull Class domainType; - Class returnType; - @Nullable String collection; - Query query; + private final MongoTemplate template; + private final Class domainType; + private final Class returnType; + @Nullable private final String collection; + private final Query query; + + ExecutableFindSupport(MongoTemplate template, Class domainType, Class returnType, + String collection, Query query) { + this.template = template; + this.domainType = domainType; + this.returnType = returnType; + this.collection = collection; + this.query = query; + } /* * (non-Javadoc) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableInsertOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableInsertOperation.java index 2827f770cb..65d16fa0e9 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableInsertOperation.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableInsertOperation.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2020 the original author or authors. + * Copyright 2017-2022 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. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableInsertOperationSupport.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableInsertOperationSupport.java index a22625f7ac..6913ec433d 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableInsertOperationSupport.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableInsertOperationSupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2020 the original author or authors. + * Copyright 2017-2022 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. @@ -15,11 +15,6 @@ */ 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; @@ -37,10 +32,13 @@ * @author Mark Paluch * @since 2.0 */ -@RequiredArgsConstructor class ExecutableInsertOperationSupport implements ExecutableInsertOperation { - private final @NonNull MongoTemplate template; + private final MongoTemplate template; + + ExecutableInsertOperationSupport(MongoTemplate template) { + this.template = template; + } /* * (non-Javadoc) @@ -58,14 +56,20 @@ public ExecutableInsert insert(Class domainType) { * @author Christoph Strobl * @since 2.0 */ - @RequiredArgsConstructor - @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) static class ExecutableInsertSupport implements ExecutableInsert { - @NonNull MongoTemplate template; - @NonNull Class domainType; - @Nullable String collection; - @Nullable BulkMode bulkMode; + private final MongoTemplate template; + private final Class domainType; + @Nullable private final String collection; + @Nullable private final BulkMode bulkMode; + + ExecutableInsertSupport(MongoTemplate template, Class domainType, String collection, BulkMode bulkMode) { + + this.template = template; + this.domainType = domainType; + this.collection = collection; + this.bulkMode = bulkMode; + } /* * (non-Javadoc) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableMapReduceOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableMapReduceOperation.java index be43d25a85..5b19a8bf30 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableMapReduceOperation.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableMapReduceOperation.java @@ -1,5 +1,5 @@ /* - * Copyright 2018-2020 the original author or authors. + * Copyright 2018-2022 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. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableMapReduceOperationSupport.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableMapReduceOperationSupport.java index 57e9d38cc4..995d22c2bd 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableMapReduceOperationSupport.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableMapReduceOperationSupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2018-2020 the original author or authors. + * Copyright 2018-2022 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. @@ -15,9 +15,6 @@ */ package org.springframework.data.mongodb.core; -import lombok.NonNull; -import lombok.RequiredArgsConstructor; - import java.util.List; import org.springframework.data.mongodb.core.mapreduce.MapReduceOptions; @@ -32,12 +29,17 @@ * @author Christoph Strobl * @since 2.1 */ -@RequiredArgsConstructor class ExecutableMapReduceOperationSupport implements ExecutableMapReduceOperation { private static final Query ALL_QUERY = new Query(); - private final @NonNull MongoTemplate template; + private final MongoTemplate template; + + ExecutableMapReduceOperationSupport(MongoTemplate template) { + + Assert.notNull(template, "Template must not be null!"); + this.template = template; + } /* * (non-Javascript) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableRemoveOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableRemoveOperation.java index 5c95c469e2..a1ae5e2cf7 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableRemoveOperation.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableRemoveOperation.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2020 the original author or authors. + * Copyright 2017-2022 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. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableRemoveOperationSupport.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableRemoveOperationSupport.java index edba892a03..2276d7461a 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableRemoveOperationSupport.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableRemoveOperationSupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2020 the original author or authors. + * Copyright 2017-2022 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. @@ -15,11 +15,6 @@ */ 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; @@ -36,12 +31,15 @@ * @author Mark Paluch * @since 2.0 */ -@RequiredArgsConstructor class ExecutableRemoveOperationSupport implements ExecutableRemoveOperation { private static final Query ALL_QUERY = new Query(); - private final @NonNull MongoTemplate tempate; + private final MongoTemplate tempate; + + public ExecutableRemoveOperationSupport(MongoTemplate tempate) { + this.tempate = tempate; + } /* * (non-Javadoc) @@ -59,14 +57,19 @@ public ExecutableRemove remove(Class domainType) { * @author Christoph Strobl * @since 2.0 */ - @RequiredArgsConstructor - @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) static class ExecutableRemoveSupport implements ExecutableRemove, RemoveWithCollection { - @NonNull MongoTemplate template; - @NonNull Class domainType; - Query query; - @Nullable String collection; + private final MongoTemplate template; + private final Class domainType; + private final Query query; + @Nullable private final String collection; + + public ExecutableRemoveSupport(MongoTemplate template, Class domainType, Query query, String collection) { + this.template = template; + this.domainType = domainType; + this.query = query; + this.collection = collection; + } /* * (non-Javadoc) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableUpdateOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableUpdateOperation.java index 7b45b10680..0e91054dca 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableUpdateOperation.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableUpdateOperation.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2020 the original author or authors. + * Copyright 2017-2022 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. @@ -89,7 +89,7 @@ default Optional findAndModify() { /** * Trigger - * findOneAndReplace + * findOneAndReplace * execution by calling one of the terminating methods. * * @author Mark Paluch diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableUpdateOperationSupport.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableUpdateOperationSupport.java index 0d84c56f9a..21edcc9c5b 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableUpdateOperationSupport.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableUpdateOperationSupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2020 the original author or authors. + * Copyright 2017-2022 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. @@ -15,11 +15,6 @@ */ 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; @@ -35,12 +30,15 @@ * @author Mark Paluch * @since 2.0 */ -@RequiredArgsConstructor class ExecutableUpdateOperationSupport implements ExecutableUpdateOperation { private static final Query ALL_QUERY = new Query(); - private final @NonNull MongoTemplate template; + private final MongoTemplate template; + + ExecutableUpdateOperationSupport(MongoTemplate template) { + this.template = template; + } /* * (non-Javadoc) @@ -58,21 +56,34 @@ public ExecutableUpdate update(Class domainType) { * @author Christoph Strobl * @since 2.0 */ - @RequiredArgsConstructor - @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) static class ExecutableUpdateSupport implements ExecutableUpdate, UpdateWithCollection, UpdateWithQuery, TerminatingUpdate, FindAndReplaceWithOptions, TerminatingFindAndReplace, FindAndReplaceWithProjection { - @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 targetType; + 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 targetType; + + ExecutableUpdateSupport(MongoTemplate template, Class domainType, Query query, UpdateDefinition update, + String collection, FindAndModifyOptions findAndModifyOptions, FindAndReplaceOptions findAndReplaceOptions, + Object replacement, Class 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; + } /* * (non-Javadoc) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/FindAndModifyOptions.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/FindAndModifyOptions.java index ac8f45d05f..b2085bcbcf 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/FindAndModifyOptions.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/FindAndModifyOptions.java @@ -1,5 +1,5 @@ /* - * Copyright 2010-2020 the original author or authors. + * Copyright 2010-2022 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. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/FindAndReplaceOptions.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/FindAndReplaceOptions.java index 2de66015c2..b9be9c1613 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/FindAndReplaceOptions.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/FindAndReplaceOptions.java @@ -1,5 +1,5 @@ /* - * Copyright 2018-2020 the original author or authors. + * Copyright 2018-2022 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. @@ -17,7 +17,7 @@ /** * Options for - * findOneAndReplace. + * findOneAndReplace. *
* Defaults to *
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/FindPublisherPreparer.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/FindPublisherPreparer.java index 3f603f9f48..15c0de4709 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/FindPublisherPreparer.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/FindPublisherPreparer.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2020 the original author or authors. + * Copyright 2016-2022 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. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/FluentMongoOperations.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/FluentMongoOperations.java index 7fbd49359e..c9ea1ba69b 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/FluentMongoOperations.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/FluentMongoOperations.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2020 the original author or authors. + * Copyright 2017-2022 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. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/GeoCommandStatistics.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/GeoCommandStatistics.java index 0d3b648289..0271e9aa0d 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/GeoCommandStatistics.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/GeoCommandStatistics.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2020 the original author or authors. + * Copyright 2016-2022 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. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/IndexConverters.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/IndexConverters.java index 7e574c6944..fb46ce0f7e 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/IndexConverters.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/IndexConverters.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2020 the original author or authors. + * Copyright 2016-2022 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. @@ -115,6 +115,10 @@ private static Converter getIndexDefinitionIndexO ops = ops.collation(fromDocument(indexOptions.get("collation", Document.class))); } + if (indexOptions.containsKey("wildcardProjection")) { + ops.wildcardProjection(indexOptions.get("wildcardProjection", Document.class)); + } + return ops; }; } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MappedDocument.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MappedDocument.java index 7df9503caa..95b1e20956 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MappedDocument.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MappedDocument.java @@ -1,5 +1,5 @@ /* - * Copyright 2018-2020 the original author or authors. + * Copyright 2018-2022 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. @@ -15,9 +15,6 @@ */ package org.springframework.data.mongodb.core; -import lombok.Getter; -import lombok.RequiredArgsConstructor; - import java.util.Collection; import java.util.List; @@ -27,8 +24,6 @@ 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. @@ -36,13 +31,20 @@ * @author Oliver Gierke * @since 2.1 */ -@RequiredArgsConstructor(staticName = "of") public class MappedDocument { private static final String ID_FIELD = "_id"; private static final Document ID_ONLY_PROJECTION = new Document(ID_FIELD, 1); - private final @Getter Document document; + private final Document document; + + private MappedDocument(Document document) { + this.document = document; + } + + public static MappedDocument of(Document document) { + return new MappedDocument(document); + } public static Document getIdOnlyProjection() { return ID_ONLY_PROJECTION; @@ -91,6 +93,10 @@ public UpdateDefinition updateWithoutId() { 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. @@ -150,5 +156,14 @@ public Boolean isIsolated() { public List getArrayFilters() { return delegate.getArrayFilters(); } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.query.UpdateDefinition#hasArrayFilters() + */ + @Override + public boolean hasArrayFilters() { + return delegate.hasArrayFilters(); + } } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MappingMongoJsonSchemaCreator.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MappingMongoJsonSchemaCreator.java index fdc7247ee7..547f0f6b0f 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MappingMongoJsonSchemaCreator.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MappingMongoJsonSchemaCreator.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 the original author or authors. + * Copyright 2019-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,13 +20,20 @@ import java.util.Collections; import java.util.EnumSet; import java.util.List; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +import org.bson.Document; import org.springframework.data.mapping.PersistentProperty; import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.mongodb.core.convert.MongoConverter; +import org.springframework.data.mongodb.core.mapping.Encrypted; import org.springframework.data.mongodb.core.mapping.Field; import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity; import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty; +import org.springframework.data.mongodb.core.schema.IdentifiableJsonSchemaProperty.ArrayJsonSchemaProperty; +import org.springframework.data.mongodb.core.schema.IdentifiableJsonSchemaProperty.EncryptedJsonSchemaProperty; import org.springframework.data.mongodb.core.schema.IdentifiableJsonSchemaProperty.ObjectJsonSchemaProperty; import org.springframework.data.mongodb.core.schema.JsonSchemaObject; import org.springframework.data.mongodb.core.schema.JsonSchemaObject.Type; @@ -34,10 +41,12 @@ import org.springframework.data.mongodb.core.schema.MongoJsonSchema; import org.springframework.data.mongodb.core.schema.MongoJsonSchema.MongoJsonSchemaBuilder; import org.springframework.data.mongodb.core.schema.TypedJsonSchemaObject; +import org.springframework.data.util.ClassTypeInformation; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.CollectionUtils; import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; /** * {@link MongoJsonSchemaCreator} implementation using both {@link MongoConverter} and {@link MappingContext} to obtain @@ -52,6 +61,7 @@ class MappingMongoJsonSchemaCreator implements MongoJsonSchemaCreator { private final MongoConverter converter; private final MappingContext, MongoPersistentProperty> mappingContext; + private final Predicate filter; /** * Create a new instance of {@link MappingMongoJsonSchemaCreator}. @@ -61,10 +71,24 @@ class MappingMongoJsonSchemaCreator implements MongoJsonSchemaCreator { @SuppressWarnings("unchecked") MappingMongoJsonSchemaCreator(MongoConverter converter) { + this(converter, (MappingContext, MongoPersistentProperty>) converter.getMappingContext(), + (property) -> true); + } + + @SuppressWarnings("unchecked") + MappingMongoJsonSchemaCreator(MongoConverter converter, + MappingContext, MongoPersistentProperty> mappingContext, + Predicate filter) { + Assert.notNull(converter, "Converter must not be null!"); this.converter = converter; - this.mappingContext = (MappingContext, MongoPersistentProperty>) converter - .getMappingContext(); + this.mappingContext = mappingContext; + this.filter = filter; + } + + @Override + public MongoJsonSchemaCreator filter(Predicate filter) { + return new MappingMongoJsonSchemaCreator(converter, mappingContext, filter); } /* @@ -77,11 +101,29 @@ public MongoJsonSchema createSchemaFor(Class type) { MongoPersistentEntity entity = mappingContext.getRequiredPersistentEntity(type); MongoJsonSchemaBuilder schemaBuilder = MongoJsonSchema.builder(); + { + Encrypted encrypted = entity.findAnnotation(Encrypted.class); + if (encrypted != null) { + + Document encryptionMetadata = new Document(); + + Collection encryptionKeyIds = entity.getEncryptionKeyIds(); + if (!CollectionUtils.isEmpty(encryptionKeyIds)) { + encryptionMetadata.append("keyId", encryptionKeyIds); + } + + if (StringUtils.hasText(encrypted.algorithm())) { + encryptionMetadata.append("algorithm", encrypted.algorithm()); + } + + schemaBuilder.encryptionMetadata(encryptionMetadata); + } + } + List schemaProperties = computePropertiesForEntity(Collections.emptyList(), entity); schemaBuilder.properties(schemaProperties.toArray(new JsonSchemaProperty[0])); return schemaBuilder.build(); - } private List computePropertiesForEntity(List path, @@ -93,6 +135,11 @@ private List computePropertiesForEntity(List currentPath = new ArrayList<>(path); + if (!filter.test(new PropertyContext( + currentPath.stream().map(PersistentProperty::getName).collect(Collectors.joining(".")), nested))) { + continue; + } + if (path.contains(nested)) { // cycle guard schemaProperties.add(createSchemaProperty(computePropertyFieldName(CollectionUtils.lastElement(currentPath)), Object.class, false)); @@ -114,21 +161,88 @@ private JsonSchemaProperty computeSchemaForProperty(List rawTargetType = computeTargetType(property); // target type before conversion Class targetType = converter.getTypeMapper().getWriteTargetTypeFor(rawTargetType); // conversion target type - if (property.isEntity() && ObjectUtils.nullSafeEquals(rawTargetType, targetType)) { + if (!isCollection(property) && property.isEntity() && ObjectUtils.nullSafeEquals(rawTargetType, targetType)) { return createObjectSchemaPropertyForEntity(path, property, required); } String fieldName = computePropertyFieldName(property); - if (property.isCollectionLike()) { - return createSchemaProperty(fieldName, targetType, required); + JsonSchemaProperty schemaProperty; + if (isCollection(property)) { + schemaProperty = createArraySchemaProperty(fieldName, property, required); } else if (property.isMap()) { - return createSchemaProperty(fieldName, Type.objectType(), required); + schemaProperty = createSchemaProperty(fieldName, Type.objectType(), required); } else if (ClassUtils.isAssignable(Enum.class, targetType)) { - return createEnumSchemaProperty(fieldName, targetType, required); + schemaProperty = createEnumSchemaProperty(fieldName, targetType, required); + } else { + schemaProperty = createSchemaProperty(fieldName, targetType, required); + } + + return applyEncryptionDataIfNecessary(property, schemaProperty); + } + + private JsonSchemaProperty createArraySchemaProperty(String fieldName, MongoPersistentProperty property, + boolean required) { + + ArrayJsonSchemaProperty schemaProperty = JsonSchemaProperty.array(fieldName); + + if (isSpecificType(property)) { + schemaProperty = potentiallyEnhanceArraySchemaProperty(property, schemaProperty); + } + + return createPotentiallyRequiredSchemaProperty(schemaProperty, required); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + private ArrayJsonSchemaProperty potentiallyEnhanceArraySchemaProperty(MongoPersistentProperty property, + ArrayJsonSchemaProperty schemaProperty) { + + MongoPersistentEntity persistentEntity = mappingContext + .getPersistentEntity(property.getTypeInformation().getRequiredComponentType()); + + if (persistentEntity != null) { + + List nestedProperties = computePropertiesForEntity(Collections.emptyList(), persistentEntity); + + if (nestedProperties.isEmpty()) { + return schemaProperty; + } + + return schemaProperty + .items(JsonSchemaObject.object().properties(nestedProperties.toArray(new JsonSchemaProperty[0]))); + } + + if (ClassUtils.isAssignable(Enum.class, property.getActualType())) { + + List possibleValues = getPossibleEnumValues((Class) property.getActualType()); + + return schemaProperty + .items(createSchemaObject(computeTargetType(property.getActualType(), possibleValues), possibleValues)); } - return createSchemaProperty(fieldName, targetType, required); + return schemaProperty.items(JsonSchemaObject.of(property.getActualType())); + } + + private boolean isSpecificType(MongoPersistentProperty property) { + return !ClassTypeInformation.OBJECT.equals(property.getTypeInformation().getActualType()); + } + + private JsonSchemaProperty applyEncryptionDataIfNecessary(MongoPersistentProperty property, + JsonSchemaProperty schemaProperty) { + + Encrypted encrypted = property.findAnnotation(Encrypted.class); + if (encrypted == null) { + return schemaProperty; + } + + EncryptedJsonSchemaProperty enc = new EncryptedJsonSchemaProperty(schemaProperty); + if (StringUtils.hasText(encrypted.algorithm())) { + enc = enc.algorithm(encrypted.algorithm()); + } + if (!ObjectUtils.isEmpty(encrypted.keyId())) { + enc = enc.keys(property.getEncryptionKeyIds()); + } + return enc; } private JsonSchemaProperty createObjectSchemaPropertyForEntity(List path, @@ -142,15 +256,12 @@ private JsonSchemaProperty createObjectSchemaPropertyForEntity(List targetType, boolean required) { - List possibleValues = new ArrayList<>(); + List possibleValues = getPossibleEnumValues((Class) targetType); - for (Object enumValue : EnumSet.allOf((Class) targetType)) { - possibleValues.add(converter.convertToMongoType(enumValue)); - } - - targetType = possibleValues.isEmpty() ? targetType : possibleValues.iterator().next().getClass(); + targetType = computeTargetType(targetType, possibleValues); return createSchemaProperty(fieldName, targetType, required, possibleValues); } @@ -161,14 +272,20 @@ JsonSchemaProperty createSchemaProperty(String fieldName, Object type, boolean r JsonSchemaProperty createSchemaProperty(String fieldName, Object type, boolean required, Collection possibleValues) { + TypedJsonSchemaObject schemaObject = createSchemaObject(type, possibleValues); + + return createPotentiallyRequiredSchemaProperty(JsonSchemaProperty.named(fieldName).with(schemaObject), required); + } + + private TypedJsonSchemaObject createSchemaObject(Object type, Collection possibleValues) { + TypedJsonSchemaObject schemaObject = type instanceof Type ? JsonSchemaObject.of(Type.class.cast(type)) : JsonSchemaObject.of(Class.class.cast(type)); if (!CollectionUtils.isEmpty(possibleValues)) { schemaObject = schemaObject.possibleValues(possibleValues); } - - return createPotentiallyRequiredSchemaProperty(JsonSchemaProperty.named(fieldName).with(schemaObject), required); + return schemaObject; } private String computePropertyFieldName(PersistentProperty property) { @@ -199,12 +316,53 @@ private Class computeTargetType(PersistentProperty property) { return mongoProperty.getFieldType() != mongoProperty.getActualType() ? Object.class : mongoProperty.getFieldType(); } + private static Class computeTargetType(Class fallback, List possibleValues) { + return possibleValues.isEmpty() ? fallback : possibleValues.iterator().next().getClass(); + } + + private > List getPossibleEnumValues(Class targetType) { + + EnumSet enumSet = EnumSet.allOf(targetType); + List possibleValues = new ArrayList<>(enumSet.size()); + + for (Object enumValue : enumSet) { + possibleValues.add(converter.convertToMongoType(enumValue)); + } + + return possibleValues; + } + + private static boolean isCollection(MongoPersistentProperty property) { + return property.isCollectionLike() && !property.getType().equals(byte[].class); + } + static JsonSchemaProperty createPotentiallyRequiredSchemaProperty(JsonSchemaProperty property, boolean required) { + return required ? JsonSchemaProperty.required(property) : property; + } + + class PropertyContext implements JsonSchemaPropertyContext { - if (!required) { + private final String path; + private final MongoPersistentProperty property; + + public PropertyContext(String path, MongoPersistentProperty property) { + this.path = path; + this.property = property; + } + + @Override + public String getPath() { + return path; + } + + @Override + public MongoPersistentProperty getProperty() { return property; } - return JsonSchemaProperty.required(property); + @Override + public MongoPersistentEntity resolveEntity(MongoPersistentProperty property) { + return (MongoPersistentEntity) mappingContext.getPersistentEntity(property); + } } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoAction.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoAction.java index c47cc04941..60c884d670 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoAction.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoAction.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2020 the original author or authors. + * Copyright 2011-2022 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. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoActionOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoActionOperation.java index 9662e31b2c..6355f8d791 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoActionOperation.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoActionOperation.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2020 the original author or authors. + * Copyright 2011-2022 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. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoAdmin.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoAdmin.java index e198368927..f215ece86c 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoAdmin.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoAdmin.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2020 the original author or authors. + * Copyright 2011-2022 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. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoAdminOperations.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoAdminOperations.java index f28113cc5a..6fbf0811b3 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoAdminOperations.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoAdminOperations.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2020 the original author or authors. + * Copyright 2011-2022 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. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoClientFactoryBean.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoClientFactoryBean.java index 7c281c7bf6..da8cafdd06 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoClientFactoryBean.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoClientFactoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-2022 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. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoClientSettingsFactoryBean.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoClientSettingsFactoryBean.java index 2c66fe3813..65b06bdb31 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoClientSettingsFactoryBean.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoClientSettingsFactoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 the original author or authors. + * Copyright 2019-2022 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. @@ -36,6 +36,7 @@ import com.mongodb.ReadConcern; import com.mongodb.ReadPreference; import com.mongodb.ServerAddress; +import com.mongodb.ServerApi; import com.mongodb.WriteConcern; import com.mongodb.connection.ClusterConnectionMode; import com.mongodb.connection.ClusterType; @@ -113,6 +114,7 @@ public class MongoClientSettingsFactoryBean extends AbstractFactoryBean getObjectType() { return MongoClientSettings.class; @@ -476,9 +487,11 @@ protected MongoClientSettings createInstance() { if (retryWrites != null) { builder = builder.retryWrites(retryWrites); } - if (uUidRepresentation != null) { - builder.uuidRepresentation(uUidRepresentation); + builder = builder.uuidRepresentation(uUidRepresentation); + } + if (serverApi != null) { + builder = builder.serverApi(serverApi); } return builder.build(); diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoDataIntegrityViolationException.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoDataIntegrityViolationException.java index f9cc9d5e8d..2ea07ff08e 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoDataIntegrityViolationException.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoDataIntegrityViolationException.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2020 the original author or authors. + * Copyright 2013-2022 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. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoDatabaseFactorySupport.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoDatabaseFactorySupport.java index 60f1ed56a9..4b873035e6 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoDatabaseFactorySupport.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoDatabaseFactorySupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2018-2019 the original author or authors. + * Copyright 2018-2022 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. @@ -15,8 +15,6 @@ */ 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; @@ -24,6 +22,7 @@ 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; @@ -34,7 +33,7 @@ /** * Common base class for usage with both {@link com.mongodb.client.MongoClients} defining common properties such as * database name and exception translator. - *

+ *
* Not intended to be used directly. * * @author Christoph Strobl @@ -171,11 +170,15 @@ protected String getDefaultDatabaseName() { * @author Christoph Strobl * @since 2.1 */ - @Value - static class ClientSessionBoundMongoDbFactory implements MongoDatabaseFactory { + static final class ClientSessionBoundMongoDbFactory implements MongoDatabaseFactory { + + private final ClientSession session; + private final MongoDatabaseFactory delegate; - ClientSession session; - MongoDatabaseFactory delegate; + public ClientSessionBoundMongoDbFactory(ClientSession session, MongoDatabaseFactory delegate) { + this.session = session; + this.delegate = delegate; + } /* * (non-Javadoc) @@ -254,7 +257,42 @@ private T createProxyInstance(com.mongodb.session.ClientSession session, T t factory.addAdvice(new SessionAwareMethodInterceptor<>(session, target, ClientSession.class, MongoDatabase.class, this::proxyDatabase, MongoCollection.class, this::proxyCollection)); - return targetType.cast(factory.getProxy()); + 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() + ")"; } } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoDbFactorySupport.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoDbFactorySupport.java index f85dd8e1fd..c6a8c6ab08 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoDbFactorySupport.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoDbFactorySupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2018-2020 the original author or authors. + * Copyright 2018-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,7 +20,7 @@ /** * Common base class for usage with both {@link com.mongodb.client.MongoClients} defining common properties such as * database name and exception translator. - *

+ *
* Not intended to be used directly. * * @author Christoph Strobl diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoEncryptionSettingsFactoryBean.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoEncryptionSettingsFactoryBean.java index 555913c548..c3cb5c0cc0 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoEncryptionSettingsFactoryBean.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoEncryptionSettingsFactoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 the original author or authors. + * Copyright 2019-2022 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. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoExceptionTranslator.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoExceptionTranslator.java index e34950375a..9ec2d844f8 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoExceptionTranslator.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoExceptionTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright 2010-2020 the original author or authors. + * Copyright 2010-2022 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. @@ -21,6 +21,7 @@ import java.util.Set; import org.bson.BsonInvalidOperationException; + import org.springframework.dao.DataAccessException; import org.springframework.dao.DataAccessResourceFailureException; import org.springframework.dao.DataIntegrityViolationException; @@ -39,6 +40,7 @@ import com.mongodb.MongoBulkWriteException; import com.mongodb.MongoException; import com.mongodb.MongoServerException; +import com.mongodb.MongoSocketException; import com.mongodb.bulk.BulkWriteError; /** @@ -49,6 +51,7 @@ * @author Oliver Gierke * @author Michal Vich * @author Christoph Strobl + * @author Brice Vandeputte */ public class MongoExceptionTranslator implements PersistenceExceptionTranslator { @@ -78,6 +81,10 @@ public DataAccessException translateExceptionIfPossible(RuntimeException ex) { throw new InvalidDataAccessApiUsageException(ex.getMessage(), ex); } + if (ex instanceof MongoSocketException) { + return new DataAccessResourceFailureException(ex.getMessage(), ex); + } + String exception = ClassUtils.getShortName(ClassUtils.getUserClass(ex.getClass())); if (DUPLICATE_KEY_EXCEPTIONS.contains(exception)) { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoJsonSchemaCreator.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoJsonSchemaCreator.java index 2757ba2c31..2cff9f6c70 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoJsonSchemaCreator.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoJsonSchemaCreator.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 the original author or authors. + * Copyright 2019-2022 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. @@ -15,7 +15,23 @@ */ package org.springframework.data.mongodb.core; +import java.util.HashSet; +import java.util.Set; +import java.util.function.Predicate; + +import org.springframework.data.mapping.PersistentProperty; +import org.springframework.data.mapping.context.MappingContext; +import org.springframework.data.mongodb.core.convert.MappingMongoConverter; import org.springframework.data.mongodb.core.convert.MongoConverter; +import org.springframework.data.mongodb.core.convert.MongoCustomConversions; +import org.springframework.data.mongodb.core.convert.NoOpDbRefResolver; +import org.springframework.data.mongodb.core.mapping.Encrypted; +import org.springframework.data.mongodb.core.mapping.MongoMappingContext; +import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity; +import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty; +import org.springframework.data.mongodb.core.mapping.MongoSimpleTypes; +import org.springframework.data.mongodb.core.mapping.Unwrapped.Nullable; +import org.springframework.data.mongodb.core.schema.JsonSchemaProperty; import org.springframework.data.mongodb.core.schema.MongoJsonSchema; import org.springframework.util.Assert; @@ -24,6 +40,7 @@ * following mapping rules. *

* Required Properties + *

*
    *
  • Properties of primitive type
  • *
@@ -45,7 +62,8 @@ * {@link org.springframework.data.annotation.Id _id} properties using types that can be converted into * {@link org.bson.types.ObjectId} like {@link String} will be mapped to {@code type : 'object'} unless there is more * specific information available via the {@link org.springframework.data.mongodb.core.mapping.MongoId} annotation. - *

+ + * {@link Encrypted} properties will contain {@literal encrypt} information. * * @author Christoph Strobl * @since 2.2 @@ -60,6 +78,88 @@ public interface MongoJsonSchemaCreator { */ MongoJsonSchema createSchemaFor(Class type); + /** + * Filter matching {@link JsonSchemaProperty properties}. + * + * @param filter the {@link Predicate} to evaluate for inclusion. Must not be {@literal null}. + * @return new instance of {@link MongoJsonSchemaCreator}. + * @since 3.3 + */ + MongoJsonSchemaCreator filter(Predicate filter); + + /** + * The context in which a specific {@link #getProperty()} is encountered during schema creation. + * + * @since 3.3 + */ + interface JsonSchemaPropertyContext { + + /** + * The path to a given field/property in dot notation. + * + * @return never {@literal null}. + */ + String getPath(); + + /** + * The current property. + * + * @return never {@literal null}. + */ + MongoPersistentProperty getProperty(); + + /** + * Obtain the {@link MongoPersistentEntity} for a given property. + * + * @param property must not be {@literal null}. + * @param + * @return {@literal null} if the property is not an entity. It is nevertheless recommend to check + * {@link PersistentProperty#isEntity()} first. + */ + @Nullable + MongoPersistentEntity resolveEntity(MongoPersistentProperty property); + + } + + /** + * A filter {@link Predicate} that matches {@link Encrypted encrypted properties} and those having nested ones. + * + * @return new instance of {@link Predicate}. + * @since 3.3 + */ + static Predicate encryptedOnly() { + + return new Predicate() { + + // cycle guard + private final Set seen = new HashSet<>(); + + @Override + public boolean test(JsonSchemaPropertyContext context) { + return extracted(context.getProperty(), context); + } + + private boolean extracted(MongoPersistentProperty property, JsonSchemaPropertyContext context) { + if (property.isAnnotationPresent(Encrypted.class)) { + return true; + } + + if (!property.isEntity() || seen.contains(property)) { + return false; + } + + seen.add(property); + + for (MongoPersistentProperty nested : context.resolveEntity(property)) { + if (extracted(nested, context)) { + return true; + } + } + return false; + } + }; + } + /** * Creates a new {@link MongoJsonSchemaCreator} that is aware of conversions applied by the given * {@link MongoConverter}. @@ -72,4 +172,41 @@ static MongoJsonSchemaCreator create(MongoConverter mongoConverter) { Assert.notNull(mongoConverter, "MongoConverter must not be null!"); return new MappingMongoJsonSchemaCreator(mongoConverter); } + + /** + * Creates a new {@link MongoJsonSchemaCreator} that is aware of type mappings and potential + * {@link org.springframework.data.spel.spi.EvaluationContextExtension extensions}. + * + * @param mappingContext must not be {@literal null}. + * @return new instance of {@link MongoJsonSchemaCreator}. + * @since 3.3 + */ + static MongoJsonSchemaCreator create(MappingContext mappingContext) { + + MappingMongoConverter converter = new MappingMongoConverter(NoOpDbRefResolver.INSTANCE, mappingContext); + converter.setCustomConversions(MongoCustomConversions.create(config -> {})); + converter.afterPropertiesSet(); + + return create(converter); + } + + /** + * Creates a new {@link MongoJsonSchemaCreator} that does not consider potential extensions - suitable for testing. We + * recommend to use {@link #create(MappingContext)}. + * + * @return new instance of {@link MongoJsonSchemaCreator}. + * @since 3.3 + */ + static MongoJsonSchemaCreator create() { + + MongoMappingContext mappingContext = new MongoMappingContext(); + mappingContext.setSimpleTypeHolder(MongoSimpleTypes.HOLDER); + mappingContext.afterPropertiesSet(); + + MappingMongoConverter converter = new MappingMongoConverter(NoOpDbRefResolver.INSTANCE, mappingContext); + converter.setCustomConversions(MongoCustomConversions.create(config -> {})); + converter.afterPropertiesSet(); + + return create(converter); + } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoOperations.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoOperations.java index bb6c402fb7..5aa554771d 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoOperations.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoOperations.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2020 the original author or authors. + * Copyright 2011-2022 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. @@ -58,7 +58,7 @@ * Interface that specifies a basic set of MongoDB operations. Implemented by {@link MongoTemplate}. Not often used but * a useful option for extensibility and testability (as it can be easily mocked, stubbed, or be the target of a JDK * proxy). - *

+ *
* NOTE: Some operations cannot be executed within a MongoDB transaction. Please refer to the MongoDB * specific documentation to learn more about Multi * Document Transactions. @@ -125,7 +125,7 @@ public interface MongoOperations extends FluentMongoOperations { /** * Executes a {@link DbCallback} translating any exceptions as necessary. - *

+ *
* Allows for returning a result object, that is a domain object or a collection of domain objects. * * @param action callback object that specifies the MongoDB actions to perform on the passed in DB instance. Must not @@ -138,7 +138,7 @@ public interface MongoOperations extends FluentMongoOperations { /** * Executes the given {@link CollectionCallback} on the entity collection of the specified class. - *

+ *
* Allows for returning a result object, that is a domain object or a collection of domain objects. * * @param entityClass class that determines the collection to use. Must not be {@literal null}. @@ -151,7 +151,7 @@ public interface MongoOperations extends FluentMongoOperations { /** * Executes the given {@link CollectionCallback} on the collection of the given name. - *

+ *
* Allows for returning a result object, that is a domain object or a collection of domain objects. * * @param collectionName the name of the collection that specifies which {@link MongoCollection} instance will be @@ -176,7 +176,7 @@ public interface MongoOperations extends FluentMongoOperations { /** * Obtain a {@link ClientSession session} bound instance of {@link SessionScoped} binding the {@link ClientSession} * provided by the given {@link Supplier} to each and every command issued against MongoDB. - *

+ *
* Note: It is up to the caller to manage the {@link ClientSession} lifecycle. Use the * {@link SessionScoped#execute(SessionCallback, Consumer)} hook to potentially close the {@link ClientSession}. * @@ -212,7 +212,7 @@ public T execute(SessionCallback action, Consumer onComple /** * Obtain a {@link ClientSession} bound instance of {@link MongoOperations}. - *

+ *
* Note: It is up to the caller to manage the {@link ClientSession} lifecycle. * * @param session must not be {@literal null}. @@ -300,7 +300,7 @@ public T execute(SessionCallback action, Consumer onComple * is created on first interaction with the server. Collections can be explicitly created via * {@link #createCollection(Class)}. Please make sure to check if the collection {@link #collectionExists(Class) * exists} first. - *

+ *
* Translate any exceptions as necessary. * * @param collectionName name of the collection. Must not be {@literal null}. @@ -310,7 +310,7 @@ public T execute(SessionCallback action, Consumer onComple /** * Check to see if a collection with a name indicated by the entity class exists. - *

+ *
* Translate any exceptions as necessary. * * @param entityClass class that determines the name of the collection. Must not be {@literal null}. @@ -320,7 +320,7 @@ public T execute(SessionCallback action, Consumer onComple /** * Check to see if a collection with a given name exists. - *

+ *
* Translate any exceptions as necessary. * * @param collectionName name of the collection. Must not be {@literal null}. @@ -330,7 +330,7 @@ public T execute(SessionCallback action, Consumer onComple /** * Drop the collection with the name indicated by the entity class. - *

+ *
* Translate any exceptions as necessary. * * @param entityClass class that determines the collection to drop/delete. Must not be {@literal null}. @@ -339,7 +339,7 @@ public T execute(SessionCallback action, Consumer onComple /** * Drop the collection with the given name. - *

+ *
* Translate any exceptions as necessary. * * @param collectionName name of the collection to drop/delete. @@ -403,10 +403,10 @@ public T execute(SessionCallback action, Consumer onComple /** * Query for a list of objects of type T from the collection used by the entity class. - *

+ *
* The object is converted from the MongoDB native representation using an instance of {@see MongoConverter}. Unless * configured otherwise, an instance of {@link MappingMongoConverter} will be used. - *

+ *
* If your collection does not contain a homogeneous collection of types, this operation will not be an efficient way * to map objects since the test for class type is done in the client and not on the server. * @@ -417,10 +417,10 @@ public T execute(SessionCallback action, Consumer onComple /** * Query for a list of objects of type T from the specified collection. - *

+ *
* The object is converted from the MongoDB native representation using an instance of {@see MongoConverter}. Unless * configured otherwise, an instance of {@link MappingMongoConverter} will be used. - *

+ *
* If your collection does not contain a homogeneous collection of types, this operation will not be an efficient way * to map objects since the test for class type is done in the client and not on the server. * @@ -539,11 +539,11 @@ GroupByResults group(@Nullable Criteria criteria, String inputCollectionN /** * Execute an aggregation operation backed by a Mongo DB {@link com.mongodb.client.AggregateIterable}. - *

+ *
* Returns a {@link CloseableIterator} that wraps the a Mongo DB {@link com.mongodb.client.AggregateIterable} that * needs to be closed. The raw results will be mapped to the given entity class and are returned as stream. The name * of the inputCollection is derived from the inputType of the aggregation. - *

+ *
* Aggregation streaming can't be used with {@link AggregationOptions#isExplain() aggregation explain}. Enabling * explanation mode will throw an {@link IllegalArgumentException}. * @@ -557,10 +557,10 @@ GroupByResults group(@Nullable Criteria criteria, String inputCollectionN /** * Execute an aggregation operation backed by a Mongo DB {@link com.mongodb.client.AggregateIterable}. - *

+ *
* Returns a {@link CloseableIterator} that wraps the a Mongo DB {@link com.mongodb.client.AggregateIterable} that * needs to be closed. The raw results will be mapped to the given entity class. - *

+ *
* Aggregation streaming can't be used with {@link AggregationOptions#isExplain() aggregation explain}. Enabling * explanation mode will throw an {@link IllegalArgumentException}. * @@ -576,10 +576,10 @@ GroupByResults group(@Nullable Criteria criteria, String inputCollectionN /** * Execute an aggregation operation backed by a Mongo DB {@link com.mongodb.client.AggregateIterable}. - *

+ *
* Returns a {@link CloseableIterator} that wraps the a Mongo DB {@link com.mongodb.client.AggregateIterable} that * needs to be closed. The raw results will be mapped to the given entity class. - *

+ *
* Aggregation streaming can't be used with {@link AggregationOptions#isExplain() aggregation explain}. Enabling * explanation mode will throw an {@link IllegalArgumentException}. * @@ -702,10 +702,10 @@ MapReduceResults mapReduce(Query query, String inputCollectionName, Strin /** * Map the results of an ad-hoc query on the collection for the entity class to a single instance of an object of the * specified type. - *

+ *
* The object is converted from the MongoDB native representation using an instance of {@see MongoConverter}. Unless * configured otherwise, an instance of {@link MappingMongoConverter} will be used. - *

+ *
* The query is specified as a {@link Query} which can be created either using the {@link BasicQuery} or the more * feature rich {@link Query}. * @@ -720,10 +720,10 @@ MapReduceResults mapReduce(Query query, String inputCollectionName, Strin /** * Map the results of an ad-hoc query on the specified collection to a single instance of an object of the specified * type. - *

+ *
* The object is converted from the MongoDB native representation using an instance of {@see MongoConverter}. Unless * configured otherwise, an instance of {@link MappingMongoConverter} will be used. - *

+ *
* The query is specified as a {@link Query} which can be created either using the {@link BasicQuery} or the more * feature rich {@link Query}. * @@ -768,10 +768,10 @@ MapReduceResults mapReduce(Query query, String inputCollectionName, Strin /** * Map the results of an ad-hoc query on the collection for the entity class to a List of the specified type. - *

+ *
* The object is converted from the MongoDB native representation using an instance of {@see MongoConverter}. Unless * configured otherwise, an instance of {@link MappingMongoConverter} will be used. - *

+ *
* The query is specified as a {@link Query} which can be created either using the {@link BasicQuery} or the more * feature rich {@link Query}. * @@ -784,10 +784,10 @@ MapReduceResults mapReduce(Query query, String inputCollectionName, Strin /** * Map the results of an ad-hoc query on the specified collection to a List of the specified type. - *

+ *
* The object is converted from the MongoDB native representation using an instance of {@see MongoConverter}. Unless * configured otherwise, an instance of {@link MappingMongoConverter} will be used. - *

+ *
* The query is specified as a {@link Query} which can be created either using the {@link BasicQuery} or the more * feature rich {@link Query}. * @@ -881,7 +881,7 @@ default List findDistinct(Query query, String field, String collection, C } /** - * Triggers findAndModify + * Triggers findAndModify * to apply provided {@link Update} on documents matching {@link Criteria} of given {@link Query}. * * @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional @@ -897,7 +897,7 @@ default List findDistinct(Query query, String field, String collection, C T findAndModify(Query query, UpdateDefinition update, Class entityClass); /** - * Triggers findAndModify + * Triggers findAndModify * to apply provided {@link Update} on documents matching {@link Criteria} of given {@link Query}. * * @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional @@ -914,7 +914,7 @@ default List findDistinct(Query query, String field, String collection, C T findAndModify(Query query, UpdateDefinition update, Class entityClass, String collectionName); /** - * Triggers findAndModify + * Triggers findAndModify * to apply provided {@link Update} on documents matching {@link Criteria} of given {@link Query} taking * {@link FindAndModifyOptions} into account. * @@ -934,7 +934,7 @@ default List findDistinct(Query query, String field, String collection, C T findAndModify(Query query, UpdateDefinition update, FindAndModifyOptions options, Class entityClass); /** - * Triggers findAndModify + * Triggers findAndModify * to apply provided {@link Update} on documents matching {@link Criteria} of given {@link Query} taking * {@link FindAndModifyOptions} into account. * @@ -957,7 +957,7 @@ T findAndModify(Query query, UpdateDefinition update, FindAndModifyOptions o /** * Triggers - * findOneAndReplace + * findOneAndReplace * to replace a single document matching {@link Criteria} of given {@link Query} with the {@code replacement} * document.
* The collection name is derived from the {@literal replacement} type.
@@ -977,7 +977,7 @@ default T findAndReplace(Query query, T replacement) { /** * Triggers - * findOneAndReplace + * findOneAndReplace * to replace a single document matching {@link Criteria} of given {@link Query} with the {@code replacement} * document.
* Options are defaulted to {@link FindAndReplaceOptions#empty()}.
@@ -997,7 +997,7 @@ default T findAndReplace(Query query, T replacement, String collectionName) /** * Triggers - * findOneAndReplace + * findOneAndReplace * to replace a single document matching {@link Criteria} of given {@link Query} with the {@code replacement} document * taking {@link FindAndReplaceOptions} into account.
* NOTE: The replacement entity must not hold an {@literal id}. @@ -1018,7 +1018,7 @@ default T findAndReplace(Query query, T replacement, FindAndReplaceOptions o /** * Triggers - * findOneAndReplace + * findOneAndReplace * to replace a single document matching {@link Criteria} of given {@link Query} with the {@code replacement} document * taking {@link FindAndReplaceOptions} into account.
* NOTE: The replacement entity must not hold an {@literal id}. @@ -1041,7 +1041,7 @@ default T findAndReplace(Query query, T replacement, FindAndReplaceOptions o /** * Triggers - * findOneAndReplace + * findOneAndReplace * to replace a single document matching {@link Criteria} of given {@link Query} with the {@code replacement} document * taking {@link FindAndReplaceOptions} into account.
* NOTE: The replacement entity must not hold an {@literal id}. @@ -1066,7 +1066,7 @@ default T findAndReplace(Query query, T replacement, FindAndReplaceOptions o /** * Triggers - * findOneAndReplace + * findOneAndReplace * to replace a single document matching {@link Criteria} of given {@link Query} with the {@code replacement} document * taking {@link FindAndReplaceOptions} into account.
* NOTE: The replacement entity must not hold an {@literal id}. @@ -1094,7 +1094,7 @@ default T findAndReplace(Query query, S replacement, FindAndReplaceOption /** * Triggers - * findOneAndReplace + * findOneAndReplace * to replace a single document matching {@link Criteria} of given {@link Query} with the {@code replacement} document * taking {@link FindAndReplaceOptions} into account.
* NOTE: The replacement entity must not hold an {@literal id}. @@ -1120,9 +1120,9 @@ T findAndReplace(Query query, S replacement, FindAndReplaceOptions option * Map the results of an ad-hoc query on the collection for the entity type to a single instance of an object of the * specified type. The first document that matches the query is returned and also removed from the collection in the * database. - *

+ *
* The object is converted from the MongoDB native representation using an instance of {@see MongoConverter}. - *

+ *
* The query is specified as a {@link Query} which can be created either using the {@link BasicQuery} or the more * feature rich {@link Query}. * @@ -1137,10 +1137,10 @@ T findAndReplace(Query query, S replacement, FindAndReplaceOptions option /** * Map the results of an ad-hoc query on the specified collection to a single instance of an object of the specified * type. The first document that matches the query is returned and also removed from the collection in the database. - *

+ *
* The object is converted from the MongoDB native representation using an instance of {@see MongoConverter}. Unless * configured otherwise, an instance of {@link MappingMongoConverter} will be used. - *

+ *
* The query is specified as a {@link Query} which can be created either using the {@link BasicQuery} or the more * feature rich {@link Query}. * @@ -1160,6 +1160,12 @@ T findAndReplace(Query query, S replacement, FindAndReplaceOptions option * 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. + *
+ * This method uses an + * {@link com.mongodb.client.MongoCollection#countDocuments(org.bson.conversions.Bson, com.mongodb.client.model.CountOptions) + * aggregation execution} even for empty {@link Query queries} which may have an impact on performance, but guarantees + * shard, session and transaction compliance. In case an inaccurate count satisfies the applications needs use + * {@link #estimatedCount(Class)} for empty queries instead. * * @param query the {@link Query} class that specifies the criteria used to find documents. Must not be * {@literal null}. @@ -1176,6 +1182,12 @@ T findAndReplace(Query query, S replacement, FindAndReplaceOptions option * 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. + *
+ * 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. @@ -1184,6 +1196,35 @@ T findAndReplace(Query query, S replacement, FindAndReplaceOptions option */ 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. + *
+ * 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. + *
+ * 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}.
@@ -1191,6 +1232,12 @@ T findAndReplace(Query query, S replacement, FindAndReplaceOptions option * 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. + *
+ * 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}. @@ -1202,34 +1249,39 @@ T findAndReplace(Query query, S replacement, FindAndReplaceOptions option /** * Insert the object into the collection for the entity type of the object to save. - *

+ *
* The object is converted to the MongoDB native representation using an instance of {@see MongoConverter}. - *

+ *
* If your object has an "Id' property, it will be set with the generated Id from MongoDB. If your Id property is a * String then MongoDB ObjectId will be used to populate that string. Otherwise, the conversion from ObjectId to your * property type will be handled by Spring's BeanWrapper class that leverages Type Conversion API. See * Spring's * Type Conversion" for more details. - *

- *

+ *
* Insert is used to initially store the object into the database. To update an existing object use the save method. + *
+ * 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 insert(T objectToSave); /** * Insert the object into the specified collection. - *

+ *
* The object is converted to the MongoDB native representation using an instance of {@see MongoConverter}. Unless * configured otherwise, an instance of {@link MappingMongoConverter} will be used. - *

+ *
* Insert is used to initially store the object into the database. To update an existing object use the save method. + *
+ * 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 insert(T objectToSave, String collectionName); @@ -1263,37 +1315,42 @@ T findAndReplace(Query query, S replacement, FindAndReplaceOptions option /** * Save the object to the collection for the entity type of the object to save. This will perform an insert if the * object is not already present, that is an 'upsert'. - *

+ *
* The object is converted to the MongoDB native representation using an instance of {@see MongoConverter}. Unless * configured otherwise, an instance of {@link MappingMongoConverter} will be used. - *

+ *
* If your object has an "Id' property, it will be set with the generated Id from MongoDB. If your Id property is a * String then MongoDB ObjectId will be used to populate that string. Otherwise, the conversion from ObjectId to your * property type will be handled by Spring's BeanWrapper class that leverages Type Conversion API. See * Spring's * Type Conversion" for more details. + *
+ * 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 save(T objectToSave); /** * Save the object to the specified collection. This will perform an insert if the object is not already present, that * is an 'upsert'. - *

+ *
* The object is converted to the MongoDB native representation using an instance of {@see MongoConverter}. Unless * configured otherwise, an instance of {@link MappingMongoConverter} will be used. - *

+ *
* If your object has an "Id' property, it will be set with the generated Id from MongoDB. If your Id property is a * String then MongoDB ObjectId will be used to populate that string. Otherwise, the conversion from ObjectId to your - * property type will be handled by Spring's BeanWrapper class that leverages Type Conversion API. See Spring's Type - * Conversion" for more details. + * property type will be handled by Spring's BeanWrapper class that leverages Type Conversion API. + * See Spring's Type Conversion for more details. + *
+ * 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 save(T objectToSave, String collectionName); diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoServerApiFactoryBean.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoServerApiFactoryBean.java new file mode 100644 index 0000000000..ce7ad5711d --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoServerApiFactoryBean.java @@ -0,0 +1,92 @@ +/* + * Copyright 2021-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.mongodb.core; + +import org.springframework.beans.factory.FactoryBean; +import org.springframework.lang.Nullable; +import org.springframework.util.ObjectUtils; + +import com.mongodb.ServerApi; +import com.mongodb.ServerApi.Builder; +import com.mongodb.ServerApiVersion; + +/** + * {@link FactoryBean} for creating {@link ServerApi} using the {@link ServerApi.Builder}. + * + * @author Christoph Strobl + * @since 3.3 + */ +public class MongoServerApiFactoryBean implements FactoryBean { + + private String version; + private @Nullable Boolean deprecationErrors; + private @Nullable Boolean strict; + + /** + * @param version the version string either as the enum name or the server version value. + * @see ServerApiVersion + */ + public void setVersion(String version) { + this.version = version; + } + + /** + * @param deprecationErrors + * @see ServerApi.Builder#deprecationErrors(boolean) + */ + public void setDeprecationErrors(@Nullable Boolean deprecationErrors) { + this.deprecationErrors = deprecationErrors; + } + + /** + * @param strict + * @see ServerApi.Builder#strict(boolean) + */ + public void setStrict(@Nullable Boolean strict) { + this.strict = strict; + } + + @Nullable + @Override + public ServerApi getObject() throws Exception { + + Builder builder = ServerApi.builder().version(version()); + + if (deprecationErrors != null) { + builder = builder.deprecationErrors(deprecationErrors); + } + if (strict != null) { + builder = builder.strict(strict); + } + return builder.build(); + } + + @Nullable + @Override + public Class getObjectType() { + return ServerApi.class; + } + + private ServerApiVersion version() { + try { + // lookup by name eg. 'V1' + return ObjectUtils.caseInsensitiveValueOf(ServerApiVersion.values(), version); + } catch (IllegalArgumentException e) { + // or just the version number, eg. just '1' + return ServerApiVersion.findByValue(version); + } + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java index b70ddcbb60..30b4bbcd75 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java @@ -1,5 +1,5 @@ /* - * Copyright 2010-2020 the original author or authors. + * Copyright 2010-2022 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. @@ -17,11 +17,6 @@ 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; @@ -52,6 +47,7 @@ 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; @@ -60,6 +56,7 @@ 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; @@ -102,12 +99,12 @@ 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; @@ -160,22 +157,14 @@ * @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 ITERABLE_CLASSES; - - static { - - Set 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, MongoPersistentProperty> mappingContext; @@ -326,6 +315,7 @@ public void setReadPreference(@Nullable ReadPreference readPreference) { * (non-Javadoc) * @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext) */ + @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { prepareIndexCreator(applicationContext); @@ -349,7 +339,7 @@ public void setApplicationContext(ApplicationContext applicationContext) throws /** * Set the {@link EntityCallbacks} instance to use when invoking * {@link org.springframework.data.mapping.callback.EntityCallback callbacks} like the {@link BeforeSaveCallback}. - *

+ *
* Overrides potentially existing {@link EntityCallbacks}. * * @param entityCallbacks must not be {@literal null}. @@ -391,6 +381,7 @@ private void prepareIndexCreator(ApplicationContext context) { * * @return */ + @Override public MongoConverter getConverter() { return this.mongoConverter; } @@ -530,6 +521,7 @@ protected void executeQuery(Query query, String collectionName, DocumentCallback * (non-Javadoc) * @see org.springframework.data.mongodb.core.MongoOperations#execute(org.springframework.data.mongodb.core.DbCallback) */ + @Override public T execute(DbCallback action) { Assert.notNull(action, "DbCallback must not be null!"); @@ -546,6 +538,7 @@ public T execute(DbCallback action) { * (non-Javadoc) * @see org.springframework.data.mongodb.core.MongoOperations#execute(java.lang.Class, org.springframework.data.mongodb.core.DbCallback) */ + @Override public T execute(Class entityClass, CollectionCallback callback) { Assert.notNull(entityClass, "EntityClass must not be null!"); @@ -556,6 +549,7 @@ public T execute(Class entityClass, CollectionCallback callback) { * (non-Javadoc) * @see org.springframework.data.mongodb.core.MongoOperations#execute(java.lang.String, org.springframework.data.mongodb.core.DbCallback) */ + @Override public T execute(String collectionName, CollectionCallback callback) { Assert.notNull(collectionName, "CollectionName must not be null!"); @@ -608,14 +602,16 @@ public void setSessionSynchronization(SessionSynchronization sessionSynchronizat * (non-Javadoc) * @see org.springframework.data.mongodb.core.MongoOperations#createCollection(java.lang.Class) */ + @Override public MongoCollection createCollection(Class entityClass) { - return createCollection(entityClass, CollectionOptions.empty()); + return createCollection(entityClass, operations.forType(entityClass).getCollectionOptions()); } /* * (non-Javadoc) * @see org.springframework.data.mongodb.core.MongoOperations#createCollection(java.lang.Class, org.springframework.data.mongodb.core.CollectionOptions) */ + @Override public MongoCollection createCollection(Class entityClass, @Nullable CollectionOptions collectionOptions) { @@ -634,6 +630,7 @@ public MongoCollection createCollection(Class entityClass, * (non-Javadoc) * @see org.springframework.data.mongodb.core.MongoOperations#createCollection(java.lang.String) */ + @Override public MongoCollection createCollection(String collectionName) { Assert.notNull(collectionName, "CollectionName must not be null!"); @@ -645,6 +642,7 @@ public MongoCollection createCollection(String collectionName) { * (non-Javadoc) * @see org.springframework.data.mongodb.core.MongoOperations#createCollection(java.lang.String, org.springframework.data.mongodb.core.CollectionOptions) */ + @Override public MongoCollection createCollection(String collectionName, @Nullable CollectionOptions collectionOptions) { @@ -656,6 +654,7 @@ public MongoCollection createCollection(String collectionName, * (non-Javadoc) * @see org.springframework.data.mongodb.core.MongoOperations#getCollection(java.lang.String) */ + @Override @SuppressWarnings("ConstantConditions") public MongoCollection getCollection(String collectionName) { @@ -668,6 +667,7 @@ public MongoCollection getCollection(String collectionName) { * (non-Javadoc) * @see org.springframework.data.mongodb.core.ExecutableInsertOperation#getCollection(java.lang.Class) */ + @Override public boolean collectionExists(Class entityClass) { return collectionExists(getCollectionName(entityClass)); } @@ -676,6 +676,7 @@ public boolean collectionExists(Class entityClass) { * (non-Javadoc) * @see org.springframework.data.mongodb.core.ExecutableInsertOperation#getCollection(java.lang.String) */ + @Override @SuppressWarnings("ConstantConditions") public boolean collectionExists(String collectionName) { @@ -696,6 +697,7 @@ public boolean collectionExists(String collectionName) { * (non-Javadoc) * @see org.springframework.data.mongodb.core.ExecutableInsertOperation#dropCollection(java.lang.Class) */ + @Override public void dropCollection(Class entityClass) { dropCollection(getCollectionName(entityClass)); } @@ -704,6 +706,7 @@ public void dropCollection(Class entityClass) { * (non-Javadoc) * @see org.springframework.data.mongodb.core.ExecutableInsertOperation#dropCollection(java.lang.String) */ + @Override public void dropCollection(String collectionName) { Assert.notNull(collectionName, "CollectionName must not be null!"); @@ -718,26 +721,34 @@ public void dropCollection(String collectionName) { }); } + @Override + public IndexOperations indexOps(String collectionName) { + return indexOps(collectionName, null); + } + /* * (non-Javadoc) * @see org.springframework.data.mongodb.core.ExecutableInsertOperation#indexOps(java.lang.String) */ - public IndexOperations indexOps(String collectionName) { - return new DefaultIndexOperations(this, collectionName, null); + @Override + public IndexOperations indexOps(String collectionName, @Nullable Class type) { + return new DefaultIndexOperations(this, collectionName, type); } /* * (non-Javadoc) * @see org.springframework.data.mongodb.core.ExecutableInsertOperation#indexOps(java.lang.Class) */ + @Override public IndexOperations indexOps(Class entityClass) { - return new DefaultIndexOperations(this, getCollectionName(entityClass), entityClass); + return indexOps(getCollectionName(entityClass), entityClass); } /* * (non-Javadoc) * @see org.springframework.data.mongodb.core.ExecutableInsertOperation#bulkOps(org.springframework.data.mongodb.core.BulkMode, java.lang.String) */ + @Override public BulkOperations bulkOps(BulkMode bulkMode, String collectionName) { return bulkOps(bulkMode, null, collectionName); } @@ -746,6 +757,7 @@ public BulkOperations bulkOps(BulkMode bulkMode, String collectionName) { * (non-Javadoc) * @see org.springframework.data.mongodb.core.ExecutableInsertOperation#bulkOps(org.springframework.data.mongodb.core.BulkMode, java.lang.Class) */ + @Override public BulkOperations bulkOps(BulkMode bulkMode, Class entityClass) { return bulkOps(bulkMode, entityClass, getCollectionName(entityClass)); } @@ -754,6 +766,7 @@ public BulkOperations bulkOps(BulkMode bulkMode, Class entityClass) { * (non-Javadoc) * @see org.springframework.data.mongodb.core.ExecutableInsertOperation#bulkOps(org.springframework.data.mongodb.core.BulkMode, java.lang.Class, java.lang.String) */ + @Override public BulkOperations bulkOps(BulkMode mode, @Nullable Class entityType, String collectionName) { Assert.notNull(mode, "BulkMode must not be null!"); @@ -763,7 +776,6 @@ public BulkOperations bulkOps(BulkMode mode, @Nullable Class entityType, Stri new BulkOperationContext(mode, Optional.ofNullable(getPersistentEntity(entityType)), queryMapper, updateMapper, eventPublisher, entityCallbacks)); - operations.setExceptionTranslator(exceptionTranslator); operations.setDefaultWriteConcern(writeConcern); return operations; @@ -981,7 +993,7 @@ public GeoResults geoNear(NearQuery near, Class domainType, String col for (Document element : results) { GeoResult geoResult = callback.doWith(element); - aggregate = aggregate.add(new BigDecimal(geoResult.getDistance().getValue())); + aggregate = aggregate.add(BigDecimal.valueOf(geoResult.getDistance().getValue())); result.add(geoResult); } @@ -1116,6 +1128,7 @@ public long count(Query query, String collectionName) { * (non-Javadoc) * @see org.springframework.data.mongodb.core.MongoOperations#count(org.springframework.data.mongodb.core.query.Query, java.lang.Class, java.lang.String) */ + @Override public long count(Query query, @Nullable Class entityClass, String collectionName) { Assert.notNull(query, "Query must not be null!"); @@ -1140,6 +1153,19 @@ protected long doCount(String collectionName, Document filter, CountOptions opti 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) @@ -1168,17 +1194,34 @@ public T insert(T objectToSave, String collectionName) { return (T) doInsert(collectionName, objectToSave, this.mongoConverter); } - protected void ensureNotIterable(@Nullable Object o) { - if (o != null) { - if (o.getClass().isArray() || ITERABLE_CLASSES.contains(o.getClass().getName())) { - throw new IllegalArgumentException("Cannot use a collection here."); - } + /** + * Ensure the given {@literal source} is not an {@link java.lang.reflect.Array}, {@link Collection} or + * {@link Iterator}. + * + * @param source can be {@literal null}. + * @deprecated since 3.2. Call {@link #ensureNotCollectionLike(Object)} instead. + */ + protected void ensureNotIterable(@Nullable Object source) { + ensureNotCollectionLike(source); + } + + /** + * Ensure the given {@literal source} is not an {@link java.lang.reflect.Array}, {@link Collection} or + * {@link Iterator}. + * + * @param source can be {@literal null}. + * @since 3.2. + */ + protected void ensureNotCollectionLike(@Nullable Object source) { + + if (EntityOperations.isCollectionLike(source)) { + throw new IllegalArgumentException("Cannot use a collection here."); } } /** * Prepare the collection before any processing is done using it. This allows a convenient way to apply settings like - * slaveOk() etc. Can be overridden in sub-classes. + * withCodecRegistry() etc. Can be overridden in sub-classes. * * @param collection */ @@ -1355,13 +1398,13 @@ public T save(T objectToSave, String collectionName) { Assert.notNull(objectToSave, "Object to save must not be null!"); Assert.hasText(collectionName, "Collection name must not be null or empty!"); + ensureNotCollectionLike(objectToSave); AdaptibleEntity source = operations.forEntity(objectToSave, mongoConverter.getConversionService()); return source.isVersionedEntity() // ? doSaveVersioned(source, collectionName) // : (T) doSave(collectionName, objectToSave, this.mongoConverter); - } @SuppressWarnings("unchecked") @@ -1826,7 +1869,7 @@ public List mapReduce(Query query, Class domainType, String inputColle Document mappedSort = getMappedSortObject(query, domainType); if (mappedSort != null && !mappedSort.isEmpty()) { - mapReduce = mapReduce.sort(getMappedSortObject(query, domainType)); + mapReduce = mapReduce.sort(mappedSort); } mapReduce = mapReduce @@ -1892,10 +1935,12 @@ public List mapReduce(Query query, Class domainType, String inputColle return mappedResults; } + @Override public GroupByResults group(String inputCollectionName, GroupBy groupBy, Class entityClass) { return group(null, inputCollectionName, groupBy, entityClass); } + @Override public GroupByResults group(@Nullable Criteria criteria, String inputCollectionName, GroupBy groupBy, Class entityClass) { @@ -1969,9 +2014,7 @@ public AggregationResults aggregate(TypedAggregation aggregation, Stri Assert.notNull(aggregation, "Aggregation pipeline must not be null!"); - AggregationOperationContext context = new TypeBasedAggregationOperationContext(aggregation.getInputType(), - mappingContext, queryMapper); - return aggregate(aggregation, inputCollectionName, outputType, context); + return aggregate(aggregation, inputCollectionName, outputType, null); } /* (non-Javadoc) @@ -1981,7 +2024,7 @@ public AggregationResults aggregate(TypedAggregation aggregation, Stri public AggregationResults aggregate(Aggregation aggregation, Class inputType, Class outputType) { return aggregate(aggregation, getCollectionName(inputType), outputType, - new TypeBasedAggregationOperationContext(inputType, mappingContext, queryMapper)); + queryOperations.createAggregation(aggregation, inputType).getAggregationOperationContext()); } /* (non-Javadoc) @@ -2088,9 +2131,13 @@ protected AggregationResults aggregate(Aggregation aggregation, String co Assert.notNull(aggregation, "Aggregation pipeline must not be null!"); Assert.notNull(outputType, "Output type must not be null!"); - AggregationOperationContext contextToUse = new AggregationUtil(queryMapper, mappingContext) - .prepareAggregationContext(aggregation, context); - return doAggregate(aggregation, collectionName, outputType, contextToUse); + return doAggregate(aggregation, collectionName, outputType, + queryOperations.createAggregation(aggregation, context)); + } + + private AggregationResults doAggregate(Aggregation aggregation, String collectionName, Class outputType, + AggregationDefinition context) { + return doAggregate(aggregation, collectionName, outputType, context.getAggregationOperationContext()); } @SuppressWarnings("ConstantConditions") @@ -2125,7 +2172,7 @@ protected AggregationResults doAggregate(Aggregation aggregation, String List rawResult = new ArrayList<>(); - Class domainType = aggregation instanceof TypedAggregation ? ((TypedAggregation) aggregation).getInputType() + Class domainType = aggregation instanceof TypedAggregation ? ((TypedAggregation) aggregation).getInputType() : null; Optional collation = Optionals.firstNonEmpty(options::getCollation, @@ -2141,11 +2188,23 @@ protected AggregationResults doAggregate(Aggregation aggregation, String } options.getComment().ifPresent(aggregateIterable::comment); + options.getHint().ifPresent(aggregateIterable::hint); if (options.hasExecutionTimeLimit()) { aggregateIterable = aggregateIterable.maxTime(options.getMaxTime().toMillis(), TimeUnit.MILLISECONDS); } + if (options.isSkipResults()) { + + // toCollection only allowed for $out and $merge if those are the last stages + if (aggregation.getPipeline().isOutOrMerge()) { + aggregateIterable.toCollection(); + } else { + aggregateIterable.first(); + } + return new AggregationResults<>(Collections.emptyList(), new Document()); + } + MongoIterable iterable = aggregateIterable.map(val -> { rawResult.add(val); @@ -2166,11 +2225,10 @@ protected CloseableIterator aggregateStream(Aggregation aggregation, Stri Assert.notNull(outputType, "Output type must not be null!"); Assert.isTrue(!aggregation.getOptions().isExplain(), "Can't use explain option with streaming!"); - AggregationUtil aggregationUtil = new AggregationUtil(queryMapper, mappingContext); - AggregationOperationContext rootContext = aggregationUtil.prepareAggregationContext(aggregation, context); + AggregationDefinition aggregationDefinition = queryOperations.createAggregation(aggregation, context); AggregationOptions options = aggregation.getOptions(); - List pipeline = aggregationUtil.createPipeline(aggregation, rootContext); + List pipeline = aggregationDefinition.getAggregationPipeline(); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Streaming aggregation: {} in collection {}", serializeToJsonSafely(pipeline), collectionName); @@ -2188,6 +2246,7 @@ protected CloseableIterator aggregateStream(Aggregation aggregation, Stri } options.getComment().ifPresent(cursor::comment); + options.getHint().ifPresent(cursor::hint); Class domainType = aggregation instanceof TypedAggregation ? ((TypedAggregation) aggregation).getInputType() : null; @@ -2288,6 +2347,7 @@ protected String replaceWithResourceIfNecessary(String function) { * (non-Javadoc) * @see org.springframework.data.mongodb.core.ExecutableInsertOperation#getCollectionNames() */ + @Override @SuppressWarnings("ConstantConditions") public Set getCollectionNames() { return execute(db -> { @@ -2398,6 +2458,20 @@ protected MongoCollection doCreateCollection(String collectionName, Do 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 coll = db.getCollection(collectionName, Document.class); @@ -2552,6 +2626,19 @@ protected Document convertToDocument(@Nullable CollectionOptions collectionOptio 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; @@ -2599,7 +2686,7 @@ Document getMappedValidator(Validator validator, Class domainType) { /** * Map the results of an ad-hoc query on the default MongoDB collection to an object using the template's converter. * The first document that matches the query is returned and also removed from the collection in the database. - *

+ *
* The query document is specified as a standard Document and so is the fields specification. * * @param collectionName name of the collection to retrieve the objects from @@ -2715,25 +2802,24 @@ private MongoCollection getAndPrepareCollection(MongoDatabase db, Stri * 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 *

    - *
  1. Execute the given {@link ConnectionCallback} for a {@link Document}.
  2. + *
  3. Execute the given {@link CollectionCallback} for a {@link Document}.
  4. *
  5. Apply the given {@link DocumentCallback} to each of the {@link Document}s to obtain the result.
  6. *
      * * @param * @param collectionCallback the callback to retrieve the {@link Document} with - * @param objectCallback the {@link DocumentCallback} to transform {@link Document}s into the actual domain type + * @param documentCallback the {@link DocumentCallback} to transform {@link Document}s into the actual domain type * @param collectionName the collection to be queried * @return */ @Nullable private T executeFindOneInternal(CollectionCallback collectionCallback, - DocumentCallback objectCallback, String collectionName) { + DocumentCallback documentCallback, String collectionName) { try { - T result = objectCallback - .doWith(collectionCallback.doInCollection(getAndPrepareCollection(doGetDatabase(), collectionName))); - return result; + Document document = collectionCallback.doInCollection(getAndPrepareCollection(doGetDatabase(), collectionName)); + return document != null ? documentCallback.doWith(document) : null; } catch (RuntimeException e) { throw potentiallyConvertRuntimeException(e, exceptionTranslator); } @@ -2743,7 +2829,7 @@ private T executeFindOneInternal(CollectionCallback collectionCall * Internal method using callback to do queries against the datastore that requires reading a collection of objects. * It will take the following steps *
        - *
      1. Execute the given {@link ConnectionCallback} for a {@link FindIterable}.
      2. + *
      3. Execute the given {@link CollectionCallback} for a {@link FindIterable}.
      4. *
      5. Prepare that {@link FindIterable} with the given {@link CursorPreparer} (will be skipped if * {@link CursorPreparer} is {@literal null}
      6. *
      7. Iterate over the {@link FindIterable} and applies the given {@link DocumentCallback} to each of the @@ -2753,36 +2839,27 @@ private T executeFindOneInternal(CollectionCallback collectionCall * @param * @param collectionCallback the callback to retrieve the {@link FindIterable} with * @param preparer the {@link CursorPreparer} to potentially modify the {@link FindIterable} before iterating over it - * @param objectCallback the {@link DocumentCallback} to transform {@link Document}s into the actual domain type + * @param documentCallback the {@link DocumentCallback} to transform {@link Document}s into the actual domain type * @param collectionName the collection to be queried * @return */ private List executeFindMultiInternal(CollectionCallback> collectionCallback, - CursorPreparer preparer, DocumentCallback objectCallback, String collectionName) { + CursorPreparer preparer, DocumentCallback documentCallback, String collectionName) { try { - MongoCursor cursor = null; - - try { - - cursor = preparer - .initiateFind(getAndPrepareCollection(doGetDatabase(), collectionName), collectionCallback::doInCollection) - .iterator(); + try (MongoCursor cursor = preparer + .initiateFind(getAndPrepareCollection(doGetDatabase(), collectionName), collectionCallback::doInCollection) + .iterator()) { List result = new ArrayList<>(); while (cursor.hasNext()) { Document object = cursor.next(); - result.add(objectCallback.doWith(object)); + result.add(documentCallback.doWith(object)); } return result; - } finally { - - if (cursor != null) { - cursor.close(); - } } } catch (RuntimeException e) { throw potentiallyConvertRuntimeException(e, exceptionTranslator); @@ -2792,23 +2869,12 @@ private List executeFindMultiInternal(CollectionCallback> collectionCallback, CursorPreparer preparer, DocumentCallbackHandler callbackHandler, String collectionName) { - try { - - MongoCursor cursor = null; + try (MongoCursor cursor = preparer + .initiateFind(getAndPrepareCollection(doGetDatabase(), collectionName), collectionCallback::doInCollection) + .iterator()) { - try { - - cursor = preparer - .initiateFind(getAndPrepareCollection(doGetDatabase(), collectionName), collectionCallback::doInCollection) - .iterator(); - - while (cursor.hasNext()) { - callbackHandler.processDocument(cursor.next()); - } - } finally { - if (cursor != null) { - cursor.close(); - } + while (cursor.hasNext()) { + callbackHandler.processDocument(cursor.next()); } } catch (RuntimeException e) { throw potentiallyConvertRuntimeException(e, exceptionTranslator); @@ -2931,6 +2997,7 @@ public FindCallback(Document query, Document fields, @Nullable com.mongodb.clien this.collation = collation; } + @Override public FindIterable doInCollection(MongoCollection collection) throws MongoException, DataAccessException { @@ -2950,12 +3017,17 @@ public FindIterable doInCollection(MongoCollection collectio * @author Christoph Strobl * @since 2.0 */ - @RequiredArgsConstructor private class ExistsCallback implements CollectionCallback { 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 collection) throws MongoException, DataAccessException { @@ -2977,7 +3049,7 @@ private static class FindAndRemoveCallback implements CollectionCallback collation; - public FindAndRemoveCallback(Document query, Document fields, Document sort, @Nullable Collation collation) { + FindAndRemoveCallback(Document query, Document fields, Document sort, @Nullable Collation collation) { this.query = query; this.fields = fields; @@ -2985,6 +3057,7 @@ public FindAndRemoveCallback(Document query, Document fields, Document sort, @Nu this.collation = Optional.ofNullable(collation); } + @Override public Document doInCollection(MongoCollection collection) throws MongoException, DataAccessException { FindOneAndDeleteOptions opts = new FindOneAndDeleteOptions().sort(sort).projection(fields); @@ -3003,8 +3076,9 @@ private static class FindAndModifyCallback implements CollectionCallback arrayFilters; private final FindAndModifyOptions options; - public FindAndModifyCallback(Document query, Document fields, Document sort, Object update, - List arrayFilters, FindAndModifyOptions options) { + FindAndModifyCallback(Document query, Document fields, Document sort, Object update, List arrayFilters, + FindAndModifyOptions options) { + this.query = query; this.fields = fields; this.sort = sort; @@ -3013,6 +3087,7 @@ public FindAndModifyCallback(Document query, Document fields, Document sort, Obj this.options = options; } + @Override public Document doInCollection(MongoCollection collection) throws MongoException, DataAccessException { FindOneAndUpdateOptions opts = new FindOneAndUpdateOptions(); @@ -3101,8 +3176,7 @@ public Document doInCollection(MongoCollection collection) throws Mong interface DocumentCallback { - @Nullable - T doWith(@Nullable Document object); + T doWith(Document object); } /** @@ -3113,29 +3187,33 @@ interface DocumentCallback { * @author Christoph Strobl * @author Roman Puchkovskiy */ - @RequiredArgsConstructor private class ReadDocumentCallback implements DocumentCallback { - private final @NonNull EntityReader reader; - private final @NonNull Class type; + private final EntityReader reader; + private final Class type; private final String collectionName; - @Nullable - public T doWith(@Nullable Document document) { + ReadDocumentCallback(EntityReader reader, Class type, String collectionName) { - T source = null; + this.reader = reader; + this.type = type; + this.collectionName = collectionName; + } - if (document != null) { - maybeEmitEvent(new AfterLoadEvent<>(document, type, collectionName)); - source = reader.read(type, document); - } + @Override + public T doWith(Document document) { + + maybeEmitEvent(new AfterLoadEvent<>(document, type, collectionName)); + T entity = reader.read(type, document); - if (source != null) { - maybeEmitEvent(new AfterConvertEvent<>(document, source, collectionName)); - source = maybeCallAfterConvert(source, document, collectionName); + if (entity == null) { + throw new MappingException(String.format("EntityReader %s returned null", reader)); } - return source; + maybeEmitEvent(new AfterConvertEvent<>(document, entity, collectionName)); + entity = maybeCallAfterConvert(entity, document, collectionName); + + return entity; } } @@ -3147,21 +3225,29 @@ public T doWith(@Nullable Document document) { * @param * @since 2.0 */ - @RequiredArgsConstructor private class ProjectingReadCallback implements DocumentCallback { - private final @NonNull EntityReader reader; - private final @NonNull Class entityType; - private final @NonNull Class targetType; - private final @NonNull String collectionName; + private final EntityReader reader; + private final Class entityType; + private final Class targetType; + private final String collectionName; + + ProjectingReadCallback(EntityReader reader, Class entityType, Class targetType, + String collectionName) { + + this.reader = reader; + this.entityType = entityType; + this.targetType = targetType; + this.collectionName = collectionName; + } /* * (non-Javadoc) * @see org.springframework.data.mongodb.core.MongoTemplate.DocumentCallback#doWith(org.bson.Document) */ + @Override @SuppressWarnings("unchecked") - @Nullable - public T doWith(@Nullable Document document) { + public T doWith(Document document) { if (document == null) { return null; @@ -3172,15 +3258,16 @@ public T doWith(@Nullable Document document) { maybeEmitEvent(new AfterLoadEvent<>(document, targetType, collectionName)); - Object source = reader.read(typeToRead, document); - Object result = targetType.isInterface() ? projectionFactory.createProjection(targetType, source) : source; + Object entity = reader.read(typeToRead, document); - if (result != null) { - maybeEmitEvent(new AfterConvertEvent<>(document, result, collectionName)); - result = maybeCallAfterConvert(result, document, collectionName); + if (entity == null) { + throw new MappingException(String.format("EntityReader %s returned null", reader)); } - return (T) result; + Object result = targetType.isInterface() ? projectionFactory.createProjection(targetType, entity) : entity; + + maybeEmitEvent(new AfterConvertEvent<>(document, result, collectionName)); + return (T) maybeCallAfterConvert(result, document, collectionName); } } @@ -3189,7 +3276,7 @@ class QueryCursorPreparer implements CursorPreparer { private final Query query; private final @Nullable Class type; - public QueryCursorPreparer(Query query, @Nullable Class type) { + QueryCursorPreparer(Query query, @Nullable Class type) { this.query = query; this.type = type; @@ -3199,6 +3286,7 @@ public QueryCursorPreparer(Query query, @Nullable Class type) { * (non-Javadoc) * @see org.springframework.data.mongodb.core.CursorPreparer#prepare(com.mongodb.DBCursor) */ + @Override public FindIterable prepare(FindIterable iterable) { FindIterable cursorToUse = iterable; @@ -3250,6 +3338,10 @@ public FindIterable prepare(FindIterable iterable) { cursorToUse = cursorToUse.batchSize(meta.getCursorBatchSize()); } + if (meta.getAllowDiskUse() != null) { + cursorToUse = cursorToUse.allowDiskUse(meta.getAllowDiskUse()); + } + for (Meta.CursorOption option : meta.getFlags()) { switch (option) { @@ -3260,6 +3352,7 @@ public FindIterable prepare(FindIterable iterable) { case PARTIAL: cursorToUse = cursorToUse.partial(true); break; + case SECONDARY_READS: case SLAVE_OK: break; default: @@ -3277,7 +3370,8 @@ public FindIterable prepare(FindIterable iterable) { @Override public ReadPreference getReadPreference() { - return query.getMeta().getFlags().contains(CursorOption.SLAVE_OK) ? ReadPreference.primaryPreferred() : null; + return (query.getMeta().getFlags().contains(CursorOption.SECONDARY_READS) + || query.getMeta().getFlags().contains(CursorOption.SLAVE_OK)) ? ReadPreference.primaryPreferred() : null; } } @@ -3311,8 +3405,8 @@ static class GeoNearResultDocumentCallback implements DocumentCallback doWith(@Nullable Document object) { + @Override + public GeoResult doWith(Document object) { double distance = Double.NaN; if (object.containsKey(distanceField)) { @@ -3331,7 +3425,6 @@ public GeoResult doWith(@Nullable Document object) { * @author Thomas Darimont * @since 1.7 */ - @AllArgsConstructor(access = AccessLevel.PACKAGE) static class CloseableIterableCursorAdapter implements CloseableIterator { private volatile @Nullable MongoCursor cursor; @@ -3340,19 +3433,23 @@ static class CloseableIterableCursorAdapter implements CloseableIterator { /** * Creates a new {@link CloseableIterableCursorAdapter} backed by the given {@link MongoCollection}. - * - * @param cursor - * @param exceptionTranslator - * @param objectReadCallback */ - public CloseableIterableCursorAdapter(MongoIterable cursor, - PersistenceExceptionTranslator exceptionTranslator, DocumentCallback objectReadCallback) { + CloseableIterableCursorAdapter(MongoIterable cursor, PersistenceExceptionTranslator exceptionTranslator, + DocumentCallback objectReadCallback) { this.cursor = cursor.iterator(); this.exceptionTranslator = exceptionTranslator; this.objectReadCallback = objectReadCallback; } + CloseableIterableCursorAdapter(MongoCursor cursor, PersistenceExceptionTranslator exceptionTranslator, + DocumentCallback objectReadCallback) { + + this.cursor = cursor; + this.exceptionTranslator = exceptionTranslator; + this.objectReadCallback = objectReadCallback; + } + @Override public boolean hasNext() { @@ -3379,8 +3476,7 @@ public T next() { try { Document item = cursor.next(); - T converted = objectReadCallback.doWith(item); - return converted; + return objectReadCallback.doWith(item); } catch (RuntimeException ex) { throw potentiallyConvertRuntimeException(ex, exceptionTranslator); } @@ -3406,14 +3502,27 @@ public void close() { } } + /** + * @deprecated since 3.1.4. Use {@link #getMongoDatabaseFactory()} instead. + * @return the {@link MongoDatabaseFactory} in use. + */ + @Deprecated public MongoDatabaseFactory getMongoDbFactory() { + return getMongoDatabaseFactory(); + } + + /** + * @return the {@link MongoDatabaseFactory} in use. + * @since 3.1.4 + */ + public MongoDatabaseFactory getMongoDatabaseFactory() { return mongoDbFactory; } /** * {@link MongoTemplate} extension bound to a specific {@link ClientSession} that is applied when interacting with the * server through the driver API. - *

        + *
        * The prepare steps for {@link MongoDatabase} and {@link MongoCollection} proxy the target and invoke the desired * target method matching the actual arguments plus a {@link ClientSession}. * diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/PropertyOperations.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/PropertyOperations.java index 6afb8e9405..15295eb155 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/PropertyOperations.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/PropertyOperations.java @@ -1,5 +1,5 @@ /* - * Copyright 2018-2020 the original author or authors. + * Copyright 2018-2022 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. @@ -15,9 +15,6 @@ */ 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; @@ -33,11 +30,14 @@ * @author Christoph Strobl * @since 2.1 */ -@RequiredArgsConstructor(access = AccessLevel.PACKAGE) class PropertyOperations { private final MappingContext, MongoPersistentProperty> mappingContext; + PropertyOperations(MappingContext, 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 diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/QueryOperations.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/QueryOperations.java index 438e53c5dc..38a921043e 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/QueryOperations.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/QueryOperations.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 the original author or authors. + * Copyright 2020-2022 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. @@ -17,6 +17,7 @@ import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Optional; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; @@ -31,11 +32,17 @@ import org.springframework.data.mapping.PropertyReferenceException; import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.mongodb.CodecRegistryProvider; +import org.springframework.data.mongodb.MongoExpression; import org.springframework.data.mongodb.core.MappedDocument.MappedUpdate; import org.springframework.data.mongodb.core.aggregation.Aggregation; +import org.springframework.data.mongodb.core.aggregation.AggregationExpression; import org.springframework.data.mongodb.core.aggregation.AggregationOperationContext; +import org.springframework.data.mongodb.core.aggregation.AggregationOptions; +import org.springframework.data.mongodb.core.aggregation.AggregationPipeline; import org.springframework.data.mongodb.core.aggregation.AggregationUpdate; import org.springframework.data.mongodb.core.aggregation.RelaxedTypeBasedAggregationOperationContext; +import org.springframework.data.mongodb.core.aggregation.TypeBasedAggregationOperationContext; +import org.springframework.data.mongodb.core.aggregation.TypedAggregation; import org.springframework.data.mongodb.core.convert.QueryMapper; import org.springframework.data.mongodb.core.convert.UpdateMapper; import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity; @@ -48,6 +55,7 @@ import org.springframework.data.mongodb.core.query.UpdateDefinition.ArrayFilter; import org.springframework.data.mongodb.util.BsonUtils; import org.springframework.data.projection.ProjectionFactory; +import org.springframework.data.util.Lazy; import org.springframework.lang.Nullable; import org.springframework.util.ClassUtils; import org.springframework.util.ObjectUtils; @@ -194,9 +202,34 @@ DeleteContext deleteSingleContext(Query query) { return new DeleteContext(query, false); } + /** + * Create a new {@link AggregationDefinition} for the given {@link Aggregation}. + * + * @param aggregation must not be {@literal null}. + * @param inputType fallback mapping type in case of untyped aggregation. Can be {@literal null}. + * @return new instance of {@link AggregationDefinition}. + * @since 3.2 + */ + AggregationDefinition createAggregation(Aggregation aggregation, @Nullable Class inputType) { + return new AggregationDefinition(aggregation, inputType); + } + + /** + * Create a new {@link AggregationDefinition} for the given {@link Aggregation}. + * + * @param aggregation must not be {@literal null}. + * @param aggregationOperationContext the {@link AggregationOperationContext} to use. Can be {@literal null}. + * @return new instance of {@link AggregationDefinition}. + * @since 3.2 + */ + AggregationDefinition createAggregation(Aggregation aggregation, + @Nullable AggregationOperationContext aggregationOperationContext) { + return new AggregationDefinition(aggregation, aggregationOperationContext); + } + /** * {@link QueryContext} encapsulates common tasks required to convert a {@link Query} into its MongoDB document - * representation, mapping fieldnames, as well as determinging and applying {@link Collation collations}. + * representation, mapping field names, as well as determining and applying {@link Collation collations}. * * @author Christoph Strobl */ @@ -205,7 +238,7 @@ class QueryContext { private final Query query; /** - * Create new a {@link QueryContext} instance from the given {@literal query} (can be eihter a {@link Query} or a + * Create new a {@link QueryContext} instance from the given {@literal query} (can be either a {@link Query} or a * plain {@link Document}. * * @param query can be {@literal null}. @@ -258,7 +291,21 @@ Document getMappedQuery(@Nullable MongoPersistentEntity entity) { Document getMappedFields(@Nullable MongoPersistentEntity entity, Class targetType, ProjectionFactory projectionFactory) { - Document fields = query.getFieldsObject(); + Document fields = new Document(); + + for (Entry entry : query.getFieldsObject().entrySet()) { + + if (entry.getValue() instanceof MongoExpression) { + + AggregationOperationContext ctx = entity == null ? Aggregation.DEFAULT_CONTEXT + : new RelaxedTypeBasedAggregationOperationContext(entity.getType(), mappingContext, queryMapper); + + fields.put(entry.getKey(), AggregationExpression.from((MongoExpression) entry.getValue()).toDocument(ctx)); + } else { + fields.put(entry.getKey(), entry.getValue()); + } + } + Document mappedFields = fields; if (entity == null) { @@ -275,7 +322,7 @@ Document getMappedFields(@Nullable MongoPersistentEntity entity, Class tar mappingContext.getRequiredPersistentEntity(targetType)); } - if (entity != null && entity.hasTextScoreProperty() && !query.getQueryObject().containsKey("$text")) { + if (entity.hasTextScoreProperty() && !query.getQueryObject().containsKey("$text")) { mappedFields.remove(entity.getTextScoreProperty().getFieldName()); } @@ -341,7 +388,8 @@ private DistinctQueryContext(@Nullable Object query, String fieldName) { } @Override - Document getMappedFields(@Nullable MongoPersistentEntity entity, Class targetType, ProjectionFactory projectionFactory) { + Document getMappedFields(@Nullable MongoPersistentEntity entity, Class targetType, + ProjectionFactory projectionFactory) { return getMappedFields(entity); } @@ -565,7 +613,7 @@ class UpdateContext extends QueryContext { UpdateContext(MappedDocument update, boolean upsert) { - super(new BasicQuery(new Document(BsonUtils.asMap(update.getIdFilter())))); + super(new BasicQuery(BsonUtils.asDocument(update.getIdFilter()))); this.multi = false; this.upsert = upsert; this.mappedDocument = update; @@ -658,7 +706,8 @@ Document applyShardKey(MongoPersistentEntity domainType, Document filter, : mappedDocument != null ? mappedDocument.getDocument() : getMappedUpdate(domainType); Document filterWithShardKey = new Document(filter); - getMappedShardKeyFields(domainType).forEach(key -> filterWithShardKey.putIfAbsent(key, shardKeySource.get(key))); + getMappedShardKeyFields(domainType) + .forEach(key -> filterWithShardKey.putIfAbsent(key, BsonUtils.resolveValue(shardKeySource, key))); return filterWithShardKey; } @@ -707,10 +756,10 @@ Document getMappedShardKey(MongoPersistentEntity entity) { */ List getUpdatePipeline(@Nullable Class domainType) { - AggregationOperationContext context = domainType != null - ? new RelaxedTypeBasedAggregationOperationContext(domainType, mappingContext, queryMapper) - : Aggregation.DEFAULT_CONTEXT; + Class type = domainType != null ? domainType : Object.class; + AggregationOperationContext context = new RelaxedTypeBasedAggregationOperationContext(type, mappingContext, + queryMapper); return aggregationUtil.createPipeline((AggregationUpdate) update, context); } @@ -760,4 +809,105 @@ boolean isMulti() { 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; + private final Lazy> 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.
        + * Creates a new {@link AggregationOperationContext} if none given, based on the {@link Aggregation} input type and + * the desired {@link AggregationOptions#getDomainTypeMapping() domain type mapping}.
        + * 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.
        + * Creates a new {@link AggregationOperationContext} based on the {@link Aggregation} input type and the desired + * {@link AggregationOptions#getDomainTypeMapping() domain type mapping}.
        + * 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 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; + } + } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveAggregationOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveAggregationOperation.java index e2a4743a40..709f940b98 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveAggregationOperation.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveAggregationOperation.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2020 the original author or authors. + * Copyright 2017-2022 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. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveAggregationOperationSupport.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveAggregationOperationSupport.java index ce512b2615..01165bb996 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveAggregationOperationSupport.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveAggregationOperationSupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2020 the original author or authors. + * Copyright 2017-2022 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. @@ -15,10 +15,6 @@ */ 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; @@ -62,15 +58,22 @@ public ReactiveAggregation aggregateAndReturn(Class domainType) { return new ReactiveAggregationSupport<>(template, domainType, null, null); } - @RequiredArgsConstructor - @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) static class ReactiveAggregationSupport implements AggregationOperationWithAggregation, ReactiveAggregation, TerminatingAggregationOperation { - @NonNull ReactiveMongoTemplate template; - @NonNull Class domainType; - Aggregation aggregation; - String collection; + private final ReactiveMongoTemplate template; + private final Class domainType; + private final Aggregation aggregation; + private final String collection; + + ReactiveAggregationSupport(ReactiveMongoTemplate template, Class domainType, Aggregation aggregation, + String collection) { + + this.template = template; + this.domainType = domainType; + this.aggregation = aggregation; + this.collection = collection; + } /* * (non-Javadoc) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveChangeStreamOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveChangeStreamOperation.java index eab988c5ab..fe73abc9c6 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveChangeStreamOperation.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveChangeStreamOperation.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 the original author or authors. + * Copyright 2019-2022 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. @@ -71,7 +71,7 @@ interface TerminatingChangeStream { /** * Start listening to changes. The stream will not be completed unless the {@link org.reactivestreams.Subscription} * is {@link org.reactivestreams.Subscription#cancel() canceled}. - *

        + *
        * However, the stream may become dead, or invalid, if all watched collections, databases are dropped. */ Flux> listen(); diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveChangeStreamOperationSupport.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveChangeStreamOperationSupport.java index 691a6e256c..978e16622a 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveChangeStreamOperationSupport.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveChangeStreamOperationSupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 the original author or authors. + * Copyright 2019-2022 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. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveCollectionCallback.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveCollectionCallback.java index f59ed5e60e..4e9cd08694 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveCollectionCallback.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveCollectionCallback.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2020 the original author or authors. + * Copyright 2016-2022 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. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveDatabaseCallback.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveDatabaseCallback.java index cc36e0358b..13160feaf2 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveDatabaseCallback.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveDatabaseCallback.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2020 the original author or authors. + * Copyright 2016-2022 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. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveFindOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveFindOperation.java index d9cdb3f257..e668ad4ed5 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveFindOperation.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveFindOperation.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2020 the original author or authors. + * Copyright 2017-2022 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. @@ -91,10 +91,10 @@ interface TerminatingFind { * Get all matching elements using a {@link com.mongodb.CursorType#TailableAwait tailable cursor}. The stream will * not be completed unless the {@link org.reactivestreams.Subscription} is * {@link org.reactivestreams.Subscription#cancel() canceled}. - *

        + *
        * However, the stream may become dead, or invalid, if either the query returns no match or the cursor returns the * document at the "end" of the collection and then the application deletes that document. - *

        + *
        * A stream that is no longer in use must be {@link reactor.core.Disposable#dispose()} disposed} otherwise the * streams will linger and exhaust resources.
        * NOTE: Requires a capped collection. @@ -106,6 +106,12 @@ interface TerminatingFind { /** * Get the number of matching elements. + *
        + * 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}. */ diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveFindOperationSupport.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveFindOperationSupport.java index b05204a7eb..4a182b2507 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveFindOperationSupport.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveFindOperationSupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2020 the original author or authors. + * Copyright 2017-2022 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. @@ -15,15 +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; 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; @@ -39,12 +34,15 @@ * @author Christoph Strobl * @since 2.0 */ -@RequiredArgsConstructor class ReactiveFindOperationSupport implements ReactiveFindOperation { private static final Query ALL_QUERY = new Query(); - private final @NonNull ReactiveMongoTemplate template; + private final ReactiveMongoTemplate template; + + ReactiveFindOperationSupport(ReactiveMongoTemplate template) { + this.template = template; + } /* * (non-Javadoc) @@ -64,16 +62,24 @@ public ReactiveFind query(Class domainType) { * @author Christoph Strobl * @since 2.0 */ - @RequiredArgsConstructor - @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) static class ReactiveFindSupport implements ReactiveFind, FindWithCollection, FindWithProjection, FindWithQuery { - @NonNull ReactiveMongoTemplate template; - @NonNull Class domainType; - Class returnType; - String collection; - Query query; + private final ReactiveMongoTemplate template; + private final Class domainType; + private final Class returnType; + private final String collection; + private final Query query; + + ReactiveFindSupport(ReactiveMongoTemplate template, Class domainType, Class returnType, + String collection, Query query) { + + this.template = template; + this.domainType = domainType; + this.returnType = returnType; + this.collection = collection; + this.query = query; + } /* * (non-Javadoc) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveFluentMongoOperations.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveFluentMongoOperations.java index 631568e4a3..3b927bc194 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveFluentMongoOperations.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveFluentMongoOperations.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2020 the original author or authors. + * Copyright 2017-2022 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. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveInsertOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveInsertOperation.java index c9801da417..c2e661b927 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveInsertOperation.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveInsertOperation.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2020 the original author or authors. + * Copyright 2017-2022 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. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveInsertOperationSupport.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveInsertOperationSupport.java index e0fc029bb6..5931cb8986 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveInsertOperationSupport.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveInsertOperationSupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2020 the original author or authors. + * Copyright 2017-2022 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. @@ -15,10 +15,6 @@ */ 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; @@ -34,10 +30,13 @@ * @author Christoph Strobl * @since 2.0 */ -@RequiredArgsConstructor class ReactiveInsertOperationSupport implements ReactiveInsertOperation { - private final @NonNull ReactiveMongoTemplate template; + private final ReactiveMongoTemplate template; + + ReactiveInsertOperationSupport(ReactiveMongoTemplate template) { + this.template = template; + } /* * (non-Javadoc) @@ -51,13 +50,18 @@ public ReactiveInsert insert(Class domainType) { return new ReactiveInsertSupport<>(template, domainType, null); } - @RequiredArgsConstructor - @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) static class ReactiveInsertSupport implements ReactiveInsert { - @NonNull ReactiveMongoTemplate template; - @NonNull Class domainType; - String collection; + private final ReactiveMongoTemplate template; + private final Class domainType; + private final String collection; + + ReactiveInsertSupport(ReactiveMongoTemplate template, Class domainType, String collection) { + + this.template = template; + this.domainType = domainType; + this.collection = collection; + } /* * (non-Javadoc) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMapReduceOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMapReduceOperation.java index 34a41cdc56..157e389fe1 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMapReduceOperation.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMapReduceOperation.java @@ -1,5 +1,5 @@ /* - * Copyright 2018-2020 the original author or authors. + * Copyright 2018-2022 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. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMapReduceOperationSupport.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMapReduceOperationSupport.java index 4f9d5669fc..d6ec0b3ec0 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMapReduceOperationSupport.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMapReduceOperationSupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2018-2020 the original author or authors. + * Copyright 2018-2022 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. @@ -15,8 +15,6 @@ */ 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; @@ -31,12 +29,15 @@ * @author Christoph Strobl * @since 2.1 */ -@RequiredArgsConstructor class ReactiveMapReduceOperationSupport implements ReactiveMapReduceOperation { private static final Query ALL_QUERY = new Query(); - private final @NonNull ReactiveMongoTemplate template; + private final ReactiveMongoTemplate template; + + ReactiveMapReduceOperationSupport(ReactiveMongoTemplate template) { + this.template = template; + } /* * (non-Javascript) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoClientFactoryBean.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoClientFactoryBean.java index c58292673c..4d828bd9b2 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoClientFactoryBean.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoClientFactoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2020 the original author or authors. + * Copyright 2016-2022 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. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoClientSettingsFactoryBean.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoClientSettingsFactoryBean.java index c62bd5c54f..62e78470c1 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoClientSettingsFactoryBean.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoClientSettingsFactoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2020 the original author or authors. + * Copyright 2016-2022 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. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoContext.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoContext.java index 007cdeb7b5..b648fa68a0 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoContext.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2018-2020 the original author or authors. + * Copyright 2018-2022 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. @@ -15,11 +15,15 @@ */ 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; /** @@ -29,7 +33,7 @@ * @author Christoph Strobl * @author Mark Paluch * @since 2.1 - * @see Mono#subscriberContext() + * @see Mono#deferContextual(Function) * @see Context */ public class ReactiveMongoContext { @@ -46,8 +50,14 @@ public class ReactiveMongoContext { */ public static Mono getSession() { - return Mono.subscriberContext().filter(ctx -> ctx.hasKey(SESSION_KEY)) - .flatMap(ctx -> ctx.> get(SESSION_KEY)); + return Mono.deferContextual(ctx -> { + + if (ctx.hasKey(SESSION_KEY)) { + return ctx.> get(SESSION_KEY); + } + + return Mono.empty(); + }); } /** diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoOperations.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoOperations.java index 9bf4514b33..72a7253a73 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoOperations.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoOperations.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2020 the original author or authors. + * Copyright 2016-2022 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. @@ -59,7 +59,7 @@ * Implemented by {@link ReactiveMongoTemplate}. Not often used but a useful option for extensibility and testability * (as it can be easily mocked, stubbed, or be the target of a JDK proxy). Command execution using * {@link ReactiveMongoOperations} is deferred until subscriber subscribes to the {@link Publisher}. - *

        + *
        * NOTE: Some operations cannot be executed within a MongoDB transaction. Please refer to the MongoDB * specific documentation to learn more about Multi * Document Transactions. @@ -121,7 +121,7 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations { /** * Executes a {@link ReactiveDatabaseCallback} translating any exceptions as necessary. - *

        + *
        * Allows for returning a result object, that is a domain object or a collection of domain objects. * * @param action callback object that specifies the MongoDB actions to perform on the passed in DB instance. Must not @@ -133,7 +133,7 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations { /** * Executes the given {@link ReactiveCollectionCallback} on the entity collection of the specified class. - *

        + *
        * Allows for returning a result object, that is a domain object or a collection of domain objects. * * @param entityClass class that determines the collection to use. Must not be {@literal null}. @@ -145,7 +145,7 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations { /** * Executes the given {@link ReactiveCollectionCallback} on the collection of the given name. - *

        + *
        * Allows for returning a result object, that is a domain object or a collection of domain objects. * * @param collectionName the name of the collection that specifies which {@link MongoCollection} instance will be @@ -159,7 +159,7 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations { /** * Obtain a {@link ClientSession session} bound instance of {@link SessionScoped} binding the {@link ClientSession} * provided by the given {@link Supplier} to each and every command issued against MongoDB. - *

        + *
        * Note: It is up to the caller to manage the {@link ClientSession} lifecycle. Use * {@link ReactiveSessionScoped#execute(ReactiveSessionCallback, Consumer)} to provide a hook for processing the * {@link ClientSession} when done. @@ -178,7 +178,7 @@ default ReactiveSessionScoped withSession(Supplier sessionProvide /** * Obtain a {@link ClientSession session} bound instance of {@link SessionScoped} binding a new {@link ClientSession} * with given {@literal sessionOptions} to each and every command issued against MongoDB. - *

        + *
        * Note: It is up to the caller to manage the {@link ClientSession} lifecycle. Use * {@link ReactiveSessionScoped#execute(ReactiveSessionCallback, Consumer)} to provide a hook for processing the * {@link ClientSession} when done. @@ -192,7 +192,7 @@ default ReactiveSessionScoped withSession(Supplier sessionProvide /** * Obtain a {@link ClientSession session} bound instance of {@link ReactiveSessionScoped} binding the * {@link ClientSession} provided by the given {@link Publisher} to each and every command issued against MongoDB. - *

        + *
        * Note: It is up to the caller to manage the {@link ClientSession} lifecycle. Use * {@link ReactiveSessionScoped#execute(ReactiveSessionCallback, Consumer)} to provide a hook for processing the * {@link ClientSession} when done. @@ -205,7 +205,7 @@ default ReactiveSessionScoped withSession(Supplier sessionProvide /** * Obtain a {@link ClientSession} bound instance of {@link ReactiveMongoOperations}. - *

        + *
        * Note: It is up to the caller to manage the {@link ClientSession} lifecycle. * * @param session must not be {@literal null}. @@ -218,7 +218,7 @@ default ReactiveSessionScoped withSession(Supplier sessionProvide * Initiate a new {@link ClientSession} and obtain a {@link ClientSession session} bound instance of * {@link ReactiveSessionScoped}. Starts the transaction and adds the {@link ClientSession} to each and every command * issued against MongoDB. - *

        + *
        * Each {@link ReactiveSessionScoped#execute(ReactiveSessionCallback) execution} initiates a new managed transaction * that is {@link ClientSession#commitTransaction() committed} on success. Transactions are * {@link ClientSession#abortTransaction() rolled back} upon errors. @@ -233,7 +233,7 @@ default ReactiveSessionScoped withSession(Supplier sessionProvide * Obtain a {@link ClientSession session} bound instance of {@link ReactiveSessionScoped}, start the transaction and * bind the {@link ClientSession} provided by the given {@link Publisher} to each and every command issued against * MongoDB. - *

        + *
        * Each {@link ReactiveSessionScoped#execute(ReactiveSessionCallback) execution} initiates a new managed transaction * that is {@link ClientSession#commitTransaction() committed} on success. Transactions are * {@link ClientSession#abortTransaction() rolled back} upon errors. @@ -293,7 +293,7 @@ Mono> createCollection(Class entityClass, * created on first interaction with the server. Collections can be explicitly created via * {@link #createCollection(Class)}. Please make sure to check if the collection {@link #collectionExists(Class) * exists} first. - *

        + *
        * Translate any exceptions as necessary. * * @param collectionName name of the collection. @@ -303,7 +303,7 @@ Mono> createCollection(Class entityClass, /** * Check to see if a collection with a name indicated by the entity class exists. - *

        + *
        * Translate any exceptions as necessary. * * @param entityClass class that determines the name of the collection. Must not be {@literal null}. @@ -313,7 +313,7 @@ Mono> createCollection(Class entityClass, /** * Check to see if a collection with a given name exists. - *

        + *
        * Translate any exceptions as necessary. * * @param collectionName name of the collection. Must not be {@literal null}. @@ -323,7 +323,7 @@ Mono> createCollection(Class entityClass, /** * Drop the collection with the name indicated by the entity class. - *

        + *
        * Translate any exceptions as necessary. * * @param entityClass class that determines the collection to drop/delete. Must not be {@literal null}. @@ -332,7 +332,7 @@ Mono> createCollection(Class entityClass, /** * Drop the collection with the given name. - *

        + *
        * Translate any exceptions as necessary. * * @param collectionName name of the collection to drop/delete. @@ -341,10 +341,10 @@ Mono> createCollection(Class entityClass, /** * Query for a {@link Flux} of objects of type T from the collection used by the entity class. - *

        + *
        * The object is converted from the MongoDB native representation using an instance of {@see MongoConverter}. Unless * configured otherwise, an instance of {@link MappingMongoConverter} will be used. - *

        + *
        * If your collection does not contain a homogeneous collection of types, this operation will not be an efficient way * to map objects since the test for class type is done in the client and not on the server. * @@ -355,10 +355,10 @@ Mono> createCollection(Class entityClass, /** * Query for a {@link Flux} of objects of type T from the specified collection. - *

        + *
        * The object is converted from the MongoDB native representation using an instance of {@see MongoConverter}. Unless * configured otherwise, an instance of {@link MappingMongoConverter} will be used. - *

        + *
        * If your collection does not contain a homogeneous collection of types, this operation will not be an efficient way * to map objects since the test for class type is done in the client and not on the server. * @@ -371,10 +371,10 @@ Mono> createCollection(Class entityClass, /** * Map the results of an ad-hoc query on the collection for the entity class to a single instance of an object of the * specified type. - *

        + *
        * The object is converted from the MongoDB native representation using an instance of {@see MongoConverter}. Unless * configured otherwise, an instance of {@link MappingMongoConverter} will be used. - *

        + *
        * The query is specified as a {@link Query} which can be created either using the {@link BasicQuery} or the more * feature rich {@link Query}. * @@ -388,10 +388,10 @@ Mono> createCollection(Class entityClass, /** * Map the results of an ad-hoc query on the specified collection to a single instance of an object of the specified * type. - *

        + *
        * The object is converted from the MongoDB native representation using an instance of {@see MongoConverter}. Unless * configured otherwise, an instance of {@link MappingMongoConverter} will be used. - *

        + *
        * The query is specified as a {@link Query} which can be created either using the {@link BasicQuery} or the more * feature rich {@link Query}. * @@ -435,10 +435,10 @@ Mono> createCollection(Class entityClass, /** * Map the results of an ad-hoc query on the collection for the entity class to a {@link Flux} of the specified type. - *

        + *
        * The object is converted from the MongoDB native representation using an instance of {@see MongoConverter}. Unless * configured otherwise, an instance of {@link MappingMongoConverter} will be used. - *

        + *
        * The query is specified as a {@link Query} which can be created either using the {@link BasicQuery} or the more * feature rich {@link Query}. * @@ -451,10 +451,10 @@ Mono> createCollection(Class entityClass, /** * Map the results of an ad-hoc query on the specified collection to a {@link Flux} of the specified type. - *

        + *
        * The object is converted from the MongoDB native representation using an instance of {@see MongoConverter}. Unless * configured otherwise, an instance of {@link MappingMongoConverter} will be used. - *

        + *
        * The query is specified as a {@link Query} which can be created either using the {@link BasicQuery} or the more * feature rich {@link Query}. * @@ -566,10 +566,10 @@ default Flux findDistinct(Query query, String field, String collection, C /** * Execute an aggregation operation. - *

        + *
        * The raw results will be mapped to the given entity class and are returned as stream. The name of the * inputCollection is derived from the {@link TypedAggregation#getInputType() aggregation input type}. - *

        + *
        * Aggregation streaming cannot be used with {@link AggregationOptions#isExplain() aggregation explain} nor with * {@link AggregationOptions#getCursorBatchSize()}. Enabling explanation mode or setting batch size cause * {@link IllegalArgumentException}. @@ -584,10 +584,10 @@ default Flux findDistinct(Query query, String field, String collection, C /** * Execute an aggregation operation. - *

        + *
        * The raw results will be mapped to the given {@code ouputType}. The name of the inputCollection is derived from the * {@code inputType}. - *

        + *
        * Aggregation streaming cannot be used with {@link AggregationOptions#isExplain() aggregation explain} nor with * {@link AggregationOptions#getCursorBatchSize()}. Enabling explanation mode or setting batch size cause * {@link IllegalArgumentException}. @@ -604,9 +604,9 @@ default Flux findDistinct(Query query, String field, String collection, C /** * Execute an aggregation operation. - *

        + *
        * The raw results will be mapped to the given entity class. - *

        + *
        * Aggregation streaming cannot be used with {@link AggregationOptions#isExplain() aggregation explain} nor with * {@link AggregationOptions#getCursorBatchSize()}. Enabling explanation mode or setting batch size cause * {@link IllegalArgumentException}. @@ -676,7 +676,7 @@ default Flux findDistinct(Query query, String field, String collection, C Flux> geoNear(NearQuery near, Class entityClass, String collectionName); /** - * Triggers findAndModify + * Triggers findAndModify * to apply provided {@link Update} on documents matching {@link Criteria} of given {@link Query}. * * @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional @@ -691,7 +691,7 @@ default Flux findDistinct(Query query, String field, String collection, C Mono findAndModify(Query query, UpdateDefinition update, Class entityClass); /** - * Triggers findAndModify + * Triggers findAndModify * to apply provided {@link Update} on documents matching {@link Criteria} of given {@link Query}. * * @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional @@ -707,7 +707,7 @@ default Flux findDistinct(Query query, String field, String collection, C Mono findAndModify(Query query, UpdateDefinition update, Class entityClass, String collectionName); /** - * Triggers findAndModify + * Triggers findAndModify * to apply provided {@link Update} on documents matching {@link Criteria} of given {@link Query} taking * {@link FindAndModifyOptions} into account. * @@ -725,7 +725,7 @@ default Flux findDistinct(Query query, String field, String collection, C Mono findAndModify(Query query, UpdateDefinition update, FindAndModifyOptions options, Class entityClass); /** - * Triggers findAndModify + * Triggers findAndModify * to apply provided {@link Update} on documents matching {@link Criteria} of given {@link Query} taking * {@link FindAndModifyOptions} into account. * @@ -746,7 +746,7 @@ Mono findAndModify(Query query, UpdateDefinition update, FindAndModifyOpt /** * Triggers - * findOneAndReplace + * findOneAndReplace * to replace a single document matching {@link Criteria} of given {@link Query} with the {@code replacement} * document.
        * Options are defaulted to {@link FindAndReplaceOptions#empty()}.
        @@ -764,7 +764,7 @@ default Mono findAndReplace(Query query, T replacement) { /** * Triggers - * findOneAndReplace + * findOneAndReplace * to replace a single document matching {@link Criteria} of given {@link Query} with the {@code replacement} * document.
        * Options are defaulted to {@link FindAndReplaceOptions#empty()}.
        @@ -783,7 +783,7 @@ default Mono findAndReplace(Query query, T replacement, String collection /** * Triggers - * findOneAndReplace + * findOneAndReplace * to replace a single document matching {@link Criteria} of given {@link Query} with the {@code replacement} document * taking {@link FindAndReplaceOptions} into account.
        * NOTE: The replacement entity must not hold an {@literal id}. @@ -803,7 +803,7 @@ default Mono findAndReplace(Query query, T replacement, FindAndReplaceOpt /** * Triggers - * findOneAndReplace + * findOneAndReplace * to replace a single document matching {@link Criteria} of given {@link Query} with the {@code replacement} document * taking {@link FindAndReplaceOptions} into account.
        * NOTE: The replacement entity must not hold an {@literal id}. @@ -825,7 +825,7 @@ default Mono findAndReplace(Query query, T replacement, FindAndReplaceOpt /** * Triggers - * findOneAndReplace + * findOneAndReplace * to replace a single document matching {@link Criteria} of given {@link Query} with the {@code replacement} document * taking {@link FindAndReplaceOptions} into account.
        * NOTE: The replacement entity must not hold an {@literal id}. @@ -849,7 +849,7 @@ default Mono findAndReplace(Query query, T replacement, FindAndReplaceOpt /** * Triggers - * findOneAndReplace + * findOneAndReplace * to replace a single document matching {@link Criteria} of given {@link Query} with the {@code replacement} document * taking {@link FindAndReplaceOptions} into account.
        * NOTE: The replacement entity must not hold an {@literal id}. @@ -876,7 +876,7 @@ default Mono findAndReplace(Query query, S replacement, FindAndReplace /** * Triggers - * findOneAndReplace + * findOneAndReplace * to replace a single document matching {@link Criteria} of given {@link Query} with the {@code replacement} document * taking {@link FindAndReplaceOptions} into account.
        * NOTE: The replacement entity must not hold an {@literal id}. @@ -902,9 +902,9 @@ Mono findAndReplace(Query query, S replacement, FindAndReplaceOptions * Map the results of an ad-hoc query on the collection for the entity type to a single instance of an object of the * specified type. The first document that matches the query is returned and also removed from the collection in the * database. - *

        + *
        * The object is converted from the MongoDB native representation using an instance of {@see MongoConverter}. - *

        + *
        * The query is specified as a {@link Query} which can be created either using the {@link BasicQuery} or the more * feature rich {@link Query}. * @@ -918,10 +918,10 @@ Mono findAndReplace(Query query, S replacement, FindAndReplaceOptions /** * Map the results of an ad-hoc query on the specified collection to a single instance of an object of the specified * type. The first document that matches the query is returned and also removed from the collection in the database. - *

        + *
        * The object is converted from the MongoDB native representation using an instance of {@see MongoConverter}. Unless * configured otherwise, an instance of {@link MappingMongoConverter} will be used. - *

        + *
        * The query is specified as a {@link Query} which can be created either using the {@link BasicQuery} or the more * feature rich {@link Query}. * @@ -940,6 +940,12 @@ Mono findAndReplace(Query query, S replacement, FindAndReplaceOptions * 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. + *
        + * This method uses an + * {@link com.mongodb.reactivestreams.client.MongoCollection#countDocuments(org.bson.conversions.Bson, com.mongodb.client.model.CountOptions) + * aggregation execution} even for empty {@link Query queries} which may have an impact on performance, but guarantees + * shard, session and transaction compliance. In case an inaccurate count satisfies the applications needs use + * {@link #estimatedCount(Class)} for empty queries instead. * * @param query the {@link Query} class that specifies the criteria used to find documents. Must not be * {@literal null}. @@ -956,6 +962,12 @@ Mono findAndReplace(Query query, S replacement, FindAndReplaceOptions * 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. + *
        + * This method uses an + * {@link com.mongodb.reactivestreams.client.MongoCollection#countDocuments(org.bson.conversions.Bson, com.mongodb.client.model.CountOptions) + * aggregation execution} even for empty {@link Query queries} which may have an impact on performance, but guarantees + * shard, session and transaction compliance. In case an inaccurate count satisfies the applications needs use + * {@link #estimatedCount(String)} for empty queries instead. * * @param query the {@link Query} class that specifies the criteria used to find documents. * @param collectionName must not be {@literal null} or empty. @@ -971,6 +983,12 @@ Mono findAndReplace(Query query, S replacement, FindAndReplaceOptions * 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. + *
        + * 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}. @@ -980,36 +998,70 @@ Mono findAndReplace(Query query, S replacement, FindAndReplaceOptions */ Mono 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. + *
        + * 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 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. + *
        + * 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 estimatedCount(String collectionName); + /** * Insert the object into the collection for the entity type of the object to save. - *

        + *
        * The object is converted to the MongoDB native representation using an instance of {@see MongoConverter}. - *

        + *
        * If your object has an "Id' property, it will be set with the generated Id from MongoDB. If your Id property is a * String then MongoDB ObjectId will be used to populate that string. Otherwise, the conversion from ObjectId to your * property type will be handled by Spring's BeanWrapper class that leverages Type Conversion API. See * Spring's * Type Conversion" for more details. - *

        - *

        + *
        * Insert is used to initially store the object into the database. To update an existing object use the save method. + *
        + * 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. */ Mono insert(T objectToSave); /** * Insert the object into the specified collection. - *

        + *
        * The object is converted to the MongoDB native representation using an instance of {@see MongoConverter}. Unless * configured otherwise, an instance of {@link MappingMongoConverter} will be used. - *

        + *
        * Insert is used to initially store the object into the database. To update an existing object use the save method. + *
        + * 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. */ Mono insert(T objectToSave, String collectionName); @@ -1042,16 +1094,15 @@ Mono findAndReplace(Query query, S replacement, FindAndReplaceOptions /** * Insert the object into the collection for the entity type of the object to save. - *

        + *
        * The object is converted to the MongoDB native representation using an instance of {@see MongoConverter}. - *

        + *
        * If your object has an "Id' property, it will be set with the generated Id from MongoDB. If your Id property is a * String then MongoDB ObjectId will be used to populate that string. Otherwise, the conversion from ObjectId to your * property type will be handled by Spring's BeanWrapper class that leverages Type Conversion API. See * Spring's * Type Conversion" for more details. - *

        - *

        + *
        * 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}. @@ -1089,52 +1140,54 @@ Mono findAndReplace(Query query, S replacement, FindAndReplaceOptions /** * Save the object to the collection for the entity type of the object to save. This will perform an insert if the * object is not already present, that is an 'upsert'. - *

        + *
        * The object is converted to the MongoDB native representation using an instance of {@see MongoConverter}. Unless * configured otherwise, an instance of {@link MappingMongoConverter} will be used. - *

        + *
        * If your object has an "Id' property, it will be set with the generated Id from MongoDB. If your Id property is a * String then MongoDB ObjectId will be used to populate that string. Otherwise, the conversion from ObjectId to your * property type will be handled by Spring's BeanWrapper class that leverages Type Conversion API. See * Spring's * Type Conversion" for more details. + *
        + * 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. */ Mono save(T objectToSave); /** * Save the object to the specified collection. This will perform an insert if the object is not already present, that * is an 'upsert'. - *

        + *
        * The object is converted to the MongoDB native representation using an instance of {@see MongoConverter}. Unless * configured otherwise, an instance of {@link MappingMongoConverter} will be used. - *

        + *
        * If your object has an "Id' property, it will be set with the generated Id from MongoDB. If your Id property is a * String then MongoDB ObjectId will be used to populate that string. Otherwise, the conversion from ObjectId to your - * property type will be handled by Spring's BeanWrapper class that leverages Type Conversion API. See Spring's Type - * Conversion" for more details. + * property type will be handled by Spring's BeanWrapper class that leverages Type Conversion API. + * See Spring's Type Conversion for more details. * * @param objectToSave the object to store in the collection. Must not be {@literal null}. * @param collectionName name of the collection to store the object in. Must not be {@literal null}. * @return the saved object. + * @throws IllegalArgumentException in case the {@code objectToSave} is collection-like. */ Mono save(T objectToSave, String collectionName); /** * Save the object to the collection for the entity type of the object to save. This will perform an insert if the * object is not already present, that is an 'upsert'. - *

        + *
        * The object is converted to the MongoDB native representation using an instance of {@see MongoConverter}. Unless * configured otherwise, an instance of {@link MappingMongoConverter} will be used. - *

        + *
        * If your object has an "Id' property, it will be set with the generated Id from MongoDB. If your Id property is a * String then MongoDB ObjectId will be used to populate that string. Otherwise, the conversion from ObjectId to your - * property type will be handled by Spring's BeanWrapper class that leverages Type Conversion API. See - * Spring's - * Type Conversion" for more details. + * property type will be handled by Spring's BeanWrapper class that leverages Type Conversion API. + * See Spring's Type Conversion for more details. * * @param objectToSave the object to store in the collection. Must not be {@literal null}. * @return the saved object. @@ -1144,17 +1197,16 @@ Mono findAndReplace(Query query, S replacement, FindAndReplaceOptions /** * Save the object to the specified collection. This will perform an insert if the object is not already present, that * is an 'upsert'. - *

        + *
        * The object is converted to the MongoDB native representation using an instance of {@see MongoConverter}. Unless * configured otherwise, an instance of {@link MappingMongoConverter} will be used. - *

        + *
        * If your object has an "Id' property, it will be set with the generated Id from MongoDB. If your Id property is a * String then MongoDB ObjectId will be used to populate that string. Otherwise, the conversion from ObjectId to your - * property type will be handled by Spring's BeanWrapper class that leverages Type Conversion API. See Spring's Type - * Conversion" for more details. + * property type will be handled by Spring's BeanWrapper class that leverages Type Conversion API. + * See Spring's Type Conversion for more details. * - * @param objectToSave the object to store in the collection. Must not be {@literal null}. + * @param objectToSave the object to store in the collReactiveMongoOperationsection. Must not be {@literal null}. * @param collectionName name of the collection to store the object in. Must not be {@literal null}. * @return the saved object. */ @@ -1426,10 +1478,10 @@ Mono findAndReplace(Query query, S replacement, FindAndReplaceOptions * type. The stream uses a {@link com.mongodb.CursorType#TailableAwait tailable} cursor that may be an infinite * stream. The stream will not be completed unless the {@link org.reactivestreams.Subscription} is * {@link Subscription#cancel() canceled}. - *

        + *
        * The object is converted from the MongoDB native representation using an instance of {@see MongoConverter}. Unless * configured otherwise, an instance of {@link MappingMongoConverter} will be used. - *

        + *
        * The query is specified as a {@link Query} which can be created either using the {@link BasicQuery} or the more * feature rich {@link Query}. * @@ -1445,10 +1497,10 @@ Mono findAndReplace(Query query, S replacement, FindAndReplaceOptions * type. The stream uses a {@link com.mongodb.CursorType#TailableAwait tailable} cursor that may be an infinite * stream. The stream will not be completed unless the {@link org.reactivestreams.Subscription} is * {@link Subscription#cancel() canceled}. - *

        + *
        * The object is converted from the MongoDB native representation using an instance of {@see MongoConverter}. Unless * configured otherwise, an instance of {@link MappingMongoConverter} will be used. - *

        + *
        * The query is specified as a {@link Query} which can be created either using the {@link BasicQuery} or the more * feature rich {@link Query}. * @@ -1465,10 +1517,10 @@ Mono findAndReplace(Query query, S replacement, FindAndReplaceOptions * the configured default database via the reactive infrastructure. Use the optional provided {@link Aggregation} to * filter events. The stream will not be completed unless the {@link org.reactivestreams.Subscription} is * {@link Subscription#cancel() canceled}. - *

        + *
        * The {@link ChangeStreamEvent#getBody()} is mapped to the {@literal resultType} while the * {@link ChangeStreamEvent#getRaw()} contains the unmodified payload. - *

        + *
        * Use {@link ChangeStreamOptions} to set arguments like {@link ChangeStreamOptions#getResumeToken() the resumseToken} * for resuming change streams. * @@ -1489,10 +1541,10 @@ default Flux> changeStream(ChangeStreamOptions options, * the given collection via the reactive infrastructure. Use the optional provided {@link Aggregation} to filter * events. The stream will not be completed unless the {@link org.reactivestreams.Subscription} is * {@link Subscription#cancel() canceled}. - *

        + *
        * The {@link ChangeStreamEvent#getBody()} is mapped to the {@literal resultType} while the * {@link ChangeStreamEvent#getRaw()} contains the unmodified payload. - *

        + *
        * Use {@link ChangeStreamOptions} to set arguments like {@link ChangeStreamOptions#getResumeToken() the resumseToken} * for resuming change streams. * @@ -1514,10 +1566,10 @@ default Flux> changeStream(@Nullable String collectionN * Subscribe to a MongoDB Change Stream via the reactive * infrastructure. Use the optional provided {@link Aggregation} to filter events. The stream will not be completed * unless the {@link org.reactivestreams.Subscription} is {@link Subscription#cancel() canceled}. - *

        + *
        * The {@link ChangeStreamEvent#getBody()} is mapped to the {@literal resultType} while the * {@link ChangeStreamEvent#getRaw()} contains the unmodified payload. - *

        + *
        * Use {@link ChangeStreamOptions} to set arguments like {@link ChangeStreamOptions#getResumeToken() the resumseToken} * for resuming change streams. * diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoTemplate.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoTemplate.java index 9bfcc533f7..b173bea7f1 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoTemplate.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoTemplate.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2020 the original author or authors. + * Copyright 2016-2022 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. @@ -17,15 +17,20 @@ 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.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Optional; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; import java.util.function.Function; @@ -39,6 +44,7 @@ import org.reactivestreams.Subscriber; import org.slf4j.Logger; import org.slf4j.LoggerFactory; + import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; @@ -54,6 +60,7 @@ 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; @@ -63,6 +70,7 @@ 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; @@ -72,6 +80,7 @@ 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; @@ -101,6 +110,7 @@ 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; @@ -119,16 +129,7 @@ import com.mongodb.MongoException; import com.mongodb.ReadPreference; import com.mongodb.WriteConcern; -import com.mongodb.client.model.CountOptions; -import com.mongodb.client.model.CreateCollectionOptions; -import com.mongodb.client.model.DeleteOptions; -import com.mongodb.client.model.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.*; import com.mongodb.client.model.changestream.FullDocument; import com.mongodb.client.result.DeleteResult; import com.mongodb.client.result.InsertOneResult; @@ -157,6 +158,7 @@ * @author Christoph Strobl * @author Roman Puchkovskiy * @author Mathieu Ouellet + * @author Yadhukrishna S Pai * @since 2.0 */ public class ReactiveMongoTemplate implements ReactiveMongoOperations, ApplicationContextAware { @@ -165,18 +167,6 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati private static final Logger LOGGER = LoggerFactory.getLogger(ReactiveMongoTemplate.class); private static final WriteResultChecking DEFAULT_WRITE_RESULT_CHECKING = WriteResultChecking.NONE; - private static final Collection> ITERABLE_CLASSES; - - static { - - Set> 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, MongoPersistentProperty> mappingContext; @@ -350,6 +340,7 @@ public void setReadPreference(ReadPreference readPreference) { * (non-Javadoc) * @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext) */ + @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { prepareIndexCreator(applicationContext); @@ -372,7 +363,7 @@ public void setApplicationContext(ApplicationContext applicationContext) throws * Set the {@link ReactiveEntityCallbacks} instance to use when invoking * {@link org.springframework.data.mapping.callback.EntityCallback callbacks} like the * {@link ReactiveBeforeSaveCallback}. - *

        + *
        * Overrides potentially existing {@link ReactiveEntityCallbacks}. * * @param entityCallbacks must not be {@literal null}. @@ -416,6 +407,7 @@ private void prepareIndexCreator(ApplicationContext context) { * * @return */ + @Override public MongoConverter getConverter() { return this.mongoConverter; } @@ -424,6 +416,7 @@ public MongoConverter getConverter() { * (non-Javadoc) * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#reactiveIndexOps(java.lang.String) */ + @Override public ReactiveIndexOperations indexOps(String collectionName) { return new DefaultReactiveIndexOperations(this, collectionName, this.queryMapper); } @@ -432,10 +425,12 @@ public ReactiveIndexOperations indexOps(String collectionName) { * (non-Javadoc) * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#reactiveIndexOps(java.lang.Class) */ + @Override public ReactiveIndexOperations indexOps(Class entityClass) { return new DefaultReactiveIndexOperations(this, getCollectionName(entityClass), this.queryMapper, entityClass); } + @Override public String getCollectionName(Class entityClass) { return operations.determineCollectionName(entityClass); } @@ -444,6 +439,7 @@ public String getCollectionName(Class entityClass) { * (non-Javadoc) * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#executeCommand(java.lang.String) */ + @Override public Mono executeCommand(String jsonCommand) { Assert.notNull(jsonCommand, "Command must not be empty!"); @@ -455,6 +451,7 @@ public Mono executeCommand(String jsonCommand) { * (non-Javadoc) * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#executeCommand(org.bson.Document) */ + @Override public Mono executeCommand(Document command) { return executeCommand(command, null); } @@ -463,6 +460,7 @@ public Mono executeCommand(Document command) { * (non-Javadoc) * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#executeCommand(org.bson.Document, com.mongodb.ReadPreference) */ + @Override public Mono executeCommand(Document command, @Nullable ReadPreference readPreference) { Assert.notNull(command, "Command must not be null!"); @@ -493,6 +491,7 @@ public Flux execute(ReactiveDatabaseCallback action) { * (non-Javadoc) * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#execute(java.lang.String, org.springframework.data.mongodb.core.ReactiveCollectionCallback) */ + @Override public Flux execute(String collectionName, ReactiveCollectionCallback callback) { Assert.notNull(callback, "ReactiveCollectionCallback must not be null!"); @@ -583,13 +582,14 @@ private Flux withSession(ReactiveSessionCallback action, ClientSession ReactiveMongoTemplate.this); return Flux.from(action.doInSession(operations)) // - .subscriberContext(ctx -> ReactiveMongoContext.setSession(ctx, Mono.just(session))); + .contextWrite(ctx -> ReactiveMongoContext.setSession(ctx, Mono.just(session))); } /* * (non-Javadoc) * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#withSession(com.mongodb.session.ClientSession) */ + @Override public ReactiveMongoOperations withSession(ClientSession session) { return new ReactiveSessionBoundMongoTemplate(session, ReactiveMongoTemplate.this); } @@ -675,14 +675,16 @@ public Mono createMono(String collectionName, ReactiveCollectionCallback< * (non-Javadoc) * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#createCollection(java.lang.Class) */ + @Override public Mono> createCollection(Class entityClass) { - return createCollection(entityClass, CollectionOptions.empty()); + return createCollection(entityClass, operations.forType(entityClass).getCollectionOptions()); } /* * (non-Javadoc) * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#createCollection(java.lang.Class, org.springframework.data.mongodb.core.CollectionOptions) */ + @Override public Mono> createCollection(Class entityClass, @Nullable CollectionOptions collectionOptions) { @@ -701,6 +703,7 @@ public Mono> createCollection(Class entityClass * (non-Javadoc) * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#createCollection(java.lang.String) */ + @Override public Mono> createCollection(String collectionName) { return doCreateCollection(collectionName, new CreateCollectionOptions()); } @@ -709,6 +712,7 @@ public Mono> createCollection(String collectionName) { * (non-Javadoc) * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#createCollection(java.lang.String, org.springframework.data.mongodb.core.CollectionOptions) */ + @Override public Mono> createCollection(String collectionName, @Nullable CollectionOptions collectionOptions) { return doCreateCollection(collectionName, convertToCreateCollectionOptions(collectionOptions)); @@ -718,6 +722,7 @@ public Mono> createCollection(String collectionName, * (non-Javadoc) * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#getCollection(java.lang.String) */ + @Override public Mono> getCollection(String collectionName) { Assert.notNull(collectionName, "Collection name must not be null!"); @@ -729,6 +734,7 @@ public Mono> getCollection(String collectionName) { * (non-Javadoc) * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#collectionExists(java.lang.Class) */ + @Override public Mono collectionExists(Class entityClass) { return collectionExists(getCollectionName(entityClass)); } @@ -737,6 +743,7 @@ public Mono collectionExists(Class entityClass) { * (non-Javadoc) * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#collectionExists(java.lang.String) */ + @Override public Mono collectionExists(String collectionName) { return createMono(db -> Flux.from(db.listCollectionNames()) // .filter(s -> s.equals(collectionName)) // @@ -748,6 +755,7 @@ public Mono collectionExists(String collectionName) { * (non-Javadoc) * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#dropCollection(java.lang.Class) */ + @Override public Mono dropCollection(Class entityClass) { return dropCollection(getCollectionName(entityClass)); } @@ -756,6 +764,7 @@ public Mono dropCollection(Class entityClass) { * (non-Javadoc) * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#dropCollection(java.lang.String) */ + @Override public Mono dropCollection(String collectionName) { return createMono(collectionName, MongoCollection::drop).doOnSuccess(success -> { @@ -769,6 +778,7 @@ public Mono dropCollection(String collectionName) { * (non-Javadoc) * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#getCollectionNames() */ + @Override public Flux getCollectionNames() { return createFlux(MongoDatabase::listCollectionNames); } @@ -785,6 +795,7 @@ protected Mono doGetDatabase() { * (non-Javadoc) * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#findOne(org.springframework.data.mongodb.core.query.Query, java.lang.Class) */ + @Override public Mono findOne(Query query, Class entityClass) { return findOne(query, entityClass, getCollectionName(entityClass)); } @@ -793,6 +804,7 @@ public Mono findOne(Query query, Class entityClass) { * (non-Javadoc) * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#findOne(org.springframework.data.mongodb.core.query.Query, java.lang.Class, java.lang.String) */ + @Override public Mono findOne(Query query, Class entityClass, String collectionName) { if (ObjectUtils.isEmpty(query.getSortObject())) { @@ -808,6 +820,7 @@ public Mono findOne(Query query, Class entityClass, String collectionN * (non-Javadoc) * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#exists(org.springframework.data.mongodb.core.query.Query, java.lang.Class) */ + @Override public Mono exists(Query query, Class entityClass) { return exists(query, entityClass, getCollectionName(entityClass)); } @@ -816,6 +829,7 @@ public Mono exists(Query query, Class entityClass) { * (non-Javadoc) * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#exists(org.springframework.data.mongodb.core.query.Query, java.lang.String) */ + @Override public Mono exists(Query query, String collectionName) { return exists(query, null, collectionName); } @@ -824,6 +838,7 @@ public Mono exists(Query query, String collectionName) { * (non-Javadoc) * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#exists(org.springframework.data.mongodb.core.query.Query, java.lang.Class, java.lang.String) */ + @Override public Mono exists(Query query, @Nullable Class entityClass, String collectionName) { if (query == null) { @@ -852,6 +867,7 @@ public Mono exists(Query query, @Nullable Class entityClass, String * (non-Javadoc) * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#find(org.springframework.data.mongodb.core.query.Query, java.lang.Class) */ + @Override public Flux find(Query query, Class entityClass) { return find(query, entityClass, getCollectionName(entityClass)); } @@ -860,6 +876,7 @@ public Flux find(Query query, Class entityClass) { * (non-Javadoc) * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#find(org.springframework.data.mongodb.core.query.Query, java.lang.Class, java.lang.String) */ + @Override public Flux find(@Nullable Query query, Class entityClass, String collectionName) { if (query == null) { @@ -874,6 +891,7 @@ public Flux find(@Nullable Query query, Class entityClass, String coll * (non-Javadoc) * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#findById(java.lang.Object, java.lang.Class) */ + @Override public Mono findById(Object id, Class entityClass) { return findById(id, entityClass, getCollectionName(entityClass)); } @@ -882,6 +900,7 @@ public Mono findById(Object id, Class entityClass) { * (non-Javadoc) * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#findById(java.lang.Object, java.lang.Class, java.lang.String) */ + @Override public Mono findById(Object id, Class entityClass, String collectionName) { String idKey = operations.getIdPropertyName(entityClass); @@ -893,6 +912,7 @@ public Mono findById(Object id, Class entityClass, String collectionNa * (non-Javadoc) * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#findDistinct(org.springframework.data.mongodb.core.query.Query, java.lang.String, java.lang.Class, java.lang.Class) */ + @Override public Flux findDistinct(Query query, String field, Class entityClass, Class resultClass) { return findDistinct(query, field, getCollectionName(entityClass), entityClass, resultClass); } @@ -901,6 +921,7 @@ public Flux findDistinct(Query query, String field, Class entityClass, * (non-Javadoc) * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#findDistinct(org.springframework.data.mongodb.core.query.Query, java.lang.String, java.lang.String, java.lang.Class, java.lang.Class) */ + @Override @SuppressWarnings("unchecked") public Flux findDistinct(Query query, String field, String collectionName, Class entityClass, Class resultClass) { @@ -955,9 +976,7 @@ public Flux aggregate(TypedAggregation aggregation, String inputCollec Assert.notNull(aggregation, "Aggregation pipeline must not be null!"); - AggregationOperationContext context = new TypeBasedAggregationOperationContext(aggregation.getInputType(), - mappingContext, queryMapper); - return aggregate(aggregation, inputCollectionName, outputType, context); + return doAggregate(aggregation, inputCollectionName, aggregation.getInputType(), outputType); } /* @@ -975,9 +994,7 @@ public Flux aggregate(TypedAggregation aggregation, Class outputTyp */ @Override public Flux aggregate(Aggregation aggregation, Class inputType, Class outputType) { - - return aggregate(aggregation, getCollectionName(inputType), outputType, - new TypeBasedAggregationOperationContext(inputType, mappingContext, queryMapper)); + return doAggregate(aggregation, getCollectionName(inputType), inputType, outputType); } /* @@ -986,42 +1003,34 @@ public Flux aggregate(Aggregation aggregation, Class inputType, Class< */ @Override public Flux aggregate(Aggregation aggregation, String collectionName, Class outputType) { - return aggregate(aggregation, collectionName, outputType, null); + return doAggregate(aggregation, collectionName, null, outputType); } - /** - * @param aggregation must not be {@literal null}. - * @param collectionName must not be {@literal null}. - * @param outputType must not be {@literal null}. - * @param context can be {@literal null} and will be defaulted to {@link Aggregation#DEFAULT_CONTEXT}. - * @return never {@literal null}. - */ - protected Flux aggregate(Aggregation aggregation, String collectionName, Class outputType, - @Nullable AggregationOperationContext context) { + protected Flux doAggregate(Aggregation aggregation, String collectionName, @Nullable Class inputType, + Class outputType) { Assert.notNull(aggregation, "Aggregation pipeline must not be null!"); Assert.hasText(collectionName, "Collection name must not be null or empty!"); Assert.notNull(outputType, "Output type must not be null!"); - AggregationUtil aggregationUtil = new AggregationUtil(queryMapper, mappingContext); - AggregationOperationContext rootContext = aggregationUtil.prepareAggregationContext(aggregation, context); - AggregationOptions options = aggregation.getOptions(); - List pipeline = aggregationUtil.createPipeline(aggregation, rootContext); - Assert.isTrue(!options.isExplain(), "Cannot use explain option with streaming!"); + AggregationDefinition ctx = queryOperations.createAggregation(aggregation, inputType); + if (LOGGER.isDebugEnabled()) { - LOGGER.debug("Streaming aggregation: {} in collection {}", serializeToJsonSafely(pipeline), collectionName); + LOGGER.debug("Streaming aggregation: {} in collection {}", serializeToJsonSafely(ctx.getAggregationPipeline()), + collectionName); } ReadDocumentCallback readCallback = new ReadDocumentCallback<>(mongoConverter, outputType, collectionName); - return execute(collectionName, collection -> aggregateAndMap(collection, pipeline, options, readCallback, - aggregation instanceof TypedAggregation ? ((TypedAggregation) aggregation).getInputType() : null)); + return execute(collectionName, collection -> aggregateAndMap(collection, ctx.getAggregationPipeline(), + ctx.isOutOrMerge(), options, readCallback, ctx.getInputType())); } private Flux aggregateAndMap(MongoCollection collection, List pipeline, - AggregationOptions options, ReadDocumentCallback readCallback, @Nullable Class inputType) { + boolean isOutOrMerge, AggregationOptions options, ReadDocumentCallback readCallback, + @Nullable Class inputType) { AggregatePublisher cursor = collection.aggregate(pipeline, Document.class) .allowDiskUse(options.isAllowDiskUse()); @@ -1031,6 +1040,7 @@ private Flux aggregateAndMap(MongoCollection collection, List operations.forType(inputType).getCollation()) // .map(Collation::toMongoCollation) // @@ -1040,6 +1050,10 @@ private Flux aggregateAndMap(MongoCollection collection, List Flux> geoNear(NearQuery near, Class entityClass, S * (non-Javadoc) * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#findAndModify(org.springframework.data.mongodb.core.query.Query, org.springframework.data.mongodb.core.query.UpdateDefinition, java.lang.Class) */ + @Override public Mono findAndModify(Query query, UpdateDefinition update, Class entityClass) { return findAndModify(query, update, new FindAndModifyOptions(), entityClass, getCollectionName(entityClass)); } @@ -1098,6 +1113,7 @@ public Mono findAndModify(Query query, UpdateDefinition update, Class * (non-Javadoc) * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#findAndModify(org.springframework.data.mongodb.core.query.Query, org.springframework.data.mongodb.core.query.UpdateDefinition, java.lang.Class, java.lang.String) */ + @Override public Mono findAndModify(Query query, UpdateDefinition update, Class entityClass, String collectionName) { return findAndModify(query, update, new FindAndModifyOptions(), entityClass, collectionName); } @@ -1106,6 +1122,7 @@ public Mono findAndModify(Query query, UpdateDefinition update, Class * (non-Javadoc) * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#findAndModify(org.springframework.data.mongodb.core.query.Query, org.springframework.data.mongodb.core.query.UpdateDefinition, org.springframework.data.mongodb.core.FindAndModifyOptions, java.lang.Class) */ + @Override public Mono findAndModify(Query query, UpdateDefinition update, FindAndModifyOptions options, Class entityClass) { return findAndModify(query, update, options, entityClass, getCollectionName(entityClass)); @@ -1115,6 +1132,7 @@ public Mono findAndModify(Query query, UpdateDefinition update, FindAndMo * (non-Javadoc) * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#findAndModify(org.springframework.data.mongodb.core.query.Query, org.springframework.data.mongodb.core.query.UpdateDefinition, org.springframework.data.mongodb.core.FindAndModifyOptions, java.lang.Class, java.lang.String) */ + @Override public Mono findAndModify(Query query, UpdateDefinition update, FindAndModifyOptions options, Class entityClass, String collectionName) { @@ -1191,6 +1209,7 @@ public Mono findAndReplace(Query query, S replacement, FindAndReplaceO * (non-Javadoc) * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#findAndRemove(org.springframework.data.mongodb.core.query.Query, java.lang.Class) */ + @Override public Mono findAndRemove(Query query, Class entityClass) { return findAndRemove(query, entityClass, getCollectionName(entityClass)); } @@ -1199,6 +1218,7 @@ public Mono findAndRemove(Query query, Class entityClass) { * (non-Javadoc) * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#findAndRemove(org.springframework.data.mongodb.core.query.Query, java.lang.Class, java.lang.String) */ + @Override public Mono findAndRemove(Query query, Class entityClass, String collectionName) { operations.forType(entityClass).getCollation(query); @@ -1211,6 +1231,7 @@ public Mono findAndRemove(Query query, Class entityClass, String colle * (non-Javadoc) * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#count(org.springframework.data.mongodb.core.query.Query, java.lang.Class) */ + @Override public Mono count(Query query, Class entityClass) { Assert.notNull(entityClass, "Entity class must not be null!"); @@ -1222,6 +1243,7 @@ public Mono count(Query query, Class entityClass) { * (non-Javadoc) * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#count(org.springframework.data.mongodb.core.query.Query, java.lang.String) */ + @Override public Mono count(Query query, String collectionName) { return count(query, null, collectionName); } @@ -1230,6 +1252,7 @@ public Mono count(Query query, String collectionName) { * (non-Javadoc) * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#count(org.springframework.data.mongodb.core.query.Query, java.lang.Class, java.lang.String) */ + @Override public Mono count(Query query, @Nullable Class entityClass, String collectionName) { Assert.notNull(query, "Query must not be null!"); @@ -1250,6 +1273,15 @@ public Mono count(Query query, @Nullable Class entityClass, String coll }); } + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#estimatedCount(java.lang.String) + */ + @Override + public Mono estimatedCount(String collectionName) { + return doEstimatedCount(collectionName, new EstimatedDocumentCountOptions()); + } + /** * Run the actual count operation against the collection with given name. * @@ -1264,6 +1296,11 @@ protected Mono doCount(String collectionName, Document filter, CountOption collection -> collection.countDocuments(CountQuery.of(filter).toQueryDocument(), options)); } + protected Mono 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) @@ -1301,6 +1338,7 @@ public Flux insertAll(Mono> batchToSave * (non-Javadoc) * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#insert(java.lang.Object) */ + @Override public Mono insert(T objectToSave) { Assert.notNull(objectToSave, "Object to insert must not be null!"); @@ -1313,6 +1351,7 @@ public Mono insert(T objectToSave) { * (non-Javadoc) * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#insert(java.lang.Object, java.lang.String) */ + @Override public Mono insert(T objectToSave, String collectionName) { Assert.notNull(objectToSave, "Object to insert must not be null!"); @@ -1355,6 +1394,7 @@ protected Mono doInsert(String collectionName, T objectToSave, MongoWrite * (non-Javadoc) * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#insert(java.util.Collection, java.lang.Class) */ + @Override public Flux insert(Collection batchToSave, Class entityClass) { return doInsertBatch(getCollectionName(entityClass), batchToSave, this.mongoConverter); } @@ -1363,6 +1403,7 @@ public Flux insert(Collection batchToSave, Class entityCl * (non-Javadoc) * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#insert(java.util.Collection, java.lang.String) */ + @Override public Flux insert(Collection batchToSave, String collectionName) { return doInsertBatch(collectionName, batchToSave, this.mongoConverter); } @@ -1371,6 +1412,7 @@ public Flux insert(Collection batchToSave, String collection * (non-Javadoc) * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#insertAll(java.util.Collection) */ + @Override public Flux insertAll(Collection objectsToSave) { return doInsertAll(objectsToSave, this.mongoConverter); } @@ -1471,6 +1513,7 @@ public Mono save(Mono objectToSave, String collectionName) { * (non-Javadoc) * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#save(java.lang.Object) */ + @Override public Mono save(T objectToSave) { Assert.notNull(objectToSave, "Object to save must not be null!"); @@ -1481,6 +1524,7 @@ public Mono save(T objectToSave) { * (non-Javadoc) * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#save(java.lang.Object, java.lang.String) */ + @Override public Mono save(T objectToSave, String collectionName) { Assert.notNull(objectToSave, "Object to save must not be null!"); @@ -1675,6 +1719,7 @@ protected Mono saveDocument(String collectionName, Document document, Cl * (non-Javadoc) * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#upsert(org.springframework.data.mongodb.core.query.Query, org.springframework.data.mongodb.core.query.UpdateDefinition, java.lang.Class) */ + @Override public Mono upsert(Query query, UpdateDefinition update, Class entityClass) { return doUpdate(getCollectionName(entityClass), query, update, entityClass, true, false); } @@ -1683,6 +1728,7 @@ public Mono upsert(Query query, UpdateDefinition update, Class * (non-Javadoc) * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#upsert(org.springframework.data.mongodb.core.query.Query, org.springframework.data.mongodb.core.query.UpdateDefinition, java.lang.String) */ + @Override public Mono upsert(Query query, UpdateDefinition update, String collectionName) { return doUpdate(collectionName, query, update, null, true, false); } @@ -1691,6 +1737,7 @@ public Mono upsert(Query query, UpdateDefinition update, String co * (non-Javadoc) * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#upsert(org.springframework.data.mongodb.core.query.Query, org.springframework.data.mongodb.core.query.UpdateDefinition, java.lang.Class, java.lang.String) */ + @Override public Mono upsert(Query query, UpdateDefinition update, Class entityClass, String collectionName) { return doUpdate(collectionName, query, update, entityClass, true, false); } @@ -1699,6 +1746,7 @@ public Mono upsert(Query query, UpdateDefinition update, Class * (non-Javadoc)) * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#updateFirst(org.springframework.data.mongodb.core.query.Query, org.springframework.data.mongodb.core.query.UpdateDefinition, java.lang.Class) */ + @Override public Mono updateFirst(Query query, UpdateDefinition update, Class entityClass) { return doUpdate(getCollectionName(entityClass), query, update, entityClass, false, false); } @@ -1707,6 +1755,7 @@ public Mono updateFirst(Query query, UpdateDefinition update, Clas * (non-Javadoc) * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#updateFirst(org.springframework.data.mongodb.core.query.Query, org.springframework.data.mongodb.core.query.UpdateDefinition, java.lang.String) */ + @Override public Mono updateFirst(Query query, UpdateDefinition update, String collectionName) { return doUpdate(collectionName, query, update, null, false, false); } @@ -1715,6 +1764,7 @@ public Mono updateFirst(Query query, UpdateDefinition update, Stri * (non-Javadoc) * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#updateFirst(org.springframework.data.mongodb.core.query.Query, org.springframework.data.mongodb.core.query.UpdateDefinition, java.lang.Class, java.lang.String) */ + @Override public Mono updateFirst(Query query, UpdateDefinition update, Class entityClass, String collectionName) { return doUpdate(collectionName, query, update, entityClass, false, false); @@ -1724,6 +1774,7 @@ public Mono updateFirst(Query query, UpdateDefinition update, Clas * (non-Javadoc) * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#updateMulti(org.springframework.data.mongodb.core.query.Query, org.springframework.data.mongodb.core.query.UpdateDefinition, java.lang.Class) */ + @Override public Mono updateMulti(Query query, UpdateDefinition update, Class entityClass) { return doUpdate(getCollectionName(entityClass), query, update, entityClass, false, true); } @@ -1732,6 +1783,7 @@ public Mono updateMulti(Query query, UpdateDefinition update, Clas * (non-Javadoc) * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#updateMulti(org.springframework.data.mongodb.core.query.Query, org.springframework.data.mongodb.core.query.UpdateDefinition, java.lang.String) */ + @Override public Mono updateMulti(Query query, UpdateDefinition update, String collectionName) { return doUpdate(collectionName, query, update, null, false, true); } @@ -1740,6 +1792,7 @@ public Mono updateMulti(Query query, UpdateDefinition update, Stri * (non-Javadoc) * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#updateMulti(org.springframework.data.mongodb.core.query.Query, org.springframework.data.mongodb.core.query.UpdateDefinition, java.lang.Class, java.lang.String) */ + @Override public Mono updateMulti(Query query, UpdateDefinition update, Class entityClass, String collectionName) { return doUpdate(collectionName, query, update, entityClass, false, true); @@ -1873,6 +1926,7 @@ public Mono remove(Mono objectToRemove, String c * (non-Javadoc) * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#remove(java.lang.Object) */ + @Override public Mono remove(Object object) { Assert.notNull(object, "Object must not be null!"); @@ -1884,6 +1938,7 @@ public Mono remove(Object object) { * (non-Javadoc) * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#remove(java.lang.Object, java.lang.String) */ + @Override public Mono remove(Object object, String collectionName) { Assert.notNull(object, "Object must not be null!"); @@ -1917,6 +1972,7 @@ private void assertUpdateableIdIfNotSet(Object value) { * (non-Javadoc) * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#remove(org.springframework.data.mongodb.core.query.Query, java.lang.String) */ + @Override public Mono remove(Query query, String collectionName) { return remove(query, null, collectionName); } @@ -1925,6 +1981,7 @@ public Mono remove(Query query, String collectionName) { * (non-Javadoc) * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#remove(org.springframework.data.mongodb.core.query.Query, java.lang.Class) */ + @Override public Mono remove(Query query, Class entityClass) { return remove(query, entityClass, getCollectionName(entityClass)); } @@ -1933,6 +1990,7 @@ public Mono remove(Query query, Class entityClass) { * (non-Javadoc) * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#remove(org.springframework.data.mongodb.core.query.Query, java.lang.Class, java.lang.String) */ + @Override public Mono remove(Query query, @Nullable Class entityClass, String collectionName) { return doRemove(collectionName, query, entityClass); } @@ -1991,6 +2049,7 @@ protected Mono doRemove(String collectionName, Query query, @N * (non-Javadoc) * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#findAll(java.lang.Class) */ + @Override public Flux findAll(Class entityClass) { return findAll(entityClass, getCollectionName(entityClass)); } @@ -1999,6 +2058,7 @@ public Flux findAll(Class entityClass) { * (non-Javadoc) * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#findAll(java.lang.Class, java.lang.String) */ + @Override public Flux findAll(Class entityClass, String collectionName) { return executeFindMultiInternal(new FindCallback(null), FindPublisherPreparer.NO_OP_PREPARER, new ReadDocumentCallback<>(mongoConverter, entityClass, collectionName), collectionName); @@ -2100,7 +2160,7 @@ List prepareFilter(ChangeStreamOptions options) { AggregationOperationContext context = agg instanceof TypedAggregation ? new TypeBasedAggregationOperationContext(((TypedAggregation) agg).getInputType(), getConverter().getMappingContext(), queryMapper) - : Aggregation.DEFAULT_CONTEXT; + : new RelaxedTypeBasedAggregationOperationContext(Object.class, mappingContext, queryMapper); return agg.toPipeline(new PrefixingDelegatingAggregationOperationContext(context, "fullDocument", Arrays.asList("operationType", "fullDocument", "documentKey", "updateDescription", "ns"))); @@ -2118,6 +2178,7 @@ List prepareFilter(ChangeStreamOptions options) { * (non-Javadoc) * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#mapReduce(org.springframework.data.mongodb.core.query.Query, java.lang.Class, java.lang.Class, java.lang.String, java.lang.String, org.springframework.data.mongodb.core.mapreduce.MapReduceOptions) */ + @Override public Flux mapReduce(Query filterQuery, Class domainType, Class resultType, String mapFunction, String reduceFunction, MapReduceOptions options) { @@ -2129,6 +2190,7 @@ public Flux mapReduce(Query filterQuery, Class domainType, Class re * (non-Javadoc) * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#mapReduce(org.springframework.data.mongodb.core.query.Query, java.lang.Class, java.lang.String, java.lang.Class, java.lang.String, java.lang.String, org.springframework.data.mongodb.core.mapreduce.MapReduceOptions) */ + @Override public Flux mapReduce(Query filterQuery, Class domainType, String inputCollectionName, Class resultType, String mapFunction, String reduceFunction, MapReduceOptions options) { @@ -2410,8 +2472,8 @@ protected Flux doFind(String collectionName, Document query, Document fie * @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 Flux doFind(String collectionName, Document query, Document fields, Class entityClass, @@ -2509,6 +2571,20 @@ protected CreateCollectionOptions convertToCreateCollectionOptions(@Nullable Col 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; } @@ -2526,7 +2602,7 @@ private Document getMappedValidator(Validator validator, Class domainType) { /** * Map the results of an ad-hoc query on the default MongoDB collection to an object using the template's converter. * The first document that matches the query is returned and also removed from the collection in the database. - *

        + *
        * The query document is specified as a standard Document and so is the fields specification. * * @param collectionName name of the collection to retrieve the objects from @@ -2669,20 +2745,34 @@ private MongoCollection getAndPrepareCollection(MongoDatabase db, Stri } } - protected void ensureNotIterable(Object o) { + /** + * Ensure the given {@literal source} is not an {@link java.lang.reflect.Array}, {@link Collection} or + * {@link Iterator}. + * + * @param source can be {@literal null}. + * @deprecated since 3.2. Call {@link #ensureNotCollectionLike(Object)} instead. + */ + protected void ensureNotIterable(@Nullable Object source) { + ensureNotCollectionLike(source); + } - boolean isIterable = o.getClass().isArray() - || ITERABLE_CLASSES.stream().anyMatch(iterableClass -> iterableClass.isAssignableFrom(o.getClass()) - || o.getClass().getName().equals(iterableClass.getName())); + /** + * Ensure the given {@literal source} is not an {@link java.lang.reflect.Array}, {@link Collection} or + * {@link Iterator}. + * + * @param source can be {@literal null}. + * @since 3.2. + */ + protected void ensureNotCollectionLike(@Nullable Object source) { - if (isIterable) { + if (EntityOperations.isCollectionLike(source) || source instanceof Publisher) { throw new IllegalArgumentException("Cannot use a collection here."); } } /** * Prepare the collection before any processing is done using it. This allows a convenient way to apply settings like - * slaveOk() etc. Can be overridden in sub-classes. + * withCodecRegistry() etc. Can be overridden in sub-classes. * * @param collection */ @@ -2717,6 +2807,14 @@ protected WriteConcern prepareWriteConcern(MongoAction mongoAction) { 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) { @@ -2885,7 +2983,6 @@ public Publisher doInCollection(MongoCollection collection) * * @author Mark Paluch */ - @RequiredArgsConstructor private static class FindCallback implements ReactiveCollectionQueryCallback { private final @Nullable Document query; @@ -2895,6 +2992,12 @@ private static class FindCallback implements ReactiveCollectionQueryCallback doInCollection(MongoCollection collection) { @@ -2948,7 +3051,6 @@ public Publisher doInCollection(MongoCollection collection) /** * @author Mark Paluch */ - @RequiredArgsConstructor private static class FindAndModifyCallback implements ReactiveCollectionCallback { private final Document query; @@ -2958,6 +3060,17 @@ private static class FindAndModifyCallback implements ReactiveCollectionCallback private final List arrayFilters; private final FindAndModifyOptions options; + FindAndModifyCallback(Document query, Document fields, Document sort, Object update, List arrayFilters, + FindAndModifyOptions options) { + + this.query = query; + this.fields = fields; + this.sort = sort; + this.update = update; + this.arrayFilters = arrayFilters; + this.options = options; + } + @Override public Publisher doInCollection(MongoCollection collection) throws MongoException, DataAccessException { @@ -3013,7 +3126,6 @@ private static FindOneAndUpdateOptions convertToFindOneAndUpdateOptions(FindAndM * @author Christoph Strobl * @since 2.1 */ - @RequiredArgsConstructor(access = AccessLevel.PACKAGE) private static class FindAndReplaceCallback implements ReactiveCollectionCallback { private final Document query; @@ -3023,6 +3135,17 @@ private static class FindAndReplaceCallback implements ReactiveCollectionCallbac 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) @@ -3089,6 +3212,7 @@ interface MongoDatabaseCallback { */ interface ReactiveCollectionQueryCallback extends ReactiveCollectionCallback { + @Override FindPublisher doInCollection(MongoCollection collection) throws MongoException, DataAccessException; } @@ -3115,17 +3239,19 @@ class ReadDocumentCallback implements DocumentCallback { this.collectionName = collectionName; } + @Override public Mono doWith(Document document) { maybeEmitEvent(new AfterLoadEvent<>(document, type, collectionName)); - T source = reader.read(type, document); - if (source != null) { - maybeEmitEvent(new AfterConvertEvent<>(document, source, collectionName)); - return maybeCallAfterConvert(source, document, collectionName); + T entity = reader.read(type, document); + + if (entity == null) { + throw new MappingException(String.format("EntityReader %s returned null", reader)); } - return Mono.empty(); + maybeEmitEvent(new AfterConvertEvent<>(document, entity, collectionName)); + return maybeCallAfterConvert(entity, document, collectionName); } } @@ -3139,14 +3265,22 @@ public Mono doWith(Document document) { * @author Roman Puchkovskiy * @since 2.0 */ - @RequiredArgsConstructor private class ProjectingReadCallback implements DocumentCallback { - private final @NonNull EntityReader reader; - private final @NonNull Class entityType; - private final @NonNull Class targetType; - private final @NonNull String collectionName; + private final EntityReader reader; + private final Class entityType; + private final Class targetType; + private final String collectionName; + ProjectingReadCallback(EntityReader reader, Class entityType, Class targetType, + String collectionName) { + this.reader = reader; + this.entityType = entityType; + this.targetType = targetType; + this.collectionName = collectionName; + } + + @Override @SuppressWarnings("unchecked") public Mono doWith(Document document) { @@ -3156,16 +3290,17 @@ public Mono doWith(Document document) { maybeEmitEvent(new AfterLoadEvent<>(document, typeToRead, collectionName)); - Object source = reader.read(typeToRead, document); - Object result = targetType.isInterface() ? projectionFactory.createProjection(targetType, source) : source; + Object entity = reader.read(typeToRead, document); - T castEntity = (T) result; - if (castEntity != null) { - maybeEmitEvent(new AfterConvertEvent<>(document, castEntity, collectionName)); - return maybeCallAfterConvert(castEntity, document, collectionName); + if (entity == null) { + throw new MappingException(String.format("EntityReader %s returned null", reader)); } - return Mono.empty(); + Object result = targetType.isInterface() ? projectionFactory.createProjection(targetType, entity) : entity; + + T castEntity = (T) result; + maybeEmitEvent(new AfterConvertEvent<>(document, castEntity, collectionName)); + return maybeCallAfterConvert(castEntity, document, collectionName); } } @@ -3200,6 +3335,7 @@ static class GeoNearResultDocumentCallback implements DocumentCallback> doWith(Document object) { double distance = getDistance(object); @@ -3231,6 +3367,7 @@ class QueryFindPublisherPreparer implements FindPublisherPreparer { this.type = type; } + @Override public FindPublisher prepare(FindPublisher findPublisher) { FindPublisher findPublisherToUse = operations.forType(type) // @@ -3284,6 +3421,10 @@ public FindPublisher prepare(FindPublisher findPublisher) { if (meta.getCursorBatchSize() != null) { findPublisherToUse = findPublisherToUse.batchSize(meta.getCursorBatchSize()); } + + if (meta.getAllowDiskUse() != null) { + findPublisherToUse = findPublisherToUse.allowDiskUse(meta.getAllowDiskUse()); + } } } catch (RuntimeException e) { @@ -3295,7 +3436,8 @@ public FindPublisher prepare(FindPublisher findPublisher) { @Override public ReadPreference getReadPreference() { - return query.getMeta().getFlags().contains(CursorOption.SLAVE_OK) ? ReadPreference.primaryPreferred() : null; + return (query.getMeta().getFlags().contains(CursorOption.SECONDARY_READS) + || query.getMeta().getFlags().contains(CursorOption.SLAVE_OK)) ? ReadPreference.primaryPreferred() : null; } } @@ -3318,7 +3460,7 @@ private static List toDocuments(Collection + *
        * The prepare steps for {@link MongoDatabase} and {@link MongoCollection} proxy the target and invoke the desired * target method matching the actual arguments plus a {@link ClientSession}. * @@ -3365,11 +3507,14 @@ public Mono getMongoDatabase() { } } - @RequiredArgsConstructor class IndexCreatorEventListener implements ApplicationListener> { final Consumer subscriptionExceptionHandler; + public IndexCreatorEventListener(Consumer subscriptionExceptionHandler) { + this.subscriptionExceptionHandler = subscriptionExceptionHandler; + } + @Override public void onApplicationEvent(MappingContextEvent event) { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveRemoveOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveRemoveOperation.java index 499592a437..4a985b73f4 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveRemoveOperation.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveRemoveOperation.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2020 the original author or authors. + * Copyright 2017-2022 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. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveRemoveOperationSupport.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveRemoveOperationSupport.java index 62835c0231..80c4dcbbdd 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveRemoveOperationSupport.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveRemoveOperationSupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2020 the original author or authors. + * Copyright 2017-2022 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. @@ -15,10 +15,6 @@ */ 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; @@ -35,12 +31,15 @@ * @author Christoph Strobl * @since 2.0 */ -@RequiredArgsConstructor class ReactiveRemoveOperationSupport implements ReactiveRemoveOperation { private static final Query ALL_QUERY = new Query(); - private final @NonNull ReactiveMongoTemplate tempate; + private final ReactiveMongoTemplate tempate; + + ReactiveRemoveOperationSupport(ReactiveMongoTemplate tempate) { + this.tempate = tempate; + } /* * (non-Javadoc) @@ -54,14 +53,20 @@ public ReactiveRemove remove(Class domainType) { return new ReactiveRemoveSupport<>(tempate, domainType, ALL_QUERY, null); } - @RequiredArgsConstructor - @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) static class ReactiveRemoveSupport implements ReactiveRemove, RemoveWithCollection { - @NonNull ReactiveMongoTemplate template; - @NonNull Class domainType; - Query query; - String collection; + private final ReactiveMongoTemplate template; + private final Class domainType; + private final Query query; + private final String collection; + + ReactiveRemoveSupport(ReactiveMongoTemplate template, Class domainType, Query query, String collection) { + + this.template = template; + this.domainType = domainType; + this.query = query; + this.collection = collection; + } /* * (non-Javadoc) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveSessionCallback.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveSessionCallback.java index 084c2ce9f5..bb67eee7a6 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveSessionCallback.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveSessionCallback.java @@ -1,5 +1,5 @@ /* - * Copyright 2018-2020 the original author or authors. + * Copyright 2018-2022 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. @@ -32,7 +32,7 @@ public interface ReactiveSessionCallback { /** * Execute operations against a MongoDB instance via session bound {@link ReactiveMongoOperations}. The session is * inferred directly into the operation so that no further interaction is necessary. - *

        + *
        * Please note that only Spring Data-specific abstractions like {@link ReactiveMongoOperations#find(Query, Class)} and * others are enhanced with the {@link com.mongodb.session.ClientSession}. When obtaining plain MongoDB gateway * objects like {@link com.mongodb.reactivestreams.client.MongoCollection} or diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveSessionScoped.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveSessionScoped.java index 8babbe3ae0..864a196d92 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveSessionScoped.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveSessionScoped.java @@ -1,5 +1,5 @@ /* - * Copyright 2018-2020 the original author or authors. + * Copyright 2018-2022 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. @@ -33,7 +33,7 @@ public interface ReactiveSessionScoped { /** * Executes the given {@link ReactiveSessionCallback} within the {@link com.mongodb.session.ClientSession}. - *

        + *
        * It is up to the caller to make sure the {@link com.mongodb.session.ClientSession} is {@link ClientSession#close() * closed} when done. * @@ -47,7 +47,7 @@ default Flux execute(ReactiveSessionCallback action) { /** * Executes the given {@link ReactiveSessionCallback} within the {@link com.mongodb.session.ClientSession}. - *

        + *
        * It is up to the caller to make sure the {@link com.mongodb.session.ClientSession} is {@link ClientSession#close() * closed} when done. * diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveUpdateOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveUpdateOperation.java index 74aa29799a..3bfdf95f1c 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveUpdateOperation.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveUpdateOperation.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2020 the original author or authors. + * Copyright 2017-2022 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. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveUpdateOperationSupport.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveUpdateOperationSupport.java index ff1aede220..dd3a46413b 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveUpdateOperationSupport.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveUpdateOperationSupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2020 the original author or authors. + * Copyright 2017-2022 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. @@ -15,13 +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.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; @@ -35,12 +32,15 @@ * @author Christoph Strobl * @since 2.0 */ -@RequiredArgsConstructor class ReactiveUpdateOperationSupport implements ReactiveUpdateOperation { private static final Query ALL_QUERY = new Query(); - private final @NonNull ReactiveMongoTemplate template; + private final ReactiveMongoTemplate template; + + ReactiveUpdateOperationSupport(ReactiveMongoTemplate template) { + this.template = template; + } /* * (non-Javadoc) @@ -54,21 +54,34 @@ public ReactiveUpdate update(Class domainType) { return new ReactiveUpdateSupport<>(template, domainType, ALL_QUERY, null, null, null, null, null, domainType); } - @RequiredArgsConstructor - @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) static class ReactiveUpdateSupport implements ReactiveUpdate, UpdateWithCollection, UpdateWithQuery, TerminatingUpdate, FindAndReplaceWithOptions, FindAndReplaceWithProjection, TerminatingFindAndReplace { - @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 targetType; + 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 targetType; + + ReactiveUpdateSupport(ReactiveMongoTemplate template, Class domainType, Query query, UpdateDefinition update, + String collection, FindAndModifyOptions findAndModifyOptions, FindAndReplaceOptions findAndReplaceOptions, + Object replacement, Class 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; + } /* * (non-Javadoc) @@ -123,7 +136,9 @@ public Mono findAndModify() { 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); } /* diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReadPreferenceAware.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReadPreferenceAware.java index fa2e16b082..e23ecc5a1d 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReadPreferenceAware.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReadPreferenceAware.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 the original author or authors. + * Copyright 2019-2022 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. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ScriptOperations.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ScriptOperations.java index e076c09bf4..faf903256f 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ScriptOperations.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ScriptOperations.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2020 the original author or authors. + * Copyright 2014-2022 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. @@ -23,7 +23,7 @@ /** - * Script operations on {@link com.mongodb.DB} level. Allows interaction with server side JavaScript functions. + * Script operations on {@link com.mongodb.client.MongoDatabase} level. Allows interaction with server side JavaScript functions. * * @author Christoph Strobl * @author Oliver Gierke @@ -72,10 +72,10 @@ public interface ScriptOperations { Object call(String scriptName, Object... args); /** - * Checks {@link DB} for existence of {@link ServerSideJavaScript} with given name. + * Checks {@link com.mongodb.client.MongoDatabase} for existence of {@literal ServerSideJavaScript} with given name. * * @param scriptName must not be {@literal null} or empty. - * @return false if no {@link ServerSideJavaScript} with given name exists. + * @return false if no {@literal ServerSideJavaScript} with given name exists. */ boolean exists(String scriptName); diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/SessionCallback.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/SessionCallback.java index 5c58121f77..0a90aee052 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/SessionCallback.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/SessionCallback.java @@ -1,5 +1,5 @@ /* - * Copyright 2018-2020 the original author or authors. + * Copyright 2018-2022 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. @@ -31,7 +31,7 @@ public interface SessionCallback { /** * Execute operations against a MongoDB instance via session bound {@link MongoOperations}. The session is inferred * directly into the operation so that no further interaction is necessary. - *

        + *
        * Please note that only Spring Data-specific abstractions like {@link MongoOperations#find(Query, Class)} and others * are enhanced with the {@link com.mongodb.session.ClientSession}. When obtaining plain MongoDB gateway objects like * {@link com.mongodb.client.MongoCollection} or {@link com.mongodb.client.MongoDatabase} via eg. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/SessionScoped.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/SessionScoped.java index 3b03afa5ce..1c77a53aff 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/SessionScoped.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/SessionScoped.java @@ -1,5 +1,5 @@ /* - * Copyright 2018-2020 the original author or authors. + * Copyright 2018-2022 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. @@ -23,7 +23,7 @@ /** * Gateway interface to execute {@link ClientSession} bound operations against MongoDB via a {@link SessionCallback}. - *

        + *
        * The very same bound {@link ClientSession} is used for all invocations of {@code execute} on the instance. * * @author Christoph Strobl @@ -34,7 +34,7 @@ public interface SessionScoped { /** * Executes the given {@link SessionCallback} within the {@link com.mongodb.session.ClientSession}. - *

        + *
        * It is up to the caller to make sure the {@link com.mongodb.session.ClientSession} is {@link ClientSession#close() * closed} when done. * @@ -49,7 +49,7 @@ default T execute(SessionCallback action) { /** * Executes the given {@link SessionCallback} within the {@link com.mongodb.session.ClientSession}. - *

        + *
        * It is up to the caller to make sure the {@link com.mongodb.session.ClientSession} is {@link ClientSession#close() * closed} when done. * diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/SimpleMongoClientDatabaseFactory.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/SimpleMongoClientDatabaseFactory.java index b665128a0c..faad6ed272 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/SimpleMongoClientDatabaseFactory.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/SimpleMongoClientDatabaseFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2018-2019 the original author or authors. + * Copyright 2018-2022 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. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/SimpleMongoClientDbFactory.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/SimpleMongoClientDbFactory.java index 08c69af2b0..b849e324a8 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/SimpleMongoClientDbFactory.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/SimpleMongoClientDbFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2018-2020 the original author or authors. + * Copyright 2018-2022 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. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/SimpleReactiveMongoDatabaseFactory.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/SimpleReactiveMongoDatabaseFactory.java index 4eedce82a2..6e269cf432 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/SimpleReactiveMongoDatabaseFactory.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/SimpleReactiveMongoDatabaseFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2020 the original author or authors. + * Copyright 2016-2022 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. @@ -15,7 +15,6 @@ */ package org.springframework.data.mongodb.core; -import lombok.Value; import reactor.core.publisher.Mono; import org.bson.codecs.configuration.CodecRegistry; @@ -27,6 +26,7 @@ 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,11 +175,16 @@ public ReactiveMongoDatabaseFactory withSession(ClientSession session) { * @author Christoph Strobl * @since 2.1 */ - @Value - static class ClientSessionBoundMongoDbFactory implements ReactiveMongoDatabaseFactory { + static final class ClientSessionBoundMongoDbFactory implements ReactiveMongoDatabaseFactory { - ClientSession session; - ReactiveMongoDatabaseFactory delegate; + private final ClientSession session; + private final ReactiveMongoDatabaseFactory delegate; + + ClientSessionBoundMongoDbFactory(ClientSession session, ReactiveMongoDatabaseFactory delegate) { + + this.session = session; + this.delegate = delegate; + } /* * (non-Javadoc) @@ -266,7 +271,42 @@ private T createProxyInstance(com.mongodb.session.ClientSession session, T t factory.addAdvice(new SessionAwareMethodInterceptor<>(session, target, ClientSession.class, MongoDatabase.class, this::proxyDatabase, MongoCollection.class, this::proxyCollection)); - return targetType.cast(factory.getProxy()); + 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() + ")"; } } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/WriteConcernResolver.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/WriteConcernResolver.java index f2434735a3..dc3b6d126b 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/WriteConcernResolver.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/WriteConcernResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2020 the original author or authors. + * Copyright 2011-2022 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. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/WriteResultChecking.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/WriteResultChecking.java index 3183bf92ba..525d953838 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/WriteResultChecking.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/WriteResultChecking.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2022 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. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AbstractAggregationExpression.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AbstractAggregationExpression.java index ad607cbcae..0046ba4e62 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AbstractAggregationExpression.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AbstractAggregationExpression.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018. the original author or authors. + * Copyright 2016-2022. 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. @@ -29,8 +29,11 @@ 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 { @@ -49,7 +52,6 @@ public Document toDocument(AggregationOperationContext context) { return toDocument(this.value, context); } - @SuppressWarnings("unchecked") public Document toDocument(Object value, AggregationOperationContext context) { return new Document(getMongoMethod(), unpack(value, context)); } @@ -101,17 +103,19 @@ private Object unpack(Object value, AggregationOperationContext context) { return value; } + @SuppressWarnings("unchecked") protected List append(Object value, Expand expandList) { if (this.value instanceof List) { - List clone = new ArrayList((List) this.value); + List clone = new ArrayList<>((List) this.value); if (value instanceof Collection && Expand.EXPAND_VALUES.equals(expandList)) { clone.addAll((Collection) value); } else { clone.add(value); } + return clone; } @@ -129,25 +133,72 @@ protected List append(Object value) { return append(value, Expand.EXPAND_VALUES); } - @SuppressWarnings("unchecked") - protected java.util.Map append(String key, Object value) { + @SuppressWarnings({ "unchecked", "rawtypes" }) + protected Map append(String key, Object value) { Assert.isInstanceOf(Map.class, this.value, "Value must be a type of Map!"); - java.util.Map clone = new LinkedHashMap<>((java.util.Map) this.value); + Map clone = new LinkedHashMap<>((java.util.Map) this.value); clone.put(key, value); return clone; } + @SuppressWarnings({ "unchecked", "rawtypes" }) + protected Map remove(String key) { + + Assert.isInstanceOf(Map.class, this.value, "Value must be a type of Map!"); + + Map 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 appendAt(int index, String key, Object value) { + + Assert.isInstanceOf(Map.class, this.value, "Value must be a type of Map!"); + + Map clone = new LinkedHashMap<>(); + + int i = 0; + for (Map.Entry entry : ((Map) 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 values() { if (value instanceof List) { return new ArrayList((List) value); } + if (value instanceof java.util.Map) { return new ArrayList(((java.util.Map) value).values()); } + return new ArrayList<>(Collections.singletonList(value)); } @@ -177,7 +228,7 @@ protected T get(Object key) { Assert.isInstanceOf(Map.class, this.value, "Value must be a type of Map!"); - return (T) ((java.util.Map) this.value).get(key); + return (T) ((Map) this.value).get(key); } /** @@ -187,11 +238,11 @@ protected T get(Object key) { * @return */ @SuppressWarnings("unchecked") - protected java.util.Map argumentMap() { + protected Map argumentMap() { Assert.isInstanceOf(Map.class, this.value, "Value must be a type of Map!"); - return Collections.unmodifiableMap((java.util.Map) value); + return Collections.unmodifiableMap((java.util.Map) value); } /** @@ -208,7 +259,7 @@ protected boolean contains(Object key) { return false; } - return ((java.util.Map) this.value).containsKey(key); + return ((Map) this.value).containsKey(key); } protected abstract String getMongoMethod(); diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AccumulatorOperators.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AccumulatorOperators.java index c9a5a73c3a..c8b1abcd3d 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AccumulatorOperators.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AccumulatorOperators.java @@ -1,5 +1,5 @@ /* - * Copyright 2016. the original author or authors. + * Copyright 2016-2022 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. @@ -142,11 +142,118 @@ public StdDevSamp stdDevSamp() { return usesFieldRef() ? StdDevSamp.stdDevSampOf(fieldReference) : StdDevSamp.stdDevSampOf(expression); } + /** + * Creates new {@link AggregationExpression} that uses the previous input (field/expression) and the value of the + * given field to calculate the population covariance of the two. + * + * @param fieldReference must not be {@literal null}. + * @return new instance of {@link CovariancePop}. + * @since 3.3 + */ + public CovariancePop covariancePop(String fieldReference) { + return covariancePop().and(fieldReference); + } + + /** + * Creates new {@link AggregationExpression} that uses the previous input (field/expression) and the result of the + * given {@link AggregationExpression expression} to calculate the population covariance of the two. + * + * @param expression must not be {@literal null}. + * @return new instance of {@link CovariancePop}. + * @since 3.3 + */ + public CovariancePop covariancePop(AggregationExpression expression) { + return covariancePop().and(expression); + } + + private CovariancePop covariancePop() { + return usesFieldRef() ? CovariancePop.covariancePopOf(fieldReference) : CovariancePop.covariancePopOf(expression); + } + + /** + * Creates new {@link AggregationExpression} that uses the previous input (field/expression) and the value of the + * given field to calculate the sample covariance of the two. + * + * @param fieldReference must not be {@literal null}. + * @return new instance of {@link CovariancePop}. + * @since 3.3 + */ + public CovarianceSamp covarianceSamp(String fieldReference) { + return covarianceSamp().and(fieldReference); + } + + /** + * Creates new {@link AggregationExpression} that uses the previous input (field/expression) and the result of the + * given {@link AggregationExpression expression} to calculate the sample covariance of the two. + * + * @param expression must not be {@literal null}. + * @return new instance of {@link CovariancePop}. + * @since 3.3 + */ + public CovarianceSamp covarianceSamp(AggregationExpression expression) { + return covarianceSamp().and(expression); + } + + private CovarianceSamp covarianceSamp() { + return usesFieldRef() ? CovarianceSamp.covarianceSampOf(fieldReference) + : CovarianceSamp.covarianceSampOf(expression); + } + + /** + * Creates new {@link ExpMovingAvgBuilder} that to build {@link AggregationExpression expMovingAvg} that calculates + * the exponential moving average of numeric values + * + * @return new instance of {@link ExpMovingAvg}. + * @since 3.3 + */ + public ExpMovingAvgBuilder expMovingAvg() { + + ExpMovingAvg expMovingAvg = usesFieldRef() ? ExpMovingAvg.expMovingAvgOf(fieldReference) + : ExpMovingAvg.expMovingAvgOf(expression); + return new ExpMovingAvgBuilder() { + + @Override + public ExpMovingAvg historicalDocuments(int numberOfHistoricalDocuments) { + return expMovingAvg.n(numberOfHistoricalDocuments); + } + + @Override + public ExpMovingAvg alpha(double exponentialDecayValue) { + return expMovingAvg.alpha(exponentialDecayValue); + } + }; + } + private boolean usesFieldRef() { return fieldReference != null; } } + /** + * Builder for {@link ExpMovingAvg}. + * + * @since 3.3 + */ + public interface ExpMovingAvgBuilder { + + /** + * Define the number of historical documents with significant mathematical weight. + * + * @param numberOfHistoricalDocuments + * @return new instance of {@link ExpMovingAvg}. + */ + ExpMovingAvg historicalDocuments(int numberOfHistoricalDocuments); + + /** + * Define the exponential decay value. + * + * @param exponentialDecayValue + * @return new instance of {@link ExpMovingAvg}. + */ + ExpMovingAvg alpha(double exponentialDecayValue); + + } + /** * {@link AggregationExpression} for {@code $sum}. * @@ -658,4 +765,185 @@ public Document toDocument(Object value, AggregationOperationContext context) { return super.toDocument(value, context); } } + + /** + * {@link AggregationExpression} for {@code $covariancePop}. + * + * @author Christoph Strobl + * @since 3.3 + */ + public static class CovariancePop extends AbstractAggregationExpression { + + private CovariancePop(Object value) { + super(value); + } + + /** + * Creates new {@link CovariancePop}. + * + * @param fieldReference must not be {@literal null}. + * @return new instance of {@link CovariancePop}. + */ + public static CovariancePop covariancePopOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new CovariancePop(asFields(fieldReference)); + } + + /** + * Creates new {@link CovariancePop}. + * + * @param expression must not be {@literal null}. + * @return new instance of {@link CovariancePop}. + */ + public static CovariancePop covariancePopOf(AggregationExpression expression) { + return new CovariancePop(Collections.singletonList(expression)); + } + + /** + * Creates new {@link CovariancePop} with all previously added arguments appending the given one. + * + * @param fieldReference must not be {@literal null}. + * @return new instance of {@link CovariancePop}. + */ + public CovariancePop and(String fieldReference) { + return new CovariancePop(append(asFields(fieldReference))); + } + + /** + * Creates new {@link CovariancePop} with all previously added arguments appending the given one. + * + * @param expression must not be {@literal null}. + * @return new instance of {@link CovariancePop}. + */ + public CovariancePop and(AggregationExpression expression) { + return new CovariancePop(append(expression)); + } + + @Override + protected String getMongoMethod() { + return "$covariancePop"; + } + } + + /** + * {@link AggregationExpression} for {@code $covarianceSamp}. + * + * @author Christoph Strobl + * @since 3.3 + */ + public static class CovarianceSamp extends AbstractAggregationExpression { + + private CovarianceSamp(Object value) { + super(value); + } + + /** + * Creates new {@link CovarianceSamp}. + * + * @param fieldReference must not be {@literal null}. + * @return new instance of {@link CovarianceSamp}. + */ + public static CovarianceSamp covarianceSampOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new CovarianceSamp(asFields(fieldReference)); + } + + /** + * Creates new {@link CovarianceSamp}. + * + * @param expression must not be {@literal null}. + * @return new instance of {@link CovarianceSamp}. + */ + public static CovarianceSamp covarianceSampOf(AggregationExpression expression) { + return new CovarianceSamp(Collections.singletonList(expression)); + } + + /** + * Creates new {@link CovarianceSamp} with all previously added arguments appending the given one. + * + * @param fieldReference must not be {@literal null}. + * @return new instance of {@link CovarianceSamp}. + */ + public CovarianceSamp and(String fieldReference) { + return new CovarianceSamp(append(asFields(fieldReference))); + } + + /** + * Creates new {@link CovarianceSamp} with all previously added arguments appending the given one. + * + * @param expression must not be {@literal null}. + * @return new instance of {@link CovarianceSamp}. + */ + public CovarianceSamp and(AggregationExpression expression) { + return new CovarianceSamp(append(expression)); + } + + @Override + protected String getMongoMethod() { + return "$covarianceSamp"; + } + } + + /** + * {@link ExpMovingAvg} calculates the exponential moving average of numeric values. + * + * @author Christoph Strobl + * @since 3.3 + */ + public static class ExpMovingAvg extends AbstractAggregationExpression { + + private ExpMovingAvg(Object value) { + super(value); + } + + /** + * Create a new {@link ExpMovingAvg} by defining the field holding the value to be used as input. + * + * @param fieldReference must not be {@literal null}. + * @return new instance of {@link ExpMovingAvg}. + */ + public static ExpMovingAvg expMovingAvgOf(String fieldReference) { + return new ExpMovingAvg(Collections.singletonMap("input", Fields.field(fieldReference))); + } + + /** + * Create a new {@link ExpMovingAvg} by defining the {@link AggregationExpression expression} to compute the value + * to be used as input. + * + * @param expression must not be {@literal null}. + * @return new instance of {@link ExpMovingAvg}. + */ + public static ExpMovingAvg expMovingAvgOf(AggregationExpression expression) { + return new ExpMovingAvg(Collections.singletonMap("input", expression)); + } + + /** + * Define the number of historical documents with significant mathematical weight.
        + * Specify either {@link #n(int) N} or {@link #alpha(double) aplha}. Not both! + * + * @param numberOfHistoricalDocuments + * @return new instance of {@link ExpMovingAvg}. + */ + public ExpMovingAvg n/*umber of historical documents*/(int numberOfHistoricalDocuments) { + return new ExpMovingAvg(append("N", numberOfHistoricalDocuments)); + } + + /** + * Define the exponential decay value.
        + * Specify either {@link #alpha(double) aplha} or {@link #n(int) N}. Not both! + * + * @param exponentialDecayValue + * @return new instance of {@link ExpMovingAvg}. + */ + public ExpMovingAvg alpha(double exponentialDecayValue) { + return new ExpMovingAvg(append("alpha", exponentialDecayValue)); + } + + @Override + protected String getMongoMethod() { + return "$expMovingAvg"; + } + } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AddFieldsOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AddFieldsOperation.java index 8ebc744adf..2470e3e76e 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AddFieldsOperation.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AddFieldsOperation.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 the original author or authors. + * Copyright 2020-2022 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. @@ -99,6 +99,10 @@ public AddFieldsOperationBuilder and() { return new AddFieldsOperationBuilder(getValueMap()); } + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.DocumentEnhancingOperation#mongoOperator() + */ @Override protected String mongoOperator() { return "$addFields"; @@ -197,4 +201,5 @@ public interface ValueAppender { AddFieldsOperationBuilder withValueOfExpression(String operation, Object... values); } } + } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/Aggregation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/Aggregation.java index 9128977411..00d7b96cbd 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/Aggregation.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/Aggregation.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2020 the original author or authors. + * Copyright 2013-2022 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. @@ -96,7 +96,7 @@ public class Aggregation { public static final AggregationOperationContext DEFAULT_CONTEXT = AggregationOperationRenderer.DEFAULT_CONTEXT; public static final AggregationOptions DEFAULT_OPTIONS = newAggregationOptions().build(); - protected final List operations; + protected final AggregationPipeline pipeline; private final AggregationOptions options; /** @@ -139,7 +139,7 @@ public static AggregationUpdate newUpdate(AggregationOperation... operations) { public Aggregation withOptions(AggregationOptions options) { Assert.notNull(options, "AggregationOptions must not be null."); - return new Aggregation(this.operations, options); + return new Aggregation(this.pipeline.getOperations(), options); } /** @@ -202,26 +202,10 @@ protected Aggregation(List aggregationOperations, Aggregat Assert.notNull(aggregationOperations, "AggregationOperations must not be null!"); Assert.notNull(options, "AggregationOptions must not be null!"); - // check $out/$merge is the last operation if it exists - for (AggregationOperation aggregationOperation : aggregationOperations) { - - if (aggregationOperation instanceof OutOperation && !isLast(aggregationOperation, aggregationOperations)) { - throw new IllegalArgumentException("The $out operator must be the last stage in the pipeline."); - } - - if (aggregationOperation instanceof MergeOperation && !isLast(aggregationOperation, aggregationOperations)) { - throw new IllegalArgumentException("The $merge operator must be the last stage in the pipeline."); - } - } - - this.operations = aggregationOperations; + this.pipeline = new AggregationPipeline(aggregationOperations); this.options = options; } - private boolean isLast(AggregationOperation aggregationOperation, List aggregationOperations) { - return aggregationOperations.indexOf(aggregationOperation) == aggregationOperations.size() - 1; - } - /** * Get the {@link AggregationOptions}. * @@ -243,7 +227,7 @@ public static String previousOperation() { /** * Obtain an {@link AddFieldsOperationBuilder builder} instance to create a new {@link AddFieldsOperation}. - *

        + *
        * Starting in version 4.2, MongoDB adds a new aggregation pipeline stage {@link AggregationUpdate#set $set} that is * an alias for {@code $addFields}. * @@ -515,6 +499,17 @@ public static MatchOperation match(CriteriaDefinition criteria) { return new MatchOperation(criteria); } + /** + * Creates a new {@link MatchOperation} using the given {@link AggregationExpression}. + * + * @param expression must not be {@literal null}. + * @return new instance of {@link MatchOperation}. + * @since 3.3 + */ + public static MatchOperation match(AggregationExpression expression) { + return new MatchOperation(expression); + } + /** * Creates a new {@link GeoNearOperation} instance from the given {@link NearQuery} and the {@code distanceField}. The * {@code distanceField} defines output field that contains the calculated distance. @@ -661,9 +656,9 @@ public static CountOperationBuilder count() { /** * Creates a new {@link RedactOperation} that can restrict the content of a document based on information stored * within the document itself. - * + * *

        -	 * 
        +	 *
         	 * Aggregation.redact(ConditionalOperators.when(Criteria.where("level").is(5)) //
         	 * 		.then(RedactOperation.PRUNE) //
         	 * 		.otherwise(RedactOperation.DESCEND));
        @@ -718,12 +713,20 @@ public static AggregationOptions.Builder newAggregationOptions() {
         	 * @since 2.1
         	 */
         	public List toPipeline(AggregationOperationContext rootContext) {
        -		return AggregationOperationRenderer.toDocument(operations, rootContext);
        +		return pipeline.toDocuments(rootContext);
        +	}
        +
        +	/**
        +	 * @return the {@link AggregationPipeline}.
        +	 * @since 3.0.2
        +	 */
        +	public AggregationPipeline getPipeline() {
        +		return pipeline;
         	}
         
         	/**
         	 * Converts this {@link Aggregation} specification to a {@link Document}.
        -	 * 

        + *
        * MongoDB requires as of 3.6 cursor-based aggregation. Use {@link #toPipeline(AggregationOperationContext)} to render * an aggregation pipeline. * diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationExpression.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationExpression.java index 7e15a70284..166b3792e8 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationExpression.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationExpression.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-2022 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. @@ -16,6 +16,7 @@ package org.springframework.data.mongodb.core.aggregation; import org.bson.Document; +import org.springframework.data.mongodb.MongoExpression; /** * An {@link AggregationExpression} can be used with field expressions in aggregation pipeline stages like @@ -25,7 +26,37 @@ * @author Oliver Gierke * @author Christoph Strobl */ -public interface AggregationExpression { +public interface AggregationExpression extends MongoExpression { + + /** + * Create an {@link AggregationExpression} out of a given {@link MongoExpression} to ensure the resulting + * {@link MongoExpression#toDocument() Document} is mapped against the {@link AggregationOperationContext}.
        + * If the given expression is already an {@link AggregationExpression} the very same instance is returned. + * + * @param expression must not be {@literal null}. + * @return never {@literal null}. + * @since 3.2 + */ + static AggregationExpression from(MongoExpression expression) { + + if (expression instanceof AggregationExpression) { + return AggregationExpression.class.cast(expression); + } + + return (context) -> context.getMappedObject(expression.toDocument()); + } + + /** + * Obtain the as is (unmapped) representation of the {@link AggregationExpression}. Use + * {@link #toDocument(AggregationOperationContext)} with a matching {@link AggregationOperationContext context} to + * engage domain type mapping including field name resolution. + * + * @see org.springframework.data.mongodb.MongoExpression#toDocument() + */ + @Override + default Document toDocument() { + return toDocument(Aggregation.DEFAULT_CONTEXT); + } /** * Turns the {@link AggregationExpression} into a {@link Document} within the given diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationExpressionTransformer.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationExpressionTransformer.java index 635f00abec..c04546abb0 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationExpressionTransformer.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationExpressionTransformer.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2020 the original author or authors. + * Copyright 2013-2022 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. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationFunctionExpressions.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationFunctionExpressions.java index 4c7f687663..ff2f9c6694 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationFunctionExpressions.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationFunctionExpressions.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * Copyright 2015-2022 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. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationOperation.java index 2b8a7171e5..2443d789d6 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationOperation.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationOperation.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2020 the original author or authors. + * Copyright 2013-2022 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. @@ -54,4 +54,15 @@ public interface AggregationOperation { default List toPipelineStages(AggregationOperationContext context) { return Collections.singletonList(toDocument(context)); } + + /** + * Return the MongoDB operator that is used for this {@link AggregationOperation}. Aggregation operations should + * implement this method to avoid document rendering. + * + * @return the operator used for this {@link AggregationOperation}. + * @since 3.0.2 + */ + default String getOperator() { + return toDocument(Aggregation.DEFAULT_CONTEXT).keySet().iterator().next(); + } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationOperationContext.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationOperationContext.java index b86407edbb..89029952eb 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationOperationContext.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationOperationContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2020 the original author or authors. + * Copyright 2013-2022 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. @@ -107,7 +107,7 @@ default Fields getFields(Class 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. - * + * * @return a more relaxed {@link AggregationOperationContext}. * @since 3.0 */ diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationOperationRenderer.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationOperationRenderer.java index eebba56434..baa1077988 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationOperationRenderer.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationOperationRenderer.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2020 the original author or authors. + * Copyright 2016-2022 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. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationOptions.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationOptions.java index 96b199b8c0..0e35e2f76c 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationOptions.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationOptions.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2020 the original author or authors. + * Copyright 2014-2022 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. @@ -32,6 +32,7 @@ * @author Oliver Gierke * @author Christoph Strobl * @author Mark Paluch + * @author Yadhukrishna S Pai * @see Aggregation#withOptions(AggregationOptions) * @see TypedAggregation#withOptions(AggregationOptions) * @since 1.6 @@ -45,13 +46,17 @@ 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 cursor; private final Optional collation; private final Optional comment; + private final Optional hint; private Duration maxTime = Duration.ZERO; + private ResultOptions resultOptions = ResultOptions.READ; + private DomainTypeMapping domainTypeMapping = DomainTypeMapping.RELAXED; /** * Creates a new {@link AggregationOptions}. @@ -70,13 +75,13 @@ public AggregationOptions(boolean allowDiskUse, boolean explain, Document cursor * @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); + this(allowDiskUse, explain, cursor, collation, null, null); } /** @@ -85,19 +90,37 @@ public AggregationOptions(boolean allowDiskUse, boolean explain, @Nullable Docum * @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); } /** @@ -129,8 +152,9 @@ public static AggregationOptions fromDocument(Document document) { 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); + AggregationOptions options = new AggregationOptions(allowDiskUse, explain, cursor, collation, comment, hint); if (document.containsKey(MAX_TIME)) { options.maxTime = Duration.ofMillis(document.getLong(MAX_TIME)); } @@ -211,6 +235,16 @@ public Optional getComment() { return comment; } + /** + * Get the hint used to to fulfill the aggregation. + * + * @return never {@literal null}. + * @since 3.1 + */ + public Optional getHint() { + return hint; + } + /** * @return the time limit for processing. {@link Duration#ZERO} is used for the default unbounded behavior. * @since 3.0 @@ -219,6 +253,23 @@ public Duration getMaxTime() { return maxTime; } + /** + * @return {@literal true} to skip results when running an aggregation. Useful in combination with {@code $merge} or + * {@code $out}. + * @since 3.0.2 + */ + public boolean isSkipResults() { + 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. @@ -238,6 +289,10 @@ Document applyAndReturnPotentiallyChangedCommand(Document command) { 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)); } @@ -267,6 +322,7 @@ public Document toDocument() { 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()); @@ -308,7 +364,10 @@ public static class Builder { 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. @@ -385,11 +444,24 @@ public Builder comment(@Nullable String comment) { 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 */ @@ -399,6 +471,58 @@ public Builder maxTime(@Nullable Duration maxTime) { return this; } + /** + * Run the aggregation, but do NOT read the aggregation result from the store.
        + * If the expected result of the aggregation is rather large, eg. when using an {@literal $out} operation, this + * option allows to execute the aggregation without having the cursor return the operation result. + * + * @return this. + * @since 3.0.2 + */ + public Builder skipOutput() { + + this.resultOptions = ResultOptions.SKIP; + 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. * @@ -406,12 +530,56 @@ public Builder maxTime(@Nullable Duration maxTime) { */ public AggregationOptions build() { - AggregationOptions options = new AggregationOptions(allowDiskUse, explain, cursor, collation, comment); + AggregationOptions options = new AggregationOptions(allowDiskUse, explain, cursor, collation, comment, hint); if (maxTime != null) { options.maxTime = maxTime; } + if (resultOptions != null) { + options.resultOptions = resultOptions; + } + if (domainTypeMapping != null) { + options.domainTypeMapping = domainTypeMapping; + } return options; } } + + /** + * @since 3.0 + */ + private enum ResultOptions { + + /** + * Just do it!, and do not read the operation result. + */ + SKIP, + /** + * Read the aggregation result from the cursor. + */ + 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 + } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationPipeline.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationPipeline.java new file mode 100644 index 0000000000..a316f04724 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationPipeline.java @@ -0,0 +1,162 @@ +/* + * Copyright 2020-2022 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.Collections; +import java.util.List; +import java.util.function.Predicate; + +import org.bson.Document; +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 { + + private final List pipeline; + + /** + * Create an empty pipeline + */ + public AggregationPipeline() { + this(new ArrayList<>()); + } + + /** + * Create a new pipeline with given {@link AggregationOperation stages}. + * + * @param aggregationOperations must not be {@literal null}. + */ + public AggregationPipeline(List aggregationOperations) { + + Assert.notNull(aggregationOperations, "AggregationOperations must not be null!"); + pipeline = new ArrayList<>(aggregationOperations); + } + + /** + * Append the given {@link AggregationOperation stage} to the pipeline. + * + * @param aggregationOperation must not be {@literal null}. + * @return this. + */ + public AggregationPipeline add(AggregationOperation aggregationOperation) { + + Assert.notNull(aggregationOperation, "AggregationOperation must not be null!"); + + pipeline.add(aggregationOperation); + return this; + } + + /** + * Get the list of {@link AggregationOperation aggregation stages}. + * + * @return never {@literal null}. + */ + public List getOperations() { + return Collections.unmodifiableList(pipeline); + } + + List toDocuments(AggregationOperationContext context) { + + verify(); + return AggregationOperationRenderer.toDocument(pipeline, context); + } + + /** + * @return {@literal true} if the last aggregation stage is either {@literal $out} or {@literal $merge}. + */ + public boolean isOutOrMerge() { + + if (isEmpty()) { + return false; + } + + AggregationOperation operation = pipeline.get(pipeline.size() - 1); + return isOut(operation) || isMerge(operation); + } + + void verify() { + + // check $out/$merge is the last operation if it exists + for (AggregationOperation operation : pipeline) { + + if (isOut(operation) && !isLast(operation)) { + throw new IllegalArgumentException("The $out operator must be the last stage in the pipeline."); + } + + if (isMerge(operation) && !isLast(operation)) { + 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 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"); + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationResults.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationResults.java index fd14b1a0b8..04fc9f371b 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationResults.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationResults.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2020 the original author or authors. + * Copyright 2013-2022 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. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationSpELExpression.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationSpELExpression.java index 0556410c89..78b1c741e6 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationSpELExpression.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationSpELExpression.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2020 the original author or authors. + * Copyright 2016-2022 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. @@ -24,15 +24,15 @@ * expression.
        *
        * Samples:
        - * *

        + * 
          * // { $and: [ { $gt: [ "$qty", 100 ] }, { $lt: [ "$qty", 250 ] } ] }
          * expressionOf("qty > 100 && qty < 250);
          *
          * // { $cond : { if : { $gte : [ "$a", 42 ]}, then : "answer", else : "no-answer" } }
          * expressionOf("cond(a >= 42, 'answer', 'no-answer')");
        - * 
        * + *
        * * @author Christoph Strobl * @author Mark Paluch diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationUpdate.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationUpdate.java index e5dadc8412..9ebc6de402 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationUpdate.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationUpdate.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 the original author or authors. + * Copyright 2019-2022 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. @@ -71,8 +71,7 @@ * * @author Christoph Strobl * @author Mark Paluch - * @see MongoDB + * @see MongoDB * Reference Documentation * @since 3.0 */ @@ -139,7 +138,7 @@ public AggregationUpdate set(SetOperation setOperation) { setOperation.getFields().forEach(it -> { keysTouched.add(it.getName()); }); - operations.add(setOperation); + pipeline.add(setOperation); return this; } @@ -155,7 +154,7 @@ public AggregationUpdate unset(UnsetOperation unsetOperation) { Assert.notNull(unsetOperation, "UnsetOperation must not be null!"); - operations.add(unsetOperation); + pipeline.add(unsetOperation); keysTouched.addAll(unsetOperation.removedFieldNames()); return this; } @@ -172,7 +171,7 @@ public AggregationUpdate unset(UnsetOperation unsetOperation) { public AggregationUpdate replaceWith(ReplaceWithOperation replaceWithOperation) { Assert.notNull(replaceWithOperation, "ReplaceWithOperation must not be null!"); - operations.add(replaceWithOperation); + pipeline.add(replaceWithOperation); return this; } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationUtils.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationUtils.java index d729b9dfaf..0cc3592f75 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationUtils.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2020 the original author or authors. + * Copyright 2017-2022 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. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ArithmeticOperators.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ArithmeticOperators.java index 2fbc64304e..477e8a900d 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ArithmeticOperators.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ArithmeticOperators.java @@ -1,5 +1,5 @@ /* - * Copyright 2016. the original author or authors. + * Copyright 2016-2022 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. @@ -17,19 +17,30 @@ import java.util.Collections; import java.util.List; +import java.util.Locale; +import org.bson.Document; import org.springframework.data.mongodb.core.aggregation.AccumulatorOperators.Avg; +import org.springframework.data.mongodb.core.aggregation.AccumulatorOperators.CovariancePop; +import org.springframework.data.mongodb.core.aggregation.AccumulatorOperators.CovarianceSamp; import org.springframework.data.mongodb.core.aggregation.AccumulatorOperators.Max; import org.springframework.data.mongodb.core.aggregation.AccumulatorOperators.Min; import org.springframework.data.mongodb.core.aggregation.AccumulatorOperators.StdDevPop; import org.springframework.data.mongodb.core.aggregation.AccumulatorOperators.StdDevSamp; import org.springframework.data.mongodb.core.aggregation.AccumulatorOperators.Sum; +import org.springframework.data.mongodb.core.aggregation.SetWindowFieldsOperation.WindowUnit; +import org.springframework.data.mongodb.core.aggregation.SetWindowFieldsOperation.WindowUnits; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; /** * Gateway to {@literal Arithmetic} aggregation operations that perform math operations on numbers. * * @author Christoph Strobl + * @author Mark Paluch + * @author Mushtaq Ahmed * @since 1.10 */ public class ArithmeticOperators { @@ -54,6 +65,17 @@ public static ArithmeticOperatorFactory valueOf(AggregationExpression expression return new ArithmeticOperatorFactory(expression); } + /** + * Creates new {@link AggregationExpression} that returns a random float between {@code 0} and {@code 1} each time it + * is called. + * + * @return new instance of {@link Rand}. + * @since 3.3 + */ + public static Rand rand() { + return new Rand(); + } + /** * @author Christoph Strobl */ @@ -147,6 +169,46 @@ public Ceil ceil() { return usesFieldRef() ? Ceil.ceilValueOf(fieldReference) : Ceil.ceilValueOf(expression); } + /** + * Creates new {@link AggregationExpression} that calculates the mathematical derivative value. + * + * @return new instance of {@link Derivative}. + * @since 3.3 + */ + public Derivative derivative() { + return derivative((String) null); + } + + /** + * Creates new {@link AggregationExpression} that calculates the mathematical derivative value. + * + * @param unit The time unit ({@link WindowUnits#WEEK}, {@link WindowUnits#DAY}, {@link WindowUnits#HOUR}, + * {@link WindowUnits#MINUTE}, {@link WindowUnits#SECOND}, {@link WindowUnits#MILLISECOND}) to apply. + * @return new instance of {@link Derivative}. + * @since 3.3 + */ + public Derivative derivative(WindowUnit unit) { + + Assert.notNull(unit, "Window unit must not be null"); + + return derivative(unit.name().toLowerCase(Locale.ROOT)); + } + + /** + * Creates new {@link AggregationExpression} that calculates the mathematical derivative value. + * + * @param unit The time unit ({@literal week, day, hour, minute, second, millisecond}) to apply can be + * {@literal null}. + * @return new instance of {@link Derivative}. + * @since 3.3 + */ + public Derivative derivative(@Nullable String unit) { + + Derivative derivative = usesFieldRef() ? Derivative.derivativeOf(fieldReference) + : Derivative.derivativeOf(expression); + return StringUtils.hasText(unit) ? derivative.unit(unit) : derivative; + } + /** * Creates new {@link AggregationExpression} that ivides the associated number by number referenced via * {@literal fieldReference}. @@ -208,6 +270,45 @@ public Floor floor() { return usesFieldRef() ? Floor.floorValueOf(fieldReference) : Floor.floorValueOf(expression); } + /** + * Creates new {@link AggregationExpression} that calculates the approximation for the mathematical integral value. + * + * @return new instance of {@link Integral}. + * @since 3.3 + */ + public Integral integral() { + return usesFieldRef() ? Integral.integralOf(fieldReference) : Integral.integralOf(expression); + } + + /** + * Creates new {@link AggregationExpression} that calculates the approximation for the mathematical integral value. + * + * @param unit The time unit ({@link WindowUnits#WEEK}, {@link WindowUnits#DAY}, {@link WindowUnits#HOUR}, + * {@link WindowUnits#MINUTE}, {@link WindowUnits#SECOND}, {@link WindowUnits#MILLISECOND}) to apply. + * @return new instance of {@link Derivative}. + * @since 3.3 + */ + public Integral integral(WindowUnit unit) { + + Assert.notNull(unit, "Window unit must not be null"); + + return integral(unit.name().toLowerCase(Locale.ROOT)); + } + + /** + * Creates new {@link AggregationExpression} that calculates the approximation for the mathematical integral value. + * + * @param unit the unit of measure. + * @return new instance of {@link Integral}. + * @since 3.3 + */ + public Integral integral(String unit) { + + Assert.hasText(unit, "Unit must not be empty!"); + + return integral().unit(unit); + } + /** * Creates new {@link AggregationExpression} that calculates the natural logarithm ln (i.e loge) of the assoicated * number. @@ -511,6 +612,63 @@ public StdDevSamp stdDevSamp() { : AccumulatorOperators.StdDevSamp.stdDevSampOf(expression); } + /** + * Creates new {@link AggregationExpression} that uses the previous input (field/expression) and the value of the + * given field to calculate the population covariance of the two. + * + * @param fieldReference must not be {@literal null}. + * @return new instance of {@link CovariancePop}. + * @since 3.3 + */ + public CovariancePop covariancePop(String fieldReference) { + return covariancePop().and(fieldReference); + } + + /** + * Creates new {@link AggregationExpression} that uses the previous input (field/expression) and the result of the + * given {@link AggregationExpression expression} to calculate the population covariance of the two. + * + * @param expression must not be {@literal null}. + * @return new instance of {@link CovariancePop}. + * @since 3.3 + */ + public CovariancePop covariancePop(AggregationExpression expression) { + return covariancePop().and(expression); + } + + private CovariancePop covariancePop() { + return usesFieldRef() ? CovariancePop.covariancePopOf(fieldReference) : CovariancePop.covariancePopOf(expression); + } + + /** + * Creates new {@link AggregationExpression} that uses the previous input (field/expression) and the value of the + * given field to calculate the sample covariance of the two. + * + * @param fieldReference must not be {@literal null}. + * @return new instance of {@link CovariancePop}. + * @since 3.3 + */ + public CovarianceSamp covarianceSamp(String fieldReference) { + return covarianceSamp().and(fieldReference); + } + + /** + * Creates new {@link AggregationExpression} that uses the previous input (field/expression) and the result of the + * given {@link AggregationExpression expression} to calculate the sample covariance of the two. + * + * @param expression must not be {@literal null}. + * @return new instance of {@link CovariancePop}. + * @since 3.3 + */ + public CovarianceSamp covarianceSamp(AggregationExpression expression) { + return covarianceSamp().and(expression); + } + + private CovarianceSamp covarianceSamp() { + return usesFieldRef() ? CovarianceSamp.covarianceSampOf(fieldReference) + : CovarianceSamp.covarianceSampOf(expression); + } + /** * Creates new {@link AggregationExpression} that rounds a number to a whole integer or to a specified decimal * place. @@ -532,6 +690,228 @@ public Round roundToPlace(int place) { return round().place(place); } + /** + * Creates new {@link AggregationExpression} that calculates the sine of a numeric value given in + * {@link AngularUnit#RADIANS radians}. + * + * @return new instance of {@link Sin}. + * @since 3.3 + */ + public Sin sin() { + return sin(AngularUnit.RADIANS); + } + + /** + * Creates new {@link AggregationExpression} that calculates the sine of a numeric value in the given + * {@link AngularUnit unit}. + * + * @param unit the unit of measure. + * @return new instance of {@link Sin}. + * @since 3.3 + */ + public Sin sin(AngularUnit unit) { + return usesFieldRef() ? Sin.sinOf(fieldReference, unit) : Sin.sinOf(expression, unit); + } + + /** + * Creates new {@link AggregationExpression} that calculates the sine of a numeric value given in + * {@link AngularUnit#RADIANS radians}. + * + * @return new instance of {@link Sinh}. + * @since 3.3 + */ + public Sinh sinh() { + return sinh(AngularUnit.RADIANS); + } + + /** + * Creates new {@link AggregationExpression} that calculates the sine of a numeric value. + * + * @param unit the unit of measure. + * @return new instance of {@link Sinh}. + * @since 3.3 + */ + public Sinh sinh(AngularUnit unit) { + return usesFieldRef() ? Sinh.sinhOf(fieldReference, unit) : Sinh.sinhOf(expression, unit); + } + + /** + * Creates new {@link AggregationExpression} that calculates the inverse sine of a numeric value. + * + * @return new instance of {@link ASin}. + * @since 3.3 + */ + public ASin asin() { + return usesFieldRef() ? ASin.asinOf(fieldReference) : ASin.asinOf(expression); + } + + /** + * Creates new {@link AggregationExpression} that calculates the inverse hyperbolic sine of a numeric value. + * + * @return new instance of {@link ASinh}. + * @since 3.3 + */ + public ASinh asinh() { + return usesFieldRef() ? ASinh.asinhOf(fieldReference) : ASinh.asinhOf(expression); + } + + /** + * Creates new {@link AggregationExpression} that calculates the cosine of a numeric value given in + * {@link AngularUnit#RADIANS radians}. + * + * @return new instance of {@link Cos}. + * @since 3.3 + */ + public Cos cos() { + return cos(AngularUnit.RADIANS); + } + + /** + * Creates new {@link AggregationExpression} that calculates the cosine of a numeric value in the given + * {@link AngularUnit unit}. + * + * @param unit the unit of measure. + * @return new instance of {@link Cos}. + * @since 3.3 + */ + public Cos cos(AngularUnit unit) { + return usesFieldRef() ? Cos.cosOf(fieldReference, unit) : Cos.cosOf(expression, unit); + } + + /** + * Creates new {@link AggregationExpression} that calculates the hyperbolic cosine of a numeric value given in + * {@link AngularUnit#RADIANS radians}. + * + * @return new instance of {@link Cosh}. + * @since 3.3 + */ + public Cosh cosh() { + return cosh(AngularUnit.RADIANS); + } + + /** + * Creates new {@link AggregationExpression} that calculates the hyperbolic cosine of a numeric value. + * + * @param unit the unit of measure. + * @return new instance of {@link Cosh}. + * @since 3.3 + */ + public Cosh cosh(AngularUnit unit) { + return usesFieldRef() ? Cosh.coshOf(fieldReference, unit) : Cosh.coshOf(expression, unit); + } + + /** + * Creates new {@link AggregationExpression} that calculates the tangent of a numeric value given in + * {@link AngularUnit#RADIANS radians}. + * + * @return new instance of {@link Tan}. + * @since 3.3 + */ + public Tan tan() { + return tan(AngularUnit.RADIANS); + } + + /** + * Creates new {@link AggregationExpression} that calculates the inverse tangent of a numeric value. + * + * @return new instance of {@link ATan}. + * @since 3.3 + */ + public ATan atan() { + return usesFieldRef() ? ATan.atanOf(fieldReference) : ATan.atanOf(expression); + } + + /** + * Creates new {@link AggregationExpression} that calculates the inverse tangent of the the numeric value divided by + * the given numeric value in the argument. + * + * @param value the numeric value + * @return new instance of {@link ATan2}. + * @since 3.3 + */ + public ATan2 atan2(Number value) { + + Assert.notNull(value, "Value must not be null!"); + return createATan2().atan2of(value); + } + + /** + * Creates new {@link AggregationExpression} that calculates the inverse tangent of the the numeric value divided by + * the given field reference in the argument. + * + * @param fieldReference the numeric value + * @return new instance of {@link ATan2}. + * @since 3.3 + */ + public ATan2 atan2(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return createATan2().atan2of(fieldReference); + } + + /** + * Creates new {@link AggregationExpression} that calculates the inverse tangent of the the numeric value divided by + * the given {@link AggregationExpression} in the argument. + * + * @param expression the expression evaluating to a numeric value + * @return new instance of {@link ATan2}. + * @since 3.3 + */ + public ATan2 atan2(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return createATan2().atan2of(expression); + } + + private ATan2 createATan2() { + + return usesFieldRef() ? ATan2.valueOf(fieldReference) : ATan2.valueOf(expression); + } + + /** + * Creates new {@link AggregationExpression} that calculates the inverse hyperbolic tangent of a numeric value. + * + * @return new instance of {@link ATanh}. + * @since 3.3 + */ + public ATanh atanh() { + return usesFieldRef() ? ATanh.atanhOf(fieldReference) : ATanh.atanhOf(expression); + } + + /** + * Creates new {@link AggregationExpression} that calculates the tangent of a numeric value in the given + * {@link AngularUnit unit}. + * + * @param unit the unit of measure. + * @return new instance of {@link Tan}. + * @since 3.3 + */ + public Tan tan(AngularUnit unit) { + return usesFieldRef() ? Tan.tanOf(fieldReference, unit) : Tan.tanOf(expression, unit); + } + + /** + * Creates new {@link AggregationExpression} that calculates the hyperbolic tangent of a numeric value given in + * {@link AngularUnit#RADIANS radians}. + * + * @return new instance of {@link Tan}. + * @since 3.3 + */ + public Tanh tanh() { + return tanh(AngularUnit.RADIANS); + } + + /** + * Creates new {@link AggregationExpression} that calculates the hyperbolic tangent of a numeric value. + * + * @param unit the unit of measure. + * @return new instance of {@link Tanh}. + * @since 3.3 + */ + public Tanh tanh(AngularUnit unit) { + return usesFieldRef() ? Tanh.tanhOf(fieldReference, unit) : Tanh.tanhOf(expression, unit); + } + private boolean usesFieldRef() { return fieldReference != null; } @@ -644,7 +1024,7 @@ public static Add valueOf(Number value) { /** * Add the value stored at the given field. - * + * * @param fieldReference must not be {@literal null}. * @return new instance of {@link Add}. */ @@ -656,7 +1036,7 @@ public Add add(String fieldReference) { /** * Add the evaluation result of the given {@link AggregationExpression}. - * + * * @param expression must not be {@literal null}. * @return new instance of {@link Add}. */ @@ -668,7 +1048,7 @@ public Add add(AggregationExpression expression) { /** * Add the given value. - * + * * @param value must not be {@literal null}. * @return new instance of {@link Add}. */ @@ -784,7 +1164,7 @@ public static Divide valueOf(Number value) { /** * Divide by the value stored at the given field. - * + * * @param fieldReference must not be {@literal null}. * @return new instance of {@link Divide}. */ @@ -796,7 +1176,7 @@ public Divide divideBy(String fieldReference) { /** * Divide by the evaluation results of the given {@link AggregationExpression}. - * + * * @param expression must not be {@literal null}. * @return new instance of {@link Divide}. */ @@ -808,7 +1188,7 @@ public Divide divideBy(AggregationExpression expression) { /** * Divide by the given value. - * + * * @param value must not be {@literal null}. * @return new instance of {@link Divide}. */ @@ -1030,7 +1410,7 @@ public static Log valueOf(Number value) { /** * Use the value stored at the given field as log base. - * + * * @param fieldReference must not be {@literal null}. * @return new instance of {@link Log}. */ @@ -1042,7 +1422,7 @@ public Log log(String fieldReference) { /** * Use the evaluated value of the given {@link AggregationExpression} as log base. - * + * * @param expression must not be {@literal null}. * @return new instance of {@link Log}. */ @@ -1054,7 +1434,7 @@ public Log log(AggregationExpression expression) { /** * Use the given value as log base. - * + * * @param base must not be {@literal null}. * @return new instance of {@link Log}. */ @@ -1170,7 +1550,7 @@ public static Mod valueOf(Number value) { /** * Use the value stored at the given field as mod base. - * + * * @param fieldReference must not be {@literal null}. * @return new instance of {@link Mod}. */ @@ -1182,7 +1562,7 @@ public Mod mod(String fieldReference) { /** * Use evaluated value of the given {@link AggregationExpression} as mod base. - * + * * @param expression must not be {@literal null}. * @return new instance of {@link Mod}. */ @@ -1194,7 +1574,7 @@ public Mod mod(AggregationExpression expression) { /** * Use the given value as mod base. - * + * * @param base must not be {@literal null}. * @return new instance of {@link Mod}. */ @@ -1257,7 +1637,7 @@ public static Multiply valueOf(Number value) { /** * Multiply by the value stored at the given field. - * + * * @param fieldReference must not be {@literal null}. * @return new instance of {@link Multiply}. */ @@ -1269,7 +1649,7 @@ public Multiply multiplyBy(String fieldReference) { /** * Multiply by the evaluated value of the given {@link AggregationExpression}. - * + * * @param expression must not be {@literal null}. * @return new instance of {@link Multiply}. */ @@ -1281,7 +1661,7 @@ public Multiply multiplyBy(AggregationExpression expression) { /** * Multiply by the given value. - * + * * @param value must not be {@literal null}. * @return new instance of {@link Multiply}. */ @@ -1344,7 +1724,7 @@ public static Pow valueOf(Number value) { /** * Pow by the value stored at the given field. - * + * * @param fieldReference must not be {@literal null}. * @return new instance of {@link Pow}. */ @@ -1356,7 +1736,7 @@ public Pow pow(String fieldReference) { /** * Pow by the evaluated value of the given {@link AggregationExpression}. - * + * * @param expression must not be {@literal null}. * @return new instance of {@link Pow}. */ @@ -1368,7 +1748,7 @@ public Pow pow(AggregationExpression expression) { /** * Pow by the given value. - * + * * @param value must not be {@literal null}. * @return new instance of {@link Pow}. */ @@ -1484,7 +1864,7 @@ public static Subtract valueOf(Number value) { /** * Subtract the value stored at the given field. - * + * * @param fieldReference must not be {@literal null}. * @return new instance of {@link Pow}. */ @@ -1496,7 +1876,7 @@ public Subtract subtract(String fieldReference) { /** * Subtract the evaluated value of the given {@link AggregationExpression}. - * + * * @param expression must not be {@literal null}. * @return new instance of {@link Pow}. */ @@ -1508,7 +1888,7 @@ public Subtract subtract(AggregationExpression expression) { /** * Subtract the given value. - * + * * @param value must not be {@literal null}. * @return new instance of {@link Pow}. */ @@ -1665,4 +2045,1028 @@ protected String getMongoMethod() { return "$round"; } } + + /** + * Value object to represent an {@link AggregationExpression expression} that calculates the average rate of change + * within the specified window. + * + * @author Christoph Strobl + * @since 3.3 + */ + public static class Derivative extends AbstractAggregationExpression { + + private Derivative(Object value) { + super(value); + } + + /** + * Create a new instance of {@link Derivative} for the value stored at the given field holding a numeric value. + * + * @param fieldReference must not be {@literal null}. + * @return new instance of {@link Derivative}. + */ + public static Derivative derivativeOf(String fieldReference) { + return new Derivative(Collections.singletonMap("input", Fields.field(fieldReference))); + } + + /** + * Create a new instance of {@link Derivative} for the value provided by the given expression that resolves to a + * numeric value. + * + * @param expression must not be {@literal null}. + * @return new instance of {@link Derivative}. + */ + public static Derivative derivativeOf(AggregationExpression expression) { + return new Derivative(Collections.singletonMap("input", expression)); + } + + public static Derivative derivativeOfValue(Number value) { + return new Derivative(Collections.singletonMap("input", value)); + } + + public Derivative unit(String unit) { + return new Derivative(append("unit", unit)); + } + + @Override + protected String getMongoMethod() { + return "$derivative"; + } + } + + /** + * Value object to represent an {@link AggregationExpression expression} that calculates the approximation for the + * mathematical integral value. + * + * @author Christoph Strobl + * @since 3.3 + */ + public static class Integral extends AbstractAggregationExpression { + + private Integral(Object value) { + super(value); + } + + /** + * Create a new instance of {@link Integral} for the value stored at the given field holding a numeric value. + * + * @param fieldReference must not be {@literal null}. + * @return new instance of {@link Integral}. + */ + public static Integral integralOf(String fieldReference) { + return new Integral(Collections.singletonMap("input", Fields.field(fieldReference))); + } + + /** + * Create a new instance of {@link Integral} for the value provided by the given expression that resolves to a + * numeric value. + * + * @param expression must not be {@literal null}. + * @return new instance of {@link Integral}. + */ + public static Integral integralOf(AggregationExpression expression) { + return new Integral(Collections.singletonMap("input", expression)); + } + + /** + * Set the unit of measure. + * + * @param unit the unit of measure. + * @return new instance of {@link Integral}. + */ + public Integral unit(String unit) { + return new Integral(append("unit", unit)); + } + + @Override + protected String getMongoMethod() { + return "$integral"; + } + } + + /** + * The unit of measure for computations that operate upon angles. + * + * @author Christoph Strobl + * @since 3.3 + */ + public enum AngularUnit { + RADIANS, DEGREES + } + + /** + * An {@link AggregationExpression expression} that calculates the sine of a value that is measured in radians. + * + * @author Christoph Strobl + * @since 3.3 + */ + public static class Sin extends AbstractAggregationExpression { + + private Sin(Object value) { + super(value); + } + + /** + * Creates a new {@link AggregationExpression} that calculates the sine of a value that is measured in + * {@link AngularUnit#RADIANS radians}. + *
        + * Use {@code sinhOf("angle", DEGREES)} as shortcut for + * + *
        +		 * { $sinh : { $degreesToRadians : "$angle" } }
        +		 * 
        + * + * . + * + * @param fieldReference the name of the {@link Field field} that resolves to a numeric value. + * @return new instance of {@link Sin}. + */ + public static Sin sinOf(String fieldReference) { + return sinOf(fieldReference, AngularUnit.RADIANS); + } + + /** + * Creates a new {@link AggregationExpression} that calculates the sine of a value that is measured in the given + * {@link AngularUnit unit}. + * + * @param fieldReference the name of the {@link Field field} that resolves to a numeric value. + * @param unit the unit of measure used by the value of the given field. + * @return new instance of {@link Sin}. + */ + public static Sin sinOf(String fieldReference, AngularUnit unit) { + return sin(Fields.field(fieldReference), unit); + } + + /** + * Creates a new {@link AggregationExpression} that calculates the sine of a value that is measured in + * {@link AngularUnit#RADIANS}. + * + * @param expression the {@link AggregationExpression expression} that resolves to a numeric value. + * @return new instance of {@link Sin}. + */ + public static Sin sinOf(AggregationExpression expression) { + return sinOf(expression, AngularUnit.RADIANS); + } + + /** + * Creates a new {@link AggregationExpression} that calculates the sine of a value that is measured in the given + * {@link AngularUnit unit}. + * + * @param expression the {@link AggregationExpression expression} that resolves to a numeric value. + * @param unit the unit of measure used by the value of the given field. + * @return new instance of {@link Sin}. + */ + public static Sin sinOf(AggregationExpression expression, AngularUnit unit) { + return sin(expression, unit); + } + + /** + * Creates a new {@link AggregationExpression} that calculates the sine of a value that is measured in + * {@link AngularUnit#RADIANS}. + * + * @param value anything ({@link Field field}, {@link AggregationExpression expression}, ...) that resolves to a + * numeric value + * @return new instance of {@link Sin}. + */ + public static Sin sin(Object value) { + return sin(value, AngularUnit.RADIANS); + } + + /** + * Creates a new {@link AggregationExpression} that calculates the sine of a value that is measured in the given + * {@link AngularUnit unit}. + * + * @param value anything ({@link Field field}, {@link AggregationExpression expression}, ...) that resolves to a + * numeric value. + * @param unit the unit of measure used by the value of the given field. + * @return new instance of {@link Sin}. + */ + public static Sin sin(Object value, AngularUnit unit) { + + if (ObjectUtils.nullSafeEquals(AngularUnit.DEGREES, unit)) { + return new Sin(ConvertOperators.DegreesToRadians.degreesToRadians(value)); + } + return new Sin(value); + } + + @Override + protected String getMongoMethod() { + return "$sin"; + } + } + + /** + * An {@link AggregationExpression expression} that calculates the hyperbolic sine of a value that is measured in + * {@link AngularUnit#RADIANS}. + * + * @author Christoph Strobl + * @since 3.3 + */ + public static class Sinh extends AbstractAggregationExpression { + + private Sinh(Object value) { + super(value); + } + + /** + * Creates a new {@link AggregationExpression} that calculates the hyperbolic sine of a value that is measured in + * {@link AngularUnit#RADIANS}. + * + * @param fieldReference the name of the {@link Field field} that resolves to a numeric value. + * @return new instance of {@link Sin}. + */ + public static Sinh sinhOf(String fieldReference) { + return sinhOf(fieldReference, AngularUnit.RADIANS); + } + + /** + * Creates a new {@link AggregationExpression} that calculates the hyperbolic sine of a value that is measured in + * the given {@link AngularUnit unit}. + *
        + * Use {@code sinhOf("angle", DEGREES)} as shortcut for + * + *
        +		 * { $sinh : { $degreesToRadians : "$angle" } }
        +		 * 
        + * + * . + * + * @param fieldReference the name of the {@link Field field} that resolves to a numeric value. + * @param unit the unit of measure used by the value of the given field. + * @return new instance of {@link Sin}. + */ + public static Sinh sinhOf(String fieldReference, AngularUnit unit) { + return sinh(Fields.field(fieldReference), unit); + } + + /** + * Creates a new {@link AggregationExpression} that calculates the hyperbolic sine of a value that is measured in + * {@link AngularUnit#RADIANS}. + *
        + * Use {@code sinhOf("angle", DEGREES)} as shortcut for eg. + * {@code sinhOf(ConvertOperators.valueOf("angle").degreesToRadians())}. + * + * @param expression the {@link AggregationExpression expression} that resolves to a numeric value. + * @return new instance of {@link Sin}. + */ + public static Sinh sinhOf(AggregationExpression expression) { + return sinhOf(expression, AngularUnit.RADIANS); + } + + /** + * Creates a new {@link AggregationExpression} that calculates the hyperbolic sine of a value that is measured in + * the given {@link AngularUnit unit}. + * + * @param expression the {@link AggregationExpression expression} that resolves to a numeric value. + * @param unit the unit of measure used by the value of the given field. + * @return new instance of {@link Sin}. + */ + public static Sinh sinhOf(AggregationExpression expression, AngularUnit unit) { + return sinh(expression, unit); + } + + /** + * Creates a new {@link AggregationExpression} that calculates the hyperbolic sine of a value that is measured in + * {@link AngularUnit#RADIANS}. + * + * @param value anything ({@link Field field}, {@link AggregationExpression expression}, ...) that resolves to a + * numeric value. + * @return new instance of {@link Sin}. + */ + public static Sinh sinh(Object value) { + return sinh(value, AngularUnit.RADIANS); + } + + /** + * Creates a new {@link AggregationExpression} that calculates the hyperbolic sine of a value that is measured in + * the given {@link AngularUnit unit}. + * + * @param value anything ({@link Field field}, {@link AggregationExpression expression}, ...) that resolves to a + * numeric value + * @param unit the unit of measure used by the value of the given field. + * @return new instance of {@link Sin}. + */ + public static Sinh sinh(Object value, AngularUnit unit) { + + if (ObjectUtils.nullSafeEquals(AngularUnit.DEGREES, unit)) { + return new Sinh(ConvertOperators.DegreesToRadians.degreesToRadians(value)); + } + return new Sinh(value); + } + + @Override + protected String getMongoMethod() { + return "$sinh"; + } + } + + /** + * An {@link AggregationExpression expression} that calculates the inverse sine of a value. + * + * @author Divya Srivastava + * @since 3.3 + */ + public static class ASin extends AbstractAggregationExpression { + + private ASin(Object value) { + super(value); + } + + /** + * Creates a new {@link AggregationExpression} that calculates the inverse sine of a value. + * + * @param fieldReference the name of the {@link Field field} that resolves to a numeric value. + * @return new instance of {@link ASin}. + */ + public static ASin asinOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new ASin(Fields.field(fieldReference)); + } + + /** + * Creates a new {@link AggregationExpression} that calculates the inverse sine of a value. + *
        + * + * @param expression the {@link AggregationExpression expression} that resolves to a numeric value. + * @return new instance of {@link ASin}. + */ + public static ASin asinOf(AggregationExpression expression) { + return new ASin(expression); + } + + /** + * Creates a new {@link AggregationExpression} that calculates the inverse sine of a value. + * + * @param value anything ({@link Field field}, {@link AggregationExpression expression}, ...) that resolves to a + * numeric value. + * @return new instance of {@link ASin}. + */ + public static ASin asinOf(Number value) { + return new ASin(value); + } + + @Override + protected String getMongoMethod() { + return "$asin"; + } + } + + /** + * An {@link AggregationExpression expression} that calculates the inverse hyperbolic sine of a value + * + * @author Divya Srivastava + * @since 3.3 + */ + public static class ASinh extends AbstractAggregationExpression { + + private ASinh(Object value) { + super(value); + } + + /** + * Creates a new {@link AggregationExpression} that calculates the inverse hyperbolic sine of a value. + * + * @param fieldReference the name of the {@link Field field} that resolves to a numeric value. + * @return new instance of {@link ASinh}. + */ + public static ASinh asinhOf(String fieldReference) { + return new ASinh(Fields.field(fieldReference)); + } + + /** + * Creates a new {@link AggregationExpression} that calculates the inverse hyperbolic sine of a value. + *
        + * + * @param expression the {@link AggregationExpression expression} that resolves to a numeric value. + * @return new instance of {@link ASinh}. + */ + public static ASinh asinhOf(AggregationExpression expression) { + return new ASinh(expression); + } + + /** + * Creates a new {@link AggregationExpression} that calculates the inverse hyperbolic sine of a value. + * + * @param value anything ({@link Field field}, {@link AggregationExpression expression}, ...) that resolves to a + * numeric value. + * @return new instance of {@link ASinh}. + */ + public static ASinh asinhOf(Object value) { + return new ASinh(value); + } + + @Override + protected String getMongoMethod() { + return "$asinh"; + } + } + + + /** + * An {@link AggregationExpression expression} that calculates the cosine of a value that is measured in radians. + * + * @author Christoph Strobl + * @since 3.3 + */ + public static class Cos extends AbstractAggregationExpression { + + private Cos(Object value) { + super(value); + } + + /** + * Creates a new {@link AggregationExpression} that calculates the cosine of a value that is measured in + * {@link AngularUnit#RADIANS radians}. + *
        + * Use {@code cosOf("angle", DEGREES)} as shortcut for + * + *
        +		 * { $cos : { $degreesToRadians : "$angle" } }
        +		 * 
        + * + * @param fieldReference the name of the {@link Field field} that resolves to a numeric value. + * @return new instance of {@link Cos}. + */ + public static Cos cosOf(String fieldReference) { + return cosOf(fieldReference, AngularUnit.RADIANS); + } + + /** + * Creates a new {@link AggregationExpression} that calculates the cosine of a value that is measured in the given + * {@link AngularUnit unit}. + * + * @param fieldReference the name of the {@link Field field} that resolves to a numeric value. + * @param unit the unit of measure used by the value of the given field. + * @return new instance of {@link Cos}. + */ + public static Cos cosOf(String fieldReference, AngularUnit unit) { + return cos(Fields.field(fieldReference), unit); + } + + /** + * Creates a new {@link AggregationExpression} that calculates the cosine of a value that is measured in + * {@link AngularUnit#RADIANS}. + * + * @param expression the {@link AggregationExpression expression} that resolves to a numeric value. + * @return new instance of {@link Cos}. + */ + public static Cos cosOf(AggregationExpression expression) { + return cosOf(expression, AngularUnit.RADIANS); + } + + /** + * Creates a new {@link AggregationExpression} that calculates the cosine of a value that is measured in the given + * {@link AngularUnit unit}. + * + * @param expression the {@link AggregationExpression expression} that resolves to a numeric value. + * @param unit the unit of measure used by the value of the given field. + * @return new instance of {@link Cos}. + */ + public static Cos cosOf(AggregationExpression expression, AngularUnit unit) { + return cos(expression, unit); + } + + /** + * Creates a new {@link AggregationExpression} that calculates the cosine of a value that is measured in + * {@link AngularUnit#RADIANS}. + * + * @param value anything ({@link Field field}, {@link AggregationExpression expression}, ...) that resolves to a + * numeric value + * @return new instance of {@link Cos}. + */ + public static Cos cos(Object value) { + return cos(value, AngularUnit.RADIANS); + } + + /** + * Creates a new {@link AggregationExpression} that calculates the cosine of a value that is measured in the given + * {@link AngularUnit unit}. + * + * @param value anything ({@link Field field}, {@link AggregationExpression expression}, ...) that resolves to a + * numeric value. + * @param unit the unit of measure used by the value of the given field. + * @return new instance of {@link Cos}. + */ + public static Cos cos(Object value, AngularUnit unit) { + + if (ObjectUtils.nullSafeEquals(AngularUnit.DEGREES, unit)) { + return new Cos(ConvertOperators.DegreesToRadians.degreesToRadians(value)); + } + return new Cos(value); + } + + @Override + protected String getMongoMethod() { + return "$cos"; + } + } + + /** + * An {@link AggregationExpression expression} that calculates the hyperbolic cosine of a value that is measured in + * {@link AngularUnit#RADIANS}. + * + * @author Christoph Strobl + * @since 3.3 + */ + public static class Cosh extends AbstractAggregationExpression { + + private Cosh(Object value) { + super(value); + } + + /** + * Creates a new {@link AggregationExpression} that calculates the hyperbolic cosine of a value that is measured in + * {@link AngularUnit#RADIANS}. + * + * @param fieldReference the name of the {@link Field field} that resolves to a numeric value. + * @return new instance of {@link Cosh}. + */ + public static Cosh coshOf(String fieldReference) { + return coshOf(fieldReference, AngularUnit.RADIANS); + } + + /** + * Creates a new {@link AggregationExpression} that calculates the hyperbolic cosine of a value that is measured in + * the given {@link AngularUnit unit}. + *
        + * Use {@code coshOf("angle", DEGREES)} as shortcut for + * + *
        +		 * { $cosh : { $degreesToRadians : "$angle" } }
        +		 * 
        + * + * @param fieldReference the name of the {@link Field field} that resolves to a numeric value. + * @param unit the unit of measure used by the value of the given field. + * @return new instance of {@link Cosh}. + */ + public static Cosh coshOf(String fieldReference, AngularUnit unit) { + return cosh(Fields.field(fieldReference), unit); + } + + /** + * Creates a new {@link AggregationExpression} that calculates the hyperbolic cosine of a value that is measured in + * {@link AngularUnit#RADIANS}. + *
        + * Use {@code sinhOf("angle", DEGREES)} as shortcut for eg. + * {@code sinhOf(ConvertOperators.valueOf("angle").degreesToRadians())}. + * + * @param expression the {@link AggregationExpression expression} that resolves to a numeric value. + * @return new instance of {@link Cosh}. + */ + public static Cosh coshOf(AggregationExpression expression) { + return coshOf(expression, AngularUnit.RADIANS); + } + + /** + * Creates a new {@link AggregationExpression} that calculates the hyperbolic cosine of a value that is measured in + * the given {@link AngularUnit unit}. + * + * @param expression the {@link AggregationExpression expression} that resolves to a numeric value. + * @param unit the unit of measure used by the value of the given field. + * @return new instance of {@link Cosh}. + */ + public static Cosh coshOf(AggregationExpression expression, AngularUnit unit) { + return cosh(expression, unit); + } + + /** + * Creates a new {@link AggregationExpression} that calculates the hyperbolic cosine of a value that is measured in + * {@link AngularUnit#RADIANS}. + * + * @param value anything ({@link Field field}, {@link AggregationExpression expression}, ...) that resolves to a + * numeric value. + * @return new instance of {@link Cosh}. + */ + public static Cosh cosh(Object value) { + return cosh(value, AngularUnit.RADIANS); + } + + /** + * Creates a new {@link AggregationExpression} that calculates the hyperbolic cosine of a value that is measured in + * the given {@link AngularUnit unit}. + * + * @param value anything ({@link Field field}, {@link AggregationExpression expression}, ...) that resolves to a + * numeric value + * @param unit the unit of measure used by the value of the given field. + * @return new instance of {@link Cosh}. + */ + public static Cosh cosh(Object value, AngularUnit unit) { + + if (ObjectUtils.nullSafeEquals(AngularUnit.DEGREES, unit)) { + return new Cosh(ConvertOperators.DegreesToRadians.degreesToRadians(value)); + } + return new Cosh(value); + } + + @Override + protected String getMongoMethod() { + return "$cosh"; + } + } + + /** + * An {@link AggregationExpression expression} that calculates the tangent of a value that is measured in radians. + * + * @author Christoph Strobl + * @since 3.3 + */ + public static class Tan extends AbstractAggregationExpression { + + private Tan(Object value) { + super(value); + } + + /** + * Creates a new {@link AggregationExpression} that calculates the tangent of a value that is measured in + * {@link AngularUnit#RADIANS radians}. + *
        + * Use {@code tanOf("angle", DEGREES)} as shortcut for + * + *
        +		 * { $tan : { $degreesToRadians : "$angle" } }
        +		 * 
        + * + * @param fieldReference the name of the {@link Field field} that resolves to a numeric value. + * @return new instance of {@link Tan}. + */ + public static Tan tanOf(String fieldReference) { + return tanOf(fieldReference, AngularUnit.RADIANS); + } + + /** + * Creates a new {@link AggregationExpression} that calculates the tangent of a value that is measured in the given + * {@link AngularUnit unit}. + * + * @param fieldReference the name of the {@link Field field} that resolves to a numeric value. + * @param unit the unit of measure used by the value of the given field. + * @return new instance of {@link Tan}. + */ + public static Tan tanOf(String fieldReference, AngularUnit unit) { + return tan(Fields.field(fieldReference), unit); + } + + /** + * Creates a new {@link AggregationExpression} that calculates the tangent of a value that is measured in + * {@link AngularUnit#RADIANS}. + * + * @param expression the {@link AggregationExpression expression} that resolves to a numeric value. + * @return new instance of {@link Tan}. + */ + public static Tan tanOf(AggregationExpression expression) { + return tanOf(expression, AngularUnit.RADIANS); + } + + /** + * Creates a new {@link AggregationExpression} that calculates the tangent of a value that is measured in the given + * {@link AngularUnit unit}. + * + * @param expression the {@link AggregationExpression expression} that resolves to a numeric value. + * @param unit the unit of measure used by the value of the given field. + * @return new instance of {@link Tan}. + */ + public static Tan tanOf(AggregationExpression expression, AngularUnit unit) { + return tan(expression, unit); + } + + /** + * Creates a new {@link AggregationExpression} that calculates the tangent of a value that is measured in + * {@link AngularUnit#RADIANS}. + * + * @param value anything ({@link Field field}, {@link AggregationExpression expression}, ...) that resolves to a + * numeric value + * @return new instance of {@link Tan}. + */ + public static Tan tan(Object value) { + return tan(value, AngularUnit.RADIANS); + } + + /** + * Creates a new {@link AggregationExpression} that calculates the tangent of a value that is measured in the given + * {@link AngularUnit unit}. + * + * @param value anything ({@link Field field}, {@link AggregationExpression expression}, ...) that resolves to a + * numeric value. + * @param unit the unit of measure used by the value of the given field. + * @return new instance of {@link Tan}. + */ + public static Tan tan(Object value, AngularUnit unit) { + + if (ObjectUtils.nullSafeEquals(AngularUnit.DEGREES, unit)) { + return new Tan(ConvertOperators.DegreesToRadians.degreesToRadians(value)); + } + return new Tan(value); + } + + @Override + protected String getMongoMethod() { + return "$tan"; + } + } + + /** + * An {@link AggregationExpression expression} that calculates the inverse tangent of a value. + * + * @author Divya Srivastava + * @since 3.3 + */ + public static class ATan extends AbstractAggregationExpression { + + private ATan(Object value) { + super(value); + } + + /** + * Creates a new {@link AggregationExpression} that calculates the inverse tangent of a value. + * + * @param fieldReference the name of the {@link Field field} that resolves to a numeric value. + * @return new instance of {@link ATan}. + */ + public static ATan atanOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new ATan(Fields.field(fieldReference)); + } + + /** + * Creates a new {@link AggregationExpression} that calculates the inverse tangent of a value. + * + * @param expression the {@link AggregationExpression expression} that resolves to a numeric value. + * @return new instance of {@link ATan}. + */ + public static ATan atanOf(AggregationExpression expression) { + return new ATan(expression); + } + + /** + * Creates a new {@link AggregationExpression} that calculates the inverse tangent of a value. + * + * @param value anything ({@link Field field}, {@link AggregationExpression expression}, ...) that resolves to a + * numeric value. + * @return new instance of {@link ATan}. + */ + public static ATan atanOf(Number value) { + return new ATan(value); + } + + @Override + protected String getMongoMethod() { + return "$atan"; + } + } + + /** + * An {@link AggregationExpression expression} that calculates the inverse tangent of y / x, where y and x are the + * first and second values passed to the expression respectively. + * + * @author Divya Srivastava + * @since 3.3 + */ + public static class ATan2 extends AbstractAggregationExpression { + + private ATan2(List value) { + super(value); + } + + /** + * Creates a new {@link AggregationExpression} that calculates the inverse tangent of of y / x, where y and x are + * the first and second values passed to the expression respectively. + * + * @param fieldReference the name of the {@link Field field} that resolves to a numeric value. + * @return new instance of {@link ATan2}. + */ + public static ATan2 valueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new ATan2(asFields(fieldReference)); + } + + /** + * Creates a new {@link AggregationExpression} that calculates the inverse tangent of of y / x, where y and x are + * the first and second values passed to the expression respectively. + * + * @param expression the {@link AggregationExpression expression} that resolves to a numeric value. + * @return new instance of {@link ATan2}. + */ + public static ATan2 valueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new ATan2((Collections.singletonList(expression))); + } + + + /** + * Creates a new {@link AggregationExpression} that calculates the inverse tangent of of y / x, where y and x are + * the first and second values passed to the expression respectively. + * + * @param fieldReference anything ({@link Field field}, {@link AggregationExpression expression}, ...) that resolves to a + * numeric value. + * @return new instance of {@link ATan2}. + */ + public ATan2 atan2of(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new ATan2(append(Fields.field(fieldReference))); + } + + /** + * Creates a new {@link AggregationExpression} that calculates the hyperbolic tangent of a value that is measured in + * {@link AngularUnit#RADIANS}. + * + * @param expression anything ({@link Field field}, {@link AggregationExpression expression}, ...) that resolves to a + * numeric value. + * @return new instance of {@link ATan2}. + */ + public ATan2 atan2of(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new ATan2(append(expression)); + } + + /** + * Creates a new {@link AggregationExpression} that calculates the inverse tangent of of y / x, where y and x are + * the first and second values passed to the expression respectively. + * + * @param value of type {@link Number} + * @return new instance of {@link ATan2}. + */ + public ATan2 atan2of(Number value) { + return new ATan2(append(value)); + } + + @Override + protected String getMongoMethod() { + return "$atan2"; + } + } + + /** + * An {@link AggregationExpression expression} that calculates the hyperbolic tangent of a value that is measured in + * {@link AngularUnit#RADIANS}. + * + * @author Christoph Strobl + * @since 3.3 + */ + public static class Tanh extends AbstractAggregationExpression { + + private Tanh(Object value) { + super(value); + } + + /** + * Creates a new {@link AggregationExpression} that calculates the hyperbolic tangent of a value that is measured in + * {@link AngularUnit#RADIANS}. + * + * @param fieldReference the name of the {@link Field field} that resolves to a numeric value. + * @return new instance of {@link Tanh}. + */ + public static Tanh tanhOf(String fieldReference) { + return tanhOf(fieldReference, AngularUnit.RADIANS); + } + + /** + * Creates a new {@link AggregationExpression} that calculates the hyperbolic tangent of a value that is measured in + * the given {@link AngularUnit unit}. + *
        + * Use {@code tanhOf("angle", DEGREES)} as shortcut for + * + *
        +		 * { $tanh : { $degreesToRadians : "$angle" } }
        +		 * 
        + * + * @param fieldReference the name of the {@link Field field} that resolves to a numeric value. + * @param unit the unit of measure used by the value of the given field. + * @return new instance of {@link Tanh}. + */ + public static Tanh tanhOf(String fieldReference, AngularUnit unit) { + return tanh(Fields.field(fieldReference), unit); + } + + /** + * Creates a new {@link AggregationExpression} that calculates the hyperbolic tangent of a value that is measured in + * {@link AngularUnit#RADIANS}. + *
        + * Use {@code sinhOf("angle", DEGREES)} as shortcut for eg. + * {@code sinhOf(ConvertOperators.valueOf("angle").degreesToRadians())}. + * + * @param expression the {@link AggregationExpression expression} that resolves to a numeric value. + * @return new instance of {@link Tanh}. + */ + public static Tanh tanhOf(AggregationExpression expression) { + return tanhOf(expression, AngularUnit.RADIANS); + } + + /** + * Creates a new {@link AggregationExpression} that calculates the hyperbolic tangent of a value that is measured in + * the given {@link AngularUnit unit}. + * + * @param expression the {@link AggregationExpression expression} that resolves to a numeric value. + * @param unit the unit of measure used by the value of the given field. + * @return new instance of {@link Tanh}. + */ + public static Tanh tanhOf(AggregationExpression expression, AngularUnit unit) { + return tanh(expression, unit); + } + + /** + * Creates a new {@link AggregationExpression} that calculates the hyperbolic tangent of a value that is measured in + * {@link AngularUnit#RADIANS}. + * + * @param value anything ({@link Field field}, {@link AggregationExpression expression}, ...) that resolves to a + * numeric value. + * @return new instance of {@link Tanh}. + */ + public static Tanh tanh(Object value) { + return tanh(value, AngularUnit.RADIANS); + } + + /** + * Creates a new {@link AggregationExpression} that calculates the hyperbolic tangent of a value that is measured in + * the given {@link AngularUnit unit}. + * + * @param value anything ({@link Field field}, {@link AggregationExpression expression}, ...) that resolves to a + * numeric value + * @param unit the unit of measure used by the value of the given field. + * @return new instance of {@link Tanh}. + */ + public static Tanh tanh(Object value, AngularUnit unit) { + + if (ObjectUtils.nullSafeEquals(AngularUnit.DEGREES, unit)) { + return new Tanh(ConvertOperators.DegreesToRadians.degreesToRadians(value)); + } + return new Tanh(value); + } + + @Override + protected String getMongoMethod() { + return "$tanh"; + } + } + + /** + * An {@link AggregationExpression expression} that calculates the inverse hyperbolic tangent of a value + * + * @author Divya Srivastava + * @since 3.3 + */ + public static class ATanh extends AbstractAggregationExpression { + + private ATanh(Object value) { + super(value); + } + + /** + * Creates a new {@link AggregationExpression} that calculates the inverse + * hyperbolic tangent of a value. + * + * @param fieldReference the name of the {@link Field field} that resolves to a + * numeric value. + * @return new instance of {@link ATanh}. + */ + public static ATanh atanhOf(String fieldReference) { + return new ATanh(Fields.field(fieldReference)); + } + + /** + * Creates a new {@link AggregationExpression} that calculates the inverse hyperbolic tangent of a value. + *
        + * + * @param expression the {@link AggregationExpression expression} that resolves to a numeric value. + * @return new instance of {@link ATanh}. + */ + public static ATanh atanhOf(AggregationExpression expression) { + return new ATanh(expression); + } + + /** + * Creates a new {@link AggregationExpression} that calculates the inverse + * hyperbolic tangent of a value. + * + * @param value anything ({@link Field field}, {@link AggregationExpression + * expression}, ...) that resolves to a numeric value. + * @return new instance of {@link ATanh}. + */ + public static ATanh atanhOf(Object value) { + return new ATanh(value); + } + + @Override + protected String getMongoMethod() { + return "$atanh"; + } + } + + /** + * {@link Rand} returns a floating value between 0 and 1. + * + * @author Mushtaq Ahmed + * @since 3.3 + */ + public static class Rand implements AggregationExpression { + + @Override + public Document toDocument(AggregationOperationContext context) { + return new Document("$rand", new Document()); + } + } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ArrayOperators.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ArrayOperators.java index 5a982c06f8..fd22a3ac12 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ArrayOperators.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ArrayOperators.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2020 the original author or authors. + * Copyright 2016-2022 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. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/BooleanOperators.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/BooleanOperators.java index b651a89616..faa4707d40 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/BooleanOperators.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/BooleanOperators.java @@ -1,5 +1,5 @@ /* - * Copyright 2016. the original author or authors. + * Copyright 2016-2022 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. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/BucketAutoOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/BucketAutoOperation.java index bb7e032efa..8648a39a27 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/BucketAutoOperation.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/BucketAutoOperation.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2020 the original author or authors. + * Copyright 2016-2022 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. @@ -15,12 +15,11 @@ */ package org.springframework.data.mongodb.core.aggregation; +import org.bson.Document; import org.springframework.data.mongodb.core.aggregation.BucketAutoOperation.BucketAutoOperationOutputBuilder; import org.springframework.data.mongodb.core.aggregation.BucketOperationSupport.OutputBuilder; import org.springframework.util.Assert; -import org.bson.Document; - /** * Encapsulates the aggregation framework {@code $bucketAuto}-operation.
        * Bucket stage is typically used with {@link Aggregation} and {@code $facet}. Categorizes incoming documents into a @@ -29,8 +28,7 @@ * We recommend to use the static factory method {@link Aggregation#bucketAuto(String, int)} instead of creating * instances of this class directly. * - * @see https://docs.mongodb.org/manual/reference/aggregation/bucketAuto/ + * @see https://docs.mongodb.org/manual/reference/aggregation/bucketAuto/ * @see BucketOperationSupport * @author Mark Paluch * @author Christoph Strobl @@ -106,7 +104,16 @@ public Document toDocument(AggregationOperationContext context) { options.putAll(super.toDocument(context)); - return new Document("$bucketAuto", options); + return new Document(getOperator(), options); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.AggregationOperation#getOperator() + */ + @Override + public String getOperator() { + return "$bucketAuto"; } /** @@ -240,8 +247,7 @@ public interface Granularity { /** * Supported MongoDB granularities. * - * @see https://docs.mongodb.com/manual/reference/operator/aggregation/bucketAuto/#granularity * @author Mark Paluch */ public enum Granularities implements Granularity { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/BucketOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/BucketOperation.java index 9fb67e624b..2fb14f646d 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/BucketOperation.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/BucketOperation.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2020 the original author or authors. + * Copyright 2016-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,17 +20,14 @@ import java.util.Collections; import java.util.List; +import org.bson.Document; import org.springframework.data.mongodb.core.aggregation.BucketOperation.BucketOperationOutputBuilder; import org.springframework.util.Assert; -import org.bson.Document; - /** * Encapsulates the aggregation framework {@code $bucket}-operation.
        - * * Bucket stage is typically used with {@link Aggregation} and {@code $facet}. Categorizes incoming documents into * groups, called buckets, based on a specified expression and bucket boundaries.
        - * * We recommend to use the static factory method {@link Aggregation#bucket(String)} instead of creating instances of * this class directly. * @@ -103,7 +100,16 @@ public Document toDocument(AggregationOperationContext context) { options.putAll(super.toDocument(context)); - return new Document("$bucket", options); + return new Document(getOperator(), options); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.AggregationOperation#getOperator() + */ + @Override + public String getOperator() { + return "$bucket"; } /** @@ -204,8 +210,8 @@ public static class ExpressionBucketOperationBuilder extends ExpressionBucketOperationBuilderSupport { /** - * Creates a new {@link ExpressionBucketOperationBuilderSupport} for the given value, {@link BucketOperation} - * and parameters. + * Creates a new {@link ExpressionBucketOperationBuilderSupport} for the given value, {@link BucketOperation} and + * parameters. * * @param expression must not be {@literal null}. * @param operation must not be {@literal null}. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/BucketOperationSupport.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/BucketOperationSupport.java index feee4422e2..3ee2c78b73 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/BucketOperationSupport.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/BucketOperationSupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2020 the original author or authors. + * Copyright 2016-2022 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. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ComparisonOperators.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ComparisonOperators.java index 70bda575e7..c85cf855cf 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ComparisonOperators.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ComparisonOperators.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2020 the original author or authors. + * Copyright 2016-2022 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. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ConditionalOperators.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ConditionalOperators.java index 5c9d1f3b92..c4af12d3eb 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ConditionalOperators.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ConditionalOperators.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2020 the original author or authors. + * Copyright 2016-2022 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. @@ -17,6 +17,7 @@ import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.List; @@ -235,7 +236,7 @@ private boolean usesCriteriaDefinition() { * * @author Mark Paluch * @see https://docs.mongodb.com/manual/reference/operator/aggregation/ifNull/ + * "https://docs.mongodb.com/manual/reference/operator/aggregation/ifNull/">https://docs.mongodb.com/manual/reference/operator/aggregation/ifNull/ */ public static class IfNull implements AggregationExpression { @@ -251,7 +252,8 @@ private IfNull(Object condition, Object value) { /** * Creates new {@link IfNull}. * - * @param fieldReference the field to check for a {@literal null} value, field reference must not be {@literal null}. + * @param fieldReference the field to check for a {@literal null} value, field reference must not be + * {@literal null}. * @return never {@literal null}. */ public static ThenBuilder ifNull(String fieldReference) { @@ -264,7 +266,7 @@ public static ThenBuilder ifNull(String fieldReference) { * Creates new {@link IfNull}. * * @param expression the expression to check for a {@literal null} value, field reference must not be - * {@literal null}. + * {@literal null}. * @return never {@literal null}. */ public static ThenBuilder ifNull(AggregationExpression expression) { @@ -282,19 +284,29 @@ public Document toDocument(AggregationOperationContext context) { List list = new ArrayList(); - if (condition instanceof Field) { - list.add(context.getReference((Field) condition).toString()); - } else if (condition instanceof AggregationExpression) { - list.add(((AggregationExpression) condition).toDocument(context)); + if (condition instanceof Collection) { + for (Object val : ((Collection) this.condition)) { + list.add(mapCondition(val, context)); + } } else { - list.add(condition); + list.add(mapCondition(condition, context)); } list.add(resolve(value, context)); - return new Document("$ifNull", list); } + private Object mapCondition(Object condition, AggregationOperationContext context) { + + if (condition instanceof Field) { + return context.getReference((Field) condition).toString(); + } else if (condition instanceof AggregationExpression) { + return ((AggregationExpression) condition).toDocument(context); + } else { + return condition; + } + } + private Object resolve(Object value, AggregationOperationContext context) { if (value instanceof Field) { @@ -315,28 +327,48 @@ public interface IfNullBuilder { /** * @param fieldReference the field to check for a {@literal null} value, field reference must not be - * {@literal null}. + * {@literal null}. * @return the {@link ThenBuilder} */ ThenBuilder ifNull(String fieldReference); /** * @param expression the expression to check for a {@literal null} value, field name must not be {@literal null} - * or empty. - * @return the {@link ThenBuilder} + * or empty. + * @return the {@link ThenBuilder}. */ ThenBuilder ifNull(AggregationExpression expression); } + /** + * @author Christoph Strobl + * @since 3.3 + */ + public interface OrBuilder { + + /** + * @param fieldReference the field to check for a {@literal null} value, field reference must not be + * {@literal null}. + * @return the {@link ThenBuilder} + */ + ThenBuilder orIfNull(String fieldReference); + + /** + * @param expression the expression to check for a {@literal null} value, + * @return the {@link ThenBuilder}. + */ + ThenBuilder orIfNull(AggregationExpression expression); + } + /** * @author Mark Paluch */ - public interface ThenBuilder { + public interface ThenBuilder extends OrBuilder { /** * @param value the value to be used if the {@code $ifNull} condition evaluates {@literal true}. Can be a - * {@link Document}, a value that is supported by MongoDB or a value that can be converted to a MongoDB - * representation but must not be {@literal null}. + * {@link Document}, a value that is supported by MongoDB or a value that can be converted to a MongoDB + * representation but must not be {@literal null}. * @return new instance of {@link IfNull}. */ IfNull then(Object value); @@ -361,9 +393,10 @@ public interface ThenBuilder { */ static final class IfNullOperatorBuilder implements IfNullBuilder, ThenBuilder { - private @Nullable Object condition; + private @Nullable List conditions; private IfNullOperatorBuilder() { + conditions = new ArrayList<>(); } /** @@ -381,7 +414,7 @@ public static IfNullOperatorBuilder newBuilder() { public ThenBuilder ifNull(String fieldReference) { Assert.hasText(fieldReference, "FieldReference name must not be null or empty!"); - this.condition = Fields.field(fieldReference); + this.conditions.add(Fields.field(fieldReference)); return this; } @@ -392,15 +425,25 @@ public ThenBuilder ifNull(String fieldReference) { public ThenBuilder ifNull(AggregationExpression expression) { Assert.notNull(expression, "AggregationExpression name must not be null or empty!"); - this.condition = expression; + this.conditions.add(expression); return this; } + @Override + public ThenBuilder orIfNull(String fieldReference) { + return ifNull(fieldReference); + } + + @Override + public ThenBuilder orIfNull(AggregationExpression expression) { + return ifNull(expression); + } + /* (non-Javadoc) * @see org.springframework.data.mongodb.core.aggregation.ConditionalOperators.IfNull.ThenBuilder#then(java.lang.Object) */ public IfNull then(Object value) { - return new IfNull(condition, value); + return new IfNull(conditions, value); } /* (non-Javadoc) @@ -409,7 +452,7 @@ public IfNull then(Object value) { public IfNull thenValueOf(String fieldReference) { Assert.notNull(fieldReference, "FieldReference must not be null!"); - return new IfNull(condition, Fields.field(fieldReference)); + return new IfNull(conditions, Fields.field(fieldReference)); } /* (non-Javadoc) @@ -418,7 +461,7 @@ public IfNull thenValueOf(String fieldReference) { public IfNull thenValueOf(AggregationExpression expression) { Assert.notNull(expression, "Expression must not be null!"); - return new IfNull(condition, expression); + return new IfNull(conditions, expression); } } } @@ -458,7 +501,7 @@ public static Switch switchCases(CaseOperator... conditions) { public static Switch switchCases(List conditions) { Assert.notNull(conditions, "Conditions must not be null!"); - return new Switch(Collections.singletonMap("branches", new ArrayList(conditions))); + return new Switch(Collections. singletonMap("branches", new ArrayList(conditions))); } /** @@ -545,7 +588,7 @@ public interface ThenBuilder { * @author Mark Paluch * @author Christoph Strobl * @see https://docs.mongodb.com/manual/reference/operator/aggregation/cond/ + * "https://docs.mongodb.com/manual/reference/operator/aggregation/cond/">https://docs.mongodb.com/manual/reference/operator/aggregation/cond/ */ public static class Cond implements AggregationExpression { @@ -806,8 +849,8 @@ public interface ThenBuilder { /** * @param value the value to be used if the condition evaluates {@literal true}. Can be a {@link Document}, a - * value that is supported by MongoDB or a value that can be converted to a MongoDB representation but - * must not be {@literal null}. + * value that is supported by MongoDB or a value that can be converted to a MongoDB representation but + * must not be {@literal null}. * @return the {@link OtherwiseBuilder} */ OtherwiseBuilder then(Object value); @@ -832,8 +875,8 @@ public interface OtherwiseBuilder { /** * @param value the value to be used if the condition evaluates {@literal false}. Can be a {@link Document}, a - * value that is supported by MongoDB or a value that can be converted to a MongoDB representation but - * must not be {@literal null}. + * value that is supported by MongoDB or a value that can be converted to a MongoDB representation but + * must not be {@literal null}. * @return the {@link Cond} */ Cond otherwise(Object value); @@ -861,8 +904,7 @@ static class ConditionalExpressionBuilder implements WhenBuilder, ThenBuilder, O private @Nullable Object condition; private @Nullable Object thenValue; - private ConditionalExpressionBuilder() { - } + private ConditionalExpressionBuilder() {} /** * Creates a new builder for {@link Cond}. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ConvertOperators.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ConvertOperators.java index 7d273dc751..be939224f4 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ConvertOperators.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ConvertOperators.java @@ -1,5 +1,5 @@ /* - * Copyright 2018-2020 the original author or authors. + * Copyright 2018-2022 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. @@ -231,6 +231,17 @@ public ToString convertToString() { return ToString.toString(valueObject()); } + /** + * {@link AggregationExpression} for {@code $degreesToRadians} that converts an input value measured in degrees to + * radians. + * + * @return new instance of {@link DegreesToRadians}. + * @since 3.3 + */ + public DegreesToRadians convertDegreesToRadians() { + return DegreesToRadians.degreesToRadians(valueObject()); + } + private Convert createConvert() { return usesFieldRef() ? Convert.convertValueOf(fieldReference) : Convert.convertValueOf(expression); } @@ -317,9 +328,9 @@ public Convert to(String stringTypeIdentifier) { *
        1
        *
        double
        *
        2
        - *
        string + *
        string
        *
        7
        - *
        objectId + *
        objectId
        *
        8
        *
        bool
        *
        9
        @@ -692,4 +703,52 @@ protected String getMongoMethod() { return "$toString"; } } + + /** + * {@link AggregationExpression} for {@code $degreesToRadians} that converts an input value measured in degrees to radians. + * + * @author Christoph Strobl + * @since 3.3 + */ + public static class DegreesToRadians extends AbstractAggregationExpression { + + private DegreesToRadians(Object value) { + super(value); + } + + /** + * Create a new instance of {@link DegreesToRadians} that converts the value of the given field, measured in degrees, to radians. + * + * @param fieldName must not be {@literal null}. + * @return new instance of {@link DegreesToRadians}. + */ + public static DegreesToRadians degreesToRadiansOf(String fieldName) { + return degreesToRadians(Fields.field(fieldName)); + } + + /** + * Create a new instance of {@link DegreesToRadians} that converts the result of the given {@link AggregationExpression expression}, measured in degrees, to radians. + * + * @param expression must not be {@literal null}. + * @return new instance of {@link DegreesToRadians}. + */ + public static DegreesToRadians degreesToRadiansOf(AggregationExpression expression) { + return degreesToRadians(expression); + } + + /** + * Create a new instance of {@link DegreesToRadians} that converts the given value, measured in degrees, to radians. + * + * @param value must not be {@literal null}. + * @return new instance of {@link DegreesToRadians}. + */ + public static DegreesToRadians degreesToRadians(Object value) { + return new DegreesToRadians(value); + } + + @Override + protected String getMongoMethod() { + return "$degreesToRadians"; + } + } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/CountOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/CountOperation.java index 4306e7630a..2f2187b18b 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/CountOperation.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/CountOperation.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2020 the original author or authors. + * Copyright 2016-2022 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. @@ -24,8 +24,7 @@ * We recommend to use the static factory method {@link Aggregation#count()} instead of creating instances of this class * directly. * - * @see https://docs.mongodb.com/manual/reference/operator/aggregation/count/ + * @see https://docs.mongodb.com/manual/reference/operator/aggregation/count/ * @author Mark Paluch * @since 1.10 */ @@ -49,7 +48,12 @@ public CountOperation(String fieldName) { */ @Override public Document toDocument(AggregationOperationContext context) { - return new Document("$count", fieldName); + return new Document(getOperator(), fieldName); + } + + @Override + public String getOperator() { + return "$count"; } /* (non-Javadoc) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/DataTypeOperators.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/DataTypeOperators.java index 9ce1f314fb..d10409dd04 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/DataTypeOperators.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/DataTypeOperators.java @@ -1,5 +1,5 @@ /* - * Copyright 2016. the original author or authors. + * Copyright 2016-2022 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. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/DateOperators.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/DateOperators.java index 6305961c41..3048b6f73f 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/DateOperators.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/DateOperators.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018. the original author or authors. + * Copyright 2016-2022 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. @@ -15,9 +15,16 @@ */ package org.springframework.data.mongodb.core.aggregation; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.temporal.ChronoUnit; import java.util.Collections; +import java.util.HashMap; import java.util.LinkedHashMap; +import java.util.Locale; import java.util.Map; +import java.util.TimeZone; +import java.util.concurrent.TimeUnit; import org.springframework.lang.Nullable; import org.springframework.util.Assert; @@ -45,6 +52,19 @@ public static DateOperatorFactory dateOf(String fieldReference) { return new DateOperatorFactory(fieldReference); } + /** + * Take the date referenced by given {@literal fieldReference}. + * + * @param fieldReference must not be {@literal null}. + * @return new instance of {@link DateOperatorFactory}. + * @since 3.3 + */ + public static DateOperatorFactory zonedDateOf(String fieldReference, Timezone timezone) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new DateOperatorFactory(fieldReference).withTimezone(timezone); + } + /** * Take the date resulting from the given {@link AggregationExpression}. * @@ -57,9 +77,22 @@ public static DateOperatorFactory dateOf(AggregationExpression expression) { return new DateOperatorFactory(expression); } + /** + * Take the date resulting from the given {@link AggregationExpression}. + * + * @param expression must not be {@literal null}. + * @return new instance of {@link DateOperatorFactory}. + * @since 3.3 + */ + public static DateOperatorFactory zonedDateOf(AggregationExpression expression, Timezone timezone) { + + Assert.notNull(expression, "Expression must not be null!"); + return new DateOperatorFactory(expression).withTimezone(timezone); + } + /** * Take the given value as date. - *

        + *
        * This can be one of: *