Compare commits
182 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b1f77f456a | |||
| b7eb4b6b5c | |||
| 8836c54719 | |||
| 1f46e0ddfe | |||
| 9d66d8d4f6 | |||
| 9487ac4540 | |||
| 1f981cdf38 | |||
| ded81c496f | |||
| 17ac86e38c | |||
| 49921a8006 | |||
| 9646ff6339 | |||
| b1ca14b28a | |||
| 0cb851799c | |||
| 45fc59a88a | |||
| f12eeb62f3 | |||
| dee79afc4c | |||
| f8e5feffb8 | |||
| 91e14c7a30 | |||
| 9e21e4448a | |||
| 297922963f | |||
| 360d83666c | |||
| 0f3619d971 | |||
| b6bdecbfcb | |||
| 85811b1140 | |||
| 280efcf293 | |||
| 759a01b10a | |||
| 3830309a29 | |||
| 715f78868e | |||
| 2466c434ed | |||
| 87e83bf66d | |||
| 4462b0d81a | |||
| ffd0abf5df | |||
| 549ef4357c | |||
| aa12fa3cc8 | |||
| 150f42398a | |||
| 0b4c68d681 | |||
| c4c7dccbeb | |||
| 6dd6663757 | |||
| fe4736113d | |||
| b544fe8615 | |||
| 50a4e85682 | |||
| 19113a35ba | |||
| 8bcbc125be | |||
| 3a85e8bdc5 | |||
| 3d5f5bcef4 | |||
| cc48101ff3 | |||
| a826f6889f | |||
| 0c6ee8c4a5 | |||
| 699f9b189d | |||
| d554fb187a | |||
| fe90296b35 | |||
| 53f749413c | |||
| 6cdedb9bdc | |||
| 13b8427fb4 | |||
| 9b8bdd836c | |||
| 501959aeee | |||
| 71e078375d | |||
| feaa7c3a98 | |||
| b4a849b422 | |||
| 54a86fa99e | |||
| d710c9ea3d | |||
| eb4ad9d870 | |||
| 48c6e9fb9c | |||
| 71b5b0570d | |||
| d7c0c0760d | |||
| 562209cefc | |||
| aba47b4e43 | |||
| 068eff77b6 | |||
| 74434d08a3 | |||
| d59436f09c | |||
| 496b224fc7 | |||
| 59a9369483 | |||
| 2affdad954 | |||
| 7364a43ef1 | |||
| 976f8a7c12 | |||
| 42c362d1c8 | |||
| ebfa5be8b2 | |||
| 17b567de98 | |||
| 48490b9a33 | |||
| cb59a1dba9 | |||
| 2aabfc8315 | |||
| 86e6c78af3 | |||
| f7ced0cdaf | |||
| 5143713317 | |||
| 6916fc1422 | |||
| 42da13527d | |||
| edcfb49c9e | |||
| 505d91953c | |||
| 85e508e6ac | |||
| efee131de0 | |||
| 583e36cde4 | |||
| a6cd3ea600 | |||
| d8abeca6f4 | |||
| 9dc3ee7b64 | |||
| 838566d318 | |||
| bee46c3ee8 | |||
| 46a604df43 | |||
| 38f5a89c8a | |||
| 3f3fb5e975 | |||
| 633d78686b | |||
| 7fa24e4ed8 | |||
| 7e532ecbb9 | |||
| 664acff5a1 | |||
| 95cc690ab4 | |||
| 21e28511d9 | |||
| 52e1525710 | |||
| 2e9846259f | |||
| 0872016ada | |||
| 4ac1836ad3 | |||
| c4b5c0a56e | |||
| 2894c0ea59 | |||
| 0d1d551d19 | |||
| a75e9a7ec2 | |||
| dd01c74bb2 | |||
| 6888c11c94 | |||
| 61244b3c19 | |||
| aaed7cdc9a | |||
| 54304d3139 | |||
| 5c00c5649b | |||
| c3ab7adb61 | |||
| dd245b27cc | |||
| 19768cd479 | |||
| 92a6eb8c1a | |||
| 3be6fad1fc | |||
| cc8f0c8ab7 | |||
| 179c3079bd | |||
| 5bfcb4214c | |||
| 5f3f3ee31d | |||
| 7a88a0f1c4 | |||
| 5083755428 | |||
| eb600cdb10 | |||
| dce250560f | |||
| b3a461211d | |||
| e11045e654 | |||
| a744074ad3 | |||
| 3d8821c644 | |||
| 7047de65b5 | |||
| 73bd06340e | |||
| ab7458d7d7 | |||
| 365b0c47d8 | |||
| 5428cc9510 | |||
| 31b8963b31 | |||
| 76d9d74c45 | |||
| fd8c62d9db | |||
| 99ecc3a06f | |||
| d16e9de083 | |||
| 00bf5924f6 | |||
| d1e21413d1 | |||
| fdd4eac20b | |||
| 84dd07d70d | |||
| 9104740c3b | |||
| 4d08095178 | |||
| 8c93961581 | |||
| ae75546cd8 | |||
| 16ba2e78cc | |||
| f9b7513ca2 | |||
| f37bbdb75c | |||
| 6bcd850eb1 | |||
| 3f43f5efed | |||
| 70f77bfd1f | |||
| d887b1bc9b | |||
| 6636bbb364 | |||
| b116cce1e8 | |||
| ed7a761912 | |||
| b0353ec4e5 | |||
| 758e697aec | |||
| 02761a48e0 | |||
| e6fbc37550 | |||
| 0220f69f3f | |||
| c0dcda00e8 | |||
| cef1fc7d77 | |||
| 9ddf4868ef | |||
| dde561a805 | |||
| 950c48a069 | |||
| df9954b3ef | |||
| 32d5f5dcb9 | |||
| 0191d69d94 | |||
| e7e0983ea5 | |||
| a299260a03 | |||
| 627d96cbfc | |||
| efa3b4d17e | |||
| f5d44ad755 |
@@ -1,7 +1,3 @@
|
||||
.DS_Store
|
||||
*.graphml
|
||||
.springBeans
|
||||
|
||||
atlassian-ide-plugin.xml
|
||||
|
||||
## Ignore svn files
|
||||
|
||||
Vendored
+19
-19
@@ -3,7 +3,7 @@ pipeline {
|
||||
|
||||
triggers {
|
||||
pollSCM 'H/10 * * * *'
|
||||
upstream(upstreamProjects: "spring-data-commons/2.2.x", threshold: hudson.model.Result.SUCCESS)
|
||||
upstream(upstreamProjects: "spring-data-commons/2.1.x", threshold: hudson.model.Result.SUCCESS)
|
||||
}
|
||||
|
||||
options {
|
||||
@@ -15,7 +15,7 @@ pipeline {
|
||||
stage("Test") {
|
||||
when {
|
||||
anyOf {
|
||||
branch '3.2.x'
|
||||
branch '3.1.x'
|
||||
not { triggeredBy 'UpstreamCause' }
|
||||
}
|
||||
}
|
||||
@@ -25,31 +25,27 @@ pipeline {
|
||||
docker {
|
||||
image 'adoptopenjdk/openjdk8:latest'
|
||||
label 'data'
|
||||
args '-u root -v /var/run/docker.sock:/var/run/docker.sock -v $HOME:/tmp/jenkins-home'
|
||||
args '-v $HOME:/tmp/jenkins-home'
|
||||
}
|
||||
}
|
||||
options { timeout(time: 30, unit: 'MINUTES') }
|
||||
environment {
|
||||
ARTIFACTORY = credentials('02bd1690-b54f-4c9f-819d-a77cb7a9822c')
|
||||
}
|
||||
steps {
|
||||
sh 'MAVEN_OPTS="-Duser.name=jenkins -Duser.home=/tmp/jenkins-home" ./mvnw -s settings.xml clean dependency:list test -Dsort -U -B -Dmaven.repo.local=/tmp/jenkins-home/.m2/spring-data-elasticsearch'
|
||||
sh 'rm -rf ?'
|
||||
sh 'MAVEN_OPTS="-Duser.name=jenkins -Duser.home=/tmp/jenkins-home" ./mvnw clean dependency:list test -Dsort -U -B'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
stage('Release to artifactory') {
|
||||
when {
|
||||
anyOf {
|
||||
branch '3.2.x'
|
||||
not { triggeredBy 'UpstreamCause' }
|
||||
}
|
||||
branch 'issue/*'
|
||||
not { triggeredBy 'UpstreamCause' }
|
||||
}
|
||||
agent {
|
||||
docker {
|
||||
image 'adoptopenjdk/openjdk8:latest'
|
||||
label 'data'
|
||||
args '-u root -v /var/run/docker.sock:/var/run/docker.sock -v $HOME:/tmp/jenkins-home'
|
||||
args '-v $HOME:/tmp/jenkins-home'
|
||||
}
|
||||
}
|
||||
options { timeout(time: 20, unit: 'MINUTES') }
|
||||
@@ -59,25 +55,26 @@ pipeline {
|
||||
}
|
||||
|
||||
steps {
|
||||
sh 'MAVEN_OPTS="-Duser.name=jenkins -Duser.home=/tmp/jenkins-home" ./mvnw -s settings.xml -Pci,artifactory -Dmaven.repo.local=/tmp/jenkins-home/.m2/spring-data-elasticsearch ' +
|
||||
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-elasticsearch " +
|
||||
"-Dartifactory.build-name=spring-data-elasticsearch-3.1 " +
|
||||
"-Dartifactory.build-number=${BUILD_NUMBER} " +
|
||||
'-Dmaven.test.skip=true clean deploy -U -B'
|
||||
}
|
||||
}
|
||||
stage('Publish documentation') {
|
||||
stage('Release to artifactory with docs') {
|
||||
when {
|
||||
branch '3.2.x'
|
||||
branch '3.1.x'
|
||||
}
|
||||
agent {
|
||||
docker {
|
||||
image 'adoptopenjdk/openjdk8:latest'
|
||||
label 'data'
|
||||
args '-u root -v /var/run/docker.sock:/var/run/docker.sock -v $HOME:/tmp/jenkins-home'
|
||||
args '-v $HOME:/tmp/jenkins-home'
|
||||
}
|
||||
}
|
||||
options { timeout(time: 20, unit: 'MINUTES') }
|
||||
@@ -87,11 +84,14 @@ pipeline {
|
||||
}
|
||||
|
||||
steps {
|
||||
sh 'MAVEN_OPTS="-Duser.name=jenkins -Duser.home=/tmp/jenkins-home" ./mvnw -s settings.xml -Pci,distribute -Dmaven.repo.local=/tmp/jenkins-home/.m2/spring-data-elasticsearch ' +
|
||||
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.distribution-repository=temp-private-local " +
|
||||
"-Dartifactory.staging-repository=libs-snapshot-local " +
|
||||
"-Dartifactory.build-name=spring-data-elasticsearch-3.1 " +
|
||||
"-Dartifactory.build-number=${BUILD_NUMBER} " +
|
||||
'-Dmaven.test.skip=true clean deploy -U -B'
|
||||
}
|
||||
}
|
||||
|
||||
-202
@@ -1,202 +0,0 @@
|
||||
|
||||
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.
|
||||
+12
-23
@@ -113,31 +113,20 @@ Add the Maven dependency:
|
||||
</dependency>
|
||||
----
|
||||
|
||||
// NOTE: since Github does not support include directives, the content of
|
||||
// the src/main/asciidoc/reference/preface.adoc file is duplicated here
|
||||
// Always change both files!
|
||||
**Compatibility Matrix**
|
||||
|
||||
The compatibility between Spring Data Elasticsearch, Elasticsearch client drivers and Spring Boot versions can be found in the https://docs.spring.io/spring-data/elasticsearch/docs/3.2.0.RC3/reference/html/#preface.versions[reference documentation].
|
||||
[cols="^,^"]
|
||||
|===
|
||||
|Spring Data Elasticsearch | Elasticsearch
|
||||
|3.2.x |6.7.2
|
||||
|3.1.x |6.2.4
|
||||
|3.0.x |5.5.0
|
||||
|2.1.x |2.4.0
|
||||
|2.0.x |2.2.0
|
||||
|1.3.x |1.5.2
|
||||
|===
|
||||
|
||||
To use the Release candidate versions of the upcoming major version, use our Maven milestone repository and declare the appropriate dependency version:
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<dependency>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-elasticsearch</artifactId>
|
||||
<version>${version}.RCx</version> <!-- x being 1, 2, ... -->
|
||||
</dependency>
|
||||
|
||||
<repository>
|
||||
<id>spring-libs-snapshot</id>
|
||||
<name>Spring Snapshot Repository</name>
|
||||
<url>https://repo.spring.io/libs-milestone</url>
|
||||
</repository>
|
||||
----
|
||||
|
||||
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]
|
||||
----
|
||||
@@ -205,7 +194,7 @@ The generated documentation is available from `target/site/reference/html/index.
|
||||
|
||||
== Examples
|
||||
|
||||
For examples on using the Spring Data for Elasticsearch, see the https://github.com/spring-projects/spring-data-examples/tree/master/elasticsearch/example[spring-data-examples] project.
|
||||
For examples on using the _Spring Data for Elasticsearch, see the https://github.com/SpringSource/spring-elasticsearch-examples[spring-elasticsearch-examples] project.
|
||||
|
||||
== License
|
||||
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
Please see the https://spring.io/projects/spring-data-elasticsearch[Spring Data Elasticsearch] project page for supported versions.
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Please don't raise security vulnerabilities here. Head over to https://pivotal.io/security to learn how to disclose them responsibly.
|
||||
@@ -1,16 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-elasticsearch</artifactId>
|
||||
<version>3.2.14.BUILD-SNAPSHOT</version>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-elasticsearch</artifactId>
|
||||
<version>3.1.22.BUILD-SNAPSHOT</version>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.data.build</groupId>
|
||||
<artifactId>spring-data-parent</artifactId>
|
||||
<version>2.2.14.BUILD-SNAPSHOT</version>
|
||||
<version>2.1.22.BUILD-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<name>Spring Data Elasticsearch</name>
|
||||
@@ -18,74 +17,14 @@
|
||||
<url>https://github.com/spring-projects/spring-data-elasticsearch</url>
|
||||
|
||||
<properties>
|
||||
<commonscollections>3.2.1</commonscollections>
|
||||
<commonslang>2.6</commonslang>
|
||||
<elasticsearch>6.8.14</elasticsearch>
|
||||
<elasticsearch>6.2.4</elasticsearch>
|
||||
<log4j>2.9.1</log4j>
|
||||
<springdata.commons>2.2.14.BUILD-SNAPSHOT</springdata.commons>
|
||||
<netty>4.1.39.Final</netty>
|
||||
<springdata.commons>2.1.22.BUILD-SNAPSHOT</springdata.commons>
|
||||
<java-module-name>spring.data.elasticsearch</java-module-name>
|
||||
</properties>
|
||||
|
||||
<developers>
|
||||
<developer>
|
||||
<id>biomedcentral</id>
|
||||
<name>BioMed Central Development Team</name>
|
||||
<timezone>+0</timezone>
|
||||
</developer>
|
||||
<developer>
|
||||
<id>cstrobl</id>
|
||||
<name>Christoph Strobl</name>
|
||||
<email>cstrobl at pivotal.io</email>
|
||||
<organization>Pivotal</organization>
|
||||
<organizationUrl>https://www.pivotal.io</organizationUrl>
|
||||
<roles>
|
||||
<role>Developer</role>
|
||||
</roles>
|
||||
<timezone>+1</timezone>
|
||||
</developer>
|
||||
<developer>
|
||||
<id>mpaluch</id>
|
||||
<name>Mark Paluch</name>
|
||||
<email>mpaluch at pivotal.io</email>
|
||||
<organization>Pivotal</organization>
|
||||
<organizationUrl>https://www.pivotal.io</organizationUrl>
|
||||
<roles>
|
||||
<role>Developer</role>
|
||||
</roles>
|
||||
<timezone>+1</timezone>
|
||||
</developer>
|
||||
</developers>
|
||||
|
||||
<scm>
|
||||
<url>https://github.com/spring-projects/spring-data-elasticsearch</url>
|
||||
<connection>scm:git:git://github.com/spring-projects/spring-data-elasticsearch.git</connection>
|
||||
<developerConnection>scm:git:ssh://git@github.com/spring-projects/spring-data-elasticsearch.git
|
||||
</developerConnection>
|
||||
</scm>
|
||||
|
||||
<ciManagement>
|
||||
<system>Bamboo</system>
|
||||
<url>https://build.spring.io/browse/SPRINGDATAES</url>
|
||||
</ciManagement>
|
||||
|
||||
<issueManagement>
|
||||
<system>JIRA</system>
|
||||
<url>https://jira.spring.io/browse/DATAES</url>
|
||||
</issueManagement>
|
||||
|
||||
<dependencyManagement>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-bom</artifactId>
|
||||
<version>${netty}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<!-- Spring -->
|
||||
@@ -112,25 +51,6 @@
|
||||
<version>${springdata.commons}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Reactive Infrastructure -->
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-webflux</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.projectreactor.netty</groupId>
|
||||
<artifactId>reactor-netty</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.projectreactor</groupId>
|
||||
<artifactId>reactor-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- APACHE -->
|
||||
<dependency>
|
||||
<groupId>commons-lang</groupId>
|
||||
@@ -159,25 +79,6 @@
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<!-- required by elasticsearch -->
|
||||
<groupId>org.elasticsearch.plugin</groupId>
|
||||
<artifactId>transport-netty4-client</artifactId>
|
||||
<version>${elasticsearch}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.elasticsearch.client</groupId>
|
||||
<artifactId>elasticsearch-rest-high-level-client</artifactId>
|
||||
<version>${elasticsearch}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>commons-logging</groupId>
|
||||
<artifactId>commons-logging</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>log4j-over-slf4j</artifactId>
|
||||
@@ -241,31 +142,6 @@
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.skyscreamer</groupId>
|
||||
<artifactId>jsonassert</artifactId>
|
||||
<version>1.5.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.github.tomakehurst</groupId>
|
||||
<artifactId>wiremock-jre8</artifactId>
|
||||
<version>2.25.1</version>
|
||||
<scope>test</scope>
|
||||
<exclusions>
|
||||
<!-- these exclusions are needed because of Elasticsearch JarHell-->
|
||||
<exclusion>
|
||||
<groupId>commons-logging</groupId>
|
||||
<artifactId>commons-logging</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.ow2.asm</groupId>
|
||||
<artifactId>asm</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<!-- Upgrade xbean to 4.5 to prevent incompatibilities due to ASM versions -->
|
||||
<dependency>
|
||||
<groupId>org.apache.xbean</groupId>
|
||||
@@ -276,8 +152,23 @@
|
||||
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>javax.servlet-api</artifactId>
|
||||
<version>3.1.0</version>
|
||||
<artifactId>servlet-api</artifactId>
|
||||
<version>3.0-alpha-1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<!-- required by elasticsearch -->
|
||||
<groupId>org.elasticsearch.plugin</groupId>
|
||||
<artifactId>transport-netty4-client</artifactId>
|
||||
<version>${elasticsearch}</version>
|
||||
<!--<scope>test</scope>-->
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>${lombok}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -285,24 +176,6 @@
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<!--
|
||||
please do not remove this configuration for surefire - we need that to avoid issue with jar hell
|
||||
-->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<configuration>
|
||||
<useSystemClassLoader>true</useSystemClassLoader>
|
||||
<useFile>false</useFile>
|
||||
<includes>
|
||||
<include>**/*Tests.java</include>
|
||||
<include>**/*Test.java</include>
|
||||
</includes>
|
||||
<systemPropertyVariables>
|
||||
<es.set.netty.runtime.available.processors>false</es.set.netty.runtime.available.processors>
|
||||
</systemPropertyVariables>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-assembly-plugin</artifactId>
|
||||
@@ -335,7 +208,9 @@
|
||||
</module>
|
||||
</checkstyleRules>
|
||||
<includes>**/*</includes>
|
||||
<excludes>.git/**/*,target/**/*,**/target/**/*,.idea/**/*,**/spring.schemas,**/*.svg,mvnw,mvnw.cmd,**/*.policy</excludes>
|
||||
<excludes>
|
||||
.git/**/*,target/**/*,**/target/**/*,.idea/**/*,**/spring.schemas,**/*.svg,mvnw,mvnw.cmd,**/*.policy
|
||||
</excludes>
|
||||
<sourceDirectories>./</sourceDirectories>
|
||||
</configuration>
|
||||
</plugin>
|
||||
@@ -346,6 +221,14 @@
|
||||
|
||||
</profiles>
|
||||
|
||||
<developers>
|
||||
<developer>
|
||||
<id>biomedcentral</id>
|
||||
<name>BioMed Central Development Team</name>
|
||||
<timezone>+0</timezone>
|
||||
</developer>
|
||||
</developers>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>spring-libs-snapshot</id>
|
||||
@@ -360,4 +243,21 @@
|
||||
</pluginRepository>
|
||||
</pluginRepositories>
|
||||
|
||||
<scm>
|
||||
<url>https://github.com/spring-projects/spring-data-elasticsearch</url>
|
||||
<connection>scm:git:git://github.com/spring-projects/spring-data-elasticsearch.git</connection>
|
||||
<developerConnection>scm:git:ssh://git@github.com/spring-projects/spring-data-elasticsearch.git
|
||||
</developerConnection>
|
||||
</scm>
|
||||
|
||||
<ciManagement>
|
||||
<system>Bamboo</system>
|
||||
<url>https://build.spring.io/browse/SPRINGDATAES</url>
|
||||
</ciManagement>
|
||||
|
||||
<issueManagement>
|
||||
<system>JIRA</system>
|
||||
<url>https://jira.spring.io/browse/DATAES</url>
|
||||
</issueManagement>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0
|
||||
https://maven.apache.org/xsd/settings-1.0.0.xsd">
|
||||
|
||||
<servers>
|
||||
<server>
|
||||
<id>spring-plugins-release</id>
|
||||
<username>${env.ARTIFACTORY_USR}</username>
|
||||
<password>${env.ARTIFACTORY_PSW}</password>
|
||||
</server>
|
||||
<server>
|
||||
<id>spring-libs-snapshot</id>
|
||||
<username>${env.ARTIFACTORY_USR}</username>
|
||||
<password>${env.ARTIFACTORY_PSW}</password>
|
||||
</server>
|
||||
<server>
|
||||
<id>spring-libs-milestone</id>
|
||||
<username>${env.ARTIFACTORY_USR}</username>
|
||||
<password>${env.ARTIFACTORY_PSW}</password>
|
||||
</server>
|
||||
<server>
|
||||
<id>spring-libs-release</id>
|
||||
<username>${env.ARTIFACTORY_USR}</username>
|
||||
<password>${env.ARTIFACTORY_PSW}</password>
|
||||
</server>
|
||||
</servers>
|
||||
|
||||
</settings>
|
||||
@@ -1,8 +1,9 @@
|
||||
= Spring Data Elasticsearch - Reference Documentation
|
||||
BioMed Central Development Team; Oliver Drotbohm; Greg Turnquist; Christoph Strobl; Peter-Josef Meisch
|
||||
= Spring Data Elasticsearch
|
||||
BioMed Central Development Team
|
||||
:revnumber: {version}
|
||||
:revdate: {localdate}
|
||||
ifdef::backend-epub3[:front-cover-image: image:epub-cover.png[Front Cover,1050,1600]]
|
||||
:toc:
|
||||
:toc-placement!:
|
||||
:spring-data-commons-docs: ../../../../spring-data-commons/src/main/asciidoc
|
||||
|
||||
(C) 2013-2019 The original author(s).
|
||||
@@ -20,10 +21,7 @@ include::{spring-data-commons-docs}/repositories.adoc[]
|
||||
= Reference Documentation
|
||||
|
||||
:leveloffset: +1
|
||||
include::reference/elasticsearch-clients.adoc[]
|
||||
include::reference/elasticsearch-object-mapping.adoc[]
|
||||
include::reference/elasticsearch-operations.adoc[]
|
||||
include::reference/elasticsearch-repositories.adoc[]
|
||||
include::reference/data-elasticsearch.adoc[]
|
||||
include::reference/elasticsearch-misc.adoc[]
|
||||
:leveloffset: -1
|
||||
|
||||
|
||||
@@ -1,44 +1,20 @@
|
||||
[[preface]]
|
||||
= Preface
|
||||
|
||||
The Spring Data Elasticsearch project applies core Spring concepts to the development of solutions using the Elasticsearch Search Engine. It provides:
|
||||
The Spring Data Elasticsearch project applies core Spring concepts to the development of solutions using the Elasticsearch Search Engine. We have povided a "template" as a high-level abstraction for storing,querying,sorting and faceting documents. You will notice similarities to the Spring data solr and mongodb support in the Spring Framework.
|
||||
|
||||
* _Templates_ as a high-level abstraction for storing, querying, sorting and faceting documents.
|
||||
* _Repositories_ which for example enable the user to express queries by defining interfaces having customized method names (for basic information about repositories see <<repositories>>).
|
||||
|
||||
You will notice similarities to the Spring data solr and mongodb support in the Spring Framework.
|
||||
|
||||
include::reference/elasticsearch-new.adoc[leveloffset=+1]
|
||||
|
||||
[[preface.metadata]]
|
||||
[[project]]
|
||||
[preface]
|
||||
== Project Metadata
|
||||
|
||||
* Version Control - https://github.com/spring-projects/spring-data-elasticsearch
|
||||
* API Documentation - https://docs.spring.io/spring-data/elasticsearch/docs/current/api/
|
||||
* Bugtracker - https://jira.spring.io/browse/DATAES
|
||||
* Release repository - https://repo.spring.io/libs-release
|
||||
* Milestone repository - https://repo.spring.io/libs-milestone
|
||||
* Snapshot repository - https://repo.spring.io/libs-snapshot
|
||||
|
||||
[[preface.requirements]]
|
||||
[[requirements]]
|
||||
[preface]
|
||||
== Requirements
|
||||
|
||||
Requires an installation of https://www.elastic.co/products/elasticsearch[Elasticsearch].
|
||||
Requires https://www.elastic.co/downloads/elasticsearch[Elasticsearch] 0.20.2 and above or optional dependency or not even that if you are using Embedded Node Client
|
||||
|
||||
[[preface.versions]]
|
||||
=== Versions
|
||||
// NOTE: since Github does not support include directives, the content of
|
||||
// this file is duplicated in the toplevel README
|
||||
// Always change both files!
|
||||
|
||||
The following table shows the Elasticsearch versions that are used by Spring Data release trains and version of Spring Data Elasticsearch included in that, as well as the Spring Boot versions refering to that particular Spring Data release train:
|
||||
[cols="^,^,^,^",options="header"]
|
||||
|===
|
||||
|Spring Data Release Train |Spring Data Elasticsearch |Elasticsearch | Spring Boot
|
||||
|Moore |3.2.x |6.8.14 |2.2.x
|
||||
|Lovelace |3.1.x |6.2.2|2.1.x
|
||||
|Kayfootnote:oom[Out of maintenance]|3.0.xfootnote:oom[] |5.5.0 |2.0.xfootnote:oom[]
|
||||
|Ingallsfootnote:oom[]|2.1.xfootnote:oom[] |2.4.0 |1.5.xfootnote:oom[]
|
||||
|===
|
||||
|
||||
Support for upcoming versions of Elasticsearch is being tracked and general compatibility should be given assuming the usage of the <<elasticsearch.clients.rest,high-level REST client>>.
|
||||
|
||||
@@ -0,0 +1,288 @@
|
||||
[[elasticsearch.repositories]]
|
||||
= Elasticsearch Repositories
|
||||
|
||||
This chapter includes details of the Elasticsearch repository implementation.
|
||||
|
||||
[[elasticsearch.introduction]]
|
||||
== Introduction
|
||||
|
||||
[[elasticsearch.namespace]]
|
||||
=== Spring Namespace
|
||||
|
||||
The Spring Data Elasticsearch module contains a custom namespace allowing definition of repository beans as well as elements for instantiating a `ElasticsearchServer` .
|
||||
|
||||
Using the `repositories` element looks up Spring Data repositories as described in <<repositories.create-instances>> .
|
||||
|
||||
.Setting up Elasticsearch repositories using Namespace
|
||||
====
|
||||
[source,xml]
|
||||
----
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:elasticsearch="http://www.springframework.org/schema/data/elasticsearch"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans
|
||||
https://www.springframework.org/schema/beans/spring-beans-3.1.xsd
|
||||
http://www.springframework.org/schema/data/elasticsearch
|
||||
https://www.springframework.org/schema/data/elasticsearch/spring-elasticsearch-1.0.xsd">
|
||||
|
||||
<elasticsearch:repositories base-package="com.acme.repositories" />
|
||||
|
||||
</beans>
|
||||
----
|
||||
====
|
||||
|
||||
Using the `Transport Client` or `Node Client` element registers an instance of `Elasticsearch Server` in the context.
|
||||
|
||||
.Transport Client using Namespace
|
||||
====
|
||||
[source,xml]
|
||||
----
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:elasticsearch="http://www.springframework.org/schema/data/elasticsearch"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans
|
||||
https://www.springframework.org/schema/beans/spring-beans-3.1.xsd
|
||||
http://www.springframework.org/schema/data/elasticsearch
|
||||
https://www.springframework.org/schema/data/elasticsearch/spring-elasticsearch-1.0.xsd">
|
||||
|
||||
<elasticsearch:transport-client id="client" cluster-nodes="localhost:9300,someip:9300" />
|
||||
|
||||
</beans>
|
||||
----
|
||||
====
|
||||
|
||||
.Node Client using Namespace
|
||||
====
|
||||
[source,xml]
|
||||
----
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:elasticsearch="http://www.springframework.org/schema/data/elasticsearch"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans
|
||||
https://www.springframework.org/schema/beans/spring-beans-3.1.xsd
|
||||
http://www.springframework.org/schema/data/elasticsearch
|
||||
https://www.springframework.org/schema/data/elasticsearch/spring-elasticsearch-1.0.xsd">
|
||||
|
||||
<elasticsearch:node-client id="client" local="true"" />
|
||||
|
||||
</beans>
|
||||
----
|
||||
====
|
||||
|
||||
[[elasticsearch.annotation]]
|
||||
=== Annotation based configuration
|
||||
|
||||
The Spring Data Elasticsearch repositories support cannot only be activated through an XML namespace but also using an annotation through JavaConfig.
|
||||
|
||||
.Spring Data Elasticsearch repositories using JavaConfig
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
@Configuration
|
||||
@EnableElasticsearchRepositories(basePackages = "org/springframework/data/elasticsearch/repositories")
|
||||
static class Config {
|
||||
|
||||
@Bean
|
||||
public ElasticsearchOperations elasticsearchTemplate() {
|
||||
return new ElasticsearchTemplate(nodeBuilder().local(true).node().client());
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
The configuration above sets up an `Embedded Elasticsearch Server` which is used by the `ElasticsearchTemplate` . Spring Data Elasticsearch Repositories are activated using the `@EnableElasticsearchRepositories` annotation, which essentially carries the same attributes as the XML namespace does. If no base package is configured, it will use the one the configuration class resides in.
|
||||
|
||||
[[elasticsearch.cdi]]
|
||||
=== Elasticsearch Repositores using CDI
|
||||
|
||||
The Spring Data Elasticsearch repositories can also be set up using CDI functionality.
|
||||
|
||||
.Spring Data Elasticsearch repositories using JavaConfig
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
class ElasticsearchTemplateProducer {
|
||||
|
||||
@Produces
|
||||
@ApplicationScoped
|
||||
public ElasticsearchOperations createElasticsearchTemplate() {
|
||||
return new ElasticsearchTemplate(nodeBuilder().local(true).node().client());
|
||||
}
|
||||
}
|
||||
|
||||
class ProductService {
|
||||
|
||||
private ProductRepository repository;
|
||||
|
||||
public Page<Product> findAvailableBookByName(String name, Pageable pageable) {
|
||||
return repository.findByAvailableTrueAndNameStartingWith(name, pageable);
|
||||
}
|
||||
|
||||
@Inject
|
||||
public void setRepository(ProductRepository repository) {
|
||||
this.repository = repository;
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
[[elasticsearch.query-methods]]
|
||||
== Query methods
|
||||
|
||||
[[elasticsearch.query-methods.finders]]
|
||||
=== Query lookup strategies
|
||||
|
||||
The Elasticsearch module supports all basic query building feature as String,Abstract,Criteria or have it being derived from the method name.
|
||||
|
||||
==== Declared queries
|
||||
|
||||
Deriving the query from the method name is not always sufficient and/or may result in unreadable method names. In this case one might make either use of `@Query` annotation (see <<elasticsearch.query-methods.at-query>> ).
|
||||
|
||||
[[elasticsearch.query-methods.criterions]]
|
||||
=== Query creation
|
||||
|
||||
Generally the query creation mechanism for Elasticsearch works as described in <<repositories.query-methods>> . Here's a short example of what a Elasticsearch query method translates into:
|
||||
|
||||
.Query creation from method names
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
public interface BookRepository extends Repository<Book, String>
|
||||
{
|
||||
List<Book> findByNameAndPrice(String name, Integer price);
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
The method name above will be translated into the following Elasticsearch json query
|
||||
|
||||
[source]
|
||||
----
|
||||
{ "bool" :
|
||||
{ "must" :
|
||||
[
|
||||
{ "field" : {"name" : "?"} },
|
||||
{ "field" : {"price" : "?"} }
|
||||
]
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
A list of supported keywords for Elasticsearch is shown below.
|
||||
|
||||
[cols="1,2,3", options="header"]
|
||||
.Supported keywords inside method names
|
||||
|===
|
||||
| Keyword
|
||||
| Sample
|
||||
| Elasticsearch Query String| `And`
|
||||
| `findByNameAndPrice`
|
||||
| `{"bool" : {"must" : [ {"field" : {"name" : "?"}},
|
||||
{"field" : {"price" : "?"}} ]}}`
|
||||
|
||||
| `Or`
|
||||
| `findByNameOrPrice`
|
||||
| `{"bool" : {"should" : [ {"field" : {"name" : "?"}},
|
||||
{"field" : {"price" : "?"}} ]}}`
|
||||
|
||||
| `Is`
|
||||
| `findByName`
|
||||
| `{"bool" : {"must" : {"field" : {"name" : "?"}}}}`
|
||||
|
||||
| `Not`
|
||||
| `findByNameNot`
|
||||
| `{"bool" : {"must_not" : {"field" : {"name" : "?"}}}}`
|
||||
|
||||
| `Between`
|
||||
| `findByPriceBetween`
|
||||
| `{"bool" : {"must" : {"range" : {"price" : {"from" :
|
||||
?,"to" : ?,"include_lower" : true,"include_upper" : true}}}}}`
|
||||
|
||||
| `LessThanEqual`
|
||||
| `findByPriceLessThan`
|
||||
| `{"bool" : {"must" : {"range" : {"price" : {"from" :
|
||||
null,"to" : ?,"include_lower" : true,"include_upper" :
|
||||
true}}}}}`
|
||||
|
||||
| `GreaterThanEqual`
|
||||
| `findByPriceGreaterThan`
|
||||
| `{"bool" : {"must" : {"range" : {"price" : {"from" :
|
||||
?,"to" : null,"include_lower" : true,"include_upper" :
|
||||
true}}}}}`
|
||||
|
||||
| `Before`
|
||||
| `findByPriceBefore`
|
||||
| `{"bool" : {"must" : {"range" : {"price" : {"from" :
|
||||
null,"to" : ?,"include_lower" : true,"include_upper" :
|
||||
true}}}}}`
|
||||
|
||||
| `After`
|
||||
| `findByPriceAfter`
|
||||
| `{"bool" : {"must" : {"range" : {"price" : {"from" :
|
||||
?,"to" : null,"include_lower" : true,"include_upper" :
|
||||
true}}}}}`
|
||||
|
||||
| `Like`
|
||||
| `findByNameLike`
|
||||
| `{"bool" : {"must" : {"field" : {"name" : {"query" :
|
||||
"?*","analyze_wildcard" : true}}}}}`
|
||||
|
||||
| `StartingWith`
|
||||
| `findByNameStartingWith`
|
||||
| `{"bool" : {"must" : {"field" : {"name" : {"query" :
|
||||
"?*","analyze_wildcard" : true}}}}}`
|
||||
|
||||
| `EndingWith`
|
||||
| `findByNameEndingWith`
|
||||
| `{"bool" : {"must" : {"field" : {"name" : {"query" :
|
||||
"*?","analyze_wildcard" : true}}}}}`
|
||||
|
||||
| `Contains/Containing`
|
||||
| `findByNameContaining`
|
||||
| `{"bool" : {"must" : {"field" : {"name" : {"query" :
|
||||
"*?*","analyze_wildcard" : true}}}}}`
|
||||
|
||||
| `In`
|
||||
| `findByNameIn(Collection<String>names)`
|
||||
| `{"bool" : {"must" : {"bool" : {"should" : [ {"field" :
|
||||
{"name" : "?"}}, {"field" : {"name" : "?"}} ]}}}}`
|
||||
|
||||
| `NotIn`
|
||||
| `findByNameNotIn(Collection<String>names)`
|
||||
| `{"bool" : {"must_not" : {"bool" : {"should" : {"field" :
|
||||
{"name" : "?"}}}}}}`
|
||||
|
||||
| `Near`
|
||||
| `findByStoreNear`
|
||||
| `Not Supported Yet !`
|
||||
|
||||
| `True`
|
||||
| `findByAvailableTrue`
|
||||
| `{"bool" : {"must" : {"field" : {"available" : true}}}}`
|
||||
|
||||
| `False`
|
||||
| `findByAvailableFalse`
|
||||
| `{"bool" : {"must" : {"field" : {"available" : false}}}}`
|
||||
|
||||
| `OrderBy`
|
||||
| `findByAvailableTrueOrderByNameDesc`
|
||||
| `{"sort" : [{ "name" : {"order" : "desc"} }],"bool" :
|
||||
{"must" : {"field" : {"available" : true}}}}`
|
||||
|===
|
||||
|
||||
[[elasticsearch.query-methods.at-query]]
|
||||
=== Using @Query Annotation
|
||||
|
||||
.Declare query at the method using the `@Query` annotation.
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
public interface BookRepository extends ElasticsearchRepository<Book, String> {
|
||||
@Query("{"bool" : {"must" : {"field" : {"name" : "?0"}}}}")
|
||||
Page<Book> findByName(String name,Pageable pageable);
|
||||
}
|
||||
----
|
||||
====
|
||||
@@ -1,184 +0,0 @@
|
||||
[[elasticsearch.clients]]
|
||||
= Elasticsearch Clients
|
||||
|
||||
This chapter illustrates configuration and usage of supported Elasticsearch client implementations.
|
||||
|
||||
Spring data Elasticsearch operates upon an Elasticsearch client that is connected to a single Elasticsearch node or a cluster. Although the Elasticsearch Client can be used to work with the cluster, applications using Spring Data Elasticsearch normally use the higher level abstractions of <<elasticsearch.operations>> and <<elasticsearch.repositories>>.
|
||||
|
||||
[[elasticsearch.clients.transport]]
|
||||
== Transport Client
|
||||
|
||||
WARNING: The well known `TransportClient` is deprecated as of Elasticsearch 7 and will be removed in Elasticsearch 8. (https://www.elastic.co/guide/en/elasticsearch/client/java-api/current/transport-client.html[see the Elasticsearch documentation]). Spring Data Elasticsearch will support the `TransportClient` as long as it is available in the used
|
||||
Elasticsearch <<elasticsearch.versions,version>>.
|
||||
|
||||
We strongly recommend to use the <<elasticsearch.clients.rest>> instead of the `TransportClient`.
|
||||
|
||||
.Transport Client
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
static class Config {
|
||||
|
||||
@Bean
|
||||
Client client() {
|
||||
Settings settings = Settings.builder()
|
||||
.put("cluster.name", "elasticsearch") <1>
|
||||
.build();
|
||||
TransportClient client = new PreBuiltTransportClient(settings);
|
||||
client.addTransportAddress(new TransportAddress(InetAddress.getByName("127.0.0.1")
|
||||
, 9300)); <2>
|
||||
return client;
|
||||
}
|
||||
}
|
||||
|
||||
// ...
|
||||
|
||||
IndexRequest request = new IndexRequest("spring-data", "elasticsearch", randomID())
|
||||
.source(someObject)
|
||||
.setRefreshPolicy(IMMEDIATE);
|
||||
|
||||
IndexResponse response = client.index(request);
|
||||
----
|
||||
<1> The `TransportClient` must be configured with the cluster name.
|
||||
<2> The host and port to connect the client to.
|
||||
====
|
||||
|
||||
[[elasticsearch.clients.rest]]
|
||||
== High Level REST Client
|
||||
|
||||
The Java High Level REST Client now is the default client of Elasticsearch, it provides a straight forward replacement for the `TransportClient` as it accepts and returns
|
||||
the very same request/response objects and therefore depends on the Elasticsearch core project.
|
||||
Asynchronous calls are operated upon a client managed thread pool and require a callback to be notified when the request is done.
|
||||
|
||||
.High Level REST Client
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
import org.springframework.beans.factory.annotation.Autowired;@Configuration
|
||||
static class Config {
|
||||
|
||||
@Bean
|
||||
RestHighLevelClient client() {
|
||||
|
||||
ClientConfiguration clientConfiguration = ClientConfiguration.builder() <1>
|
||||
.connectedTo("localhost:9200", "localhost:9201")
|
||||
.build();
|
||||
|
||||
return RestClients.create(clientConfiguration).rest(); <2>
|
||||
}
|
||||
}
|
||||
|
||||
// ...
|
||||
|
||||
@Autowired
|
||||
RestHighLevelClient highLevelClient;
|
||||
|
||||
RestClient lowLevelClient = highLevelClient.lowLevelClient(); <3>
|
||||
|
||||
// ...
|
||||
|
||||
IndexRequest request = new IndexRequest("spring-data", "elasticsearch", randomID())
|
||||
.source(singletonMap("feature", "high-level-rest-client"))
|
||||
.setRefreshPolicy(IMMEDIATE);
|
||||
|
||||
IndexResponse response = highLevelClient.index(request);
|
||||
----
|
||||
<1> Use the builder to provide cluster addresses, set default `HttpHeaders` or enable SSL.
|
||||
<2> Create the RestHighLevelClient.
|
||||
<3> It is also possible to obtain the `lowLevelRest()` client.
|
||||
====
|
||||
|
||||
[[elasticsearch.clients.reactive]]
|
||||
== Reactive Client
|
||||
|
||||
The `ReactiveElasticsearchClient` is a non official driver based on `WebClient`.
|
||||
It uses the request/response objects provided by the Elasticsearch core project.
|
||||
Calls are directly operated on the reactive stack, **not** wrapping async (thread pool bound) responses into reactive types.
|
||||
|
||||
.Reactive REST Client
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
static class Config {
|
||||
|
||||
@Bean
|
||||
ReactiveElasticsearchClient client() {
|
||||
|
||||
ClientConfiguration clientConfiguration = ClientConfiguration.builder() <1>
|
||||
.connectedTo("localhost:9200", "localhost:9291")
|
||||
.withWebClientConfigurer(webClient -> { <2>
|
||||
ExchangeStrategies exchangeStrategies = ExchangeStrategies.builder()
|
||||
.codecs(configurer -> configurer.defaultCodecs()
|
||||
.maxInMemorySize(-1))
|
||||
.build();
|
||||
return webClient.mutate().exchangeStrategies(exchangeStrategies).build();
|
||||
})
|
||||
.build();
|
||||
|
||||
return ReactiveRestClients.create(clientConfiguration);
|
||||
}
|
||||
}
|
||||
|
||||
// ...
|
||||
|
||||
Mono<IndexResponse> response = client.index(request ->
|
||||
|
||||
request.index("spring-data")
|
||||
.type("elasticsearch")
|
||||
.id(randomID())
|
||||
.source(singletonMap("feature", "reactive-client"))
|
||||
.setRefreshPolicy(IMMEDIATE);
|
||||
);
|
||||
----
|
||||
<1> Use the builder to provide cluster addresses, set default `HttpHeaders` or enable SSL.
|
||||
<2> when configuring a reactive client, the `withWebClientConfigurer` hook can be used to customize the WebClient.
|
||||
====
|
||||
|
||||
NOTE: The ReactiveClient response, especially for search operations, is bound to the `from` (offset) & `size` (limit) options of the request.
|
||||
|
||||
[[elasticsearch.clients.configuration]]
|
||||
== Client Configuration
|
||||
|
||||
Client behaviour can be changed via the `ClientConfiguration` that allows to set options for SSL, connect and socket timeouts.
|
||||
|
||||
.Client Configuration
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
// optional if Basic Auhtentication is needed
|
||||
HttpHeaders defaultHeaders = new HttpHeaders();
|
||||
defaultHeaders.setBasicAuth(USER_NAME, USER_PASS); <1>
|
||||
|
||||
ClientConfiguration clientConfiguration = ClientConfiguration.builder()
|
||||
.connectedTo("localhost:9200", "localhost:9291") <2>
|
||||
.withConnectTimeout(Duration.ofSeconds(5)) <3>
|
||||
.withSocketTimeout(Duration.ofSeconds(3)) <4>
|
||||
.usingSsl() <5>
|
||||
.withDefaultHeaders(defaultHeaders) <6>
|
||||
.withBasicAuth(username, password) <7>
|
||||
. // ... other options
|
||||
.build();
|
||||
|
||||
----
|
||||
<1> Define default headers, if they need to be customized
|
||||
<2> Use the builder to provide cluster addresses, set default `HttpHeaders` or enable SSL.
|
||||
<3> Set the connection timeout. Default is 10 sec.
|
||||
<4> Set the socket timeout. Default is 5 sec.
|
||||
<5> Optionally enable SSL.
|
||||
<6> Optionally set headers.
|
||||
<7> Add basic authentication.
|
||||
====
|
||||
|
||||
[[elasticsearch.clients.logging]]
|
||||
== Client Logging
|
||||
|
||||
To see what is actually sent to and received from the server `Request` / `Response` logging on the transport level needs
|
||||
to be turned on as outlined in the snippet below.
|
||||
|
||||
.Enable transport layer logging
|
||||
[source,xml]
|
||||
----
|
||||
<logger name="org.springframework.data.elasticsearch.client.WIRE" level="trace"/>
|
||||
----
|
||||
|
||||
NOTE: The above applies to both the `RestHighLevelClient` and `ReactiveElasticsearchClient` when obtained via `RestClients` respectively `ReactiveRestClients`, is not available for the `TransportClient`.
|
||||
@@ -14,12 +14,12 @@ Filter Builder improves query speed.
|
||||
private ElasticsearchTemplate elasticsearchTemplate;
|
||||
|
||||
SearchQuery searchQuery = new NativeSearchQueryBuilder()
|
||||
.withQuery(matchAllQuery())
|
||||
.withFilter(boolFilter().must(termFilter("id", documentId)))
|
||||
.build();
|
||||
|
||||
.withQuery(matchAllQuery())
|
||||
.withFilter(boolFilter().must(termFilter("id", documentId)))
|
||||
.build();
|
||||
|
||||
Page<SampleEntity> sampleEntities =
|
||||
elasticsearchTemplate.queryForPage(searchQuery,SampleEntity.class);
|
||||
elasticsearchTemplate.queryForPage(searchQuery,SampleEntity.class);
|
||||
----
|
||||
====
|
||||
|
||||
@@ -33,21 +33,21 @@ Elasticsearch has a scroll API for getting big result set in chunks. `Elasticsea
|
||||
[source,java]
|
||||
----
|
||||
SearchQuery searchQuery = new NativeSearchQueryBuilder()
|
||||
.withQuery(matchAllQuery())
|
||||
.withIndices(INDEX_NAME)
|
||||
.withTypes(TYPE_NAME)
|
||||
.withFields("message")
|
||||
.withPageable(PageRequest.of(0, 10))
|
||||
.build();
|
||||
.withQuery(matchAllQuery())
|
||||
.withIndices(INDEX_NAME)
|
||||
.withTypes(TYPE_NAME)
|
||||
.withFields("message")
|
||||
.withPageable(PageRequest.of(0, 10))
|
||||
.build();
|
||||
|
||||
ScrolledPage<SampleEntity> scroll = elasticsearchTemplate.startScroll(1000, searchQuery, SampleEntity.class);
|
||||
Page<SampleEntity> scroll = elasticsearchTemplate.startScroll(1000, searchQuery, SampleEntity.class);
|
||||
|
||||
String scrollId = scroll.getScrollId();
|
||||
String scrollId = ((ScrolledPage) scroll).getScrollId();
|
||||
List<SampleEntity> sampleEntities = new ArrayList<>();
|
||||
while (scroll.hasContent()) {
|
||||
sampleEntities.addAll(scroll.getContent());
|
||||
scrollId = scroll.getScrollId();
|
||||
scroll = elasticsearchTemplate.continueScroll(scrollId, 1000, SampleEntity.class);
|
||||
sampleEntities.addAll(scroll.getContent());
|
||||
scrollId = ((ScrolledPage) scroll).getScrollId();
|
||||
scroll = elasticsearchTemplate.continueScroll(scrollId, 1000, SampleEntity.class);
|
||||
}
|
||||
elasticsearchTemplate.clearScroll(scrollId);
|
||||
----
|
||||
@@ -60,18 +60,18 @@ elasticsearchTemplate.clearScroll(scrollId);
|
||||
[source,java]
|
||||
----
|
||||
SearchQuery searchQuery = new NativeSearchQueryBuilder()
|
||||
.withQuery(matchAllQuery())
|
||||
.withIndices(INDEX_NAME)
|
||||
.withTypes(TYPE_NAME)
|
||||
.withFields("message")
|
||||
.withPageable(PageRequest.of(0, 10))
|
||||
.build();
|
||||
.withQuery(matchAllQuery())
|
||||
.withIndices(INDEX_NAME)
|
||||
.withTypes(TYPE_NAME)
|
||||
.withFields("message")
|
||||
.withPageable(PageRequest.of(0, 10))
|
||||
.build();
|
||||
|
||||
CloseableIterator<SampleEntity> stream = elasticsearchTemplate.stream(searchQuery, SampleEntity.class);
|
||||
|
||||
List<SampleEntity> sampleEntities = new ArrayList<>();
|
||||
while (stream.hasNext()) {
|
||||
sampleEntities.add(stream.next());
|
||||
sampleEntities.add(stream.next());
|
||||
}
|
||||
----
|
||||
====
|
||||
====
|
||||
@@ -1,12 +0,0 @@
|
||||
[[new-features]]
|
||||
= What's new
|
||||
|
||||
[[new-features.3-2-0]]
|
||||
== New in Spring Data Elasticsearch 3.2
|
||||
|
||||
* Secured Elasticsearch cluster support with Basic Authentication and SSL transport.
|
||||
* Upgrade to Elasticsearch 6.8.1.
|
||||
* Reactive programming support with <<elasticsearch.reactive.operations>> and <<elasticsearch.reactive.repositories>>.
|
||||
* Introduction of the <<elasticsearch.mapping.meta-model,ElasticsearchEntityMapper>> as an alternative to the Jackson `ObjectMapper`.
|
||||
* Field name customization in `@Field`.
|
||||
* Support for Delete by Query.
|
||||
@@ -1,316 +0,0 @@
|
||||
[[elasticsearch.mapping]]
|
||||
= Elasticsearch Object Mapping
|
||||
|
||||
Spring Data Elasticsearch allows to choose between two mapping implementations abstracted via the `EntityMapper` interface:
|
||||
|
||||
* <<elasticsearch.mapping.jackson2>>
|
||||
* <<elasticsearch.mapping.meta-model>>
|
||||
|
||||
[[elasticsearch.mapping.jackson2]]
|
||||
== Jackson Object Mapping
|
||||
|
||||
The Jackson2 based approach (used by default) utilizes a customized `ObjectMapper` instance with spring data specific modules.
|
||||
Extensions to the actual mapping need to be customized via Jackson annotations like `@JsonInclude`.
|
||||
|
||||
.Jackson2 Object Mapping Configuration
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
@Configuration
|
||||
public class Config extends AbstractElasticsearchConfiguration { <1>
|
||||
|
||||
@Override
|
||||
public RestHighLevelClient elasticsearchClient() {
|
||||
return RestClients.create(ClientConfiguration.create("localhost:9200")).rest();
|
||||
}
|
||||
}
|
||||
----
|
||||
<1> `AbstractElasticsearchConfiguration` already defines a Jackson2 based `entityMapper` via `ElasticsearchConfigurationSupport`.
|
||||
====
|
||||
|
||||
[WARNING]
|
||||
`CustomConversions`, `@ReadingConverter` & `@WritingConverter` cannot be applied when using the Jackson based `EntityMapper`. +
|
||||
Setting the name of a mapped field with `@Field(name="custom-name")` also cannot be used with this Mapper.
|
||||
|
||||
[[elasticsearch.mapping.meta-model]]
|
||||
== Meta Model Object Mapping
|
||||
|
||||
The Metamodel based approach uses domain type information for reading/writing from/to Elasticsearch.
|
||||
This allows to register `Converter` instances for specific domain type mapping.
|
||||
|
||||
.Meta Model Object Mapping Configuration
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
@Configuration
|
||||
public class Config extends AbstractElasticsearchConfiguration {
|
||||
|
||||
@Override
|
||||
public RestHighLevelClient elasticsearchClient() {
|
||||
return RestClients.create(ClientConfiguration.create("localhost:9200")).rest()
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Override
|
||||
public EntityMapper entityMapper() { <1>
|
||||
|
||||
ElasticsearchEntityMapper entityMapper = new ElasticsearchEntityMapper(
|
||||
elasticsearchMappingContext(), new DefaultConversionService() <2>
|
||||
);
|
||||
entityMapper.setConversions(elasticsearchCustomConversions()); <3>
|
||||
|
||||
return entityMapper;
|
||||
}
|
||||
}
|
||||
----
|
||||
<1> Overwrite the default `EntityMapper` from `ElasticsearchConfigurationSupport` and expose it as bean.
|
||||
<2> Use the provided `SimpleElasticsearchMappingContext` to avoid inconsistencies and provide a `GenericConversionService`
|
||||
for `Converter` registration.
|
||||
<3> Optionally set `CustomConversions` if applicable.
|
||||
====
|
||||
|
||||
[[elasticsearch.mapping.meta-model.annotations]]
|
||||
=== Mapping Annotation Overview
|
||||
|
||||
The `ElasticsearchEntityMapper` can use metadata to drive the mapping of objects to documents. The following annotations are available:
|
||||
|
||||
* `@Id`: Applied at the field level to mark the field used for identity purpose.
|
||||
* `@Document`: Applied at the class level to indicate this class is a candidate for mapping to the database. The most important attributes are:
|
||||
** `indexName`: the name of the index to store this entity in
|
||||
** `type`: the mapping type. If not set, the lowercased simple name of the class is used.
|
||||
** `shards`: the number of shards for the index.
|
||||
** `replicas`: the number of replicas for the index.
|
||||
** `refreshIntervall`: Refresh interval for the index. Used for index creation. Default value is _"1s"_.
|
||||
** `indexStoreType`: Index storage type for the index. Used for index creation. Default value is _"fs"_.
|
||||
** `createIndex`: Configuration whether to create an index on repository bootstrapping. Default value is _true_.
|
||||
** `versionType`: Configuration of version management. Default value is _EXTERNAL_.
|
||||
* `@Transient`: By default all private fields are mapped to the document, this annotation excludes the field where it is applied from being stored in the database
|
||||
* `@PersistenceConstructor`: Marks a given constructor - even a package protected one - to use when instantiating the object from the database. Constructor arguments are mapped by name to the key values in the retrieved Document.
|
||||
* `@Field`: Applied at the field level and defines properties of the field, most of the attributes map to the respective https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping.html[Elasticsearch Mapping] definitions:
|
||||
** `name`: The name of the field as it will be represented in the Elasticsearch document, if not set, the Java field name is used.
|
||||
** `type`: the field type, can be one of _Text, Integer, Long, Date, Float, Double, Boolean, Object, Auto, Nested, Ip, Attachment, Keyword_.
|
||||
** `format` and `pattern` custom definitions for the _Date_ type.
|
||||
** `store`: Flag wether the original field value should be store in Elasticsearch, default value is _false_.
|
||||
** `analyzer`, `searchAnalyzer`, `normalizer` for specifying custom custom analyzers and normalizer.
|
||||
** `copy_to`: the target field to copy multiple document fields to.
|
||||
* `@GeoPoint`: marks a field as _geo_point_ datatype. Can be omitted if the field is an instance of the `GeoPoint` class.
|
||||
|
||||
|
||||
The mapping metadata infrastructure is defined in a separate spring-data-commons project that is technology agnostic.
|
||||
|
||||
[[elasticsearch.mapping.meta-model.rules]]
|
||||
=== Mapping Rules
|
||||
|
||||
==== Type Hints
|
||||
|
||||
Mapping uses _type hints_ embedded in the document sent to the server to allow generic type mapping.
|
||||
Those type hints are represented as `_class` attributes within the document and are written for each aggregate root.
|
||||
|
||||
.Type Hints
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
public class Person { <1>
|
||||
|
||||
@Id String id;
|
||||
String firstname;
|
||||
String lastname;
|
||||
}
|
||||
----
|
||||
[source,json]
|
||||
----
|
||||
{
|
||||
"_class" : "com.example.Person", <1>
|
||||
"id" : "cb7bef",
|
||||
"firstname" : "Sarah",
|
||||
"lastname" : "Connor"
|
||||
}
|
||||
----
|
||||
<1> By default the domain types class name is used for the type hint.
|
||||
====
|
||||
|
||||
Type hints can be configured to hold custom information. Use the `@TypeAlias` annotation to do so.
|
||||
|
||||
NOTE: Make sure to add types with `@TypeAlias` to the initial entity set (`AbstractElasticsearchConfiguration#getInitialEntitySet`)
|
||||
to already have entity information available when first reading data from the store.
|
||||
|
||||
.Type Hints with Alias
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
@TypeAlias("human") <1>
|
||||
public class Person {
|
||||
|
||||
@Id String id;
|
||||
// ...
|
||||
}
|
||||
----
|
||||
[source,json]
|
||||
----
|
||||
{
|
||||
"_class" : "human", <1>
|
||||
"id" : ...
|
||||
}
|
||||
----
|
||||
<1> The configured alias is used when writing the entity.
|
||||
====
|
||||
|
||||
NOTE: Type hints will not be written for nested Objects unless the properties type is `Object`, an interface or the actual value type does not match the properties declaration.
|
||||
|
||||
==== Geospatial Types
|
||||
|
||||
Geospatial types like `Point` & `GeoPoint` are converted into _lat/lon_ pairs.
|
||||
|
||||
.Geospatial types
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
public class Address {
|
||||
|
||||
String city, street;
|
||||
Point location;
|
||||
}
|
||||
----
|
||||
[source,json]
|
||||
----
|
||||
{
|
||||
"city" : "Los Angeles",
|
||||
"street" : "2800 East Observatory Road",
|
||||
"location" : { "lat" : 34.118347, "lon" : -118.3026284 }
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
==== Collections
|
||||
|
||||
For values inside Collections apply the same mapping rules as for aggregate roots when it comes to _type hints_ and <<elasticsearch.mapping.meta-model.conversions>>.
|
||||
|
||||
.Collections
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
public class Person {
|
||||
|
||||
// ...
|
||||
|
||||
List<Person> friends;
|
||||
|
||||
}
|
||||
----
|
||||
[source,json]
|
||||
----
|
||||
{
|
||||
// ...
|
||||
|
||||
"friends" : [ { "firstname" : "Kyle", "lastname" : "Reese" } ]
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
==== Maps
|
||||
|
||||
For values inside Maps apply the same mapping rules as for aggregate roots when it comes to _type hints_ and <<elasticsearch.mapping.meta-model.conversions>>.
|
||||
However the Map key needs to a String to be processed by Elasticsearch.
|
||||
|
||||
.Collections
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
public class Person {
|
||||
|
||||
// ...
|
||||
|
||||
Map<String, Address> knownLocations;
|
||||
|
||||
}
|
||||
----
|
||||
[source,json]
|
||||
----
|
||||
{
|
||||
// ...
|
||||
|
||||
"knownLocations" : {
|
||||
"arrivedAt" : {
|
||||
"city" : "Los Angeles",
|
||||
"street" : "2800 East Observatory Road",
|
||||
"location" : { "lat" : 34.118347, "lon" : -118.3026284 }
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
[[elasticsearch.mapping.meta-model.conversions]]
|
||||
=== Custom Conversions
|
||||
|
||||
Looking at the `Configuration` from the <<elasticsearch.mapping.meta-model, previous section>> `ElasticsearchCustomConversions` allows registering specific rules for mapping domain and simple types.
|
||||
|
||||
.Meta Model Object Mapping Configuration
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
@Configuration
|
||||
public class Config extends AbstractElasticsearchConfiguration {
|
||||
|
||||
@Override
|
||||
public RestHighLevelClient elasticsearchClient() {
|
||||
return RestClients.create(ClientConfiguration.create("localhost:9200")).rest();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Override
|
||||
public EntityMapper entityMapper() {
|
||||
|
||||
ElasticsearchEntityMapper entityMapper = new ElasticsearchEntityMapper(
|
||||
elasticsearchMappingContext(), new DefaultConversionService());
|
||||
entityMapper.setConversions(elasticsearchCustomConversions()); <1>
|
||||
|
||||
return entityMapper;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Override
|
||||
public ElasticsearchCustomConversions elasticsearchCustomConversions() {
|
||||
return new ElasticsearchCustomConversions(
|
||||
Arrays.asList(new AddressToMap(), new MapToAddress())); <2>
|
||||
}
|
||||
|
||||
@WritingConverter <3>
|
||||
static class AddressToMap implements Converter<Address, Map<String, Object>> {
|
||||
|
||||
@Override
|
||||
public Map<String, Object> convert(Address source) {
|
||||
|
||||
LinkedHashMap<String, Object> target = new LinkedHashMap<>();
|
||||
target.put("ciudad", source.getCity());
|
||||
// ...
|
||||
|
||||
return target;
|
||||
}
|
||||
}
|
||||
|
||||
@ReadingConverter <4>
|
||||
static class MapToAddress implements Converter<Map<String, Object>, Address> {
|
||||
|
||||
@Override
|
||||
public Address convert(Map<String, Object> source) {
|
||||
|
||||
// ...
|
||||
return address;
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
[source,json]
|
||||
----
|
||||
{
|
||||
"ciudad" : "Los Angeles",
|
||||
"calle" : "2800 East Observatory Road",
|
||||
"localidad" : { "lat" : 34.118347, "lon" : -118.3026284 }
|
||||
}
|
||||
----
|
||||
<1> Register `ElasticsearchCustomConversions` with the `EntityMapper`.
|
||||
<2> Add `Converter` implementations.
|
||||
<3> Set up the `Converter` used for writing `DomainType` to Elasticsearch.
|
||||
<4> Set up the `Converter` used for reading `DomainType` from search result.
|
||||
====
|
||||
@@ -1,135 +0,0 @@
|
||||
[[elasticsearch.operations]]
|
||||
= Elasticsearch Operations
|
||||
|
||||
Spring Data Elasticsearch uses two interfaces to define the operations that can be called against an Elasticsearch index. These are `ElasticsearchOperations` and `ReactiveElasticsearchOperations`. Whereas the first is used with the classic synchronous implementations, the second one uses reactive infrastructure.
|
||||
|
||||
The default implementations of the interfaces offer:
|
||||
|
||||
* Read/Write mapping support for domain types.
|
||||
* A rich query and criteria api.
|
||||
* Resource management and Exception translation.
|
||||
|
||||
[[elasticsearch.operations.template]]
|
||||
== ElasticsearchTemplate
|
||||
|
||||
The `ElasticsearchTemplate` is an implementation of the `ElasticsearchOperations` interface using the <<elasticsearch.clients.transport>>.
|
||||
|
||||
.ElasticsearchTemplate configuration
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
@Configuration
|
||||
public class TransportClientConfig extends ElasticsearchConfigurationSupport {
|
||||
|
||||
@Bean
|
||||
public Client elasticsearchClient() throws UnknownHostException { <1>
|
||||
Settings settings = Settings.builder().put("cluster.name", "elasticsearch").build();
|
||||
TransportClient client = new PreBuiltTransportClient(settings);
|
||||
client.addTransportAddress(new TransportAddress(InetAddress.getByName("127.0.0.1"), 9300));
|
||||
return client;
|
||||
}
|
||||
|
||||
@Bean(name = {"elasticsearchOperations", "elasticsearchTemplate"})
|
||||
public ElasticsearchTemplate elasticsearchTemplate() throws UnknownHostException { <2>
|
||||
return new ElasticsearchTemplate(elasticsearchClient(), entityMapper());
|
||||
}
|
||||
|
||||
// use the ElasticsearchEntityMapper
|
||||
@Bean
|
||||
@Override
|
||||
public EntityMapper entityMapper() { <3>
|
||||
ElasticsearchEntityMapper entityMapper = new ElasticsearchEntityMapper(elasticsearchMappingContext(),
|
||||
new DefaultConversionService());
|
||||
entityMapper.setConversions(elasticsearchCustomConversions());
|
||||
return entityMapper;
|
||||
}
|
||||
}
|
||||
----
|
||||
<1> Setting up the <<elasticsearch.clients.transport>>.
|
||||
<2> Creating the `ElasticsearchTemplate` bean, offering both names, _elasticsearchOperations_ and _elasticsearchTemplate_.
|
||||
<3> Using the <<elasticsearch.mapping.meta-model>> ElasticsearchMapper.
|
||||
====
|
||||
|
||||
[[elasticsearch.operations.resttemplate]]
|
||||
== ElasticsearchRestTemplate
|
||||
|
||||
The `ElasticsearchRestTemplate` is an implementation of the `ElasticsearchOperations` interface using the <<elasticsearch.clients.rest>>.
|
||||
|
||||
.ElasticsearchRestTemplate configuration
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
@Configuration
|
||||
public class RestClientConfig extends AbstractElasticsearchConfiguration {
|
||||
@Override
|
||||
public RestHighLevelClient elasticsearchClient() { <1>
|
||||
return RestClients.create(ClientConfiguration.localhost()).rest();
|
||||
}
|
||||
|
||||
// no special bean creation needed <2>
|
||||
|
||||
// use the ElasticsearchEntityMapper
|
||||
@Bean
|
||||
@Override
|
||||
public EntityMapper entityMapper() { <3>
|
||||
ElasticsearchEntityMapper entityMapper = new ElasticsearchEntityMapper(elasticsearchMappingContext(),
|
||||
new DefaultConversionService());
|
||||
entityMapper.setConversions(elasticsearchCustomConversions());
|
||||
|
||||
return entityMapper;
|
||||
}
|
||||
}
|
||||
----
|
||||
<1> Setting up the <<elasticsearch.clients.rest>>.
|
||||
<2> The base class `AbstractElasticsearchConfiguration` already provides the `elasticsearchTemplate` bean.
|
||||
<3> Using the <<elasticsearch.mapping.meta-model>> ElasticsearchMapper.
|
||||
====
|
||||
|
||||
[[elasticsearch.operations.usage]]
|
||||
== Usage examples
|
||||
|
||||
As both `ElasticsearchTemplate` and `ElasticsearchRestTemplate` implement the `ElasticsearchOperations` interface, the code to use them is not different. The example shows how to use an injected `ElasticsearchOperations` instance in a Spring REST controller. The decision, if this is using the `TransportClient` or the `RestClient` is made by providing the
|
||||
corresponding Bean with one of the configurations shown above.
|
||||
|
||||
.ElasticsearchOperations usage
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
@RestController
|
||||
@RequestMapping("/")
|
||||
public class TestController {
|
||||
|
||||
private ElasticsearchOperations elasticsearchOperations;
|
||||
|
||||
public TestController(ElasticsearchOperations elasticsearchOperations) { <1>
|
||||
this.elasticsearchOperations = elasticsearchOperations;
|
||||
}
|
||||
|
||||
@PostMapping("/person")
|
||||
public String save(@RequestBody Person person) { <2>
|
||||
|
||||
IndexQuery indexQuery = new IndexQueryBuilder()
|
||||
.withId(person.getId().toString())
|
||||
.withObject(person)
|
||||
.build();
|
||||
String documentId = elasticsearchOperations.index(indexQuery);
|
||||
return documentId;
|
||||
}
|
||||
|
||||
@GetMapping("/person/{id}")
|
||||
public Person findById(@PathVariable("id") Long id) { <3>
|
||||
Person person = elasticsearchOperations
|
||||
.queryForObject(GetQuery.getById(id.toString()), Person.class);
|
||||
return person;
|
||||
}
|
||||
}
|
||||
|
||||
----
|
||||
<1> Let Spring inject the provided `ElasticsearchOperations` bean in the constructor.
|
||||
<2> Store some entity in the Elasticsearch cluster.
|
||||
<3> Retrieve the entity with a query by id.
|
||||
====
|
||||
|
||||
To see the full possibilities of `ElasticsearchOperations` please refer to the API documentation.
|
||||
|
||||
include::reactive-elasticsearch-operations.adoc[leveloffset=+1]
|
||||
@@ -1,149 +0,0 @@
|
||||
[[elasticsearch.repositories]]
|
||||
= Elasticsearch Repositories
|
||||
|
||||
This chapter includes details of the Elasticsearch repository implementation.
|
||||
|
||||
include::elasticsearch-repository-queries.adoc[leveloffset=+1]
|
||||
|
||||
[[elasticsearch.annotation]]
|
||||
== Annotation based configuration
|
||||
|
||||
The Spring Data Elasticsearch repositories support can be activated using an annotation through JavaConfig.
|
||||
|
||||
.Spring Data Elasticsearch repositories using JavaConfig
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
@Configuration
|
||||
@EnableElasticsearchRepositories( <1>
|
||||
basePackages = "org.springframework.data.elasticsearch.repositories"
|
||||
)
|
||||
static class Config {
|
||||
|
||||
@Bean
|
||||
public ElasticsearchOperations elasticsearchTemplate() { <2>
|
||||
// ...
|
||||
}
|
||||
}
|
||||
|
||||
class ProductService {
|
||||
|
||||
private ProductRepository repository; <3>
|
||||
|
||||
public ProductService(ProductRepository repository) {
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
public Page<Product> findAvailableBookByName(String name, Pageable pageable) {
|
||||
return repository.findByAvailableTrueAndNameStartingWith(name, pageable);
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
<1> The `EnableElasticsearchRepositories` annotation activates the Repository support. If no base package is configured, it will use the one of the configuration class it is put on.
|
||||
<2> Provide a Bean named `elasticsearchTemplate` of type `ElasticsearchOperations` by using one of the configurations shown in the <<elasticsearch.operations>> chapter.
|
||||
<3> Let Spring inject the Repository bean into your class.
|
||||
====
|
||||
|
||||
[[elasticsearch.cdi]]
|
||||
== Elasticsearch Repositories using CDI
|
||||
|
||||
The Spring Data Elasticsearch repositories can also be set up using CDI functionality.
|
||||
|
||||
.Spring Data Elasticsearch repositories using CDI
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
class ElasticsearchTemplateProducer {
|
||||
|
||||
@Produces
|
||||
@ApplicationScoped
|
||||
public ElasticsearchOperations createElasticsearchTemplate() {
|
||||
// ... <1>
|
||||
}
|
||||
}
|
||||
|
||||
class ProductService {
|
||||
|
||||
private ProductRepository repository; <2>
|
||||
public Page<Product> findAvailableBookByName(String name, Pageable pageable) {
|
||||
return repository.findByAvailableTrueAndNameStartingWith(name, pageable);
|
||||
}
|
||||
@Inject
|
||||
public void setRepository(ProductRepository repository) {
|
||||
this.repository = repository;
|
||||
}
|
||||
}
|
||||
----
|
||||
<1> Create a component by using the same calls as are used in the <<elasticsearch.operations>> chapter.
|
||||
<2> Let the CDI framework inject the Repository into your class.
|
||||
|
||||
====
|
||||
|
||||
[[elasticsearch.namespace]]
|
||||
== Spring Namespace
|
||||
|
||||
The Spring Data Elasticsearch module contains a custom namespace allowing definition of repository beans as well as elements for instantiating a `ElasticsearchServer` .
|
||||
|
||||
Using the `repositories` element looks up Spring Data repositories as described in <<repositories.create-instances>> .
|
||||
|
||||
.Setting up Elasticsearch repositories using Namespace
|
||||
====
|
||||
[source,xml]
|
||||
----
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:elasticsearch="http://www.springframework.org/schema/data/elasticsearch"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans
|
||||
https://www.springframework.org/schema/beans/spring-beans-3.1.xsd
|
||||
http://www.springframework.org/schema/data/elasticsearch
|
||||
https://www.springframework.org/schema/data/elasticsearch/spring-elasticsearch-1.0.xsd">
|
||||
|
||||
<elasticsearch:repositories base-package="com.acme.repositories" />
|
||||
|
||||
</beans>
|
||||
----
|
||||
====
|
||||
|
||||
Using the `Transport Client` or `Rest Client` element registers an instance of `Elasticsearch Server` in the context.
|
||||
|
||||
.Transport Client using Namespace
|
||||
====
|
||||
[source,xml]
|
||||
----
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:elasticsearch="http://www.springframework.org/schema/data/elasticsearch"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans
|
||||
https://www.springframework.org/schema/beans/spring-beans-3.1.xsd
|
||||
http://www.springframework.org/schema/data/elasticsearch
|
||||
https://www.springframework.org/schema/data/elasticsearch/spring-elasticsearch-1.0.xsd">
|
||||
|
||||
<elasticsearch:transport-client id="client" cluster-nodes="localhost:9300,someip:9300" />
|
||||
|
||||
</beans>
|
||||
----
|
||||
====
|
||||
|
||||
.Rest Client using Namespace
|
||||
====
|
||||
[source,xml]
|
||||
----
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:elasticsearch="http://www.springframework.org/schema/data/elasticsearch"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/data/elasticsearch
|
||||
https://www.springframework.org/schema/data/elasticsearch/spring-elasticsearch.xsd
|
||||
http://www.springframework.org/schema/beans
|
||||
https://www.springframework.org/schema/beans/spring-beans.xsd">
|
||||
|
||||
<elasticsearch:rest-client id="restClient" hosts="http://localhost:9200">
|
||||
|
||||
</beans>
|
||||
----
|
||||
====
|
||||
|
||||
include::reactive-elasticsearch-repositories.adoc[leveloffset=+1]
|
||||
@@ -1,302 +0,0 @@
|
||||
[[elasticsearch.query-methods]]
|
||||
= Query methods
|
||||
|
||||
[[elasticsearch.query-methods.finders]]
|
||||
== Query lookup strategies
|
||||
|
||||
The Elasticsearch module supports all basic query building feature as string queries, native search queries, criteria based queries or have it being derived from the method name.
|
||||
|
||||
=== Declared queries
|
||||
|
||||
Deriving the query from the method name is not always sufficient and/or may result in unreadable method names. In this case one might make use of the `@Query` annotation (see <<elasticsearch.query-methods.at-query>> ).
|
||||
|
||||
[[elasticsearch.query-methods.criterions]]
|
||||
== Query creation
|
||||
|
||||
Generally the query creation mechanism for Elasticsearch works as described in <<repositories.query-methods>>. Here's a short example of what a Elasticsearch query method translates into:
|
||||
|
||||
.Query creation from method names
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
interface BookRepository extends Repository<Book, String> {
|
||||
List<Book> findByNameAndPrice(String name, Integer price);
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
The method name above will be translated into the following Elasticsearch json query
|
||||
|
||||
[source]
|
||||
----
|
||||
{
|
||||
"query": {
|
||||
"bool" : {
|
||||
"must" : [
|
||||
{ "query_string" : { "query" : "?", "fields" : [ "name" ] } },
|
||||
{ "query_string" : { "query" : "?", "fields" : [ "price" ] } }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
A list of supported keywords for Elasticsearch is shown below.
|
||||
|
||||
[cols="1,2,3", options="header"]
|
||||
.Supported keywords inside method names
|
||||
|===
|
||||
| Keyword
|
||||
| Sample
|
||||
| Elasticsearch Query String| `And`
|
||||
| `findByNameAndPrice`
|
||||
| `{ "query" : {
|
||||
"bool" : {
|
||||
"must" : [
|
||||
{ "query_string" : { "query" : "?", "fields" : [ "name" ] } },
|
||||
{ "query_string" : { "query" : "?", "fields" : [ "price" ] } }
|
||||
]
|
||||
}
|
||||
}}`
|
||||
|
||||
| `Or`
|
||||
| `findByNameOrPrice`
|
||||
| `{ "query" : {
|
||||
"bool" : {
|
||||
"should" : [
|
||||
{ "query_string" : { "query" : "?", "fields" : [ "name" ] } },
|
||||
{ "query_string" : { "query" : "?", "fields" : [ "price" ] } }
|
||||
]
|
||||
}
|
||||
}}`
|
||||
|
||||
| `Is`
|
||||
| `findByName`
|
||||
| `{ "query" : {
|
||||
"bool" : {
|
||||
"must" : [
|
||||
{ "query_string" : { "query" : "?", "fields" : [ "name" ] } }
|
||||
]
|
||||
}
|
||||
}}`
|
||||
|
||||
| `Not`
|
||||
| `findByNameNot`
|
||||
| `{ "query" : {
|
||||
"bool" : {
|
||||
"must_not" : [
|
||||
{ "query_string" : { "query" : "?", "fields" : [ "name" ] } }
|
||||
]
|
||||
}
|
||||
}}`
|
||||
|
||||
| `Between`
|
||||
| `findByPriceBetween`
|
||||
| `{ "query" : {
|
||||
"bool" : {
|
||||
"must" : [
|
||||
{"range" : {"price" : {"from" : ?, "to" : ?, "include_lower" : true, "include_upper" : true } } }
|
||||
]
|
||||
}
|
||||
}}`
|
||||
|
||||
| `LessThan`
|
||||
| `findByPriceLessThan`
|
||||
| `{ "query" : {
|
||||
"bool" : {
|
||||
"must" : [
|
||||
{"range" : {"price" : {"from" : null, "to" : ?, "include_lower" : true, "include_upper" : false } } }
|
||||
]
|
||||
}
|
||||
}}`
|
||||
|
||||
| `LessThanEqual`
|
||||
| `findByPriceLessThanEqual`
|
||||
| `{ "query" : {
|
||||
"bool" : {
|
||||
"must" : [
|
||||
{"range" : {"price" : {"from" : null, "to" : ?, "include_lower" : true, "include_upper" : true } } }
|
||||
]
|
||||
}
|
||||
}}`
|
||||
|
||||
| `GreaterThan`
|
||||
| `findByPriceGreaterThan`
|
||||
| `{ "query" : {
|
||||
"bool" : {
|
||||
"must" : [
|
||||
{"range" : {"price" : {"from" : ?, "to" : null, "include_lower" : false, "include_upper" : true } } }
|
||||
]
|
||||
}
|
||||
}}`
|
||||
|
||||
|
||||
| `GreaterThanEqual`
|
||||
| `findByPriceGreaterThan`
|
||||
| `{ "query" : {
|
||||
"bool" : {
|
||||
"must" : [
|
||||
{"range" : {"price" : {"from" : ?, "to" : null, "include_lower" : true, "include_upper" : true } } }
|
||||
]
|
||||
}
|
||||
}}`
|
||||
|
||||
| `Before`
|
||||
| `findByPriceBefore`
|
||||
| `{ "query" : {
|
||||
"bool" : {
|
||||
"must" : [
|
||||
{"range" : {"price" : {"from" : null, "to" : ?, "include_lower" : true, "include_upper" : true } } }
|
||||
]
|
||||
}
|
||||
}}`
|
||||
|
||||
| `After`
|
||||
| `findByPriceAfter`
|
||||
| `{ "query" : {
|
||||
"bool" : {
|
||||
"must" : [
|
||||
{"range" : {"price" : {"from" : ?, "to" : null, "include_lower" : true, "include_upper" : true } } }
|
||||
]
|
||||
}
|
||||
}}`
|
||||
|
||||
| `Like`
|
||||
| `findByNameLike`
|
||||
| `{ "query" : {
|
||||
"bool" : {
|
||||
"must" : [
|
||||
{ "query_string" : { "query" : "?*", "fields" : [ "name" ] }, "analyze_wildcard": true }
|
||||
]
|
||||
}
|
||||
}}`
|
||||
|
||||
| `StartingWith`
|
||||
| `findByNameStartingWith`
|
||||
| `{ "query" : {
|
||||
"bool" : {
|
||||
"must" : [
|
||||
{ "query_string" : { "query" : "?*", "fields" : [ "name" ] }, "analyze_wildcard": true }
|
||||
]
|
||||
}
|
||||
}}`
|
||||
|
||||
| `EndingWith`
|
||||
| `findByNameEndingWith`
|
||||
| `{ "query" : {
|
||||
"bool" : {
|
||||
"must" : [
|
||||
{ "query_string" : { "query" : "*?", "fields" : [ "name" ] }, "analyze_wildcard": true }
|
||||
]
|
||||
}
|
||||
}}`
|
||||
|
||||
| `Contains/Containing`
|
||||
| `findByNameContaining`
|
||||
| `{ "query" : {
|
||||
"bool" : {
|
||||
"must" : [
|
||||
{ "query_string" : { "query" : "\*?*", "fields" : [ "name" ] }, "analyze_wildcard": true }
|
||||
]
|
||||
}
|
||||
}}`
|
||||
|
||||
| `In`
|
||||
| `findByNameIn(Collection<String>names)`
|
||||
| `{ "query" : {
|
||||
"bool" : {
|
||||
"must" : [
|
||||
{"bool" : {"must" : [
|
||||
{"terms" : {"name" : ["?","?"]}}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}}`
|
||||
|
||||
| `NotIn`
|
||||
| `findByNameNotIn(Collection<String>names)`
|
||||
| `{ "query" : {
|
||||
"bool" : {
|
||||
"must" : [
|
||||
{"bool" : {"must_not" : [
|
||||
{"terms" : {"name" : ["?","?"]}}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}}`
|
||||
|
||||
| `Near`
|
||||
| `findByStoreNear`
|
||||
| `Not Supported Yet !`
|
||||
|
||||
| `True`
|
||||
| `findByAvailableTrue`
|
||||
| `{ "query" : {
|
||||
"bool" : {
|
||||
"must" : [
|
||||
{ "query_string" : { "query" : "true", "fields" : [ "available" ] } }
|
||||
]
|
||||
}
|
||||
}}`
|
||||
|
||||
| `False`
|
||||
| `findByAvailableFalse`
|
||||
| `{ "query" : {
|
||||
"bool" : {
|
||||
"must" : [
|
||||
{ "query_string" : { "query" : "false", "fields" : [ "available" ] } }
|
||||
]
|
||||
}
|
||||
}}`
|
||||
|
||||
| `OrderBy`
|
||||
| `findByAvailableTrueOrderByNameDesc`
|
||||
| `{ "query" : {
|
||||
"bool" : {
|
||||
"must" : [
|
||||
{ "query_string" : { "query" : "true", "fields" : [ "available" ] } }
|
||||
]
|
||||
}
|
||||
}, "sort":[{"name":{"order":"desc"}}]
|
||||
}`
|
||||
|
||||
|===
|
||||
|
||||
== Method return types
|
||||
|
||||
Repository methods can be defined to have the following return types for returning multiple Elements:
|
||||
|
||||
* `List<T>`
|
||||
* `Stream<T>`
|
||||
* `AggregatedPage<T>`
|
||||
|
||||
[[elasticsearch.query-methods.at-query]]
|
||||
== Using @Query Annotation
|
||||
|
||||
.Declare query at the method using the `@Query` annotation.
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
interface BookRepository extends ElasticsearchRepository<Book, String> {
|
||||
@Query("{\"match\": {\"name\": {\"query\": \"?0\"}}}")
|
||||
Page<Book> findByName(String name,Pageable pageable);
|
||||
}
|
||||
----
|
||||
The String that is set as the annotation argument must be a valid Elasticsearch JSON query. It will be sent to Easticsearch as value of the query element; if for example the function is called with the parameter _John_, it would produce the following query body:
|
||||
[source,json]
|
||||
----
|
||||
{
|
||||
"query": {
|
||||
"match": {
|
||||
"name": {
|
||||
"query": "John"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
@@ -1,124 +0,0 @@
|
||||
[[elasticsearch.reactive.operations]]
|
||||
= Reactive Elasticsearch Operations
|
||||
|
||||
`ReactiveElasticsearchOperations` is the gateway to executing high level commands against an Elasticsearch cluster using the `ReactiveElasticsearchClient`.
|
||||
|
||||
The `ReactiveElasticsearchTemplate` is the default implementation of `ReactiveElasticsearchOperations`.
|
||||
|
||||
[[elasticsearch.reactive.template]]
|
||||
== Reactive Elasticsearch Template
|
||||
|
||||
To get started the `ReactiveElasticsearchTemplate` needs to know about the actual client to work with.
|
||||
Please see <<elasticsearch.clients.reactive>> for details on the client.
|
||||
|
||||
[[elasticsearch.reactive.template.configuration]]
|
||||
=== Reactive Template Configuration
|
||||
|
||||
The easiest way of setting up the `ReactiveElasticsearchTemplate` is via `AbstractReactiveElasticsearchConfiguration` providing
|
||||
dedicated configuration method hooks for `base package`, the `initial entity set` etc.
|
||||
|
||||
.The AbstractReactiveElasticsearchConfiguration
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
@Configuration
|
||||
public class Config extends AbstractReactiveElasticsearchConfiguration {
|
||||
|
||||
@Bean <1>
|
||||
@Override
|
||||
public ReactiveElasticsearchClient reactiveElasticsearchClient() {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
<1> Configure the client to use. This can be done by `ReactiveRestClients` or directly via `DefaultReactiveElasticsearchClient`.
|
||||
====
|
||||
|
||||
NOTE: If applicable set default `HttpHeaders` via the `ClientConfiguration` of the `ReactiveElasticsearchClient`. See <<elasticsearch.clients.configuration>>.
|
||||
|
||||
TIP: If needed the `ReactiveElasticsearchTemplate` can be configured with default `RefreshPolicy` and `IndicesOptions` that get applied to the related requests by overriding the defaults of `refreshPolicy()` and `indicesOptions()`.
|
||||
|
||||
However one might want to be more in control over the actual components and use a more verbose approach.
|
||||
|
||||
.Configure the ReactiveElasticsearchTemplate
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
@Configuration
|
||||
public class Config {
|
||||
|
||||
@Bean <1>
|
||||
public ReactiveElasticsearchClient reactiveElasticsearchClient() {
|
||||
// ...
|
||||
}
|
||||
@Bean <2>
|
||||
public ElasticsearchConverter elasticsearchConverter() {
|
||||
return new MappingElasticsearchConverter(elasticsearchMappingContext());
|
||||
}
|
||||
@Bean <3>
|
||||
public SimpleElasticsearchMappingContext elasticsearchMappingContext() {
|
||||
return new SimpleElasticsearchMappingContext();
|
||||
}
|
||||
@Bean <4>
|
||||
public ReactiveElasticsearchOperations reactiveElasticsearchOperations() {
|
||||
return new ReactiveElasticsearchTemplate(reactiveElasticsearchClient(), elasticsearchConverter());
|
||||
}
|
||||
}
|
||||
----
|
||||
<1> Configure the client to use. This can be done by `ReactiveRestClients` or directly via `DefaultReactiveElasticsearchClient`.
|
||||
<2> Set up the `ElasticsearchConverter` used for domain type mapping utilizing metadata provided by the mapping context.
|
||||
<3> The Elasticsearch specific mapping context for domain type metadata.
|
||||
<4> The actual template based on the client and conversion infrastructure.
|
||||
====
|
||||
|
||||
[[elasticsearch.reactive.template.usage]]
|
||||
=== Reactive Template Usage
|
||||
|
||||
`ReactiveElasticsearchTemplate` lets you save, find and delete your domain objects and map those objects to documents stored in Elasticsearch.
|
||||
|
||||
Consider the following:
|
||||
|
||||
.Use the ReactiveElasticsearchTemplate
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
@Document(indexName = "marvel", type = "characters")
|
||||
public class Person {
|
||||
|
||||
private @Id String id;
|
||||
private String name;
|
||||
private int age;
|
||||
// Getter/Setter omitted...
|
||||
}
|
||||
----
|
||||
|
||||
[source,java]
|
||||
----
|
||||
template.save(new Person("Bruce Banner", 42)) <1>
|
||||
.doOnNext(System.out::println)
|
||||
.flatMap(person -> template.findById(person.id, Person.class)) <2>
|
||||
.doOnNext(System.out::println)
|
||||
.flatMap(person -> template.delete(person)) <3>
|
||||
.doOnNext(System.out::println)
|
||||
.flatMap(id -> template.count(Person.class)) <4>
|
||||
.doOnNext(System.out::println)
|
||||
.subscribe(); <5>
|
||||
----
|
||||
|
||||
The above outputs the following sequence on the console.
|
||||
|
||||
[source,text]
|
||||
----
|
||||
> Person(id=QjWCWWcBXiLAnp77ksfR, name=Bruce Banner, age=42)
|
||||
> Person(id=QjWCWWcBXiLAnp77ksfR, name=Bruce Banner, age=42)
|
||||
> QjWCWWcBXiLAnp77ksfR
|
||||
> 0
|
||||
----
|
||||
<1> Insert a new `Person` document into the _marvel_ index under type _characters_. The `id` is generated on server side and set into the instance returned.
|
||||
<2> Lookup the `Person` with matching `id` in the _marvel_ index under type _characters_.
|
||||
<3> Delete the `Person` with matching `id`, extracted from the given instance, in the _marvel_ index under type _characters_.
|
||||
<4> Count the total number of documents in the _marvel_ index under type _characters_.
|
||||
<5> Don't forget to _subscribe()_.
|
||||
====
|
||||
|
||||
|
||||
@@ -1,130 +0,0 @@
|
||||
[[elasticsearch.reactive.repositories]]
|
||||
= Reactive Elasticsearch Repositories
|
||||
|
||||
Reactive Elasticsearch repository support builds on the core repository support explained in <<repositories>> utilizing
|
||||
operations provided via <<elasticsearch.reactive.operations>> executed by a <<elasticsearch.clients.reactive>>.
|
||||
|
||||
Spring Data Elasticsearch reactive repository support uses https://projectreactor.io/[Project Reactor] as its reactive
|
||||
composition library of choice.
|
||||
|
||||
There are 3 main interfaces to be used:
|
||||
|
||||
* `ReactiveRepository`
|
||||
* `ReactiveCrudRepository`
|
||||
* `ReactiveSortingRepository`
|
||||
|
||||
[[elasticsearch.reactive.repositories.usage]]
|
||||
== Usage
|
||||
|
||||
To access domain objects stored in a Elasticsearch using a `Repository`, just create an interface for it.
|
||||
Before you can actually go on and do that you will need an entity.
|
||||
|
||||
.Sample `Person` entity
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
public class Person {
|
||||
|
||||
@Id
|
||||
private String id;
|
||||
private String firstname;
|
||||
private String lastname;
|
||||
private Address address;
|
||||
|
||||
// … getters and setters omitted
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
NOTE: Please note that the `id` property needs to be of type `String`.
|
||||
|
||||
.Basic repository interface to persist Person entities
|
||||
====
|
||||
[source]
|
||||
----
|
||||
interface ReactivePersonRepository extends ReactiveSortingRepository<Person, String> {
|
||||
|
||||
Flux<Person> findByFirstname(String firstname); <1>
|
||||
|
||||
Flux<Person> findByFirstname(Publisher<String> firstname); <2>
|
||||
|
||||
Flux<Person> findByFirstnameOrderByLastname(String firstname); <3>
|
||||
|
||||
Flux<Person> findByFirstname(String firstname, Sort sort); <4>
|
||||
|
||||
Flux<Person> findByFirstname(String firstname, Pageable page); <5>
|
||||
|
||||
Mono<Person> findByFirstnameAndLastname(String firstname, String lastname); <6>
|
||||
|
||||
Mono<Person> findFirstByLastname(String lastname); <7>
|
||||
|
||||
@Query("{ \"bool\" : { \"must\" : { \"term\" : { \"lastname\" : \"?0\" } } } }")
|
||||
Flux<Person> findByLastname(String lastname); <8>
|
||||
|
||||
Mono<Long> countByFirstname(String firstname) <9>
|
||||
|
||||
Mono<Boolean> existsByFirstname(String firstname) <10>
|
||||
|
||||
Mono<Long> deleteByFirstname(String firstname) <11>
|
||||
}
|
||||
----
|
||||
<1> The method shows a query for all people with the given `lastname`.
|
||||
<2> Finder method awaiting input from `Publisher` to bind parameter value for `firstname`.
|
||||
<3> Finder method ordering matching documents by `lastname`.
|
||||
<4> Finder method ordering matching documents by the expression defined via the `Sort` parameter.
|
||||
<5> Use `Pageable` to pass offset and sorting parameters to the database.
|
||||
<6> Finder method concating criteria using `And` / `Or` keywords.
|
||||
<7> Find the first matching entity.
|
||||
<8> The method shows a query for all people with the given `lastname` looked up by running the annotated `@Query` with given
|
||||
parameters.
|
||||
<9> Count all entities with matching `firstname`.
|
||||
<10> Check if at least one entity with matching `firstname` exists.
|
||||
<11> Delete all entites with matching `firstname`.
|
||||
====
|
||||
|
||||
[[elasticsearch.reactive.repositories.configuration]]
|
||||
== Configuration
|
||||
|
||||
For Java configuration, use the `@EnableReactiveElasticsearchRepositories` annotation. If no base package is configured,
|
||||
the infrastructure scans the package of the annotated configuration class.
|
||||
|
||||
The following listing shows how to use Java configuration for a repository:
|
||||
|
||||
.Java configuration for repositories
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
@Configuration
|
||||
@EnableReactiveElasticsearchRepositories
|
||||
public class Config extends AbstractReactiveElasticsearchConfiguration {
|
||||
|
||||
@Override
|
||||
public ReactiveElasticsearchClient reactiveElasticsearchClient() {
|
||||
return ReactiveRestClients.create(ClientConfiguration.localhost());
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
Because the repository from the previous example extends `ReactiveSortingRepository`, all CRUD operations are available
|
||||
as well as methods for sorted access to the entities. Working with the repository instance is a matter of dependency
|
||||
injecting it into a client, as the following example shows:
|
||||
|
||||
.Sorted access to Person entities
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
public class PersonRepositoryTests {
|
||||
|
||||
@Autowired ReactivePersonRepository repository;
|
||||
|
||||
@Test
|
||||
public void sortsElementsCorrectly() {
|
||||
|
||||
Flux<Person> persons = repository.findAll(Sort.by(new Order(ASC, "lastname")));
|
||||
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
@@ -15,81 +15,41 @@
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.annotations;
|
||||
|
||||
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 java.lang.annotation.*;
|
||||
|
||||
import org.elasticsearch.index.VersionType;
|
||||
|
||||
import org.springframework.data.annotation.Persistent;
|
||||
|
||||
/**
|
||||
* Identifies a domain object to be persisted to Elasticsearch.
|
||||
* Document
|
||||
*
|
||||
* @author Rizwan Idrees
|
||||
* @author Mohsin Husen
|
||||
* @author Mason Chan
|
||||
* @author Ivan Greene
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
|
||||
@Persistent
|
||||
@Inherited
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ ElementType.TYPE })
|
||||
@Target({ElementType.TYPE})
|
||||
public @interface Document {
|
||||
|
||||
/**
|
||||
* Name of the Elasticsearch index.
|
||||
* <ul>
|
||||
* <li>Lowercase only</li>
|
||||
* <li><Cannot include \, /, *, ?, ", <, >, |, ` ` (space character), ,, #/li>
|
||||
* <li>Cannot start with -, _, +</li>
|
||||
* <li>Cannot be . or ..</li>
|
||||
* <li>Cannot be longer than 255 bytes (note it is bytes, so multi-byte characters will count towards the 255 limit
|
||||
* faster)</li>
|
||||
* </ul>
|
||||
*/
|
||||
String indexName();
|
||||
|
||||
/**
|
||||
* Mapping type name.
|
||||
*/
|
||||
String type() default "";
|
||||
|
||||
/**
|
||||
* Use server-side settings when creating the index.
|
||||
*/
|
||||
boolean useServerConfiguration() default false;
|
||||
|
||||
/**
|
||||
* Number of shards for the index {@link #indexName()}. Used for index creation.
|
||||
*/
|
||||
short shards() default 5;
|
||||
|
||||
/**
|
||||
* Number of replicas for the index {@link #indexName()}. Used for index creation.
|
||||
*/
|
||||
short replicas() default 1;
|
||||
|
||||
/**
|
||||
* Refresh interval for the index {@link #indexName()}. Used for index creation.
|
||||
*/
|
||||
String refreshInterval() default "1s";
|
||||
|
||||
/**
|
||||
* Index storage type for the index {@link #indexName()}. Used for index creation.
|
||||
*/
|
||||
String indexStoreType() default "fs";
|
||||
|
||||
/**
|
||||
* Configuration whether to create an index on repository bootstrapping.
|
||||
*/
|
||||
boolean createIndex() default true;
|
||||
|
||||
/**
|
||||
* Configuration of version management.
|
||||
*/
|
||||
VersionType versionType() default VersionType.EXTERNAL;
|
||||
}
|
||||
|
||||
@@ -15,8 +15,6 @@
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.annotations;
|
||||
|
||||
import org.springframework.core.annotation.AliasFor;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Inherited;
|
||||
@@ -31,7 +29,6 @@ import java.lang.annotation.Target;
|
||||
* @author Jonathan Yan
|
||||
* @author Jakub Vavrik
|
||||
* @author Kevin Leturc
|
||||
* @author Peter-Josef Meisch
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
@@ -39,21 +36,6 @@ import java.lang.annotation.Target;
|
||||
@Inherited
|
||||
public @interface Field {
|
||||
|
||||
/**
|
||||
* Alias for {@link #name}.
|
||||
* @since 3.2
|
||||
*/
|
||||
@AliasFor("name")
|
||||
String value() default "";
|
||||
|
||||
/**
|
||||
* The <em>name</em> to be used to store the field inside the document.
|
||||
* <p>If not set, the name of the annotated property is used.
|
||||
* @since 3.2
|
||||
*/
|
||||
@AliasFor("value")
|
||||
String name() default "";
|
||||
|
||||
FieldType type() default FieldType.Auto;
|
||||
|
||||
boolean index() default true;
|
||||
|
||||
@@ -19,16 +19,12 @@ package org.springframework.data.elasticsearch.annotations;
|
||||
* @author Rizwan Idrees
|
||||
* @author Mohsin Husen
|
||||
* @author Artur Konczak
|
||||
* @author Zeng Zetang
|
||||
*/
|
||||
public enum FieldType {
|
||||
Text,
|
||||
Byte,
|
||||
Short,
|
||||
Integer,
|
||||
Long,
|
||||
Date,
|
||||
Half_Float,
|
||||
Float,
|
||||
Double,
|
||||
Boolean,
|
||||
|
||||
@@ -1,343 +0,0 @@
|
||||
/*
|
||||
* Copyright 2018-2020 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.elasticsearch.client;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketAddress;
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
import javax.net.ssl.SSLContext;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
/**
|
||||
* Configuration interface exposing common client configuration properties for Elasticsearch clients.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Peter-Josef Meisch
|
||||
* @author Huw Ayling-Miller
|
||||
* @author Henrique Amaral
|
||||
* @since 3.2
|
||||
*/
|
||||
public interface ClientConfiguration {
|
||||
|
||||
/**
|
||||
* Creates a new {@link ClientConfigurationBuilder} instance.
|
||||
*
|
||||
* @return a new {@link ClientConfigurationBuilder} instance.
|
||||
*/
|
||||
static ClientConfigurationBuilderWithRequiredEndpoint builder() {
|
||||
return new ClientConfigurationBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link ClientConfiguration} instance configured to localhost.
|
||||
* <p/>
|
||||
*
|
||||
* <pre class="code">
|
||||
* // "localhost:9200"
|
||||
* ClientConfiguration configuration = ClientConfiguration.localhost();
|
||||
* </pre>
|
||||
*
|
||||
* @return a new {@link ClientConfiguration} instance
|
||||
* @see ClientConfigurationBuilder#connectedToLocalhost()
|
||||
*/
|
||||
static ClientConfiguration localhost() {
|
||||
return new ClientConfigurationBuilder().connectedToLocalhost().build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link ClientConfiguration} instance configured to a single host given {@code hostAndPort}.
|
||||
* <p/>
|
||||
* For example given the endpoint http://localhost:9200
|
||||
*
|
||||
* <pre class="code">
|
||||
* ClientConfiguration configuration = ClientConfiguration.create("localhost:9200");
|
||||
* </pre>
|
||||
*
|
||||
* @return a new {@link ClientConfigurationBuilder} instance.
|
||||
*/
|
||||
static ClientConfiguration create(String hostAndPort) {
|
||||
return new ClientConfigurationBuilder().connectedTo(hostAndPort).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link ClientConfiguration} instance configured to a single host given {@link InetSocketAddress}.
|
||||
* <p/>
|
||||
* For example given the endpoint http://localhost:9200
|
||||
*
|
||||
* <pre class="code">
|
||||
* ClientConfiguration configuration = ClientConfiguration
|
||||
* .create(InetSocketAddress.createUnresolved("localhost", 9200));
|
||||
* </pre>
|
||||
*
|
||||
* @return a new {@link ClientConfigurationBuilder} instance.
|
||||
*/
|
||||
static ClientConfiguration create(InetSocketAddress socketAddress) {
|
||||
return new ClientConfigurationBuilder().connectedTo(socketAddress).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the configured endpoints.
|
||||
*
|
||||
* @return the configured endpoints.
|
||||
*/
|
||||
List<InetSocketAddress> getEndpoints();
|
||||
|
||||
/**
|
||||
* Obtain the {@link HttpHeaders} to be used by default.
|
||||
*
|
||||
* @return the {@link HttpHeaders} to be used by default.
|
||||
*/
|
||||
HttpHeaders getDefaultHeaders();
|
||||
|
||||
/**
|
||||
* Returns {@literal true} when the client should use SSL.
|
||||
*
|
||||
* @return {@literal true} when the client should use SSL.
|
||||
*/
|
||||
boolean useSsl();
|
||||
|
||||
/**
|
||||
* Returns the {@link SSLContext} to use. Can be {@link Optional#empty()} if unconfigured.
|
||||
*
|
||||
* @return the {@link SSLContext} to use. Can be {@link Optional#empty()} if unconfigured.
|
||||
*/
|
||||
Optional<SSLContext> getSslContext();
|
||||
|
||||
/**
|
||||
* Returns the {@link HostnameVerifier} to use. Can be {@link Optional#empty()} if unconfigured.
|
||||
*
|
||||
* @return the {@link HostnameVerifier} to use. Can be {@link Optional#empty()} if unconfigured.
|
||||
*/
|
||||
Optional<HostnameVerifier> getHostNameVerifier();
|
||||
|
||||
/**
|
||||
* Returns the {@link java.time.Duration connect timeout}.
|
||||
*
|
||||
* @see java.net.Socket#connect(SocketAddress, int)
|
||||
* @see io.netty.channel.ChannelOption#CONNECT_TIMEOUT_MILLIS
|
||||
*/
|
||||
Duration getConnectTimeout();
|
||||
|
||||
/**
|
||||
* Returns the {@link java.time.Duration socket timeout} which is typically applied as SO-timeout/read timeout.
|
||||
*
|
||||
* @see java.net.Socket#setSoTimeout(int)
|
||||
* @see io.netty.handler.timeout.ReadTimeoutHandler
|
||||
* @see io.netty.handler.timeout.WriteTimeoutHandler
|
||||
*/
|
||||
Duration getSocketTimeout();
|
||||
|
||||
/**
|
||||
* Returns the path prefix that should be prepended to HTTP(s) requests for Elasticsearch behind a proxy.
|
||||
*
|
||||
* @return the path prefix.
|
||||
* @since 3.2.4
|
||||
*/
|
||||
String getPathPrefix();
|
||||
|
||||
/**
|
||||
* returns an optionally set proxy in the form host:port
|
||||
*
|
||||
* @return the optional proxy
|
||||
* @since 3.2.4
|
||||
*/
|
||||
Optional<String> getProxy();
|
||||
|
||||
/**
|
||||
* @return the function for configuring a WebClient.
|
||||
*/
|
||||
Function<WebClient, WebClient> getWebClientConfigurer();
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
interface ClientConfigurationBuilderWithRequiredEndpoint {
|
||||
|
||||
/**
|
||||
* @param hostAndPort the {@literal host} and {@literal port} formatted as String {@literal host:port}.
|
||||
* @return the {@link MaybeSecureClientConfigurationBuilder}.
|
||||
*/
|
||||
default MaybeSecureClientConfigurationBuilder connectedTo(String hostAndPort) {
|
||||
return connectedTo(new String[] { hostAndPort });
|
||||
}
|
||||
|
||||
/**
|
||||
* @param hostAndPorts the list of {@literal host} and {@literal port} combinations formatted as String
|
||||
* {@literal host:port}.
|
||||
* @return the {@link MaybeSecureClientConfigurationBuilder}.
|
||||
*/
|
||||
MaybeSecureClientConfigurationBuilder connectedTo(String... hostAndPorts);
|
||||
|
||||
/**
|
||||
* @param endpoint the {@literal host} and {@literal port}.
|
||||
* @return the {@link MaybeSecureClientConfigurationBuilder}.
|
||||
*/
|
||||
default MaybeSecureClientConfigurationBuilder connectedTo(InetSocketAddress endpoint) {
|
||||
return connectedTo(new InetSocketAddress[] { endpoint });
|
||||
}
|
||||
|
||||
/**
|
||||
* @param endpoints the list of {@literal host} and {@literal port} combinations.
|
||||
* @return the {@link MaybeSecureClientConfigurationBuilder}.
|
||||
*/
|
||||
MaybeSecureClientConfigurationBuilder connectedTo(InetSocketAddress... endpoints);
|
||||
|
||||
/**
|
||||
* Obviously for testing.
|
||||
*
|
||||
* @return the {@link MaybeSecureClientConfigurationBuilder}.
|
||||
*/
|
||||
default MaybeSecureClientConfigurationBuilder connectedToLocalhost() {
|
||||
return connectedTo("localhost:9200");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
interface MaybeSecureClientConfigurationBuilder extends TerminalClientConfigurationBuilder {
|
||||
|
||||
/**
|
||||
* Connect via {@literal https} <br />
|
||||
* <strong>NOTE</strong> You need to leave out the protocol in
|
||||
* {@link ClientConfigurationBuilderWithRequiredEndpoint#connectedTo(String)}.
|
||||
*
|
||||
* @return the {@link TerminalClientConfigurationBuilder}.
|
||||
*/
|
||||
TerminalClientConfigurationBuilder usingSsl();
|
||||
|
||||
/**
|
||||
* Connect via {@literal https} using the given {@link SSLContext}.<br />
|
||||
* <strong>NOTE</strong> You need to leave out the protocol in
|
||||
* {@link ClientConfigurationBuilderWithRequiredEndpoint#connectedTo(String)}.
|
||||
*
|
||||
* @return the {@link TerminalClientConfigurationBuilder}.
|
||||
*/
|
||||
TerminalClientConfigurationBuilder usingSsl(SSLContext sslContext);
|
||||
|
||||
/**
|
||||
* Connect via {@literal https} using the givens {@link SSLContext} and HostnameVerifier {@link HostnameVerifier}
|
||||
* .<br />
|
||||
* <strong>NOTE</strong> You need to leave out the protocol in
|
||||
* {@link ClientConfigurationBuilderWithRequiredEndpoint#connectedTo(String)}.
|
||||
*
|
||||
* @return the {@link TerminalClientConfigurationBuilder}.
|
||||
*/
|
||||
TerminalClientConfigurationBuilder usingSsl(SSLContext sslContext, HostnameVerifier hostnameVerifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
interface TerminalClientConfigurationBuilder {
|
||||
|
||||
/**
|
||||
* @param defaultHeaders must not be {@literal null}.
|
||||
* @return the {@link TerminalClientConfigurationBuilder}
|
||||
*/
|
||||
TerminalClientConfigurationBuilder withDefaultHeaders(HttpHeaders defaultHeaders);
|
||||
|
||||
/**
|
||||
* Configure the {@literal milliseconds} for the connect timeout.
|
||||
*
|
||||
* @param millis the timeout to use.
|
||||
* @return the {@link TerminalClientConfigurationBuilder}
|
||||
* @see #withConnectTimeout(Duration)
|
||||
*/
|
||||
default TerminalClientConfigurationBuilder withConnectTimeout(long millis) {
|
||||
return withConnectTimeout(Duration.ofMillis(millis));
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure a {@link java.time.Duration} connect timeout.
|
||||
*
|
||||
* @param timeout the timeout to use. Must not be {@literal null}.
|
||||
* @return the {@link TerminalClientConfigurationBuilder}
|
||||
* @see java.net.Socket#connect(SocketAddress, int)
|
||||
* @see io.netty.channel.ChannelOption#CONNECT_TIMEOUT_MILLIS
|
||||
*/
|
||||
TerminalClientConfigurationBuilder withConnectTimeout(Duration timeout);
|
||||
|
||||
/**
|
||||
* Configure the {@literal milliseconds} for the socket timeout.
|
||||
*
|
||||
* @param millis the timeout to use.
|
||||
* @return the {@link TerminalClientConfigurationBuilder}
|
||||
* @see #withSocketTimeout(Duration)
|
||||
*/
|
||||
default TerminalClientConfigurationBuilder withSocketTimeout(long millis) {
|
||||
return withSocketTimeout(Duration.ofMillis(millis));
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure a {@link java.time.Duration socket timeout} which is typically applied as SO-timeout/read timeout.
|
||||
*
|
||||
* @param timeout the timeout to use. Must not be {@literal null}.
|
||||
* @return the {@link TerminalClientConfigurationBuilder}
|
||||
* @see java.net.Socket#setSoTimeout(int)
|
||||
* @see io.netty.handler.timeout.ReadTimeoutHandler
|
||||
* @see io.netty.handler.timeout.WriteTimeoutHandler
|
||||
*/
|
||||
TerminalClientConfigurationBuilder withSocketTimeout(Duration timeout);
|
||||
|
||||
/**
|
||||
* Configure the username and password to be sent as a Basic Authentication header
|
||||
*
|
||||
* @param username the username. Must not be {@literal null}.
|
||||
* @param password the password. Must not be {@literal null}.
|
||||
* @return the {@link TerminalClientConfigurationBuilder}
|
||||
*/
|
||||
TerminalClientConfigurationBuilder withBasicAuth(String username, String password);
|
||||
|
||||
/**
|
||||
* Configure the path prefix that will be prepended to any HTTP(s) requests
|
||||
*
|
||||
* @param pathPrefix the pathPrefix.
|
||||
* @return the {@link TerminalClientConfigurationBuilder}
|
||||
* @since 3.2.4
|
||||
*/
|
||||
TerminalClientConfigurationBuilder withPathPrefix(String pathPrefix);
|
||||
|
||||
/**
|
||||
* @param proxy a proxy formatted as String {@literal host:port}.
|
||||
* @return the {@link TerminalClientConfigurationBuilder}.
|
||||
*/
|
||||
TerminalClientConfigurationBuilder withProxy(String proxy);
|
||||
|
||||
/**
|
||||
* set customization hook in case of a reactive configuration
|
||||
*
|
||||
* @param webClientConfigurer function to configure the WebClient
|
||||
* @return the {@link TerminalClientConfigurationBuilder}.
|
||||
*/
|
||||
TerminalClientConfigurationBuilder withWebClientConfigurer(Function<WebClient, WebClient> webClientConfigurer);
|
||||
|
||||
/**
|
||||
* Build the {@link ClientConfiguration} object.
|
||||
*
|
||||
* @return the {@link ClientConfiguration} object.
|
||||
*/
|
||||
ClientConfiguration build();
|
||||
}
|
||||
}
|
||||
-229
@@ -1,229 +0,0 @@
|
||||
/*
|
||||
* Copyright 2018-2020 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.elasticsearch.client;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
import javax.net.ssl.SSLContext;
|
||||
|
||||
import org.springframework.data.elasticsearch.client.ClientConfiguration.ClientConfigurationBuilderWithRequiredEndpoint;
|
||||
import org.springframework.data.elasticsearch.client.ClientConfiguration.MaybeSecureClientConfigurationBuilder;
|
||||
import org.springframework.data.elasticsearch.client.ClientConfiguration.TerminalClientConfigurationBuilder;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
/**
|
||||
* Default builder implementation for {@link ClientConfiguration}.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
* @author Peter-Josef Meisch
|
||||
* @author Huw Ayling-Miller
|
||||
* @author Henrique Amaral
|
||||
* @since 3.2
|
||||
*/
|
||||
class ClientConfigurationBuilder
|
||||
implements ClientConfigurationBuilderWithRequiredEndpoint, MaybeSecureClientConfigurationBuilder {
|
||||
|
||||
private List<InetSocketAddress> hosts = new ArrayList<>();
|
||||
private HttpHeaders headers = HttpHeaders.EMPTY;
|
||||
private boolean useSsl;
|
||||
private @Nullable SSLContext sslContext;
|
||||
private @Nullable HostnameVerifier hostnameVerifier;
|
||||
private Duration connectTimeout = Duration.ofSeconds(10);
|
||||
private Duration soTimeout = Duration.ofSeconds(5);
|
||||
private String username;
|
||||
private String password;
|
||||
private String pathPrefix;
|
||||
private String proxy;
|
||||
private Function<WebClient, WebClient> webClientConfigurer;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.client.ClientConfiguration.ClientConfigurationBuilderWithRequiredEndpoint#connectedTo(java.lang.String[])
|
||||
*/
|
||||
@Override
|
||||
public MaybeSecureClientConfigurationBuilder connectedTo(String... hostAndPorts) {
|
||||
|
||||
Assert.notEmpty(hostAndPorts, "At least one host is required");
|
||||
|
||||
this.hosts.addAll(Arrays.stream(hostAndPorts).map(ClientConfigurationBuilder::parse).collect(Collectors.toList()));
|
||||
return this;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.client.ClientConfiguration.ClientConfigurationBuilderWithRequiredEndpoint#connectedTo(java.net.InetSocketAddress[])
|
||||
*/
|
||||
@Override
|
||||
public MaybeSecureClientConfigurationBuilder connectedTo(InetSocketAddress... endpoints) {
|
||||
|
||||
Assert.notEmpty(endpoints, "At least one endpoint is required");
|
||||
|
||||
this.hosts.addAll(Arrays.asList(endpoints));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MaybeSecureClientConfigurationBuilder withProxy(String proxy) {
|
||||
Assert.hasLength(proxy, "proxy must not be null or empty");
|
||||
this.proxy = proxy;
|
||||
return this;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.client.ClientConfiguration.MaybeSecureClientConfigurationBuilder#usingSsl()
|
||||
*/
|
||||
@Override
|
||||
public TerminalClientConfigurationBuilder usingSsl() {
|
||||
|
||||
this.useSsl = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.client.ClientConfiguration.MaybeSecureClientConfigurationBuilder#usingSsl(javax.net.ssl.SSLContext)
|
||||
*/
|
||||
@Override
|
||||
public TerminalClientConfigurationBuilder usingSsl(SSLContext sslContext) {
|
||||
|
||||
Assert.notNull(sslContext, "SSL Context must not be null");
|
||||
|
||||
this.useSsl = true;
|
||||
this.sslContext = sslContext;
|
||||
return this;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.client.ClientConfiguration.MaybeSecureClientConfigurationBuilder#usingSsl(javax.net.ssl.SSLContext, javax.net.ssl.HostnameVerifier)
|
||||
*/
|
||||
@Override
|
||||
public TerminalClientConfigurationBuilder usingSsl(SSLContext sslContext, HostnameVerifier hostnameVerifier) {
|
||||
|
||||
Assert.notNull(sslContext, "SSL Context must not be null");
|
||||
Assert.notNull(hostnameVerifier, "Host Name Verifier must not be null");
|
||||
|
||||
this.useSsl = true;
|
||||
this.sslContext = sslContext;
|
||||
this.hostnameVerifier = hostnameVerifier;
|
||||
return this;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.client.ClientConfiguration.TerminalClientConfigurationBuilder#withDefaultHeaders(org.springframework.http.HttpHeaders)
|
||||
*/
|
||||
@Override
|
||||
public TerminalClientConfigurationBuilder withDefaultHeaders(HttpHeaders defaultHeaders) {
|
||||
|
||||
Assert.notNull(defaultHeaders, "Default HTTP headers must not be null");
|
||||
|
||||
this.headers = new HttpHeaders();
|
||||
this.headers.addAll(defaultHeaders);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.client.ClientConfiguration.TerminalClientConfigurationBuilder#withConnectTimeout(java.time.Duration)
|
||||
*/
|
||||
@Override
|
||||
public TerminalClientConfigurationBuilder withConnectTimeout(Duration timeout) {
|
||||
|
||||
Assert.notNull(timeout, "I/O timeout must not be null!");
|
||||
|
||||
this.connectTimeout = timeout;
|
||||
return this;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.client.ClientConfiguration.TerminalClientConfigurationBuilder#withTimeout(java.time.Duration)
|
||||
*/
|
||||
@Override
|
||||
public TerminalClientConfigurationBuilder withSocketTimeout(Duration timeout) {
|
||||
|
||||
Assert.notNull(timeout, "Socket timeout must not be null!");
|
||||
|
||||
this.soTimeout = timeout;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TerminalClientConfigurationBuilder withBasicAuth(String username, String password) {
|
||||
|
||||
Assert.notNull(username, "username must not be null");
|
||||
Assert.notNull(password, "password must not be null");
|
||||
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TerminalClientConfigurationBuilder withPathPrefix(String pathPrefix) {
|
||||
this.pathPrefix = pathPrefix;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TerminalClientConfigurationBuilder withWebClientConfigurer(Function<WebClient, WebClient> webClientConfigurer) {
|
||||
|
||||
Assert.notNull(webClientConfigurer, "webClientConfigurer must not be null");
|
||||
|
||||
this.webClientConfigurer = webClientConfigurer;
|
||||
return this;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.client.ClientConfiguration.ClientConfigurationBuilderWithOptionalDefaultHeaders#build()
|
||||
*/
|
||||
@Override
|
||||
public ClientConfiguration build() {
|
||||
|
||||
if (username != null && password != null) {
|
||||
if (HttpHeaders.EMPTY.equals(headers)) {
|
||||
headers = new HttpHeaders();
|
||||
}
|
||||
headers.setBasicAuth(username, password);
|
||||
}
|
||||
|
||||
return new DefaultClientConfiguration(hosts, headers, useSsl, sslContext, soTimeout, connectTimeout, pathPrefix,
|
||||
hostnameVerifier, proxy, webClientConfigurer);
|
||||
}
|
||||
|
||||
private static InetSocketAddress parse(String hostAndPort) {
|
||||
return InetSocketAddressParser.parse(hostAndPort, ElasticsearchHost.DEFAULT_PORT);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,127 +0,0 @@
|
||||
/*
|
||||
* Copyright 2018-2020 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.elasticsearch.client;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
/**
|
||||
* Logging Utility to log client requests and responses. Logs client requests and responses to Elasticsearch to a
|
||||
* dedicated logger: {@code org.springframework.data.elasticsearch.client.WIRE} on {@link org.slf4j.event.Level#TRACE}
|
||||
* level.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Christoph Strobl
|
||||
* @since 3.2
|
||||
*/
|
||||
public abstract class ClientLogger {
|
||||
|
||||
private static final String lineSeparator = System.getProperty("line.separator");
|
||||
private static final Logger WIRE_LOGGER = LoggerFactory
|
||||
.getLogger("org.springframework.data.elasticsearch.client.WIRE");
|
||||
|
||||
private ClientLogger() {}
|
||||
|
||||
/**
|
||||
* Returns {@literal true} if the logger is enabled.
|
||||
*
|
||||
* @return {@literal true} if the logger is enabled.
|
||||
*/
|
||||
public static boolean isEnabled() {
|
||||
return WIRE_LOGGER.isTraceEnabled();
|
||||
}
|
||||
|
||||
/**
|
||||
* Log an outgoing HTTP request.
|
||||
*
|
||||
* @param logId the correlation Id, see {@link #newLogId()}.
|
||||
* @param method HTTP method
|
||||
* @param endpoint URI
|
||||
* @param parameters optional parameters.
|
||||
*/
|
||||
public static void logRequest(String logId, String method, String endpoint, Object parameters) {
|
||||
|
||||
if (isEnabled()) {
|
||||
|
||||
WIRE_LOGGER.trace("[{}] Sending request {} {} with parameters: {}", logId, method.toUpperCase(), endpoint,
|
||||
parameters);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Log an outgoing HTTP request with a request body.
|
||||
*
|
||||
* @param logId the correlation Id, see {@link #newLogId()}.
|
||||
* @param method HTTP method
|
||||
* @param endpoint URI
|
||||
* @param parameters optional parameters.
|
||||
* @param body body content supplier.
|
||||
*/
|
||||
public static void logRequest(String logId, String method, String endpoint, Object parameters,
|
||||
Supplier<Object> body) {
|
||||
|
||||
if (isEnabled()) {
|
||||
|
||||
WIRE_LOGGER.trace("[{}] Sending request {} {} with parameters: {}{}Request body: {}", logId, method.toUpperCase(),
|
||||
endpoint, parameters, lineSeparator, body.get());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Log a raw HTTP response without logging the body.
|
||||
*
|
||||
* @param logId the correlation Id, see {@link #newLogId()}.
|
||||
* @param statusCode the HTTP status code.
|
||||
*/
|
||||
public static void logRawResponse(String logId, HttpStatus statusCode) {
|
||||
|
||||
if (isEnabled()) {
|
||||
WIRE_LOGGER.trace("[{}] Received raw response: {}", logId, statusCode);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Log a raw HTTP response along with the body.
|
||||
*
|
||||
* @param logId the correlation Id, see {@link #newLogId()}.
|
||||
* @param statusCode the HTTP status code.
|
||||
* @param body body content.
|
||||
*/
|
||||
public static void logResponse(String logId, HttpStatus statusCode, String body) {
|
||||
|
||||
if (isEnabled()) {
|
||||
WIRE_LOGGER.trace("[{}] Received response: {}{}Response body: {}", logId, statusCode, lineSeparator, body);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new, unique correlation Id to improve tracing across log events.
|
||||
*
|
||||
* @return a new, unique correlation Id.
|
||||
*/
|
||||
public static String newLogId() {
|
||||
|
||||
if (!isEnabled()) {
|
||||
return "-";
|
||||
}
|
||||
|
||||
return ObjectUtils.getIdentityHexString(new Object());
|
||||
}
|
||||
}
|
||||
-120
@@ -1,120 +0,0 @@
|
||||
/*
|
||||
* Copyright 2018-2020 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.elasticsearch.client;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
import javax.net.ssl.SSLContext;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
/**
|
||||
* Default {@link ClientConfiguration} implementation.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Christoph Strobl
|
||||
* @author Huw Ayling-Miller
|
||||
* @author Peter-Josef Meisch
|
||||
* @since 3.2
|
||||
*/
|
||||
class DefaultClientConfiguration implements ClientConfiguration {
|
||||
|
||||
private final List<InetSocketAddress> hosts;
|
||||
private final HttpHeaders headers;
|
||||
private final boolean useSsl;
|
||||
private final @Nullable SSLContext sslContext;
|
||||
private final Duration soTimeout;
|
||||
private final Duration connectTimeout;
|
||||
private final String pathPrefix;
|
||||
private final @Nullable HostnameVerifier hostnameVerifier;
|
||||
private final String proxy;
|
||||
private final Function<WebClient, WebClient> webClientConfigurer;
|
||||
|
||||
DefaultClientConfiguration(List<InetSocketAddress> hosts, HttpHeaders headers, boolean useSsl,
|
||||
@Nullable SSLContext sslContext, Duration soTimeout, Duration connectTimeout, @Nullable String pathPrefix,
|
||||
@Nullable HostnameVerifier hostnameVerifier, String proxy, Function<WebClient, WebClient> webClientConfigurer) {
|
||||
|
||||
this.hosts = Collections.unmodifiableList(new ArrayList<>(hosts));
|
||||
this.headers = new HttpHeaders(headers);
|
||||
this.useSsl = useSsl;
|
||||
this.sslContext = sslContext;
|
||||
this.soTimeout = soTimeout;
|
||||
this.connectTimeout = connectTimeout;
|
||||
this.pathPrefix = pathPrefix;
|
||||
this.hostnameVerifier = hostnameVerifier;
|
||||
this.proxy = proxy;
|
||||
this.webClientConfigurer = webClientConfigurer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<InetSocketAddress> getEndpoints() {
|
||||
return this.hosts;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpHeaders getDefaultHeaders() {
|
||||
return this.headers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean useSsl() {
|
||||
return this.useSsl;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<SSLContext> getSslContext() {
|
||||
return Optional.ofNullable(this.sslContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<HostnameVerifier> getHostNameVerifier() {
|
||||
return Optional.ofNullable(this.hostnameVerifier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Duration getConnectTimeout() {
|
||||
return this.connectTimeout;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Duration getSocketTimeout() {
|
||||
return this.soTimeout;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPathPrefix() {
|
||||
return this.pathPrefix;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<String> getProxy() {
|
||||
return Optional.ofNullable(proxy);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Function<WebClient, WebClient> getWebClientConfigurer() {
|
||||
return webClientConfigurer != null ? webClientConfigurer : Function.identity();
|
||||
}
|
||||
}
|
||||
@@ -1,113 +0,0 @@
|
||||
/*
|
||||
* Copyright 2018-2020 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.elasticsearch.client;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.time.Instant;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Value Object containing information about Elasticsearch cluster nodes.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 3.2
|
||||
*/
|
||||
public class ElasticsearchHost {
|
||||
|
||||
/**
|
||||
* Default HTTP port for Elasticsearch servers.
|
||||
*/
|
||||
public static final int DEFAULT_PORT = 9200;
|
||||
|
||||
private final InetSocketAddress endpoint;
|
||||
private final State state;
|
||||
private final Instant timestamp;
|
||||
|
||||
public ElasticsearchHost(InetSocketAddress endpoint, State state) {
|
||||
|
||||
Assert.notNull(endpoint, "Host must not be null");
|
||||
Assert.notNull(state, "State must not be null");
|
||||
|
||||
this.endpoint = endpoint;
|
||||
this.state = state;
|
||||
this.timestamp = Instant.now();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param host must not be {@literal null}.
|
||||
* @return new instance of {@link ElasticsearchHost}.
|
||||
*/
|
||||
public static ElasticsearchHost online(InetSocketAddress host) {
|
||||
return new ElasticsearchHost(host, State.ONLINE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param host must not be {@literal null}.
|
||||
* @return new instance of {@link ElasticsearchHost}.
|
||||
*/
|
||||
public static ElasticsearchHost offline(InetSocketAddress host) {
|
||||
return new ElasticsearchHost(host, State.OFFLINE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a {@literal hostAndPort} string into a {@link InetSocketAddress}.
|
||||
*
|
||||
* @param hostAndPort the string containing host and port or IP address and port in the format {@code host:port}.
|
||||
* @return the parsed {@link InetSocketAddress}.
|
||||
*/
|
||||
public static InetSocketAddress parse(String hostAndPort) {
|
||||
return InetSocketAddressParser.parse(hostAndPort, DEFAULT_PORT);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@literal true} if the last known {@link State} was {@link State#ONLINE}
|
||||
*/
|
||||
public boolean isOnline() {
|
||||
return State.ONLINE.equals(state);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
public InetSocketAddress getEndpoint() {
|
||||
return endpoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the last known {@link State}.
|
||||
*/
|
||||
public State getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the {@link Instant} the information was captured.
|
||||
*/
|
||||
public Instant getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ElasticsearchHost(" + endpoint + ", " + state.name() + ")";
|
||||
}
|
||||
|
||||
public enum State {
|
||||
ONLINE, OFFLINE, UNKNOWN
|
||||
}
|
||||
}
|
||||
-117
@@ -1,117 +0,0 @@
|
||||
/*
|
||||
* Copyright 2018-2020 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.elasticsearch.client;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Utility to parse endpoints in {@code host:port} format into {@link java.net.InetSocketAddress}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 3.2
|
||||
*/
|
||||
class InetSocketAddressParser {
|
||||
|
||||
/**
|
||||
* Parse a host and port string into a {@link InetSocketAddress}.
|
||||
*
|
||||
* @param hostPortString Hostname/IP address and port formatted as {@code host:port} or {@code host}.
|
||||
* @param defaultPort default port to apply if {@code hostPostString} does not contain a port.
|
||||
* @return a {@link InetSocketAddress} that is unresolved to avoid DNS lookups.
|
||||
* @see InetSocketAddress#createUnresolved(String, int)
|
||||
*/
|
||||
static InetSocketAddress parse(String hostPortString, int defaultPort) {
|
||||
|
||||
Assert.notNull(hostPortString, "HostPortString must not be null");
|
||||
String host;
|
||||
String portString = null;
|
||||
|
||||
if (hostPortString.startsWith("[")) {
|
||||
String[] hostAndPort = getHostAndPortFromBracketedHost(hostPortString);
|
||||
host = hostAndPort[0];
|
||||
portString = hostAndPort[1];
|
||||
} else {
|
||||
int colonPos = hostPortString.indexOf(':');
|
||||
if (colonPos >= 0 && hostPortString.indexOf(':', colonPos + 1) == -1) {
|
||||
// Exactly 1 colon. Split into host:port.
|
||||
host = hostPortString.substring(0, colonPos);
|
||||
portString = hostPortString.substring(colonPos + 1);
|
||||
} else {
|
||||
// 0 or 2+ colons. Bare hostname or IPv6 literal.
|
||||
host = hostPortString;
|
||||
}
|
||||
}
|
||||
|
||||
int port = defaultPort;
|
||||
if (StringUtils.hasText(portString)) {
|
||||
// Try to parse the whole port string as a number.
|
||||
Assert.isTrue(!portString.startsWith("+"), String.format("Cannot parse port number: %s", hostPortString));
|
||||
try {
|
||||
port = Integer.parseInt(portString);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new IllegalArgumentException(String.format("Cannot parse port number: %s", hostPortString));
|
||||
}
|
||||
|
||||
Assert.isTrue(isValidPort(port), String.format("Port number out of range: %s", hostPortString));
|
||||
}
|
||||
|
||||
return InetSocketAddress.createUnresolved(host, port);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a bracketed host-port string, throwing IllegalArgumentException if parsing fails.
|
||||
*
|
||||
* @param hostPortString the full bracketed host-port specification. Post might not be specified.
|
||||
* @return an array with 2 strings: host and port, in that order.
|
||||
* @throws IllegalArgumentException if parsing the bracketed host-port string fails.
|
||||
*/
|
||||
private static String[] getHostAndPortFromBracketedHost(String hostPortString) {
|
||||
|
||||
Assert.isTrue(hostPortString.charAt(0) == '[',
|
||||
String.format("Bracketed host-port string must start with a bracket: %s", hostPortString));
|
||||
|
||||
int colonIndex = hostPortString.indexOf(':');
|
||||
int closeBracketIndex = hostPortString.lastIndexOf(']');
|
||||
|
||||
Assert.isTrue(colonIndex > -1 && closeBracketIndex > colonIndex,
|
||||
String.format("Invalid bracketed host/port: %s", hostPortString));
|
||||
|
||||
String host = hostPortString.substring(1, closeBracketIndex);
|
||||
if (closeBracketIndex + 1 == hostPortString.length()) {
|
||||
return new String[] { host, "" };
|
||||
} else {
|
||||
|
||||
Assert.isTrue(hostPortString.charAt(closeBracketIndex + 1) == ':',
|
||||
"Only a colon may follow a close bracket: " + hostPortString);
|
||||
for (int i = closeBracketIndex + 2; i < hostPortString.length(); ++i) {
|
||||
Assert.isTrue(Character.isDigit(hostPortString.charAt(i)),
|
||||
String.format("Port must be numeric: %s", hostPortString));
|
||||
}
|
||||
return new String[] { host, hostPortString.substring(closeBracketIndex + 2) };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param port the port number
|
||||
* @return {@literal true} for valid port numbers.
|
||||
*/
|
||||
private static boolean isValidPort(int port) {
|
||||
return port >= 0 && port <= 65535;
|
||||
}
|
||||
}
|
||||
-45
@@ -1,45 +0,0 @@
|
||||
/*
|
||||
* Copyright 2018-2020 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.elasticsearch.client;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* {@link RuntimeException} to be emitted / thrown when the cluster is down (aka none of the known nodes is reachable).
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 3.2
|
||||
*/
|
||||
public class NoReachableHostException extends RuntimeException {
|
||||
|
||||
public NoReachableHostException(Set<ElasticsearchHost> hosts) {
|
||||
super(createMessage(hosts));
|
||||
}
|
||||
|
||||
public NoReachableHostException(Set<ElasticsearchHost> hosts, Throwable cause) {
|
||||
super(createMessage(hosts), cause);
|
||||
}
|
||||
|
||||
private static String createMessage(Set<ElasticsearchHost> hosts) {
|
||||
|
||||
if (hosts.size() == 1) {
|
||||
return String.format("Host '%s' not reachable. Cluster state is offline.", hosts.iterator().next().getEndpoint());
|
||||
}
|
||||
|
||||
return String.format("No active host found in cluster. (%s) of (%s) nodes offline.", hosts.size(), hosts.size());
|
||||
}
|
||||
}
|
||||
+16
-22
@@ -15,15 +15,11 @@
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.client;
|
||||
|
||||
import static java.util.Arrays.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Collection;
|
||||
|
||||
import org.elasticsearch.client.Client;
|
||||
import org.elasticsearch.client.node.NodeClient;
|
||||
import org.elasticsearch.common.logging.LogConfigurator;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.node.InternalSettingsPreparer;
|
||||
import org.elasticsearch.node.Node;
|
||||
@@ -36,6 +32,8 @@ import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import static java.util.Arrays.*;
|
||||
|
||||
/**
|
||||
* NodeClientFactoryBean
|
||||
*
|
||||
@@ -43,6 +41,7 @@ import org.springframework.util.StringUtils;
|
||||
* @author Mohsin Husen
|
||||
* @author Ilkang Na
|
||||
*/
|
||||
|
||||
public class NodeClientFactoryBean implements FactoryBean<Client>, InitializingBean, DisposableBean {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(NodeClientFactoryBean.class);
|
||||
@@ -55,22 +54,13 @@ public class NodeClientFactoryBean implements FactoryBean<Client>, InitializingB
|
||||
private String pathConfiguration;
|
||||
|
||||
public static class TestNode extends Node {
|
||||
|
||||
public TestNode(Settings preparedSettings, Collection<Class<? extends Plugin>> classpathPlugins) {
|
||||
|
||||
super(InternalSettingsPreparer.prepareEnvironment(preparedSettings, null), classpathPlugins, false);
|
||||
}
|
||||
|
||||
protected void registerDerivedNodeNameWithLogger(String nodeName) {
|
||||
try {
|
||||
LogConfigurator.setNodeName(nodeName);
|
||||
} catch (Exception e) {
|
||||
// nagh - just forget about it
|
||||
}
|
||||
super(InternalSettingsPreparer.prepareEnvironment(preparedSettings, null), classpathPlugins);
|
||||
}
|
||||
}
|
||||
|
||||
NodeClientFactoryBean() {}
|
||||
NodeClientFactoryBean() {
|
||||
}
|
||||
|
||||
public NodeClientFactoryBean(boolean local) {
|
||||
this.local = local;
|
||||
@@ -94,18 +84,22 @@ public class NodeClientFactoryBean implements FactoryBean<Client>, InitializingB
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
|
||||
nodeClient = (NodeClient) new TestNode(Settings.builder().put(loadConfig()).put("transport.type", "netty4")
|
||||
.put("http.type", "netty4").put("path.home", this.pathHome).put("path.data", this.pathData)
|
||||
.put("cluster.name", this.clusterName).put("node.max_local_storage_nodes", 100).build(),
|
||||
asList(Netty4Plugin.class)).start().client();
|
||||
nodeClient = (NodeClient) new TestNode(
|
||||
Settings.builder().put(loadConfig())
|
||||
.put("transport.type", "netty4")
|
||||
.put("http.type", "netty4")
|
||||
.put("path.home", this.pathHome)
|
||||
.put("path.data", this.pathData)
|
||||
.put("cluster.name", this.clusterName)
|
||||
.put("node.max_local_storage_nodes", 100)
|
||||
.build(), asList(Netty4Plugin.class)).start().client();
|
||||
}
|
||||
|
||||
private Settings loadConfig() throws IOException {
|
||||
if (!StringUtils.isEmpty(pathConfiguration)) {
|
||||
InputStream stream = getClass().getClassLoader().getResourceAsStream(pathConfiguration);
|
||||
if (stream != null) {
|
||||
return Settings.builder().loadFromStream(pathConfiguration,
|
||||
getClass().getClassLoader().getResourceAsStream(pathConfiguration), false).build();
|
||||
return Settings.builder().loadFromStream(pathConfiguration, getClass().getClassLoader().getResourceAsStream(pathConfiguration), false).build();
|
||||
}
|
||||
logger.error(String.format("Unable to read node configuration from file [%s]", pathConfiguration));
|
||||
}
|
||||
|
||||
-93
@@ -1,93 +0,0 @@
|
||||
/*
|
||||
* Copyright 2018-2020 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.elasticsearch.client;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import org.apache.http.HttpHost;
|
||||
import org.elasticsearch.client.RestClient;
|
||||
import org.elasticsearch.client.RestHighLevelClient;
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* RestClientFactoryBean
|
||||
*
|
||||
* @author Don Wellington
|
||||
*/
|
||||
@Slf4j
|
||||
public class RestClientFactoryBean implements FactoryBean<RestHighLevelClient>, InitializingBean, DisposableBean {
|
||||
|
||||
private RestHighLevelClient client;
|
||||
private String hosts = "http://localhost:9200";
|
||||
static final String COMMA = ",";
|
||||
|
||||
@Override
|
||||
public void destroy() throws Exception {
|
||||
try {
|
||||
log.info("Closing elasticSearch client");
|
||||
if (client != null) {
|
||||
client.close();
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
log.error("Error closing ElasticSearch client: ", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
buildClient();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RestHighLevelClient getObject() throws Exception {
|
||||
return client;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getObjectType() {
|
||||
return RestHighLevelClient.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSingleton() {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected void buildClient() throws Exception {
|
||||
|
||||
Assert.hasText(hosts, "[Assertion Failed] At least one host must be set.");
|
||||
ArrayList<HttpHost> httpHosts = new ArrayList<HttpHost>();
|
||||
for (String host : hosts.split(COMMA)) {
|
||||
URL hostUrl = new URL(host);
|
||||
httpHosts.add(new HttpHost(hostUrl.getHost(), hostUrl.getPort(), hostUrl.getProtocol()));
|
||||
}
|
||||
client = new RestHighLevelClient(RestClient.builder(httpHosts.toArray(new HttpHost[httpHosts.size()])));
|
||||
}
|
||||
|
||||
public void setHosts(String hosts) {
|
||||
this.hosts = hosts;
|
||||
}
|
||||
|
||||
public String getHosts() {
|
||||
return this.hosts;
|
||||
}
|
||||
}
|
||||
@@ -1,215 +0,0 @@
|
||||
/*
|
||||
* Copyright 2018-2020 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.elasticsearch.client;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
import javax.net.ssl.SSLContext;
|
||||
|
||||
import org.apache.http.Header;
|
||||
import org.apache.http.HttpEntity;
|
||||
import org.apache.http.HttpEntityEnclosingRequest;
|
||||
import org.apache.http.HttpHost;
|
||||
import org.apache.http.HttpRequest;
|
||||
import org.apache.http.HttpRequestInterceptor;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.HttpResponseInterceptor;
|
||||
import org.apache.http.client.config.RequestConfig;
|
||||
import org.apache.http.client.config.RequestConfig.Builder;
|
||||
import org.apache.http.entity.ByteArrayEntity;
|
||||
import org.apache.http.message.BasicHeader;
|
||||
import org.apache.http.protocol.HttpContext;
|
||||
import org.elasticsearch.client.RestClient;
|
||||
import org.elasticsearch.client.RestClientBuilder;
|
||||
import org.elasticsearch.client.RestHighLevelClient;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Utility class for common access to Elasticsearch clients. {@link RestClients} consolidates set up routines for the
|
||||
* various drivers into a single place.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
* @author Huw Ayling-Miller
|
||||
* @author Henrique Amaral
|
||||
* @author Peter-Josef Meisch
|
||||
* @since 3.2
|
||||
*/
|
||||
public final class RestClients {
|
||||
|
||||
/**
|
||||
* Name of whose value can be used to correlate log messages for this request.
|
||||
*/
|
||||
private static final String LOG_ID_ATTRIBUTE = RestClients.class.getName() + ".LOG_ID";
|
||||
|
||||
private RestClients() {}
|
||||
|
||||
/**
|
||||
* Start here to create a new client tailored to your needs.
|
||||
*
|
||||
* @return new instance of {@link ElasticsearchRestClient}.
|
||||
*/
|
||||
public static ElasticsearchRestClient create(ClientConfiguration clientConfiguration) {
|
||||
|
||||
Assert.notNull(clientConfiguration, "ClientConfiguration must not be null!");
|
||||
|
||||
HttpHost[] httpHosts = formattedHosts(clientConfiguration.getEndpoints(), clientConfiguration.useSsl()).stream()
|
||||
.map(HttpHost::create).toArray(HttpHost[]::new);
|
||||
RestClientBuilder builder = RestClient.builder(httpHosts);
|
||||
|
||||
if (clientConfiguration.getPathPrefix() != null) {
|
||||
builder.setPathPrefix(clientConfiguration.getPathPrefix());
|
||||
}
|
||||
|
||||
HttpHeaders headers = clientConfiguration.getDefaultHeaders();
|
||||
|
||||
if (!headers.isEmpty()) {
|
||||
|
||||
Header[] httpHeaders = headers.toSingleValueMap().entrySet().stream()
|
||||
.map(it -> new BasicHeader(it.getKey(), it.getValue())).toArray(Header[]::new);
|
||||
builder.setDefaultHeaders(httpHeaders);
|
||||
}
|
||||
|
||||
builder.setHttpClientConfigCallback(clientBuilder -> {
|
||||
|
||||
Optional<SSLContext> sslContext = clientConfiguration.getSslContext();
|
||||
Optional<HostnameVerifier> hostNameVerifier = clientConfiguration.getHostNameVerifier();
|
||||
sslContext.ifPresent(clientBuilder::setSSLContext);
|
||||
hostNameVerifier.ifPresent(clientBuilder::setSSLHostnameVerifier);
|
||||
|
||||
if (ClientLogger.isEnabled()) {
|
||||
|
||||
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
|
||||
|
||||
clientBuilder.addInterceptorLast((HttpRequestInterceptor) interceptor);
|
||||
clientBuilder.addInterceptorLast((HttpResponseInterceptor) interceptor);
|
||||
}
|
||||
|
||||
Duration connectTimeout = clientConfiguration.getConnectTimeout();
|
||||
Duration timeout = clientConfiguration.getSocketTimeout();
|
||||
|
||||
Builder requestConfigBuilder = RequestConfig.custom();
|
||||
|
||||
if (!connectTimeout.isNegative()) {
|
||||
|
||||
requestConfigBuilder.setConnectTimeout(Math.toIntExact(connectTimeout.toMillis()));
|
||||
requestConfigBuilder.setConnectionRequestTimeout(Math.toIntExact(connectTimeout.toMillis()));
|
||||
}
|
||||
|
||||
if (!timeout.isNegative()) {
|
||||
requestConfigBuilder.setSocketTimeout(Math.toIntExact(timeout.toMillis()));
|
||||
}
|
||||
|
||||
clientBuilder.setDefaultRequestConfig(requestConfigBuilder.build());
|
||||
|
||||
clientConfiguration.getProxy().map(HttpHost::create).ifPresent(clientBuilder::setProxy);
|
||||
|
||||
return clientBuilder;
|
||||
});
|
||||
|
||||
RestHighLevelClient client = new RestHighLevelClient(builder);
|
||||
return () -> client;
|
||||
}
|
||||
|
||||
private static List<String> formattedHosts(List<InetSocketAddress> hosts, boolean useSsl) {
|
||||
return hosts.stream().map(it -> (useSsl ? "https" : "http") + "://" + it.getHostString() + ':' + it.getPort())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface ElasticsearchRestClient extends Closeable {
|
||||
|
||||
/**
|
||||
* Apply the configuration to create a {@link RestHighLevelClient}.
|
||||
*
|
||||
* @return new instance of {@link RestHighLevelClient}.
|
||||
*/
|
||||
RestHighLevelClient rest();
|
||||
|
||||
/**
|
||||
* Apply the configuration to create a {@link RestClient}.
|
||||
*
|
||||
* @return new instance of {@link RestClient}.
|
||||
*/
|
||||
default RestClient lowLevelRest() {
|
||||
return rest().getLowLevelClient();
|
||||
}
|
||||
|
||||
@Override
|
||||
default void close() throws IOException {
|
||||
rest().close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Logging interceptors for Elasticsearch client logging.
|
||||
*
|
||||
* @see ClientLogger
|
||||
* @since 3.2
|
||||
*/
|
||||
private static class HttpLoggingInterceptor implements HttpResponseInterceptor, HttpRequestInterceptor {
|
||||
|
||||
@Override
|
||||
public void process(HttpRequest request, HttpContext context) throws IOException {
|
||||
|
||||
String logId = (String) context.getAttribute(RestClients.LOG_ID_ATTRIBUTE);
|
||||
|
||||
if (logId == null) {
|
||||
|
||||
logId = ClientLogger.newLogId();
|
||||
context.setAttribute(RestClients.LOG_ID_ATTRIBUTE, logId);
|
||||
}
|
||||
|
||||
if (request instanceof HttpEntityEnclosingRequest && ((HttpEntityEnclosingRequest) request).getEntity() != null) {
|
||||
|
||||
HttpEntityEnclosingRequest entityRequest = (HttpEntityEnclosingRequest) request;
|
||||
HttpEntity entity = ((HttpEntityEnclosingRequest) request).getEntity();
|
||||
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
||||
entity.writeTo(buffer);
|
||||
|
||||
if (!entity.isRepeatable()) {
|
||||
entityRequest.setEntity(new ByteArrayEntity(buffer.toByteArray()));
|
||||
}
|
||||
|
||||
ClientLogger.logRequest(logId, request.getRequestLine().getMethod(), request.getRequestLine().getUri(), "",
|
||||
() -> new String(buffer.toByteArray()));
|
||||
} else {
|
||||
ClientLogger.logRequest(logId, request.getRequestLine().getMethod(), request.getRequestLine().getUri(), "");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(HttpResponse response, HttpContext context) {
|
||||
|
||||
String logId = (String) context.getAttribute(RestClients.LOG_ID_ATTRIBUTE);
|
||||
|
||||
ClientLogger.logRawResponse(logId, HttpStatus.resolve(response.getStatusLine().getStatusCode()));
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -72,7 +72,7 @@ public class TransportClientFactoryBean implements FactoryBean<TransportClient>,
|
||||
|
||||
@Override
|
||||
public boolean isSingleton() {
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
-956
@@ -1,956 +0,0 @@
|
||||
/*
|
||||
* Copyright 2018-2020 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.elasticsearch.client.reactive;
|
||||
|
||||
import io.netty.channel.ChannelOption;
|
||||
import io.netty.handler.ssl.ApplicationProtocolConfig;
|
||||
import io.netty.handler.ssl.ClientAuth;
|
||||
import io.netty.handler.ssl.IdentityCipherSuiteFilter;
|
||||
import io.netty.handler.ssl.JdkSslContext;
|
||||
import io.netty.handler.timeout.ReadTimeoutHandler;
|
||||
import io.netty.handler.timeout.WriteTimeoutHandler;
|
||||
import reactor.core.publisher.EmitterProcessor;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.FluxSink;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.netty.http.client.HttpClient;
|
||||
import reactor.netty.tcp.TcpClient;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.ConnectException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Function;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
|
||||
import org.apache.http.util.EntityUtils;
|
||||
import org.elasticsearch.ElasticsearchException;
|
||||
import org.elasticsearch.ElasticsearchStatusException;
|
||||
import org.elasticsearch.action.ActionRequest;
|
||||
import org.elasticsearch.action.admin.indices.close.CloseIndexRequest;
|
||||
import org.elasticsearch.action.admin.indices.create.CreateIndexRequest;
|
||||
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
|
||||
import org.elasticsearch.action.admin.indices.flush.FlushRequest;
|
||||
import org.elasticsearch.action.admin.indices.flush.FlushResponse;
|
||||
import org.elasticsearch.action.admin.indices.get.GetIndexRequest;
|
||||
import org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest;
|
||||
import org.elasticsearch.action.admin.indices.open.OpenIndexRequest;
|
||||
import org.elasticsearch.action.admin.indices.refresh.RefreshRequest;
|
||||
import org.elasticsearch.action.admin.indices.refresh.RefreshResponse;
|
||||
import org.elasticsearch.action.bulk.BulkRequest;
|
||||
import org.elasticsearch.action.bulk.BulkResponse;
|
||||
import org.elasticsearch.action.delete.DeleteRequest;
|
||||
import org.elasticsearch.action.delete.DeleteResponse;
|
||||
import org.elasticsearch.action.get.GetRequest;
|
||||
import org.elasticsearch.action.get.GetResponse;
|
||||
import org.elasticsearch.action.get.MultiGetRequest;
|
||||
import org.elasticsearch.action.get.MultiGetResponse;
|
||||
import org.elasticsearch.action.index.IndexRequest;
|
||||
import org.elasticsearch.action.index.IndexResponse;
|
||||
import org.elasticsearch.action.main.MainRequest;
|
||||
import org.elasticsearch.action.main.MainResponse;
|
||||
import org.elasticsearch.action.search.ClearScrollRequest;
|
||||
import org.elasticsearch.action.search.ClearScrollResponse;
|
||||
import org.elasticsearch.action.search.SearchRequest;
|
||||
import org.elasticsearch.action.search.SearchResponse;
|
||||
import org.elasticsearch.action.search.SearchScrollRequest;
|
||||
import org.elasticsearch.action.support.master.AcknowledgedResponse;
|
||||
import org.elasticsearch.action.update.UpdateRequest;
|
||||
import org.elasticsearch.action.update.UpdateResponse;
|
||||
import org.elasticsearch.client.Request;
|
||||
import org.elasticsearch.client.core.CountRequest;
|
||||
import org.elasticsearch.client.core.CountResponse;
|
||||
import org.elasticsearch.common.unit.TimeValue;
|
||||
import org.elasticsearch.common.xcontent.DeprecationHandler;
|
||||
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.common.xcontent.XContentType;
|
||||
import org.elasticsearch.index.get.GetResult;
|
||||
import org.elasticsearch.index.reindex.BulkByScrollResponse;
|
||||
import org.elasticsearch.index.reindex.DeleteByQueryRequest;
|
||||
import org.elasticsearch.rest.BytesRestResponse;
|
||||
import org.elasticsearch.rest.RestStatus;
|
||||
import org.elasticsearch.search.Scroll;
|
||||
import org.elasticsearch.search.SearchHit;
|
||||
import org.elasticsearch.search.SearchHits;
|
||||
import org.reactivestreams.Publisher;
|
||||
import org.springframework.data.elasticsearch.client.ClientConfiguration;
|
||||
import org.springframework.data.elasticsearch.client.ClientLogger;
|
||||
import org.springframework.data.elasticsearch.client.ElasticsearchHost;
|
||||
import org.springframework.data.elasticsearch.client.NoReachableHostException;
|
||||
import org.springframework.data.elasticsearch.client.reactive.HostProvider.Verification;
|
||||
import org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient.Indices;
|
||||
import org.springframework.data.elasticsearch.client.util.RequestConverters;
|
||||
import org.springframework.data.util.Lazy;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.reactive.function.BodyExtractors;
|
||||
import org.springframework.web.reactive.function.client.ClientRequest;
|
||||
import org.springframework.web.reactive.function.client.ClientResponse;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
import org.springframework.web.reactive.function.client.WebClient.RequestBodySpec;
|
||||
|
||||
/**
|
||||
* A {@link WebClient} based {@link ReactiveElasticsearchClient} that connects to an Elasticsearch cluster using HTTP.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
* @author Peter-Josef Meisch
|
||||
* @author Huw Ayling-Miller
|
||||
* @author Henrique Amaral
|
||||
* @since 3.2
|
||||
* @see ClientConfiguration
|
||||
* @see ReactiveRestClients
|
||||
*/
|
||||
public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearchClient, Indices {
|
||||
|
||||
private final HostProvider hostProvider;
|
||||
|
||||
/**
|
||||
* Create a new {@link DefaultReactiveElasticsearchClient} using the given {@link HostProvider} to obtain server
|
||||
* connections.
|
||||
*
|
||||
* @param hostProvider must not be {@literal null}.
|
||||
*/
|
||||
public DefaultReactiveElasticsearchClient(HostProvider hostProvider) {
|
||||
|
||||
Assert.notNull(hostProvider, "HostProvider must not be null");
|
||||
|
||||
this.hostProvider = hostProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link DefaultReactiveElasticsearchClient} aware of the given nodes in the cluster. <br />
|
||||
* <strong>NOTE</strong> If the cluster requires authentication be sure to provide the according {@link HttpHeaders}
|
||||
* correctly.
|
||||
*
|
||||
* @param headers Use {@link HttpHeaders} to provide eg. authentication data. Must not be {@literal null}.
|
||||
* @param hosts must not be {@literal null} nor empty!
|
||||
* @return new instance of {@link DefaultReactiveElasticsearchClient}.
|
||||
*/
|
||||
public static ReactiveElasticsearchClient create(HttpHeaders headers, String... hosts) {
|
||||
|
||||
Assert.notNull(headers, "HttpHeaders must not be null");
|
||||
Assert.notEmpty(hosts, "Elasticsearch Cluster needs to consist of at least one host");
|
||||
|
||||
ClientConfiguration clientConfiguration = ClientConfiguration.builder().connectedTo(hosts)
|
||||
.withDefaultHeaders(headers).build();
|
||||
return create(clientConfiguration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link DefaultReactiveElasticsearchClient} given {@link ClientConfiguration}. <br />
|
||||
* <strong>NOTE</strong> If the cluster requires authentication be sure to provide the according {@link HttpHeaders}
|
||||
* correctly.
|
||||
*
|
||||
* @param clientConfiguration Client configuration. Must not be {@literal null}.
|
||||
* @return new instance of {@link DefaultReactiveElasticsearchClient}.
|
||||
*/
|
||||
public static ReactiveElasticsearchClient create(ClientConfiguration clientConfiguration) {
|
||||
|
||||
Assert.notNull(clientConfiguration, "ClientConfiguration must not be null");
|
||||
|
||||
WebClientProvider provider = getWebClientProvider(clientConfiguration);
|
||||
|
||||
HostProvider hostProvider = HostProvider.provider(provider,
|
||||
clientConfiguration.getEndpoints().toArray(new InetSocketAddress[0]));
|
||||
return new DefaultReactiveElasticsearchClient(hostProvider);
|
||||
}
|
||||
|
||||
private static WebClientProvider getWebClientProvider(ClientConfiguration clientConfiguration) {
|
||||
|
||||
Duration connectTimeout = clientConfiguration.getConnectTimeout();
|
||||
Duration soTimeout = clientConfiguration.getSocketTimeout();
|
||||
|
||||
TcpClient tcpClient = TcpClient.create();
|
||||
|
||||
if (!connectTimeout.isNegative()) {
|
||||
tcpClient = tcpClient.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, Math.toIntExact(connectTimeout.toMillis()));
|
||||
}
|
||||
|
||||
if (!soTimeout.isNegative()) {
|
||||
tcpClient = tcpClient.doOnConnected(connection -> connection //
|
||||
.addHandlerLast(new ReadTimeoutHandler(soTimeout.toMillis(), TimeUnit.MILLISECONDS))
|
||||
.addHandlerLast(new WriteTimeoutHandler(soTimeout.toMillis(), TimeUnit.MILLISECONDS)));
|
||||
}
|
||||
|
||||
String scheme = "http";
|
||||
HttpClient httpClient = HttpClient.from(tcpClient);
|
||||
|
||||
if (clientConfiguration.useSsl()) {
|
||||
|
||||
Optional<SSLContext> sslContext = clientConfiguration.getSslContext();
|
||||
|
||||
if (sslContext.isPresent()) {
|
||||
httpClient = httpClient.secure(sslContextSpec -> {
|
||||
sslContextSpec.sslContext(new JdkSslContext(sslContext.get(), true, null, IdentityCipherSuiteFilter.INSTANCE,
|
||||
ApplicationProtocolConfig.DISABLED, ClientAuth.NONE, null, false));
|
||||
});
|
||||
} else {
|
||||
httpClient = httpClient.secure();
|
||||
}
|
||||
|
||||
scheme = "https";
|
||||
}
|
||||
|
||||
ReactorClientHttpConnector connector = new ReactorClientHttpConnector(httpClient);
|
||||
WebClientProvider provider = WebClientProvider.create(scheme, connector);
|
||||
|
||||
if (clientConfiguration.getPathPrefix() != null) {
|
||||
provider = provider.withPathPrefix(clientConfiguration.getPathPrefix());
|
||||
}
|
||||
|
||||
provider = provider.withDefaultHeaders(clientConfiguration.getDefaultHeaders()) //
|
||||
.withWebClientConfigurer(clientConfiguration.getWebClientConfigurer());
|
||||
return provider;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient#ping(org.springframework.http.HttpHeaders)
|
||||
*/
|
||||
@Override
|
||||
public Mono<Boolean> ping(HttpHeaders headers) {
|
||||
|
||||
return sendRequest(new MainRequest(), RequestCreator.ping(), RawActionResponse.class, headers) //
|
||||
.map(response -> response.statusCode().is2xxSuccessful()) //
|
||||
.onErrorResume(NoReachableHostException.class, error -> Mono.just(false)).next();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient#info(org.springframework.http.HttpHeaders)
|
||||
*/
|
||||
@Override
|
||||
public Mono<MainResponse> info(HttpHeaders headers) {
|
||||
|
||||
return sendRequest(new MainRequest(), RequestCreator.info(), MainResponse.class, headers) //
|
||||
.next();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient#get(org.springframework.http.HttpHeaders, org.elasticsearch.action.get.GetRequest)
|
||||
*/
|
||||
@Override
|
||||
public Mono<GetResult> get(HttpHeaders headers, GetRequest getRequest) {
|
||||
|
||||
return sendRequest(getRequest, RequestCreator.get(), GetResponse.class, headers) //
|
||||
.filter(GetResponse::isExists) //
|
||||
.map(DefaultReactiveElasticsearchClient::getResponseToGetResult) //
|
||||
.next();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient#multiGet(org.springframework.http.HttpHeaders, org.elasticsearch.action.get.MultiGetRequest)
|
||||
*/
|
||||
@Override
|
||||
public Flux<GetResult> multiGet(HttpHeaders headers, MultiGetRequest multiGetRequest) {
|
||||
|
||||
return sendRequest(multiGetRequest, RequestCreator.multiGet(), MultiGetResponse.class, headers)
|
||||
.map(MultiGetResponse::getResponses) //
|
||||
.flatMap(Flux::fromArray) //
|
||||
.filter(it -> !it.isFailed() && it.getResponse().isExists()) //
|
||||
.map(it -> DefaultReactiveElasticsearchClient.getResponseToGetResult(it.getResponse()));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient#exists(org.springframework.http.HttpHeaders, org.elasticsearch.action.get.GetRequest)
|
||||
*/
|
||||
@Override
|
||||
public Mono<Boolean> exists(HttpHeaders headers, GetRequest getRequest) {
|
||||
|
||||
return sendRequest(getRequest, RequestCreator.exists(), RawActionResponse.class, headers) //
|
||||
.map(response -> response.statusCode().is2xxSuccessful()) //
|
||||
.next();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient#ping(org.springframework.http.HttpHeaders, org.elasticsearch.action.index.IndexRequest)
|
||||
*/
|
||||
@Override
|
||||
public Mono<IndexResponse> index(HttpHeaders headers, IndexRequest indexRequest) {
|
||||
return sendRequest(indexRequest, RequestCreator.index(), IndexResponse.class, headers).publishNext();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient#indices()
|
||||
*/
|
||||
@Override
|
||||
public Indices indices() {
|
||||
return this;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient#ping(org.springframework.http.HttpHeaders, org.elasticsearch.action.update.UpdateRequest)
|
||||
*/
|
||||
@Override
|
||||
public Mono<UpdateResponse> update(HttpHeaders headers, UpdateRequest updateRequest) {
|
||||
return sendRequest(updateRequest, RequestCreator.update(), UpdateResponse.class, headers).publishNext();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient#ping(org.springframework.http.HttpHeaders, org.elasticsearch.action.delete.DeleteRequest)
|
||||
*/
|
||||
@Override
|
||||
public Mono<DeleteResponse> delete(HttpHeaders headers, DeleteRequest deleteRequest) {
|
||||
|
||||
return sendRequest(deleteRequest, RequestCreator.delete(), DeleteResponse.class, headers) //
|
||||
.publishNext();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient#count(org.springframework.http.HttpHeaders, org.elasticsearch.action.search.SearchRequest)
|
||||
*/
|
||||
@Override
|
||||
public Mono<Long> count(HttpHeaders headers, CountRequest countRequest) {
|
||||
return sendRequest(countRequest, RequestCreator.count(), CountResponse.class, headers) //
|
||||
.map(CountResponse::getCount) //
|
||||
.next();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient#ping(org.springframework.http.HttpHeaders, org.elasticsearch.action.search.SearchRequest)
|
||||
*/
|
||||
@Override
|
||||
public Flux<SearchHit> search(HttpHeaders headers, SearchRequest searchRequest) {
|
||||
|
||||
return sendRequest(searchRequest, RequestCreator.search(), SearchResponse.class, headers) //
|
||||
.map(SearchResponse::getHits) //
|
||||
.flatMap(Flux::fromIterable);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient#scroll(org.springframework.http.HttpHeaders, org.elasticsearch.action.search.SearchRequest)
|
||||
*/
|
||||
@Override
|
||||
public Flux<SearchHit> scroll(HttpHeaders headers, SearchRequest searchRequest) {
|
||||
|
||||
TimeValue scrollTimeout = searchRequest.scroll() != null ? searchRequest.scroll().keepAlive()
|
||||
: TimeValue.timeValueMinutes(1);
|
||||
|
||||
if (searchRequest.scroll() == null) {
|
||||
searchRequest.scroll(scrollTimeout);
|
||||
}
|
||||
|
||||
EmitterProcessor<ActionRequest> outbound = EmitterProcessor.create(false);
|
||||
FluxSink<ActionRequest> request = outbound.sink();
|
||||
|
||||
EmitterProcessor<SearchResponse> inbound = EmitterProcessor.create(false);
|
||||
|
||||
Flux<SearchResponse> exchange = outbound.startWith(searchRequest).flatMap(it -> {
|
||||
|
||||
if (it instanceof SearchRequest) {
|
||||
return sendRequest((SearchRequest) it, RequestCreator.search(), SearchResponse.class, headers);
|
||||
} else if (it instanceof SearchScrollRequest) {
|
||||
return sendRequest((SearchScrollRequest) it, RequestCreator.scroll(), SearchResponse.class, headers);
|
||||
} else if (it instanceof ClearScrollRequest) {
|
||||
return sendRequest((ClearScrollRequest) it, RequestCreator.clearScroll(), ClearScrollResponse.class, headers)
|
||||
.flatMap(discard -> Flux.empty());
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException(
|
||||
String.format("Cannot handle '%s'. Please make sure to use a 'SearchRequest' or 'SearchScrollRequest'.", it));
|
||||
});
|
||||
|
||||
return Flux.usingWhen(Mono.fromSupplier(ScrollState::new),
|
||||
|
||||
scrollState -> {
|
||||
|
||||
Flux<SearchHit> searchHits = inbound.<SearchResponse> handle((searchResponse, sink) -> {
|
||||
|
||||
scrollState.updateScrollId(searchResponse.getScrollId());
|
||||
if (isEmpty(searchResponse.getHits())) {
|
||||
|
||||
inbound.onComplete();
|
||||
outbound.onComplete();
|
||||
|
||||
} else {
|
||||
|
||||
sink.next(searchResponse);
|
||||
|
||||
SearchScrollRequest searchScrollRequest = new SearchScrollRequest(scrollState.getScrollId())
|
||||
.scroll(scrollTimeout);
|
||||
request.next(searchScrollRequest);
|
||||
}
|
||||
|
||||
}).map(SearchResponse::getHits) //
|
||||
.flatMap(Flux::fromIterable);
|
||||
|
||||
return searchHits.doOnSubscribe(ignore -> exchange.subscribe(inbound));
|
||||
|
||||
}, state -> cleanupScroll(headers, state), //
|
||||
state -> cleanupScroll(headers, state), //
|
||||
state -> cleanupScroll(headers, state)); //
|
||||
}
|
||||
|
||||
private static boolean isEmpty(@Nullable SearchHits hits) {
|
||||
return hits != null && hits.getHits() != null && hits.getHits().length == 0;
|
||||
}
|
||||
|
||||
private Publisher<?> cleanupScroll(HttpHeaders headers, ScrollState state) {
|
||||
|
||||
if (state.getScrollIds().isEmpty()) {
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
ClearScrollRequest clearScrollRequest = new ClearScrollRequest();
|
||||
clearScrollRequest.scrollIds(state.getScrollIds());
|
||||
|
||||
// just send the request, resources get cleaned up anyways after scrollTimeout has been reached.
|
||||
return sendRequest(clearScrollRequest, RequestCreator.clearScroll(), ClearScrollResponse.class, headers);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient#ping(org.springframework.http.HttpHeaders, org.elasticsearch.index.reindex.DeleteByQueryRequest)
|
||||
*/
|
||||
@Override
|
||||
public Mono<BulkByScrollResponse> deleteBy(HttpHeaders headers, DeleteByQueryRequest deleteRequest) {
|
||||
|
||||
return sendRequest(deleteRequest, RequestCreator.deleteByQuery(), BulkByScrollResponse.class, headers) //
|
||||
.publishNext();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient#bulk(org.springframework.http.HttpHeaders, org.elasticsearch.action.bulk.BulkRequest)
|
||||
*/
|
||||
@Override
|
||||
public Mono<BulkResponse> bulk(HttpHeaders headers, BulkRequest bulkRequest) {
|
||||
return sendRequest(bulkRequest, RequestCreator.bulk(), BulkResponse.class, headers) //
|
||||
.publishNext();
|
||||
}
|
||||
|
||||
// --> INDICES
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient.Indices#existsIndex(org.springframework.http.HttpHeaders, org.elasticsearch.action.admin.indices.get.GetIndexRequest)
|
||||
*/
|
||||
@Override
|
||||
public Mono<Boolean> existsIndex(HttpHeaders headers, GetIndexRequest request) {
|
||||
|
||||
return sendRequest(request, RequestCreator.indexExists(), RawActionResponse.class, headers) //
|
||||
.map(response -> response.statusCode().is2xxSuccessful()) //
|
||||
.next();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient.Indices#deleteIndex(org.springframework.http.HttpHeaders, org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest)
|
||||
*/
|
||||
@Override
|
||||
public Mono<Void> deleteIndex(HttpHeaders headers, DeleteIndexRequest request) {
|
||||
|
||||
return sendRequest(request, RequestCreator.indexDelete(), AcknowledgedResponse.class, headers) //
|
||||
.then();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient.Indices#createIndex(org.springframework.http.HttpHeaders, org.elasticsearch.action.admin.indices.create.CreateIndexRequest)
|
||||
*/
|
||||
@Override
|
||||
public Mono<Void> createIndex(HttpHeaders headers, CreateIndexRequest createIndexRequest) {
|
||||
|
||||
return sendRequest(createIndexRequest, RequestCreator.indexCreate(), AcknowledgedResponse.class, headers) //
|
||||
.then();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient.Indices#openIndex(org.springframework.http.HttpHeaders, org.elasticsearch.action.admin.indices.open.OpenIndexRequest)
|
||||
*/
|
||||
@Override
|
||||
public Mono<Void> openIndex(HttpHeaders headers, OpenIndexRequest request) {
|
||||
|
||||
return sendRequest(request, RequestCreator.indexOpen(), AcknowledgedResponse.class, headers) //
|
||||
.then();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient.Indices#closeIndex(org.springframework.http.HttpHeaders, org.elasticsearch.action.admin.indices.close.CloseIndexRequest)
|
||||
*/
|
||||
@Override
|
||||
public Mono<Void> closeIndex(HttpHeaders headers, CloseIndexRequest closeIndexRequest) {
|
||||
|
||||
return sendRequest(closeIndexRequest, RequestCreator.indexClose(), AcknowledgedResponse.class, headers) //
|
||||
.then();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient.Indices#refreshIndex(org.springframework.http.HttpHeaders, org.elasticsearch.action.admin.indices.refresh.RefreshRequest)
|
||||
*/
|
||||
@Override
|
||||
public Mono<Void> refreshIndex(HttpHeaders headers, RefreshRequest refreshRequest) {
|
||||
|
||||
return sendRequest(refreshRequest, RequestCreator.indexRefresh(), RefreshResponse.class, headers) //
|
||||
.then();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient.Indices#updateMapping(org.springframework.http.HttpHeaders, org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest)
|
||||
*/
|
||||
@Override
|
||||
public Mono<Void> updateMapping(HttpHeaders headers, PutMappingRequest putMappingRequest) {
|
||||
|
||||
return sendRequest(putMappingRequest, RequestCreator.putMapping(), AcknowledgedResponse.class, headers) //
|
||||
.then();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient.Indices#flushIndex(org.springframework.http.HttpHeaders, org.elasticsearch.action.admin.indices.flush.FlushRequest)
|
||||
*/
|
||||
@Override
|
||||
public Mono<Void> flushIndex(HttpHeaders headers, FlushRequest flushRequest) {
|
||||
|
||||
return sendRequest(flushRequest, RequestCreator.flushIndex(), FlushResponse.class, headers) //
|
||||
.then();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient#ping(org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient.ReactiveElasticsearchClientCallback)
|
||||
*/
|
||||
@Override
|
||||
public Mono<ClientResponse> execute(ReactiveElasticsearchClientCallback callback) {
|
||||
|
||||
return this.hostProvider.getActive(Verification.LAZY) //
|
||||
.flatMap(callback::doWithClient) //
|
||||
.onErrorResume(throwable -> {
|
||||
|
||||
if (throwable instanceof ConnectException) {
|
||||
|
||||
return hostProvider.getActive(Verification.ACTIVE) //
|
||||
.flatMap(callback::doWithClient);
|
||||
}
|
||||
|
||||
return Mono.error(throwable);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Status> status() {
|
||||
|
||||
return hostProvider.clusterInfo() //
|
||||
.map(it -> new ClientStatus(it.getNodes()));
|
||||
}
|
||||
|
||||
// --> Private Response helpers
|
||||
|
||||
private static GetResult getResponseToGetResult(GetResponse response) {
|
||||
|
||||
return new GetResult(response.getIndex(), response.getType(), response.getId(), response.getSeqNo(),
|
||||
response.getPrimaryTerm(), response.getVersion(), response.isExists(), response.getSourceAsBytesRef(),
|
||||
response.getFields());
|
||||
}
|
||||
|
||||
// -->
|
||||
|
||||
private <Req extends ActionRequest, Resp> Flux<Resp> sendRequest(Req request, Function<Req, Request> converter,
|
||||
Class<Resp> responseType, HttpHeaders headers) {
|
||||
return sendRequest(converter.apply(request), responseType, headers);
|
||||
}
|
||||
|
||||
private <Resp> Flux<Resp> sendRequest(Request request, Class<Resp> responseType, HttpHeaders headers) {
|
||||
|
||||
String logId = ClientLogger.newLogId();
|
||||
|
||||
return execute(webClient -> sendRequest(webClient, logId, request, headers))
|
||||
.flatMapMany(response -> readResponseBody(logId, request, response, responseType));
|
||||
}
|
||||
|
||||
private Mono<ClientResponse> sendRequest(WebClient webClient, String logId, Request request, HttpHeaders headers) {
|
||||
|
||||
RequestBodySpec requestBodySpec = webClient.method(HttpMethod.valueOf(request.getMethod().toUpperCase())) //
|
||||
.uri(builder -> {
|
||||
|
||||
builder = builder.path(request.getEndpoint());
|
||||
|
||||
if (!ObjectUtils.isEmpty(request.getParameters())) {
|
||||
for (Entry<String, String> entry : request.getParameters().entrySet()) {
|
||||
builder = builder.queryParam(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
return builder.build();
|
||||
}) //
|
||||
.attribute(ClientRequest.LOG_ID_ATTRIBUTE, logId) //
|
||||
.headers(theHeaders -> {
|
||||
|
||||
// add all the headers explicitly set
|
||||
theHeaders.addAll(headers);
|
||||
|
||||
// and now those that might be set on the request.
|
||||
if (request.getOptions() != null) {
|
||||
|
||||
if (!ObjectUtils.isEmpty(request.getOptions().getHeaders())) {
|
||||
request.getOptions().getHeaders().forEach(it -> theHeaders.add(it.getName(), it.getValue()));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (request.getEntity() != null) {
|
||||
|
||||
Lazy<String> body = bodyExtractor(request);
|
||||
|
||||
ClientLogger.logRequest(logId, request.getMethod().toUpperCase(), request.getEndpoint(), request.getParameters(),
|
||||
body::get);
|
||||
|
||||
requestBodySpec.contentType(MediaType.valueOf(request.getEntity().getContentType().getValue()));
|
||||
requestBodySpec.body(Mono.fromSupplier(body::get), String.class);
|
||||
} else {
|
||||
ClientLogger.logRequest(logId, request.getMethod().toUpperCase(), request.getEndpoint(), request.getParameters());
|
||||
}
|
||||
|
||||
return requestBodySpec //
|
||||
.exchange() //
|
||||
.onErrorReturn(ConnectException.class, ClientResponse.create(HttpStatus.SERVICE_UNAVAILABLE).build());
|
||||
}
|
||||
|
||||
private Lazy<String> bodyExtractor(Request request) {
|
||||
|
||||
return Lazy.of(() -> {
|
||||
|
||||
try {
|
||||
return EntityUtils.toString(request.getEntity());
|
||||
} catch (IOException e) {
|
||||
throw new RequestBodyEncodingException("Error encoding request", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private <T> Publisher<? extends T> readResponseBody(String logId, Request request, ClientResponse response,
|
||||
Class<T> responseType) {
|
||||
|
||||
if (RawActionResponse.class.equals(responseType)) {
|
||||
|
||||
ClientLogger.logRawResponse(logId, response.statusCode());
|
||||
return Mono.just(responseType.cast(RawActionResponse.create(response)));
|
||||
}
|
||||
|
||||
if (response.statusCode().is5xxServerError()) {
|
||||
|
||||
ClientLogger.logRawResponse(logId, response.statusCode());
|
||||
return handleServerError(request, response);
|
||||
}
|
||||
|
||||
if (response.statusCode().is4xxClientError()) {
|
||||
|
||||
ClientLogger.logRawResponse(logId, response.statusCode());
|
||||
return handleClientError(logId, request, response, responseType);
|
||||
}
|
||||
|
||||
return response.body(BodyExtractors.toMono(byte[].class)) //
|
||||
.map(it -> new String(it, StandardCharsets.UTF_8)) //
|
||||
.doOnNext(it -> ClientLogger.logResponse(logId, response.statusCode(), it)) //
|
||||
.flatMap(content -> doDecode(response, responseType, content));
|
||||
}
|
||||
|
||||
private static <T> Mono<T> doDecode(ClientResponse response, Class<T> responseType, String content) {
|
||||
|
||||
String mediaType = response.headers().contentType().map(MediaType::toString).orElse(XContentType.JSON.mediaType());
|
||||
|
||||
try {
|
||||
|
||||
Method fromXContent = ReflectionUtils.findMethod(responseType, "fromXContent", XContentParser.class);
|
||||
|
||||
return Mono.justOrEmpty(responseType
|
||||
.cast(ReflectionUtils.invokeMethod(fromXContent, responseType, createParser(mediaType, content))));
|
||||
|
||||
} catch (Throwable errorParseFailure) { // cause elasticsearch also uses AssertionError
|
||||
|
||||
try {
|
||||
return Mono.error(BytesRestResponse.errorFromXContent(createParser(mediaType, content)));
|
||||
} catch (Exception e) {
|
||||
|
||||
return Mono
|
||||
.error(new ElasticsearchStatusException(content, RestStatus.fromCode(response.statusCode().value())));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static XContentParser createParser(String mediaType, String content) throws IOException {
|
||||
|
||||
return XContentType.fromMediaTypeOrFormat(mediaType) //
|
||||
.xContent() //
|
||||
.createParser(NamedXContentRegistry.EMPTY, DeprecationHandler.THROW_UNSUPPORTED_OPERATION, content);
|
||||
}
|
||||
|
||||
private <T> Publisher<? extends T> handleServerError(Request request, ClientResponse response) {
|
||||
|
||||
RestStatus status = RestStatus.fromCode(response.statusCode().value());
|
||||
|
||||
return Mono.error(new ElasticsearchStatusException(String.format("%s request to %s returned error code %s.",
|
||||
request.getMethod(), request.getEndpoint(), response.statusCode().value()), status));
|
||||
}
|
||||
|
||||
private <T> Publisher<? extends T> handleClientError(String logId, Request request, ClientResponse response,
|
||||
Class<T> responseType) {
|
||||
|
||||
return response.body(BodyExtractors.toMono(byte[].class)) //
|
||||
.map(bytes -> new String(bytes, StandardCharsets.UTF_8)) //
|
||||
.flatMap(content -> {
|
||||
String mediaType = response.headers().contentType().map(MediaType::toString)
|
||||
.orElse(XContentType.JSON.mediaType());
|
||||
RestStatus status = RestStatus.fromCode(response.statusCode().value());
|
||||
try {
|
||||
ElasticsearchException exception = getElasticsearchException(response, content, mediaType);
|
||||
if (exception != null) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
buildExceptionMessages(sb, exception);
|
||||
return Mono.error(new ElasticsearchStatusException(sb.toString(), status, exception));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
return Mono.error(new ElasticsearchStatusException(content, status));
|
||||
}
|
||||
return Mono.just(content);
|
||||
}).doOnNext(it -> ClientLogger.logResponse(logId, response.statusCode(), it)) //
|
||||
.flatMap(content -> doDecode(response, responseType, content));
|
||||
}
|
||||
|
||||
// region ElasticsearchException helper
|
||||
@Nullable
|
||||
private ElasticsearchException getElasticsearchException(ClientResponse response, String content, String mediaType)
|
||||
throws IOException {
|
||||
|
||||
XContentParser parser = createParser(mediaType, content);
|
||||
// we have a JSON object with an error and a status field
|
||||
XContentParser.Token token = parser.nextToken(); // Skip START_OBJECT
|
||||
|
||||
do {
|
||||
token = parser.nextToken();
|
||||
|
||||
if (parser.currentName().equals("error")) {
|
||||
return ElasticsearchException.failureFromXContent(parser);
|
||||
}
|
||||
} while (token == XContentParser.Token.FIELD_NAME);
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void buildExceptionMessages(StringBuilder sb, Throwable t) {
|
||||
|
||||
sb.append(t.getMessage());
|
||||
for (Throwable throwable : t.getSuppressed()) {
|
||||
sb.append(", ");
|
||||
buildExceptionMessages(sb, throwable);
|
||||
}
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region internal classes
|
||||
static class RequestCreator {
|
||||
|
||||
static Function<SearchRequest, Request> search() {
|
||||
return RequestConverters::search;
|
||||
}
|
||||
|
||||
static Function<SearchScrollRequest, Request> scroll() {
|
||||
return RequestConverters::searchScroll;
|
||||
}
|
||||
|
||||
static Function<ClearScrollRequest, Request> clearScroll() {
|
||||
return RequestConverters::clearScroll;
|
||||
}
|
||||
|
||||
static Function<IndexRequest, Request> index() {
|
||||
return RequestConverters::index;
|
||||
}
|
||||
|
||||
static Function<GetRequest, Request> get() {
|
||||
return RequestConverters::get;
|
||||
}
|
||||
|
||||
static Function<MainRequest, Request> ping() {
|
||||
return (request) -> RequestConverters.ping();
|
||||
}
|
||||
|
||||
static Function<MainRequest, Request> info() {
|
||||
return (request) -> RequestConverters.info();
|
||||
}
|
||||
|
||||
static Function<MultiGetRequest, Request> multiGet() {
|
||||
return RequestConverters::multiGet;
|
||||
}
|
||||
|
||||
static Function<GetRequest, Request> exists() {
|
||||
return RequestConverters::exists;
|
||||
}
|
||||
|
||||
static Function<UpdateRequest, Request> update() {
|
||||
return RequestConverters::update;
|
||||
}
|
||||
|
||||
static Function<DeleteRequest, Request> delete() {
|
||||
return RequestConverters::delete;
|
||||
}
|
||||
|
||||
static Function<DeleteByQueryRequest, Request> deleteByQuery() {
|
||||
|
||||
return request -> {
|
||||
|
||||
try {
|
||||
return RequestConverters.deleteByQuery(request);
|
||||
} catch (IOException e) {
|
||||
throw new org.springframework.data.elasticsearch.ElasticsearchException("Could not parse request", e);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
static Function<BulkRequest, Request> bulk() {
|
||||
|
||||
return request -> {
|
||||
|
||||
try {
|
||||
return RequestConverters.bulk(request);
|
||||
} catch (IOException e) {
|
||||
throw new org.springframework.data.elasticsearch.ElasticsearchException("Could not parse request", e);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// --> INDICES
|
||||
|
||||
static Function<GetIndexRequest, Request> indexExists() {
|
||||
return RequestConverters::indexExists;
|
||||
}
|
||||
|
||||
static Function<DeleteIndexRequest, Request> indexDelete() {
|
||||
return RequestConverters::indexDelete;
|
||||
}
|
||||
|
||||
static Function<CreateIndexRequest, Request> indexCreate() {
|
||||
return RequestConverters::indexCreate;
|
||||
}
|
||||
|
||||
static Function<OpenIndexRequest, Request> indexOpen() {
|
||||
return RequestConverters::indexOpen;
|
||||
}
|
||||
|
||||
static Function<CloseIndexRequest, Request> indexClose() {
|
||||
return RequestConverters::indexClose;
|
||||
}
|
||||
|
||||
static Function<RefreshRequest, Request> indexRefresh() {
|
||||
return RequestConverters::indexRefresh;
|
||||
}
|
||||
|
||||
static Function<PutMappingRequest, Request> putMapping() {
|
||||
return RequestConverters::putMapping;
|
||||
}
|
||||
|
||||
static Function<FlushRequest, Request> flushIndex() {
|
||||
return RequestConverters::flushIndex;
|
||||
}
|
||||
|
||||
static Function<CountRequest, Request> count() {
|
||||
return RequestConverters::count;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Reactive client {@link ReactiveElasticsearchClient.Status} implementation.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
class ClientStatus implements Status {
|
||||
|
||||
private final Collection<ElasticsearchHost> connectedHosts;
|
||||
|
||||
ClientStatus(Collection<ElasticsearchHost> connectedHosts) {
|
||||
this.connectedHosts = connectedHosts;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient.Status#hosts()
|
||||
*/
|
||||
@Override
|
||||
public Collection<ElasticsearchHost> hosts() {
|
||||
return connectedHosts;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mutable state object holding scrollId to be used for {@link SearchScrollRequest#scroll(Scroll)}
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 3.2
|
||||
*/
|
||||
private static class ScrollState {
|
||||
|
||||
private final Object lock = new Object();
|
||||
|
||||
private final List<String> pastIds = new ArrayList<>(1);
|
||||
private String scrollId;
|
||||
|
||||
String getScrollId() {
|
||||
return scrollId;
|
||||
}
|
||||
|
||||
List<String> getScrollIds() {
|
||||
|
||||
synchronized (lock) {
|
||||
return Collections.unmodifiableList(new ArrayList<>(pastIds));
|
||||
}
|
||||
}
|
||||
|
||||
void updateScrollId(String scrollId) {
|
||||
|
||||
if (StringUtils.hasText(scrollId)) {
|
||||
|
||||
synchronized (lock) {
|
||||
|
||||
this.scrollId = scrollId;
|
||||
pastIds.add(scrollId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// endregion
|
||||
}
|
||||
-162
@@ -1,162 +0,0 @@
|
||||
/*
|
||||
* Copyright 2018-2020 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.elasticsearch.client.reactive;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.client.reactive.ClientHttpConnector;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
import org.springframework.web.reactive.function.client.WebClient.Builder;
|
||||
|
||||
/**
|
||||
* Default {@link WebClientProvider} that uses cached {@link WebClient} instances per {@code hostAndPort}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Christoph Strobl
|
||||
* @author Huw Ayling-Miller
|
||||
* @author Peter-Josef Meisch
|
||||
* @since 3.2
|
||||
*/
|
||||
class DefaultWebClientProvider implements WebClientProvider {
|
||||
|
||||
private final Map<InetSocketAddress, WebClient> cachedClients;
|
||||
|
||||
private final String scheme;
|
||||
private final @Nullable ClientHttpConnector connector;
|
||||
private final Consumer<Throwable> errorListener;
|
||||
private final HttpHeaders headers;
|
||||
private final String pathPrefix;
|
||||
private final Function<WebClient, WebClient> webClientConfigurer;
|
||||
|
||||
/**
|
||||
* Create new {@link DefaultWebClientProvider} with empty {@link HttpHeaders} and no-op {@literal error listener}.
|
||||
*
|
||||
* @param scheme must not be {@literal null}.
|
||||
* @param connector can be {@literal null}.
|
||||
*/
|
||||
DefaultWebClientProvider(String scheme, @Nullable ClientHttpConnector connector) {
|
||||
this(scheme, connector, e -> {}, HttpHeaders.EMPTY, null, Function.identity());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new {@link DefaultWebClientProvider} with empty {@link HttpHeaders} and no-op {@literal error listener}.
|
||||
*
|
||||
* @param scheme must not be {@literal null}.
|
||||
* @param connector can be {@literal null}.
|
||||
* @param errorListener must not be {@literal null}.
|
||||
* @param headers must not be {@literal null}.
|
||||
* @param pathPrefix can be {@literal null}
|
||||
* @param webClientConfigurer must not be {@literal null}.
|
||||
*/
|
||||
private DefaultWebClientProvider(String scheme, @Nullable ClientHttpConnector connector,
|
||||
Consumer<Throwable> errorListener, HttpHeaders headers, @Nullable String pathPrefix,
|
||||
Function<WebClient, WebClient> webClientConfigurer) {
|
||||
|
||||
Assert.notNull(scheme, "Scheme must not be null! A common scheme would be 'http'.");
|
||||
Assert.notNull(errorListener, "errorListener must not be null! You may want use a no-op one 'e -> {}' instead.");
|
||||
Assert.notNull(headers, "headers must not be null! Think about using 'HttpHeaders.EMPTY' as an alternative.");
|
||||
Assert.notNull(webClientConfigurer,
|
||||
"webClientConfigurer must not be null! You may want use a no-op one 'Function.identity()' instead.");
|
||||
|
||||
this.cachedClients = new ConcurrentHashMap<>();
|
||||
this.scheme = scheme;
|
||||
this.connector = connector;
|
||||
this.errorListener = errorListener;
|
||||
this.headers = headers;
|
||||
this.pathPrefix = pathPrefix;
|
||||
this.webClientConfigurer = webClientConfigurer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebClient get(InetSocketAddress endpoint) {
|
||||
|
||||
Assert.notNull(endpoint, "Endpoint must not be empty!");
|
||||
|
||||
return this.cachedClients.computeIfAbsent(endpoint, this::createWebClientForSocketAddress);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpHeaders getDefaultHeaders() {
|
||||
return headers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Consumer<Throwable> getErrorListener() {
|
||||
return this.errorListener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPathPrefix() {
|
||||
return pathPrefix;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebClientProvider withDefaultHeaders(HttpHeaders headers) {
|
||||
|
||||
Assert.notNull(headers, "HttpHeaders must not be null.");
|
||||
|
||||
HttpHeaders merged = new HttpHeaders();
|
||||
merged.addAll(this.headers);
|
||||
merged.addAll(headers);
|
||||
|
||||
return new DefaultWebClientProvider(scheme, connector, errorListener, merged, pathPrefix, webClientConfigurer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebClientProvider withErrorListener(Consumer<Throwable> errorListener) {
|
||||
|
||||
Assert.notNull(errorListener, "Error listener must not be null.");
|
||||
|
||||
Consumer<Throwable> listener = this.errorListener.andThen(errorListener);
|
||||
return new DefaultWebClientProvider(scheme, this.connector, listener, headers, pathPrefix, webClientConfigurer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebClientProvider withPathPrefix(String pathPrefix) {
|
||||
Assert.notNull(pathPrefix, "pathPrefix must not be null.");
|
||||
|
||||
return new DefaultWebClientProvider(this.scheme, this.connector, this.errorListener, this.headers, pathPrefix,
|
||||
webClientConfigurer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebClientProvider withWebClientConfigurer(Function<WebClient, WebClient> webClientConfigurer) {
|
||||
return new DefaultWebClientProvider(scheme, connector, errorListener, headers, pathPrefix, webClientConfigurer);
|
||||
|
||||
}
|
||||
|
||||
|
||||
protected WebClient createWebClientForSocketAddress(InetSocketAddress socketAddress) {
|
||||
|
||||
Builder builder = WebClient.builder().defaultHeaders(it -> it.addAll(getDefaultHeaders()));
|
||||
|
||||
if (connector != null) {
|
||||
builder = builder.clientConnector(connector);
|
||||
}
|
||||
|
||||
String baseUrl = String.format("%s://%s:%d%s", this.scheme, socketAddress.getHostString(), socketAddress.getPort(),
|
||||
pathPrefix == null ? "" : '/' + pathPrefix);
|
||||
WebClient webClient = builder.baseUrl(baseUrl).filter((request, next) -> next.exchange(request).doOnError(errorListener)).build();
|
||||
return webClientConfigurer.apply(webClient);
|
||||
}
|
||||
}
|
||||
-149
@@ -1,149 +0,0 @@
|
||||
/*
|
||||
* Copyright 2018-2020 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.elasticsearch.client.reactive;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.data.elasticsearch.client.ElasticsearchHost;
|
||||
import org.springframework.data.elasticsearch.client.NoReachableHostException;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
/**
|
||||
* Infrastructure helper class aware of hosts within the cluster and the health of those allowing easy selection of
|
||||
* active ones.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
* @since 3.2
|
||||
*/
|
||||
public interface HostProvider {
|
||||
|
||||
/**
|
||||
* Create a new {@link HostProvider} best suited for the given {@link WebClientProvider} and number of hosts.
|
||||
*
|
||||
* @param clientProvider must not be {@literal null} .
|
||||
* @param endpoints must not be {@literal null} nor empty.
|
||||
* @return new instance of {@link HostProvider}.
|
||||
*/
|
||||
static HostProvider provider(WebClientProvider clientProvider, InetSocketAddress... endpoints) {
|
||||
|
||||
Assert.notNull(clientProvider, "WebClientProvider must not be null");
|
||||
Assert.notEmpty(endpoints, "Please provide at least one endpoint to connect to.");
|
||||
|
||||
if (endpoints.length == 1) {
|
||||
return new SingleNodeHostProvider(clientProvider, endpoints[0]);
|
||||
} else {
|
||||
return new MultiNodeHostProvider(clientProvider, endpoints);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup an active host in {@link Verification#LAZY lazy} mode utilizing cached {@link ElasticsearchHost}.
|
||||
*
|
||||
* @return the {@link Mono} emitting the active host or {@link Mono#error(Throwable) an error} if none found.
|
||||
*/
|
||||
default Mono<InetSocketAddress> lookupActiveHost() {
|
||||
return lookupActiveHost(Verification.LAZY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup an active host in using the given {@link Verification}.
|
||||
*
|
||||
* @param verification
|
||||
* @return the {@link Mono} emitting the active host or {@link Mono#error(Throwable) an error}
|
||||
* ({@link NoReachableHostException}) if none found.
|
||||
*/
|
||||
Mono<InetSocketAddress> lookupActiveHost(Verification verification);
|
||||
|
||||
/**
|
||||
* Get the {@link WebClient} connecting to an active host utilizing cached {@link ElasticsearchHost}.
|
||||
*
|
||||
* @return the {@link Mono} emitting the client for an active host or {@link Mono#error(Throwable) an error} if none
|
||||
* found.
|
||||
*/
|
||||
default Mono<WebClient> getActive() {
|
||||
return getActive(Verification.LAZY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link WebClient} connecting to an active host.
|
||||
*
|
||||
* @param verification must not be {@literal null}.
|
||||
* @return the {@link Mono} emitting the client for an active host or {@link Mono#error(Throwable) an error} if none
|
||||
* found.
|
||||
*/
|
||||
default Mono<WebClient> getActive(Verification verification) {
|
||||
return lookupActiveHost(verification).map(this::createWebClient);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link WebClient} for {@link InetSocketAddress endpoint}.
|
||||
*
|
||||
* @param endpoint must not be {@literal null}.
|
||||
* @return a {@link WebClient} using the the given endpoint as {@literal base url}.
|
||||
*/
|
||||
WebClient createWebClient(InetSocketAddress endpoint);
|
||||
|
||||
/**
|
||||
* Obtain information about known cluster nodes.
|
||||
*
|
||||
* @return the {@link Mono} emitting {@link ClusterInformation} when available.
|
||||
*/
|
||||
Mono<ClusterInformation> clusterInfo();
|
||||
|
||||
/**
|
||||
* {@link Verification} allows to influence the lookup strategy for active hosts.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 3.2
|
||||
*/
|
||||
enum Verification {
|
||||
|
||||
/**
|
||||
* Actively check for cluster node health.
|
||||
*/
|
||||
ACTIVE,
|
||||
|
||||
/**
|
||||
* Use cached data for cluster node health.
|
||||
*/
|
||||
LAZY
|
||||
}
|
||||
|
||||
/**
|
||||
* Value object accumulating information about an Elasticsearch cluster.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 3.2
|
||||
*/
|
||||
class ClusterInformation {
|
||||
|
||||
private final Set<ElasticsearchHost> nodes;
|
||||
|
||||
public ClusterInformation(Set<ElasticsearchHost> nodes) {
|
||||
this.nodes = nodes;
|
||||
}
|
||||
|
||||
public Set<ElasticsearchHost> getNodes() {
|
||||
return Collections.unmodifiableSet(nodes);
|
||||
}
|
||||
}
|
||||
}
|
||||
-153
@@ -1,153 +0,0 @@
|
||||
/*
|
||||
* Copyright 2018-2020 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.elasticsearch.client.reactive;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.util.function.Tuple2;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.springframework.data.elasticsearch.client.ElasticsearchHost;
|
||||
import org.springframework.data.elasticsearch.client.ElasticsearchHost.State;
|
||||
import org.springframework.data.elasticsearch.client.NoReachableHostException;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.web.reactive.function.client.ClientResponse;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
/**
|
||||
* {@link HostProvider} for a cluster of nodes.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
* @since 3.2
|
||||
*/
|
||||
class MultiNodeHostProvider implements HostProvider {
|
||||
|
||||
private final WebClientProvider clientProvider;
|
||||
private final Map<InetSocketAddress, ElasticsearchHost> hosts;
|
||||
|
||||
MultiNodeHostProvider(WebClientProvider clientProvider, InetSocketAddress... endpoints) {
|
||||
|
||||
this.clientProvider = clientProvider;
|
||||
this.hosts = new ConcurrentHashMap<>();
|
||||
for (InetSocketAddress endpoint : endpoints) {
|
||||
this.hosts.put(endpoint, new ElasticsearchHost(endpoint, State.UNKNOWN));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.client.reactive.HostProvider#clusterInfo()
|
||||
*/
|
||||
public Mono<ClusterInformation> clusterInfo() {
|
||||
return nodes(null).map(this::updateNodeState).buffer(hosts.size())
|
||||
.then(Mono.just(new ClusterInformation(new LinkedHashSet<>(this.hosts.values()))));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.client.reactive.HostProvider#createWebClient(java.net.InetSocketAddress)
|
||||
*/
|
||||
@Override
|
||||
public WebClient createWebClient(InetSocketAddress endpoint) {
|
||||
return this.clientProvider.get(endpoint);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.client.reactive.HostProvider#lookupActiveHost(org.springframework.data.elasticsearch.client.reactive.HostProvider.Verification)
|
||||
*/
|
||||
@Override
|
||||
public Mono<InetSocketAddress> lookupActiveHost(Verification verification) {
|
||||
|
||||
if (Verification.LAZY.equals(verification)) {
|
||||
for (ElasticsearchHost entry : hosts()) {
|
||||
if (entry.isOnline()) {
|
||||
return Mono.just(entry.getEndpoint());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return findActiveHostInKnownActives() //
|
||||
.switchIfEmpty(findActiveHostInUnresolved()) //
|
||||
.switchIfEmpty(findActiveHostInDead()) //
|
||||
.switchIfEmpty(Mono.error(() -> new NoReachableHostException(new LinkedHashSet<>(getCachedHostState()))));
|
||||
}
|
||||
|
||||
Collection<ElasticsearchHost> getCachedHostState() {
|
||||
return hosts.values();
|
||||
}
|
||||
|
||||
private Mono<InetSocketAddress> findActiveHostInKnownActives() {
|
||||
return findActiveForSate(State.ONLINE);
|
||||
}
|
||||
|
||||
private Mono<InetSocketAddress> findActiveHostInUnresolved() {
|
||||
return findActiveForSate(State.UNKNOWN);
|
||||
}
|
||||
|
||||
private Mono<InetSocketAddress> findActiveHostInDead() {
|
||||
return findActiveForSate(State.OFFLINE);
|
||||
}
|
||||
|
||||
private Mono<InetSocketAddress> findActiveForSate(State state) {
|
||||
return nodes(state).map(this::updateNodeState).filter(ElasticsearchHost::isOnline)
|
||||
.map(ElasticsearchHost::getEndpoint).next();
|
||||
}
|
||||
|
||||
private ElasticsearchHost updateNodeState(Tuple2<InetSocketAddress, ClientResponse> tuple2) {
|
||||
|
||||
State state = tuple2.getT2().statusCode().isError() ? State.OFFLINE : State.ONLINE;
|
||||
ElasticsearchHost elasticsearchHost = new ElasticsearchHost(tuple2.getT1(), state);
|
||||
hosts.put(tuple2.getT1(), elasticsearchHost);
|
||||
return elasticsearchHost;
|
||||
}
|
||||
|
||||
private Flux<Tuple2<InetSocketAddress, ClientResponse>> nodes(@Nullable State state) {
|
||||
|
||||
return Flux.fromIterable(hosts()) //
|
||||
.filter(entry -> state == null || entry.getState().equals(state)) //
|
||||
.map(ElasticsearchHost::getEndpoint) //
|
||||
.flatMap(host -> {
|
||||
|
||||
Mono<ClientResponse> exchange = createWebClient(host) //
|
||||
.head().uri("/").exchange().doOnError(throwable -> {
|
||||
|
||||
hosts.put(host, new ElasticsearchHost(host, State.OFFLINE));
|
||||
clientProvider.getErrorListener().accept(throwable);
|
||||
});
|
||||
|
||||
return Mono.just(host).zipWith(exchange);
|
||||
}) //
|
||||
.onErrorContinue((throwable, o) -> clientProvider.getErrorListener().accept(throwable));
|
||||
}
|
||||
|
||||
private List<ElasticsearchHost> hosts() {
|
||||
|
||||
List<ElasticsearchHost> hosts = new ArrayList<>(this.hosts.values());
|
||||
Collections.shuffle(hosts);
|
||||
|
||||
return hosts;
|
||||
}
|
||||
}
|
||||
-64
@@ -1,64 +0,0 @@
|
||||
/*
|
||||
* Copyright 2018-2020 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.elasticsearch.client.reactive;
|
||||
|
||||
import org.elasticsearch.action.ActionResponse;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.client.reactive.ClientHttpResponse;
|
||||
import org.springframework.web.reactive.function.BodyExtractor;
|
||||
import org.springframework.web.reactive.function.client.ClientResponse;
|
||||
|
||||
/**
|
||||
* Extension to {@link ActionResponse} that also delegates to {@link ClientResponse}.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author Peter-Josef Meisch
|
||||
* @author Mark Paluch
|
||||
* @since 3.2
|
||||
*/
|
||||
class RawActionResponse extends ActionResponse {
|
||||
|
||||
private final ClientResponse delegate;
|
||||
|
||||
private RawActionResponse(ClientResponse delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
static RawActionResponse create(ClientResponse response) {
|
||||
return new RawActionResponse(response);
|
||||
}
|
||||
|
||||
public HttpStatus statusCode() {
|
||||
return delegate.statusCode();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.web.reactive.function.client.ClientResponse#headers()
|
||||
*/
|
||||
public ClientResponse.Headers headers() {
|
||||
return delegate.headers();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.web.reactive.function.client.ClientResponse#body(org.springframework.web.reactive.function.BodyExtractor)
|
||||
*/
|
||||
public <T> T body(BodyExtractor<T, ? super ClientHttpResponse> extractor) {
|
||||
return delegate.body(extractor);
|
||||
}
|
||||
}
|
||||
-910
@@ -1,910 +0,0 @@
|
||||
/*
|
||||
* Copyright 2018-2020 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.elasticsearch.client.reactive;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.net.ConnectException;
|
||||
import java.util.Collection;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.elasticsearch.action.admin.indices.close.CloseIndexRequest;
|
||||
import org.elasticsearch.action.admin.indices.create.CreateIndexRequest;
|
||||
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
|
||||
import org.elasticsearch.action.admin.indices.flush.FlushRequest;
|
||||
import org.elasticsearch.action.admin.indices.get.GetIndexRequest;
|
||||
import org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest;
|
||||
import org.elasticsearch.action.admin.indices.open.OpenIndexRequest;
|
||||
import org.elasticsearch.action.admin.indices.refresh.RefreshRequest;
|
||||
import org.elasticsearch.action.bulk.BulkRequest;
|
||||
import org.elasticsearch.action.bulk.BulkResponse;
|
||||
import org.elasticsearch.action.delete.DeleteRequest;
|
||||
import org.elasticsearch.action.delete.DeleteResponse;
|
||||
import org.elasticsearch.action.get.GetRequest;
|
||||
import org.elasticsearch.action.get.MultiGetRequest;
|
||||
import org.elasticsearch.action.index.IndexRequest;
|
||||
import org.elasticsearch.action.index.IndexResponse;
|
||||
import org.elasticsearch.action.main.MainResponse;
|
||||
import org.elasticsearch.action.search.SearchRequest;
|
||||
import org.elasticsearch.action.search.SearchResponse;
|
||||
import org.elasticsearch.action.update.UpdateRequest;
|
||||
import org.elasticsearch.action.update.UpdateResponse;
|
||||
import org.elasticsearch.client.core.CountRequest;
|
||||
import org.elasticsearch.index.get.GetResult;
|
||||
import org.elasticsearch.index.reindex.BulkByScrollResponse;
|
||||
import org.elasticsearch.index.reindex.DeleteByQueryRequest;
|
||||
import org.elasticsearch.search.SearchHit;
|
||||
import org.springframework.data.elasticsearch.client.ClientConfiguration;
|
||||
import org.springframework.data.elasticsearch.client.ElasticsearchHost;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.web.reactive.function.client.ClientResponse;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
/**
|
||||
* A reactive client to connect to Elasticsearch.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
* @author Peter-Josef Meisch
|
||||
* @author Henrique Amaral
|
||||
* @since 3.2
|
||||
* @see ClientConfiguration
|
||||
* @see ReactiveRestClients
|
||||
*/
|
||||
public interface ReactiveElasticsearchClient {
|
||||
|
||||
/**
|
||||
* Pings the remote Elasticsearch cluster and emits {@literal true} if the ping succeeded, {@literal false} otherwise.
|
||||
*
|
||||
* @return the {@link Mono} emitting the result of the ping attempt.
|
||||
*/
|
||||
default Mono<Boolean> ping() {
|
||||
return ping(HttpHeaders.EMPTY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pings the remote Elasticsearch cluster and emits {@literal true} if the ping succeeded, {@literal false} otherwise.
|
||||
*
|
||||
* @param headers Use {@link HttpHeaders} to provide eg. authentication data. Must not be {@literal null}.
|
||||
* @return the {@link Mono} emitting the result of the ping attempt.
|
||||
*/
|
||||
Mono<Boolean> ping(HttpHeaders headers);
|
||||
|
||||
/**
|
||||
* Get the cluster info otherwise provided when sending an HTTP request to port 9200.
|
||||
*
|
||||
* @return the {@link Mono} emitting the result of the info request.
|
||||
*/
|
||||
default Mono<MainResponse> info() {
|
||||
return info(HttpHeaders.EMPTY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the cluster info otherwise provided when sending an HTTP request to port 9200.
|
||||
*
|
||||
* @param headers Use {@link HttpHeaders} to provide eg. authentication data. Must not be {@literal null}.
|
||||
* @return the {@link Mono} emitting the result of the info request.
|
||||
*/
|
||||
Mono<MainResponse> info(HttpHeaders headers);
|
||||
|
||||
/**
|
||||
* Execute a {@link GetRequest} against the {@literal get} API to retrieve a document by id.
|
||||
*
|
||||
* @param consumer never {@literal null}.
|
||||
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-get.html">Get API on
|
||||
* elastic.co</a>
|
||||
* @return the {@link Mono} emitting the {@link GetResult result}.
|
||||
*/
|
||||
default Mono<GetResult> get(Consumer<GetRequest> consumer) {
|
||||
|
||||
GetRequest request = new GetRequest();
|
||||
consumer.accept(request);
|
||||
return get(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the given {@link GetRequest} against the {@literal get} API to retrieve a document by id.
|
||||
*
|
||||
* @param getRequest must not be {@literal null}.
|
||||
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-get.html">Get API on
|
||||
* elastic.co</a>
|
||||
* @return the {@link Mono} emitting the {@link GetResult result}.
|
||||
*/
|
||||
default Mono<GetResult> get(GetRequest getRequest) {
|
||||
return get(HttpHeaders.EMPTY, getRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the given {@link GetRequest} against the {@literal get} API to retrieve a document by id.
|
||||
*
|
||||
* @param headers Use {@link HttpHeaders} to provide eg. authentication data. Must not be {@literal null}.
|
||||
* @param getRequest must not be {@literal null}.
|
||||
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-get.html">Get API on
|
||||
* elastic.co</a>
|
||||
* @return the {@link Mono} emitting the {@link GetResult result}.
|
||||
*/
|
||||
Mono<GetResult> get(HttpHeaders headers, GetRequest getRequest);
|
||||
|
||||
/**
|
||||
* Execute a {@link MultiGetRequest} against the {@literal multi-get} API to retrieve multiple documents by id.
|
||||
*
|
||||
* @param consumer never {@literal null}.
|
||||
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-multi-get.html">Multi Get API on
|
||||
* elastic.co</a>
|
||||
* @return the {@link Flux} emitting the {@link GetResult result}.
|
||||
*/
|
||||
default Flux<GetResult> multiGet(Consumer<MultiGetRequest> consumer) {
|
||||
|
||||
MultiGetRequest request = new MultiGetRequest();
|
||||
consumer.accept(request);
|
||||
return multiGet(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the given {@link MultiGetRequest} against the {@literal multi-get} API to retrieve multiple documents by
|
||||
* id.
|
||||
*
|
||||
* @param multiGetRequest must not be {@literal null}.
|
||||
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-multi-get.html">Multi Get API on
|
||||
* elastic.co</a>
|
||||
* @return the {@link Flux} emitting the {@link GetResult result}.
|
||||
*/
|
||||
default Flux<GetResult> multiGet(MultiGetRequest multiGetRequest) {
|
||||
return multiGet(HttpHeaders.EMPTY, multiGetRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the given {@link MultiGetRequest} against the {@literal multi-get} API to retrieve multiple documents by
|
||||
* id.
|
||||
*
|
||||
* @param headers Use {@link HttpHeaders} to provide eg. authentication data. Must not be {@literal null}.
|
||||
* @param multiGetRequest must not be {@literal null}.
|
||||
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-multi-get.html">Multi Get API on
|
||||
* elastic.co</a>
|
||||
* @return the {@link Flux} emitting the {@link GetResult result}.
|
||||
*/
|
||||
Flux<GetResult> multiGet(HttpHeaders headers, MultiGetRequest multiGetRequest);
|
||||
|
||||
/**
|
||||
* Checks for the existence of a document. Emits {@literal true} if it exists, {@literal false} otherwise.
|
||||
*
|
||||
* @param consumer never {@literal null}.
|
||||
* @return the {@link Mono} emitting {@literal true} if it exists, {@literal false} otherwise.
|
||||
*/
|
||||
default Mono<Boolean> exists(Consumer<GetRequest> consumer) {
|
||||
|
||||
GetRequest request = new GetRequest();
|
||||
consumer.accept(request);
|
||||
return exists(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for the existence of a document. Emits {@literal true} if it exists, {@literal false} otherwise.
|
||||
*
|
||||
* @param getRequest must not be {@literal null}.
|
||||
* @return the {@link Mono} emitting {@literal true} if it exists, {@literal false} otherwise.
|
||||
*/
|
||||
default Mono<Boolean> exists(GetRequest getRequest) {
|
||||
return exists(HttpHeaders.EMPTY, getRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for the existence of a document. Emits {@literal true} if it exists, {@literal false} otherwise.
|
||||
*
|
||||
* @param headers Use {@link HttpHeaders} to provide eg. authentication data. Must not be {@literal null}.
|
||||
* @param getRequest must not be {@literal null}.
|
||||
* @return the {@link Mono} emitting {@literal true} if it exists, {@literal false} otherwise.
|
||||
*/
|
||||
Mono<Boolean> exists(HttpHeaders headers, GetRequest getRequest);
|
||||
|
||||
/**
|
||||
* Execute an {@link IndexRequest} against the {@literal index} API to index a document.
|
||||
*
|
||||
* @param consumer never {@literal null}.
|
||||
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-index.html">Index API on
|
||||
* elastic.co</a>
|
||||
* @return the {@link Mono} emitting the {@link IndexResponse}.
|
||||
*/
|
||||
default Mono<IndexResponse> index(Consumer<IndexRequest> consumer) {
|
||||
|
||||
IndexRequest request = new IndexRequest();
|
||||
consumer.accept(request);
|
||||
return index(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the given {@link IndexRequest} against the {@literal index} API to index a document.
|
||||
*
|
||||
* @param indexRequest must not be {@literal null}.
|
||||
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-index.html">Index API on
|
||||
* elastic.co</a>
|
||||
* @return the {@link Mono} emitting the {@link IndexResponse}.
|
||||
*/
|
||||
default Mono<IndexResponse> index(IndexRequest indexRequest) {
|
||||
return index(HttpHeaders.EMPTY, indexRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the given {@link IndexRequest} against the {@literal index} API to index a document.
|
||||
*
|
||||
* @param headers Use {@link HttpHeaders} to provide eg. authentication data. Must not be {@literal null}.
|
||||
* @param indexRequest must not be {@literal null}.
|
||||
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-index.html">Index API on
|
||||
* elastic.co</a>
|
||||
* @return the {@link Mono} emitting the {@link IndexResponse}.
|
||||
*/
|
||||
Mono<IndexResponse> index(HttpHeaders headers, IndexRequest indexRequest);
|
||||
|
||||
/**
|
||||
* Gain access to index related commands.
|
||||
*
|
||||
* @return access to index related commands.
|
||||
*/
|
||||
Indices indices();
|
||||
|
||||
/**
|
||||
* Execute an {@link UpdateRequest} against the {@literal update} API to alter a document.
|
||||
*
|
||||
* @param consumer never {@literal null}.
|
||||
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-update.html">Update API on
|
||||
* elastic.co</a>
|
||||
* @return the {@link Mono} emitting the {@link UpdateResponse}.
|
||||
*/
|
||||
default Mono<UpdateResponse> update(Consumer<UpdateRequest> consumer) {
|
||||
|
||||
UpdateRequest request = new UpdateRequest();
|
||||
consumer.accept(request);
|
||||
return update(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the given {@link UpdateRequest} against the {@literal update} API to alter a document.
|
||||
*
|
||||
* @param updateRequest must not be {@literal null}.
|
||||
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-update.html">Update API on
|
||||
* elastic.co</a>
|
||||
* @return the {@link Mono} emitting the {@link UpdateResponse}.
|
||||
*/
|
||||
default Mono<UpdateResponse> update(UpdateRequest updateRequest) {
|
||||
return update(HttpHeaders.EMPTY, updateRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the given {@link UpdateRequest} against the {@literal update} API to alter a document.
|
||||
*
|
||||
* @param headers Use {@link HttpHeaders} to provide eg. authentication data. Must not be {@literal null}.
|
||||
* @param updateRequest must not be {@literal null}.
|
||||
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-update.html">Update API on
|
||||
* elastic.co</a>
|
||||
* @return the {@link Mono} emitting the {@link UpdateResponse}.
|
||||
*/
|
||||
Mono<UpdateResponse> update(HttpHeaders headers, UpdateRequest updateRequest);
|
||||
|
||||
/**
|
||||
* Execute a {@link DeleteRequest} against the {@literal delete} API to remove a document.
|
||||
*
|
||||
* @param consumer never {@literal null}.
|
||||
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-delete.html">Delete API on
|
||||
* elastic.co</a>
|
||||
* @return the {@link Mono} emitting the {@link DeleteResponse}.
|
||||
*/
|
||||
default Mono<DeleteResponse> delete(Consumer<DeleteRequest> consumer) {
|
||||
|
||||
DeleteRequest request = new DeleteRequest();
|
||||
consumer.accept(request);
|
||||
return delete(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the given {@link DeleteRequest} against the {@literal delete} API to remove a document.
|
||||
*
|
||||
* @param deleteRequest must not be {@literal null}.
|
||||
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-delete.html">Delete API on
|
||||
* elastic.co</a>
|
||||
* @return the {@link Mono} emitting the {@link DeleteResponse}.
|
||||
*/
|
||||
default Mono<DeleteResponse> delete(DeleteRequest deleteRequest) {
|
||||
return delete(HttpHeaders.EMPTY, deleteRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the given {@link DeleteRequest} against the {@literal delete} API to remove a document.
|
||||
*
|
||||
* @param headers Use {@link HttpHeaders} to provide eg. authentication data. Must not be {@literal null}.
|
||||
* @param deleteRequest must not be {@literal null}.
|
||||
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-delete.html">Delete API on
|
||||
* elastic.co</a>
|
||||
* @return the {@link Mono} emitting the {@link DeleteResponse}.
|
||||
*/
|
||||
Mono<DeleteResponse> delete(HttpHeaders headers, DeleteRequest deleteRequest);
|
||||
|
||||
/**
|
||||
* Execute a {@link SearchRequest} against the {@literal count} API.
|
||||
*
|
||||
* @param consumer new {@literal null}.
|
||||
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-count.html">Count API on
|
||||
* elastic.co</a>
|
||||
* @return the {@link Mono} emitting the count result.
|
||||
* @since 3.2.4
|
||||
*/
|
||||
default Mono<Long> count(Consumer<CountRequest> consumer) {
|
||||
|
||||
CountRequest countRequest = new CountRequest();
|
||||
consumer.accept(countRequest);
|
||||
return count(countRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a {@link SearchRequest} against the {@literal count} API.
|
||||
*
|
||||
* @param countRequest must not be {@literal null}.
|
||||
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-count.html">Count API on
|
||||
* elastic.co</a>
|
||||
* @return the {@link Mono} emitting the count result.
|
||||
* @since 3.2.4
|
||||
*/
|
||||
default Mono<Long> count(CountRequest countRequest) {
|
||||
return count(HttpHeaders.EMPTY, countRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a {@link SearchRequest} against the {@literal count} API.
|
||||
*
|
||||
* @param headers Use {@link HttpHeaders} to provide eg. authentication data. Must not be {@literal null}.
|
||||
* @param countRequest must not be {@literal null}.
|
||||
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-count.html">Count API on
|
||||
* elastic.co</a>
|
||||
* @return the {@link Mono} emitting the count result.
|
||||
* @since 3.2.4
|
||||
*/
|
||||
Mono<Long> count(HttpHeaders headers, CountRequest countRequest);
|
||||
|
||||
/**
|
||||
* Execute a {@link SearchRequest} against the {@literal search} API.
|
||||
*
|
||||
* @param consumer never {@literal null}.
|
||||
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-search.html">Search API on
|
||||
* elastic.co</a>
|
||||
* @return the {@link Flux} emitting {@link SearchHit hits} one by one.
|
||||
*/
|
||||
default Flux<SearchHit> search(Consumer<SearchRequest> consumer) {
|
||||
|
||||
SearchRequest request = new SearchRequest();
|
||||
consumer.accept(request);
|
||||
return search(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the given {@link SearchRequest} against the {@literal search} API.
|
||||
*
|
||||
* @param searchRequest must not be {@literal null}.
|
||||
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-search.html">Search API on
|
||||
* elastic.co</a>
|
||||
* @return the {@link Flux} emitting {@link SearchHit hits} one by one.
|
||||
*/
|
||||
default Flux<SearchHit> search(SearchRequest searchRequest) {
|
||||
return search(HttpHeaders.EMPTY, searchRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the given {@link SearchRequest} against the {@literal search} API.
|
||||
*
|
||||
* @param headers Use {@link HttpHeaders} to provide eg. authentication data. Must not be {@literal null}.
|
||||
* @param searchRequest must not be {@literal null}.
|
||||
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-search.html">Search API on
|
||||
* elastic.co</a>
|
||||
* @return the {@link Flux} emitting {@link SearchHit hits} one by one.
|
||||
*/
|
||||
Flux<SearchHit> search(HttpHeaders headers, SearchRequest searchRequest);
|
||||
|
||||
/**
|
||||
* Execute the given {@link SearchRequest} against the {@literal search scroll} API.
|
||||
*
|
||||
* @param searchRequest must not be {@literal null}.
|
||||
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-scroll.html">Search
|
||||
* Scroll API on elastic.co</a>
|
||||
* @return the {@link Flux} emitting {@link SearchHit hits} one by one.
|
||||
*/
|
||||
default Flux<SearchHit> scroll(SearchRequest searchRequest) {
|
||||
return scroll(HttpHeaders.EMPTY, searchRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the given {@link SearchRequest} against the {@literal search scroll} API. <br />
|
||||
* Scroll keeps track of {@link SearchResponse#getScrollId() scrollIds} returned by the server and provides them when
|
||||
* requesting more results via {@code _search/scroll}. All bound server resources are freed on completion.
|
||||
*
|
||||
* @param headers Use {@link HttpHeaders} to provide eg. authentication data. Must not be {@literal null}.
|
||||
* @param searchRequest must not be {@literal null}.
|
||||
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-scroll.html">Search
|
||||
* Scroll API on elastic.co</a>
|
||||
* @return the {@link Flux} emitting {@link SearchHit hits} one by one.
|
||||
*/
|
||||
Flux<SearchHit> scroll(HttpHeaders headers, SearchRequest searchRequest);
|
||||
|
||||
/**
|
||||
* Execute a {@link DeleteByQueryRequest} against the {@literal delete by query} API.
|
||||
*
|
||||
* @param consumer never {@literal null}.
|
||||
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-delete-by-query.html">Delete By
|
||||
* Query API on elastic.co</a>
|
||||
* @return a {@link Mono} emitting the emitting operation response.
|
||||
*/
|
||||
default Mono<BulkByScrollResponse> deleteBy(Consumer<DeleteByQueryRequest> consumer) {
|
||||
|
||||
DeleteByQueryRequest request = new DeleteByQueryRequest();
|
||||
consumer.accept(request);
|
||||
return deleteBy(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a {@link DeleteByQueryRequest} against the {@literal delete by query} API.
|
||||
*
|
||||
* @param deleteRequest must not be {@literal null}.
|
||||
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-delete-by-query.html">Delete By
|
||||
* Query API on elastic.co</a>
|
||||
* @return a {@link Mono} emitting the emitting operation response.
|
||||
*/
|
||||
default Mono<BulkByScrollResponse> deleteBy(DeleteByQueryRequest deleteRequest) {
|
||||
return deleteBy(HttpHeaders.EMPTY, deleteRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a {@link DeleteByQueryRequest} against the {@literal delete by query} API.
|
||||
*
|
||||
* @param headers Use {@link HttpHeaders} to provide eg. authentication data. Must not be {@literal null}.
|
||||
* @param deleteRequest must not be {@literal null}.
|
||||
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-delete-by-query.html">Delete By
|
||||
* Query API on elastic.co</a>
|
||||
* @return a {@link Mono} emitting operation response.
|
||||
*/
|
||||
Mono<BulkByScrollResponse> deleteBy(HttpHeaders headers, DeleteByQueryRequest deleteRequest);
|
||||
|
||||
/**
|
||||
* Execute a {@link BulkRequest} against the {@literal bulk} API.
|
||||
*
|
||||
* @param consumer never {@literal null}.
|
||||
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-bulk.html">Bulk API on
|
||||
* elastic.co</a>
|
||||
* @return a {@link Mono} emitting the emitting operation response.
|
||||
*/
|
||||
default Mono<BulkResponse> bulk(Consumer<BulkRequest> consumer) {
|
||||
|
||||
BulkRequest request = new BulkRequest();
|
||||
consumer.accept(request);
|
||||
return bulk(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a {@link BulkRequest} against the {@literal bulk} API.
|
||||
*
|
||||
* @param bulkRequest must not be {@literal null}.
|
||||
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-bulk.html">Bulk API on
|
||||
* elastic.co</a>
|
||||
* @return a {@link Mono} emitting the emitting operation response.
|
||||
*/
|
||||
default Mono<BulkResponse> bulk(BulkRequest bulkRequest) {
|
||||
return bulk(HttpHeaders.EMPTY, bulkRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a {@link BulkRequest} against the {@literal bulk} API.
|
||||
*
|
||||
* @param headers Use {@link HttpHeaders} to provide eg. authentication data. Must not be {@literal null}.
|
||||
* @param bulkRequest must not be {@literal null}.
|
||||
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-bulk.html">Bulk API on
|
||||
* elastic.co</a>
|
||||
* @return a {@link Mono} emitting operation response.
|
||||
*/
|
||||
Mono<BulkResponse> bulk(HttpHeaders headers, BulkRequest bulkRequest);
|
||||
|
||||
/**
|
||||
* Compose the actual command/s to run against Elasticsearch using the underlying {@link WebClient connection}.
|
||||
* {@link #execute(ReactiveElasticsearchClientCallback) Execute} selects an active server from the available ones and
|
||||
* retries operations that fail with a {@link ConnectException} on another node if the previously selected one becomes
|
||||
* unavailable.
|
||||
*
|
||||
* @param callback the {@link ReactiveElasticsearchClientCallback callback} wielding the actual command to run.
|
||||
* @return the {@link Mono} emitting the {@link ClientResponse} once subscribed.
|
||||
*/
|
||||
Mono<ClientResponse> execute(ReactiveElasticsearchClientCallback callback);
|
||||
|
||||
/**
|
||||
* Get the current client {@link Status}. <br />
|
||||
* <strong>NOTE</strong> the actual implementation might choose to actively check the current cluster state by pinging
|
||||
* known nodes.
|
||||
*
|
||||
* @return the actual {@link Status} information.
|
||||
*/
|
||||
Mono<Status> status();
|
||||
|
||||
/**
|
||||
* Low level callback interface operating upon {@link WebClient} to send commands towards elasticsearch.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 3.2
|
||||
*/
|
||||
interface ReactiveElasticsearchClientCallback {
|
||||
Mono<ClientResponse> doWithClient(WebClient client);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cumulative client {@link ElasticsearchHost} information.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 3.2
|
||||
*/
|
||||
interface Status {
|
||||
|
||||
/**
|
||||
* Get the collection of known hosts.
|
||||
*
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
Collection<ElasticsearchHost> hosts();
|
||||
|
||||
/**
|
||||
* @return {@literal true} if at least one host is available.
|
||||
*/
|
||||
default boolean isOk() {
|
||||
|
||||
Collection<ElasticsearchHost> hosts = hosts();
|
||||
|
||||
if (CollectionUtils.isEmpty(hosts)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return hosts().stream().anyMatch(ElasticsearchHost::isOnline);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encapsulation of methods for accessing the Indices API.
|
||||
*
|
||||
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/client/java-rest/master/_indices_apis.html">Indices
|
||||
* API</a>.
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
interface Indices {
|
||||
|
||||
/**
|
||||
* Execute the given {@link GetIndexRequest} against the {@literal indices} API.
|
||||
*
|
||||
* @param consumer never {@literal null}.
|
||||
* @return the {@link Mono} emitting {@literal true} if the index exists, {@literal false} otherwise.
|
||||
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-exists.html"> Indices
|
||||
* Exists API on elastic.co</a>
|
||||
*/
|
||||
default Mono<Boolean> existsIndex(Consumer<GetIndexRequest> consumer) {
|
||||
|
||||
GetIndexRequest request = new GetIndexRequest();
|
||||
consumer.accept(request);
|
||||
return existsIndex(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the given {@link GetIndexRequest} against the {@literal indices} API.
|
||||
*
|
||||
* @param getIndexRequest must not be {@literal null}.
|
||||
* @return the {@link Mono} emitting {@literal true} if the index exists, {@literal false} otherwise.
|
||||
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-exists.html"> Indices
|
||||
* Exists API on elastic.co</a>
|
||||
*/
|
||||
default Mono<Boolean> existsIndex(GetIndexRequest getIndexRequest) {
|
||||
return existsIndex(HttpHeaders.EMPTY, getIndexRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the given {@link GetIndexRequest} against the {@literal indices} API.
|
||||
*
|
||||
* @param headers Use {@link HttpHeaders} to provide eg. authentication data. Must not be {@literal null}.
|
||||
* @param getIndexRequest must not be {@literal null}.
|
||||
* @return the {@link Mono} emitting {@literal true} if the index exists, {@literal false} otherwise.
|
||||
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-exists.html"> Indices
|
||||
* Exists API on elastic.co</a>
|
||||
*/
|
||||
Mono<Boolean> existsIndex(HttpHeaders headers, GetIndexRequest getIndexRequest);
|
||||
|
||||
/**
|
||||
* Execute the given {@link DeleteIndexRequest} against the {@literal indices} API.
|
||||
*
|
||||
* @param consumer never {@literal null}.
|
||||
* @return a {@link Mono} signalling operation completion or an {@link Mono#error(Throwable) error} if eg. the index
|
||||
* does not exist.
|
||||
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-delete-index.html"> Indices
|
||||
* Delete API on elastic.co</a>
|
||||
*/
|
||||
default Mono<Void> deleteIndex(Consumer<DeleteIndexRequest> consumer) {
|
||||
|
||||
DeleteIndexRequest request = new DeleteIndexRequest();
|
||||
consumer.accept(request);
|
||||
return deleteIndex(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the given {@link DeleteIndexRequest} against the {@literal indices} API.
|
||||
*
|
||||
* @param deleteIndexRequest must not be {@literal null}.
|
||||
* @return a {@link Mono} signalling operation completion or an {@link Mono#error(Throwable) error} if eg. the index
|
||||
* does not exist.
|
||||
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-delete-index.html"> Indices
|
||||
* Delete API on elastic.co</a>
|
||||
*/
|
||||
default Mono<Void> deleteIndex(DeleteIndexRequest deleteIndexRequest) {
|
||||
return deleteIndex(HttpHeaders.EMPTY, deleteIndexRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the given {@link DeleteIndexRequest} against the {@literal indices} API.
|
||||
*
|
||||
* @param headers Use {@link HttpHeaders} to provide eg. authentication data. Must not be {@literal null}.
|
||||
* @param deleteIndexRequest must not be {@literal null}.
|
||||
* @return a {@link Mono} signalling operation completion or an {@link Mono#error(Throwable) error} if eg. the index
|
||||
* does not exist.
|
||||
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-delete-index.html"> Indices
|
||||
* Delete API on elastic.co</a>
|
||||
*/
|
||||
Mono<Void> deleteIndex(HttpHeaders headers, DeleteIndexRequest deleteIndexRequest);
|
||||
|
||||
/**
|
||||
* Execute the given {@link CreateIndexRequest} against the {@literal indices} API.
|
||||
*
|
||||
* @param consumer never {@literal null}.
|
||||
* @return a {@link Mono} signalling operation completion or an {@link Mono#error(Throwable) error} if eg. the index
|
||||
* already exist.
|
||||
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-create-index.html"> Indices
|
||||
* Create API on elastic.co</a>
|
||||
*/
|
||||
default Mono<Void> createIndex(Consumer<CreateIndexRequest> consumer) {
|
||||
|
||||
CreateIndexRequest request = new CreateIndexRequest();
|
||||
consumer.accept(request);
|
||||
return createIndex(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the given {@link CreateIndexRequest} against the {@literal indices} API.
|
||||
*
|
||||
* @param createIndexRequest must not be {@literal null}.
|
||||
* @return a {@link Mono} signalling operation completion or an {@link Mono#error(Throwable) error} if eg. the index
|
||||
* already exist.
|
||||
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-create-index.html"> Indices
|
||||
* Create API on elastic.co</a>
|
||||
*/
|
||||
default Mono<Void> createIndex(CreateIndexRequest createIndexRequest) {
|
||||
return createIndex(HttpHeaders.EMPTY, createIndexRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the given {@link CreateIndexRequest} against the {@literal indices} API.
|
||||
*
|
||||
* @param headers Use {@link HttpHeaders} to provide eg. authentication data. Must not be {@literal null}.
|
||||
* @param createIndexRequest must not be {@literal null}.
|
||||
* @return a {@link Mono} signalling operation completion or an {@link Mono#error(Throwable) error} if eg. the index
|
||||
* already exist.
|
||||
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-create-index.html"> Indices
|
||||
* Create API on elastic.co</a>
|
||||
*/
|
||||
Mono<Void> createIndex(HttpHeaders headers, CreateIndexRequest createIndexRequest);
|
||||
|
||||
/**
|
||||
* Execute the given {@link OpenIndexRequest} against the {@literal indices} API.
|
||||
*
|
||||
* @param consumer never {@literal null}.
|
||||
* @return a {@link Mono} signalling operation completion or an {@link Mono#error(Throwable) error} if eg. the index
|
||||
* does not exist.
|
||||
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-open-close.html"> Indices
|
||||
* Open API on elastic.co</a>
|
||||
*/
|
||||
default Mono<Void> openIndex(Consumer<OpenIndexRequest> consumer) {
|
||||
|
||||
OpenIndexRequest request = new OpenIndexRequest();
|
||||
consumer.accept(request);
|
||||
return openIndex(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the given {@link OpenIndexRequest} against the {@literal indices} API.
|
||||
*
|
||||
* @param openIndexRequest must not be {@literal null}.
|
||||
* @return a {@link Mono} signalling operation completion or an {@link Mono#error(Throwable) error} if eg. the index
|
||||
* does not exist.
|
||||
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-open-close.html"> Indices
|
||||
* Open API on elastic.co</a>
|
||||
*/
|
||||
default Mono<Void> openIndex(OpenIndexRequest openIndexRequest) {
|
||||
return openIndex(HttpHeaders.EMPTY, openIndexRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the given {@link OpenIndexRequest} against the {@literal indices} API.
|
||||
*
|
||||
* @param headers Use {@link HttpHeaders} to provide eg. authentication data. Must not be {@literal null}.
|
||||
* @param openIndexRequest must not be {@literal null}.
|
||||
* @return a {@link Mono} signalling operation completion or an {@link Mono#error(Throwable) error} if eg. the index
|
||||
* does not exist.
|
||||
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-open-close.html"> Indices
|
||||
* Open API on elastic.co</a>
|
||||
*/
|
||||
Mono<Void> openIndex(HttpHeaders headers, OpenIndexRequest openIndexRequest);
|
||||
|
||||
/**
|
||||
* Execute the given {@link CloseIndexRequest} against the {@literal indices} API.
|
||||
*
|
||||
* @param consumer never {@literal null}.
|
||||
* @return a {@link Mono} signalling operation completion or an {@link Mono#error(Throwable) error} if eg. the index
|
||||
* does not exist.
|
||||
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-open-close.html"> Indices
|
||||
* Close API on elastic.co</a>
|
||||
*/
|
||||
default Mono<Void> closeIndex(Consumer<CloseIndexRequest> consumer) {
|
||||
|
||||
CloseIndexRequest request = new CloseIndexRequest();
|
||||
consumer.accept(request);
|
||||
return closeIndex(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the given {@link CloseIndexRequest} against the {@literal indices} API.
|
||||
*
|
||||
* @param closeIndexRequest must not be {@literal null}.
|
||||
* @return a {@link Mono} signalling operation completion or an {@link Mono#error(Throwable) error} if eg. the index
|
||||
* does not exist.
|
||||
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-open-close.html"> Indices
|
||||
* Close API on elastic.co</a>
|
||||
*/
|
||||
default Mono<Void> closeIndex(CloseIndexRequest closeIndexRequest) {
|
||||
return closeIndex(HttpHeaders.EMPTY, closeIndexRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the given {@link CloseIndexRequest} against the {@literal indices} API.
|
||||
*
|
||||
* @param headers Use {@link HttpHeaders} to provide eg. authentication data. Must not be {@literal null}.
|
||||
* @param closeIndexRequest must not be {@literal null}.
|
||||
* @return a {@link Mono} signalling operation completion or an {@link Mono#error(Throwable) error} if eg. the index
|
||||
* does not exist.
|
||||
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-open-close.html"> Indices
|
||||
* CLose API on elastic.co</a>
|
||||
*/
|
||||
Mono<Void> closeIndex(HttpHeaders headers, CloseIndexRequest closeIndexRequest);
|
||||
|
||||
/**
|
||||
* Execute the given {@link RefreshRequest} against the {@literal indices} API.
|
||||
*
|
||||
* @param consumer never {@literal null}.
|
||||
* @return a {@link Mono} signalling operation completion or an {@link Mono#error(Throwable) error} if eg. the index
|
||||
* does not exist.
|
||||
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-refresh.html"> Indices
|
||||
* Refresh API on elastic.co</a>
|
||||
*/
|
||||
default Mono<Void> refreshIndex(Consumer<RefreshRequest> consumer) {
|
||||
|
||||
RefreshRequest request = new RefreshRequest();
|
||||
consumer.accept(request);
|
||||
return refreshIndex(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the given {@link RefreshRequest} against the {@literal indices} API.
|
||||
*
|
||||
* @param refreshRequest must not be {@literal null}.
|
||||
* @return a {@link Mono} signalling operation completion or an {@link Mono#error(Throwable) error} if eg. the index
|
||||
* does not exist.
|
||||
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-refresh.html"> Indices
|
||||
* Refresh API on elastic.co</a>
|
||||
*/
|
||||
default Mono<Void> refreshIndex(RefreshRequest refreshRequest) {
|
||||
return refreshIndex(HttpHeaders.EMPTY, refreshRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the given {@link RefreshRequest} against the {@literal indices} API.
|
||||
*
|
||||
* @param headers Use {@link HttpHeaders} to provide eg. authentication data. Must not be {@literal null}.
|
||||
* @param refreshRequest must not be {@literal null}.
|
||||
* @return a {@link Mono} signalling operation completion or an {@link Mono#error(Throwable) error} if eg. the index
|
||||
* does not exist.
|
||||
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-refresh.html"> Indices
|
||||
* Refresh API on elastic.co</a>
|
||||
*/
|
||||
Mono<Void> refreshIndex(HttpHeaders headers, RefreshRequest refreshRequest);
|
||||
|
||||
/**
|
||||
* Execute the given {@link PutMappingRequest} against the {@literal indices} API.
|
||||
*
|
||||
* @param consumer never {@literal null}.
|
||||
* @return a {@link Mono} signalling operation completion or an {@link Mono#error(Throwable) error} if eg. the index
|
||||
* does not exist.
|
||||
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-put-mapping.html"> Indices
|
||||
* Put Mapping API on elastic.co</a>
|
||||
*/
|
||||
default Mono<Void> updateMapping(Consumer<PutMappingRequest> consumer) {
|
||||
|
||||
PutMappingRequest request = new PutMappingRequest();
|
||||
consumer.accept(request);
|
||||
return updateMapping(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the given {@link PutMappingRequest} against the {@literal indices} API.
|
||||
*
|
||||
* @param putMappingRequest must not be {@literal null}.
|
||||
* @return a {@link Mono} signalling operation completion or an {@link Mono#error(Throwable) error} if eg. the index
|
||||
* does not exist.
|
||||
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-put-mapping.html"> Indices
|
||||
* Put Mapping API on elastic.co</a>
|
||||
*/
|
||||
default Mono<Void> updateMapping(PutMappingRequest putMappingRequest) {
|
||||
return updateMapping(HttpHeaders.EMPTY, putMappingRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the given {@link PutMappingRequest} against the {@literal indices} API.
|
||||
*
|
||||
* @param headers Use {@link HttpHeaders} to provide eg. authentication data. Must not be {@literal null}.
|
||||
* @param putMappingRequest must not be {@literal null}.
|
||||
* @return a {@link Mono} signalling operation completion or an {@link Mono#error(Throwable) error} if eg. the index
|
||||
* does not exist.
|
||||
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-put-mapping.html"> Indices
|
||||
* Put Mapping API on elastic.co</a>
|
||||
*/
|
||||
Mono<Void> updateMapping(HttpHeaders headers, PutMappingRequest putMappingRequest);
|
||||
|
||||
/**
|
||||
* Execute the given {@link FlushRequest} against the {@literal indices} API.
|
||||
*
|
||||
* @param consumer never {@literal null}.
|
||||
* @return a {@link Mono} signalling operation completion or an {@link Mono#error(Throwable) error} if eg. the index
|
||||
* does not exist.
|
||||
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-flush.html"> Indices Flush
|
||||
* API on elastic.co</a>
|
||||
*/
|
||||
default Mono<Void> flushIndex(Consumer<FlushRequest> consumer) {
|
||||
|
||||
FlushRequest request = new FlushRequest();
|
||||
consumer.accept(request);
|
||||
return flushIndex(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the given {@link RefreshRequest} against the {@literal indices} API.
|
||||
*
|
||||
* @param flushRequest must not be {@literal null}.
|
||||
* @return a {@link Mono} signalling operation completion or an {@link Mono#error(Throwable) error} if eg. the index
|
||||
* does not exist.
|
||||
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-flush.html"> Indices Flush
|
||||
* API on elastic.co</a>
|
||||
*/
|
||||
default Mono<Void> flushIndex(FlushRequest flushRequest) {
|
||||
return flushIndex(HttpHeaders.EMPTY, flushRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the given {@link RefreshRequest} against the {@literal indices} API.
|
||||
*
|
||||
* @param headers Use {@link HttpHeaders} to provide eg. authentication data. Must not be {@literal null}.
|
||||
* @param flushRequest must not be {@literal null}.
|
||||
* @return a {@link Mono} signalling operation completion or an {@link Mono#error(Throwable) error} if eg. the index
|
||||
* does not exist.
|
||||
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-flush.html"> Indices Flush
|
||||
* API on elastic.co</a>
|
||||
*/
|
||||
Mono<Void> flushIndex(HttpHeaders headers, FlushRequest flushRequest);
|
||||
}
|
||||
}
|
||||
-44
@@ -1,44 +0,0 @@
|
||||
/*
|
||||
* Copyright 2018-2020 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.elasticsearch.client.reactive;
|
||||
|
||||
import org.springframework.data.elasticsearch.client.ClientConfiguration;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Utility class for common access to reactive Elasticsearch clients. {@link ReactiveRestClients} consolidates set up
|
||||
* routines for the various drivers into a single place.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
* @since 3.2
|
||||
*/
|
||||
public final class ReactiveRestClients {
|
||||
|
||||
private ReactiveRestClients() {}
|
||||
|
||||
/**
|
||||
* Start here to create a new client tailored to your needs.
|
||||
*
|
||||
* @return new instance of {@link ReactiveElasticsearchClient}.
|
||||
*/
|
||||
public static ReactiveElasticsearchClient create(ClientConfiguration clientConfiguration) {
|
||||
|
||||
Assert.notNull(clientConfiguration, "ClientConfiguration must not be null!");
|
||||
|
||||
return DefaultReactiveElasticsearchClient.create(clientConfiguration);
|
||||
}
|
||||
}
|
||||
-40
@@ -1,40 +0,0 @@
|
||||
/*
|
||||
* Copyright 2018-2020 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.elasticsearch.client.reactive;
|
||||
|
||||
import org.springframework.web.reactive.function.client.WebClientException;
|
||||
|
||||
/**
|
||||
* Exception thrown if the request body cannot be properly encoded.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
* @since 3.2
|
||||
*/
|
||||
public class RequestBodyEncodingException extends WebClientException {
|
||||
|
||||
private static final long serialVersionUID = 472776714118912855L;
|
||||
|
||||
/**
|
||||
* Construct a new instance of {@link RequestBodyEncodingException} with the given message and exception.
|
||||
*
|
||||
* @param msg the message
|
||||
* @param ex the exception
|
||||
*/
|
||||
public RequestBodyEncodingException(String msg, Throwable ex) {
|
||||
super(msg, ex);
|
||||
}
|
||||
}
|
||||
-108
@@ -1,108 +0,0 @@
|
||||
/*
|
||||
* Copyright 2018-2020 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.elasticsearch.client.reactive;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.springframework.data.elasticsearch.client.ElasticsearchHost;
|
||||
import org.springframework.data.elasticsearch.client.ElasticsearchHost.State;
|
||||
import org.springframework.data.elasticsearch.client.NoReachableHostException;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
/**
|
||||
* {@link HostProvider} for a single host.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
* @since 3.2
|
||||
*/
|
||||
class SingleNodeHostProvider implements HostProvider {
|
||||
|
||||
private final WebClientProvider clientProvider;
|
||||
private final InetSocketAddress endpoint;
|
||||
private volatile ElasticsearchHost state;
|
||||
|
||||
SingleNodeHostProvider(WebClientProvider clientProvider, InetSocketAddress endpoint) {
|
||||
|
||||
this.clientProvider = clientProvider;
|
||||
this.endpoint = endpoint;
|
||||
this.state = new ElasticsearchHost(this.endpoint, State.UNKNOWN);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.client.reactive.HostProvider#clusterInfo()
|
||||
*/
|
||||
@Override
|
||||
public Mono<ClusterInformation> clusterInfo() {
|
||||
|
||||
return createWebClient(endpoint) //
|
||||
.head().uri("/").exchange() //
|
||||
.flatMap(it -> {
|
||||
|
||||
if (it.statusCode().isError()) {
|
||||
state = ElasticsearchHost.offline(endpoint);
|
||||
} else {
|
||||
state = ElasticsearchHost.online(endpoint);
|
||||
}
|
||||
return Mono.just(state);
|
||||
}).onErrorResume(throwable -> {
|
||||
|
||||
state = ElasticsearchHost.offline(endpoint);
|
||||
clientProvider.getErrorListener().accept(throwable);
|
||||
return Mono.just(state);
|
||||
}) //
|
||||
.flatMap(it -> Mono.just(new ClusterInformation(Collections.singleton(it))));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.client.reactive.HostProvider#createWebClient(java.net.InetSocketAddress)
|
||||
*/
|
||||
@Override
|
||||
public WebClient createWebClient(InetSocketAddress endpoint) {
|
||||
return this.clientProvider.get(endpoint);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.client.reactive.HostProvider#lookupActiveHost(org.springframework.data.elasticsearch.client.reactive.HostProvider.Verification)
|
||||
*/
|
||||
@Override
|
||||
public Mono<InetSocketAddress> lookupActiveHost(Verification verification) {
|
||||
|
||||
if (Verification.LAZY.equals(verification) && state.isOnline()) {
|
||||
return Mono.just(endpoint);
|
||||
}
|
||||
|
||||
return clusterInfo().flatMap(it -> {
|
||||
|
||||
ElasticsearchHost host = it.getNodes().iterator().next();
|
||||
if (host.isOnline()) {
|
||||
return Mono.just(host.getEndpoint());
|
||||
}
|
||||
|
||||
return Mono.error(() -> new NoReachableHostException(Collections.singleton(host)));
|
||||
});
|
||||
}
|
||||
|
||||
ElasticsearchHost getCachedHostState() {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
-142
@@ -1,142 +0,0 @@
|
||||
/*
|
||||
* Copyright 2018-2020 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.elasticsearch.client.reactive;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.client.reactive.ClientHttpConnector;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
/**
|
||||
* Provider for {@link WebClient}s using a pre-configured {@code scheme}. This class returns {@link WebClient} for a
|
||||
* specific {@link InetSocketAddress endpoint} and encapsulates common configuration aspects of {@link WebClient} so
|
||||
* that code using {@link WebClient} is not required to apply further configuration to the actual client.
|
||||
* <p/>
|
||||
* Client instances are typically cached allowing reuse of pooled connections if configured on the
|
||||
* {@link ClientHttpConnector}.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
* @author Huw Ayling-Miller
|
||||
* @author Peter-Josef Meisch
|
||||
* @since 3.2
|
||||
*/
|
||||
public interface WebClientProvider {
|
||||
|
||||
/**
|
||||
* Creates a new {@link WebClientProvider} using the {@code http} scheme and a default {@link ClientHttpConnector}.
|
||||
*
|
||||
* @return the resulting {@link WebClientProvider}.
|
||||
*/
|
||||
static WebClientProvider http() {
|
||||
return create("http");
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link WebClientProvider} using the given {@code scheme} and a default {@link ClientHttpConnector}.
|
||||
*
|
||||
* @param scheme protocol scheme such as {@literal http} or {@literal https}.
|
||||
* @return the resulting {@link WebClientProvider}.
|
||||
*/
|
||||
static WebClientProvider create(String scheme) {
|
||||
|
||||
Assert.hasText(scheme, "Protocol scheme must not be empty");
|
||||
|
||||
return new DefaultWebClientProvider(scheme, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link WebClientProvider} given {@code scheme} and {@link ClientHttpConnector}.
|
||||
*
|
||||
* @param scheme protocol scheme such as {@literal http} or {@literal https}.
|
||||
* @param connector the HTTP connector to use. Can be {@literal null}.
|
||||
* @return the resulting {@link WebClientProvider}.
|
||||
*/
|
||||
static WebClientProvider create(String scheme, @Nullable ClientHttpConnector connector) {
|
||||
|
||||
Assert.hasText(scheme, "Protocol scheme must not be empty");
|
||||
|
||||
return new DefaultWebClientProvider(scheme, connector);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain the {@link WebClient} configured with {@link #withDefaultHeaders(HttpHeaders) default HTTP headers} and
|
||||
* {@link Consumer} error callback for a given {@link InetSocketAddress endpoint}.
|
||||
*
|
||||
* @return the {@link WebClient} for the given {@link InetSocketAddress endpoint}.
|
||||
*/
|
||||
WebClient get(InetSocketAddress endpoint);
|
||||
|
||||
/**
|
||||
* Obtain the {@link HttpHeaders} to be used by default.
|
||||
*
|
||||
* @return never {@literal null}. {@link HttpHeaders#EMPTY} by default.
|
||||
*/
|
||||
HttpHeaders getDefaultHeaders();
|
||||
|
||||
/**
|
||||
* Obtain the {@link Consumer error listener} to be used;
|
||||
*
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
Consumer<Throwable> getErrorListener();
|
||||
|
||||
/**
|
||||
* Obtain the {@link String pathPrefix} to be used.
|
||||
*
|
||||
* @return the pathPrefix if set.
|
||||
* @since 3.2.4
|
||||
*/
|
||||
String getPathPrefix();
|
||||
|
||||
/**
|
||||
* Create a new instance of {@link WebClientProvider} applying the given headers by default.
|
||||
*
|
||||
* @param headers must not be {@literal null}.
|
||||
* @return new instance of {@link WebClientProvider}.
|
||||
*/
|
||||
WebClientProvider withDefaultHeaders(HttpHeaders headers);
|
||||
|
||||
/**
|
||||
* Create a new instance of {@link WebClientProvider} calling the given {@link Consumer} on error.
|
||||
*
|
||||
* @param errorListener must not be {@literal null}.
|
||||
* @return new instance of {@link WebClientProvider}.
|
||||
*/
|
||||
WebClientProvider withErrorListener(Consumer<Throwable> errorListener);
|
||||
|
||||
/**
|
||||
* Create a new instance of {@link WebClientProvider} where HTTP requests are called with the given path prefix.
|
||||
*
|
||||
* @param pathPrefix Path prefix to add to requests
|
||||
* @return new instance of {@link WebClientProvider}
|
||||
* @since 3.2.4
|
||||
*/
|
||||
WebClientProvider withPathPrefix(String pathPrefix);
|
||||
|
||||
/**
|
||||
* Create a new instance of {@link WebClientProvider} calling the given {@link Function} to configure the {@link WebClient}.
|
||||
* @param webClientConfigurer configuration function
|
||||
* @return new instance of {@link WebClientProvider}
|
||||
* @since 3.2.4
|
||||
*/
|
||||
WebClientProvider withWebClientConfigurer(Function<WebClient, WebClient> webClientConfigurer);
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
/**
|
||||
* Everything required for a Reactive Elasticsearch client.
|
||||
*/
|
||||
@org.springframework.lang.NonNullApi
|
||||
@org.springframework.lang.NonNullFields
|
||||
package org.springframework.data.elasticsearch.client.reactive;
|
||||
-1190
File diff suppressed because it is too large
Load Diff
-49
@@ -1,49 +0,0 @@
|
||||
/*
|
||||
* Copyright 2018-2020 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.elasticsearch.config;
|
||||
|
||||
import org.elasticsearch.client.RestHighLevelClient;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
|
||||
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
* @author Peter-Josef Meisch
|
||||
* @since 3.2
|
||||
* @see ElasticsearchConfigurationSupport
|
||||
*/
|
||||
public abstract class AbstractElasticsearchConfiguration extends ElasticsearchConfigurationSupport {
|
||||
|
||||
/**
|
||||
* Return the {@link RestHighLevelClient} instance used to connect to the cluster. <br />
|
||||
* Annotate with {@link Bean} in case you want to expose a {@link RestHighLevelClient} instance to the
|
||||
* {@link org.springframework.context.ApplicationContext}.
|
||||
*
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
public abstract RestHighLevelClient elasticsearchClient();
|
||||
|
||||
/**
|
||||
* Creates {@link ElasticsearchOperations}.
|
||||
*
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
@Bean(name = {"elasticsearchOperations", "elasticsearchTemplate"})
|
||||
public ElasticsearchOperations elasticsearchOperations() {
|
||||
return new ElasticsearchRestTemplate(elasticsearchClient(), elasticsearchConverter(), resultsMapper());
|
||||
}
|
||||
}
|
||||
-80
@@ -1,80 +0,0 @@
|
||||
/*
|
||||
* Copyright 2018-2020 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.elasticsearch.config;
|
||||
|
||||
import org.elasticsearch.action.support.IndicesOptions;
|
||||
import org.elasticsearch.action.support.WriteRequest.RefreshPolicy;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient;
|
||||
import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations;
|
||||
import org.springframework.data.elasticsearch.core.ReactiveElasticsearchTemplate;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
* @since 3.2
|
||||
* @see ElasticsearchConfigurationSupport
|
||||
*/
|
||||
@Configuration
|
||||
public abstract class AbstractReactiveElasticsearchConfiguration extends ElasticsearchConfigurationSupport {
|
||||
|
||||
/**
|
||||
* Return the {@link ReactiveElasticsearchClient} instance used to connect to the cluster. <br />
|
||||
* Annotate with {@link Bean} in case you want to expose a {@link ReactiveElasticsearchClient} instance to the
|
||||
* {@link org.springframework.context.ApplicationContext}.
|
||||
*
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
public abstract ReactiveElasticsearchClient reactiveElasticsearchClient();
|
||||
|
||||
/**
|
||||
* Creates {@link ReactiveElasticsearchOperations}.
|
||||
*
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
@Bean
|
||||
public ReactiveElasticsearchOperations reactiveElasticsearchTemplate() {
|
||||
|
||||
ReactiveElasticsearchTemplate template = new ReactiveElasticsearchTemplate(reactiveElasticsearchClient(),
|
||||
elasticsearchConverter(), resultsMapper());
|
||||
template.setIndicesOptions(indicesOptions());
|
||||
template.setRefreshPolicy(refreshPolicy());
|
||||
|
||||
return template;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up the write {@link RefreshPolicy}. Default is set to {@link RefreshPolicy#IMMEDIATE}.
|
||||
*
|
||||
* @return {@literal null} to use the server defaults.
|
||||
*/
|
||||
@Nullable
|
||||
protected RefreshPolicy refreshPolicy() {
|
||||
return RefreshPolicy.IMMEDIATE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up the read {@link IndicesOptions}. Default is set to {@link IndicesOptions#strictExpandOpenAndForbidClosed()}.
|
||||
*
|
||||
* @return {@literal null} to use the server defaults.
|
||||
*/
|
||||
@Nullable
|
||||
protected IndicesOptions indicesOptions() {
|
||||
return IndicesOptions.strictExpandOpenAndForbidClosed();
|
||||
}
|
||||
|
||||
}
|
||||
-179
@@ -1,179 +0,0 @@
|
||||
/*
|
||||
* Copyright 2018-2020 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.elasticsearch.config;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.core.type.filter.AnnotationTypeFilter;
|
||||
import org.springframework.data.annotation.Persistent;
|
||||
import org.springframework.data.elasticsearch.annotations.Document;
|
||||
import org.springframework.data.elasticsearch.core.DefaultEntityMapper;
|
||||
import org.springframework.data.elasticsearch.core.DefaultResultMapper;
|
||||
import org.springframework.data.elasticsearch.core.EntityMapper;
|
||||
import org.springframework.data.elasticsearch.core.ResultsMapper;
|
||||
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
|
||||
import org.springframework.data.elasticsearch.core.convert.ElasticsearchCustomConversions;
|
||||
import org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter;
|
||||
import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
* @since 3.2
|
||||
*/
|
||||
@Configuration
|
||||
public class ElasticsearchConfigurationSupport {
|
||||
|
||||
@Bean
|
||||
public ElasticsearchConverter elasticsearchConverter() {
|
||||
return new MappingElasticsearchConverter(elasticsearchMappingContext());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link SimpleElasticsearchMappingContext} equipped with entity classes scanned from the mapping base
|
||||
* package.
|
||||
*
|
||||
* @see #getMappingBasePackages()
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
@Bean
|
||||
@SneakyThrows
|
||||
public SimpleElasticsearchMappingContext elasticsearchMappingContext() {
|
||||
|
||||
SimpleElasticsearchMappingContext mappingContext = new SimpleElasticsearchMappingContext();
|
||||
mappingContext.setInitialEntitySet(getInitialEntitySet());
|
||||
mappingContext.setSimpleTypeHolder(elasticsearchCustomConversions().getSimpleTypeHolder());
|
||||
|
||||
return mappingContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link EntityMapper} used for mapping between the source and domain type. <br />
|
||||
* <strong>Hint</strong>: you can use {@link org.springframework.data.elasticsearch.core.ElasticsearchEntityMapper} as
|
||||
* an alternative to the {@link DefaultEntityMapper}.
|
||||
*
|
||||
* <pre class="code">
|
||||
* ElasticsearchEntityMapper entityMapper = new ElasticsearchEntityMapper(elasticsearchMappingContext(),
|
||||
* new DefaultConversionService());
|
||||
* entityMapper.setConversions(elasticsearchCustomConversions());
|
||||
* </pre>
|
||||
*
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
@Bean
|
||||
public EntityMapper entityMapper() {
|
||||
return new DefaultEntityMapper(elasticsearchMappingContext());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link ResultsMapper} to be used for search responses.
|
||||
*
|
||||
* @see #entityMapper()
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
@Bean
|
||||
public ResultsMapper resultsMapper() {
|
||||
return new DefaultResultMapper(elasticsearchMappingContext(), entityMapper());
|
||||
}
|
||||
|
||||
/**
|
||||
* Register custom {@link Converter}s in a {@link ElasticsearchCustomConversions} object if required.
|
||||
*
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
@Bean
|
||||
public ElasticsearchCustomConversions elasticsearchCustomConversions() {
|
||||
return new ElasticsearchCustomConversions(Collections.emptyList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the base packages to scan for Elasticsearch mapped entities at startup. Will return the package name of the
|
||||
* configuration class' (the concrete class, not this one here) by default. So if you have a
|
||||
* {@code com.acme.AppConfig} extending {@link ElasticsearchConfigurationSupport} the base package will be considered
|
||||
* {@code com.acme} unless the method is overridden to implement alternate behavior.
|
||||
*
|
||||
* @return the base packages to scan for mapped {@link Document} classes or an empty collection to not enable scanning
|
||||
* for entities.
|
||||
*/
|
||||
protected Collection<String> getMappingBasePackages() {
|
||||
|
||||
Package mappingBasePackage = getClass().getPackage();
|
||||
return Collections.singleton(mappingBasePackage == null ? null : mappingBasePackage.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Scans the mapping base package for classes annotated with {@link Document}. By default, it scans for entities in
|
||||
* all packages returned by {@link #getMappingBasePackages()}.
|
||||
*
|
||||
* @see #getMappingBasePackages()
|
||||
* @return never {@literal null}.
|
||||
* @throws ClassNotFoundException
|
||||
*/
|
||||
protected Set<Class<?>> getInitialEntitySet() throws ClassNotFoundException {
|
||||
|
||||
Set<Class<?>> initialEntitySet = new HashSet<>();
|
||||
|
||||
for (String basePackage : getMappingBasePackages()) {
|
||||
initialEntitySet.addAll(scanForEntities(basePackage));
|
||||
}
|
||||
|
||||
return initialEntitySet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scans the given base package for entities, i.e. Elasticsearch specific types annotated with {@link Document} and
|
||||
* {@link Persistent}.
|
||||
*
|
||||
* @param basePackage must not be {@literal null}.
|
||||
* @return never {@literal null}.
|
||||
* @throws ClassNotFoundException
|
||||
*/
|
||||
protected Set<Class<?>> scanForEntities(String basePackage) throws ClassNotFoundException {
|
||||
|
||||
if (!StringUtils.hasText(basePackage)) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
Set<Class<?>> initialEntitySet = new HashSet<Class<?>>();
|
||||
|
||||
if (StringUtils.hasText(basePackage)) {
|
||||
|
||||
ClassPathScanningCandidateComponentProvider componentProvider = new ClassPathScanningCandidateComponentProvider(
|
||||
false);
|
||||
componentProvider.addIncludeFilter(new AnnotationTypeFilter(Document.class));
|
||||
componentProvider.addIncludeFilter(new AnnotationTypeFilter(Persistent.class));
|
||||
|
||||
for (BeanDefinition candidate : componentProvider.findCandidateComponents(basePackage)) {
|
||||
|
||||
initialEntitySet.add(ClassUtils.forName(candidate.getBeanClassName(),
|
||||
AbstractReactiveElasticsearchConfiguration.class.getClassLoader()));
|
||||
}
|
||||
}
|
||||
|
||||
return initialEntitySet;
|
||||
}
|
||||
}
|
||||
+1
-2
@@ -25,8 +25,8 @@ import org.springframework.data.repository.config.RepositoryConfigurationExtensi
|
||||
*
|
||||
* @author Rizwan Idrees
|
||||
* @author Mohsin Husen
|
||||
* @author Don Wellington
|
||||
*/
|
||||
|
||||
public class ElasticsearchNamespaceHandler extends NamespaceHandlerSupport {
|
||||
|
||||
@Override
|
||||
@@ -37,6 +37,5 @@ public class ElasticsearchNamespaceHandler extends NamespaceHandlerSupport {
|
||||
registerBeanDefinitionParser("repositories", parser);
|
||||
registerBeanDefinitionParser("node-client", new NodeClientBeanDefinitionParser());
|
||||
registerBeanDefinitionParser("transport-client", new TransportClientBeanDefinitionParser());
|
||||
registerBeanDefinitionParser("rest-client", new RestClientBeanDefinitionParser());
|
||||
}
|
||||
}
|
||||
|
||||
-47
@@ -1,47 +0,0 @@
|
||||
/*
|
||||
* Copyright 2018-2020 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.elasticsearch.config;
|
||||
|
||||
import org.springframework.beans.factory.support.AbstractBeanDefinition;
|
||||
import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.xml.ParserContext;
|
||||
import org.springframework.data.elasticsearch.client.RestClientFactoryBean;
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
/**
|
||||
* @author Don Wellington
|
||||
*/
|
||||
public class RestClientBeanDefinitionParser extends AbstractBeanDefinitionParser {
|
||||
|
||||
@Override
|
||||
protected AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
|
||||
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(RestClientFactoryBean.class);
|
||||
setConfigurations(element, builder);
|
||||
return getSourcedBeanDefinition(builder, element, parserContext);
|
||||
}
|
||||
|
||||
private void setConfigurations(Element element, BeanDefinitionBuilder builder) {
|
||||
builder.addPropertyValue("hosts", element.getAttribute("hosts"));
|
||||
}
|
||||
|
||||
private AbstractBeanDefinition getSourcedBeanDefinition(BeanDefinitionBuilder builder, Element source,
|
||||
ParserContext context) {
|
||||
AbstractBeanDefinition definition = builder.getBeanDefinition();
|
||||
definition.setSource(context.extractSource(source));
|
||||
return definition;
|
||||
}
|
||||
}
|
||||
-54
@@ -1,54 +0,0 @@
|
||||
package org.springframework.data.elasticsearch.core;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.data.elasticsearch.ElasticsearchException;
|
||||
import org.springframework.data.elasticsearch.annotations.Mapping;
|
||||
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* AbstractElasticsearchTemplate
|
||||
*
|
||||
* @author Sascha Woo
|
||||
*/
|
||||
public abstract class AbstractElasticsearchTemplate {
|
||||
|
||||
static final Integer INDEX_MAX_RESULT_WINDOW = 10_000;
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(AbstractElasticsearchTemplate.class);
|
||||
|
||||
protected ElasticsearchConverter elasticsearchConverter;
|
||||
|
||||
public AbstractElasticsearchTemplate(ElasticsearchConverter elasticsearchConverter) {
|
||||
|
||||
Assert.notNull(elasticsearchConverter, "elasticsearchConverter must not be null.");
|
||||
this.elasticsearchConverter = elasticsearchConverter;
|
||||
}
|
||||
|
||||
protected String buildMapping(Class<?> clazz) {
|
||||
|
||||
// load mapping specified in Mapping annotation if present
|
||||
if (clazz.isAnnotationPresent(Mapping.class)) {
|
||||
String mappingPath = clazz.getAnnotation(Mapping.class).mappingPath();
|
||||
if (!StringUtils.isEmpty(mappingPath)) {
|
||||
String mappings = ResourceUtil.readFileFromClasspath(mappingPath);
|
||||
if (!StringUtils.isEmpty(mappings)) {
|
||||
return mappings;
|
||||
}
|
||||
} else {
|
||||
LOGGER.info("mappingPath in @Mapping has to be defined. Building mappings using @Field");
|
||||
}
|
||||
}
|
||||
|
||||
// build mapping from field annotations
|
||||
try {
|
||||
MappingBuilder mappingBuilder = new MappingBuilder(elasticsearchConverter);
|
||||
return mappingBuilder.buildPropertyMapping(clazz);
|
||||
} catch (Exception e) {
|
||||
throw new ElasticsearchException("Failed to build mapping for " + clazz.getSimpleName(), e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+18
-38
@@ -15,59 +15,39 @@
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.core;
|
||||
|
||||
import org.springframework.data.projection.ProjectionFactory;
|
||||
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.springframework.data.elasticsearch.ElasticsearchException;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* @author Artur Konczak
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public abstract class AbstractResultMapper implements ResultsMapper {
|
||||
|
||||
private final EntityMapper entityMapper;
|
||||
private final ProjectionFactory projectionFactory;
|
||||
private EntityMapper entityMapper;
|
||||
|
||||
/**
|
||||
* Create a new {@link AbstractResultMapper}.
|
||||
*
|
||||
* @param entityMapper must not be {@literal null}.
|
||||
*/
|
||||
public AbstractResultMapper(EntityMapper entityMapper) {
|
||||
this(entityMapper, new SpelAwareProxyProjectionFactory());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link AbstractResultMapper}.
|
||||
*
|
||||
* @param entityMapper must not be {@literal null}.
|
||||
* @param projectionFactory must not be {@literal null}.
|
||||
* @since 3.2
|
||||
*/
|
||||
public AbstractResultMapper(EntityMapper entityMapper, ProjectionFactory projectionFactory) {
|
||||
|
||||
|
||||
Assert.notNull(entityMapper, "EntityMapper must not be null!");
|
||||
Assert.notNull(projectionFactory, "ProjectionFactory must not be null!");
|
||||
|
||||
|
||||
this.entityMapper = entityMapper;
|
||||
this.projectionFactory = projectionFactory;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.ResultsMapper#getEntityMapper()
|
||||
*/
|
||||
public <T> T mapEntity(String source, Class<T> clazz) {
|
||||
if (StringUtils.isEmpty(source)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return entityMapper.mapToObject(source, clazz);
|
||||
} catch (IOException e) {
|
||||
throw new ElasticsearchException("failed to map source [ " + source + "] to class " + clazz.getSimpleName(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public EntityMapper getEntityMapper() {
|
||||
return this.entityMapper;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.ResultsMapper#getProjectionFactory()
|
||||
*/
|
||||
@Override
|
||||
public ProjectionFactory getProjectionFactory() {
|
||||
return this.projectionFactory;
|
||||
}
|
||||
}
|
||||
|
||||
+14
-31
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.core;
|
||||
|
||||
import static org.elasticsearch.index.query.Operator.*;
|
||||
import static org.elasticsearch.index.query.Operator.AND;
|
||||
import static org.elasticsearch.index.query.QueryBuilders.*;
|
||||
import static org.springframework.data.elasticsearch.core.query.Criteria.*;
|
||||
|
||||
@@ -25,9 +25,7 @@ import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
|
||||
import org.apache.lucene.queryparser.flexible.core.util.StringUtils;
|
||||
import org.apache.lucene.queryparser.flexible.standard.QueryParserUtil;
|
||||
import org.elasticsearch.index.query.BoolQueryBuilder;
|
||||
import org.elasticsearch.index.query.QueryBuilder;
|
||||
import org.elasticsearch.index.query.*;
|
||||
import org.springframework.data.elasticsearch.core.query.Criteria;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@@ -41,6 +39,7 @@ import org.springframework.util.Assert;
|
||||
*/
|
||||
class CriteriaQueryProcessor {
|
||||
|
||||
|
||||
QueryBuilder createQueryFromCriteria(Criteria criteria) {
|
||||
if (criteria == null)
|
||||
return null;
|
||||
@@ -105,6 +104,7 @@ class CriteriaQueryProcessor {
|
||||
return query;
|
||||
}
|
||||
|
||||
|
||||
private QueryBuilder createQueryFragmentForCriteria(Criteria chainedCriteria) {
|
||||
if (chainedCriteria.getQueryCriteriaEntries().isEmpty())
|
||||
return null;
|
||||
@@ -131,8 +131,8 @@ class CriteriaQueryProcessor {
|
||||
return query;
|
||||
}
|
||||
|
||||
private QueryBuilder processCriteriaEntry(Criteria.CriteriaEntry entry,
|
||||
/* OperationKey key, Object value,*/ String fieldName) {
|
||||
|
||||
private QueryBuilder processCriteriaEntry(Criteria.CriteriaEntry entry,/* OperationKey key, Object value,*/ String fieldName) {
|
||||
Object value = entry.getValue();
|
||||
if (value == null) {
|
||||
return null;
|
||||
@@ -180,15 +180,17 @@ class CriteriaQueryProcessor {
|
||||
query = fuzzyQuery(fieldName, searchText);
|
||||
break;
|
||||
case IN:
|
||||
if (value instanceof Iterable) {
|
||||
Iterable<?> iterable = (Iterable<?>) value;
|
||||
query = queryStringQuery(orQueryString(iterable)).field(fieldName);
|
||||
query = boolQuery();
|
||||
collection = (Iterable<Object>) value;
|
||||
for (Object item : collection) {
|
||||
((BoolQueryBuilder) query).should(queryStringQuery(item.toString()).field(fieldName));
|
||||
}
|
||||
break;
|
||||
case NOT_IN:
|
||||
if (value instanceof Iterable) {
|
||||
Iterable<?> iterable = (Iterable<?>) value;
|
||||
query = queryStringQuery("NOT(" + orQueryString(iterable) + ')').field(fieldName);
|
||||
query = boolQuery();
|
||||
collection = (Iterable<Object>) value;
|
||||
for (Object item : collection) {
|
||||
((BoolQueryBuilder) query).mustNot(queryStringQuery(item.toString()).field(fieldName));
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -201,23 +203,4 @@ class CriteriaQueryProcessor {
|
||||
}
|
||||
query.boost(boost);
|
||||
}
|
||||
|
||||
private static String orQueryString(Iterable<?> iterable) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
for (Object item : iterable) {
|
||||
|
||||
if (item != null) {
|
||||
|
||||
if (sb.length() > 0) {
|
||||
sb.append(' ');
|
||||
}
|
||||
sb.append('"');
|
||||
sb.append(QueryParserUtil.escape(item.toString()));
|
||||
sb.append('"');
|
||||
}
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,15 +17,12 @@ package org.springframework.data.elasticsearch.core;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.data.annotation.ReadOnlyProperty;
|
||||
import org.springframework.data.elasticsearch.core.geo.CustomGeoModule;
|
||||
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
|
||||
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
|
||||
import org.springframework.data.mapping.MappingException;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@@ -43,7 +40,6 @@ import com.fasterxml.jackson.databind.ser.BeanSerializerModifier;
|
||||
* @author Artur Konczak
|
||||
* @author Petar Tahchiev
|
||||
* @author Oliver Gierke
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public class DefaultEntityMapper implements EntityMapper {
|
||||
|
||||
@@ -56,14 +52,14 @@ public class DefaultEntityMapper implements EntityMapper {
|
||||
*/
|
||||
public DefaultEntityMapper(
|
||||
MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> context) {
|
||||
|
||||
|
||||
Assert.notNull(context, "MappingContext must not be null!");
|
||||
|
||||
objectMapper = new ObjectMapper();
|
||||
|
||||
|
||||
objectMapper.registerModule(new SpringDataElasticsearchModule(context));
|
||||
objectMapper.registerModule(new CustomGeoModule());
|
||||
|
||||
|
||||
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||
objectMapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
|
||||
}
|
||||
@@ -77,20 +73,6 @@ public class DefaultEntityMapper implements EntityMapper {
|
||||
return objectMapper.writeValueAsString(object);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.EntityMapper#mapObject(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public Map<String, Object> mapObject(Object source) {
|
||||
|
||||
try {
|
||||
return objectMapper.readValue(mapToString(source), HashMap.class);
|
||||
} catch (IOException e) {
|
||||
throw new MappingException(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.EntityMapper#mapToObject(java.lang.String, java.lang.Class)
|
||||
@@ -100,20 +82,6 @@ public class DefaultEntityMapper implements EntityMapper {
|
||||
return objectMapper.readValue(source, clazz);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.EntityMapper#readObject(java.util.Map, java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public <T> T readObject (Map<String, Object> source, Class<T> targetType) {
|
||||
|
||||
try {
|
||||
return mapToObject(mapToString(source), targetType);
|
||||
} catch (IOException e) {
|
||||
throw new MappingException(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A simple Jackson module to register the {@link SpringDataSerializerModifier}.
|
||||
*
|
||||
@@ -133,7 +101,7 @@ public class DefaultEntityMapper implements EntityMapper {
|
||||
MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> context) {
|
||||
|
||||
Assert.notNull(context, "MappingContext must not be null!");
|
||||
|
||||
|
||||
setSerializerModifier(new SpringDataSerializerModifier(context));
|
||||
}
|
||||
|
||||
|
||||
+26
-38
@@ -20,6 +20,7 @@ import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import org.elasticsearch.action.get.GetResponse;
|
||||
@@ -28,9 +29,6 @@ import org.elasticsearch.action.get.MultiGetResponse;
|
||||
import org.elasticsearch.action.search.SearchResponse;
|
||||
import org.elasticsearch.common.document.DocumentField;
|
||||
import org.elasticsearch.search.SearchHit;
|
||||
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.support.DefaultConversionService;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.elasticsearch.ElasticsearchException;
|
||||
import org.springframework.data.elasticsearch.annotations.Document;
|
||||
@@ -40,16 +38,13 @@ import org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPa
|
||||
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
|
||||
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
|
||||
import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext;
|
||||
import org.springframework.data.mapping.PersistentPropertyAccessor;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonEncoding;
|
||||
import com.fasterxml.jackson.core.JsonFactory;
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* @author Artur Konczak
|
||||
@@ -60,21 +55,22 @@ import com.fasterxml.jackson.core.JsonGenerator;
|
||||
* @author Mark Paluch
|
||||
* @author Ilkang Na
|
||||
* @author Sascha Woo
|
||||
* @author Christoph Strobl
|
||||
* @author Dmitriy Yakovlev
|
||||
*/
|
||||
public class DefaultResultMapper extends AbstractResultMapper {
|
||||
|
||||
private final MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext;
|
||||
private final ConversionService conversionService = new DefaultConversionService();
|
||||
|
||||
public DefaultResultMapper() {
|
||||
this(new SimpleElasticsearchMappingContext());
|
||||
}
|
||||
|
||||
public DefaultResultMapper(
|
||||
MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext) {
|
||||
this(mappingContext, initEntityMapper(mappingContext));
|
||||
public DefaultResultMapper(MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext) {
|
||||
|
||||
super(new DefaultEntityMapper(mappingContext));
|
||||
|
||||
Assert.notNull(mappingContext, "MappingContext must not be null!");
|
||||
|
||||
this.mappingContext = mappingContext;
|
||||
}
|
||||
|
||||
public DefaultResultMapper(EntityMapper entityMapper) {
|
||||
@@ -83,22 +79,18 @@ public class DefaultResultMapper extends AbstractResultMapper {
|
||||
|
||||
public DefaultResultMapper(
|
||||
MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext,
|
||||
@Nullable EntityMapper entityMapper) {
|
||||
|
||||
super(entityMapper != null ? entityMapper : initEntityMapper(mappingContext));
|
||||
this.mappingContext = mappingContext;
|
||||
}
|
||||
|
||||
private static EntityMapper initEntityMapper(
|
||||
MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext) {
|
||||
|
||||
EntityMapper entityMapper) {
|
||||
|
||||
super(entityMapper);
|
||||
|
||||
Assert.notNull(mappingContext, "MappingContext must not be null!");
|
||||
return new DefaultEntityMapper(mappingContext);
|
||||
|
||||
this.mappingContext = mappingContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> clazz, Pageable pageable) {
|
||||
|
||||
|
||||
long totalHits = response.getHits().getTotalHits();
|
||||
float maxScore = response.getHits().getMaxScore();
|
||||
|
||||
@@ -106,9 +98,8 @@ public class DefaultResultMapper extends AbstractResultMapper {
|
||||
for (SearchHit hit : response.getHits()) {
|
||||
if (hit != null) {
|
||||
T result = null;
|
||||
String hitSourceAsString = hit.getSourceAsString();
|
||||
if (!StringUtils.isEmpty(hitSourceAsString)) {
|
||||
result = mapEntity(hitSourceAsString, clazz);
|
||||
if (!StringUtils.isEmpty(hit.getSourceAsString())) {
|
||||
result = mapEntity(hit.getSourceAsString(), clazz);
|
||||
} else {
|
||||
result = mapEntity(hit.getFields().values(), clazz);
|
||||
}
|
||||
@@ -116,7 +107,7 @@ public class DefaultResultMapper extends AbstractResultMapper {
|
||||
setPersistentEntityId(result, hit.getId(), clazz);
|
||||
setPersistentEntityVersion(result, hit.getVersion(), clazz);
|
||||
setPersistentEntityScore(result, hit.getScore(), clazz);
|
||||
|
||||
|
||||
populateScriptFields(result, hit);
|
||||
results.add(result);
|
||||
}
|
||||
@@ -189,8 +180,8 @@ public class DefaultResultMapper extends AbstractResultMapper {
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> List<T> mapResults(MultiGetResponse responses, Class<T> clazz) {
|
||||
List<T> list = new ArrayList<>();
|
||||
public <T> LinkedList<T> mapResults(MultiGetResponse responses, Class<T> clazz) {
|
||||
LinkedList<T> list = new LinkedList<>();
|
||||
for (MultiGetItemResponse response : responses.getResponses()) {
|
||||
if (!response.isFailed() && response.getResponse().isExists()) {
|
||||
T result = mapEntity(response.getResponse().getSourceAsString(), clazz);
|
||||
@@ -203,26 +194,23 @@ public class DefaultResultMapper extends AbstractResultMapper {
|
||||
}
|
||||
|
||||
private <T> void setPersistentEntityId(T result, String id, Class<T> clazz) {
|
||||
|
||||
|
||||
if (clazz.isAnnotationPresent(Document.class)) {
|
||||
|
||||
|
||||
ElasticsearchPersistentEntity<?> persistentEntity = mappingContext.getRequiredPersistentEntity(clazz);
|
||||
ElasticsearchPersistentProperty idProperty = persistentEntity.getIdProperty();
|
||||
|
||||
PersistentPropertyAccessor<T> accessor = new ConvertingPropertyAccessor<>(
|
||||
persistentEntity.getPropertyAccessor(result), conversionService);
|
||||
|
||||
// Only deal with String because ES generated Ids are strings !
|
||||
if (idProperty != null && idProperty.getType().isAssignableFrom(String.class)) {
|
||||
accessor.setProperty(idProperty, id);
|
||||
persistentEntity.getPropertyAccessor(result).setProperty(idProperty, id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private <T> void setPersistentEntityVersion(T result, long version, Class<T> clazz) {
|
||||
|
||||
|
||||
if (clazz.isAnnotationPresent(Document.class)) {
|
||||
|
||||
|
||||
ElasticsearchPersistentEntity<?> persistentEntity = mappingContext.getPersistentEntity(clazz);
|
||||
ElasticsearchPersistentProperty versionProperty = persistentEntity.getVersionProperty();
|
||||
|
||||
|
||||
-721
@@ -1,721 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019-2020 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.elasticsearch.core;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.core.CollectionFactory;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.support.DefaultConversionService;
|
||||
import org.springframework.core.convert.support.GenericConversionService;
|
||||
import org.springframework.data.convert.CustomConversions;
|
||||
import org.springframework.data.convert.EntityConverter;
|
||||
import org.springframework.data.convert.EntityInstantiator;
|
||||
import org.springframework.data.convert.EntityInstantiators;
|
||||
import org.springframework.data.convert.EntityReader;
|
||||
import org.springframework.data.convert.EntityWriter;
|
||||
import org.springframework.data.elasticsearch.core.convert.ElasticsearchCustomConversions;
|
||||
import org.springframework.data.elasticsearch.core.convert.ElasticsearchTypeMapper;
|
||||
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
|
||||
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
|
||||
import org.springframework.data.mapping.PersistentPropertyAccessor;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
|
||||
import org.springframework.data.mapping.model.PersistentEntityParameterValueProvider;
|
||||
import org.springframework.data.mapping.model.PropertyValueProvider;
|
||||
import org.springframework.data.util.ClassTypeInformation;
|
||||
import org.springframework.data.util.Streamable;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.format.datetime.DateFormatterRegistrar;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.ObjectReader;
|
||||
import com.fasterxml.jackson.databind.ObjectWriter;
|
||||
|
||||
/**
|
||||
* Elasticsearch specific {@link EntityReader} & {@link EntityWriter} implementation based on domain type
|
||||
* {@link ElasticsearchPersistentEntity metadata}.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author Peter-Josef Meisch
|
||||
* @since 3.2
|
||||
*/
|
||||
public class ElasticsearchEntityMapper implements
|
||||
EntityConverter<ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty, Object, Map<String, Object>>,
|
||||
EntityWriter<Object, Map<String, Object>>, EntityReader<Object, Map<String, Object>>, InitializingBean,
|
||||
EntityMapper {
|
||||
|
||||
private final MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext;
|
||||
private final GenericConversionService conversionService;
|
||||
private final ObjectReader objectReader;
|
||||
private final ObjectWriter objectWriter;
|
||||
|
||||
private CustomConversions conversions = new ElasticsearchCustomConversions(Collections.emptyList());
|
||||
private EntityInstantiators instantiators = new EntityInstantiators();
|
||||
|
||||
private ElasticsearchTypeMapper typeMapper;
|
||||
|
||||
public ElasticsearchEntityMapper(
|
||||
MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext,
|
||||
@Nullable GenericConversionService conversionService) {
|
||||
|
||||
this.mappingContext = mappingContext;
|
||||
this.conversionService = conversionService != null ? conversionService : new DefaultConversionService();
|
||||
this.typeMapper = ElasticsearchTypeMapper.create(mappingContext);
|
||||
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
objectReader = objectMapper.readerFor(HashMap.class);
|
||||
objectWriter = objectMapper.writer();
|
||||
}
|
||||
|
||||
// --> GETTERS / SETTERS
|
||||
|
||||
@Override
|
||||
public MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> getMappingContext() {
|
||||
return mappingContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConversionService getConversionService() {
|
||||
return conversionService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link CustomConversions} to be applied during the mapping process. <br />
|
||||
* Conversions are registered after {@link #afterPropertiesSet() bean initialization}.
|
||||
*
|
||||
* @param conversions must not be {@literal null}.
|
||||
*/
|
||||
public void setConversions(CustomConversions conversions) {
|
||||
this.conversions = conversions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link ElasticsearchTypeMapper} to use for reading / writing type hints.
|
||||
*
|
||||
* @param typeMapper must not be {@literal null}.
|
||||
*/
|
||||
public void setTypeMapper(ElasticsearchTypeMapper typeMapper) {
|
||||
this.typeMapper = typeMapper;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
|
||||
*/
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
|
||||
DateFormatterRegistrar.addDateConverters(conversionService);
|
||||
conversions.registerConvertersIn(conversionService);
|
||||
}
|
||||
|
||||
// --> READ
|
||||
|
||||
@Override
|
||||
public <T> T readObject(Map<String, Object> source, Class<T> targetType) {
|
||||
return read(targetType, source);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
@Nullable
|
||||
public <R> R read(Class<R> type, Map<String, Object> source) {
|
||||
return doRead(source, ClassTypeInformation.from((Class<R>) ClassUtils.getUserClass(type)));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Nullable
|
||||
protected <R> R doRead(Map<String, Object> source, TypeInformation<R> typeHint) {
|
||||
|
||||
if (source == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
typeHint = (TypeInformation<R>) typeMapper.readType(source, typeHint);
|
||||
|
||||
if (conversions.hasCustomReadTarget(Map.class, typeHint.getType())) {
|
||||
return conversionService.convert(source, typeHint.getType());
|
||||
}
|
||||
|
||||
if (typeHint.isMap() || ClassTypeInformation.OBJECT.equals(typeHint)) {
|
||||
return (R) source;
|
||||
}
|
||||
|
||||
ElasticsearchPersistentEntity<?> entity = mappingContext.getRequiredPersistentEntity(typeHint);
|
||||
return readEntity(entity, source);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected <R> R readEntity(ElasticsearchPersistentEntity<?> entity, Map<String, Object> source) {
|
||||
|
||||
ElasticsearchPersistentEntity<?> targetEntity = computeClosestEntity(entity, source);
|
||||
|
||||
ElasticsearchPropertyValueProvider propertyValueProvider = new ElasticsearchPropertyValueProvider(
|
||||
new MapValueAccessor(source));
|
||||
|
||||
EntityInstantiator instantiator = instantiators.getInstantiatorFor(targetEntity);
|
||||
|
||||
R instance = (R) instantiator.createInstance(targetEntity,
|
||||
new PersistentEntityParameterValueProvider<>(targetEntity, propertyValueProvider, null));
|
||||
|
||||
return targetEntity.requiresPropertyPopulation() ? readProperties(targetEntity, instance, propertyValueProvider)
|
||||
: instance;
|
||||
}
|
||||
|
||||
protected <R> R readProperties(ElasticsearchPersistentEntity<?> entity, R instance,
|
||||
ElasticsearchPropertyValueProvider valueProvider) {
|
||||
|
||||
PersistentPropertyAccessor<R> accessor = new ConvertingPropertyAccessor<>(entity.getPropertyAccessor(instance),
|
||||
conversionService);
|
||||
|
||||
for (ElasticsearchPersistentProperty prop : entity) {
|
||||
|
||||
if (entity.isConstructorArgument(prop) || prop.isScoreProperty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Object value = valueProvider.getPropertyValue(prop);
|
||||
if (value != null) {
|
||||
accessor.setProperty(prop, valueProvider.getPropertyValue(prop));
|
||||
}
|
||||
}
|
||||
|
||||
return accessor.getBean();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected <R> R readValue(@Nullable Object source, ElasticsearchPersistentProperty property,
|
||||
TypeInformation<R> targetType) {
|
||||
|
||||
if (source == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Class<R> rawType = targetType.getType();
|
||||
if (conversions.hasCustomReadTarget(source.getClass(), rawType)) {
|
||||
return rawType.cast(conversionService.convert(source, rawType));
|
||||
} else if (source instanceof List) {
|
||||
return readCollectionValue((List) source, property, targetType);
|
||||
} else if (source instanceof Map) {
|
||||
return readMapValue((Map<String, Object>) source, property, targetType);
|
||||
}
|
||||
|
||||
return (R) readSimpleValue(source, targetType);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <R> R readMapValue(@Nullable Map<String, Object> source, ElasticsearchPersistentProperty property,
|
||||
TypeInformation<R> targetType) {
|
||||
|
||||
TypeInformation information = typeMapper.readType(source);
|
||||
if (property.isEntity() && !property.isMap() || information != null) {
|
||||
|
||||
ElasticsearchPersistentEntity<?> targetEntity = information != null
|
||||
? mappingContext.getRequiredPersistentEntity(information)
|
||||
: mappingContext.getRequiredPersistentEntity(property);
|
||||
return readEntity(targetEntity, source);
|
||||
}
|
||||
|
||||
Map<String, Object> target = new LinkedHashMap<>();
|
||||
for (Entry<String, Object> entry : source.entrySet()) {
|
||||
|
||||
String entryKey = entry.getKey();
|
||||
Object entryValue = entry.getValue();
|
||||
|
||||
if (entryValue == null) {
|
||||
target.put(entryKey, null);
|
||||
} else if (isSimpleType(entryValue)) {
|
||||
target.put(entryKey,
|
||||
readSimpleValue(entryValue, targetType.isMap() ? targetType.getComponentType() : targetType));
|
||||
} else {
|
||||
|
||||
ElasticsearchPersistentEntity<?> targetEntity = computeGenericValueTypeForRead(property, entryValue);
|
||||
|
||||
if (targetEntity.getTypeInformation().isMap()) {
|
||||
|
||||
Map<String, Object> valueMap = (Map) entryValue;
|
||||
if (typeMapper.containsTypeInformation(valueMap)) {
|
||||
target.put(entryKey, readEntity(targetEntity, (Map) entryValue));
|
||||
} else {
|
||||
target.put(entryKey, readValue(valueMap, property, targetEntity.getTypeInformation()));
|
||||
}
|
||||
|
||||
} else if (targetEntity.getTypeInformation().isCollectionLike()) {
|
||||
target.put(entryKey, readValue(entryValue, property, targetEntity.getTypeInformation().getActualType()));
|
||||
} else {
|
||||
target.put(entryKey, readEntity(targetEntity, (Map) entryValue));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (R) target;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <R> R readCollectionValue(@Nullable List<?> source, ElasticsearchPersistentProperty property,
|
||||
TypeInformation<R> targetType) {
|
||||
|
||||
if (source == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Collection<Object> target = createCollectionForValue(targetType, source.size());
|
||||
|
||||
for (Object value : source) {
|
||||
|
||||
if (value == null) {
|
||||
target.add(null);
|
||||
} else if (isSimpleType(value)) {
|
||||
target.add(
|
||||
readSimpleValue(value, targetType.getComponentType() != null ? targetType.getComponentType() : targetType));
|
||||
} else {
|
||||
|
||||
if (value instanceof List) {
|
||||
target.add(readValue(value, property, property.getTypeInformation().getActualType()));
|
||||
} else if (value instanceof Map) {
|
||||
target
|
||||
.add(readMapValue((Map<String, Object>) value, property, property.getTypeInformation().getActualType()));
|
||||
} else {
|
||||
target.add(readEntity(computeGenericValueTypeForRead(property, value), (Map) value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (R) target;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Object readSimpleValue(@Nullable Object value, TypeInformation<?> targetType) {
|
||||
|
||||
Class<?> target = targetType.getType();
|
||||
|
||||
if (value == null || target == null || ClassUtils.isAssignableValue(target, value)) {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (conversions.hasCustomReadTarget(value.getClass(), target)) {
|
||||
return conversionService.convert(value, target);
|
||||
}
|
||||
|
||||
if (Enum.class.isAssignableFrom(target)) {
|
||||
return Enum.valueOf((Class<Enum>) target, value.toString());
|
||||
}
|
||||
|
||||
return conversionService.convert(value, target);
|
||||
}
|
||||
|
||||
// --> WRITE
|
||||
|
||||
@Override
|
||||
public Map<String, Object> mapObject(Object source) {
|
||||
|
||||
LinkedHashMap<String, Object> target = new LinkedHashMap<>();
|
||||
write(source, target);
|
||||
return target;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public void write(@Nullable Object source, Map<String, Object> sink) {
|
||||
|
||||
if (source == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (source instanceof Map) {
|
||||
|
||||
sink.putAll((Map<String, Object>) source);
|
||||
return;
|
||||
}
|
||||
|
||||
Class<?> entityType = ClassUtils.getUserClass(source.getClass());
|
||||
TypeInformation<?> type = ClassTypeInformation.from(entityType);
|
||||
|
||||
if (requiresTypeHint(type, source.getClass(), null)) {
|
||||
typeMapper.writeType(source.getClass(), sink);
|
||||
}
|
||||
|
||||
doWrite(source, sink, type);
|
||||
}
|
||||
|
||||
protected void doWrite(@Nullable Object source, Map<String, Object> sink,
|
||||
@Nullable TypeInformation<? extends Object> typeHint) {
|
||||
|
||||
if (source == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Class<?> entityType = source.getClass();
|
||||
Optional<Class<?>> customTarget = conversions.getCustomWriteTarget(entityType, Map.class);
|
||||
|
||||
if (customTarget.isPresent()) {
|
||||
|
||||
sink.putAll(conversionService.convert(source, Map.class));
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeHint != null) {
|
||||
|
||||
ElasticsearchPersistentEntity<?> entity = typeHint.getType().equals(entityType)
|
||||
? mappingContext.getRequiredPersistentEntity(typeHint)
|
||||
: mappingContext.getRequiredPersistentEntity(entityType);
|
||||
|
||||
writeEntity(entity, source, sink, null);
|
||||
return;
|
||||
}
|
||||
|
||||
// write Entity
|
||||
ElasticsearchPersistentEntity<?> entity = mappingContext.getRequiredPersistentEntity(entityType);
|
||||
writeEntity(entity, source, sink, null);
|
||||
}
|
||||
|
||||
protected void writeEntity(ElasticsearchPersistentEntity<?> entity, Object source, Map<String, Object> sink,
|
||||
@Nullable TypeInformation containingStructure) {
|
||||
|
||||
PersistentPropertyAccessor<?> accessor = entity.getPropertyAccessor(source);
|
||||
|
||||
if (requiresTypeHint(entity.getTypeInformation(), source.getClass(), containingStructure)) {
|
||||
typeMapper.writeType(source.getClass(), sink);
|
||||
}
|
||||
|
||||
writeProperties(entity, accessor, sink);
|
||||
}
|
||||
|
||||
protected void writeProperties(ElasticsearchPersistentEntity<?> entity, PersistentPropertyAccessor<?> accessor,
|
||||
Map<String, Object> sink) {
|
||||
|
||||
for (ElasticsearchPersistentProperty property : entity) {
|
||||
|
||||
if (!property.isWritable()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Object value = accessor.getProperty(property);
|
||||
|
||||
if (value == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isSimpleType(value)) {
|
||||
writeProperty(property, value, sink);
|
||||
} else {
|
||||
sink.put(property.getFieldName(), getWriteSimpleValue(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void writeProperty(ElasticsearchPersistentProperty property, Object value, Map<String, Object> sink) {
|
||||
|
||||
Optional<Class<?>> customWriteTarget = conversions.getCustomWriteTarget(value.getClass());
|
||||
|
||||
if (customWriteTarget.isPresent()) {
|
||||
|
||||
Class<?> writeTarget = customWriteTarget.get();
|
||||
sink.put(property.getFieldName(), conversionService.convert(value, writeTarget));
|
||||
return;
|
||||
}
|
||||
|
||||
TypeInformation<?> typeHint = property.getTypeInformation();
|
||||
if (typeHint.equals(ClassTypeInformation.OBJECT)) {
|
||||
|
||||
if (value instanceof List) {
|
||||
typeHint = ClassTypeInformation.LIST;
|
||||
} else if (value instanceof Map) {
|
||||
typeHint = ClassTypeInformation.MAP;
|
||||
} else if (value instanceof Set) {
|
||||
typeHint = ClassTypeInformation.SET;
|
||||
} else if (value instanceof Collection) {
|
||||
typeHint = ClassTypeInformation.COLLECTION;
|
||||
}
|
||||
}
|
||||
|
||||
sink.put(property.getFieldName(), getWriteComplexValue(property, typeHint, value));
|
||||
}
|
||||
|
||||
protected Object getWriteSimpleValue(Object value) {
|
||||
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Optional<Class<?>> customTarget = conversions.getCustomWriteTarget(value.getClass());
|
||||
|
||||
if (customTarget.isPresent()) {
|
||||
return conversionService.convert(value, customTarget.get());
|
||||
}
|
||||
|
||||
return Enum.class.isAssignableFrom(value.getClass()) ? ((Enum<?>) value).name() : value;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected Object getWriteComplexValue(ElasticsearchPersistentProperty property, TypeInformation<?> typeHint,
|
||||
Object value) {
|
||||
|
||||
if (typeHint.isCollectionLike() || value instanceof Iterable) {
|
||||
return writeCollectionValue(value, property, typeHint);
|
||||
}
|
||||
if (typeHint.isMap()) {
|
||||
return writeMapValue((Map<String, Object>) value, property, typeHint);
|
||||
}
|
||||
|
||||
if (property.isEntity() || !isSimpleType(value)) {
|
||||
return writeEntity(value, property, typeHint);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
private Object writeEntity(Object value, ElasticsearchPersistentProperty property, TypeInformation<?> typeHint) {
|
||||
Map<String, Object> target = new LinkedHashMap<>();
|
||||
writeEntity(mappingContext.getRequiredPersistentEntity(value.getClass()), value, target,
|
||||
property.getTypeInformation());
|
||||
return target;
|
||||
}
|
||||
|
||||
private Object writeMapValue(Map<String, Object> value, ElasticsearchPersistentProperty property,
|
||||
TypeInformation<?> typeHint) {
|
||||
|
||||
Map<Object, Object> target = new LinkedHashMap<>();
|
||||
Streamable<Entry<String, Object>> mapSource = Streamable.of(value.entrySet());
|
||||
|
||||
if (!typeHint.getActualType().getType().equals(Object.class)
|
||||
&& isSimpleType(typeHint.getMapValueType().getType())) {
|
||||
mapSource.forEach(it -> {
|
||||
|
||||
if (it.getValue() == null) {
|
||||
target.put(it.getKey(), null);
|
||||
} else {
|
||||
target.put(it.getKey(), getWriteSimpleValue(it.getValue()));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
|
||||
mapSource.forEach(it -> {
|
||||
|
||||
Object converted = null;
|
||||
if (it.getValue() != null) {
|
||||
|
||||
if (isSimpleType(it.getValue())) {
|
||||
converted = getWriteSimpleValue(it.getValue());
|
||||
} else {
|
||||
converted = getWriteComplexValue(property, ClassTypeInformation.from(it.getValue().getClass()),
|
||||
it.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
target.put(it.getKey(), converted);
|
||||
});
|
||||
}
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
private Object writeCollectionValue(Object value, ElasticsearchPersistentProperty property,
|
||||
TypeInformation<?> typeHint) {
|
||||
|
||||
Streamable<?> collectionSource = value instanceof Iterable ? Streamable.of((Iterable<?>) value)
|
||||
: Streamable.of(ObjectUtils.toObjectArray(value));
|
||||
|
||||
List<Object> target = new ArrayList<>();
|
||||
if (!typeHint.getActualType().getType().equals(Object.class) && isSimpleType(typeHint.getActualType().getType())) {
|
||||
collectionSource.map(this::getWriteSimpleValue).forEach(target::add);
|
||||
} else {
|
||||
|
||||
collectionSource.map(it -> {
|
||||
|
||||
if (it == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isSimpleType(it)) {
|
||||
return getWriteSimpleValue(it);
|
||||
}
|
||||
|
||||
return getWriteComplexValue(property, ClassTypeInformation.from(it.getClass()), it);
|
||||
}).forEach(target::add);
|
||||
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
// --> LEGACY
|
||||
|
||||
@Override
|
||||
public String mapToString(Object source) throws IOException {
|
||||
|
||||
Map<String, Object> sink = new LinkedHashMap<>();
|
||||
write(source, sink);
|
||||
|
||||
return objectWriter.writeValueAsString(sink);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T mapToObject(String source, Class<T> clazz) throws IOException {
|
||||
return read(clazz, objectReader.readValue(source));
|
||||
}
|
||||
|
||||
// --> PRIVATE HELPERS
|
||||
|
||||
private boolean requiresTypeHint(TypeInformation<?> type, Class<?> actualType,
|
||||
@Nullable TypeInformation<?> container) {
|
||||
|
||||
if (container != null) {
|
||||
|
||||
if (container.isCollectionLike()) {
|
||||
if (type.equals(container.getActualType()) && type.getType().equals(actualType)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (container.isMap()) {
|
||||
if (type.equals(container.getMapValueType()) && type.getType().equals(actualType)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (container.equals(type) && type.getType().equals(actualType)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return !conversions.isSimpleType(type.getType()) && !type.isCollectionLike()
|
||||
&& !conversions.hasCustomWriteTarget(type.getType());
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the type to use by checking the given entity against the store type;
|
||||
*
|
||||
* @param entity
|
||||
* @param source
|
||||
* @return
|
||||
*/
|
||||
private ElasticsearchPersistentEntity<?> computeClosestEntity(ElasticsearchPersistentEntity<?> entity,
|
||||
Map<String, Object> source) {
|
||||
|
||||
TypeInformation<?> typeToUse = typeMapper.readType(source);
|
||||
|
||||
if (typeToUse == null) {
|
||||
return entity;
|
||||
}
|
||||
|
||||
if (!entity.getTypeInformation().getType().isInterface() && !entity.getTypeInformation().isCollectionLike()
|
||||
&& !entity.getTypeInformation().isMap()
|
||||
&& !ClassUtils.isAssignableValue(entity.getType(), typeToUse.getType())) {
|
||||
return entity;
|
||||
}
|
||||
|
||||
return mappingContext.getRequiredPersistentEntity(typeToUse);
|
||||
}
|
||||
|
||||
private ElasticsearchPersistentEntity<?> computeGenericValueTypeForRead(ElasticsearchPersistentProperty property,
|
||||
Object value) {
|
||||
|
||||
return ClassTypeInformation.OBJECT.equals(property.getTypeInformation().getActualType())
|
||||
? mappingContext.getRequiredPersistentEntity(value.getClass())
|
||||
: mappingContext.getRequiredPersistentEntity(property.getTypeInformation().getActualType());
|
||||
}
|
||||
|
||||
private Collection<Object> createCollectionForValue(TypeInformation<?> collectionTypeInformation, int size) {
|
||||
|
||||
Class<?> collectionType = collectionTypeInformation.isSubTypeOf(Collection.class) //
|
||||
? collectionTypeInformation.getType() //
|
||||
: List.class;
|
||||
|
||||
TypeInformation<?> componentType = collectionTypeInformation.getComponentType() != null //
|
||||
? collectionTypeInformation.getComponentType() //
|
||||
: ClassTypeInformation.OBJECT;
|
||||
|
||||
return collectionTypeInformation.getType().isArray() //
|
||||
? new ArrayList<>(size) //
|
||||
: CollectionFactory.createCollection(collectionType, componentType.getType(), size);
|
||||
}
|
||||
|
||||
private boolean isSimpleType(Object value) {
|
||||
return isSimpleType(value.getClass());
|
||||
}
|
||||
|
||||
private boolean isSimpleType(Class<?> type) {
|
||||
return conversions.isSimpleType(type);
|
||||
}
|
||||
|
||||
// --> OHTER STUFF
|
||||
|
||||
@RequiredArgsConstructor
|
||||
class ElasticsearchPropertyValueProvider implements PropertyValueProvider<ElasticsearchPersistentProperty> {
|
||||
|
||||
final MapValueAccessor mapValueAccessor;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public <T> T getPropertyValue(ElasticsearchPersistentProperty property) {
|
||||
return (T) readValue(mapValueAccessor.get(property), property, property.getTypeInformation());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class MapValueAccessor {
|
||||
|
||||
final Map<String, Object> target;
|
||||
|
||||
MapValueAccessor(Map<String, Object> target) {
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
public Object get(ElasticsearchPersistentProperty property) {
|
||||
|
||||
String fieldName = property.getFieldName();
|
||||
|
||||
if (!fieldName.contains(".")) {
|
||||
return target.get(fieldName);
|
||||
}
|
||||
|
||||
Iterator<String> parts = Arrays.asList(fieldName.split("\\.")).iterator();
|
||||
Map<String, Object> source = target;
|
||||
Object result = null;
|
||||
|
||||
while (source != null && parts.hasNext()) {
|
||||
|
||||
result = source.get(parts.next());
|
||||
|
||||
if (parts.hasNext()) {
|
||||
source = getAsMap(result);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Map<String, Object> getAsMap(Object result) {
|
||||
|
||||
if (result instanceof Map) {
|
||||
return (Map) result;
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException(String.format("%s is not a Map.", result));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
-68
@@ -1,68 +0,0 @@
|
||||
/*
|
||||
* Copyright 2018-2020 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.elasticsearch.core;
|
||||
|
||||
import java.net.ConnectException;
|
||||
import java.util.List;
|
||||
|
||||
import org.elasticsearch.ElasticsearchException;
|
||||
import org.elasticsearch.ElasticsearchStatusException;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.dao.DataAccessResourceFailureException;
|
||||
import org.springframework.dao.support.PersistenceExceptionTranslator;
|
||||
import org.springframework.data.elasticsearch.NoSuchIndexException;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
* @since 3.2
|
||||
*/
|
||||
public class ElasticsearchExceptionTranslator implements PersistenceExceptionTranslator {
|
||||
|
||||
@Override
|
||||
public DataAccessException translateExceptionIfPossible(RuntimeException ex) {
|
||||
|
||||
if (ex instanceof ElasticsearchException) {
|
||||
|
||||
ElasticsearchException elasticsearchException = (ElasticsearchException) ex;
|
||||
|
||||
if (!indexAvailable(elasticsearchException)) {
|
||||
return new NoSuchIndexException(ObjectUtils.nullSafeToString(elasticsearchException.getMetadata("es.index")),
|
||||
ex);
|
||||
}
|
||||
}
|
||||
|
||||
if (ex.getCause() instanceof ConnectException) {
|
||||
return new DataAccessResourceFailureException(ex.getMessage(), ex);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean indexAvailable(ElasticsearchException ex) {
|
||||
|
||||
List<String> metadata = ex.getMetadata("es.index_uuid");
|
||||
if (metadata == null) {
|
||||
if (ex instanceof ElasticsearchStatusException) {
|
||||
return StringUtils.hasText(ObjectUtils.nullSafeToString(((ElasticsearchStatusException) ex).getIndex()));
|
||||
}
|
||||
}
|
||||
return !CollectionUtils.contains(metadata.iterator(), "_na_");
|
||||
}
|
||||
}
|
||||
+67
-167
@@ -15,27 +15,19 @@
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.core;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.elasticsearch.action.update.UpdateResponse;
|
||||
import org.elasticsearch.client.Client;
|
||||
import org.elasticsearch.cluster.metadata.AliasMetaData;
|
||||
import org.elasticsearch.common.Nullable;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
|
||||
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
|
||||
import org.springframework.data.elasticsearch.core.query.AliasQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.BulkOptions;
|
||||
import org.springframework.data.elasticsearch.core.query.CriteriaQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.DeleteQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.GetQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.IndexQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.MoreLikeThisQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.SearchQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.StringQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.UpdateQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.*;
|
||||
import org.springframework.data.util.CloseableIterator;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* ElasticsearchOperations
|
||||
@@ -43,27 +35,18 @@ import org.springframework.lang.Nullable;
|
||||
* @author Rizwan Idrees
|
||||
* @author Mohsin Husen
|
||||
* @author Kevin Leturc
|
||||
* @author Zetang Zeng
|
||||
* @author Dmitriy Yakovlev
|
||||
* @author Peter-Josef Meisch
|
||||
*/
|
||||
public interface ElasticsearchOperations {
|
||||
|
||||
/**
|
||||
* adding new alias
|
||||
*
|
||||
* @param query
|
||||
* @return
|
||||
* @return Converter in use
|
||||
*/
|
||||
boolean addAlias(AliasQuery query);
|
||||
ElasticsearchConverter getElasticsearchConverter();
|
||||
|
||||
/**
|
||||
* removing previously created alias
|
||||
*
|
||||
* @param query
|
||||
* @return
|
||||
* @return elasticsearch client
|
||||
*/
|
||||
boolean removeAlias(AliasQuery query);
|
||||
Client getClient();
|
||||
|
||||
/**
|
||||
* Create an index for a class
|
||||
@@ -104,16 +87,6 @@ public interface ElasticsearchOperations {
|
||||
*/
|
||||
<T> boolean putMapping(Class<T> clazz);
|
||||
|
||||
/**
|
||||
* Create mapping for the given class and put the mapping to the given indexName and type.
|
||||
*
|
||||
* @param indexName
|
||||
* @param type
|
||||
* @param mappings
|
||||
* @since 3.2
|
||||
*/
|
||||
<T> boolean putMapping(String indexName, String type, Class<T> clazz);
|
||||
|
||||
/**
|
||||
* Create mapping for a given indexName and type
|
||||
*
|
||||
@@ -131,13 +104,14 @@ public interface ElasticsearchOperations {
|
||||
*/
|
||||
<T> boolean putMapping(Class<T> clazz, Object mappings);
|
||||
|
||||
|
||||
/**
|
||||
* Get mapping for a class
|
||||
*
|
||||
* @param clazz
|
||||
* @param <T>
|
||||
*/
|
||||
<T> Map<String, Object> getMapping(Class<T> clazz);
|
||||
<T> Map getMapping(Class<T> clazz);
|
||||
|
||||
/**
|
||||
* Get mapping for a given indexName and type
|
||||
@@ -145,31 +119,22 @@ public interface ElasticsearchOperations {
|
||||
* @param indexName
|
||||
* @param type
|
||||
*/
|
||||
Map<String, Object> getMapping(String indexName, String type);
|
||||
Map getMapping(String indexName, String type);
|
||||
|
||||
/**
|
||||
* Get settings for a given indexName
|
||||
*
|
||||
* @param indexName
|
||||
*/
|
||||
Map<String, Object> getSetting(String indexName);
|
||||
Map getSetting(String indexName);
|
||||
|
||||
/**
|
||||
* Get settings for a given class
|
||||
*
|
||||
* @param clazz
|
||||
*/
|
||||
<T> Map<String, Object> getSetting(Class<T> clazz);
|
||||
<T> Map getSetting(Class<T> clazz);
|
||||
|
||||
/**
|
||||
* get all the alias pointing to specified index
|
||||
*
|
||||
* @param indexName
|
||||
* @return
|
||||
*/
|
||||
List<AliasMetaData> queryForAlias(String indexName);
|
||||
|
||||
<T> T query(SearchQuery query, ResultsExtractor<T> resultsExtractor);
|
||||
|
||||
/**
|
||||
* Execute the query against elasticsearch and return the first returned object
|
||||
@@ -226,44 +191,6 @@ public interface ElasticsearchOperations {
|
||||
*/
|
||||
<T> Page<T> queryForPage(SearchQuery query, Class<T> clazz, SearchResultMapper mapper);
|
||||
|
||||
/**
|
||||
* Execute the multi-search against elasticsearch and return result as {@link List} of {@link Page}
|
||||
*
|
||||
* @param queries
|
||||
* @param clazz
|
||||
* @return
|
||||
*/
|
||||
<T> List<Page<T>> queryForPage(List<SearchQuery> queries, Class<T> clazz);
|
||||
|
||||
/**
|
||||
* Execute the multi-search against elasticsearch and return result as {@link List} of {@link Page} using custom
|
||||
* mapper
|
||||
*
|
||||
* @param queries
|
||||
* @param clazz
|
||||
* @return
|
||||
*/
|
||||
<T> List<Page<T>> queryForPage(List<SearchQuery> queries, Class<T> clazz, SearchResultMapper mapper);
|
||||
|
||||
/**
|
||||
* Execute the multi-search against elasticsearch and return result as {@link List} of {@link Page}
|
||||
*
|
||||
* @param queries
|
||||
* @param classes
|
||||
* @return
|
||||
*/
|
||||
List<Page<?>> queryForPage(List<SearchQuery> queries, List<Class<?>> classes);
|
||||
|
||||
/**
|
||||
* Execute the multi-search against elasticsearch and return result as {@link List} of {@link Page} using custom
|
||||
* mapper
|
||||
*
|
||||
* @param queries
|
||||
* @param classes
|
||||
* @return
|
||||
*/
|
||||
List<Page<?>> queryForPage(List<SearchQuery> queries, List<Class<?>> classes, SearchResultMapper mapper);
|
||||
|
||||
/**
|
||||
* Execute the query against elasticsearch and return result as {@link Page}
|
||||
*
|
||||
@@ -294,8 +221,7 @@ public interface ElasticsearchOperations {
|
||||
/**
|
||||
* Executes the given {@link CriteriaQuery} against elasticsearch and return result as {@link CloseableIterator}.
|
||||
* <p>
|
||||
* Returns a {@link CloseableIterator} that wraps an Elasticsearch scroll context that needs to be closed in case of
|
||||
* error.
|
||||
* Returns a {@link CloseableIterator} that wraps an Elasticsearch scroll context that needs to be closed in case of error.
|
||||
*
|
||||
* @param <T> element return type
|
||||
* @param query
|
||||
@@ -308,8 +234,7 @@ public interface ElasticsearchOperations {
|
||||
/**
|
||||
* Executes the given {@link SearchQuery} against elasticsearch and return result as {@link CloseableIterator}.
|
||||
* <p>
|
||||
* Returns a {@link CloseableIterator} that wraps an Elasticsearch scroll context that needs to be closed in case of
|
||||
* error.
|
||||
* Returns a {@link CloseableIterator} that wraps an Elasticsearch scroll context that needs to be closed in case of error.
|
||||
*
|
||||
* @param <T> element return type
|
||||
* @param query
|
||||
@@ -320,11 +245,9 @@ public interface ElasticsearchOperations {
|
||||
<T> CloseableIterator<T> stream(SearchQuery query, Class<T> clazz);
|
||||
|
||||
/**
|
||||
* Executes the given {@link SearchQuery} against elasticsearch and return result as {@link CloseableIterator} using
|
||||
* custom mapper.
|
||||
* Executes the given {@link SearchQuery} against elasticsearch and return result as {@link CloseableIterator} using custom mapper.
|
||||
* <p>
|
||||
* Returns a {@link CloseableIterator} that wraps an Elasticsearch scroll context that needs to be closed in case of
|
||||
* error.
|
||||
* Returns a {@link CloseableIterator} that wraps an Elasticsearch scroll context that needs to be closed in case of error.
|
||||
*
|
||||
* @param <T> element return type
|
||||
* @param query
|
||||
@@ -365,29 +288,6 @@ public interface ElasticsearchOperations {
|
||||
*/
|
||||
<T> List<T> queryForList(SearchQuery query, Class<T> clazz);
|
||||
|
||||
/**
|
||||
* Execute the multi search query against elasticsearch and return result as {@link List}
|
||||
*
|
||||
* @param queries
|
||||
* @param clazz
|
||||
* @param <T>
|
||||
* @return
|
||||
*/
|
||||
default <T> List<List<T>> queryForList(List<SearchQuery> queries, Class<T> clazz) {
|
||||
return queryForPage(queries, clazz).stream().map(Page::getContent).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the multi search query against elasticsearch and return result as {@link List}
|
||||
*
|
||||
* @param queries
|
||||
* @param classes
|
||||
* @return
|
||||
*/
|
||||
default List<List<?>> queryForList(List<SearchQuery> queries, List<Class<?>> classes) {
|
||||
return queryForPage(queries, classes).stream().map(Page::getContent).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the query against elasticsearch and return ids
|
||||
*
|
||||
@@ -437,7 +337,7 @@ public interface ElasticsearchOperations {
|
||||
* @param clazz
|
||||
* @return
|
||||
*/
|
||||
<T> List<T> multiGet(SearchQuery searchQuery, Class<T> clazz);
|
||||
<T> LinkedList<T> multiGet(SearchQuery searchQuery, Class<T> clazz);
|
||||
|
||||
/**
|
||||
* Execute a multiGet against elasticsearch for the given ids with MultiGetResultMapper
|
||||
@@ -447,7 +347,7 @@ public interface ElasticsearchOperations {
|
||||
* @param multiGetResultMapper
|
||||
* @return
|
||||
*/
|
||||
<T> List<T> multiGet(SearchQuery searchQuery, Class<T> clazz, MultiGetResultMapper multiGetResultMapper);
|
||||
<T> LinkedList<T> multiGet(SearchQuery searchQuery, Class<T> clazz, MultiGetResultMapper multiGetResultMapper);
|
||||
|
||||
/**
|
||||
* Index an object. Will do save or update
|
||||
@@ -466,40 +366,18 @@ public interface ElasticsearchOperations {
|
||||
UpdateResponse update(UpdateQuery updateQuery);
|
||||
|
||||
/**
|
||||
* Bulk index all objects. Will do save or update.
|
||||
* Bulk index all objects. Will do save or update
|
||||
*
|
||||
* @param queries the queries to execute in bulk
|
||||
* @param queries
|
||||
*/
|
||||
default void bulkIndex(List<IndexQuery> queries) {
|
||||
bulkIndex(queries, BulkOptions.defaultOptions());
|
||||
}
|
||||
|
||||
/**
|
||||
* Bulk index all objects. Will do save or update.
|
||||
*
|
||||
* @param queries the queries to execute in bulk
|
||||
* @param bulkOptions options to be added to the bulk request
|
||||
* @since 3.2
|
||||
*/
|
||||
void bulkIndex(List<IndexQuery> queries, BulkOptions bulkOptions);
|
||||
void bulkIndex(List<IndexQuery> queries);
|
||||
|
||||
/**
|
||||
* Bulk update all objects. Will do update
|
||||
*
|
||||
* @param queries the queries to execute in bulk
|
||||
* @param queries
|
||||
*/
|
||||
default void bulkUpdate(List<UpdateQuery> queries) {
|
||||
bulkUpdate(queries, BulkOptions.defaultOptions());
|
||||
}
|
||||
|
||||
/**
|
||||
* Bulk update all objects. Will do update
|
||||
*
|
||||
* @param queries the queries to execute in bulk
|
||||
* @param bulkOptions options to be added to the bulk request
|
||||
* @since 3.2
|
||||
*/
|
||||
void bulkUpdate(List<UpdateQuery> queries, BulkOptions bulkOptions);
|
||||
void bulkUpdate(List<UpdateQuery> queries);
|
||||
|
||||
/**
|
||||
* Delete the one object with provided id
|
||||
@@ -511,6 +389,7 @@ public interface ElasticsearchOperations {
|
||||
*/
|
||||
String delete(String indexName, String type, String id);
|
||||
|
||||
|
||||
/**
|
||||
* Delete all records matching the criteria
|
||||
*
|
||||
@@ -518,7 +397,6 @@ public interface ElasticsearchOperations {
|
||||
* @param criteriaQuery
|
||||
*/
|
||||
<T> void delete(CriteriaQuery criteriaQuery, Class<T> clazz);
|
||||
|
||||
/**
|
||||
* Delete the one object with provided id
|
||||
*
|
||||
@@ -590,6 +468,7 @@ public interface ElasticsearchOperations {
|
||||
* refresh the index
|
||||
*
|
||||
* @param indexName
|
||||
*
|
||||
*/
|
||||
void refresh(String indexName);
|
||||
|
||||
@@ -597,6 +476,7 @@ public interface ElasticsearchOperations {
|
||||
* refresh the index
|
||||
*
|
||||
* @param clazz
|
||||
*
|
||||
*/
|
||||
<T> void refresh(Class<T> clazz);
|
||||
|
||||
@@ -605,56 +485,53 @@ public interface ElasticsearchOperations {
|
||||
*
|
||||
* @param query The search query.
|
||||
* @param scrollTimeInMillis The time in millisecond for scroll feature
|
||||
* {@link org.elasticsearch.action.search.SearchRequestBuilder#setScroll(org.elasticsearch.common.unit.TimeValue)}.
|
||||
* {@link org.elasticsearch.action.search.SearchRequestBuilder#setScroll(org.elasticsearch.common.unit.TimeValue)}.
|
||||
* @param clazz The class of entity to retrieve.
|
||||
* @return The scan id for input query.
|
||||
*/
|
||||
<T> ScrolledPage<T> startScroll(long scrollTimeInMillis, SearchQuery query, Class<T> clazz);
|
||||
<T> Page<T> startScroll(long scrollTimeInMillis, SearchQuery query, Class<T> clazz);
|
||||
|
||||
/**
|
||||
* Returns scrolled page for given query
|
||||
*
|
||||
* @param query The search query.
|
||||
* @param scrollTimeInMillis The time in millisecond for scroll feature
|
||||
* {@link org.elasticsearch.action.search.SearchRequestBuilder#setScroll(org.elasticsearch.common.unit.TimeValue)}.
|
||||
* {@link org.elasticsearch.action.search.SearchRequestBuilder#setScroll(org.elasticsearch.common.unit.TimeValue)}.
|
||||
* @param mapper Custom impl to map result to entities
|
||||
* @return The scan id for input query.
|
||||
*/
|
||||
<T> ScrolledPage<T> startScroll(long scrollTimeInMillis, SearchQuery query, Class<T> clazz,
|
||||
SearchResultMapper mapper);
|
||||
<T> Page<T> startScroll(long scrollTimeInMillis, SearchQuery query, Class<T> clazz, SearchResultMapper mapper);
|
||||
|
||||
/**
|
||||
* Returns scrolled page for given query
|
||||
*
|
||||
* @param criteriaQuery The search query.
|
||||
* @param scrollTimeInMillis The time in millisecond for scroll feature
|
||||
* {@link org.elasticsearch.action.search.SearchRequestBuilder#setScroll(org.elasticsearch.common.unit.TimeValue)}.
|
||||
* {@link org.elasticsearch.action.search.SearchRequestBuilder#setScroll(org.elasticsearch.common.unit.TimeValue)}.
|
||||
* @param clazz The class of entity to retrieve.
|
||||
* @return The scan id for input query.
|
||||
*/
|
||||
<T> ScrolledPage<T> startScroll(long scrollTimeInMillis, CriteriaQuery criteriaQuery, Class<T> clazz);
|
||||
<T> Page<T> startScroll(long scrollTimeInMillis, CriteriaQuery criteriaQuery, Class<T> clazz);
|
||||
|
||||
/**
|
||||
* Returns scrolled page for given query
|
||||
*
|
||||
* @param criteriaQuery The search query.
|
||||
* @param scrollTimeInMillis The time in millisecond for scroll feature
|
||||
* {@link org.elasticsearch.action.search.SearchRequestBuilder#setScroll(org.elasticsearch.common.unit.TimeValue)}.
|
||||
* {@link org.elasticsearch.action.search.SearchRequestBuilder#setScroll(org.elasticsearch.common.unit.TimeValue)}.
|
||||
* @param mapper Custom impl to map result to entities
|
||||
* @return The scan id for input query.
|
||||
*/
|
||||
<T> ScrolledPage<T> startScroll(long scrollTimeInMillis, CriteriaQuery criteriaQuery, Class<T> clazz,
|
||||
SearchResultMapper mapper);
|
||||
<T> Page<T> startScroll(long scrollTimeInMillis, CriteriaQuery criteriaQuery, Class<T> clazz, SearchResultMapper mapper);
|
||||
|
||||
<T> ScrolledPage<T> continueScroll(@Nullable String scrollId, long scrollTimeInMillis, Class<T> clazz);
|
||||
|
||||
<T> ScrolledPage<T> continueScroll(@Nullable String scrollId, long scrollTimeInMillis, Class<T> clazz,
|
||||
SearchResultMapper mapper);
|
||||
|
||||
<T> Page<T> continueScroll(@Nullable String scrollId, long scrollTimeInMillis, Class<T> clazz);
|
||||
<T> Page<T> continueScroll(@Nullable String scrollId, long scrollTimeInMillis, Class<T> clazz, SearchResultMapper mapper);
|
||||
/**
|
||||
* Clears the search contexts associated with specified scroll ids.
|
||||
*
|
||||
* @param scrollId
|
||||
*
|
||||
*/
|
||||
<T> void clearScroll(String scrollId);
|
||||
|
||||
@@ -668,10 +545,33 @@ public interface ElasticsearchOperations {
|
||||
*/
|
||||
<T> Page<T> moreLikeThis(MoreLikeThisQuery query, Class<T> clazz);
|
||||
|
||||
ElasticsearchPersistentEntity getPersistentEntityFor(Class clazz);
|
||||
/**
|
||||
* adding new alias
|
||||
*
|
||||
* @param query
|
||||
* @return
|
||||
*/
|
||||
Boolean addAlias(AliasQuery query);
|
||||
|
||||
/**
|
||||
* @return Converter in use
|
||||
* removing previously created alias
|
||||
*
|
||||
* @param query
|
||||
* @return
|
||||
*/
|
||||
ElasticsearchConverter getElasticsearchConverter();
|
||||
Boolean removeAlias(AliasQuery query);
|
||||
|
||||
/**
|
||||
* get all the alias pointing to specified index
|
||||
*
|
||||
* @param indexName
|
||||
* @return
|
||||
*/
|
||||
List<AliasMetaData> queryForAlias(String indexName);
|
||||
|
||||
|
||||
<T> T query(SearchQuery query, ResultsExtractor<T> resultsExtractor);
|
||||
|
||||
|
||||
ElasticsearchPersistentEntity getPersistentEntityFor(Class clazz);
|
||||
}
|
||||
|
||||
-1653
File diff suppressed because it is too large
Load Diff
+284
-355
File diff suppressed because it is too large
Load Diff
@@ -16,9 +16,6 @@
|
||||
package org.springframework.data.elasticsearch.core;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* DocumentMapper interface, it will allow to customize how we mapping object to json
|
||||
@@ -26,32 +23,10 @@ import org.springframework.lang.Nullable;
|
||||
* @author Artur Konczak
|
||||
* @author Rizwan Idrees
|
||||
* @author Mohsin Husen
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public interface EntityMapper {
|
||||
|
||||
String mapToString(Object object) throws IOException;
|
||||
public String mapToString(Object object) throws IOException;
|
||||
|
||||
<T> T mapToObject(String source, Class<T> clazz) throws IOException;
|
||||
|
||||
/**
|
||||
* Map the given {@literal source} to {@link Map}.
|
||||
*
|
||||
* @param source must not be {@literal null}.
|
||||
* @return never {@literal null}
|
||||
* @since 3.2
|
||||
*/
|
||||
Map<String, Object> mapObject(Object source);
|
||||
|
||||
/**
|
||||
* Map the given {@link Map} into an instance of the {@literal targetType}.
|
||||
*
|
||||
* @param source must not be {@literal null}.
|
||||
* @param targetType must not be {@literal null}.
|
||||
* @param <T>
|
||||
* @return can be {@literal null}.
|
||||
* @since 3.2
|
||||
*/
|
||||
@Nullable
|
||||
<T> T readObject(Map<String, Object> source, Class<T> targetType);
|
||||
public <T> T mapToObject(String source, Class<T> clazz) throws IOException;
|
||||
}
|
||||
|
||||
@@ -1,628 +0,0 @@
|
||||
/*
|
||||
* Copyright 2018-2020 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.elasticsearch.core;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
|
||||
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
|
||||
import org.springframework.data.mapping.IdentifierAccessor;
|
||||
import org.springframework.data.mapping.PersistentPropertyAccessor;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Common operations performed on an entity in the context of it's mapping metadata.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Christoph Strobl
|
||||
* @since 3.2
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
class EntityOperations {
|
||||
|
||||
private static final String ID_FIELD = "id";
|
||||
|
||||
private final @NonNull MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> context;
|
||||
|
||||
/**
|
||||
* Creates a new {@link Entity} for the given bean.
|
||||
*
|
||||
* @param entity must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
<T> Entity<T> forEntity(T entity) {
|
||||
|
||||
Assert.notNull(entity, "Bean must not be null!");
|
||||
|
||||
if (entity instanceof Map) {
|
||||
return new SimpleMappedEntity((Map<String, Object>) entity);
|
||||
}
|
||||
|
||||
return MappedEntity.of(entity, context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link AdaptibleEntity} for the given bean and {@link ConversionService}.
|
||||
*
|
||||
* @param entity must not be {@literal null}.
|
||||
* @param conversionService must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
<T> AdaptibleEntity<T> forEntity(T entity, ConversionService conversionService) {
|
||||
|
||||
Assert.notNull(entity, "Bean must not be null!");
|
||||
Assert.notNull(conversionService, "ConversionService must not be null!");
|
||||
|
||||
if (entity instanceof Map) {
|
||||
return new SimpleMappedEntity((Map<String, Object>) entity);
|
||||
}
|
||||
|
||||
return AdaptibleMappedEntity.of(entity, context, conversionService);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine index name and type name from {@link Entity} with {@code index} and {@code type} overrides. Allows using
|
||||
* preferred values for index and type if provided, otherwise fall back to index and type defined on entity level.
|
||||
*
|
||||
* @param entity the entity to determine the index name. Can be {@literal null} if {@code index} and {@literal type}
|
||||
* are provided.
|
||||
* @param index index name override can be {@literal null}.
|
||||
* @param type index type override can be {@literal null}.
|
||||
* @return the {@link IndexCoordinates} containing index name and index type.
|
||||
* @see ElasticsearchPersistentEntity#getIndexName()
|
||||
* @see ElasticsearchPersistentEntity#getIndexType()
|
||||
*/
|
||||
IndexCoordinates determineIndex(Entity<?> entity, @Nullable String index, @Nullable String type) {
|
||||
return determineIndex(entity.getPersistentEntity(), index, type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine index name and type name from {@link ElasticsearchPersistentEntity} with {@code index} and {@code type}
|
||||
* overrides. Allows using preferred values for index and type if provided, otherwise fall back to index and type
|
||||
* defined on entity level.
|
||||
*
|
||||
* @param persistentEntity the entity to determine the index name. Can be {@literal null} if {@code index} and
|
||||
* {@literal type} are provided.
|
||||
* @param index index name override can be {@literal null}.
|
||||
* @param type index type override can be {@literal null}.
|
||||
* @return the {@link IndexCoordinates} containing index name and index type.
|
||||
* @see ElasticsearchPersistentEntity#getIndexName()
|
||||
* @see ElasticsearchPersistentEntity#getIndexType()
|
||||
*/
|
||||
IndexCoordinates determineIndex(ElasticsearchPersistentEntity<?> persistentEntity, @Nullable String index,
|
||||
@Nullable String type) {
|
||||
return new IndexCoordinates(indexName(persistentEntity, index), typeName(persistentEntity, type));
|
||||
}
|
||||
|
||||
private static String indexName(@Nullable ElasticsearchPersistentEntity<?> entity, @Nullable String index) {
|
||||
|
||||
if (StringUtils.isEmpty(index)) {
|
||||
Assert.notNull(entity, "Cannot determine index name");
|
||||
return entity.getIndexName();
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
private static String typeName(@Nullable ElasticsearchPersistentEntity<?> entity, @Nullable String type) {
|
||||
|
||||
if (StringUtils.isEmpty(type)) {
|
||||
Assert.notNull(entity, "Cannot determine index type");
|
||||
return entity.getIndexType();
|
||||
}
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* A representation of information about an entity.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
interface Entity<T> {
|
||||
|
||||
/**
|
||||
* Returns the identifier of the entity.
|
||||
*
|
||||
* @return the ID value, can be {@literal null}.
|
||||
*/
|
||||
@Nullable
|
||||
Object getId();
|
||||
|
||||
/**
|
||||
* Returns whether the entity is versioned, i.e. if it contains a version property.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
default boolean isVersionedEntity() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the version if the entity has a version property, {@literal null} otherwise.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Nullable
|
||||
Object getVersion();
|
||||
|
||||
/**
|
||||
* Returns the underlying bean.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
T getBean();
|
||||
|
||||
/**
|
||||
* Returns whether the entity is considered to be new.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
boolean isNew();
|
||||
|
||||
/**
|
||||
* Returns the {@link ElasticsearchPersistentEntity} associated with this entity.
|
||||
*
|
||||
* @return can be {@literal null} if this entity is not mapped.
|
||||
*/
|
||||
@Nullable
|
||||
ElasticsearchPersistentEntity<?> getPersistentEntity();
|
||||
|
||||
/**
|
||||
* Returns the required {@link ElasticsearchPersistentEntity}.
|
||||
*
|
||||
* @return
|
||||
* @throws IllegalStateException if no {@link ElasticsearchPersistentEntity} is associated with this entity.
|
||||
*/
|
||||
default ElasticsearchPersistentEntity<?> getRequiredPersistentEntity() {
|
||||
|
||||
ElasticsearchPersistentEntity<?> persistentEntity = getPersistentEntity();
|
||||
if (persistentEntity == null) {
|
||||
throw new IllegalStateException("No ElasticsearchPersistentEntity available for this entity!");
|
||||
}
|
||||
|
||||
return persistentEntity;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Information and commands on an entity.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
interface AdaptibleEntity<T> extends Entity<T> {
|
||||
|
||||
/**
|
||||
* Returns whether the entity has a parent.
|
||||
*
|
||||
* @return {@literal true} if the entity has a parent that has an {@literal id}.
|
||||
*/
|
||||
boolean hasParent();
|
||||
|
||||
/**
|
||||
* Returns the parent Id. Can be {@literal null}.
|
||||
*
|
||||
* @return can be {@literal null}.
|
||||
*/
|
||||
@Nullable
|
||||
Object getParentId();
|
||||
|
||||
/**
|
||||
* Populates the identifier of the backing entity if it has an identifier property and there's no identifier
|
||||
* currently present.
|
||||
*
|
||||
* @param id can be {@literal null}.
|
||||
* @return can be {@literal null}.
|
||||
*/
|
||||
@Nullable
|
||||
T populateIdIfNecessary(@Nullable Object id);
|
||||
|
||||
/**
|
||||
* Initializes the version property of the of the current entity if available.
|
||||
*
|
||||
* @return the entity with the version property updated if available.
|
||||
*/
|
||||
T initializeVersionProperty();
|
||||
|
||||
/**
|
||||
* Increments the value of the version property if available.
|
||||
*
|
||||
* @return the entity with the version property incremented if available.
|
||||
*/
|
||||
T incrementVersion();
|
||||
|
||||
/**
|
||||
* Returns the current version value if the entity has a version property.
|
||||
*
|
||||
* @return the current version or {@literal null} in case it's uninitialized or the entity doesn't expose a version
|
||||
* property.
|
||||
*/
|
||||
@Nullable
|
||||
Number getVersion();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param <T>
|
||||
* @author Christoph Strobl
|
||||
* @since 3.2
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
private static class MapBackedEntity<T extends Map<String, Object>> implements AdaptibleEntity<T> {
|
||||
|
||||
private final T map;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.EntityOperations.Entity#getId()
|
||||
*/
|
||||
@Override
|
||||
public Object getId() {
|
||||
return map.get(ID_FIELD);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.EntityOperations.AdaptibleEntity#hasParent()
|
||||
*/
|
||||
@Override
|
||||
public boolean hasParent() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.EntityOperations.AdaptibleEntity#getParentId()
|
||||
*/
|
||||
@Override
|
||||
public Entity<?> getParentId() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.EntityOperations.AdaptibleEntity#populateIdIfNecessary(java.lang.Object)
|
||||
*/
|
||||
@Nullable
|
||||
@Override
|
||||
public T populateIdIfNecessary(@Nullable Object id) {
|
||||
|
||||
map.put(ID_FIELD, id);
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.EntityOperations.AdaptibleEntity#initializeVersionProperty()
|
||||
*/
|
||||
@Override
|
||||
public T initializeVersionProperty() {
|
||||
return map;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.EntityOperations.AdaptibleEntity#getVersion()
|
||||
*/
|
||||
@Override
|
||||
@Nullable
|
||||
public Number getVersion() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.EntityOperations.AdaptibleEntity#incrementVersion()
|
||||
*/
|
||||
@Override
|
||||
public T incrementVersion() {
|
||||
return map;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.EntityOperations.Entity#getBean()
|
||||
*/
|
||||
@Override
|
||||
public T getBean() {
|
||||
return map;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.EntityOperations.Entity#isNew()
|
||||
*/
|
||||
@Override
|
||||
public boolean isNew() {
|
||||
return map.get(ID_FIELD) != null;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.EntityOperations.Entity#getPersistentEntity()
|
||||
*/
|
||||
@Override
|
||||
public ElasticsearchPersistentEntity<?> getPersistentEntity() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Plain entity without applying further mapping.
|
||||
*
|
||||
* @param <T>
|
||||
* @since 3.2
|
||||
*/
|
||||
private static class UnmappedEntity<T extends Map<String, Object>> extends MapBackedEntity<T> {
|
||||
|
||||
UnmappedEntity(T map) {
|
||||
super(map);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple mapped entity without an associated {@link ElasticsearchPersistentEntity}.
|
||||
*
|
||||
* @param <T>
|
||||
* @since 3.2
|
||||
*/
|
||||
private static class SimpleMappedEntity<T extends Map<String, Object>> extends MapBackedEntity<T> {
|
||||
|
||||
SimpleMappedEntity(T map) {
|
||||
super(map);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.EntityOperations.UnmappedEntity#getId()
|
||||
*/
|
||||
@Override
|
||||
public Object getId() {
|
||||
return getBean().get(ID_FIELD);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mapped entity with an associated {@link ElasticsearchPersistentEntity}.
|
||||
*
|
||||
* @param <T>
|
||||
* @since 3.2
|
||||
*/
|
||||
@RequiredArgsConstructor(access = AccessLevel.PROTECTED)
|
||||
private static class MappedEntity<T> implements Entity<T> {
|
||||
|
||||
private final ElasticsearchPersistentEntity<?> entity;
|
||||
private final IdentifierAccessor idAccessor;
|
||||
private final PersistentPropertyAccessor<T> propertyAccessor;
|
||||
|
||||
private static <T> MappedEntity<T> of(T bean,
|
||||
MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> context) {
|
||||
|
||||
ElasticsearchPersistentEntity<?> entity = context.getRequiredPersistentEntity(bean.getClass());
|
||||
IdentifierAccessor identifierAccessor = entity.getIdentifierAccessor(bean);
|
||||
PersistentPropertyAccessor<T> propertyAccessor = entity.getPropertyAccessor(bean);
|
||||
|
||||
return new MappedEntity<>(entity, identifierAccessor, propertyAccessor);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.EntityOperations.Entity#getId()
|
||||
*/
|
||||
@Override
|
||||
public Object getId() {
|
||||
return idAccessor.getIdentifier();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.EntityOperations.Entity#isVersionedEntity()
|
||||
*/
|
||||
@Override
|
||||
public boolean isVersionedEntity() {
|
||||
return entity.hasVersionProperty();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.EntityOperations.Entity#getVersion()
|
||||
*/
|
||||
@Override
|
||||
@Nullable
|
||||
public Object getVersion() {
|
||||
return propertyAccessor.getProperty(entity.getRequiredVersionProperty());
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.EntityOperations.Entity#getBean()
|
||||
*/
|
||||
@Override
|
||||
public T getBean() {
|
||||
return propertyAccessor.getBean();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.EntityOperations.Entity#isNew()
|
||||
*/
|
||||
@Override
|
||||
public boolean isNew() {
|
||||
return entity.isNew(propertyAccessor.getBean());
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.EntityOperations.Entity#getPersistentEntity()
|
||||
*/
|
||||
@Override
|
||||
public ElasticsearchPersistentEntity<?> getPersistentEntity() {
|
||||
return entity;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param <T>
|
||||
* @since 3.2
|
||||
*/
|
||||
private static class AdaptibleMappedEntity<T> extends MappedEntity<T> implements AdaptibleEntity<T> {
|
||||
|
||||
private final ElasticsearchPersistentEntity<?> entity;
|
||||
private final ConvertingPropertyAccessor<T> propertyAccessor;
|
||||
private final IdentifierAccessor identifierAccessor;
|
||||
|
||||
private AdaptibleMappedEntity(ElasticsearchPersistentEntity<?> entity, IdentifierAccessor identifierAccessor,
|
||||
ConvertingPropertyAccessor<T> propertyAccessor) {
|
||||
|
||||
super(entity, identifierAccessor, propertyAccessor);
|
||||
|
||||
this.entity = entity;
|
||||
this.propertyAccessor = propertyAccessor;
|
||||
this.identifierAccessor = identifierAccessor;
|
||||
}
|
||||
|
||||
static <T> AdaptibleEntity<T> of(T bean,
|
||||
MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> context,
|
||||
ConversionService conversionService) {
|
||||
|
||||
ElasticsearchPersistentEntity<?> entity = context.getRequiredPersistentEntity(bean.getClass());
|
||||
IdentifierAccessor identifierAccessor = entity.getIdentifierAccessor(bean);
|
||||
PersistentPropertyAccessor<T> propertyAccessor = entity.getPropertyAccessor(bean);
|
||||
|
||||
return new AdaptibleMappedEntity<>(entity, identifierAccessor,
|
||||
new ConvertingPropertyAccessor<>(propertyAccessor, conversionService));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.EntityOperations.AdaptibleEntity#hasParent()
|
||||
*/
|
||||
@Override
|
||||
public boolean hasParent() {
|
||||
return getRequiredPersistentEntity().getParentIdProperty() != null;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.EntityOperations.AdaptibleEntity#getParentId()
|
||||
*/
|
||||
@Override
|
||||
public Object getParentId() {
|
||||
|
||||
ElasticsearchPersistentProperty parentProperty = getRequiredPersistentEntity().getParentIdProperty();
|
||||
return propertyAccessor.getProperty(parentProperty);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.EntityOperations.AdaptibleEntity#populateIdIfNecessary(java.lang.Object)
|
||||
*/
|
||||
@Nullable
|
||||
@Override
|
||||
public T populateIdIfNecessary(@Nullable Object id) {
|
||||
|
||||
if (id == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
T bean = propertyAccessor.getBean();
|
||||
ElasticsearchPersistentProperty idProperty = entity.getIdProperty();
|
||||
|
||||
if (idProperty == null) {
|
||||
return bean;
|
||||
}
|
||||
|
||||
if (identifierAccessor.getIdentifier() != null) {
|
||||
return bean;
|
||||
}
|
||||
|
||||
propertyAccessor.setProperty(idProperty, id);
|
||||
|
||||
return propertyAccessor.getBean();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.EntityOperations.MappedEntity#getVersion()
|
||||
*/
|
||||
@Override
|
||||
@Nullable
|
||||
public Number getVersion() {
|
||||
|
||||
ElasticsearchPersistentProperty versionProperty = entity.getRequiredVersionProperty();
|
||||
|
||||
return propertyAccessor.getProperty(versionProperty, Number.class);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.EntityOperations.AdaptibleEntity#initializeVersionProperty()
|
||||
*/
|
||||
@Override
|
||||
public T initializeVersionProperty() {
|
||||
|
||||
if (!entity.hasVersionProperty()) {
|
||||
return propertyAccessor.getBean();
|
||||
}
|
||||
|
||||
ElasticsearchPersistentProperty versionProperty = entity.getRequiredVersionProperty();
|
||||
|
||||
propertyAccessor.setProperty(versionProperty, versionProperty.getType().isPrimitive() ? 1 : 0);
|
||||
|
||||
return propertyAccessor.getBean();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.EntityOperations.AdaptibleEntity#incrementVersion()
|
||||
*/
|
||||
@Override
|
||||
public T incrementVersion() {
|
||||
|
||||
ElasticsearchPersistentProperty versionProperty = entity.getRequiredVersionProperty();
|
||||
Number version = getVersion();
|
||||
Number nextVersion = version == null ? 0 : version.longValue() + 1;
|
||||
|
||||
propertyAccessor.setProperty(versionProperty, nextVersion);
|
||||
|
||||
return propertyAccessor.getBean();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Value object encapsulating index name and index type.
|
||||
*/
|
||||
@RequiredArgsConstructor(access = AccessLevel.PROTECTED)
|
||||
@Getter
|
||||
static class IndexCoordinates {
|
||||
|
||||
private final String indexName;
|
||||
private final String typeName;
|
||||
}
|
||||
}
|
||||
@@ -16,27 +16,12 @@
|
||||
package org.springframework.data.elasticsearch.core;
|
||||
|
||||
import org.elasticsearch.action.get.GetResponse;
|
||||
import org.elasticsearch.index.get.GetResult;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* @author Artur Konczak
|
||||
* @author Mohsin Husen
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public interface GetResultMapper {
|
||||
|
||||
<T> T mapResult(GetResponse response, Class<T> clazz);
|
||||
|
||||
/**
|
||||
* Map a single {@link GetResult} to the given {@link Class type}.
|
||||
*
|
||||
* @param getResult must not be {@literal null}.
|
||||
* @param type must not be {@literal null}.
|
||||
* @param <T>
|
||||
* @return can be {@literal null}.
|
||||
* @since 3.2
|
||||
*/
|
||||
@Nullable
|
||||
<T> T mapGetResult(GetResult getResult, Class<T> type);
|
||||
}
|
||||
|
||||
@@ -20,14 +20,14 @@ import static org.springframework.util.StringUtils.*;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
import org.elasticsearch.common.xcontent.XContentType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.data.annotation.Transient;
|
||||
import org.springframework.data.elasticsearch.annotations.CompletionContext;
|
||||
@@ -41,13 +41,10 @@ import org.springframework.data.elasticsearch.annotations.InnerField;
|
||||
import org.springframework.data.elasticsearch.annotations.Mapping;
|
||||
import org.springframework.data.elasticsearch.annotations.MultiField;
|
||||
import org.springframework.data.elasticsearch.core.completion.Completion;
|
||||
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
|
||||
import org.springframework.data.elasticsearch.core.geo.GeoPoint;
|
||||
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
|
||||
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
|
||||
import org.springframework.data.mapping.PropertyHandler;
|
||||
import org.springframework.data.mapping.model.SimpleTypeHolder;
|
||||
import org.springframework.data.util.ClassTypeInformation;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
@@ -66,234 +63,198 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
* @author Nordine Bittich
|
||||
* @author Robert Gruendler
|
||||
* @author Petr Kukral
|
||||
* @author Peter-Josef Meisch
|
||||
*/
|
||||
class MappingBuilder {
|
||||
|
||||
private static final String FIELD_DATA = "fielddata";
|
||||
private static final String FIELD_STORE = "store";
|
||||
private static final String FIELD_TYPE = "type";
|
||||
private static final String FIELD_INDEX = "index";
|
||||
private static final String FIELD_FORMAT = "format";
|
||||
private static final String FIELD_SEARCH_ANALYZER = "search_analyzer";
|
||||
private static final String FIELD_INDEX_ANALYZER = "analyzer";
|
||||
private static final String FIELD_NORMALIZER = "normalizer";
|
||||
private static final String FIELD_PROPERTIES = "properties";
|
||||
private static final String FIELD_PARENT = "_parent";
|
||||
private static final String FIELD_COPY_TO = "copy_to";
|
||||
private static final String FIELD_CONTEXT_NAME = "name";
|
||||
private static final String FIELD_CONTEXT_TYPE = "type";
|
||||
private static final String FIELD_CONTEXT_PRECISION = "precision";
|
||||
private static final String FIELD_DYNAMIC_TEMPLATES = "dynamic_templates";
|
||||
public static final String FIELD_DATA = "fielddata";
|
||||
public static final String FIELD_STORE = "store";
|
||||
public static final String FIELD_TYPE = "type";
|
||||
public static final String FIELD_INDEX = "index";
|
||||
public static final String FIELD_FORMAT = "format";
|
||||
public static final String FIELD_SEARCH_ANALYZER = "search_analyzer";
|
||||
public static final String FIELD_INDEX_ANALYZER = "analyzer";
|
||||
public static final String FIELD_NORMALIZER = "normalizer";
|
||||
public static final String FIELD_PROPERTIES = "properties";
|
||||
public static final String FIELD_PARENT = "_parent";
|
||||
public static final String FIELD_COPY_TO = "copy_to";
|
||||
public static final String FIELD_CONTEXT_NAME = "name";
|
||||
public static final String FIELD_CONTEXT_TYPE = "type";
|
||||
public static final String FIELD_CONTEXT_PRECISION = "precision";
|
||||
public static final String FIELD_DYNAMIC_TEMPLATES = "dynamic_templates";
|
||||
|
||||
private static final String COMPLETION_PRESERVE_SEPARATORS = "preserve_separators";
|
||||
private static final String COMPLETION_PRESERVE_POSITION_INCREMENTS = "preserve_position_increments";
|
||||
private static final String COMPLETION_MAX_INPUT_LENGTH = "max_input_length";
|
||||
private static final String COMPLETION_CONTEXTS = "contexts";
|
||||
public static final String COMPLETION_PRESERVE_SEPARATORS = "preserve_separators";
|
||||
public static final String COMPLETION_PRESERVE_POSITION_INCREMENTS = "preserve_position_increments";
|
||||
public static final String COMPLETION_MAX_INPUT_LENGTH = "max_input_length";
|
||||
public static final String COMPLETION_CONTEXTS = "contexts";
|
||||
|
||||
private static final String TYPE_VALUE_KEYWORD = "keyword";
|
||||
private static final String TYPE_VALUE_GEO_POINT = "geo_point";
|
||||
private static final String TYPE_VALUE_COMPLETION = "completion";
|
||||
public static final String TYPE_VALUE_KEYWORD = "keyword";
|
||||
public static final String TYPE_VALUE_GEO_POINT = "geo_point";
|
||||
public static final String TYPE_VALUE_COMPLETION = "completion";
|
||||
public static final String TYPE_VALUE_GEO_HASH_PREFIX = "geohash_prefix";
|
||||
public static final String TYPE_VALUE_GEO_HASH_PRECISION = "geohash_precision";
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ElasticsearchRestTemplate.class);
|
||||
private static SimpleTypeHolder SIMPLE_TYPE_HOLDER = SimpleTypeHolder.DEFAULT;
|
||||
|
||||
private final ElasticsearchConverter elasticsearchConverter;
|
||||
static XContentBuilder buildMapping(Class<?> clazz, String indexType, String idFieldName, String parentType) throws IOException {
|
||||
|
||||
MappingBuilder(ElasticsearchConverter elasticsearchConverter) {
|
||||
this.elasticsearchConverter = elasticsearchConverter;
|
||||
}
|
||||
|
||||
/**
|
||||
* builds the Elasticsearch mapping for the given clazz.
|
||||
*
|
||||
* @return JSON string
|
||||
* @throws IOException
|
||||
*/
|
||||
String buildPropertyMapping(Class<?> clazz) throws IOException {
|
||||
|
||||
ElasticsearchPersistentEntity<?> entity = elasticsearchConverter.getMappingContext()
|
||||
.getRequiredPersistentEntity(clazz);
|
||||
|
||||
XContentBuilder builder = jsonBuilder().startObject().startObject(entity.getIndexType());
|
||||
XContentBuilder mapping = jsonBuilder().startObject().startObject(indexType);
|
||||
|
||||
// Dynamic templates
|
||||
addDynamicTemplatesMapping(builder, entity);
|
||||
addDynamicTemplatesMapping(mapping, clazz);
|
||||
|
||||
// Parent
|
||||
String parentType = entity.getParentType();
|
||||
if (hasText(parentType)) {
|
||||
builder.startObject(FIELD_PARENT).field(FIELD_TYPE, parentType).endObject();
|
||||
mapping.startObject(FIELD_PARENT).field(FIELD_TYPE, parentType).endObject();
|
||||
}
|
||||
|
||||
// Properties
|
||||
builder.startObject(FIELD_PROPERTIES);
|
||||
XContentBuilder xContentBuilder = mapping.startObject(FIELD_PROPERTIES);
|
||||
|
||||
mapEntity(builder, entity, true, "", false, FieldType.Auto, null);
|
||||
mapEntity(xContentBuilder, clazz, true, idFieldName, "", false, FieldType.Auto, null);
|
||||
|
||||
builder.endObject() // FIELD_PROPERTIES
|
||||
.endObject() // indexType
|
||||
.endObject() // root object
|
||||
.close();
|
||||
|
||||
return builder.getOutputStream().toString();
|
||||
return xContentBuilder.endObject().endObject().endObject();
|
||||
}
|
||||
|
||||
private void mapEntity(XContentBuilder builder, @Nullable ElasticsearchPersistentEntity entity, boolean isRootObject,
|
||||
String nestedObjectFieldName, boolean nestedOrObjectField, FieldType fieldType,
|
||||
@Nullable Field parentFieldAnnotation) throws IOException {
|
||||
private static void mapEntity(XContentBuilder xContentBuilder, Class<?> clazz, boolean isRootObject, String idFieldName,
|
||||
String nestedObjectFieldName, boolean nestedOrObjectField, FieldType fieldType, Field fieldAnnotation) throws IOException {
|
||||
|
||||
boolean writeNestedProperties = !isRootObject && (isAnyPropertyAnnotatedWithField(entity) || nestedOrObjectField);
|
||||
if (writeNestedProperties) {
|
||||
java.lang.reflect.Field[] fields = retrieveFields(clazz);
|
||||
|
||||
String type = nestedOrObjectField ? fieldType.toString().toLowerCase()
|
||||
: FieldType.Object.toString().toLowerCase();
|
||||
builder.startObject(nestedObjectFieldName).field(FIELD_TYPE, type);
|
||||
if (!isRootObject && (isAnyPropertyAnnotatedAsField(fields) || nestedOrObjectField)) {
|
||||
String type = FieldType.Object.toString().toLowerCase();
|
||||
if (nestedOrObjectField) {
|
||||
type = fieldType.toString().toLowerCase();
|
||||
}
|
||||
XContentBuilder t = xContentBuilder.startObject(nestedObjectFieldName).field(FIELD_TYPE, type);
|
||||
|
||||
if (nestedOrObjectField && FieldType.Nested == fieldType && parentFieldAnnotation != null
|
||||
&& parentFieldAnnotation.includeInParent()) {
|
||||
if (nestedOrObjectField && FieldType.Nested == fieldType && fieldAnnotation.includeInParent()) {
|
||||
t.field("include_in_parent", fieldAnnotation.includeInParent());
|
||||
}
|
||||
t.startObject(FIELD_PROPERTIES);
|
||||
}
|
||||
|
||||
builder.field("include_in_parent", parentFieldAnnotation.includeInParent());
|
||||
for (java.lang.reflect.Field field : fields) {
|
||||
|
||||
if (field.isAnnotationPresent(Transient.class) || isInIgnoreFields(field, fieldAnnotation)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
builder.startObject(FIELD_PROPERTIES);
|
||||
}
|
||||
if (entity != null) {
|
||||
|
||||
entity.doWithProperties((PropertyHandler<ElasticsearchPersistentProperty>) property -> {
|
||||
try {
|
||||
if (property.isAnnotationPresent(Transient.class) || isInIgnoreFields(property, parentFieldAnnotation)) {
|
||||
return;
|
||||
if (field.isAnnotationPresent(Mapping.class)) {
|
||||
String mappingPath = field.getAnnotation(Mapping.class).mappingPath();
|
||||
if (!StringUtils.isEmpty(mappingPath)) {
|
||||
ClassPathResource mappings = new ClassPathResource(mappingPath);
|
||||
if (mappings.exists()) {
|
||||
xContentBuilder.rawField(field.getName(), mappings.getInputStream(), XContentType.JSON);
|
||||
continue;
|
||||
}
|
||||
|
||||
buildPropertyMapping(builder, isRootObject, property);
|
||||
} catch (IOException e) {
|
||||
logger.warn("error mapping property with name {}", property.getName(), e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (writeNestedProperties) {
|
||||
builder.endObject().endObject();
|
||||
}
|
||||
}
|
||||
|
||||
private void buildPropertyMapping(XContentBuilder builder, boolean isRootObject,
|
||||
ElasticsearchPersistentProperty property) throws IOException {
|
||||
|
||||
if (property.isAnnotationPresent(Mapping.class)) {
|
||||
|
||||
String mappingPath = property.getRequiredAnnotation(Mapping.class).mappingPath();
|
||||
if (!StringUtils.isEmpty(mappingPath)) {
|
||||
|
||||
ClassPathResource mappings = new ClassPathResource(mappingPath);
|
||||
if (mappings.exists()) {
|
||||
builder.rawField(property.getFieldName(), mappings.getInputStream(), XContentType.JSON);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
boolean isGeoPointProperty = isGeoPointProperty(property);
|
||||
boolean isCompletionProperty = isCompletionProperty(property);
|
||||
boolean isNestedOrObjectProperty = isNestedOrObjectProperty(property);
|
||||
boolean isGeoPointField = isGeoPointField(field);
|
||||
boolean isCompletionField = isCompletionField(field);
|
||||
|
||||
Field fieldAnnotation = property.findAnnotation(Field.class);
|
||||
if (!isGeoPointProperty && !isCompletionProperty && property.isEntity() && hasRelevantAnnotation(property)) {
|
||||
|
||||
if (fieldAnnotation == null) {
|
||||
return;
|
||||
Field singleField = field.getAnnotation(Field.class);
|
||||
if (!isGeoPointField && !isCompletionField && isEntity(field) && isAnnotated(field)) {
|
||||
if (singleField == null) {
|
||||
continue;
|
||||
}
|
||||
boolean nestedOrObject = isNestedOrObjectField(field);
|
||||
mapEntity(xContentBuilder, getFieldType(field), false, "", field.getName(), nestedOrObject, singleField.type(), field.getAnnotation(Field.class));
|
||||
if (nestedOrObject) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
Iterator<? extends TypeInformation<?>> iterator = property.getPersistentEntityTypes().iterator();
|
||||
ElasticsearchPersistentEntity<?> persistentEntity = iterator.hasNext()
|
||||
? elasticsearchConverter.getMappingContext().getPersistentEntity(iterator.next())
|
||||
: null;
|
||||
MultiField multiField = field.getAnnotation(MultiField.class);
|
||||
|
||||
mapEntity(builder, persistentEntity, false, property.getFieldName(), isNestedOrObjectProperty,
|
||||
fieldAnnotation.type(), fieldAnnotation);
|
||||
if (isGeoPointField) {
|
||||
applyGeoPointFieldMapping(xContentBuilder, field);
|
||||
}
|
||||
|
||||
if (isNestedOrObjectProperty) {
|
||||
return;
|
||||
if (isCompletionField) {
|
||||
CompletionField completionField = field.getAnnotation(CompletionField.class);
|
||||
applyCompletionFieldMapping(xContentBuilder, field, completionField);
|
||||
}
|
||||
|
||||
if (isRootObject && singleField != null && isIdField(field, idFieldName)) {
|
||||
applyDefaultIdFieldMapping(xContentBuilder, field);
|
||||
} else if (multiField != null) {
|
||||
addMultiFieldMapping(xContentBuilder, field, multiField, isNestedOrObjectField(field));
|
||||
} else if (singleField != null) {
|
||||
addSingleFieldMapping(xContentBuilder, field, singleField, isNestedOrObjectField(field));
|
||||
}
|
||||
}
|
||||
|
||||
MultiField multiField = property.findAnnotation(MultiField.class);
|
||||
|
||||
if (isGeoPointProperty) {
|
||||
applyGeoPointFieldMapping(builder, property);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isCompletionProperty) {
|
||||
CompletionField completionField = property.findAnnotation(CompletionField.class);
|
||||
applyCompletionFieldMapping(builder, property, completionField);
|
||||
}
|
||||
|
||||
if (isRootObject && fieldAnnotation != null && property.isIdProperty()) {
|
||||
applyDefaultIdFieldMapping(builder, property);
|
||||
} else if (multiField != null) {
|
||||
addMultiFieldMapping(builder, property, multiField, isNestedOrObjectProperty);
|
||||
} else if (fieldAnnotation != null) {
|
||||
addSingleFieldMapping(builder, property, fieldAnnotation, isNestedOrObjectProperty);
|
||||
if (!isRootObject && isAnyPropertyAnnotatedAsField(fields) || nestedOrObjectField) {
|
||||
xContentBuilder.endObject().endObject();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasRelevantAnnotation(ElasticsearchPersistentProperty property) {
|
||||
private static java.lang.reflect.Field[] retrieveFields(Class<?> clazz) {
|
||||
// Create list of fields.
|
||||
List<java.lang.reflect.Field> fields = new ArrayList<>();
|
||||
|
||||
return property.findAnnotation(Field.class) != null || property.findAnnotation(MultiField.class) != null
|
||||
|| property.findAnnotation(GeoPointField.class) != null
|
||||
|| property.findAnnotation(CompletionField.class) != null;
|
||||
// Keep backing up the inheritance hierarchy.
|
||||
Class<?> targetClass = clazz;
|
||||
do {
|
||||
fields.addAll(Arrays.asList(targetClass.getDeclaredFields()));
|
||||
targetClass = targetClass.getSuperclass();
|
||||
}
|
||||
while (targetClass != null && targetClass != Object.class);
|
||||
|
||||
return fields.toArray(new java.lang.reflect.Field[fields.size()]);
|
||||
}
|
||||
|
||||
private void applyGeoPointFieldMapping(XContentBuilder builder, ElasticsearchPersistentProperty property)
|
||||
throws IOException {
|
||||
|
||||
builder.startObject(property.getFieldName()).field(FIELD_TYPE, TYPE_VALUE_GEO_POINT).endObject();
|
||||
private static boolean isAnnotated(java.lang.reflect.Field field) {
|
||||
return field.getAnnotation(Field.class) != null ||
|
||||
field.getAnnotation(MultiField.class) != null ||
|
||||
field.getAnnotation(GeoPointField.class) != null ||
|
||||
field.getAnnotation(CompletionField.class) != null;
|
||||
}
|
||||
|
||||
private void applyCompletionFieldMapping(XContentBuilder builder, ElasticsearchPersistentProperty property,
|
||||
@Nullable CompletionField annotation) throws IOException {
|
||||
|
||||
builder.startObject(property.getFieldName());
|
||||
builder.field(FIELD_TYPE, TYPE_VALUE_COMPLETION);
|
||||
private static void applyGeoPointFieldMapping(XContentBuilder xContentBuilder, java.lang.reflect.Field field) throws IOException {
|
||||
xContentBuilder.startObject(field.getName());
|
||||
xContentBuilder.field(FIELD_TYPE, TYPE_VALUE_GEO_POINT);
|
||||
xContentBuilder.endObject();
|
||||
}
|
||||
|
||||
private static void applyCompletionFieldMapping(XContentBuilder xContentBuilder, java.lang.reflect.Field field, CompletionField annotation) throws IOException {
|
||||
xContentBuilder.startObject(field.getName());
|
||||
xContentBuilder.field(FIELD_TYPE, TYPE_VALUE_COMPLETION);
|
||||
if (annotation != null) {
|
||||
|
||||
builder.field(COMPLETION_MAX_INPUT_LENGTH, annotation.maxInputLength());
|
||||
builder.field(COMPLETION_PRESERVE_POSITION_INCREMENTS, annotation.preservePositionIncrements());
|
||||
builder.field(COMPLETION_PRESERVE_SEPARATORS, annotation.preserveSeparators());
|
||||
xContentBuilder.field(COMPLETION_MAX_INPUT_LENGTH, annotation.maxInputLength());
|
||||
xContentBuilder.field(COMPLETION_PRESERVE_POSITION_INCREMENTS, annotation.preservePositionIncrements());
|
||||
xContentBuilder.field(COMPLETION_PRESERVE_SEPARATORS, annotation.preserveSeparators());
|
||||
if (!StringUtils.isEmpty(annotation.searchAnalyzer())) {
|
||||
builder.field(FIELD_SEARCH_ANALYZER, annotation.searchAnalyzer());
|
||||
xContentBuilder.field(FIELD_SEARCH_ANALYZER, annotation.searchAnalyzer());
|
||||
}
|
||||
if (!StringUtils.isEmpty(annotation.analyzer())) {
|
||||
builder.field(FIELD_INDEX_ANALYZER, annotation.analyzer());
|
||||
xContentBuilder.field(FIELD_INDEX_ANALYZER, annotation.analyzer());
|
||||
}
|
||||
|
||||
if (annotation.contexts().length > 0) {
|
||||
|
||||
builder.startArray(COMPLETION_CONTEXTS);
|
||||
xContentBuilder.startArray(COMPLETION_CONTEXTS);
|
||||
for (CompletionContext context : annotation.contexts()) {
|
||||
|
||||
builder.startObject();
|
||||
builder.field(FIELD_CONTEXT_NAME, context.name());
|
||||
builder.field(FIELD_CONTEXT_TYPE, context.type().name().toLowerCase());
|
||||
xContentBuilder.startObject();
|
||||
xContentBuilder.field(FIELD_CONTEXT_NAME, context.name());
|
||||
xContentBuilder.field(FIELD_CONTEXT_TYPE, context.type().name().toLowerCase());
|
||||
if (context.precision().length() > 0) {
|
||||
builder.field(FIELD_CONTEXT_PRECISION, context.precision());
|
||||
xContentBuilder.field(FIELD_CONTEXT_PRECISION, context.precision());
|
||||
}
|
||||
builder.endObject();
|
||||
xContentBuilder.endObject();
|
||||
}
|
||||
builder.endArray();
|
||||
xContentBuilder.endArray();
|
||||
}
|
||||
|
||||
}
|
||||
builder.endObject();
|
||||
xContentBuilder.endObject();
|
||||
}
|
||||
|
||||
private void applyDefaultIdFieldMapping(XContentBuilder builder, ElasticsearchPersistentProperty property)
|
||||
private static void applyDefaultIdFieldMapping(XContentBuilder xContentBuilder, java.lang.reflect.Field field)
|
||||
throws IOException {
|
||||
|
||||
builder.startObject(property.getFieldName()).field(FIELD_TYPE, TYPE_VALUE_KEYWORD).field(FIELD_INDEX, true)
|
||||
.endObject();
|
||||
xContentBuilder.startObject(field.getName())
|
||||
.field(FIELD_TYPE, TYPE_VALUE_KEYWORD)
|
||||
.field(FIELD_INDEX, true);
|
||||
xContentBuilder.endObject();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -301,10 +262,8 @@ class MappingBuilder {
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
private void addSingleFieldMapping(XContentBuilder builder, ElasticsearchPersistentProperty property,
|
||||
Field annotation, boolean nestedOrObjectField) throws IOException {
|
||||
|
||||
builder.startObject(property.getFieldName());
|
||||
private static void addSingleFieldMapping(XContentBuilder builder, java.lang.reflect.Field field, Field annotation, boolean nestedOrObjectField) throws IOException {
|
||||
builder.startObject(field.getName());
|
||||
addFieldMappingParameters(builder, annotation, nestedOrObjectField);
|
||||
builder.endObject();
|
||||
}
|
||||
@@ -314,11 +273,14 @@ class MappingBuilder {
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
private void addMultiFieldMapping(XContentBuilder builder, ElasticsearchPersistentProperty property,
|
||||
MultiField annotation, boolean nestedOrObjectField) throws IOException {
|
||||
private static void addMultiFieldMapping(
|
||||
XContentBuilder builder,
|
||||
java.lang.reflect.Field field,
|
||||
MultiField annotation,
|
||||
boolean nestedOrObjectField) throws IOException {
|
||||
|
||||
// main field
|
||||
builder.startObject(property.getFieldName());
|
||||
builder.startObject(field.getName());
|
||||
addFieldMappingParameters(builder, annotation.mainField(), nestedOrObjectField);
|
||||
|
||||
// inner fields
|
||||
@@ -333,9 +295,7 @@ class MappingBuilder {
|
||||
builder.endObject();
|
||||
}
|
||||
|
||||
private void addFieldMappingParameters(XContentBuilder builder, Object annotation, boolean nestedOrObjectField)
|
||||
throws IOException {
|
||||
|
||||
private static void addFieldMappingParameters(XContentBuilder builder, Object annotation, boolean nestedOrObjectField) throws IOException {
|
||||
boolean index = true;
|
||||
boolean store = false;
|
||||
boolean fielddata = false;
|
||||
@@ -411,19 +371,15 @@ class MappingBuilder {
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
private void addDynamicTemplatesMapping(XContentBuilder builder, ElasticsearchPersistentEntity<?> entity)
|
||||
throws IOException {
|
||||
|
||||
if (entity.isAnnotationPresent(DynamicTemplates.class)) {
|
||||
String mappingPath = entity.getRequiredAnnotation(DynamicTemplates.class).mappingPath();
|
||||
private static void addDynamicTemplatesMapping(XContentBuilder builder, Class<?> clazz) throws IOException {
|
||||
if (clazz.isAnnotationPresent(DynamicTemplates.class)){
|
||||
String mappingPath = ((DynamicTemplates) clazz.getAnnotation(DynamicTemplates.class)).mappingPath();
|
||||
if (hasText(mappingPath)) {
|
||||
|
||||
String jsonString = ResourceUtil.readFileFromClasspath(mappingPath);
|
||||
String jsonString = ElasticsearchTemplate.readFileFromClasspath(mappingPath);
|
||||
if (hasText(jsonString)) {
|
||||
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
JsonNode jsonNode = objectMapper.readTree(jsonString).get("dynamic_templates");
|
||||
if (jsonNode != null && jsonNode.isArray()) {
|
||||
if (jsonNode != null && jsonNode.isArray()){
|
||||
String json = objectMapper.writeValueAsString(jsonNode);
|
||||
builder.rawField(FIELD_DYNAMIC_TEMPLATES, new ByteArrayInputStream(json.getBytes()), XContentType.JSON);
|
||||
}
|
||||
@@ -432,33 +388,63 @@ class MappingBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isAnyPropertyAnnotatedWithField(@Nullable ElasticsearchPersistentEntity entity) {
|
||||
|
||||
return entity != null && entity.getPersistentProperty(Field.class) != null;
|
||||
protected static boolean isEntity(java.lang.reflect.Field field) {
|
||||
TypeInformation<?> typeInformation = ClassTypeInformation.from(field.getType());
|
||||
Class<?> clazz = getFieldType(field);
|
||||
boolean isComplexType = !SIMPLE_TYPE_HOLDER.isSimpleType(clazz);
|
||||
return isComplexType && !Map.class.isAssignableFrom(typeInformation.getType());
|
||||
}
|
||||
|
||||
private boolean isInIgnoreFields(ElasticsearchPersistentProperty property, @Nullable Field parentFieldAnnotation) {
|
||||
protected static Class<?> getFieldType(java.lang.reflect.Field field) {
|
||||
|
||||
if (null != parentFieldAnnotation) {
|
||||
ResolvableType resolvableType = ResolvableType.forField(field);
|
||||
|
||||
String[] ignoreFields = parentFieldAnnotation.ignoreFields();
|
||||
return Arrays.asList(ignoreFields).contains(property.getFieldName());
|
||||
if (resolvableType.isArray()) {
|
||||
return resolvableType.getComponentType().getRawClass();
|
||||
}
|
||||
|
||||
ResolvableType componentType = resolvableType.getGeneric(0);
|
||||
if (Iterable.class.isAssignableFrom(field.getType())
|
||||
&& componentType != ResolvableType.NONE) {
|
||||
return componentType.getRawClass();
|
||||
}
|
||||
|
||||
return resolvableType.getRawClass();
|
||||
}
|
||||
|
||||
private static boolean isAnyPropertyAnnotatedAsField(java.lang.reflect.Field[] fields) {
|
||||
if (fields != null) {
|
||||
for (java.lang.reflect.Field field : fields) {
|
||||
if (field.isAnnotationPresent(Field.class)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isNestedOrObjectProperty(ElasticsearchPersistentProperty property) {
|
||||
|
||||
Field fieldAnnotation = property.findAnnotation(Field.class);
|
||||
return fieldAnnotation != null
|
||||
&& (FieldType.Nested == fieldAnnotation.type() || FieldType.Object == fieldAnnotation.type());
|
||||
private static boolean isIdField(java.lang.reflect.Field field, String idFieldName) {
|
||||
return idFieldName.equals(field.getName());
|
||||
}
|
||||
|
||||
private boolean isGeoPointProperty(ElasticsearchPersistentProperty property) {
|
||||
return property.getActualType() == GeoPoint.class || property.isAnnotationPresent(GeoPointField.class);
|
||||
private static boolean isInIgnoreFields(java.lang.reflect.Field field, Field parentFieldAnnotation) {
|
||||
if (null != parentFieldAnnotation) {
|
||||
String[] ignoreFields = parentFieldAnnotation.ignoreFields();
|
||||
return Arrays.asList(ignoreFields).contains(field.getName());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isCompletionProperty(ElasticsearchPersistentProperty property) {
|
||||
return property.getActualType() == Completion.class;
|
||||
private static boolean isNestedOrObjectField(java.lang.reflect.Field field) {
|
||||
Field fieldAnnotation = field.getAnnotation(Field.class);
|
||||
return fieldAnnotation != null && (FieldType.Nested == fieldAnnotation.type() || FieldType.Object == fieldAnnotation.type());
|
||||
}
|
||||
|
||||
private static boolean isGeoPointField(java.lang.reflect.Field field) {
|
||||
return field.getType() == GeoPoint.class || field.getAnnotation(GeoPointField.class) != null;
|
||||
}
|
||||
|
||||
private static boolean isCompletionField(java.lang.reflect.Field field) {
|
||||
return field.getType() == Completion.class;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
package org.springframework.data.elasticsearch.core;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.LinkedList;
|
||||
|
||||
import org.elasticsearch.action.get.MultiGetResponse;
|
||||
|
||||
@@ -25,5 +25,5 @@ import org.elasticsearch.action.get.MultiGetResponse;
|
||||
*/
|
||||
public interface MultiGetResultMapper {
|
||||
|
||||
<T> List<T> mapResults(MultiGetResponse responses, Class<T> clazz);
|
||||
<T> LinkedList<T> mapResults(MultiGetResponse responses, Class<T> clazz);
|
||||
}
|
||||
|
||||
-474
@@ -1,474 +0,0 @@
|
||||
/*
|
||||
* Copyright 2018-2020 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.elasticsearch.core;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.elasticsearch.index.query.QueryBuilders;
|
||||
import org.reactivestreams.Publisher;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient;
|
||||
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
|
||||
import org.springframework.data.elasticsearch.core.query.Query;
|
||||
import org.springframework.data.elasticsearch.core.query.StringQuery;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Interface that specifies a basic set of Elasticsearch operations executed in a reactive way.
|
||||
* <p>
|
||||
* Implemented by {@link ReactiveElasticsearchTemplate}. 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 ReactiveElasticsearchOperations} is deferred until a {@link org.reactivestreams.Subscriber} subscribes to the
|
||||
* {@link Publisher}.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 3.2
|
||||
*/
|
||||
public interface ReactiveElasticsearchOperations {
|
||||
|
||||
/**
|
||||
* Execute within a {@link ClientCallback} managing resources and translating errors.
|
||||
*
|
||||
* @param callback must not be {@literal null}.
|
||||
* @param <T>
|
||||
* @return the {@link Publisher} emitting results.
|
||||
*/
|
||||
<T> Publisher<T> execute(ClientCallback<Publisher<T>> callback);
|
||||
|
||||
/**
|
||||
* Index the given entity, once available, extracting index and type from entity metadata.
|
||||
*
|
||||
* @param entityPublisher must not be {@literal null}.
|
||||
* @param <T>
|
||||
* @return a {@link Mono} emitting the saved entity.
|
||||
*/
|
||||
default <T> Mono<T> save(Mono<? extends T> entityPublisher) {
|
||||
|
||||
Assert.notNull(entityPublisher, "EntityPublisher must not be null!");
|
||||
return entityPublisher.flatMap(this::save);
|
||||
}
|
||||
|
||||
/**
|
||||
* Index the given entity extracting index and type from entity metadata.
|
||||
*
|
||||
* @param entity must not be {@literal null}.
|
||||
* @param <T>
|
||||
* @return a {@link Mono} emitting the saved entity.
|
||||
*/
|
||||
default <T> Mono<T> save(T entity) {
|
||||
return save(entity, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Index the entity, once available, in the given {@literal index}. If the index is {@literal null} or empty the index
|
||||
* name provided via entity metadata is used.
|
||||
*
|
||||
* @param entityPublisher must not be {@literal null}.
|
||||
* @param index the name of the target index. Can be {@literal null}.
|
||||
* @param <T>
|
||||
* @return a {@link Mono} emitting the saved entity.
|
||||
*/
|
||||
default <T> Mono<T> save(Mono<? extends T> entityPublisher, String index) {
|
||||
|
||||
Assert.notNull(entityPublisher, "EntityPublisher must not be null!");
|
||||
return entityPublisher.flatMap(it -> save(it, index));
|
||||
}
|
||||
|
||||
/**
|
||||
* Index the entity in the given {@literal index}. If the index is {@literal null} or empty the index name provided
|
||||
* via entity metadata is used.
|
||||
*
|
||||
* @param entity must not be {@literal null}.
|
||||
* @param index the name of the target index. Can be {@literal null}.
|
||||
* @param <T>
|
||||
* @return a {@link Mono} emitting the saved entity.
|
||||
*/
|
||||
default <T> Mono<T> save(T entity, @Nullable String index) {
|
||||
return save(entity, index, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Index the entity, once available, under the given {@literal type} in the given {@literal index}. If the
|
||||
* {@literal index} is {@literal null} or empty the index name provided via entity metadata is used. Same for the
|
||||
* {@literal type}.
|
||||
*
|
||||
* @param entityPublisher must not be {@literal null}.
|
||||
* @param index the name of the target index. Can be {@literal null}.
|
||||
* @param type the name of the type within the index. Can be {@literal null}.
|
||||
* @param <T>
|
||||
* @return a {@link Mono} emitting the saved entity.
|
||||
*/
|
||||
default <T> Mono<T> save(Mono<? extends T> entityPublisher, @Nullable String index, @Nullable String type) {
|
||||
|
||||
Assert.notNull(entityPublisher, "EntityPublisher must not be null!");
|
||||
return entityPublisher.flatMap(it -> save(it, index, type));
|
||||
}
|
||||
|
||||
/**
|
||||
* Index the entity under the given {@literal type} in the given {@literal index}. If the {@literal index} is
|
||||
* {@literal null} or empty the index name provided via entity metadata is used. Same for the {@literal type}.
|
||||
*
|
||||
* @param entity must not be {@literal null}.
|
||||
* @param index the name of the target index. Can be {@literal null}.
|
||||
* @param type the name of the type within the index. Can be {@literal null}.
|
||||
* @param <T>
|
||||
* @return a {@link Mono} emitting the saved entity.
|
||||
*/
|
||||
<T> Mono<T> save(T entity, @Nullable String index, @Nullable String type);
|
||||
|
||||
/**
|
||||
* Find the document with the given {@literal id} mapped onto the given {@literal entityType}.
|
||||
*
|
||||
* @param id the {@literal _id} of the document to fetch.
|
||||
* @param entityType the domain type used for mapping the document.
|
||||
* @param <T>
|
||||
* @return {@link Mono#empty()} if not found.
|
||||
*/
|
||||
default <T> Mono<T> findById(String id, Class<T> entityType) {
|
||||
return findById(id, entityType, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the entity with given {@literal id}.
|
||||
*
|
||||
* @param id the {@literal _id} of the document to fetch.
|
||||
* @param entityType the domain type used for mapping the document.
|
||||
* @param index the name of the target index. Overwrites document metadata from {@literal entityType} if not
|
||||
* {@literal null}.
|
||||
* @param <T>
|
||||
* @return {@link Mono#empty()} if not found.
|
||||
*/
|
||||
default <T> Mono<T> findById(String id, Class<T> entityType, @Nullable String index) {
|
||||
return findById(id, entityType, index, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the entity with given {@literal id}.
|
||||
*
|
||||
* @param id must not be {@literal null}.
|
||||
* @param index the name of the target index. Overwrites document metadata from {@literal entityType} if not
|
||||
* {@literal null}.
|
||||
* @param type the name of the target type. Overwrites document metadata from {@literal entityType} if not
|
||||
* {@literal null}.
|
||||
* @param <T>
|
||||
* @return the {@link Mono} emitting the entity or signalling completion if none found.
|
||||
*/
|
||||
<T> Mono<T> findById(String id, Class<T> entityType, @Nullable String index, @Nullable String type);
|
||||
|
||||
/**
|
||||
* Check if an entity with given {@literal id} exists.
|
||||
*
|
||||
* @param id the {@literal _id} of the document to look for.
|
||||
* @param entityType the domain type used.
|
||||
* @return a {@link Mono} emitting {@literal true} if a matching document exists, {@literal false} otherwise.
|
||||
*/
|
||||
default Mono<Boolean> exists(String id, Class<?> entityType) {
|
||||
return exists(id, entityType, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an entity with given {@literal id} exists.
|
||||
*
|
||||
* @param id the {@literal _id} of the document to look for.
|
||||
* @param entityType the domain type used.
|
||||
* @param index the name of the target index. Overwrites document metadata from {@literal entityType} if not
|
||||
* {@literal null}.
|
||||
* @return a {@link Mono} emitting {@literal true} if a matching document exists, {@literal false} otherwise.
|
||||
*/
|
||||
default Mono<Boolean> exists(String id, Class<?> entityType, @Nullable String index) {
|
||||
return exists(id, entityType, index, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an entity with given {@literal id} exists.
|
||||
*
|
||||
* @param id the {@literal _id} of the document to look for.
|
||||
* @param index the name of the target index. Overwrites document metadata from {@literal entityType} if not
|
||||
* {@literal null}.
|
||||
* @param type the name of the target type. Overwrites document metadata from {@literal entityType} if not
|
||||
* {@literal null}.
|
||||
* @return a {@link Mono} emitting {@literal true} if a matching document exists, {@literal false} otherwise.
|
||||
*/
|
||||
Mono<Boolean> exists(String id, Class<?> entityType, @Nullable String index, @Nullable String type);
|
||||
|
||||
/**
|
||||
* Search the index for entities matching the given {@link Query query}. <br />
|
||||
* {@link Pageable#isUnpaged() Unpaged} queries may overrule elasticsearch server defaults for page size by either
|
||||
* delegating to the scroll API or using a max {@link org.elasticsearch.search.builder.SearchSourceBuilder#size(int)
|
||||
* size}.
|
||||
*
|
||||
* @param query must not be {@literal null}.
|
||||
* @param entityType must not be {@literal null}.
|
||||
* @param <T>
|
||||
* @return a {@link Flux} emitting matching entities one by one.
|
||||
*/
|
||||
default <T> Flux<T> find(Query query, Class<T> entityType) {
|
||||
return find(query, entityType, entityType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search the index for entities matching the given {@link Query query}. <br />
|
||||
* {@link Pageable#isUnpaged() Unpaged} queries may overrule elasticsearch server defaults for page size by either *
|
||||
* delegating to the scroll API or using a max {@link org.elasticsearch.search.builder.SearchSourceBuilder#size(int) *
|
||||
* size}.
|
||||
*
|
||||
* @param query must not be {@literal null}.
|
||||
* @param entityType The entity type for mapping the query. Must not be {@literal null}.
|
||||
* @param returnType The mapping target type. Must not be {@literal null}. Th
|
||||
* @param <T>
|
||||
* @return a {@link Flux} emitting matching entities one by one.
|
||||
*/
|
||||
default <T> Flux<T> find(Query query, Class<?> entityType, Class<T> returnType) {
|
||||
return find(query, entityType, null, null, returnType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search the index for entities matching the given {@link Query query}.
|
||||
*
|
||||
* @param query must not be {@literal null}.
|
||||
* @param entityType must not be {@literal null}.
|
||||
* @param <T>
|
||||
* @return a {@link Flux} emitting matching entities one by one.
|
||||
*/
|
||||
default <T> Flux<T> find(Query query, Class<T> entityType, @Nullable String index) {
|
||||
return find(query, entityType, index, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search the index for entities matching the given {@link Query query}.
|
||||
*
|
||||
* @param query must not be {@literal null}.
|
||||
* @param entityType must not be {@literal null}.
|
||||
* @param index the name of the target index. Overwrites document metadata from {@literal entityType} if not
|
||||
* {@literal null}.
|
||||
* @param type the name of the target type. Overwrites document metadata from {@literal entityType} if not
|
||||
* {@literal null}.
|
||||
* @param <T>
|
||||
* @returnm a {@link Flux} emitting matching entities one by one.
|
||||
*/
|
||||
default <T> Flux<T> find(Query query, Class<T> entityType, @Nullable String index, @Nullable String type) {
|
||||
return find(query, entityType, index, type, entityType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search the index for entities matching the given {@link Query query}.
|
||||
*
|
||||
* @param query must not be {@literal null}.
|
||||
* @param entityType must not be {@literal null}.
|
||||
* @param index the name of the target index. Overwrites document metadata from {@literal entityType} if not
|
||||
* {@literal null}.
|
||||
* @param type the name of the target type. Overwrites document metadata from {@literal entityType} if not
|
||||
* {@literal null}.
|
||||
* @param resultType the projection result type.
|
||||
* @param <T>
|
||||
* @return a {@link Flux} emitting matching entities one by one.
|
||||
*/
|
||||
<T> Flux<T> find(Query query, Class<?> entityType, @Nullable String index, @Nullable String type,
|
||||
Class<T> resultType);
|
||||
|
||||
/**
|
||||
* Count the number of documents matching the given {@link Query}.
|
||||
*
|
||||
* @param entityType must not be {@literal null}.
|
||||
* @return a {@link Mono} emitting the nr of matching documents.
|
||||
*/
|
||||
default Mono<Long> count(Class<?> entityType) {
|
||||
return count(new StringQuery(QueryBuilders.matchAllQuery().toString()), entityType, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Count the number of documents matching the given {@link Query}.
|
||||
*
|
||||
* @param query must not be {@literal null}.
|
||||
* @param entityType must not be {@literal null}.
|
||||
* @return a {@link Mono} emitting the nr of matching documents.
|
||||
*/
|
||||
default Mono<Long> count(Query query, Class<?> entityType) {
|
||||
return count(query, entityType, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Count the number of documents matching the given {@link Query}.
|
||||
*
|
||||
* @param query must not be {@literal null}.
|
||||
* @param entityType must not be {@literal null}.
|
||||
* @param index the name of the target index. Overwrites document metadata from {@literal entityType} if not
|
||||
* {@literal null}.
|
||||
* @return a {@link Mono} emitting the nr of matching documents.
|
||||
*/
|
||||
default Mono<Long> count(Query query, Class<?> entityType, @Nullable String index) {
|
||||
return count(query, entityType, index, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Count the number of documents matching the given {@link Query}.
|
||||
*
|
||||
* @param query must not be {@literal null}.
|
||||
* @param entityType must not be {@literal null}.
|
||||
* @param index the name of the target index. Overwrites document metadata from {@literal entityType} if not
|
||||
* {@literal null}.
|
||||
* @param type the name of the target type. Overwrites document metadata from {@literal entityType} if not
|
||||
* {@literal null}.
|
||||
* @return a {@link Mono} emitting the nr of matching documents.
|
||||
*/
|
||||
Mono<Long> count(Query query, Class<?> entityType, @Nullable String index, @Nullable String type);
|
||||
|
||||
/**
|
||||
* Delete the given entity extracting index and type from entity metadata.
|
||||
*
|
||||
* @param entity must not be {@literal null}.
|
||||
* @return a {@link Mono} emitting the {@literal id} of the removed document.
|
||||
*/
|
||||
default Mono<String> delete(Object entity) {
|
||||
return delete(entity, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the given entity extracting index and type from entity metadata.
|
||||
*
|
||||
* @param entity must not be {@literal null}.
|
||||
* @param index the name of the target index. Overwrites document metadata from {@literal entityType} if not
|
||||
* {@literal null}.
|
||||
* @return a {@link Mono} emitting the {@literal id} of the removed document.
|
||||
*/
|
||||
default Mono<String> delete(Object entity, @Nullable String index) {
|
||||
return delete(entity, index, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the given entity extracting index and type from entity metadata.
|
||||
*
|
||||
* @param entity must not be {@literal null}.
|
||||
* @param index the name of the target index. Overwrites document metadata from {@literal entityType} if not
|
||||
* {@literal null}.
|
||||
* @param type the name of the target type. Overwrites document metadata from {@literal entityType} if not
|
||||
* {@literal null}.
|
||||
* @return a {@link Mono} emitting the {@literal id} of the removed document.
|
||||
*/
|
||||
Mono<String> delete(Object entity, @Nullable String index, @Nullable String type);
|
||||
|
||||
/**
|
||||
* Delete the entity with given {@literal id}.
|
||||
*
|
||||
* @param id must not be {@literal null}.
|
||||
* @param index the name of the target index.
|
||||
* @param type the name of the target type.
|
||||
* @return a {@link Mono} emitting the {@literal id} of the removed document.
|
||||
*/
|
||||
default Mono<String> deleteById(String id, String index, String type) {
|
||||
|
||||
Assert.notNull(index, "Index must not be null!");
|
||||
Assert.notNull(type, "Type must not be null!");
|
||||
|
||||
return deleteById(id, Object.class, index, type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the entity with given {@literal id} extracting index and type from entity metadata.
|
||||
*
|
||||
* @param id must not be {@literal null}.
|
||||
* @param entityType must not be {@literal null}.
|
||||
* @return a {@link Mono} emitting the {@literal id} of the removed document.
|
||||
*/
|
||||
default Mono<String> deleteById(String id, Class<?> entityType) {
|
||||
return deleteById(id, entityType, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the entity with given {@literal id} extracting index and type from entity metadata.
|
||||
*
|
||||
* @param id must not be {@literal null}.
|
||||
* @param entityType must not be {@literal null}.
|
||||
* @param index the name of the target index. Overwrites document metadata from {@literal entityType} if not
|
||||
* {@literal null}.
|
||||
* @return a {@link Mono} emitting the {@literal id} of the removed document.
|
||||
*/
|
||||
default Mono<String> deleteById(String id, Class<?> entityType, @Nullable String index) {
|
||||
return deleteById(id, entityType, index, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the entity with given {@literal id} extracting index and type from entity metadata.
|
||||
*
|
||||
* @param id must not be {@literal null}.
|
||||
* @param entityType must not be {@literal null}.
|
||||
* @param index the name of the target index. Overwrites document metadata from {@literal entityType} if not
|
||||
* {@literal null}.
|
||||
* @param type the name of the target type. Overwrites document metadata from {@literal entityType} if not
|
||||
* {@literal null}.
|
||||
* @return a {@link Mono} emitting the {@literal id} of the removed document.
|
||||
*/
|
||||
Mono<String> deleteById(String id, Class<?> entityType, @Nullable String index, @Nullable String type);
|
||||
|
||||
/**
|
||||
* Delete the documents matching the given {@link Query} extracting index and type from entity metadata.
|
||||
*
|
||||
* @param query must not be {@literal null}.
|
||||
* @param entityType must not be {@literal null}.
|
||||
* @return a {@link Mono} emitting the number of the removed documents.
|
||||
*/
|
||||
default Mono<Long> deleteBy(Query query, Class<?> entityType) {
|
||||
return deleteBy(query, entityType, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the documents matching the given {@link Query} extracting index and type from entity metadata.
|
||||
*
|
||||
* @param query must not be {@literal null}.
|
||||
* @param entityType must not be {@literal null}.
|
||||
* @param index the name of the target index. Overwrites document metadata from {@literal entityType} if not
|
||||
* {@literal null}.
|
||||
* @return a {@link Mono} emitting the number of the removed documents.
|
||||
*/
|
||||
default Mono<Long> deleteBy(Query query, Class<?> entityType, @Nullable String index) {
|
||||
return deleteBy(query, entityType, index, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the documents matching the given {@link Query} extracting index and type from entity metadata.
|
||||
*
|
||||
* @param query must not be {@literal null}.
|
||||
* @param entityType must not be {@literal null}.
|
||||
* @param index the name of the target index. Overwrites document metadata from {@literal entityType} if not
|
||||
* {@literal null}.
|
||||
* @param type the name of the target type. Overwrites document metadata from {@literal entityType} if not
|
||||
* {@literal null}.
|
||||
* @return a {@link Mono} emitting the number of the removed documents.
|
||||
*/
|
||||
Mono<Long> deleteBy(Query query, Class<?> entityType, @Nullable String index, @Nullable String type);
|
||||
|
||||
/**
|
||||
* Get the {@link ElasticsearchConverter} used.
|
||||
*
|
||||
* @return never {@literal null}
|
||||
*/
|
||||
ElasticsearchConverter getElasticsearchConverter();
|
||||
|
||||
/**
|
||||
* Callback interface to be used with {@link #execute(ClientCallback)} for operating directly on
|
||||
* {@link ReactiveElasticsearchClient}.
|
||||
*
|
||||
* @param <T>
|
||||
* @author Christoph Strobl
|
||||
* @since 3.2
|
||||
*/
|
||||
interface ClientCallback<T extends Publisher<?>> {
|
||||
|
||||
T doWithClient(ReactiveElasticsearchClient client);
|
||||
}
|
||||
}
|
||||
-786
@@ -1,786 +0,0 @@
|
||||
/*
|
||||
* Copyright 2018-2020 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.elasticsearch.core;
|
||||
|
||||
import static org.elasticsearch.index.VersionType.*;
|
||||
|
||||
import lombok.NonNull;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.elasticsearch.action.delete.DeleteRequest;
|
||||
import org.elasticsearch.action.get.GetRequest;
|
||||
import org.elasticsearch.action.index.IndexRequest;
|
||||
import org.elasticsearch.action.index.IndexResponse;
|
||||
import org.elasticsearch.action.search.SearchRequest;
|
||||
import org.elasticsearch.action.support.IndicesOptions;
|
||||
import org.elasticsearch.action.support.WriteRequest;
|
||||
import org.elasticsearch.action.support.WriteRequest.RefreshPolicy;
|
||||
import org.elasticsearch.client.Requests;
|
||||
import org.elasticsearch.client.core.CountRequest;
|
||||
import org.elasticsearch.index.get.GetResult;
|
||||
import org.elasticsearch.index.query.QueryBuilder;
|
||||
import org.elasticsearch.index.query.QueryBuilders;
|
||||
import org.elasticsearch.index.query.WrapperQueryBuilder;
|
||||
import org.elasticsearch.index.reindex.BulkByScrollResponse;
|
||||
import org.elasticsearch.index.reindex.DeleteByQueryRequest;
|
||||
import org.elasticsearch.search.SearchHit;
|
||||
import org.elasticsearch.search.builder.SearchSourceBuilder;
|
||||
import org.elasticsearch.search.sort.FieldSortBuilder;
|
||||
import org.elasticsearch.search.sort.SortBuilders;
|
||||
import org.elasticsearch.search.sort.SortOrder;
|
||||
import org.reactivestreams.Publisher;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.elasticsearch.NoSuchIndexException;
|
||||
import org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient;
|
||||
import org.springframework.data.elasticsearch.core.EntityOperations.AdaptibleEntity;
|
||||
import org.springframework.data.elasticsearch.core.EntityOperations.Entity;
|
||||
import org.springframework.data.elasticsearch.core.EntityOperations.IndexCoordinates;
|
||||
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
|
||||
import org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter;
|
||||
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
|
||||
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
|
||||
import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext;
|
||||
import org.springframework.data.elasticsearch.core.query.CriteriaQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.Query;
|
||||
import org.springframework.data.elasticsearch.core.query.SearchQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.StringQuery;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
* @author Farid Azaza
|
||||
* @author Martin Choraine
|
||||
* @author Peter-Josef Meisch
|
||||
* @author Mathias Teier
|
||||
* @since 3.2
|
||||
*/
|
||||
public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOperations {
|
||||
|
||||
private static final Logger QUERY_LOGGER = LoggerFactory
|
||||
.getLogger("org.springframework.data.elasticsearch.core.QUERY");
|
||||
|
||||
private final ReactiveElasticsearchClient client;
|
||||
private final ElasticsearchConverter converter;
|
||||
private final @NonNull MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext;
|
||||
private final ResultsMapper resultMapper;
|
||||
private final ElasticsearchExceptionTranslator exceptionTranslator;
|
||||
private final EntityOperations operations;
|
||||
|
||||
private @Nullable RefreshPolicy refreshPolicy = RefreshPolicy.IMMEDIATE;
|
||||
private @Nullable IndicesOptions indicesOptions = IndicesOptions.strictExpandOpenAndForbidClosedIgnoreThrottled();
|
||||
|
||||
public ReactiveElasticsearchTemplate(ReactiveElasticsearchClient client) {
|
||||
this(client, new MappingElasticsearchConverter(new SimpleElasticsearchMappingContext()));
|
||||
}
|
||||
|
||||
public ReactiveElasticsearchTemplate(ReactiveElasticsearchClient client, ElasticsearchConverter converter) {
|
||||
this(client, converter, new DefaultResultMapper(converter.getMappingContext()));
|
||||
}
|
||||
|
||||
public ReactiveElasticsearchTemplate(ReactiveElasticsearchClient client, ElasticsearchConverter converter,
|
||||
ResultsMapper resultsMapper) {
|
||||
|
||||
this.client = client;
|
||||
this.converter = converter;
|
||||
this.mappingContext = converter.getMappingContext();
|
||||
|
||||
this.resultMapper = resultsMapper;
|
||||
this.exceptionTranslator = new ElasticsearchExceptionTranslator();
|
||||
this.operations = new EntityOperations(this.mappingContext);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations#exctute(ClientCallback)
|
||||
*/
|
||||
@Override
|
||||
public <T> Publisher<T> execute(ClientCallback<Publisher<T>> callback) {
|
||||
return Flux.defer(() -> callback.doWithClient(getClient())).onErrorMap(this::translateException);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations#index(Object, String, String)
|
||||
*/
|
||||
@Override
|
||||
public <T> Mono<T> save(T entity, @Nullable String index, @Nullable String type) {
|
||||
|
||||
Assert.notNull(entity, "Entity must not be null!");
|
||||
|
||||
AdaptibleEntity<T> adaptableEntity = operations.forEntity(entity, converter.getConversionService());
|
||||
|
||||
return doIndex(entity, adaptableEntity, index, type) //
|
||||
.map(it -> {
|
||||
return adaptableEntity.populateIdIfNecessary(it.getId());
|
||||
});
|
||||
}
|
||||
|
||||
private Mono<IndexResponse> doIndex(Object value, AdaptibleEntity<?> entity, @Nullable String index,
|
||||
@Nullable String type) {
|
||||
|
||||
return Mono.defer(() -> {
|
||||
|
||||
Object id = entity.getId();
|
||||
|
||||
IndexCoordinates indexCoordinates = operations.determineIndex(entity, index, type);
|
||||
|
||||
IndexRequest request = id != null
|
||||
? new IndexRequest(indexCoordinates.getIndexName(), indexCoordinates.getTypeName(), converter.convertId(id))
|
||||
: new IndexRequest(indexCoordinates.getIndexName(), indexCoordinates.getTypeName());
|
||||
|
||||
try {
|
||||
request.source(resultMapper.getEntityMapper().mapToString(value), Requests.INDEX_CONTENT_TYPE);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
if (entity.isVersionedEntity()) {
|
||||
|
||||
Object version = entity.getVersion();
|
||||
if (version != null) {
|
||||
request.version(((Number) version).longValue());
|
||||
request.versionType(EXTERNAL);
|
||||
}
|
||||
}
|
||||
|
||||
if (entity.hasParent()) {
|
||||
|
||||
Object parentId = entity.getParentId();
|
||||
if (parentId != null) {
|
||||
request.parent(converter.convertId(parentId));
|
||||
}
|
||||
}
|
||||
|
||||
request = prepareIndexRequest(value, request);
|
||||
return doIndex(request);
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations#findById(String, Class, String, String)
|
||||
*/
|
||||
@Override
|
||||
public <T> Mono<T> findById(String id, Class<T> entityType, @Nullable String index, @Nullable String type) {
|
||||
|
||||
Assert.notNull(id, "Id must not be null!");
|
||||
|
||||
return doFindById(id, getPersistentEntity(entityType), index, type)
|
||||
.map(it -> resultMapper.mapGetResult(it, entityType));
|
||||
}
|
||||
|
||||
private Mono<GetResult> doFindById(String id, ElasticsearchPersistentEntity<?> entity, @Nullable String index,
|
||||
@Nullable String type) {
|
||||
|
||||
return Mono.defer(() -> {
|
||||
|
||||
IndexCoordinates indexCoordinates = operations.determineIndex(entity, index, type);
|
||||
|
||||
return doFindById(new GetRequest(indexCoordinates.getIndexName(), indexCoordinates.getTypeName(), id));
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations#exists(String, Class, String, String)
|
||||
* @see org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations#exists(String, Class, String, String)
|
||||
*/
|
||||
@Override
|
||||
public Mono<Boolean> exists(String id, Class<?> entityType, String index, String type) {
|
||||
|
||||
Assert.notNull(id, "Id must not be null!");
|
||||
|
||||
return doExists(id, getPersistentEntity(entityType), index, type);
|
||||
}
|
||||
|
||||
private Mono<Boolean> doExists(String id, ElasticsearchPersistentEntity<?> entity, @Nullable String index,
|
||||
@Nullable String type) {
|
||||
|
||||
return Mono.defer(() -> {
|
||||
|
||||
IndexCoordinates indexCoordinates = operations.determineIndex(entity, index, type);
|
||||
|
||||
return doExists(new GetRequest(indexCoordinates.getIndexName(), indexCoordinates.getTypeName(), id));
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations#find(Query, Class, String, String, Class)
|
||||
*/
|
||||
@Override
|
||||
public <T> Flux<T> find(Query query, Class<?> entityType, @Nullable String index, @Nullable String type,
|
||||
Class<T> resultType) {
|
||||
|
||||
return doFind(query, getPersistentEntity(entityType), index, type)
|
||||
.map(it -> resultMapper.mapSearchHit(it, resultType));
|
||||
}
|
||||
|
||||
private Flux<SearchHit> doFind(Query query, ElasticsearchPersistentEntity<?> entity, @Nullable String index,
|
||||
@Nullable String type) {
|
||||
|
||||
return Flux.defer(() -> {
|
||||
SearchRequest request = prepareSearchRequest(buildSearchRequest(query, entity, index, type));
|
||||
|
||||
if (query.getPageable().isPaged()) {
|
||||
return doFind(request);
|
||||
} else {
|
||||
return doScroll(request);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Long> count(Query query, Class<?> entityType, String index, String type) {
|
||||
return doCount(query, getPersistentEntity(entityType), index, type);
|
||||
}
|
||||
|
||||
private Mono<Long> doCount(Query query, ElasticsearchPersistentEntity<?> entity, @Nullable String index,
|
||||
@Nullable String type) {
|
||||
return Mono.defer(() -> {
|
||||
|
||||
CountRequest countRequest = buildCountRequest(query, entity, index, type);
|
||||
CountRequest request = prepareCountRequest(countRequest);
|
||||
return doCount(request);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private CountRequest buildCountRequest(Query query, ElasticsearchPersistentEntity<?> entity, @Nullable String index,
|
||||
@Nullable String type) {
|
||||
|
||||
IndexCoordinates indexCoordinates = operations.determineIndex(entity, index, type);
|
||||
CountRequest request = new CountRequest(indices(query, indexCoordinates::getIndexName));
|
||||
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
|
||||
searchSourceBuilder.query(mappedQuery(query, entity));
|
||||
searchSourceBuilder.trackScores(query.getTrackScores());
|
||||
|
||||
QueryBuilder postFilterQuery = mappedFilterQuery(query, entity);
|
||||
if (postFilterQuery != null) {
|
||||
searchSourceBuilder.postFilter(postFilterQuery);
|
||||
}
|
||||
|
||||
if (query.getSourceFilter() != null) {
|
||||
searchSourceBuilder.fetchSource(query.getSourceFilter().getIncludes(), query.getSourceFilter().getExcludes());
|
||||
}
|
||||
|
||||
if (query instanceof SearchQuery && ((SearchQuery) query).getCollapseBuilder() != null) {
|
||||
searchSourceBuilder.collapse(((SearchQuery) query).getCollapseBuilder());
|
||||
}
|
||||
|
||||
sort(query, entity).forEach(searchSourceBuilder::sort);
|
||||
|
||||
if (query.getMinScore() > 0) {
|
||||
searchSourceBuilder.minScore(query.getMinScore());
|
||||
}
|
||||
|
||||
if (query.getIndicesOptions() != null) {
|
||||
request.indicesOptions(query.getIndicesOptions());
|
||||
}
|
||||
|
||||
if (query.getPreference() != null) {
|
||||
request.preference(query.getPreference());
|
||||
}
|
||||
request.source(searchSourceBuilder);
|
||||
return request;
|
||||
}
|
||||
|
||||
private SearchRequest buildSearchRequest(Query query, ElasticsearchPersistentEntity<?> entity, @Nullable String index,
|
||||
@Nullable String type) {
|
||||
IndexCoordinates indexCoordinates = operations.determineIndex(entity, index, type);
|
||||
SearchRequest request = new SearchRequest(indices(query, indexCoordinates::getIndexName));
|
||||
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
|
||||
searchSourceBuilder.query(mappedQuery(query, entity));
|
||||
searchSourceBuilder.version(entity.hasVersionProperty());
|
||||
searchSourceBuilder.trackScores(query.getTrackScores());
|
||||
|
||||
QueryBuilder postFilterQuery = mappedFilterQuery(query, entity);
|
||||
if (postFilterQuery != null) {
|
||||
searchSourceBuilder.postFilter(postFilterQuery);
|
||||
}
|
||||
|
||||
if (query.getSourceFilter() != null) {
|
||||
searchSourceBuilder.fetchSource(query.getSourceFilter().getIncludes(), query.getSourceFilter().getExcludes());
|
||||
}
|
||||
|
||||
if (query instanceof SearchQuery && ((SearchQuery) query).getCollapseBuilder() != null) {
|
||||
searchSourceBuilder.collapse(((SearchQuery) query).getCollapseBuilder());
|
||||
}
|
||||
|
||||
sort(query, entity).forEach(searchSourceBuilder::sort);
|
||||
|
||||
if (query.getMinScore() > 0) {
|
||||
searchSourceBuilder.minScore(query.getMinScore());
|
||||
}
|
||||
|
||||
if (query.getIndicesOptions() != null) {
|
||||
request.indicesOptions(query.getIndicesOptions());
|
||||
}
|
||||
|
||||
if (query.getPreference() != null) {
|
||||
request.preference(query.getPreference());
|
||||
}
|
||||
|
||||
if (query.getSearchType() != null) {
|
||||
request.searchType(query.getSearchType());
|
||||
}
|
||||
|
||||
Pageable pageable = query.getPageable();
|
||||
|
||||
if (pageable.isPaged()) {
|
||||
|
||||
long offset = pageable.getOffset();
|
||||
if (offset > Integer.MAX_VALUE) {
|
||||
throw new IllegalArgumentException(String.format("Offset must not be more than %s", Integer.MAX_VALUE));
|
||||
}
|
||||
|
||||
searchSourceBuilder.from((int) offset);
|
||||
searchSourceBuilder.size(pageable.getPageSize());
|
||||
|
||||
request.source(searchSourceBuilder);
|
||||
} else {
|
||||
searchSourceBuilder.from(0);
|
||||
searchSourceBuilder.size(AbstractElasticsearchTemplate.INDEX_MAX_RESULT_WINDOW);
|
||||
request.source(searchSourceBuilder);
|
||||
}
|
||||
return request;
|
||||
}
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations#count(Query, Class, String, String)
|
||||
*/
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations#delete(Object, String, String)
|
||||
*/
|
||||
@Override
|
||||
public Mono<String> delete(Object entity, @Nullable String index, @Nullable String type) {
|
||||
|
||||
Entity<?> elasticsearchEntity = operations.forEntity(entity);
|
||||
|
||||
return Mono.defer(() -> doDeleteById(entity, converter.convertId(elasticsearchEntity.getId()),
|
||||
elasticsearchEntity.getPersistentEntity(), index, type));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations#delete(String, Class, String, String)
|
||||
*/
|
||||
@Override
|
||||
public Mono<String> deleteById(String id, Class<?> entityType, @Nullable String index, @Nullable String type) {
|
||||
|
||||
Assert.notNull(id, "Id must not be null!");
|
||||
|
||||
return doDeleteById(null, id, getPersistentEntity(entityType), index, type);
|
||||
|
||||
}
|
||||
|
||||
private Mono<String> doDeleteById(@Nullable Object source, String id, ElasticsearchPersistentEntity<?> entity,
|
||||
@Nullable String index, @Nullable String type) {
|
||||
|
||||
return Mono.defer(() -> {
|
||||
|
||||
IndexCoordinates indexCoordinates = operations.determineIndex(entity, index, type);
|
||||
|
||||
return doDelete(prepareDeleteRequest(source,
|
||||
new DeleteRequest(indexCoordinates.getIndexName(), indexCoordinates.getTypeName(), id)));
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations#deleteBy(Query, Class, String, String)
|
||||
*/
|
||||
@Override
|
||||
public Mono<Long> deleteBy(Query query, Class<?> entityType, String index, String type) {
|
||||
|
||||
Assert.notNull(query, "Query must not be null!");
|
||||
|
||||
return doDeleteBy(query, getPersistentEntity(entityType), index, type).map(BulkByScrollResponse::getDeleted)
|
||||
.publishNext();
|
||||
}
|
||||
|
||||
private Flux<BulkByScrollResponse> doDeleteBy(Query query, ElasticsearchPersistentEntity<?> entity,
|
||||
@Nullable String index, @Nullable String type) {
|
||||
|
||||
return Flux.defer(() -> {
|
||||
|
||||
IndexCoordinates indexCoordinates = operations.determineIndex(entity, index, type);
|
||||
|
||||
DeleteByQueryRequest request = new DeleteByQueryRequest(indices(query, indexCoordinates::getIndexName));
|
||||
request.types(indexTypes(query, indexCoordinates::getTypeName));
|
||||
request.setQuery(mappedQuery(query, entity));
|
||||
|
||||
return doDeleteBy(prepareDeleteByRequest(request));
|
||||
});
|
||||
}
|
||||
|
||||
// Property Setters / Getters
|
||||
|
||||
/**
|
||||
* Set the default {@link RefreshPolicy} to apply when writing to Elasticsearch.
|
||||
*
|
||||
* @param refreshPolicy can be {@literal null}.
|
||||
*/
|
||||
public void setRefreshPolicy(@Nullable RefreshPolicy refreshPolicy) {
|
||||
this.refreshPolicy = refreshPolicy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the default {@link IndicesOptions} for {@link SearchRequest search requests}.
|
||||
*
|
||||
* @param indicesOptions can be {@literal null}.
|
||||
*/
|
||||
public void setIndicesOptions(@Nullable IndicesOptions indicesOptions) {
|
||||
this.indicesOptions = indicesOptions;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations#getElasticsearchConverter()
|
||||
*/
|
||||
@Override
|
||||
public ElasticsearchConverter getElasticsearchConverter() {
|
||||
return converter;
|
||||
}
|
||||
|
||||
// Customization Hooks
|
||||
|
||||
/**
|
||||
* Obtain the {@link ReactiveElasticsearchClient} to operate upon.
|
||||
*
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
protected ReactiveElasticsearchClient getClient() {
|
||||
return this.client;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pre process the write request before it is sent to the server, eg. by setting the
|
||||
* {@link WriteRequest#setRefreshPolicy(String) refresh policy} if applicable.
|
||||
*
|
||||
* @param request must not be {@literal null}.
|
||||
* @param <R>
|
||||
* @return the processed {@link WriteRequest}.
|
||||
*/
|
||||
protected <R extends WriteRequest<R>> R prepareWriteRequest(R request) {
|
||||
|
||||
if (refreshPolicy == null) {
|
||||
return request;
|
||||
}
|
||||
|
||||
return request.setRefreshPolicy(refreshPolicy);
|
||||
}
|
||||
|
||||
/**
|
||||
* Customization hook to modify a generated {@link IndexRequest} prior to its execution. Eg. by setting the
|
||||
* {@link WriteRequest#setRefreshPolicy(String) refresh policy} if applicable.
|
||||
*
|
||||
* @param source the source object the {@link IndexRequest} was derived from.
|
||||
* @param request the generated {@link IndexRequest}.
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
protected IndexRequest prepareIndexRequest(Object source, IndexRequest request) {
|
||||
return prepareWriteRequest(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Customization hook to modify a generated {@link SearchRequest} prior to its execution. Eg. by setting the
|
||||
* {@link SearchRequest#indicesOptions(IndicesOptions) indices options} if applicable.
|
||||
*
|
||||
* @param request the generated {@link CountRequest}.
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
protected CountRequest prepareCountRequest(CountRequest request) {
|
||||
|
||||
if (indicesOptions == null) {
|
||||
return request;
|
||||
}
|
||||
|
||||
return request.indicesOptions(indicesOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Customization hook to modify a generated {@link SearchRequest} prior to its execution. Eg. by setting the
|
||||
* {@link SearchRequest#indicesOptions(IndicesOptions) indices options} if applicable.
|
||||
*
|
||||
* @param request the generated {@link SearchRequest}.
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
protected SearchRequest prepareSearchRequest(SearchRequest request) {
|
||||
|
||||
if (indicesOptions == null) {
|
||||
return request;
|
||||
}
|
||||
|
||||
return request.indicesOptions(indicesOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Customization hook to modify a generated {@link DeleteRequest} prior to its execution. Eg. by setting the
|
||||
* {@link WriteRequest#setRefreshPolicy(String) refresh policy} if applicable.
|
||||
*
|
||||
* @param source the source object the {@link DeleteRequest} was derived from. My be {@literal null} if using the
|
||||
* {@literal id} directly.
|
||||
* @param request the generated {@link DeleteRequest}.
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
protected DeleteRequest prepareDeleteRequest(@Nullable Object source, DeleteRequest request) {
|
||||
return prepareWriteRequest(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Customization hook to modify a generated {@link DeleteByQueryRequest} prior to its execution. Eg. by setting the
|
||||
* {@link WriteRequest#setRefreshPolicy(String) refresh policy} if applicable.
|
||||
*
|
||||
* @param request the generated {@link DeleteByQueryRequest}.
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
protected DeleteByQueryRequest prepareDeleteByRequest(DeleteByQueryRequest request) {
|
||||
|
||||
if (refreshPolicy != null && !RefreshPolicy.NONE.equals(refreshPolicy)) {
|
||||
request = request.setRefresh(true);
|
||||
}
|
||||
|
||||
if (indicesOptions != null) {
|
||||
request = request.setIndicesOptions(indicesOptions);
|
||||
}
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Customization hook on the actual execution result {@link Publisher}. <br />
|
||||
* You know what you're doing here? Well fair enough, go ahead on your own risk.
|
||||
*
|
||||
* @param request the already prepared {@link IndexRequest} ready to be executed.
|
||||
* @return a {@link Mono} emitting the result of the operation.
|
||||
*/
|
||||
protected Mono<IndexResponse> doIndex(IndexRequest request) {
|
||||
return Mono.from(execute(client -> client.index(request)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Customization hook on the actual execution result {@link Publisher}. <br />
|
||||
*
|
||||
* @param request the already prepared {@link GetRequest} ready to be executed.
|
||||
* @return a {@link Mono} emitting the result of the operation.
|
||||
*/
|
||||
protected Mono<GetResult> doFindById(GetRequest request) {
|
||||
|
||||
return Mono.from(execute(client -> client.get(request))) //
|
||||
.onErrorResume(NoSuchIndexException.class, it -> Mono.empty());
|
||||
}
|
||||
|
||||
/**
|
||||
* Customization hook on the actual execution result {@link Publisher}. <br />
|
||||
*
|
||||
* @param request the already prepared {@link GetRequest} ready to be executed.
|
||||
* @return a {@link Mono} emitting the result of the operation.
|
||||
*/
|
||||
protected Mono<Boolean> doExists(GetRequest request) {
|
||||
|
||||
return Mono.from(execute(client -> client.exists(request))) //
|
||||
.onErrorReturn(NoSuchIndexException.class, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Customization hook on the actual execution result {@link Publisher}. <br />
|
||||
*
|
||||
* @param request the already prepared {@link SearchRequest} ready to be executed.
|
||||
* @return a {@link Flux} emitting the result of the operation.
|
||||
*/
|
||||
protected Flux<SearchHit> doFind(SearchRequest request) {
|
||||
|
||||
if (QUERY_LOGGER.isDebugEnabled()) {
|
||||
QUERY_LOGGER.debug("Executing doFind: {}", request);
|
||||
}
|
||||
|
||||
return Flux.from(execute(client -> client.search(request))) //
|
||||
.onErrorResume(NoSuchIndexException.class, it -> Mono.empty());
|
||||
}
|
||||
|
||||
/**
|
||||
* Customization hook on the actual execution result {@link Publisher}. <br />
|
||||
*
|
||||
* @param request the already prepared {@link CountRequest} ready to be executed.
|
||||
* @return a {@link Mono} emitting the result of the operation.
|
||||
*/
|
||||
protected Mono<Long> doCount(CountRequest request) {
|
||||
|
||||
if (QUERY_LOGGER.isDebugEnabled()) {
|
||||
QUERY_LOGGER.debug("Executing doCount: {}", request);
|
||||
}
|
||||
|
||||
return Mono.from(execute(client -> client.count(request))) //
|
||||
.onErrorResume(NoSuchIndexException.class, it -> Mono.empty());
|
||||
}
|
||||
|
||||
/**
|
||||
* Customization hook on the actual execution result {@link Publisher}. <br />
|
||||
*
|
||||
* @param request the already prepared {@link SearchRequest} ready to be executed.
|
||||
* @return a {@link Flux} emitting the result of the operation.
|
||||
*/
|
||||
protected Flux<SearchHit> doScroll(SearchRequest request) {
|
||||
|
||||
if (QUERY_LOGGER.isDebugEnabled()) {
|
||||
QUERY_LOGGER.debug("Executing doScroll: {}", request);
|
||||
}
|
||||
|
||||
return Flux.from(execute(client -> client.scroll(request))) //
|
||||
.onErrorResume(NoSuchIndexException.class, it -> Mono.empty());
|
||||
}
|
||||
|
||||
/**
|
||||
* Customization hook on the actual execution result {@link Publisher}. <br />
|
||||
*
|
||||
* @param request the already prepared {@link DeleteRequest} ready to be executed.
|
||||
* @return a {@link Mono} emitting the result of the operation.
|
||||
*/
|
||||
protected Mono<String> doDelete(DeleteRequest request) {
|
||||
|
||||
return Mono.from(execute(client -> client.delete(request))) //
|
||||
|
||||
.flatMap(it -> {
|
||||
|
||||
if (HttpStatus.valueOf(it.status().getStatus()).equals(HttpStatus.NOT_FOUND)) {
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
return Mono.just(it.getId());
|
||||
}) //
|
||||
.onErrorResume(NoSuchIndexException.class, it -> Mono.empty());
|
||||
}
|
||||
|
||||
/**
|
||||
* Customization hook on the actual execution result {@link Publisher}. <br />
|
||||
*
|
||||
* @param request the already prepared {@link DeleteByQueryRequest} ready to be executed.
|
||||
* @return a {@link Mono} emitting the result of the operation.
|
||||
*/
|
||||
protected Mono<BulkByScrollResponse> doDeleteBy(DeleteByQueryRequest request) {
|
||||
|
||||
return Mono.from(execute(client -> client.deleteBy(request))) //
|
||||
.onErrorResume(NoSuchIndexException.class, it -> Mono.empty());
|
||||
}
|
||||
|
||||
// private helpers
|
||||
|
||||
private static String[] indices(Query query, Supplier<String> index) {
|
||||
|
||||
if (query.getIndices().isEmpty()) {
|
||||
return new String[] { index.get() };
|
||||
}
|
||||
|
||||
return query.getIndices().toArray(new String[0]);
|
||||
}
|
||||
|
||||
private static String[] indexTypes(Query query, Supplier<String> indexType) {
|
||||
|
||||
if (query.getTypes().isEmpty()) {
|
||||
return new String[] { indexType.get() };
|
||||
}
|
||||
|
||||
return query.getTypes().toArray(new String[0]);
|
||||
}
|
||||
|
||||
private static List<FieldSortBuilder> sort(Query query, ElasticsearchPersistentEntity<?> entity) {
|
||||
|
||||
if (query.getSort() == null || query.getSort().isUnsorted()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<FieldSortBuilder> mappedSort = new ArrayList<>();
|
||||
for (Sort.Order order : query.getSort()) {
|
||||
|
||||
ElasticsearchPersistentProperty property = entity.getPersistentProperty(order.getProperty());
|
||||
String fieldName = property != null ? property.getFieldName() : order.getProperty();
|
||||
|
||||
FieldSortBuilder sort = SortBuilders.fieldSort(fieldName)
|
||||
.order(order.getDirection().isDescending() ? SortOrder.DESC : SortOrder.ASC);
|
||||
|
||||
if (order.getNullHandling() == Sort.NullHandling.NULLS_FIRST) {
|
||||
sort.missing("_first");
|
||||
} else if (order.getNullHandling() == Sort.NullHandling.NULLS_LAST) {
|
||||
sort.missing("_last");
|
||||
}
|
||||
|
||||
mappedSort.add(sort);
|
||||
}
|
||||
|
||||
return mappedSort;
|
||||
}
|
||||
|
||||
private QueryBuilder mappedQuery(Query query, ElasticsearchPersistentEntity<?> entity) {
|
||||
|
||||
// TODO: we need to actually map the fields to the according field names!
|
||||
|
||||
QueryBuilder elasticsearchQuery = null;
|
||||
|
||||
if (query instanceof CriteriaQuery) {
|
||||
elasticsearchQuery = new CriteriaQueryProcessor().createQueryFromCriteria(((CriteriaQuery) query).getCriteria());
|
||||
} else if (query instanceof StringQuery) {
|
||||
elasticsearchQuery = new WrapperQueryBuilder(((StringQuery) query).getSource());
|
||||
} else if (query instanceof NativeSearchQuery) {
|
||||
elasticsearchQuery = ((NativeSearchQuery) query).getQuery();
|
||||
} else {
|
||||
throw new IllegalArgumentException(String.format("Unknown query type '%s'.", query.getClass()));
|
||||
}
|
||||
|
||||
return elasticsearchQuery != null ? elasticsearchQuery : QueryBuilders.matchAllQuery();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private QueryBuilder mappedFilterQuery(Query query, ElasticsearchPersistentEntity<?> entity) {
|
||||
|
||||
if (query instanceof SearchQuery) {
|
||||
return ((SearchQuery) query).getFilter();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private ElasticsearchPersistentEntity<?> getPersistentEntity(@Nullable Class<?> type) {
|
||||
return type != null ? mappingContext.getPersistentEntity(type) : null;
|
||||
}
|
||||
|
||||
private Throwable translateException(Throwable throwable) {
|
||||
|
||||
RuntimeException exception = throwable instanceof RuntimeException ? (RuntimeException) throwable
|
||||
: new RuntimeException(throwable.getMessage(), throwable);
|
||||
RuntimeException potentiallyTranslatedException = exceptionTranslator.translateExceptionIfPossible(exception);
|
||||
|
||||
return potentiallyTranslatedException != null ? potentiallyTranslatedException : throwable;
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019-2020 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.elasticsearch.core;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.util.StreamUtils;
|
||||
|
||||
/**
|
||||
* Utility to read {@link org.springframework.core.io.Resource}s.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 3.2
|
||||
*/
|
||||
abstract class ResourceUtil {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(ResourceUtil.class);
|
||||
|
||||
/**
|
||||
* Read a {@link ClassPathResource} into a {@link String}.
|
||||
*
|
||||
* @param url
|
||||
* @return
|
||||
*/
|
||||
public static String readFileFromClasspath(String url) {
|
||||
|
||||
ClassPathResource classPathResource = new ClassPathResource(url);
|
||||
try (InputStream is = classPathResource.getInputStream()) {
|
||||
return StreamUtils.copyToString(is, Charset.defaultCharset());
|
||||
} catch (Exception e) {
|
||||
LOGGER.debug(String.format("Failed to load file from url: %s: %s", url, e.getMessage()));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Utility constructor
|
||||
private ResourceUtil() {}
|
||||
}
|
||||
@@ -15,118 +15,15 @@
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.core;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
import org.elasticsearch.index.get.GetResult;
|
||||
import org.elasticsearch.search.SearchHit;
|
||||
import org.springframework.data.elasticsearch.ElasticsearchException;
|
||||
import org.springframework.data.projection.ProjectionFactory;
|
||||
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* ResultsMapper
|
||||
*
|
||||
* @author Rizwan Idrees
|
||||
* @author Mohsin Husen
|
||||
* @author Artur Konczak
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
|
||||
public interface ResultsMapper extends SearchResultMapper, GetResultMapper, MultiGetResultMapper {
|
||||
|
||||
EntityMapper getEntityMapper();
|
||||
|
||||
/**
|
||||
* Get the configured {@link ProjectionFactory}. <br />
|
||||
* <strong>NOTE</strong> Should be overwritten in implementation to make use of the type cache.
|
||||
*
|
||||
* @since 3.2
|
||||
*/
|
||||
default ProjectionFactory getProjectionFactory() {
|
||||
return new SpelAwareProxyProjectionFactory();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
default <T> T mapEntity(String source, Class<T> clazz) {
|
||||
|
||||
if (StringUtils.isEmpty(source)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return getEntityMapper().mapToObject(source, clazz);
|
||||
} catch (IOException e) {
|
||||
throw new ElasticsearchException("failed to map source [ " + source + "] to class " + clazz.getSimpleName(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Map a single {@link GetResult} to an instance of the given type.
|
||||
*
|
||||
* @param getResult must not be {@literal null}.
|
||||
* @param type must not be {@literal null}.
|
||||
* @param <T>
|
||||
* @return can be {@literal null} if the {@link GetResult#isSourceEmpty() is empty}.
|
||||
* @since 3.2
|
||||
*/
|
||||
@Nullable
|
||||
default <T> T mapGetResult(GetResult getResult, Class<T> type) {
|
||||
|
||||
if (getResult.isSourceEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Map<String, Object> source = getResult.getSource();
|
||||
if (!source.containsKey("id") || source.get("id") == null) {
|
||||
source.put("id", getResult.getId());
|
||||
}
|
||||
|
||||
Object mappedResult = getEntityMapper().readObject(source, type);
|
||||
|
||||
if (mappedResult == null) {
|
||||
return (T) null;
|
||||
}
|
||||
|
||||
if (type.isInterface() || !ClassUtils.isAssignableValue(type, mappedResult)) {
|
||||
return getProjectionFactory().createProjection(type, mappedResult);
|
||||
}
|
||||
|
||||
return type.cast(mappedResult);
|
||||
}
|
||||
|
||||
/**
|
||||
* Map a single {@link SearchHit} to an instance of the given type.
|
||||
*
|
||||
* @param searchHit must not be {@literal null}.
|
||||
* @param type must not be {@literal null}.
|
||||
* @param <T>
|
||||
* @return can be {@literal null} if the {@link SearchHit} does not have {@link SearchHit#hasSource() a source}.
|
||||
* @since 3.2
|
||||
*/
|
||||
@Nullable
|
||||
default <T> T mapSearchHit(SearchHit searchHit, Class<T> type) {
|
||||
|
||||
if (!searchHit.hasSource()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Map<String, Object> source = searchHit.getSourceAsMap();
|
||||
if (!source.containsKey("id") || source.get("id") == null) {
|
||||
source.put("id", searchHit.getId());
|
||||
}
|
||||
|
||||
Object mappedResult = getEntityMapper().readObject(source, type);
|
||||
|
||||
if (mappedResult == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (type.isInterface()) {
|
||||
return getProjectionFactory().createProjection(type, mappedResult);
|
||||
}
|
||||
|
||||
return type.cast(mappedResult);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,14 +2,12 @@
|
||||
package org.springframework.data.elasticsearch.core;
|
||||
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* @author Artur Konczak
|
||||
* @author Peter-Josef Meisch
|
||||
* @author Sascha Woo
|
||||
*/
|
||||
public interface ScrolledPage<T> extends Page<T> {
|
||||
|
||||
String getScrollId();
|
||||
String getScrollId();
|
||||
|
||||
}
|
||||
|
||||
@@ -16,29 +16,14 @@
|
||||
package org.springframework.data.elasticsearch.core;
|
||||
|
||||
import org.elasticsearch.action.search.SearchResponse;
|
||||
import org.elasticsearch.search.SearchHit;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* @author Artur Konczak
|
||||
* @author Petar Tahchiev
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public interface SearchResultMapper {
|
||||
|
||||
<T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> clazz, Pageable pageable);
|
||||
|
||||
/**
|
||||
* Map a single {@link SearchHit} to the given {@link Class type}.
|
||||
*
|
||||
* @param searchHit must not be {@literal null}.
|
||||
* @param type must not be {@literal null}.
|
||||
* @param <T>
|
||||
* @return can be {@literal null}.
|
||||
* @since 3.2
|
||||
*/
|
||||
@Nullable
|
||||
<T> T mapSearchHit(SearchHit searchHit, Class<T> type);
|
||||
}
|
||||
|
||||
-49
@@ -1,49 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019-2020 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.elasticsearch.core;
|
||||
|
||||
import org.elasticsearch.action.search.SearchResponse;
|
||||
import org.elasticsearch.search.SearchHit;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
|
||||
|
||||
/**
|
||||
* Adapter utility for {@link SearchResultMapper} that wish to implement a subset of mapping methods. Default
|
||||
* implementations throw {@link UnsupportedOperationException}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 3.2
|
||||
*/
|
||||
abstract class SearchResultMapperAdapter implements SearchResultMapper {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.SearchResultMapper#mapResults(org.elasticsearch.action.search.SearchResponse, java.lang.Class, org.springframework.data.domain.Pageable)
|
||||
*/
|
||||
@Override
|
||||
public <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> clazz, Pageable pageable) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.SearchResultMapper#mapSearchHit(org.elasticsearch.search.SearchHit, java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public <T> T mapSearchHit(SearchHit searchHit, Class<T> type) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019-2020 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.elasticsearch.core;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.springframework.data.util.CloseableIterator;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Utility to support streaming queries.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Sascha Woo
|
||||
* @since 3.2
|
||||
*/
|
||||
abstract class StreamQueries {
|
||||
|
||||
/**
|
||||
* Stream query results using {@link ScrolledPage}.
|
||||
*
|
||||
* @param page the initial scrolled page.
|
||||
* @param continueScrollFunction function to continue scrolling applies to the current scrollId.
|
||||
* @param clearScrollConsumer consumer to clear the scroll context by accepting the current scrollId.
|
||||
* @param <T>
|
||||
* @return the {@link CloseableIterator}.
|
||||
*/
|
||||
static <T> CloseableIterator<T> streamResults(ScrolledPage<T> page,
|
||||
Function<String, ScrolledPage<T>> continueScrollFunction, Consumer<String> clearScrollConsumer) {
|
||||
|
||||
Assert.notNull(page, "page must not be null.");
|
||||
Assert.notNull(page.getScrollId(), "scrollId must not be null.");
|
||||
Assert.notNull(continueScrollFunction, "continueScrollFunction must not be null.");
|
||||
Assert.notNull(clearScrollConsumer, "clearScrollConsumer must not be null.");
|
||||
|
||||
return new CloseableIterator<T>() {
|
||||
|
||||
// As we couldn't retrieve single result with scroll, store current hits.
|
||||
private volatile Iterator<T> scrollHits = page.iterator();
|
||||
private volatile String scrollId = page.getScrollId();
|
||||
private volatile boolean continueScroll = scrollHits.hasNext();
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
try {
|
||||
clearScrollConsumer.accept(scrollId);
|
||||
} finally {
|
||||
scrollHits = null;
|
||||
scrollId = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
|
||||
if (!continueScroll) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!scrollHits.hasNext()) {
|
||||
ScrolledPage<T> nextPage = continueScrollFunction.apply(scrollId);
|
||||
scrollHits = nextPage.iterator();
|
||||
scrollId = nextPage.getScrollId();
|
||||
continueScroll = scrollHits.hasNext();
|
||||
}
|
||||
|
||||
return scrollHits.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public T next() {
|
||||
if (hasNext()) {
|
||||
return scrollHits.next();
|
||||
}
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// utility constructor
|
||||
private StreamQueries() {
|
||||
}
|
||||
}
|
||||
-34
@@ -1,34 +0,0 @@
|
||||
/*
|
||||
* Copyright 2018-2020 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.elasticsearch.core.client.support;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.Getter;
|
||||
|
||||
import org.elasticsearch.cluster.metadata.AliasMetaData;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown=true)
|
||||
@Data
|
||||
public class AliasData {
|
||||
String filter = null;
|
||||
String routing = null;
|
||||
String search_routing = null;
|
||||
String index_routing= null;
|
||||
}
|
||||
+1
@@ -30,6 +30,7 @@ import org.springframework.core.convert.converter.Converter;
|
||||
* @author Rizwan Idrees
|
||||
* @author Mohsin Husen
|
||||
*/
|
||||
|
||||
public final class DateTimeConverters {
|
||||
|
||||
private static DateTimeFormatter formatter = ISODateTimeFormat.dateTime().withZone(DateTimeZone.UTC);
|
||||
|
||||
-104
@@ -1,104 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019-2020 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.elasticsearch.core.convert;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.data.convert.DefaultTypeMapper;
|
||||
import org.springframework.data.convert.SimpleTypeInformationMapper;
|
||||
import org.springframework.data.convert.TypeAliasAccessor;
|
||||
import org.springframework.data.convert.TypeInformationMapper;
|
||||
import org.springframework.data.mapping.Alias;
|
||||
import org.springframework.data.mapping.PersistentEntity;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Elasticsearch specific {@link org.springframework.data.convert.TypeMapper} implementation.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
* @since 3.2
|
||||
*/
|
||||
public class DefaultElasticsearchTypeMapper extends DefaultTypeMapper<Map<String, Object>>
|
||||
implements ElasticsearchTypeMapper {
|
||||
|
||||
private final @Nullable String typeKey;
|
||||
|
||||
public DefaultElasticsearchTypeMapper(@Nullable String typeKey) {
|
||||
this(typeKey, Collections.singletonList(new SimpleTypeInformationMapper()));
|
||||
}
|
||||
|
||||
public DefaultElasticsearchTypeMapper(@Nullable String typeKey,
|
||||
MappingContext<? extends PersistentEntity<?, ?>, ?> mappingContext) {
|
||||
this(typeKey, new MapTypeAliasAccessor(typeKey), mappingContext,
|
||||
Collections.singletonList(new SimpleTypeInformationMapper()));
|
||||
}
|
||||
|
||||
public DefaultElasticsearchTypeMapper(@Nullable String typeKey, List<? extends TypeInformationMapper> mappers) {
|
||||
this(typeKey, new MapTypeAliasAccessor(typeKey), null, mappers);
|
||||
}
|
||||
|
||||
public DefaultElasticsearchTypeMapper(@Nullable String typeKey, TypeAliasAccessor<Map<String, Object>> accessor,
|
||||
@Nullable MappingContext<? extends PersistentEntity<?, ?>, ?> mappingContext,
|
||||
List<? extends TypeInformationMapper> mappers) {
|
||||
|
||||
super(accessor, mappingContext, mappers);
|
||||
this.typeKey = typeKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTypeKey(String key) {
|
||||
return typeKey != null && typeKey.equals(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link TypeAliasAccessor} to store aliases in a {@link Map}.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public static class MapTypeAliasAccessor implements TypeAliasAccessor<Map<String, Object>> {
|
||||
|
||||
private final @Nullable String typeKey;
|
||||
|
||||
public MapTypeAliasAccessor(@Nullable String typeKey) {
|
||||
this.typeKey = typeKey;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.convert.TypeAliasAccessor#readAliasFrom(java.lang.Object)
|
||||
*/
|
||||
public Alias readAliasFrom(Map<String, Object> source) {
|
||||
return Alias.ofNullable(source.get(typeKey));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.convert.TypeAliasAccessor#writeTypeTo(java.lang.Object, java.lang.Object)
|
||||
*/
|
||||
public void writeTypeTo(Map<String, Object> sink, Object alias) {
|
||||
|
||||
if (typeKey == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
sink.put(typeKey, alias);
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-20
@@ -19,15 +19,14 @@ import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
|
||||
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* ElasticsearchConverter
|
||||
*
|
||||
* @author Rizwan Idrees
|
||||
* @author Mohsin Husen
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
|
||||
public interface ElasticsearchConverter {
|
||||
|
||||
/**
|
||||
@@ -43,22 +42,4 @@ public interface ElasticsearchConverter {
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
ConversionService getConversionService();
|
||||
|
||||
/**
|
||||
* Convert a given {@literal idValue} to its {@link String} representation taking potentially registered
|
||||
* {@link org.springframework.core.convert.converter.Converter Converters} into account.
|
||||
*
|
||||
* @param idValue must not be {@literal null}.
|
||||
* @return never {@literal null}.
|
||||
* @since 3.2
|
||||
*/
|
||||
default String convertId(Object idValue) {
|
||||
|
||||
Assert.notNull(idValue, "idValue must not be null!");
|
||||
if (!getConversionService().canConvert(idValue.getClass(), String.class)) {
|
||||
return idValue.toString();
|
||||
}
|
||||
|
||||
return getConversionService().convert(idValue, String.class);
|
||||
}
|
||||
}
|
||||
|
||||
-121
@@ -1,121 +0,0 @@
|
||||
/*
|
||||
* Copyright 2018-2020 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.elasticsearch.core.convert;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.data.convert.CustomConversions;
|
||||
import org.springframework.data.convert.ReadingConverter;
|
||||
import org.springframework.data.convert.WritingConverter;
|
||||
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchSimpleTypes;
|
||||
import org.springframework.util.NumberUtils;
|
||||
|
||||
/**
|
||||
* Elasticsearch specific {@link CustomConversions}.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 3.2
|
||||
*/
|
||||
public class ElasticsearchCustomConversions extends CustomConversions {
|
||||
|
||||
private static final StoreConversions STORE_CONVERSIONS;
|
||||
private static final List<Object> STORE_CONVERTERS;
|
||||
|
||||
static {
|
||||
|
||||
List<Object> converters = new ArrayList<>();
|
||||
|
||||
converters.addAll(GeoConverters.getConvertersToRegister());
|
||||
converters.add(StringToUUIDConverter.INSTANCE);
|
||||
converters.add(UUIDToStringConverter.INSTANCE);
|
||||
converters.add(BigDecimalToDoubleConverter.INSTANCE);
|
||||
converters.add(DoubleToBigDecimalConverter.INSTANCE);
|
||||
|
||||
STORE_CONVERTERS = Collections.unmodifiableList(converters);
|
||||
STORE_CONVERSIONS = StoreConversions.of(ElasticsearchSimpleTypes.HOLDER, STORE_CONVERTERS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link CustomConversions} instance registering the given converters.
|
||||
*
|
||||
* @param converters must not be {@literal null}.
|
||||
*/
|
||||
public ElasticsearchCustomConversions(Collection<?> converters) {
|
||||
super(STORE_CONVERSIONS, converters);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link Converter} to read a {@link UUID} from its {@link String} representation.
|
||||
*/
|
||||
@ReadingConverter
|
||||
enum StringToUUIDConverter implements Converter<String, UUID> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public UUID convert(String source) {
|
||||
return UUID.fromString(source);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link Converter} to write a {@link UUID} to its {@link String} representation.
|
||||
*/
|
||||
@WritingConverter
|
||||
enum UUIDToStringConverter implements Converter<UUID, String> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public String convert(UUID source) {
|
||||
return source.toString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link Converter} to read a {@link BigDecimal} from a {@link Double} value.
|
||||
*/
|
||||
@ReadingConverter
|
||||
enum DoubleToBigDecimalConverter implements Converter<Double, BigDecimal> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public BigDecimal convert(Double source) {
|
||||
return NumberUtils.convertNumberToTargetClass(source, BigDecimal.class);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link Converter} to write a {@link BigDecimal} to a {@link Double} value.
|
||||
*/
|
||||
@WritingConverter
|
||||
enum BigDecimalToDoubleConverter implements Converter<BigDecimal, Double> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public Double convert(BigDecimal source) {
|
||||
return NumberUtils.convertNumberToTargetClass(source, Double.class);
|
||||
}
|
||||
}
|
||||
}
|
||||
-57
@@ -1,57 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019-2020 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.elasticsearch.core.convert;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.data.convert.TypeMapper;
|
||||
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
|
||||
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
|
||||
/**
|
||||
* Elasticsearch specific {@link TypeMapper} definition.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 3.2
|
||||
*/
|
||||
public interface ElasticsearchTypeMapper extends TypeMapper<Map<String, Object>> {
|
||||
|
||||
String DEFAULT_TYPE_KEY = "_class";
|
||||
|
||||
/**
|
||||
* Returns whether the given key is the type key.
|
||||
*
|
||||
* @return {@literal true} if given {@literal key} is used as type hint key.
|
||||
*/
|
||||
boolean isTypeKey(String key);
|
||||
|
||||
default boolean containsTypeInformation(Map<String, Object> source) {
|
||||
return readType(source) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new default {@link ElasticsearchTypeMapper}.
|
||||
*
|
||||
* @param mappingContext the mapping context.
|
||||
* @return a new default {@link ElasticsearchTypeMapper}.
|
||||
* @see DefaultElasticsearchTypeMapper
|
||||
*/
|
||||
static ElasticsearchTypeMapper create(
|
||||
MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext) {
|
||||
return new DefaultElasticsearchTypeMapper(DEFAULT_TYPE_KEY, mappingContext);
|
||||
}
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019-2020 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.elasticsearch.core.convert;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.data.convert.ReadingConverter;
|
||||
import org.springframework.data.convert.WritingConverter;
|
||||
import org.springframework.data.elasticsearch.core.geo.GeoPoint;
|
||||
import org.springframework.data.geo.Point;
|
||||
import org.springframework.util.NumberUtils;
|
||||
|
||||
/**
|
||||
* Set of {@link Converter converters} specific to Elasticsearch Geo types.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 3.2
|
||||
*/
|
||||
class GeoConverters {
|
||||
|
||||
static Collection<Object> getConvertersToRegister() {
|
||||
|
||||
return Arrays.asList(PointToMapConverter.INSTANCE, MapToPointConverter.INSTANCE, GeoPointToMapConverter.INSTANCE,
|
||||
MapToGeoPointConverter.INSTANCE);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link Converter} to write a {@link Point} to {@link Map} using {@code lat/long} properties.
|
||||
*/
|
||||
@WritingConverter
|
||||
enum PointToMapConverter implements Converter<Point, Map<String, Object>> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public Map convert(Point source) {
|
||||
|
||||
Map<String, Object> target = new LinkedHashMap<>();
|
||||
target.put("lat", source.getX());
|
||||
target.put("lon", source.getY());
|
||||
return target;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link Converter} to write a {@link GeoPoint} to {@link Map} using {@code lat/long} properties.
|
||||
*/
|
||||
@WritingConverter
|
||||
enum GeoPointToMapConverter implements Converter<GeoPoint, Map<String, Object>> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public Map convert(GeoPoint source) {
|
||||
Map<String, Object> target = new LinkedHashMap<>();
|
||||
target.put("lat", source.getLat());
|
||||
target.put("lon", source.getLon());
|
||||
return target;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link Converter} to read a {@link Point} from {@link Map} using {@code lat/long} properties.
|
||||
*/
|
||||
@ReadingConverter
|
||||
enum MapToPointConverter implements Converter<Map<String, Object>, Point> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public Point convert(Map source) {
|
||||
Double x = NumberUtils.convertNumberToTargetClass((Number) source.get("lat"), Double.class);
|
||||
Double y = NumberUtils.convertNumberToTargetClass((Number) source.get("lon"), Double.class);
|
||||
|
||||
return new Point(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link Converter} to read a {@link GeoPoint} from {@link Map} using {@code lat/long} properties.
|
||||
*/
|
||||
@ReadingConverter
|
||||
enum MapToGeoPointConverter implements Converter<Map<String, Object>, GeoPoint> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public GeoPoint convert(Map source) {
|
||||
Double x = NumberUtils.convertNumberToTargetClass((Number) source.get("lat"), Double.class);
|
||||
Double y = NumberUtils.convertNumberToTargetClass((Number) source.get("lon"), Double.class);
|
||||
|
||||
return new GeoPoint(x, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
-6
@@ -25,15 +25,9 @@ import org.springframework.data.mapping.PersistentProperty;
|
||||
* @author Mohsin Husen
|
||||
* @author Sascha Woo
|
||||
* @author Oliver Gierke
|
||||
* @author Peter-Josef Meisch
|
||||
*/
|
||||
public interface ElasticsearchPersistentProperty extends PersistentProperty<ElasticsearchPersistentProperty> {
|
||||
|
||||
/**
|
||||
* Returns the name to be used to store the property in the document.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
String getFieldName();
|
||||
|
||||
/**
|
||||
|
||||
-46
@@ -1,46 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019-2020 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.elasticsearch.core.mapping;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.data.mapping.model.SimpleTypeHolder;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
* @since 3.2
|
||||
*/
|
||||
public class ElasticsearchSimpleTypes {
|
||||
|
||||
static final Set<Class<?>> AUTOGENERATED_ID_TYPES;
|
||||
|
||||
static {
|
||||
Set<Class<?>> classes = new HashSet<>();
|
||||
classes.add(String.class);
|
||||
AUTOGENERATED_ID_TYPES = Collections.unmodifiableSet(classes);
|
||||
|
||||
Set<Class<?>> simpleTypes = new HashSet<>();
|
||||
ELASTICSEARCH_SIMPLE_TYPES = Collections.unmodifiableSet(simpleTypes);
|
||||
}
|
||||
|
||||
private static final Set<Class<?>> ELASTICSEARCH_SIMPLE_TYPES;
|
||||
public static final SimpleTypeHolder HOLDER = new SimpleTypeHolder(ELASTICSEARCH_SIMPLE_TYPES, true);
|
||||
|
||||
private ElasticsearchSimpleTypes() {}
|
||||
|
||||
}
|
||||
+4
-6
@@ -22,7 +22,6 @@ import org.springframework.data.mapping.context.AbstractMappingContext;
|
||||
import org.springframework.data.mapping.model.Property;
|
||||
import org.springframework.data.mapping.model.SimpleTypeHolder;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* SimpleElasticsearchMappingContext
|
||||
@@ -31,15 +30,14 @@ import org.springframework.lang.Nullable;
|
||||
* @author Mohsin Husen
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class SimpleElasticsearchMappingContext
|
||||
extends AbstractMappingContext<SimpleElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty>
|
||||
implements ApplicationContextAware {
|
||||
public class SimpleElasticsearchMappingContext extends
|
||||
AbstractMappingContext<SimpleElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> implements ApplicationContextAware {
|
||||
|
||||
private @Nullable ApplicationContext context;
|
||||
private ApplicationContext context;
|
||||
|
||||
@Override
|
||||
protected <T> SimpleElasticsearchPersistentEntity<?> createPersistentEntity(TypeInformation<T> typeInformation) {
|
||||
SimpleElasticsearchPersistentEntity<T> persistentEntity = new SimpleElasticsearchPersistentEntity<>(
|
||||
final SimpleElasticsearchPersistentEntity<T> persistentEntity = new SimpleElasticsearchPersistentEntity<>(
|
||||
typeInformation);
|
||||
if (context != null) {
|
||||
persistentEntity.setApplicationContext(context);
|
||||
|
||||
+22
-35
@@ -55,22 +55,21 @@ public class SimpleElasticsearchPersistentEntity<T> extends BasicPersistentEntit
|
||||
private final StandardEvaluationContext context;
|
||||
private final SpelExpressionParser parser;
|
||||
|
||||
private @Nullable String indexName;
|
||||
private @Nullable String indexType;
|
||||
private String indexName;
|
||||
private String indexType;
|
||||
private boolean useServerConfiguration;
|
||||
private short shards;
|
||||
private short replicas;
|
||||
private @Nullable String refreshInterval;
|
||||
private @Nullable String indexStoreType;
|
||||
private @Nullable String parentType;
|
||||
private @Nullable ElasticsearchPersistentProperty parentIdProperty;
|
||||
private @Nullable ElasticsearchPersistentProperty scoreProperty;
|
||||
private @Nullable String settingPath;
|
||||
private String refreshInterval;
|
||||
private String indexStoreType;
|
||||
private String parentType;
|
||||
private ElasticsearchPersistentProperty parentIdProperty;
|
||||
private ElasticsearchPersistentProperty scoreProperty;
|
||||
private String settingPath;
|
||||
private VersionType versionType;
|
||||
private boolean createIndexAndMapping;
|
||||
|
||||
public SimpleElasticsearchPersistentEntity(TypeInformation<T> typeInformation) {
|
||||
|
||||
super(typeInformation);
|
||||
this.context = new StandardEvaluationContext();
|
||||
this.parser = new SpelExpressionParser();
|
||||
@@ -104,24 +103,14 @@ public class SimpleElasticsearchPersistentEntity<T> extends BasicPersistentEntit
|
||||
|
||||
@Override
|
||||
public String getIndexName() {
|
||||
|
||||
if (indexName != null) {
|
||||
Expression expression = parser.parseExpression(indexName, ParserContext.TEMPLATE_EXPRESSION);
|
||||
return expression.getValue(context, String.class);
|
||||
}
|
||||
|
||||
return getTypeInformation().getType().getSimpleName();
|
||||
Expression expression = parser.parseExpression(indexName, ParserContext.TEMPLATE_EXPRESSION);
|
||||
return expression.getValue(context, String.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIndexType() {
|
||||
|
||||
if (indexType != null) {
|
||||
Expression expression = parser.parseExpression(indexType, ParserContext.TEMPLATE_EXPRESSION);
|
||||
return expression.getValue(context, String.class);
|
||||
}
|
||||
|
||||
return "";
|
||||
Expression expression = parser.parseExpression(indexType, ParserContext.TEMPLATE_EXPRESSION);
|
||||
return expression.getValue(context, String.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -193,10 +182,9 @@ public class SimpleElasticsearchPersistentEntity<T> extends BasicPersistentEntit
|
||||
ElasticsearchPersistentProperty parentProperty = this.parentIdProperty;
|
||||
|
||||
if (parentProperty != null) {
|
||||
throw new MappingException(String.format(
|
||||
"Attempt to add parent property %s but already have property %s registered "
|
||||
+ "as parent property. Check your mapping configuration!",
|
||||
property.getField(), parentProperty.getField()));
|
||||
throw new MappingException(
|
||||
String.format("Attempt to add parent property %s but already have property %s registered "
|
||||
+ "as parent property. Check your mapping configuration!", property.getField(), parentProperty.getField()));
|
||||
}
|
||||
|
||||
Parent parentAnnotation = property.findAnnotation(Parent.class);
|
||||
@@ -205,27 +193,26 @@ public class SimpleElasticsearchPersistentEntity<T> extends BasicPersistentEntit
|
||||
}
|
||||
|
||||
if (property.isScoreProperty()) {
|
||||
|
||||
|
||||
ElasticsearchPersistentProperty scoreProperty = this.scoreProperty;
|
||||
|
||||
if (scoreProperty != null) {
|
||||
throw new MappingException(String.format(
|
||||
"Attempt to add score property %s but already have property %s registered "
|
||||
+ "as score property. Check your mapping configuration!",
|
||||
property.getField(), scoreProperty.getField()));
|
||||
throw new MappingException(
|
||||
String.format("Attempt to add score property %s but already have property %s registered "
|
||||
+ "as score property. Check your mapping configuration!", property.getField(), scoreProperty.getField()));
|
||||
}
|
||||
|
||||
this.scoreProperty = property;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mapping.model.BasicPersistentEntity#setPersistentPropertyAccessorFactory(org.springframework.data.mapping.model.PersistentPropertyAccessorFactory)
|
||||
*/
|
||||
@Override
|
||||
public void setPersistentPropertyAccessorFactory(PersistentPropertyAccessorFactory factory) {
|
||||
|
||||
|
||||
// Do nothing to avoid the usage of ClassGeneratingPropertyAccessorFactory for now
|
||||
// DATACMNS-1322 switches to proper immutability behavior which Spring Data Elasticsearch
|
||||
// cannot yet implement
|
||||
|
||||
+5
-26
@@ -18,8 +18,6 @@ package org.springframework.data.elasticsearch.core.mapping;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.elasticsearch.annotations.Field;
|
||||
import org.springframework.data.elasticsearch.annotations.MultiField;
|
||||
import org.springframework.data.elasticsearch.annotations.Parent;
|
||||
import org.springframework.data.elasticsearch.annotations.Score;
|
||||
import org.springframework.data.mapping.Association;
|
||||
@@ -28,8 +26,6 @@ import org.springframework.data.mapping.PersistentEntity;
|
||||
import org.springframework.data.mapping.model.AnnotationBasedPersistentProperty;
|
||||
import org.springframework.data.mapping.model.Property;
|
||||
import org.springframework.data.mapping.model.SimpleTypeHolder;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Elasticsearch specific {@link org.springframework.data.mapping.PersistentProperty} implementation processing
|
||||
@@ -39,7 +35,6 @@ import org.springframework.util.StringUtils;
|
||||
* @author Mark Paluch
|
||||
* @author Sascha Woo
|
||||
* @author Oliver Gierke
|
||||
* @author Peter-Josef Meisch
|
||||
*/
|
||||
public class SimpleElasticsearchPersistentProperty extends
|
||||
AnnotationBasedPersistentProperty<ElasticsearchPersistentProperty> implements ElasticsearchPersistentProperty {
|
||||
@@ -49,53 +44,37 @@ public class SimpleElasticsearchPersistentProperty extends
|
||||
private final boolean isScore;
|
||||
private final boolean isParent;
|
||||
private final boolean isId;
|
||||
private final @Nullable String annotatedFieldName;
|
||||
|
||||
public SimpleElasticsearchPersistentProperty(Property property,
|
||||
PersistentEntity<?, ElasticsearchPersistentProperty> owner, SimpleTypeHolder simpleTypeHolder) {
|
||||
|
||||
super(property, owner, simpleTypeHolder);
|
||||
|
||||
this.annotatedFieldName = getAnnotatedFieldName();
|
||||
this.isId = super.isIdProperty() || SUPPORTED_ID_PROPERTY_NAMES.contains(getFieldName());
|
||||
this.isScore = isAnnotationPresent(Score.class);
|
||||
this.isParent = isAnnotationPresent(Parent.class);
|
||||
|
||||
if (isVersionProperty() && !getType().equals(Long.class)) {
|
||||
if (isVersionProperty() && getType() != Long.class) {
|
||||
throw new MappingException(String.format("Version property %s must be of type Long!", property.getName()));
|
||||
}
|
||||
|
||||
if (isScore && !getType().equals(Float.TYPE) && !getType().equals(Float.class)) {
|
||||
if (isScore && !Arrays.asList(Float.TYPE, Float.class).contains(getType())) {
|
||||
throw new MappingException(
|
||||
String.format("Score property %s must be either of type float or Float!", property.getName()));
|
||||
}
|
||||
|
||||
if (isParent && !getType().equals(String.class)) {
|
||||
if (isParent && getType() != String.class) {
|
||||
throw new MappingException(String.format("Parent property %s must be of type String!", property.getName()));
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private String getAnnotatedFieldName() {
|
||||
|
||||
String name = null;
|
||||
|
||||
if (isAnnotationPresent(MultiField.class)) {
|
||||
name = findAnnotation(MultiField.class).mainField().name();
|
||||
} else if (isAnnotationPresent(Field.class)) {
|
||||
name = findAnnotation(Field.class).name();
|
||||
}
|
||||
|
||||
return StringUtils.hasText(name) ? name : null;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty#getFieldName()
|
||||
*/
|
||||
@Override
|
||||
public String getFieldName() {
|
||||
return annotatedFieldName == null ? getProperty().getName() : annotatedFieldName;
|
||||
return getProperty().getName();
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -125,7 +104,7 @@ public class SimpleElasticsearchPersistentProperty extends
|
||||
return isScore;
|
||||
}
|
||||
|
||||
/*
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mapping.model.AbstractPersistentProperty#isImmutable()
|
||||
*/
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
|
||||
/**
|
||||
* Infrastructure for the Elasticsearch document-to-object mapping subsystem.
|
||||
*/
|
||||
@org.springframework.lang.NonNullApi
|
||||
package org.springframework.data.elasticsearch.core.mapping;
|
||||
@@ -35,7 +35,6 @@ import org.springframework.util.Assert;
|
||||
* @author Mark Paluch
|
||||
* @author Alen Turkovic
|
||||
* @author Sascha Woo
|
||||
* @author Farid Azaza
|
||||
*/
|
||||
abstract class AbstractQuery implements Query {
|
||||
|
||||
@@ -51,7 +50,6 @@ abstract class AbstractQuery implements Query {
|
||||
protected SearchType searchType = SearchType.DFS_QUERY_THEN_FETCH;
|
||||
protected IndicesOptions indicesOptions;
|
||||
protected boolean trackScores;
|
||||
protected String preference;
|
||||
|
||||
@Override
|
||||
public Sort getSort() {
|
||||
@@ -65,9 +63,9 @@ abstract class AbstractQuery implements Query {
|
||||
|
||||
@Override
|
||||
public final <T extends Query> T setPageable(Pageable pageable) {
|
||||
|
||||
|
||||
Assert.notNull(pageable, "Pageable must not be null!");
|
||||
|
||||
|
||||
this.pageable = pageable;
|
||||
return (T) this.addSort(pageable.getSort());
|
||||
}
|
||||
@@ -185,14 +183,4 @@ abstract class AbstractQuery implements Query {
|
||||
public void setTrackScores(boolean trackScores) {
|
||||
this.trackScores = trackScores;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPreference() {
|
||||
return preference;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPreference(String preference) {
|
||||
this.preference = preference;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,140 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019-2020 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.elasticsearch.core.query;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.elasticsearch.action.support.ActiveShardCount;
|
||||
import org.elasticsearch.action.support.WriteRequest;
|
||||
import org.elasticsearch.common.unit.TimeValue;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Options that may be passed to an
|
||||
* {@link org.springframework.data.elasticsearch.core.ElasticsearchOperations#bulkIndex(List, BulkOptions)} or
|
||||
* {@link org.springframework.data.elasticsearch.core.ElasticsearchOperations#bulkUpdate(List, BulkOptions)} call. <br/>
|
||||
* Use {@link BulkOptions#builder()} to obtain a builder, then set the desired properties and call
|
||||
* {@link BulkOptionsBuilder#build()} to get the BulkOptions object.
|
||||
*
|
||||
* @author Peter-Josef Meisch
|
||||
* @author Mark Paluch
|
||||
* @since 3.2
|
||||
*/
|
||||
public class BulkOptions {
|
||||
|
||||
private static final BulkOptions defaultOptions = builder().build();
|
||||
|
||||
private final @Nullable TimeValue timeout;
|
||||
private final @Nullable WriteRequest.RefreshPolicy refreshPolicy;
|
||||
private final @Nullable ActiveShardCount waitForActiveShards;
|
||||
private final @Nullable String pipeline;
|
||||
private final @Nullable String routingId;
|
||||
|
||||
private BulkOptions(@Nullable TimeValue timeout, @Nullable WriteRequest.RefreshPolicy refreshPolicy,
|
||||
@Nullable ActiveShardCount waitForActiveShards, @Nullable String pipeline, @Nullable String routingId) {
|
||||
this.timeout = timeout;
|
||||
this.refreshPolicy = refreshPolicy;
|
||||
this.waitForActiveShards = waitForActiveShards;
|
||||
this.pipeline = pipeline;
|
||||
this.routingId = routingId;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public TimeValue getTimeout() {
|
||||
return timeout;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public WriteRequest.RefreshPolicy getRefreshPolicy() {
|
||||
return refreshPolicy;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public ActiveShardCount getWaitForActiveShards() {
|
||||
return waitForActiveShards;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getPipeline() {
|
||||
return pipeline;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getRoutingId() {
|
||||
return routingId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link BulkOptionsBuilder} to build {@link BulkOptions}.
|
||||
*
|
||||
* @return a new {@link BulkOptionsBuilder} to build {@link BulkOptions}.
|
||||
*/
|
||||
public static BulkOptionsBuilder builder() {
|
||||
return new BulkOptionsBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return default {@link BulkOptions}.
|
||||
*
|
||||
* @return default {@link BulkOptions}.
|
||||
*/
|
||||
public static BulkOptions defaultOptions() {
|
||||
return defaultOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder for {@link BulkOptions}.
|
||||
*/
|
||||
public static class BulkOptionsBuilder {
|
||||
|
||||
private @Nullable TimeValue timeout;
|
||||
private @Nullable WriteRequest.RefreshPolicy refreshPolicy;
|
||||
private @Nullable ActiveShardCount waitForActiveShards;
|
||||
private @Nullable String pipeline;
|
||||
private @Nullable String routingId;
|
||||
|
||||
private BulkOptionsBuilder() {}
|
||||
|
||||
public BulkOptionsBuilder withTimeout(TimeValue timeout) {
|
||||
this.timeout = timeout;
|
||||
return this;
|
||||
}
|
||||
|
||||
public BulkOptionsBuilder withRefreshPolicy(WriteRequest.RefreshPolicy refreshPolicy) {
|
||||
this.refreshPolicy = refreshPolicy;
|
||||
return this;
|
||||
}
|
||||
|
||||
public BulkOptionsBuilder withWaitForActiveShards(ActiveShardCount waitForActiveShards) {
|
||||
this.waitForActiveShards = waitForActiveShards;
|
||||
return this;
|
||||
}
|
||||
|
||||
public BulkOptionsBuilder withPipeline(String pipeline) {
|
||||
this.pipeline = pipeline;
|
||||
return this;
|
||||
}
|
||||
|
||||
public BulkOptionsBuilder withRoutingId(String routingId) {
|
||||
this.routingId = routingId;
|
||||
return this;
|
||||
}
|
||||
|
||||
public BulkOptions build() {
|
||||
return new BulkOptions(timeout, refreshPolicy, waitForActiveShards, pipeline, routingId);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,13 +15,10 @@
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.core.query;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.*;
|
||||
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
import org.springframework.data.elasticsearch.core.geo.GeoBox;
|
||||
import org.springframework.data.elasticsearch.core.geo.GeoPoint;
|
||||
@@ -29,8 +26,6 @@ import org.springframework.data.geo.Box;
|
||||
import org.springframework.data.geo.Distance;
|
||||
import org.springframework.data.geo.Point;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Criteria is the central class when constructing queries. It follows more or less a fluent API style, which allows to
|
||||
@@ -44,9 +39,13 @@ public class Criteria {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Criteria{" + "field=" + field.getName() + ", boost=" + boost + ", negating=" + negating + ", queryCriteria="
|
||||
+ ObjectUtils.nullSafeToString(queryCriteria) + ", filterCriteria="
|
||||
+ ObjectUtils.nullSafeToString(filterCriteria) + '}';
|
||||
return "Criteria{" +
|
||||
"field=" + field.getName() +
|
||||
", boost=" + boost +
|
||||
", negating=" + negating +
|
||||
", queryCriteria=" + ObjectUtils.nullSafeToString(queryCriteria) +
|
||||
", filterCriteria=" + ObjectUtils.nullSafeToString(filterCriteria) +
|
||||
'}';
|
||||
}
|
||||
|
||||
public static final String WILDCARD = "*";
|
||||
@@ -65,7 +64,8 @@ public class Criteria {
|
||||
|
||||
private Set<CriteriaEntry> filterCriteria = new LinkedHashSet<>();
|
||||
|
||||
public Criteria() {}
|
||||
public Criteria() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Criteria with provided field name
|
||||
@@ -305,7 +305,7 @@ public class Criteria {
|
||||
throw new InvalidDataAccessApiUsageException("Range [* TO *] is not allowed");
|
||||
}
|
||||
|
||||
queryCriteria.add(new CriteriaEntry(OperationKey.BETWEEN, new Object[] { lowerBound, upperBound }));
|
||||
queryCriteria.add(new CriteriaEntry(OperationKey.BETWEEN, new Object[]{lowerBound, upperBound}));
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -376,6 +376,11 @@ public class Criteria {
|
||||
}
|
||||
|
||||
private List<Object> toCollection(Object... values) {
|
||||
if (values.length == 0 || (values.length > 1 && values[1] instanceof Collection)) {
|
||||
throw new InvalidDataAccessApiUsageException("At least one element "
|
||||
+ (values.length > 0 ? ("of argument of type " + values[1].getClass().getName()) : "")
|
||||
+ " has to be present.");
|
||||
}
|
||||
return Arrays.asList(values);
|
||||
}
|
||||
|
||||
@@ -393,14 +398,15 @@ public class Criteria {
|
||||
* Creates new CriteriaEntry for {@code location WITHIN distance}
|
||||
*
|
||||
* @param location {@link org.springframework.data.elasticsearch.core.geo.GeoPoint} center coordinates
|
||||
* @param distance {@link String} radius as a string (e.g. : '100km'). Distance unit : either mi/miles or km can be
|
||||
* set
|
||||
* @param distance {@link String} radius as a string (e.g. : '100km').
|
||||
* Distance unit :
|
||||
* either mi/miles or km can be set
|
||||
* @return Criteria the chaind criteria with the new 'within' criteria included.
|
||||
*/
|
||||
public Criteria within(GeoPoint location, String distance) {
|
||||
Assert.notNull(location, "Location value for near criteria must not be null");
|
||||
Assert.notNull(location, "Distance value for near criteria must not be null");
|
||||
filterCriteria.add(new CriteriaEntry(OperationKey.WITHIN, new Object[] { location, distance }));
|
||||
filterCriteria.add(new CriteriaEntry(OperationKey.WITHIN, new Object[]{location, distance}));
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -408,54 +414,56 @@ public class Criteria {
|
||||
* Creates new CriteriaEntry for {@code location WITHIN distance}
|
||||
*
|
||||
* @param location {@link org.springframework.data.geo.Point} center coordinates
|
||||
* @param distance {@link org.springframework.data.geo.Distance} radius .
|
||||
* @param distance {@link org.springframework.data.geo.Distance} radius
|
||||
* .
|
||||
* @return Criteria the chaind criteria with the new 'within' criteria included.
|
||||
*/
|
||||
public Criteria within(Point location, Distance distance) {
|
||||
Assert.notNull(location, "Location value for near criteria must not be null");
|
||||
Assert.notNull(location, "Distance value for near criteria must not be null");
|
||||
filterCriteria.add(new CriteriaEntry(OperationKey.WITHIN, new Object[] { location, distance }));
|
||||
filterCriteria.add(new CriteriaEntry(OperationKey.WITHIN, new Object[]{location, distance}));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new CriteriaEntry for {@code geoLocation WITHIN distance}
|
||||
*
|
||||
* @param geoLocation {@link String} center point supported formats: lat on = > "41.2,45.1", geohash = > "asd9as0d"
|
||||
* @param distance {@link String} radius as a string (e.g. : '100km'). Distance unit : either mi/miles or km can be
|
||||
* set
|
||||
* @param geoLocation {@link String} center point
|
||||
* supported formats:
|
||||
* lat on = > "41.2,45.1",
|
||||
* geohash = > "asd9as0d"
|
||||
* @param distance {@link String} radius as a string (e.g. : '100km').
|
||||
* Distance unit :
|
||||
* either mi/miles or km can be set
|
||||
* @return
|
||||
*/
|
||||
public Criteria within(String geoLocation, String distance) {
|
||||
Assert.isTrue(!StringUtils.isEmpty(geoLocation), "geoLocation value must not be null");
|
||||
filterCriteria.add(new CriteriaEntry(OperationKey.WITHIN, new Object[] { geoLocation, distance }));
|
||||
filterCriteria.add(new CriteriaEntry(OperationKey.WITHIN, new Object[]{geoLocation, distance}));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new CriteriaEntry for {@code location GeoBox bounding box}
|
||||
*
|
||||
* @param boundingBox {@link org.springframework.data.elasticsearch.core.geo.GeoBox} bounding box(left top corner +
|
||||
* right bottom corner)
|
||||
* @param boundingBox {@link org.springframework.data.elasticsearch.core.geo.GeoBox} bounding box(left top corner + right bottom corner)
|
||||
* @return Criteria the chaind criteria with the new 'boundingBox' criteria included.
|
||||
*/
|
||||
public Criteria boundedBy(GeoBox boundingBox) {
|
||||
Assert.notNull(boundingBox, "boundingBox value for boundedBy criteria must not be null");
|
||||
filterCriteria.add(new CriteriaEntry(OperationKey.BBOX, new Object[] { boundingBox }));
|
||||
filterCriteria.add(new CriteriaEntry(OperationKey.BBOX, new Object[]{boundingBox}));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new CriteriaEntry for {@code location Box bounding box}
|
||||
*
|
||||
* @param boundingBox {@link org.springframework.data.elasticsearch.core.geo.GeoBox} bounding box(left top corner +
|
||||
* right bottom corner)
|
||||
* @param boundingBox {@link org.springframework.data.elasticsearch.core.geo.GeoBox} bounding box(left top corner + right bottom corner)
|
||||
* @return Criteria the chaind criteria with the new 'boundingBox' criteria included.
|
||||
*/
|
||||
public Criteria boundedBy(Box boundingBox) {
|
||||
Assert.notNull(boundingBox, "boundingBox value for boundedBy criteria must not be null");
|
||||
filterCriteria
|
||||
.add(new CriteriaEntry(OperationKey.BBOX, new Object[] { boundingBox.getFirst(), boundingBox.getSecond() }));
|
||||
filterCriteria.add(new CriteriaEntry(OperationKey.BBOX, new Object[]{boundingBox.getFirst(), boundingBox.getSecond()}));
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -469,7 +477,7 @@ public class Criteria {
|
||||
public Criteria boundedBy(String topLeftGeohash, String bottomRightGeohash) {
|
||||
Assert.isTrue(!StringUtils.isEmpty(topLeftGeohash), "topLeftGeohash must not be empty");
|
||||
Assert.isTrue(!StringUtils.isEmpty(bottomRightGeohash), "bottomRightGeohash must not be empty");
|
||||
filterCriteria.add(new CriteriaEntry(OperationKey.BBOX, new Object[] { topLeftGeohash, bottomRightGeohash }));
|
||||
filterCriteria.add(new CriteriaEntry(OperationKey.BBOX, new Object[]{topLeftGeohash, bottomRightGeohash}));
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -483,15 +491,14 @@ public class Criteria {
|
||||
public Criteria boundedBy(GeoPoint topLeftPoint, GeoPoint bottomRightPoint) {
|
||||
Assert.notNull(topLeftPoint, "topLeftPoint must not be null");
|
||||
Assert.notNull(bottomRightPoint, "bottomRightPoint must not be null");
|
||||
filterCriteria.add(new CriteriaEntry(OperationKey.BBOX, new Object[] { topLeftPoint, bottomRightPoint }));
|
||||
filterCriteria.add(new CriteriaEntry(OperationKey.BBOX, new Object[]{topLeftPoint, bottomRightPoint}));
|
||||
return this;
|
||||
}
|
||||
|
||||
public Criteria boundedBy(Point topLeftPoint, Point bottomRightPoint) {
|
||||
Assert.notNull(topLeftPoint, "topLeftPoint must not be null");
|
||||
Assert.notNull(bottomRightPoint, "bottomRightPoint must not be null");
|
||||
filterCriteria.add(new CriteriaEntry(OperationKey.BBOX,
|
||||
new Object[] { GeoPoint.fromPoint(topLeftPoint), GeoPoint.fromPoint(bottomRightPoint) }));
|
||||
filterCriteria.add(new CriteriaEntry(OperationKey.BBOX, new Object[]{GeoPoint.fromPoint(topLeftPoint), GeoPoint.fromPoint(bottomRightPoint)}));
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -604,7 +611,10 @@ public class Criteria {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "CriteriaEntry{" + "key=" + key + ", value=" + value + '}';
|
||||
return "CriteriaEntry{" +
|
||||
"key=" + key +
|
||||
", value=" + value +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,11 +32,4 @@ public class GetQuery {
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public static GetQuery getById(String id) {
|
||||
|
||||
GetQuery query = new GetQuery();
|
||||
query.setId(id);
|
||||
return query;
|
||||
}
|
||||
}
|
||||
|
||||
+14
-35
@@ -16,16 +16,16 @@
|
||||
package org.springframework.data.elasticsearch.core.query;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.elasticsearch.index.query.QueryBuilder;
|
||||
import org.elasticsearch.search.aggregations.AbstractAggregationBuilder;
|
||||
import org.elasticsearch.search.collapse.CollapseBuilder;
|
||||
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
|
||||
import org.elasticsearch.search.sort.SortBuilder;
|
||||
import org.springframework.data.elasticsearch.core.facet.FacetRequest;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* NativeSearchQuery
|
||||
*
|
||||
@@ -33,42 +33,36 @@ import org.springframework.data.elasticsearch.core.facet.FacetRequest;
|
||||
* @author Mohsin Husen
|
||||
* @author Artur Konczak
|
||||
* @author Jean-Baptiste Nizet
|
||||
* @author Martin Choraine
|
||||
*/
|
||||
public class NativeSearchQuery extends AbstractQuery implements SearchQuery {
|
||||
|
||||
private QueryBuilder query;
|
||||
private QueryBuilder filter;
|
||||
private List<SortBuilder> sorts;
|
||||
private final List<ScriptField> scriptFields = new ArrayList<>();
|
||||
private CollapseBuilder collapseBuilder;
|
||||
private final List<ScriptField> scriptFields = new ArrayList<>();
|
||||
private List<FacetRequest> facets;
|
||||
private List<AbstractAggregationBuilder> aggregations;
|
||||
private HighlightBuilder highlightBuilder;
|
||||
private HighlightBuilder.Field[] highlightFields;
|
||||
private List<IndexBoost> indicesBoost;
|
||||
|
||||
public NativeSearchQuery(QueryBuilder query) {
|
||||
|
||||
public NativeSearchQuery(QueryBuilder query) {
|
||||
this.query = query;
|
||||
}
|
||||
|
||||
public NativeSearchQuery(QueryBuilder query, QueryBuilder filter) {
|
||||
|
||||
this.query = query;
|
||||
this.filter = filter;
|
||||
}
|
||||
|
||||
public NativeSearchQuery(QueryBuilder query, QueryBuilder filter, List<SortBuilder> sorts) {
|
||||
|
||||
this.query = query;
|
||||
this.filter = filter;
|
||||
this.sorts = sorts;
|
||||
}
|
||||
|
||||
public NativeSearchQuery(QueryBuilder query, QueryBuilder filter, List<SortBuilder> sorts,
|
||||
HighlightBuilder.Field[] highlightFields) {
|
||||
|
||||
public NativeSearchQuery(QueryBuilder query, QueryBuilder filter, List<SortBuilder> sorts, HighlightBuilder.Field[] highlightFields) {
|
||||
this.query = query;
|
||||
this.filter = filter;
|
||||
this.sorts = sorts;
|
||||
@@ -77,7 +71,6 @@ public class NativeSearchQuery extends AbstractQuery implements SearchQuery {
|
||||
|
||||
public NativeSearchQuery(QueryBuilder query, QueryBuilder filter, List<SortBuilder> sorts,
|
||||
HighlightBuilder highlighBuilder, HighlightBuilder.Field[] highlightFields) {
|
||||
|
||||
this.query = query;
|
||||
this.filter = filter;
|
||||
this.sorts = sorts;
|
||||
@@ -107,34 +100,21 @@ public class NativeSearchQuery extends AbstractQuery implements SearchQuery {
|
||||
return highlightFields;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ScriptField> getScriptFields() {
|
||||
return scriptFields;
|
||||
}
|
||||
@Override
|
||||
public List<ScriptField> getScriptFields() { return scriptFields; }
|
||||
|
||||
public void setScriptFields(List<ScriptField> scriptFields) {
|
||||
this.scriptFields.addAll(scriptFields);
|
||||
}
|
||||
public void setScriptFields(List<ScriptField> scriptFields) {
|
||||
this.scriptFields.addAll(scriptFields);
|
||||
}
|
||||
|
||||
public void addScriptField(ScriptField... scriptField) {
|
||||
scriptFields.addAll(Arrays.asList(scriptField));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CollapseBuilder getCollapseBuilder() {
|
||||
return collapseBuilder;
|
||||
}
|
||||
|
||||
public void setCollapseBuilder(CollapseBuilder collapseBuilder) {
|
||||
this.collapseBuilder = collapseBuilder;
|
||||
}
|
||||
public void addScriptField(ScriptField... scriptField) {
|
||||
scriptFields.addAll(Arrays.asList(scriptField));
|
||||
}
|
||||
|
||||
public void addFacet(FacetRequest facetRequest) {
|
||||
|
||||
if (facets == null) {
|
||||
facets = new ArrayList<>();
|
||||
}
|
||||
|
||||
facets.add(facetRequest);
|
||||
}
|
||||
|
||||
@@ -152,12 +132,11 @@ public class NativeSearchQuery extends AbstractQuery implements SearchQuery {
|
||||
return aggregations;
|
||||
}
|
||||
|
||||
public void addAggregation(AbstractAggregationBuilder aggregationBuilder) {
|
||||
|
||||
public void addAggregation(AbstractAggregationBuilder aggregationBuilder) {
|
||||
if (aggregations == null) {
|
||||
aggregations = new ArrayList<>();
|
||||
}
|
||||
|
||||
aggregations.add(aggregationBuilder);
|
||||
}
|
||||
|
||||
|
||||
+1
-25
@@ -15,17 +15,14 @@
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.core.query;
|
||||
|
||||
import static org.springframework.util.CollectionUtils.*;
|
||||
|
||||
import static org.springframework.util.CollectionUtils.isEmpty;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.elasticsearch.action.search.SearchType;
|
||||
import org.elasticsearch.action.support.IndicesOptions;
|
||||
import org.elasticsearch.index.query.QueryBuilder;
|
||||
import org.elasticsearch.search.aggregations.AbstractAggregationBuilder;
|
||||
import org.elasticsearch.search.collapse.CollapseBuilder;
|
||||
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
|
||||
import org.elasticsearch.search.sort.SortBuilder;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
@@ -41,8 +38,6 @@ import org.springframework.data.elasticsearch.core.facet.FacetRequest;
|
||||
* @author Alen Turkovic
|
||||
* @author Sascha Woo
|
||||
* @author Jean-Baptiste Nizet
|
||||
* @author Martin Choraine
|
||||
* @author Farid Azaza
|
||||
*/
|
||||
public class NativeSearchQueryBuilder {
|
||||
|
||||
@@ -59,7 +54,6 @@ public class NativeSearchQueryBuilder {
|
||||
private String[] types;
|
||||
private String[] fields;
|
||||
private SourceFilter sourceFilter;
|
||||
private CollapseBuilder collapseBuilder;
|
||||
private List<IndexBoost> indicesBoost;
|
||||
private float minScore;
|
||||
private boolean trackScores;
|
||||
@@ -67,7 +61,6 @@ public class NativeSearchQueryBuilder {
|
||||
private String route;
|
||||
private SearchType searchType;
|
||||
private IndicesOptions indicesOptions;
|
||||
private String preference;
|
||||
|
||||
public NativeSearchQueryBuilder withQuery(QueryBuilder queryBuilder) {
|
||||
this.queryBuilder = queryBuilder;
|
||||
@@ -89,11 +82,6 @@ public class NativeSearchQueryBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
public NativeSearchQueryBuilder withCollapseField(String collapseField) {
|
||||
this.collapseBuilder = new CollapseBuilder(collapseField);
|
||||
return this;
|
||||
}
|
||||
|
||||
public NativeSearchQueryBuilder addAggregation(AbstractAggregationBuilder aggregationBuilder) {
|
||||
this.aggregationBuilders.add(aggregationBuilder);
|
||||
return this;
|
||||
@@ -179,11 +167,6 @@ public class NativeSearchQueryBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
public NativeSearchQueryBuilder withPreference(String preference) {
|
||||
this.preference = preference;
|
||||
return this;
|
||||
}
|
||||
|
||||
public NativeSearchQuery build() {
|
||||
NativeSearchQuery nativeSearchQuery = new NativeSearchQuery(queryBuilder, filterBuilder, sortBuilders,
|
||||
highlightBuilder, highlightFields);
|
||||
@@ -215,10 +198,6 @@ public class NativeSearchQueryBuilder {
|
||||
nativeSearchQuery.setScriptFields(scriptFields);
|
||||
}
|
||||
|
||||
if (collapseBuilder != null) {
|
||||
nativeSearchQuery.setCollapseBuilder(collapseBuilder);
|
||||
}
|
||||
|
||||
if (!isEmpty(facetRequests)) {
|
||||
nativeSearchQuery.setFacets(facetRequests);
|
||||
}
|
||||
@@ -246,9 +225,6 @@ public class NativeSearchQueryBuilder {
|
||||
if (indicesOptions != null) {
|
||||
nativeSearchQuery.setIndicesOptions(indicesOptions);
|
||||
}
|
||||
if (preference != null) {
|
||||
nativeSearchQuery.setPreference(preference);
|
||||
}
|
||||
|
||||
return nativeSearchQuery;
|
||||
}
|
||||
|
||||
@@ -17,10 +17,8 @@ package org.springframework.data.elasticsearch.core.query;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.elasticsearch.action.search.SearchType;
|
||||
import org.elasticsearch.action.support.IndicesOptions;
|
||||
import org.elasticsearch.index.query.QueryBuilders;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
@@ -33,25 +31,12 @@ import org.springframework.data.domain.Sort;
|
||||
* @author Mark Paluch
|
||||
* @author Alen Turkovic
|
||||
* @author Sascha Woo
|
||||
* @author Christoph Strobl
|
||||
* @author Farid Azaza
|
||||
*/
|
||||
public interface Query {
|
||||
|
||||
int DEFAULT_PAGE_SIZE = 10;
|
||||
Pageable DEFAULT_PAGE = PageRequest.of(0, DEFAULT_PAGE_SIZE);
|
||||
|
||||
/**
|
||||
* Get get a {@link Query} that matches all documents in the index.
|
||||
*
|
||||
* @return new instance of {@link Query}.
|
||||
* @since 3.2
|
||||
* @see QueryBuilders#matchAllQuery()
|
||||
*/
|
||||
static Query findAll() {
|
||||
return new StringQuery(QueryBuilders.matchAllQuery().toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* restrict result to entries on given page. Corresponds to the 'start' and 'rows' parameter in elasticsearch
|
||||
*
|
||||
@@ -178,20 +163,4 @@ public interface Query {
|
||||
* @return null if not set
|
||||
*/
|
||||
IndicesOptions getIndicesOptions();
|
||||
|
||||
/**
|
||||
* Get preference
|
||||
*
|
||||
* @return
|
||||
* @since 3.2
|
||||
*/
|
||||
String getPreference();
|
||||
|
||||
/**
|
||||
* Add preference filter to be added as part of search request
|
||||
*
|
||||
* @param preference
|
||||
* @since 3.2
|
||||
*/
|
||||
void setPreference(String preference);
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ import java.util.List;
|
||||
|
||||
import org.elasticsearch.index.query.QueryBuilder;
|
||||
import org.elasticsearch.search.aggregations.AbstractAggregationBuilder;
|
||||
import org.elasticsearch.search.collapse.CollapseBuilder;
|
||||
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
|
||||
import org.elasticsearch.search.sort.SortBuilder;
|
||||
import org.springframework.data.elasticsearch.core.facet.FacetRequest;
|
||||
@@ -31,7 +30,6 @@ import org.springframework.data.elasticsearch.core.facet.FacetRequest;
|
||||
* @author Mohsin Husen
|
||||
* @author Artur Konczak
|
||||
* @author Jean-Baptiste Nizet
|
||||
* @author Martin Choraine
|
||||
*/
|
||||
public interface SearchQuery extends Query {
|
||||
|
||||
@@ -52,8 +50,6 @@ public interface SearchQuery extends Query {
|
||||
|
||||
List<IndexBoost> getIndicesBoost();
|
||||
|
||||
List<ScriptField> getScriptFields();
|
||||
|
||||
CollapseBuilder getCollapseBuilder();
|
||||
List<ScriptField> getScriptFields();
|
||||
|
||||
}
|
||||
|
||||
+3
-2
@@ -15,6 +15,8 @@
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.repository;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import org.springframework.data.repository.NoRepositoryBean;
|
||||
import org.springframework.data.repository.PagingAndSortingRepository;
|
||||
|
||||
@@ -24,9 +26,8 @@ import org.springframework.data.repository.PagingAndSortingRepository;
|
||||
* @author Rizwan Idrees
|
||||
* @author Mohsin Husen
|
||||
* @author Oliver Gierke
|
||||
* @author Sascha Woo
|
||||
*/
|
||||
@NoRepositoryBean
|
||||
public interface ElasticsearchCrudRepository<T, ID> extends PagingAndSortingRepository<T, ID> {
|
||||
public interface ElasticsearchCrudRepository<T, ID extends Serializable> extends PagingAndSortingRepository<T, ID> {
|
||||
|
||||
}
|
||||
|
||||
+3
-10
@@ -15,6 +15,8 @@
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.repository;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import org.elasticsearch.index.query.QueryBuilder;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
@@ -26,21 +28,12 @@ import org.springframework.data.repository.NoRepositoryBean;
|
||||
* @param <ID>
|
||||
* @author Rizwan Idrees
|
||||
* @author Mohsin Husen
|
||||
* @author Sascha Woo
|
||||
* @author Murali Chevuri
|
||||
*/
|
||||
@NoRepositoryBean
|
||||
public interface ElasticsearchRepository<T, ID> extends ElasticsearchCrudRepository<T, ID> {
|
||||
public interface ElasticsearchRepository<T, ID extends Serializable> extends ElasticsearchCrudRepository<T, ID> {
|
||||
|
||||
<S extends T> S index(S entity);
|
||||
|
||||
/**
|
||||
* This method is intended to be used when many single inserts must be made that cannot be aggregated to be inserted
|
||||
* with {@link #saveAll(Iterable)}. This might lead to a temporary inconsistent state until {@link #refresh()} is
|
||||
* called.
|
||||
*/
|
||||
<S extends T> S indexWithoutRefresh(S entity);
|
||||
|
||||
Iterable<T> search(QueryBuilder query);
|
||||
|
||||
Page<T> search(QueryBuilder query, Pageable pageable);
|
||||
|
||||
-30
@@ -1,30 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019-2020 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.elasticsearch.repository;
|
||||
|
||||
import org.springframework.data.repository.NoRepositoryBean;
|
||||
import org.springframework.data.repository.reactive.ReactiveSortingRepository;
|
||||
|
||||
/**
|
||||
* Elasticsearch specific {@link org.springframework.data.repository.Repository} interface with reactive support.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 3.2
|
||||
*/
|
||||
@NoRepositoryBean
|
||||
public interface ReactiveElasticsearchRepository<T, ID> extends ReactiveSortingRepository<T, ID> {
|
||||
|
||||
}
|
||||
+2
-13
@@ -29,7 +29,6 @@ import org.springframework.data.elasticsearch.repository.support.ElasticsearchRe
|
||||
import org.springframework.data.repository.config.AnnotationRepositoryConfigurationSource;
|
||||
import org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport;
|
||||
import org.springframework.data.repository.config.XmlRepositoryConfigurationSource;
|
||||
import org.springframework.data.repository.core.RepositoryMetadata;
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
/**
|
||||
@@ -40,7 +39,6 @@ import org.w3c.dom.Element;
|
||||
* @author Rizwan Idrees
|
||||
* @author Mohsin Husen
|
||||
* @author Mark Paluch
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public class ElasticsearchRepositoryConfigExtension extends RepositoryConfigurationExtensionSupport {
|
||||
|
||||
@@ -90,7 +88,7 @@ public class ElasticsearchRepositoryConfigExtension extends RepositoryConfigurat
|
||||
*/
|
||||
@Override
|
||||
protected Collection<Class<? extends Annotation>> getIdentifyingAnnotations() {
|
||||
return Collections.singleton(Document.class);
|
||||
return Collections.<Class<? extends Annotation>> singleton(Document.class);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -99,15 +97,6 @@ public class ElasticsearchRepositoryConfigExtension extends RepositoryConfigurat
|
||||
*/
|
||||
@Override
|
||||
protected Collection<Class<?>> getIdentifyingTypes() {
|
||||
return Arrays.asList(ElasticsearchRepository.class, ElasticsearchCrudRepository.class);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport#useRepositoryConfiguration(org.springframework.data.repository.core.RepositoryMetadata)
|
||||
*/
|
||||
@Override
|
||||
protected boolean useRepositoryConfiguration(RepositoryMetadata metadata) {
|
||||
return !metadata.isReactiveRepository();
|
||||
return Arrays.<Class<?>> asList(ElasticsearchRepository.class, ElasticsearchCrudRepository.class);
|
||||
}
|
||||
}
|
||||
|
||||
-130
@@ -1,130 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019-2020 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.elasticsearch.repository.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.beans.factory.FactoryBean;
|
||||
import org.springframework.context.annotation.ComponentScan.Filter;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.data.elasticsearch.repository.support.ReactiveElasticsearchRepositoryFactoryBean;
|
||||
import org.springframework.data.repository.config.DefaultRepositoryBaseClass;
|
||||
import org.springframework.data.repository.query.QueryLookupStrategy;
|
||||
import org.springframework.data.repository.query.QueryLookupStrategy.Key;
|
||||
|
||||
/**
|
||||
* Annotation to activate reactive Elasticsearch repositories. If no base package is configured through either
|
||||
* {@link #value()}, {@link #basePackages()} or {@link #basePackageClasses()} it will trigger scanning of the package of
|
||||
* annotated class.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 3.2
|
||||
*/
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@Inherited
|
||||
@Import(ReactiveElasticsearchRepositoriesRegistrar.class)
|
||||
public @interface EnableReactiveElasticsearchRepositories {
|
||||
|
||||
/**
|
||||
* Alias for the {@link #basePackages()} attribute.
|
||||
*/
|
||||
String[] value() default {};
|
||||
|
||||
/**
|
||||
* Base packages to scan for annotated components. {@link #value()} is an alias for (and mutually exclusive with) this
|
||||
* attribute. Use {@link #basePackageClasses()} for a type-safe alternative to String-based package names.
|
||||
*/
|
||||
String[] basePackages() default {};
|
||||
|
||||
/**
|
||||
* Type-safe alternative to {@link #basePackages()} for specifying the packages to scan for annotated components. The
|
||||
* package of each class specified will be scanned. Consider creating a special no-op marker class or interface in
|
||||
* each package that serves no purpose other than being referenced by this attribute.
|
||||
*/
|
||||
Class<?>[] basePackageClasses() default {};
|
||||
|
||||
/**
|
||||
* Specifies which types are eligible for component scanning. Further narrows the set of candidate components from
|
||||
* everything in {@link #basePackages()} to everything in the base packages that matches the given filter or filters.
|
||||
*/
|
||||
Filter[] includeFilters() default {};
|
||||
|
||||
/**
|
||||
* Specifies which types are not eligible for component scanning.
|
||||
*/
|
||||
Filter[] excludeFilters() default {};
|
||||
|
||||
/**
|
||||
* Returns the postfix to be used when looking up custom repository implementations. Defaults to {@literal Impl}. So
|
||||
* for a repository named {@code PersonRepository} the corresponding implementation class will be looked up scanning
|
||||
* for {@code PersonRepositoryImpl}.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
String repositoryImplementationPostfix() default "Impl";
|
||||
|
||||
/**
|
||||
* Configures the location of where to find the Spring Data named queries properties file. Will default to
|
||||
* {@code META-INF/elasticsearch-named-queries.properties}.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
String namedQueriesLocation() default "";
|
||||
|
||||
/**
|
||||
* Returns the key of the {@link QueryLookupStrategy} to be used for lookup queries for query methods. Defaults to
|
||||
* {@link Key#CREATE_IF_NOT_FOUND}.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
Key queryLookupStrategy() default Key.CREATE_IF_NOT_FOUND;
|
||||
|
||||
/**
|
||||
* Returns the {@link FactoryBean} class to be used for each repository instance. Defaults to
|
||||
* {@link ReactiveElasticsearchRepositoryFactoryBean}.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
Class<?> repositoryFactoryBeanClass() default ReactiveElasticsearchRepositoryFactoryBean.class;
|
||||
|
||||
/**
|
||||
* Configure the repository base class to be used to create repository proxies for this particular configuration.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
Class<?> repositoryBaseClass() default DefaultRepositoryBaseClass.class;
|
||||
|
||||
/**
|
||||
* Configures the name of the {@link org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations} bean
|
||||
* to be used with the repositories detected.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
String reactiveElasticsearchTemplateRef() default "reactiveElasticsearchTemplate";
|
||||
|
||||
/**
|
||||
* Configures whether nested repository-interfaces (e.g. defined as inner classes) should be discovered by the
|
||||
* repositories infrastructure.
|
||||
*/
|
||||
boolean considerNestedRepositories() default false;
|
||||
}
|
||||
-46
@@ -1,46 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019-2020 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.elasticsearch.repository.config;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
|
||||
import org.springframework.data.repository.config.RepositoryBeanDefinitionRegistrarSupport;
|
||||
import org.springframework.data.repository.config.RepositoryConfigurationExtension;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
* @since 3.2
|
||||
*/
|
||||
class ReactiveElasticsearchRepositoriesRegistrar extends RepositoryBeanDefinitionRegistrarSupport {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.config.RepositoryBeanDefinitionRegistrarSupport#getAnnotation()
|
||||
*/
|
||||
@Override
|
||||
protected Class<? extends Annotation> getAnnotation() {
|
||||
return EnableReactiveElasticsearchRepositories.class;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.config.RepositoryBeanDefinitionRegistrarSupport#getExtension()
|
||||
*/
|
||||
@Override
|
||||
protected RepositoryConfigurationExtension getExtension() {
|
||||
return new ReactiveElasticsearchRepositoryConfigurationExtension();
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user