Compare commits
176 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 262af9f0e8 | |||
| bc74126d7e | |||
| 308de7f8db | |||
| ae5f72fb62 | |||
| 170facc3f3 | |||
| 0aaffebf16 | |||
| d1da6ac4ed | |||
| 133bc315ed | |||
| 9f243fd2c9 | |||
| de57159c7f | |||
| 74ed69877d | |||
| 7314bfc21d | |||
| 5909c19ead | |||
| 9df27ef289 | |||
| 28d92359b2 | |||
| d8b0d526b4 | |||
| dc832e75a6 | |||
| 866cd96477 | |||
| 162c57df31 | |||
| 3600452796 | |||
| 4b1c4c8000 | |||
| 70d556e526 | |||
| 214d91f3c1 | |||
| a723f07401 | |||
| 737578606c | |||
| f056e0957b | |||
| f385fa1908 | |||
| 27cfea7862 | |||
| a9ea407bf3 | |||
| 5c8689727c | |||
| 1e6cfade1d | |||
| 83a586caae | |||
| da20cc1684 | |||
| 7198a02a00 | |||
| 8a6e1254bb | |||
| 23ac6d77cf | |||
| 9bc4bee86f | |||
| 0ce2c499d5 | |||
| e13c9483ae | |||
| a74fd895b0 | |||
| 76ad3c4e60 | |||
| d8579d8610 | |||
| 0a6d91d09a | |||
| a5d93b0620 | |||
| 5c30241572 | |||
| bee7dbf65f | |||
| d80a4bdaa1 | |||
| a5d9e929d9 | |||
| 83658121f3 | |||
| 49068b4e00 | |||
| 4a25d3463b | |||
| f1dc7fc95c | |||
| 980aff30ae | |||
| 2a8c1dbdf8 | |||
| 36d8e7cc5e | |||
| e200791dc2 | |||
| 502bdb40a3 | |||
| b44e2bfdde | |||
| a4c1505bec | |||
| 7117e5d70d | |||
| 8d4c305732 | |||
| 54909a83cb | |||
| 3edc5b0fb0 | |||
| 5dc68600f4 | |||
| b7b17180f6 | |||
| bc92c3ad9a | |||
| cba702fe07 | |||
| faca0162a1 | |||
| 2c359018bd | |||
| 52bdfe45dc | |||
| 93d5ec3a40 | |||
| ed496f6351 | |||
| 1212a720a2 | |||
| 0e7791a687 | |||
| 6034f38d72 | |||
| 92a11e6eda | |||
| 628c925c9a | |||
| fd707abdf0 | |||
| d03510528b | |||
| ef1cbc35f6 | |||
| 4344a65dc2 | |||
| 6eb038a344 | |||
| 63efb2adef | |||
| 6361a1eefe | |||
| 79fdc449b8 | |||
| 4ef442966f | |||
| 368957f735 | |||
| a62e8af14f | |||
| 0208bffc0a | |||
| 26ab5f6db4 | |||
| c82792b34d | |||
| 131f0318cc | |||
| c8c6e7a646 | |||
| 7b1e4cc126 | |||
| 0626a5736f | |||
| 0825db2a02 | |||
| 9459d1b8a1 | |||
| db78ef0ac6 | |||
| da2e49763f | |||
| 53762db51d | |||
| 2b6e639951 | |||
| 73bf3dd988 | |||
| fd23c10c16 | |||
| fd77f62cc4 | |||
| 99ed967b71 | |||
| 68bdc93a0b | |||
| 0cfb1b563e | |||
| 103bf9f1b9 | |||
| b30f12503d | |||
| f19bf64827 | |||
| f989cf873b | |||
| fe458612e9 | |||
| 04977680ed | |||
| c9e813ff72 | |||
| a4cb8ef57c | |||
| ddee81f82e | |||
| bdcecd0950 | |||
| 0944d1654a | |||
| 0f940b36d7 | |||
| df4e6c449d | |||
| 168bc2dab5 | |||
| bfe9d290a6 | |||
| 3782c8e738 | |||
| ea98ef4533 | |||
| 7d8bc81fdd | |||
| 44a669d66c | |||
| c6b2276029 | |||
| ef836cb038 | |||
| db2a6b84a9 | |||
| 223ff41145 | |||
| 604f23384b | |||
| 011d2d5740 | |||
| 5b1e179e88 | |||
| 6332534ea1 | |||
| 36f0907881 | |||
| 7cd871a419 | |||
| d428db704e | |||
| 9bf1c09457 | |||
| 92f16846ab | |||
| 1de1aeb2c7 | |||
| b177dd1681 | |||
| aeaa27cb99 | |||
| 3c44a1c969 | |||
| 384e52b2c3 | |||
| 7b24ea9575 | |||
| 282214f8bc | |||
| bbf4c24195 | |||
| 407c8c6c17 | |||
| 407da040ab | |||
| 275560ecf3 | |||
| 846dbea2b8 | |||
| 8dbbc80887 | |||
| 8fea655854 | |||
| caebe08cf2 | |||
| 859b22db8e | |||
| 79dae4ee03 | |||
| 852273eff5 | |||
| d26dfbba70 | |||
| fa317014a7 | |||
| cb750e03a9 | |||
| 0f84158e1e | |||
| dc6734db43 | |||
| bcd7c2a1d8 | |||
| d8d3f9431b | |||
| 2875c62cfe | |||
| 75a430d431 | |||
| 49f1516b9e | |||
| b439acac16 | |||
| 506f79a45a | |||
| 391e240b49 | |||
| aaef626684 | |||
| c7339dc248 | |||
| 7540a2a1a8 | |||
| 6487d0ddd4 | |||
| 9ac18855fc | |||
| 0dc0f40cba |
@@ -1,8 +1,8 @@
|
||||
= Continuous Integration
|
||||
|
||||
image:https://jenkins.spring.io/buildStatus/icon?job=spring-data-elasticsearch%2Fmaster&subject=Moore%20(master)[link=https://jenkins.spring.io/view/SpringData/job/spring-data-elasticsearch/]
|
||||
image:https://jenkins.spring.io/buildStatus/icon?job=spring-data-elasticsearch%2F3.1.x&subject=Lovelace%20(3.1.x)[link=https://jenkins.spring.io/view/SpringData/job/spring-data-elasticsearch/]
|
||||
image:https://jenkins.spring.io/buildStatus/icon?job=spring-data-elasticsearch%2F2.1.x&subject=Ingalls%20(2.1.x)[link=https://jenkins.spring.io/view/SpringData/job/spring-data-elasticsearch/]
|
||||
image:https://jenkins.spring.io/buildStatus/icon?job=spring-data-elasticsearch%2Fmaster&subject=2020.0.0%20(master)[link=https://jenkins.spring.io/view/SpringData/job/spring-data-elasticsearch/]
|
||||
image:https://jenkins.spring.io/buildStatus/icon?job=spring-data-elasticsearch%2F4.0.x&subject=Neumann%20(4.0.x)[link=https://jenkins.spring.io/view/SpringData/job/spring-data-elasticsearch/]
|
||||
image:https://jenkins.spring.io/buildStatus/icon?job=spring-data-elasticsearch%2F3.2.x&subject=Moore%20(3.2.x)[link=https://jenkins.spring.io/view/SpringData/job/spring-data-elasticsearch/]
|
||||
|
||||
== Running CI tasks locally
|
||||
|
||||
@@ -30,7 +30,7 @@ Since the container is binding to your source, you can make edits from your IDE
|
||||
|
||||
If you need to package things up, do this:
|
||||
|
||||
1. `docker run -it --mount type=bind,source="$(pwd)",target=/spring-data-elasticsearch-github adoptopenjdk/openjdk8:latest /bin/bash`
|
||||
1. `docker run -it -v /var/run/docker.sock:/var/run/docker.sock --mount type=bind,source="$(pwd)",target=/spring-data-elasticsearch-github adoptopenjdk/openjdk8:latest /bin/bash`
|
||||
+
|
||||
This will launch the Docker image and mount your source code at `spring-data-elasticsearch-github`.
|
||||
+
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
= Contributor Code of Conduct
|
||||
|
||||
As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
|
||||
|
||||
We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, or nationality.
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery
|
||||
* Personal attacks
|
||||
* Trolling or insulting/derogatory comments
|
||||
* Public or private harassment
|
||||
* Publishing other's private information, such as physical or electronic addresses,
|
||||
without explicit permission
|
||||
* Other unethical or unprofessional conduct
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
|
||||
|
||||
By adopting this Code of Conduct, project maintainers commit themselves to fairly and consistently applying these principles to every aspect of managing this project. Project maintainers who do not follow or enforce the Code of Conduct may be permanently removed from the project team.
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community.
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting a project maintainer at spring-code-of-conduct@pivotal.io.
|
||||
All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances.
|
||||
Maintainers are obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
|
||||
This Code of Conduct is adapted from the https://contributor-covenant.org[Contributor Covenant], version 1.3.0, available at https://contributor-covenant.org/version/1/3/0/[contributor-covenant.org/version/1/3/0/].
|
||||
@@ -1,3 +1,7 @@
|
||||
= Spring Data contribution guidelines
|
||||
|
||||
You find the contribution guidelines for Spring Data projects https://github.com/spring-projects/spring-data-build/blob/master/CONTRIBUTING.adoc[here].
|
||||
|
||||
== Running the test locally
|
||||
|
||||
In order to run the tests locally with `./mvnw test` you need to have docker running because Spring Data Elasticsearch uses https://www.testcontainers.org/[Testcontainers] to start a local running Elasticsearch instance.
|
||||
|
||||
Vendored
+20
-22
@@ -3,7 +3,7 @@ pipeline {
|
||||
|
||||
triggers {
|
||||
pollSCM 'H/10 * * * *'
|
||||
upstream(upstreamProjects: "spring-data-commons/master", threshold: hudson.model.Result.SUCCESS)
|
||||
upstream(upstreamProjects: "spring-data-commons/2.4.x", threshold: hudson.model.Result.SUCCESS)
|
||||
}
|
||||
|
||||
options {
|
||||
@@ -15,7 +15,7 @@ pipeline {
|
||||
stage("test: baseline (jdk8)") {
|
||||
when {
|
||||
anyOf {
|
||||
branch 'master'
|
||||
branch '4.1.x'
|
||||
not { triggeredBy 'UpstreamCause' }
|
||||
}
|
||||
}
|
||||
@@ -23,20 +23,21 @@ pipeline {
|
||||
docker {
|
||||
image 'adoptopenjdk/openjdk8:latest'
|
||||
label 'data'
|
||||
args '-v $HOME:/tmp/jenkins-home'
|
||||
args '-u root -v /var/run/docker.sock:/var/run/docker.sock -v $HOME:/tmp/jenkins-home'
|
||||
}
|
||||
}
|
||||
options { timeout(time: 30, unit: 'MINUTES') }
|
||||
steps {
|
||||
sh 'rm -rf ?'
|
||||
sh 'MAVEN_OPTS="-Duser.name=jenkins -Duser.home=/tmp/jenkins-home" ./mvnw clean dependency:list test -Dsort -U -B'
|
||||
sh 'mkdir -p /tmp/jenkins-home'
|
||||
sh 'chown -R 1001:1001 .'
|
||||
sh 'MAVEN_OPTS="-Duser.name=jenkins -Duser.home=/tmp/jenkins-home" ./mvnw clean dependency:list verify -Dsort -U -B -Dmaven.repo.local=/tmp/jenkins-home/.m2/spring-data-elasticsearch'
|
||||
}
|
||||
}
|
||||
|
||||
stage("Test other configurations") {
|
||||
when {
|
||||
anyOf {
|
||||
branch 'master'
|
||||
allOf {
|
||||
branch '4.1.x'
|
||||
not { triggeredBy 'UpstreamCause' }
|
||||
}
|
||||
}
|
||||
@@ -46,28 +47,26 @@ pipeline {
|
||||
docker {
|
||||
image 'adoptopenjdk/openjdk11:latest'
|
||||
label 'data'
|
||||
args '-v $HOME:/tmp/jenkins-home'
|
||||
args '-u root -v /var/run/docker.sock:/var/run/docker.sock -v $HOME:/tmp/jenkins-home'
|
||||
}
|
||||
}
|
||||
options { timeout(time: 30, unit: 'MINUTES') }
|
||||
steps {
|
||||
sh 'rm -rf ?'
|
||||
sh 'MAVEN_OPTS="-Duser.name=jenkins -Duser.home=/tmp/jenkins-home" ./mvnw -Pjava11 clean dependency:list test -Dsort -U -B'
|
||||
sh 'MAVEN_OPTS="-Duser.name=jenkins -Duser.home=/tmp/jenkins-home" ./mvnw -Pjava11 clean dependency:list verify -Dsort -U -B -Dmaven.repo.local=/tmp/jenkins-home/.m2/spring-data-elasticsearch'
|
||||
}
|
||||
}
|
||||
|
||||
stage("test: baseline (jdk12)") {
|
||||
stage("test: baseline (jdk15)") {
|
||||
agent {
|
||||
docker {
|
||||
image 'adoptopenjdk/openjdk12:latest'
|
||||
image 'adoptopenjdk/openjdk15:latest'
|
||||
label 'data'
|
||||
args '-v $HOME:/tmp/jenkins-home'
|
||||
args '-u root -v /var/run/docker.sock:/var/run/docker.sock -v $HOME:/tmp/jenkins-home'
|
||||
}
|
||||
}
|
||||
options { timeout(time: 30, unit: 'MINUTES') }
|
||||
steps {
|
||||
sh 'rm -rf ?'
|
||||
sh 'MAVEN_OPTS="-Duser.name=jenkins -Duser.home=/tmp/jenkins-home" ./mvnw -Pjava11 clean dependency:list test -Dsort -U -B'
|
||||
sh 'MAVEN_OPTS="-Duser.name=jenkins -Duser.home=/tmp/jenkins-home" ./mvnw -Pjava11 clean dependency:list verify -Dsort -U -B -Dmaven.repo.local=/tmp/jenkins-home/.m2/spring-data-elasticsearch'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -76,7 +75,7 @@ pipeline {
|
||||
stage('Release to artifactory') {
|
||||
when {
|
||||
anyOf {
|
||||
branch 'master'
|
||||
branch '4.1.x'
|
||||
not { triggeredBy 'UpstreamCause' }
|
||||
}
|
||||
}
|
||||
@@ -84,7 +83,7 @@ pipeline {
|
||||
docker {
|
||||
image 'adoptopenjdk/openjdk8:latest'
|
||||
label 'data'
|
||||
args '-v $HOME:/tmp/jenkins-home'
|
||||
args '-u root -v /var/run/docker.sock:/var/run/docker.sock -v $HOME:/tmp/jenkins-home'
|
||||
}
|
||||
}
|
||||
options { timeout(time: 20, unit: 'MINUTES') }
|
||||
@@ -94,8 +93,7 @@ pipeline {
|
||||
}
|
||||
|
||||
steps {
|
||||
sh 'rm -rf ?'
|
||||
sh 'MAVEN_OPTS="-Duser.name=jenkins -Duser.home=/tmp/jenkins-home" ./mvnw -Pci,artifactory ' +
|
||||
sh 'MAVEN_OPTS="-Duser.name=jenkins -Duser.home=/tmp/jenkins-home" ./mvnw -Pci,artifactory -Dmaven.repo.local=/tmp/jenkins-home/.m2/spring-data-elasticsearch ' +
|
||||
'-Dartifactory.server=https://repo.spring.io ' +
|
||||
"-Dartifactory.username=${ARTIFACTORY_USR} " +
|
||||
"-Dartifactory.password=${ARTIFACTORY_PSW} " +
|
||||
@@ -107,13 +105,13 @@ pipeline {
|
||||
}
|
||||
stage('Publish documentation') {
|
||||
when {
|
||||
branch 'master'
|
||||
branch '4.1.x'
|
||||
}
|
||||
agent {
|
||||
docker {
|
||||
image 'adoptopenjdk/openjdk8:latest'
|
||||
label 'data'
|
||||
args '-v $HOME:/tmp/jenkins-home'
|
||||
args '-u root -v /var/run/docker.sock:/var/run/docker.sock -v $HOME:/tmp/jenkins-home'
|
||||
}
|
||||
}
|
||||
options { timeout(time: 20, unit: 'MINUTES') }
|
||||
@@ -123,7 +121,7 @@ pipeline {
|
||||
}
|
||||
|
||||
steps {
|
||||
sh 'MAVEN_OPTS="-Duser.name=jenkins -Duser.home=/tmp/jenkins-home" ./mvnw -Pci,distribute ' +
|
||||
sh 'MAVEN_OPTS="-Duser.name=jenkins -Duser.home=/tmp/jenkins-home" ./mvnw -Pci,distribute -Dmaven.repo.local=/tmp/jenkins-home/.m2/spring-data-elasticsearch ' +
|
||||
'-Dartifactory.server=https://repo.spring.io ' +
|
||||
"-Dartifactory.username=${ARTIFACTORY_USR} " +
|
||||
"-Dartifactory.password=${ARTIFACTORY_PSW} " +
|
||||
|
||||
+6
-2
@@ -19,7 +19,7 @@ This project is lead and maintained by the community.
|
||||
|
||||
== Code of Conduct
|
||||
|
||||
This project is governed by the link:CODE_OF_CONDUCT.adoc[Spring Code of Conduct]. By participating, you are expected to uphold this code of conduct. Please report unacceptable behavior to spring-code-of-conduct@pivotal.io.
|
||||
This project is governed by the https://github.com/spring-projects/.github/blob/e3cc2ff230d8f1dca06535aa6b5a4a23815861d4/CODE_OF_CONDUCT.md[Spring Code of Conduct]. By participating, you are expected to uphold this code of conduct. Please report unacceptable behavior to spring-code-of-conduct@pivotal.io.
|
||||
|
||||
== Getting Started
|
||||
|
||||
@@ -123,7 +123,7 @@ Add the Maven dependency:
|
||||
// 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].
|
||||
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/current/reference/html/#preface.versions[reference documentation].
|
||||
|
||||
To use the Release candidate versions of the upcoming major version, use our Maven milestone repository and declare the appropriate dependency version:
|
||||
|
||||
@@ -197,6 +197,10 @@ If you want to build with the regular `mvn` command, you will need https://maven
|
||||
|
||||
_Also see link:CONTRIBUTING.adoc[CONTRIBUTING.adoc] if you wish to submit pull requests, and in particular please sign the https://cla.pivotal.io/sign/spring[Contributor’s Agreement] before submitting your first pull request._
|
||||
|
||||
IMPORTANT: When contributing, please make sure an issue exists in Jira and comment on this issue with how you want to address it. By this we not only know that someone is working on an issue, we can also align architectural questions and possible solutions before work is invested. We so can prevent that much work is put into Pull Requests that have little
|
||||
or no chances of being merged.
|
||||
|
||||
|
||||
=== Building reference documentation
|
||||
|
||||
Building the documentation builds also the project without running tests.
|
||||
|
||||
@@ -5,12 +5,12 @@
|
||||
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-elasticsearch</artifactId>
|
||||
<version>4.0.0.RELEASE</version>
|
||||
<version>4.1.2</version>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.data.build</groupId>
|
||||
<artifactId>spring-data-parent</artifactId>
|
||||
<version>2.3.0.RELEASE</version>
|
||||
<version>2.4.2</version>
|
||||
</parent>
|
||||
|
||||
<name>Spring Data Elasticsearch</name>
|
||||
@@ -19,10 +19,11 @@
|
||||
|
||||
<properties>
|
||||
<commonslang>2.6</commonslang>
|
||||
<elasticsearch>7.6.2</elasticsearch>
|
||||
<log4j>2.9.1</log4j>
|
||||
<springdata.commons>2.3.0.RELEASE</springdata.commons>
|
||||
<netty>4.1.39.Final</netty>
|
||||
<elasticsearch>7.9.3</elasticsearch>
|
||||
<log4j>2.13.3</log4j>
|
||||
<netty>4.1.52.Final</netty>
|
||||
<springdata.commons>2.4.2</springdata.commons>
|
||||
<testcontainers>1.14.3</testcontainers>
|
||||
<java-module-name>spring.data.elasticsearch</java-module-name>
|
||||
</properties>
|
||||
|
||||
@@ -74,7 +75,6 @@
|
||||
</issueManagement>
|
||||
|
||||
<dependencyManagement>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
@@ -121,7 +121,7 @@
|
||||
|
||||
<dependency>
|
||||
<groupId>io.projectreactor.netty</groupId>
|
||||
<artifactId>reactor-netty</artifactId>
|
||||
<artifactId>reactor-netty-http</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
@@ -152,12 +152,6 @@
|
||||
<groupId>org.elasticsearch.client</groupId>
|
||||
<artifactId>transport</artifactId>
|
||||
<version>${elasticsearch}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>commons-logging</groupId>
|
||||
<artifactId>commons-logging</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -225,6 +219,13 @@
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.logging.log4j</groupId>
|
||||
<artifactId>log4j-to-slf4j</artifactId>
|
||||
<version>${log4j}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.openwebbeans.test</groupId>
|
||||
<artifactId>cditest-owb</artifactId>
|
||||
@@ -289,6 +290,13 @@
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>elasticsearch</artifactId>
|
||||
<version>${testcontainers}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
@@ -327,6 +335,30 @@
|
||||
<es.set.netty.runtime.available.processors>false</es.set.netty.runtime.available.processors>
|
||||
</systemPropertyVariables>
|
||||
</configuration>
|
||||
<executions>
|
||||
<!-- the default-test execution runs only the unit tests -->
|
||||
<execution>
|
||||
<id>default-test</id>
|
||||
<phase>test</phase>
|
||||
<goals>
|
||||
<goal>test</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<excludedGroups>integration-test</excludedGroups>
|
||||
</configuration>
|
||||
</execution>
|
||||
<!-- execution to run the integration tests -->
|
||||
<execution>
|
||||
<id>integration-test</id>
|
||||
<phase>integration-test</phase>
|
||||
<goals>
|
||||
<goal>test</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<groups>integration-test</groups>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
|
||||
@@ -44,4 +44,5 @@ include::{spring-data-commons-docs}/repository-namespace-reference.adoc[]
|
||||
include::{spring-data-commons-docs}/repository-populator-namespace-reference.adoc[]
|
||||
include::{spring-data-commons-docs}/repository-query-keywords-reference.adoc[]
|
||||
include::{spring-data-commons-docs}/repository-query-return-types-reference.adoc[]
|
||||
include::reference/migration-guides.adoc[]
|
||||
:leveloffset: -1
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
[[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.
|
||||
It provides:
|
||||
|
||||
* _Templates_ as a high-level abstraction for storing, searching, sorting documents and building aggregations.
|
||||
* _Repositories_ which for example enable the user to express queries by defining interfaces having customized method names (for basic information about repositories see <<repositories>>).
|
||||
@@ -9,7 +10,6 @@ The Spring Data Elasticsearch project applies core Spring concepts to the develo
|
||||
You will notice similarities to the Spring data solr and mongodb support in the Spring Framework.
|
||||
|
||||
include::reference/elasticsearch-new.adoc[leveloffset=+1]
|
||||
include::reference/elasticsearch-migration-guide-3.2-4.0.adoc[leveloffset=+1]
|
||||
|
||||
[[preface.metadata]]
|
||||
== Project Metadata
|
||||
@@ -28,16 +28,15 @@ Requires an installation of https://www.elastic.co/products/elasticsearch[Elasti
|
||||
|
||||
[[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:
|
||||
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 referring to that particular Spring Data release train:
|
||||
|
||||
[cols="^,^,^,^",options="header"]
|
||||
|===
|
||||
| Spring Data Release Train |Spring Data Elasticsearch |Elasticsearch | Spring Boot
|
||||
| Neumannfootnote:cdv[Currently in development] |4.0.xfootnote:cdv[]|7.6.2 |2.3.xfootnote:cdv[]
|
||||
| Moore | 3.2.x |6.8.6 | 2.2.x
|
||||
| 2020.0.0footnote:cdv[Currently in development] |4.1.xfootnote:cdv[]|7.9.3 |2.4.xfootnote:cdv[]
|
||||
| Neumann | 4.0.x | 7.6.2 |2.3.x
|
||||
| Moore | 3.2.x |6.8.12 | 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[]
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
=== Preparing entities
|
||||
|
||||
In order for the auditing code to be able to decide wether an entity instance is new, the entity must implement the `Persistable<ID>` interface which is defined as follows:
|
||||
In order for the auditing code to be able to decide whether an entity instance is new, the entity must implement the `Persistable<ID>` interface which is defined as follows:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@@ -37,22 +37,22 @@ public class Person implements Persistable<Long> {
|
||||
private Instant lastModifiedDate;
|
||||
private String lastModifiedBy;
|
||||
|
||||
public Long getId() { <1>
|
||||
public Long getId() { // <.>
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNew() {
|
||||
return id == null || (createdDate == null && createdBy == null); <2>
|
||||
return id == null || (createdDate == null && createdBy == null); // <.>
|
||||
}
|
||||
}
|
||||
----
|
||||
<1> the getter also is the required implementation from the interface
|
||||
<2> an object is new if it either has no `id` or none of fields containing creation attributes are set.
|
||||
<.> the getter is the required implementation from the interface
|
||||
<.> an object is new if it either has no `id` or none of fields containing creation attributes are set.
|
||||
|
||||
=== Activating auditing
|
||||
|
||||
After the entities have been set up and providing the `AuditorAware` the Auditing must be activated by setting the `@EnableElasticsearchAuditing` on a configuration class:
|
||||
After the entities have been set up and providing the `AuditorAware` - or `ReactiveAuditorAware` - the Auditing must be activated by setting the `@EnableElasticsearchAuditing` on a configuration class:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@@ -64,5 +64,16 @@ class MyConfiguration {
|
||||
}
|
||||
----
|
||||
|
||||
When using the reactive stack this must be:
|
||||
[source,java]
|
||||
----
|
||||
@Configuration
|
||||
@EnableReactiveElasticsearchRepositories
|
||||
@EnableReactiveElasticsearchAuditing
|
||||
class MyConfiguration {
|
||||
// configuration code
|
||||
}
|
||||
----
|
||||
|
||||
If your code contains more than one `AuditorAware` bean for different types, you must provide the name of the bean to use as an argument to the `auditorAwareRef` parameter of the
|
||||
`@EnableElasticsearchAuditing` annotation.
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
[[elasticsearch-migration-guide-3.2-4.0]]
|
||||
== Upgrading from 3.2.x to 4.0.x
|
||||
= Upgrading from 3.2.x to 4.0.x
|
||||
|
||||
This section describes breaking changes from version 3.2.x to 4.0.x and how removed features can be replaced by new introduced features.
|
||||
|
||||
|
||||
=== Removal of the used Jackson Mapper.
|
||||
[[elasticsearch-migration-guide-3.2-4.0.jackson-removal]]
|
||||
== Removal of the used Jackson Mapper
|
||||
|
||||
One of the changes in version 4.0.x is that Spring Data Elasticsearch does not use the Jackson Mapper anymore to map an entity to the JSON representation needed for Elasticsearch (see <<elasticsearch.mapping>>). In version 3.2.x the Jackson Mapper was the default that was used. It was possible to switch to the meta-model based converter (named `ElasticsearchEntityMapper`) by explicitly configuring it (<<elasticsearch.mapping.meta-model>>).
|
||||
|
||||
In version 4.0.x the meta-model based converter is the only one that is available and does not need to be configured explicitly. If you had a custom configuration to enable the meta-model converter by providing a bean like this:
|
||||
|
||||
[code,java]
|
||||
[source,java]
|
||||
----
|
||||
@Bean
|
||||
@Override
|
||||
public EntityMapper entityMapper() {
|
||||
public EntityMapper entityMapper() {
|
||||
|
||||
ElasticsearchEntityMapper entityMapper = new ElasticsearchEntityMapper(
|
||||
elasticsearchMappingContext(), new DefaultConversionService()
|
||||
elasticsearchMappingContext(), new DefaultConversionService()
|
||||
);
|
||||
entityMapper.setConversions(elasticsearchCustomConversions());
|
||||
entityMapper.setConversions(elasticsearchCustomConversions());
|
||||
|
||||
return entityMapper;
|
||||
}
|
||||
@@ -30,15 +30,15 @@ You now have to remove this bean, the `ElasticsearchEntityMapper` interface has
|
||||
.Entity configuration
|
||||
Some users had custom Jackson annotations on the entity class, for example in order to define a custom name for the mapped document in Elasticsearch or to configure date conversions. These are not taken into account anymore. The needed functionality is now provided with Spring Data Elasticsearch's `@Field` annotation. Please see <<elasticsearch.mapping.meta-model.annotations>> for detailed information.
|
||||
|
||||
|
||||
=== Removal of implicit index name from query objects
|
||||
[[elasticsearch-migration-guide-3.2-4.0.implicit-index-name]]
|
||||
== Removal of implicit index name from query objects
|
||||
|
||||
In 3.2.x the different query classes like `IndexQuery` or `SearchQuery` had properties that were taking the index name or index names that they were operating upon. If these were not set, the passed in entity was inspected to retrieve the index name that was set in the `@Document` annotation. +
|
||||
In 4.0.x the index name(s) must now be provided in an additional parameter of type `IndexCoordinates`. By separating this, it now is possible to use one query object against different indices.
|
||||
|
||||
So for example the following code:
|
||||
|
||||
[code,java]
|
||||
[source,java]
|
||||
----
|
||||
IndexQuery indexQuery = new IndexQueryBuilder()
|
||||
.withId(person.getId().toString())
|
||||
@@ -50,7 +50,7 @@ String documentId = elasticsearchOperations.index(indexQuery);
|
||||
|
||||
must be changed to:
|
||||
|
||||
[code,java]
|
||||
[source,java]
|
||||
----
|
||||
IndexCoordinates indexCoordinates = elasticsearchOperations.getIndexCoordinatesFor(person.getClass());
|
||||
|
||||
@@ -58,14 +58,14 @@ IndexQuery indexQuery = new IndexQueryBuilder()
|
||||
.withId(person.getId().toString())
|
||||
.withObject(person)
|
||||
.build();
|
||||
|
||||
|
||||
String documentId = elasticsearchOperations.index(indexQuery, indexCoordinates);
|
||||
----
|
||||
|
||||
To make it easier to work with entities and use the index name that is contained in the entitie's `@Document` annotation, new methods have been added like `DocumentOperations.save(T entity)`;
|
||||
|
||||
|
||||
=== The new Operations interfaces
|
||||
[[elasticsearch-migration-guide-3.2-4.0.new-operations]]
|
||||
== The new Operations interfaces
|
||||
|
||||
In version 3.2 there was the `ElasticsearchOperations` interface that defined all the methods for the `ElasticsearchTemplate` class. In version 4 the functions have been split into different interfaces, aligning these interfaces with the Elasticsearch API:
|
||||
|
||||
@@ -77,10 +77,10 @@ In version 3.2 there was the `ElasticsearchOperations` interface that defined al
|
||||
|
||||
NOTE: All the functions from the `ElasticsearchOperations` interface in version 3.2 that are now moved to the `IndexOperations` interface are still available, they are marked as deprecated and have default implementations that delegate to the new implementation:
|
||||
|
||||
[code,java]
|
||||
[source,java]
|
||||
----
|
||||
/**
|
||||
* Create an index for given indexName .
|
||||
* Create an index for given indexName.
|
||||
*
|
||||
* @param indexName the name of the index
|
||||
* @return {@literal true} if the index was created
|
||||
@@ -92,17 +92,17 @@ default boolean createIndex(String indexName) {
|
||||
}
|
||||
----
|
||||
|
||||
[[elasticsearch-migration-guide-3.2-4.0.deprecations]]
|
||||
== Deprecations
|
||||
|
||||
=== Deprecations
|
||||
|
||||
==== Methods and classes
|
||||
=== Methods and classes
|
||||
|
||||
Many functions and classes have been deprecated. These functions still work, but the Javadocs show with what they should be replaced.
|
||||
|
||||
.Example from ElasticsearchOperations
|
||||
[code,java]
|
||||
[source,java]
|
||||
----
|
||||
/**
|
||||
/*
|
||||
* Retrieves an object from an index.
|
||||
*
|
||||
* @param query the query defining the id of the object to get
|
||||
@@ -113,15 +113,16 @@ Many functions and classes have been deprecated. These functions still work, but
|
||||
@Deprecated
|
||||
@Nullable
|
||||
<T> T queryForObject(GetQuery query, Class<T> clazz);
|
||||
----
|
||||
----
|
||||
|
||||
==== Elasticsearch deprecations
|
||||
=== Elasticsearch deprecations
|
||||
|
||||
Since version 7 the Elasticsearch `TransportClient` is deprecated, it will be removed with Elasticsearch version 8. Spring Data Elasticsearch deprecates the `ElasticsearchTemplate` class which uses the `TransportClient` in version 4.0.
|
||||
|
||||
Mapping types were removed from Elasticsearch 7, they still exist as deprecated values in the Spring Data `@Document` annotation and the `IndexCoordinates` class but they are not used anymore internally.
|
||||
|
||||
=== Removals
|
||||
[[elasticsearch-migration-guide-3.2-4.0.removal]]
|
||||
== Removals
|
||||
|
||||
* As already described, the `ElasticsearchEntityMapper` interface has been removed.
|
||||
|
||||
@@ -130,4 +131,3 @@ Mapping types were removed from Elasticsearch 7, they still exist as deprecated
|
||||
* The method `org.springframework.data.elasticsearch.core.ElasticsearchOperations.query(SearchQuery query, ResultsExtractor<T> resultsExtractor);` and the `org.springframework.data.elasticsearch.core.ResultsExtractor` interface have been removed. These could be used to parse the result from Elasticsearch for cases in which the response mapping done with the Jackson based mapper was not enough. Since version 4.0, there are the new <<elasticsearch.operations.searchresulttypes>> to return the information from an Elasticsearch response, so there is no need to expose this low level functionality.
|
||||
|
||||
* The low level methods `startScroll`, `continueScroll` and `clearScroll` have been removed from the `ElasticsearchOperations` interface. For low level scroll API access, there now are `searchScrollStart`, `searchScrollContinue` and `searchScrollClear` methods on the `ElasticsearchRestTemplate` class.
|
||||
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
[[elasticsearch-migration-guide-4.0-4.1]]
|
||||
= Upgrading from 4.0.x to 4.1.x
|
||||
|
||||
This section describes breaking changes from version 4.0.x to 4.1.x and how removed features can be replaced by new introduced features.
|
||||
|
||||
[[elasticsearch-migration-guide-4.0-4.1.deprecations]]
|
||||
== Deprecations
|
||||
|
||||
.Definition of the id property
|
||||
It is possible to define a property of en entity as the id property by naming it either `id` or `document`.
|
||||
This behaviour is now deprecated and will produce a warning.
|
||||
PLease us the `@Id` annotation to mark a property as being the id property.
|
||||
|
||||
.Index mappings
|
||||
In the `ReactiveElasticsearchClient.Indices` interface the `updateMapping` methods are deprecated in favour of the `putMapping` methods.
|
||||
They do the same, but `putMapping` is consistent with the naming in the Elasticsearch API:
|
||||
|
||||
.Alias handling
|
||||
In the `IndexOperations` interface the methods `addAlias(AliasQuery)`, `removeAlias(AliasQuery)` and `queryForAlias()` have been deprecated.
|
||||
The new methods `alias(AliasAction)`, `getAliases(String...)` and `getAliasesForIndex(String...)` offer more functionality and a cleaner API.
|
||||
|
||||
.Parent-ID
|
||||
Usage of a parent-id has been removed from Elasticsearch since version 6. We now deprecate the corresponding fields and methods.
|
||||
|
||||
[[elasticsearch-migration-guide-4.0-4.1.removal]]
|
||||
== Removals
|
||||
|
||||
.Type mappings
|
||||
The _type mappings_ parameters of the `@Document` annotation and the `IndexCoordinates` object were removed.
|
||||
They had been deprecated in Spring Data Elasticsearch 4.0 and their values weren't used anymore.
|
||||
|
||||
[[elasticsearch-migration-guide-4.0-4.1.breaking-changes]]
|
||||
== Breaking Changes
|
||||
|
||||
=== Return types of ReactiveElasticsearchClient.Indices methods
|
||||
|
||||
The methods in the `ReactiveElasticsearchClient.Indices` were not used up to now.
|
||||
With the introduction of the `ReactiveIndexOperations` it became necessary to change some of the return types:
|
||||
|
||||
* the `createIndex` variants now return a `Mono<Boolean>` instead of a `Mono<Void>` to signal successful index creation.
|
||||
* the `updateMapping` variants now return a `Mono<Boolean>` instead of a `Mono<Void>` to signal successful mappings storage.
|
||||
|
||||
=== Return types of DocumentOperartions.bulkIndex methods
|
||||
|
||||
These methods were returing a `List<String>` containing the ids of the new indexed records.
|
||||
Now they return a `List<IndexedObjectInformation>`; these objects contain the id and information about optimistic locking (seq_no and primary_term)
|
||||
@@ -35,8 +35,6 @@ IndexCoordinates index = IndexCoordinates.of("sample-index");
|
||||
|
||||
SearchQuery searchQuery = new NativeSearchQueryBuilder()
|
||||
.withQuery(matchAllQuery())
|
||||
.withIndices(INDEX_NAME)
|
||||
.withTypes(TYPE_NAME)
|
||||
.withFields("message")
|
||||
.withPageable(PageRequest.of(0, 10))
|
||||
.build();
|
||||
@@ -62,8 +60,6 @@ IndexCoordinates index = IndexCoordinates.of("sample-index");
|
||||
|
||||
SearchQuery searchQuery = new NativeSearchQueryBuilder()
|
||||
.withQuery(matchAllQuery())
|
||||
.withIndices(INDEX_NAME)
|
||||
.withTypes(TYPE_NAME)
|
||||
.withFields("message")
|
||||
.withPageable(PageRequest.of(0, 10))
|
||||
.build();
|
||||
@@ -103,4 +99,208 @@ If the class to be retrieved has a `GeoPoint` property named _location_, the fol
|
||||
Sort.by(new GeoDistanceOrder("location", new GeoPoint(48.137154, 11.5761247)))
|
||||
----
|
||||
|
||||
[[elasticsearch.misc.jointype]]
|
||||
== Join-Type implementation
|
||||
|
||||
Spring Data Elasticsearch supports the https://www.elastic.co/guide/en/elasticsearch/reference/current/parent-join.html[Join data type] for creating the corresponding index mappings and for storing the relevant information.
|
||||
|
||||
=== Setting up the data
|
||||
|
||||
For an entity to be used in a parent child join relationship, it must have a property of type `JoinField` which must be annotated.
|
||||
Let's assume a `Statement` entity where a statement may be a _question_, an _answer_, a _comment_ or a _vote_ (a _Builder_ is also shown in this example, it's not necessary, but later used in the sample code):
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
@Document(indexName = "statements")
|
||||
public class Statement {
|
||||
@Id
|
||||
private String id;
|
||||
|
||||
@Field(type = FieldType.Text)
|
||||
private String text;
|
||||
|
||||
@JoinTypeRelations(
|
||||
relations =
|
||||
{
|
||||
@JoinTypeRelation(parent = "question", children = {"answer", "comment"}), <1>
|
||||
@JoinTypeRelation(parent = "answer", children = "vote") <2>
|
||||
}
|
||||
)
|
||||
private JoinField<String> relation; <3>
|
||||
|
||||
private Statement() {
|
||||
}
|
||||
|
||||
public static StatementBuilder builder() {
|
||||
return new StatementBuilder();
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
public void setText(String text) {
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
public JoinField<String> getRelation() {
|
||||
return relation;
|
||||
}
|
||||
|
||||
public void setRelation(JoinField<String> relation) {
|
||||
this.relation = relation;
|
||||
}
|
||||
|
||||
public static final class StatementBuilder {
|
||||
private String id;
|
||||
private String text;
|
||||
private JoinField<String> relation;
|
||||
|
||||
private StatementBuilder() {
|
||||
}
|
||||
|
||||
public StatementBuilder withId(String id) {
|
||||
this.id = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
public StatementBuilder withText(String text) {
|
||||
this.text = text;
|
||||
return this;
|
||||
}
|
||||
|
||||
public StatementBuilder withRelation(JoinField<String> relation) {
|
||||
this.relation = relation;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Statement build() {
|
||||
Statement statement = new Statement();
|
||||
statement.setId(id);
|
||||
statement.setText(text);
|
||||
statement.setRelation(relation);
|
||||
return statement;
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
<1> a question can have answers and comments
|
||||
<2> an answer can have votes
|
||||
<3> the `JoinField` property is used to combine the name (_question_, _answer_, _comment_ or _vote_) of the relation with the parent id. The generic type must be the same as the `@Id` annotated property.
|
||||
====
|
||||
|
||||
Spring Data Elasticsearch will build the following mapping for this class:
|
||||
|
||||
====
|
||||
[source,json]
|
||||
----
|
||||
{
|
||||
"statements": {
|
||||
"mappings": {
|
||||
"properties": {
|
||||
"_class": {
|
||||
"type": "text",
|
||||
"fields": {
|
||||
"keyword": {
|
||||
"type": "keyword",
|
||||
"ignore_above": 256
|
||||
}
|
||||
}
|
||||
},
|
||||
"relation": {
|
||||
"type": "join",
|
||||
"eager_global_ordinals": true,
|
||||
"relations": {
|
||||
"question": [
|
||||
"answer",
|
||||
"comment"
|
||||
],
|
||||
"answer": "vote"
|
||||
}
|
||||
},
|
||||
"text": {
|
||||
"type": "text"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
=== Storing data
|
||||
|
||||
Given a repository for this class the following code inserts a question, two answers, a comment and a vote:
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
void init() {
|
||||
repository.deleteAll();
|
||||
|
||||
Statement savedWeather = repository.save(
|
||||
Statement.builder()
|
||||
.withText("How is the weather?")
|
||||
.withRelation(new JoinField<>("question")) <1>
|
||||
.build());
|
||||
|
||||
Statement sunnyAnswer = repository.save(
|
||||
Statement.builder()
|
||||
.withText("sunny")
|
||||
.withRelation(new JoinField<>("answer", savedWeather.getId())) <2>
|
||||
.build());
|
||||
|
||||
repository.save(
|
||||
Statement.builder()
|
||||
.withText("rainy")
|
||||
.withRelation(new JoinField<>("answer", savedWeather.getId())) <3>
|
||||
.build());
|
||||
|
||||
repository.save(
|
||||
Statement.builder()
|
||||
.withText("I don't like the rain")
|
||||
.withRelation(new JoinField<>("comment", savedWeather.getId())) <4>
|
||||
.build());
|
||||
|
||||
repository.save(
|
||||
Statement.builder()
|
||||
.withText("+1 for the sun")
|
||||
.withRelation(new JoinField<>("vote", sunnyAnswer.getId())) <5>
|
||||
.build());
|
||||
}
|
||||
----
|
||||
<1> create a question statement
|
||||
<2> the first answer to the question
|
||||
<3> the second answer
|
||||
<4> a comment to the question
|
||||
<5> a vote for the first answer
|
||||
====
|
||||
|
||||
=== Retrieving data
|
||||
|
||||
Currently native search queries must be used to query the data, so there is no support from standard repository methods. <<repositories.custom-implementations>> can be used instead.
|
||||
|
||||
The following code shows as an example how to retrieve all entries that have a _vote_ (which must be _answers_, because only answers can have a vote) using an `ElasticsearchOperations` instance:
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
SearchHits<Statement> hasVotes() {
|
||||
NativeSearchQuery query = new NativeSearchQueryBuilder()
|
||||
.withQuery(hasChildQuery("vote", matchAllQuery(), ScoreMode.None))
|
||||
.build();
|
||||
|
||||
return operations.search(query, Statement.class);
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
|
||||
@@ -1,6 +1,16 @@
|
||||
[[new-features]]
|
||||
= What's new
|
||||
|
||||
[[new-features.4-1-0]]
|
||||
== New in Spring Data Elasticsearch 4.1
|
||||
|
||||
* Uses Spring 5.3.
|
||||
* Upgrade to Elasticsearch 7.9.3.
|
||||
* Improved API for alias management.
|
||||
* Introduction of `ReactiveIndexOperations` for index management.
|
||||
* Index templates support.
|
||||
* Support for Geo-shape data with GeoJson.
|
||||
|
||||
[[new-features.4-0-0]]
|
||||
== New in Spring Data Elasticsearch 4.0
|
||||
|
||||
@@ -11,8 +21,7 @@
|
||||
* Removal of the Jackson `ObjectMapper`, now using the <<elasticsearch.mapping.meta-model,MappingElasticsearchConverter>>
|
||||
* Cleanup of the API in the `*Operations` interfaces, grouping and renaming methods so that they match the Elasticsearch API, deprecating the old methods, aligning with other Spring Data modules.
|
||||
* Introduction of `SearchHit<T>` class to represent a found document together with the relevant result metadata for this document (i.e. _sortValues_).
|
||||
* Introduction of the `SearchHits<T>` class to represent a whole search result together with the
|
||||
metadata for the complete search result (i.e. _max_score_).
|
||||
* Introduction of the `SearchHits<T>` class to represent a whole search result together with the metadata for the complete search result (i.e. _max_score_).
|
||||
* Introduction of `SearchPage<T>` class to represent a paged result containing a `SearchHits<T>` instance.
|
||||
* Introduction of the `GeoDistanceOrder` class to be able to create sorting by geographical distance
|
||||
* Implementation of Auditing Support
|
||||
|
||||
@@ -3,17 +3,19 @@
|
||||
|
||||
Spring Data Elasticsearch Object Mapping is the process that maps a Java object - the domain entity - into the JSON representation that is stored in Elasticsearch and back.
|
||||
|
||||
Earlier versions of Spring Data Elasticsearch used a Jackson based conversion, Spring Data Elasticsearch 3.2.x introduced the <<elasticsearch.mapping.meta-model>>. As of version 4.0 only the Meta Object Mapping is used, the Jackson based mapper is not available anymore and the `MappingElasticsearchConverter` is used.
|
||||
Earlier versions of Spring Data Elasticsearch used a Jackson based conversion, Spring Data Elasticsearch 3.2.x introduced the <<elasticsearch.mapping.meta-model>>.
|
||||
As of version 4.0 only the Meta Object Mapping is used, the Jackson based mapper is not available anymore and the `MappingElasticsearchConverter` is used.
|
||||
|
||||
The main reasons for the removal of the Jackson based mapper are:
|
||||
|
||||
* Custom mappings of fields needed to be done with annotations like `@JsonFormat` or `@JsonInclude`. This often caused problems when the same object was used in different JSON based datastores or sent over a JSON based API.
|
||||
* Custom field types and formats also need to be stored into the Elasticsearch index mappings. The Jackson based annotations did not fully provide all the information that is necessary to represent the types of Elasticsearch.
|
||||
* Custom mappings of fields needed to be done with annotations like `@JsonFormat` or `@JsonInclude`.
|
||||
This often caused problems when the same object was used in different JSON based datastores or sent over a JSON based API.
|
||||
* Custom field types and formats also need to be stored into the Elasticsearch index mappings.
|
||||
The Jackson based annotations did not fully provide all the information that is necessary to represent the types of Elasticsearch.
|
||||
* Fields must be mapped not only when converting from and to entities, but also in query argument, returned data and on other places.
|
||||
|
||||
Using the `MappingElasticsearchConverter` now covers all these cases.
|
||||
|
||||
|
||||
[[elasticsearch.mapping.meta-model]]
|
||||
== Meta Model Object Mapping
|
||||
|
||||
@@ -23,30 +25,49 @@ This allows to register `Converter` instances for specific domain type mapping.
|
||||
[[elasticsearch.mapping.meta-model.annotations]]
|
||||
=== Mapping Annotation Overview
|
||||
|
||||
The `MappingElasticsearchConverter` uses metadata to drive the mapping of objects to documents. The metadata is taken from the entity's properties which can be annotated.
|
||||
The `MappingElasticsearchConverter` uses metadata to drive the mapping of objects to documents.
|
||||
The metadata is taken from the entity's properties which can be annotated.
|
||||
|
||||
The following annotations are available:
|
||||
|
||||
* `@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`: [line-through]#the mapping type. If not set, the lowercased simple name of the class is used.# (deprecated since version 4.0)
|
||||
* `@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.
|
||||
This can contain a SpEL template expression like `"log-#{T(java.time.LocalDate).now().toString()}"`
|
||||
** `type`: [line-through]#the mapping type.
|
||||
If not set, the lowercased simple name of the class is used.# (deprecated since version 4.0)
|
||||
** `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_.
|
||||
** `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`: flag whether to create an index on repository bootstrapping.
|
||||
Default value is _true_.
|
||||
See <<elasticsearch.repositories.autocreation>>
|
||||
** `versionType`: Configuration of version management.
|
||||
Default value is _EXTERNAL_.
|
||||
|
||||
* `@Id`: Applied at the field level to mark the field used for identity purpose.
|
||||
* `@Transient`: By default all fields are mapped to the document when it is stored or retrieved, this annotation excludes the field.
|
||||
* `@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.
|
||||
* `@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 (the following list is not complete, check the annotation Javadoc for a complete reference):
|
||||
** `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, Keyword, Long, Integer, Short, Byte, Double, Float, Half_Float, Scaled_Float, Date, Date_Nanos, Boolean, Binary, Integer_Range, Float_Range, Long_Range, Double_Range, Date_Range, Ip_Range, Object, Nested, Ip, TokenCount, Percolator, Flattened, Search_As_You_Type_. See https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-types.html[Elasticsearch Mapping Types]
|
||||
** `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.
|
||||
* `@GeoPoint`: marks a field as _geo_point_ datatype. Can be omitted if the field is an instance of the `GeoPoint` class.
|
||||
** `type`: the field type, can be one of _Text, Keyword, Long, Integer, Short, Byte, Double, Float, Half_Float, Scaled_Float, Date, Date_Nanos, Boolean, Binary, Integer_Range, Float_Range, Long_Range, Double_Range, Date_Range, Ip_Range, Object, Nested, Ip, TokenCount, Percolator, Flattened, Search_As_You_Type_.
|
||||
See https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-types.html[Elasticsearch Mapping Types]
|
||||
** `format` and `pattern` definitions for the _Date_ type.
|
||||
** `store`: Flag whether the original field value should be store in Elasticsearch, default value is _false_.
|
||||
** `analyzer`, `searchAnalyzer`, `normalizer` for specifying custom analyzers and normalizer.
|
||||
* `@GeoPoint`: marks a field as _geo_point_ datatype.
|
||||
Can be omitted if the field is an instance of the `GeoPoint` class.
|
||||
|
||||
NOTE: Properties that derive from `TemporalAccessor` or are of type `java.util.Date` must either have a `@Field` annotation of type `FieldType.Date` and a
|
||||
format different from `DateFormat.none` or a custom converter must be registered for this type. +
|
||||
If you are using a custom date format, you need to use _uuuu_ for the year instead of _yyyy_.
|
||||
This is due to a https://www.elastic.co/guide/en/elasticsearch/reference/current/migrate-to-java-time.html#java-time-migration-incompatible-date-formats[change in Elasticsearch 7].
|
||||
|
||||
The mapping metadata infrastructure is defined in a separate spring-data-commons project that is technology agnostic.
|
||||
|
||||
@@ -69,6 +90,7 @@ public class Person { <1>
|
||||
String lastname;
|
||||
}
|
||||
----
|
||||
|
||||
[source,json]
|
||||
----
|
||||
{
|
||||
@@ -81,10 +103,10 @@ public class Person { <1>
|
||||
<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.
|
||||
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.
|
||||
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
|
||||
====
|
||||
@@ -97,6 +119,7 @@ public class Person {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
[source,json]
|
||||
----
|
||||
{
|
||||
@@ -123,6 +146,7 @@ public class Address {
|
||||
Point location;
|
||||
}
|
||||
----
|
||||
|
||||
[source,json]
|
||||
----
|
||||
{
|
||||
@@ -133,6 +157,46 @@ public class Address {
|
||||
----
|
||||
====
|
||||
|
||||
==== GeoJson Types
|
||||
|
||||
Spring Data Elasticsearch supports the GeoJson types by providing an interface `GeoJson` and implementations for the different geometries.
|
||||
They are mapped to Elasticsearch documents according to the GeoJson specification.
|
||||
The corresponding properties of the entity are specified in the index mappings as `geo_shape` when the index mappings is written. (check the https://www.elastic.co/guide/en/elasticsearch/reference/current/geo-shape.html[Elasticsearch documentation] as well)
|
||||
|
||||
.GeoJson types
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
public class Address {
|
||||
|
||||
String city, street;
|
||||
GeoJsonPoint location;
|
||||
}
|
||||
----
|
||||
|
||||
[source,json]
|
||||
----
|
||||
{
|
||||
"city": "Los Angeles",
|
||||
"street": "2800 East Observatory Road",
|
||||
"location": {
|
||||
"type": "Point",
|
||||
"coordinates": [-118.3026284, 34.118347]
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
The following GeoJson types are implemented:
|
||||
|
||||
* `GeoJsonPoint`
|
||||
* `GeoJsonMultiPoint`
|
||||
* `GeoJsonLineString`
|
||||
* `GeoJsonMultiLineString`
|
||||
* `GeoJsonPolygon`
|
||||
* `GeoJsonMultiPolygon`
|
||||
* `GeoJsonGeometryCollection`
|
||||
|
||||
==== 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>>.
|
||||
@@ -149,6 +213,7 @@ public class Person {
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
[source,json]
|
||||
----
|
||||
{
|
||||
@@ -176,6 +241,7 @@ public class Person {
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
[source,json]
|
||||
----
|
||||
{
|
||||
@@ -242,6 +308,7 @@ public class Config extends AbstractElasticsearchConfiguration {
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
[source,json]
|
||||
----
|
||||
{
|
||||
|
||||
@@ -17,6 +17,18 @@ The default implementations of the interfaces offer:
|
||||
* A rich query and criteria api.
|
||||
* Resource management and Exception translation.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
.Index management and automatic creation of indices and mappings.
|
||||
|
||||
The `IndexOperations` interface and the provided implementation which can be obtained from an `ElasticsearchOperations` instance - for example with a call to `operations.indexOps(clazz)`- give the user the ability to create indices, put mappings or store template and alias information in the Elasticsearch cluster.
|
||||
|
||||
**None of these operations are done automatically** by the implementations of `IndexOperations` or `ElasticsearchOperations`. It is the user's responsibility to call the methods.
|
||||
|
||||
There is support for automatic creation of indices and writing the mappings when using Spring Data Elasticsearch repositories, see <<elasticsearch.repositories.autocreation>>
|
||||
|
||||
====
|
||||
|
||||
[[elasticsearch.operations.template]]
|
||||
== ElasticsearchTemplate
|
||||
|
||||
@@ -45,7 +57,8 @@ public class TransportClientConfig extends ElasticsearchConfigurationSupport {
|
||||
}
|
||||
}
|
||||
----
|
||||
<1> Setting up the <<elasticsearch.clients.transport>>. Deprecated as of version 4.0.
|
||||
<1> Setting up the <<elasticsearch.clients.transport>>.
|
||||
Deprecated as of version 4.0.
|
||||
<2> Creating the `ElasticsearchTemplate` bean, offering both names, _elasticsearchOperations_ and _elasticsearchTemplate_.
|
||||
====
|
||||
|
||||
@@ -75,7 +88,9 @@ public class RestClientConfig extends AbstractElasticsearchConfiguration {
|
||||
[[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.
|
||||
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
|
||||
====
|
||||
@@ -123,9 +138,12 @@ include::reactive-elasticsearch-operations.adoc[leveloffset=+1]
|
||||
[[elasticsearch.operations.searchresulttypes]]
|
||||
== Search Result Types
|
||||
|
||||
When a document is retrieved with the methods of the `DocumentOperations` interface, just the found entity will be returned. When searching with the methods of the `SearchOperations` interface, additional information is available for each entity, for example the _score_ or the _sortValues_ of the found entity.
|
||||
When a document is retrieved with the methods of the `DocumentOperations` interface, just the found entity will be returned.
|
||||
When searching with the methods of the `SearchOperations` interface, additional information is available for each entity, for example the _score_ or the _sortValues_ of the found entity.
|
||||
|
||||
In order to return this information, each entity is wrapped in a `SearchHit` object that contains this entity-specific additional information. These `SearchHit` objects themselves are returned within a `SearchHits` object which additionally contains informations about the whole search like the _maxScore_ or requested aggregations. The following classes and interfaces are now available:
|
||||
In order to return this information, each entity is wrapped in a `SearchHit` object that contains this entity-specific additional information.
|
||||
These `SearchHit` objects themselves are returned within a `SearchHits` object which additionally contains informations about the whole search like the _maxScore_ or requested aggregations.
|
||||
The following classes and interfaces are now available:
|
||||
|
||||
.SearchHit<T>
|
||||
Contains the following information:
|
||||
@@ -134,6 +152,7 @@ Contains the following information:
|
||||
* Score
|
||||
* Sort Values
|
||||
* Highlight fields
|
||||
* Inner hits (this is an embedded `SearchHits` object containing eventually returned inner hits)
|
||||
* The retrieved entity of type <T>
|
||||
|
||||
.SearchHits<T>
|
||||
@@ -154,3 +173,108 @@ Returned by the low level scroll API functions in `ElasticsearchRestTemplate`, i
|
||||
.SearchHitsIterator<T>
|
||||
An Iterator returned by the streaming functions of the `SearchOperations` interface.
|
||||
|
||||
== Queries
|
||||
|
||||
Almost all of the methods defined in the `SearchOperations` and `ReactiveSearchOperations` interface take a `Query` parameter that defines the query to execute for searching. `Query` is an interface and Spring Data Elasticsearch provides three implementations: `CriteriaQuery`, `StringQuery` and `NativeSearchQuery`.
|
||||
|
||||
=== CriteriaQuery
|
||||
|
||||
`CriteriaQuery` based queries allow the creation of queries to search for data without knowing the syntax or basics of Elasticsearch queries. They allow the user to build queries by simply chaining and combining `Criteria` objects that specifiy the criteria the searched documents must fulfill.
|
||||
|
||||
NOTE: when talking about AND or OR when combining criteria keep in mind, that in Elasticsearch AND are converted to a **must** condition and OR to a **should**
|
||||
|
||||
`Criteria` and their usage are best explained by example
|
||||
(let's assume we have a `Book` entity with a `price` property):
|
||||
|
||||
.Get books with a given price
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
Criteria criteria = new Criteria("price").is(42.0);
|
||||
Query query = new CriteriaQuery(criteria);
|
||||
----
|
||||
====
|
||||
|
||||
Conditions for the same field can be chained, they will be combined with a logical AND:
|
||||
|
||||
.Get books with a given price
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
Criteria criteria = new Criteria("price").greaterThan(42.0).lessThan(34.0L);
|
||||
Query query = new CriteriaQuery(criteria);
|
||||
----
|
||||
====
|
||||
|
||||
When chaining `Criteria`, by default a AND logic is used:
|
||||
|
||||
.Get all persons with first name _James_ and last name _Miller_:
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
Criteria criteria = new Criteria("lastname").is("Miller") <1>
|
||||
.and("firstname").is("James") <2>
|
||||
Query query = new CriteriaQuery(criteria);
|
||||
----
|
||||
<1> the first `Criteria`
|
||||
<2> the and() creates a new `Criteria` and chaines it to the first one.
|
||||
====
|
||||
|
||||
If you want to create nested queries, you need to use subqueries for this. Let's assume we want to find all persons with a last name of _Miller_ and a first name of either _Jack_ or _John_:
|
||||
|
||||
.Nested subqueries
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
Criteria miller = new Criteria("lastName").is("Miller") <.>
|
||||
.subCriteria( <.>
|
||||
new Criteria().or("firstName").is("John") <.>
|
||||
.or("firstName").is("Jack") <.>
|
||||
);
|
||||
Query query = new CriteriaQuery(criteria);
|
||||
----
|
||||
<.> create a first `Criteria` for the last name
|
||||
<.> this is combined with AND to a subCriteria
|
||||
<.> This sub Criteria is an OR combination for the first name _John_
|
||||
<.> and the first name Jack
|
||||
====
|
||||
|
||||
Please refer to the API documentation of the `Criteria` class for a complete overview of the different available operations.
|
||||
|
||||
=== StringQuery
|
||||
|
||||
This class takes an Elasticsearch query as JSON String.
|
||||
The following code shows a query that searches for persons having the first name "Jack":
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
|
||||
Query query = new SearchQuery("{ \"match\": { \"firstname\": { \"query\": \"Jack\" } } } ");
|
||||
SearchHits<Person> searchHits = operations.search(query, Person.class);
|
||||
|
||||
----
|
||||
====
|
||||
|
||||
Using `StringQuery` may be appropriate if you already have an Elasticsearch query to use.
|
||||
|
||||
=== NativeSearchQuery
|
||||
|
||||
`NativeSearchQuery` is the class to use when you have a complex query, or a query that cannot be expressed by using the `Criteria` API, for example when building queries and using aggregates.
|
||||
It allows to use all the different `QueryBuilder` implementations from the Elasticsearch library therefore named "native".
|
||||
|
||||
The following code shows how to search for persons with a given firstname and for the found documents have a terms aggregation that counts the number of occurences of the lastnames for these persons:
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
Query query = new NativeSearchQueryBuilder()
|
||||
.addAggregation(terms("lastnames").field("lastname").size(10)) //
|
||||
.withQuery(QueryBuilders.matchQuery("firstname", firstName))
|
||||
.build();
|
||||
|
||||
SearchHits<Person> searchHits = operations.search(query, Person.class);
|
||||
----
|
||||
====
|
||||
|
||||
|
||||
|
||||
@@ -3,8 +3,65 @@
|
||||
|
||||
This chapter includes details of the Elasticsearch repository implementation.
|
||||
|
||||
.The sample `Book` entity
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
@Document(indexName="books")
|
||||
class Book {
|
||||
@Id
|
||||
private String id;
|
||||
|
||||
@Field(type = FieldType.text)
|
||||
private String name;
|
||||
|
||||
@Field(type = FieldType.text)
|
||||
private String summary;
|
||||
|
||||
@Field(type = FieldType.Integer)
|
||||
private Integer price;
|
||||
|
||||
// getter/setter ...
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
[[elasticsearch.repositories.autocreation]]
|
||||
== Automatic creation of indices with the corresponding mapping
|
||||
|
||||
The `@Document` annotation has an argument `createIndex`. If this argument is set to true - which is the default value - Spring Data Elasticsearch will during bootstrapping the repository support on application startup check if the index defined by the `@Document` annotation exists.
|
||||
|
||||
If it does not exist, the index will be created and the mappings derived from the entity's annotations (see <<elasticsearch.mapping>>) will be written to the newly created index.
|
||||
|
||||
include::elasticsearch-repository-queries.adoc[leveloffset=+1]
|
||||
|
||||
include::reactive-elasticsearch-repositories.adoc[leveloffset=+1]
|
||||
|
||||
[[elasticsearch.repositories.annotations]]
|
||||
== Annotations for repository methods
|
||||
|
||||
=== @Highlight
|
||||
|
||||
The `@Highlight` annotation on a repository method defines for which fields of the returned entity highlighting should be included. To search for some text in a `Book` 's name or summary and have the found data highlighted, the following repository method can be used:
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
interface BookRepository extends Repository<Book, String> {
|
||||
|
||||
@Highlight(fields = {
|
||||
@HighlightField(name = "name"),
|
||||
@HighlightField(name = "summary")
|
||||
})
|
||||
List<SearchHit<Book>> findByNameOrSummary(String text, String summary);
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
It is possible to define multiple fields to be highlighted like above, and both the `@Highlight` and the `@HighlightField` annotation can further be customized with a `@HighlightParameters` annotation. Check the Javadocs for the possible configuration options.
|
||||
|
||||
In the search results the highlight data can be retrieved from the `SearchHit` class.
|
||||
|
||||
[[elasticsearch.annotation]]
|
||||
== Annotation based configuration
|
||||
|
||||
@@ -40,7 +97,8 @@ class ProductService {
|
||||
}
|
||||
----
|
||||
|
||||
<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.
|
||||
<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.
|
||||
====
|
||||
@@ -145,5 +203,3 @@ Using the `Transport Client` or `Rest Client` element registers an instance of `
|
||||
</beans>
|
||||
----
|
||||
====
|
||||
|
||||
include::reactive-elasticsearch-repositories.adoc[leveloffset=+1]
|
||||
|
||||
@@ -8,12 +8,14 @@ The Elasticsearch module supports all basic query building feature as string que
|
||||
|
||||
=== 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>> ).
|
||||
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:
|
||||
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
|
||||
====
|
||||
@@ -43,20 +45,22 @@ The method name above will be translated into the following Elasticsearch json q
|
||||
|
||||
A list of supported keywords for Elasticsearch is shown below.
|
||||
|
||||
[cols="1,2,3", options="header"]
|
||||
[cols="1,2,3",options="header"]
|
||||
.Supported keywords inside method names
|
||||
|===
|
||||
| Keyword
|
||||
| Sample
|
||||
| Elasticsearch Query String| `And`
|
||||
| Elasticsearch Query String
|
||||
|
||||
| `And`
|
||||
| `findByNameAndPrice`
|
||||
| `{ "query" : {
|
||||
"bool" : {
|
||||
"must" : [
|
||||
{ "query_string" : { "query" : "?", "fields" : [ "name" ] } },
|
||||
{ "query_string" : { "query" : "?", "fields" : [ "price" ] } }
|
||||
]
|
||||
}
|
||||
{ "query_string" : { "query" : "?", "fields" : [ "name" ] } },
|
||||
{ "query_string" : { "query" : "?", "fields" : [ "price" ] } }
|
||||
]
|
||||
}
|
||||
}}`
|
||||
|
||||
| `Or`
|
||||
@@ -64,10 +68,10 @@ A list of supported keywords for Elasticsearch is shown below.
|
||||
| `{ "query" : {
|
||||
"bool" : {
|
||||
"should" : [
|
||||
{ "query_string" : { "query" : "?", "fields" : [ "name" ] } },
|
||||
{ "query_string" : { "query" : "?", "fields" : [ "price" ] } }
|
||||
]
|
||||
}
|
||||
{ "query_string" : { "query" : "?", "fields" : [ "name" ] } },
|
||||
{ "query_string" : { "query" : "?", "fields" : [ "price" ] } }
|
||||
]
|
||||
}
|
||||
}}`
|
||||
|
||||
| `Is`
|
||||
@@ -75,9 +79,9 @@ A list of supported keywords for Elasticsearch is shown below.
|
||||
| `{ "query" : {
|
||||
"bool" : {
|
||||
"must" : [
|
||||
{ "query_string" : { "query" : "?", "fields" : [ "name" ] } }
|
||||
]
|
||||
}
|
||||
{ "query_string" : { "query" : "?", "fields" : [ "name" ] } }
|
||||
]
|
||||
}
|
||||
}}`
|
||||
|
||||
| `Not`
|
||||
@@ -85,9 +89,9 @@ A list of supported keywords for Elasticsearch is shown below.
|
||||
| `{ "query" : {
|
||||
"bool" : {
|
||||
"must_not" : [
|
||||
{ "query_string" : { "query" : "?", "fields" : [ "name" ] } }
|
||||
]
|
||||
}
|
||||
{ "query_string" : { "query" : "?", "fields" : [ "name" ] } }
|
||||
]
|
||||
}
|
||||
}}`
|
||||
|
||||
| `Between`
|
||||
@@ -95,9 +99,9 @@ A list of supported keywords for Elasticsearch is shown below.
|
||||
| `{ "query" : {
|
||||
"bool" : {
|
||||
"must" : [
|
||||
{"range" : {"price" : {"from" : ?, "to" : ?, "include_lower" : true, "include_upper" : true } } }
|
||||
]
|
||||
}
|
||||
{"range" : {"price" : {"from" : ?, "to" : ?, "include_lower" : true, "include_upper" : true } } }
|
||||
]
|
||||
}
|
||||
}}`
|
||||
|
||||
| `LessThan`
|
||||
@@ -105,9 +109,9 @@ A list of supported keywords for Elasticsearch is shown below.
|
||||
| `{ "query" : {
|
||||
"bool" : {
|
||||
"must" : [
|
||||
{"range" : {"price" : {"from" : null, "to" : ?, "include_lower" : true, "include_upper" : false } } }
|
||||
]
|
||||
}
|
||||
{"range" : {"price" : {"from" : null, "to" : ?, "include_lower" : true, "include_upper" : false } } }
|
||||
]
|
||||
}
|
||||
}}`
|
||||
|
||||
| `LessThanEqual`
|
||||
@@ -115,9 +119,9 @@ A list of supported keywords for Elasticsearch is shown below.
|
||||
| `{ "query" : {
|
||||
"bool" : {
|
||||
"must" : [
|
||||
{"range" : {"price" : {"from" : null, "to" : ?, "include_lower" : true, "include_upper" : true } } }
|
||||
]
|
||||
}
|
||||
{"range" : {"price" : {"from" : null, "to" : ?, "include_lower" : true, "include_upper" : true } } }
|
||||
]
|
||||
}
|
||||
}}`
|
||||
|
||||
| `GreaterThan`
|
||||
@@ -125,9 +129,9 @@ A list of supported keywords for Elasticsearch is shown below.
|
||||
| `{ "query" : {
|
||||
"bool" : {
|
||||
"must" : [
|
||||
{"range" : {"price" : {"from" : ?, "to" : null, "include_lower" : false, "include_upper" : true } } }
|
||||
]
|
||||
}
|
||||
{"range" : {"price" : {"from" : ?, "to" : null, "include_lower" : false, "include_upper" : true } } }
|
||||
]
|
||||
}
|
||||
}}`
|
||||
|
||||
|
||||
@@ -136,9 +140,9 @@ A list of supported keywords for Elasticsearch is shown below.
|
||||
| `{ "query" : {
|
||||
"bool" : {
|
||||
"must" : [
|
||||
{"range" : {"price" : {"from" : ?, "to" : null, "include_lower" : true, "include_upper" : true } } }
|
||||
]
|
||||
}
|
||||
{"range" : {"price" : {"from" : ?, "to" : null, "include_lower" : true, "include_upper" : true } } }
|
||||
]
|
||||
}
|
||||
}}`
|
||||
|
||||
| `Before`
|
||||
@@ -146,9 +150,9 @@ A list of supported keywords for Elasticsearch is shown below.
|
||||
| `{ "query" : {
|
||||
"bool" : {
|
||||
"must" : [
|
||||
{"range" : {"price" : {"from" : null, "to" : ?, "include_lower" : true, "include_upper" : true } } }
|
||||
]
|
||||
}
|
||||
{"range" : {"price" : {"from" : null, "to" : ?, "include_lower" : true, "include_upper" : true } } }
|
||||
]
|
||||
}
|
||||
}}`
|
||||
|
||||
| `After`
|
||||
@@ -156,9 +160,9 @@ A list of supported keywords for Elasticsearch is shown below.
|
||||
| `{ "query" : {
|
||||
"bool" : {
|
||||
"must" : [
|
||||
{"range" : {"price" : {"from" : ?, "to" : null, "include_lower" : true, "include_upper" : true } } }
|
||||
]
|
||||
}
|
||||
{"range" : {"price" : {"from" : ?, "to" : null, "include_lower" : true, "include_upper" : true } } }
|
||||
]
|
||||
}
|
||||
}}`
|
||||
|
||||
| `Like`
|
||||
@@ -166,9 +170,9 @@ A list of supported keywords for Elasticsearch is shown below.
|
||||
| `{ "query" : {
|
||||
"bool" : {
|
||||
"must" : [
|
||||
{ "query_string" : { "query" : "?*", "fields" : [ "name" ] }, "analyze_wildcard": true }
|
||||
]
|
||||
}
|
||||
{ "query_string" : { "query" : "?*", "fields" : [ "name" ] }, "analyze_wildcard": true }
|
||||
]
|
||||
}
|
||||
}}`
|
||||
|
||||
| `StartingWith`
|
||||
@@ -176,9 +180,9 @@ A list of supported keywords for Elasticsearch is shown below.
|
||||
| `{ "query" : {
|
||||
"bool" : {
|
||||
"must" : [
|
||||
{ "query_string" : { "query" : "?*", "fields" : [ "name" ] }, "analyze_wildcard": true }
|
||||
]
|
||||
}
|
||||
{ "query_string" : { "query" : "?*", "fields" : [ "name" ] }, "analyze_wildcard": true }
|
||||
]
|
||||
}
|
||||
}}`
|
||||
|
||||
| `EndingWith`
|
||||
@@ -186,9 +190,9 @@ A list of supported keywords for Elasticsearch is shown below.
|
||||
| `{ "query" : {
|
||||
"bool" : {
|
||||
"must" : [
|
||||
{ "query_string" : { "query" : "*?", "fields" : [ "name" ] }, "analyze_wildcard": true }
|
||||
]
|
||||
}
|
||||
{ "query_string" : { "query" : "*?", "fields" : [ "name" ] }, "analyze_wildcard": true }
|
||||
]
|
||||
}
|
||||
}}`
|
||||
|
||||
| `Contains/Containing`
|
||||
@@ -196,39 +200,48 @@ A list of supported keywords for Elasticsearch is shown below.
|
||||
| `{ "query" : {
|
||||
"bool" : {
|
||||
"must" : [
|
||||
{ "query_string" : { "query" : "\*?*", "fields" : [ "name" ] }, "analyze_wildcard": true }
|
||||
]
|
||||
}
|
||||
{ "query_string" : { "query" : "\*?*", "fields" : [ "name" ] }, "analyze_wildcard": true }
|
||||
]
|
||||
}
|
||||
}}`
|
||||
|
||||
| `In`
|
||||
| `In` (when annotated as FieldType.Keyword)
|
||||
| `findByNameIn(Collection<String>names)`
|
||||
| `{ "query" : {
|
||||
"bool" : {
|
||||
"must" : [
|
||||
{"bool" : {"must" : [
|
||||
{"terms" : {"name" : ["?","?"]}}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
{"bool" : {"must" : [
|
||||
{"terms" : {"name" : ["?","?"]}}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}}`
|
||||
|
||||
| `NotIn`
|
||||
|
||||
| `In`
|
||||
| `findByNameIn(Collection<String>names)`
|
||||
| `{ "query": {"bool": {"must": [{"query_string":{"query": "\"?\" \"?\"", "fields": ["name"]}}]}}}`
|
||||
|
||||
| `NotIn` (when annotated as FieldType.Keyword)
|
||||
| `findByNameNotIn(Collection<String>names)`
|
||||
| `{ "query" : {
|
||||
"bool" : {
|
||||
"must" : [
|
||||
{"bool" : {"must_not" : [
|
||||
{"terms" : {"name" : ["?","?"]}}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
{"bool" : {"must_not" : [
|
||||
{"terms" : {"name" : ["?","?"]}}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}}`
|
||||
|
||||
| `NotIn`
|
||||
| `findByNameNotIn(Collection<String>names)`
|
||||
| `{"query": {"bool": {"must": [{"query_string": {"query": "NOT(\"?\" \"?\")", "fields": ["name"]}}]}}}`
|
||||
|
||||
| `Near`
|
||||
| `findByStoreNear`
|
||||
| `Not Supported Yet !`
|
||||
@@ -238,9 +251,9 @@ A list of supported keywords for Elasticsearch is shown below.
|
||||
| `{ "query" : {
|
||||
"bool" : {
|
||||
"must" : [
|
||||
{ "query_string" : { "query" : "true", "fields" : [ "available" ] } }
|
||||
]
|
||||
}
|
||||
{ "query_string" : { "query" : "true", "fields" : [ "available" ] } }
|
||||
]
|
||||
}
|
||||
}}`
|
||||
|
||||
| `False`
|
||||
@@ -248,9 +261,9 @@ A list of supported keywords for Elasticsearch is shown below.
|
||||
| `{ "query" : {
|
||||
"bool" : {
|
||||
"must" : [
|
||||
{ "query_string" : { "query" : "false", "fields" : [ "available" ] } }
|
||||
]
|
||||
}
|
||||
{ "query_string" : { "query" : "false", "fields" : [ "available" ] } }
|
||||
]
|
||||
}
|
||||
}}`
|
||||
|
||||
| `OrderBy`
|
||||
@@ -258,14 +271,17 @@ A list of supported keywords for Elasticsearch is shown below.
|
||||
| `{ "query" : {
|
||||
"bool" : {
|
||||
"must" : [
|
||||
{ "query_string" : { "query" : "true", "fields" : [ "available" ] } }
|
||||
]
|
||||
}
|
||||
{ "query_string" : { "query" : "true", "fields" : [ "available" ] } }
|
||||
]
|
||||
}
|
||||
}, "sort":[{"name":{"order":"desc"}}]
|
||||
}`
|
||||
|
||||
|===
|
||||
|
||||
NOTE: Methods names to build Geo-shape queries taking `GeoJson` parameters are not supported.
|
||||
Use `ElasticsearchOperations` with `CriteriaQuery` in a custom repository implementation if you need to have such a function in a repository.
|
||||
|
||||
== Method return types
|
||||
|
||||
Repository methods can be defined to have the following return types for returning multiple Elements:
|
||||
@@ -289,7 +305,10 @@ interface BookRepository extends ElasticsearchRepository<Book, String> {
|
||||
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:
|
||||
|
||||
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]
|
||||
----
|
||||
{
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
[[elasticsearch.migration]]
|
||||
= Appendix E: Migration Guides
|
||||
|
||||
// line breaks required otherwise the TOC breaks due to joining of first/last lines.
|
||||
:leveloffset: +1
|
||||
include::elasticsearch-migration-guide-3.2-4.0.adoc[]
|
||||
|
||||
include::elasticsearch-migration-guide-4.0-4.1.adoc[]
|
||||
:leveloffset: -1
|
||||
+14
-11
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2019-2020 the original author or authors.
|
||||
* Copyright 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.
|
||||
@@ -15,20 +15,23 @@
|
||||
*/
|
||||
package org.springframework.data.elasticsearch;
|
||||
|
||||
import org.elasticsearch.client.RestHighLevelClient;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.elasticsearch.config.AbstractElasticsearchConfiguration;
|
||||
import org.springframework.dao.DataRetrievalFailureException;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author Peter-Josef Meisch
|
||||
* @since 4.1
|
||||
*/
|
||||
@Configuration
|
||||
public class RestElasticsearchTestConfiguration extends AbstractElasticsearchConfiguration {
|
||||
public class BulkFailureException extends DataRetrievalFailureException {
|
||||
private final Map<String, String> failedDocuments;
|
||||
|
||||
@Override
|
||||
@Bean
|
||||
public RestHighLevelClient elasticsearchClient() {
|
||||
return TestUtils.restHighLevelClient();
|
||||
public BulkFailureException(String msg, Map<String, String> failedDocuments) {
|
||||
super(msg);
|
||||
this.failedDocuments = failedDocuments;
|
||||
}
|
||||
|
||||
public Map<String, String> getFailedDocuments() {
|
||||
return failedDocuments;
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,7 @@ import org.springframework.lang.Nullable;
|
||||
* @author Rizwan Idrees
|
||||
* @author Mohsin Husen
|
||||
* @author Peter-Josef Meisch
|
||||
* @deprecated since 4.0, use {@link org.springframework.dao.UncategorizedDataAccessException}
|
||||
* @deprecated since 4.0, use {@link UncategorizedElasticsearchException}
|
||||
*/
|
||||
@Deprecated
|
||||
public class ElasticsearchException extends RuntimeException {
|
||||
|
||||
+5
@@ -22,6 +22,11 @@ import org.springframework.dao.UncategorizedDataAccessException;
|
||||
* @since 4.0
|
||||
*/
|
||||
public class UncategorizedElasticsearchException extends UncategorizedDataAccessException {
|
||||
|
||||
public UncategorizedElasticsearchException(String msg) {
|
||||
super(msg, null);
|
||||
}
|
||||
|
||||
public UncategorizedElasticsearchException(String msg, Throwable cause) {
|
||||
super(msg, cause);
|
||||
}
|
||||
|
||||
@@ -16,16 +16,58 @@
|
||||
package org.springframework.data.elasticsearch.annotations;
|
||||
|
||||
/**
|
||||
* Values based on reference doc - https://www.elastic.co/guide/reference/mapping/date-format/
|
||||
*
|
||||
* @author Jakub Vavrik
|
||||
* Values based on reference doc - https://www.elastic.co/guide/reference/mapping/date-format/
|
||||
* @author Tim te Beek
|
||||
* @author Peter-Josef Meisch
|
||||
*/
|
||||
public enum DateFormat {
|
||||
none, custom, basic_date, basic_date_time, basic_date_time_no_millis, basic_ordinal_date, basic_ordinal_date_time,
|
||||
basic_ordinal_date_time_no_millis, basic_time, basic_time_no_millis, basic_t_time, basic_t_time_no_millis,
|
||||
basic_week_date, basic_week_date_time, basic_week_date_time_no_millis, date, date_hour, date_hour_minute,
|
||||
date_hour_minute_second, date_hour_minute_second_fraction, date_hour_minute_second_millis, date_optional_time,
|
||||
date_time, date_time_no_millis, hour, hour_minute, hour_minute_second, hour_minute_second_fraction,
|
||||
hour_minute_second_millis, ordinal_date, ordinal_date_time, ordinal_date_time_no_millis, time, time_no_millis,
|
||||
t_time, t_time_no_millis, week_date, week_date_time, weekDateTimeNoMillis, week_year, weekyearWeek,
|
||||
weekyearWeekDay, year, year_month, year_month_day
|
||||
none, //
|
||||
custom, //
|
||||
basic_date, //
|
||||
basic_date_time, //
|
||||
basic_date_time_no_millis, //
|
||||
basic_ordinal_date, //
|
||||
basic_ordinal_date_time, //
|
||||
basic_ordinal_date_time_no_millis, //
|
||||
basic_time, //
|
||||
basic_time_no_millis, //
|
||||
basic_t_time, //
|
||||
basic_t_time_no_millis, //
|
||||
basic_week_date, //
|
||||
basic_week_date_time, //
|
||||
basic_week_date_time_no_millis, //
|
||||
date, //
|
||||
date_hour, //
|
||||
date_hour_minute, //
|
||||
date_hour_minute_second, //
|
||||
date_hour_minute_second_fraction, //
|
||||
date_hour_minute_second_millis, //
|
||||
date_optional_time, //
|
||||
date_time, //
|
||||
date_time_no_millis, //
|
||||
epoch_millis, //
|
||||
epoch_second, //
|
||||
hour, //
|
||||
hour_minute, //
|
||||
hour_minute_second, //
|
||||
hour_minute_second_fraction, //
|
||||
hour_minute_second_millis, //
|
||||
ordinal_date, //
|
||||
ordinal_date_time, //
|
||||
ordinal_date_time_no_millis, //
|
||||
time, //
|
||||
time_no_millis, //
|
||||
t_time, //
|
||||
t_time_no_millis, //
|
||||
week_date, //
|
||||
week_date_time, //
|
||||
week_date_time_no_millis, //
|
||||
weekyear, //
|
||||
weekyear_week, //
|
||||
weekyear_week_day, //
|
||||
year, //
|
||||
year_month, //
|
||||
year_month_day //
|
||||
}
|
||||
|
||||
@@ -53,17 +53,6 @@ public @interface Document {
|
||||
*/
|
||||
String indexName();
|
||||
|
||||
/**
|
||||
* Mapping type name. <br/>
|
||||
* deprecated as Elasticsearch does not support this anymore
|
||||
* (@see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/7.3/removal-of-types.html">Elastisearch removal of types documentation</a>) and will remove it in
|
||||
* Elasticsearch 8.
|
||||
*
|
||||
* @deprecated since 4.0
|
||||
*/
|
||||
@Deprecated
|
||||
String type() default "";
|
||||
|
||||
/**
|
||||
* Use server-side settings when creating the index.
|
||||
*/
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ import org.springframework.data.annotation.Persistent;
|
||||
* Elasticsearch dynamic templates mapping.
|
||||
* This annotation is handy if you prefer apply dynamic templates on fields with annotation e.g. {@link Field}
|
||||
* with type = FieldType.Object etc. instead of static mapping on Document via {@link Mapping} annotation.
|
||||
* DynamicTemplates annotation is ommited if {@link Mapping} annotation is used.
|
||||
* DynamicTemplates annotation is omitted if {@link Mapping} annotation is used.
|
||||
*
|
||||
* @author Petr Kukral
|
||||
*/
|
||||
|
||||
@@ -154,4 +154,35 @@ public @interface Field {
|
||||
* @since 4.0
|
||||
*/
|
||||
int maxShingleSize() default -1;
|
||||
|
||||
/**
|
||||
* if true, the field will be stored in Elasticsearch even if it has a null value
|
||||
*
|
||||
* @since 4.1
|
||||
*/
|
||||
boolean storeNullValue() default false;
|
||||
|
||||
/**
|
||||
* to be used in combination with {@link FieldType#Rank_Feature}
|
||||
*
|
||||
* @since 4.1
|
||||
*/
|
||||
boolean positiveScoreImpact() default true;
|
||||
|
||||
/**
|
||||
* to be used in combination with {@link FieldType#Object}
|
||||
*
|
||||
* @since 4.1
|
||||
*/
|
||||
boolean enabled() default true;
|
||||
|
||||
/**
|
||||
* @since 4.1
|
||||
*/
|
||||
boolean eagerGlobalOrdinals() default false;
|
||||
|
||||
/**
|
||||
* @since 4.1
|
||||
*/
|
||||
NullValueType nullValueType() default NullValueType.String;
|
||||
}
|
||||
|
||||
@@ -24,32 +24,36 @@ package org.springframework.data.elasticsearch.annotations;
|
||||
* @author Aleksei Arsenev
|
||||
*/
|
||||
public enum FieldType {
|
||||
Auto, //
|
||||
Text, //
|
||||
Keyword, //
|
||||
Long, //
|
||||
Integer, //
|
||||
Short, //
|
||||
Byte, //
|
||||
Double, //
|
||||
Float, //
|
||||
Half_Float, //
|
||||
Scaled_Float, //
|
||||
Date, //
|
||||
Date_Nanos, //
|
||||
Boolean, //
|
||||
Binary, //
|
||||
Integer_Range, //
|
||||
Float_Range, //
|
||||
Long_Range, //
|
||||
Double_Range, //
|
||||
Date_Range, //
|
||||
Ip_Range, //
|
||||
Object, //
|
||||
Nested, //
|
||||
Ip, //
|
||||
TokenCount, //
|
||||
Percolator, //
|
||||
Flattened, //
|
||||
Search_As_You_Type //
|
||||
Auto, //
|
||||
Text, //
|
||||
Keyword, //
|
||||
Long, //
|
||||
Integer, //
|
||||
Short, //
|
||||
Byte, //
|
||||
Double, //
|
||||
Float, //
|
||||
Half_Float, //
|
||||
Scaled_Float, //
|
||||
Date, //
|
||||
Date_Nanos, //
|
||||
Boolean, //
|
||||
Binary, //
|
||||
Integer_Range, //
|
||||
Float_Range, //
|
||||
Long_Range, //
|
||||
Double_Range, //
|
||||
Date_Range, //
|
||||
Ip_Range, //
|
||||
Object, //
|
||||
Nested, //
|
||||
Ip, //
|
||||
TokenCount, //
|
||||
Percolator, //
|
||||
Flattened, //
|
||||
Search_As_You_Type, //
|
||||
/** @since 4.1 */
|
||||
Rank_Feature, //
|
||||
/** @since 4.1 */
|
||||
Rank_Features //
|
||||
}
|
||||
|
||||
+16
-17
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2018-2020 the original author or authors.
|
||||
* Copyright 2017-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.
|
||||
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.junit.junit4;
|
||||
package org.springframework.data.elasticsearch.annotations;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
@@ -22,24 +22,23 @@ import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
* @author Lukas Vorisek
|
||||
* @author Peter-Josef Meisch
|
||||
* @since 4.1
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
@Target(ElementType.FIELD)
|
||||
@Documented
|
||||
public @interface ElasticsearchVersion {
|
||||
public @interface GeoShapeField {
|
||||
Orientation orientation() default Orientation.ccw;
|
||||
|
||||
/**
|
||||
* Inclusive lower bound of Elasticsearch server range.
|
||||
*
|
||||
* @return {@code 0.0.0} by default.
|
||||
*/
|
||||
String asOf() default "0.0.0";
|
||||
boolean ignoreMalformed() default false;
|
||||
|
||||
/**
|
||||
* Exclusive upper bound of Elasticsearch server range.
|
||||
*
|
||||
* @return {@code 9999.9999.9999} by default.
|
||||
*/
|
||||
String until() default "9999.9999.9999";
|
||||
boolean ignoreZValue() default true;
|
||||
|
||||
boolean coerce() default false;
|
||||
|
||||
enum Orientation {
|
||||
right, ccw, counterclockwise, left, cw, clockwise
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,7 @@ import java.lang.annotation.Target;
|
||||
* @author Aleksei Arsenev
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
@Target(ElementType.ANNOTATION_TYPE)
|
||||
public @interface InnerField {
|
||||
|
||||
String suffix();
|
||||
@@ -123,4 +123,21 @@ public @interface InnerField {
|
||||
* @since 4.0
|
||||
*/
|
||||
int maxShingleSize() default -1;
|
||||
|
||||
/**
|
||||
* to be used in combination with {@link FieldType#Rank_Feature}
|
||||
*
|
||||
* @since 4.1
|
||||
*/
|
||||
boolean positiveScoreImpact() default true;
|
||||
|
||||
/**
|
||||
* @since 4.1
|
||||
*/
|
||||
boolean eagerGlobalOrdinals() default false;
|
||||
|
||||
/**
|
||||
* @since 4.1
|
||||
*/
|
||||
NullValueType nullValueType() default NullValueType.String;
|
||||
}
|
||||
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 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.annotations;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* @author Subhobrata Dey
|
||||
* @since 4.1
|
||||
*/
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.ANNOTATION_TYPE)
|
||||
public @interface JoinTypeRelation {
|
||||
|
||||
String parent();
|
||||
|
||||
String[] children();
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 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.annotations;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* @author Subhobrata Dey
|
||||
* @since 4.1
|
||||
*/
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
@Inherited
|
||||
public @interface JoinTypeRelations {
|
||||
|
||||
JoinTypeRelation[] relations();
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright 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.annotations;
|
||||
|
||||
/**
|
||||
* @author Peter-Josef Meisch
|
||||
* @since 4.1
|
||||
*/
|
||||
public enum NullValueType {
|
||||
String, Integer, Long, Double
|
||||
}
|
||||
@@ -23,8 +23,9 @@ import org.springframework.data.annotation.Persistent;
|
||||
* Parent
|
||||
*
|
||||
* @author Philipp Jardas
|
||||
* @deprecated since 4.1, not supported anymore by Elasticsearch
|
||||
*/
|
||||
|
||||
@Deprecated
|
||||
@Persistent
|
||||
@Inherited
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
|
||||
@@ -20,5 +20,5 @@ package org.springframework.data.elasticsearch.annotations;
|
||||
* @since 4.0
|
||||
*/
|
||||
public enum TermVector {
|
||||
none, no, yes, with_positions, with_offsets, woth_positions_offsets, with_positions_payloads, with_positions_offets_payloads
|
||||
none, no, yes, with_positions, with_offsets, with_positions_offsets, with_positions_payloads, with_positions_offsets_payloads
|
||||
}
|
||||
|
||||
@@ -44,7 +44,9 @@ import org.springframework.util.StringUtils;
|
||||
* @author Mohsin Husen
|
||||
* @author Ilkang Na
|
||||
* @author Peter-Josef Meisch
|
||||
* @deprecated since 4.1, we're not supporting embedded Node clients anymore, use the REST client
|
||||
*/
|
||||
@Deprecated
|
||||
public class NodeClientFactoryBean implements FactoryBean<Client>, InitializingBean, DisposableBean {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(NodeClientFactoryBean.class);
|
||||
|
||||
+265
-223
@@ -22,13 +22,10 @@ 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.ProxyProvider;
|
||||
import reactor.netty.tcp.TcpClient;
|
||||
import reactor.netty.transport.ProxyProvider;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Method;
|
||||
@@ -48,17 +45,23 @@ 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.alias.IndicesAliasesRequest;
|
||||
import org.elasticsearch.action.admin.indices.alias.get.GetAliasesRequest;
|
||||
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.get.GetMappingsRequest;
|
||||
import org.elasticsearch.action.admin.indices.mapping.get.GetMappingsResponse;
|
||||
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.admin.indices.settings.get.GetSettingsRequest;
|
||||
import org.elasticsearch.action.admin.indices.settings.get.GetSettingsResponse;
|
||||
import org.elasticsearch.action.admin.indices.template.delete.DeleteIndexTemplateRequest;
|
||||
import org.elasticsearch.action.bulk.BulkRequest;
|
||||
import org.elasticsearch.action.bulk.BulkResponse;
|
||||
import org.elasticsearch.action.delete.DeleteRequest;
|
||||
@@ -79,7 +82,12 @@ 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.GetAliasesResponse;
|
||||
import org.elasticsearch.client.Request;
|
||||
import org.elasticsearch.client.indices.GetIndexTemplatesRequest;
|
||||
import org.elasticsearch.client.indices.GetIndexTemplatesResponse;
|
||||
import org.elasticsearch.client.indices.IndexTemplatesExistRequest;
|
||||
import org.elasticsearch.client.indices.PutIndexTemplateRequest;
|
||||
import org.elasticsearch.common.unit.TimeValue;
|
||||
import org.elasticsearch.common.xcontent.DeprecationHandler;
|
||||
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
|
||||
@@ -93,7 +101,9 @@ import org.elasticsearch.rest.RestStatus;
|
||||
import org.elasticsearch.search.SearchHit;
|
||||
import org.elasticsearch.search.SearchHits;
|
||||
import org.elasticsearch.search.aggregations.Aggregation;
|
||||
import org.elasticsearch.search.suggest.Suggest;
|
||||
import org.reactivestreams.Publisher;
|
||||
import org.springframework.data.elasticsearch.UncategorizedElasticsearchException;
|
||||
import org.springframework.data.elasticsearch.client.ClientConfiguration;
|
||||
import org.springframework.data.elasticsearch.client.ClientLogger;
|
||||
import org.springframework.data.elasticsearch.client.ElasticsearchHost;
|
||||
@@ -105,7 +115,6 @@ import org.springframework.data.elasticsearch.client.util.ScrollState;
|
||||
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;
|
||||
@@ -128,6 +137,8 @@ import org.springframework.web.reactive.function.client.WebClient.RequestBodySpe
|
||||
* @author Henrique Amaral
|
||||
* @author Roman Puchkovskiy
|
||||
* @author Russell Parry
|
||||
* @author Thomas Geese
|
||||
* @author Brian Clozel
|
||||
* @since 3.2
|
||||
* @see ClientConfiguration
|
||||
* @see ReactiveRestClients
|
||||
@@ -164,13 +175,6 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
|
||||
this.requestCreator = requestCreator;
|
||||
}
|
||||
|
||||
public void setHeadersSupplier(Supplier<HttpHeaders> headersSupplier) {
|
||||
|
||||
Assert.notNull(headersSupplier, "headersSupplier must not be null");
|
||||
|
||||
this.headersSupplier = headersSupplier;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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}
|
||||
@@ -235,14 +239,14 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
|
||||
Duration connectTimeout = clientConfiguration.getConnectTimeout();
|
||||
Duration soTimeout = clientConfiguration.getSocketTimeout();
|
||||
|
||||
TcpClient tcpClient = TcpClient.create();
|
||||
HttpClient httpClient = HttpClient.create().compress(true);
|
||||
|
||||
if (!connectTimeout.isNegative()) {
|
||||
tcpClient = tcpClient.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, Math.toIntExact(connectTimeout.toMillis()));
|
||||
httpClient = httpClient.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, Math.toIntExact(connectTimeout.toMillis()));
|
||||
}
|
||||
|
||||
if (!soTimeout.isNegative()) {
|
||||
tcpClient = tcpClient.doOnConnected(connection -> connection //
|
||||
httpClient = httpClient.doOnConnected(connection -> connection //
|
||||
.addHandlerLast(new ReadTimeoutHandler(soTimeout.toMillis(), TimeUnit.MILLISECONDS))
|
||||
.addHandlerLast(new WriteTimeoutHandler(soTimeout.toMillis(), TimeUnit.MILLISECONDS)));
|
||||
}
|
||||
@@ -254,22 +258,20 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
|
||||
if (hostPort.length != 2) {
|
||||
throw new IllegalArgumentException("invalid proxy configuration " + proxy + ", should be \"host:port\"");
|
||||
}
|
||||
tcpClient = tcpClient.proxy(proxyOptions -> proxyOptions.type(ProxyProvider.Proxy.HTTP).host(hostPort[0])
|
||||
httpClient = httpClient.proxy(proxyOptions -> proxyOptions.type(ProxyProvider.Proxy.HTTP).host(hostPort[0])
|
||||
.port(Integer.parseInt(hostPort[1])));
|
||||
}
|
||||
|
||||
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));
|
||||
});
|
||||
httpClient = httpClient
|
||||
.secure(sslContextSpec -> sslContextSpec.sslContext(new JdkSslContext(sslContext.get(), true, null,
|
||||
IdentityCipherSuiteFilter.INSTANCE, ApplicationProtocolConfig.DISABLED, ClientAuth.NONE, null, false)));
|
||||
} else {
|
||||
httpClient = httpClient.secure();
|
||||
}
|
||||
@@ -289,6 +291,13 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
|
||||
return provider;
|
||||
}
|
||||
|
||||
public void setHeadersSupplier(Supplier<HttpHeaders> headersSupplier) {
|
||||
|
||||
Assert.notNull(headersSupplier, "headersSupplier must not be null");
|
||||
|
||||
this.headersSupplier = headersSupplier;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient#ping(org.springframework.http.HttpHeaders)
|
||||
@@ -297,7 +306,7 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
|
||||
public Mono<Boolean> ping(HttpHeaders headers) {
|
||||
|
||||
return sendRequest(new MainRequest(), requestCreator.ping(), RawActionResponse.class, headers) //
|
||||
.map(response -> response.statusCode().is2xxSuccessful()) //
|
||||
.flatMap(response -> response.releaseBody().thenReturn(response.statusCode().is2xxSuccessful())) //
|
||||
.onErrorResume(NoReachableHostException.class, error -> Mono.just(false)).next();
|
||||
}
|
||||
|
||||
@@ -347,7 +356,7 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
|
||||
public Mono<Boolean> exists(HttpHeaders headers, GetRequest getRequest) {
|
||||
|
||||
return sendRequest(getRequest, requestCreator.exists(), RawActionResponse.class, headers) //
|
||||
.map(response -> response.statusCode().is2xxSuccessful()) //
|
||||
.flatMap(response -> response.releaseBody().thenReturn(response.statusCode().is2xxSuccessful())) //
|
||||
.next();
|
||||
}
|
||||
|
||||
@@ -357,7 +366,7 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
|
||||
*/
|
||||
@Override
|
||||
public Mono<IndexResponse> index(HttpHeaders headers, IndexRequest indexRequest) {
|
||||
return sendRequest(indexRequest, requestCreator.index(), IndexResponse.class, headers).publishNext();
|
||||
return sendRequest(indexRequest, requestCreator.index(), IndexResponse.class, headers).next();
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -375,7 +384,7 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
|
||||
*/
|
||||
@Override
|
||||
public Mono<UpdateResponse> update(HttpHeaders headers, UpdateRequest updateRequest) {
|
||||
return sendRequest(updateRequest, requestCreator.update(), UpdateResponse.class, headers).publishNext();
|
||||
return sendRequest(updateRequest, requestCreator.update(), UpdateResponse.class, headers).next();
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -386,7 +395,7 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
|
||||
public Mono<DeleteResponse> delete(HttpHeaders headers, DeleteRequest deleteRequest) {
|
||||
|
||||
return sendRequest(deleteRequest, requestCreator.delete(), DeleteResponse.class, headers) //
|
||||
.publishNext();
|
||||
.next();
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -416,6 +425,17 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
|
||||
.flatMap(Flux::fromIterable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<SearchResponse> searchForResponse(HttpHeaders headers, SearchRequest searchRequest) {
|
||||
return sendRequest(searchRequest, requestCreator.search(), SearchResponse.class, headers).next();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<Suggest> suggest(HttpHeaders headers, SearchRequest searchRequest) {
|
||||
return sendRequest(searchRequest, requestCreator.search(), SearchResponse.class, headers) //
|
||||
.map(SearchResponse::getSuggest);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient#aggregate(org.springframework.http.HttpHeaders, org.elasticsearch.action.search.SearchRequest)
|
||||
@@ -448,55 +468,26 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
|
||||
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 -> {
|
||||
state -> sendRequest(searchRequest, requestCreator.search(), SearchResponse.class, headers)
|
||||
.expand(searchResponse -> {
|
||||
|
||||
Flux<SearchHit> searchHits = inbound.<SearchResponse> handle((searchResponse, sink) -> {
|
||||
state.updateScrollId(searchResponse.getScrollId());
|
||||
if (isEmpty(searchResponse.getHits())) {
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
scrollState.updateScrollId(searchResponse.getScrollId());
|
||||
if (isEmpty(searchResponse.getHits())) {
|
||||
return sendRequest(new SearchScrollRequest(searchResponse.getScrollId()).scroll(scrollTimeout),
|
||||
requestCreator.scroll(), SearchResponse.class, headers);
|
||||
|
||||
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)); //
|
||||
(state, ex) -> cleanupScroll(headers, state), //
|
||||
state -> cleanupScroll(headers, state)) //
|
||||
.filter(it -> !isEmpty(it.getHits())) //
|
||||
.map(SearchResponse::getHits) //
|
||||
.flatMapIterable(Function.identity()); //
|
||||
}
|
||||
|
||||
private static boolean isEmpty(@Nullable SearchHits hits) {
|
||||
@@ -524,7 +515,7 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
|
||||
public Mono<BulkByScrollResponse> deleteBy(HttpHeaders headers, DeleteByQueryRequest deleteRequest) {
|
||||
|
||||
return sendRequest(deleteRequest, requestCreator.deleteByQuery(), BulkByScrollResponse.class, headers) //
|
||||
.publishNext();
|
||||
.next();
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -534,106 +525,11 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
|
||||
@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) {
|
||||
public <T> Mono<T> execute(ReactiveElasticsearchClientCallback<T> callback) {
|
||||
|
||||
return this.hostProvider.getActive(Verification.LAZY) //
|
||||
.flatMap(callback::doWithClient) //
|
||||
@@ -667,8 +563,8 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
|
||||
|
||||
// -->
|
||||
|
||||
private <Req extends ActionRequest, Resp> Flux<Resp> sendRequest(Req request, Function<Req, Request> converter,
|
||||
Class<Resp> responseType, HttpHeaders headers) {
|
||||
private <REQ, RESP> Flux<RESP> sendRequest(REQ request, Function<REQ, Request> converter, Class<RESP> responseType,
|
||||
HttpHeaders headers) {
|
||||
return sendRequest(converter.apply(request), responseType, headers);
|
||||
}
|
||||
|
||||
@@ -676,11 +572,14 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
|
||||
|
||||
String logId = ClientLogger.newLogId();
|
||||
|
||||
return execute(webClient -> sendRequest(webClient, logId, request, headers))
|
||||
.flatMapMany(response -> readResponseBody(logId, request, response, responseType));
|
||||
return Flux
|
||||
.from(execute(webClient -> sendRequest(webClient, logId, request, headers).exchangeToMono(clientResponse -> {
|
||||
Publisher<? extends Resp> publisher = readResponseBody(logId, request, clientResponse, responseType);
|
||||
return Mono.from(publisher);
|
||||
})));
|
||||
}
|
||||
|
||||
private Mono<ClientResponse> sendRequest(WebClient webClient, String logId, Request request, HttpHeaders headers) {
|
||||
private RequestBodySpec sendRequest(WebClient webClient, String logId, Request request, HttpHeaders headers) {
|
||||
|
||||
RequestBodySpec requestBodySpec = webClient.method(HttpMethod.valueOf(request.getMethod().toUpperCase())) //
|
||||
.uri(builder -> {
|
||||
@@ -728,23 +627,120 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
|
||||
ClientLogger.logRequest(logId, request.getMethod().toUpperCase(), request.getEndpoint(), request.getParameters());
|
||||
}
|
||||
|
||||
return requestBodySpec //
|
||||
.exchange() //
|
||||
.onErrorReturn(ConnectException.class, ClientResponse.create(HttpStatus.SERVICE_UNAVAILABLE).build());
|
||||
return requestBodySpec;
|
||||
}
|
||||
|
||||
private Lazy<String> bodyExtractor(Request request) {
|
||||
// region indices operations
|
||||
@Override
|
||||
public Mono<Boolean> createIndex(HttpHeaders headers, CreateIndexRequest createIndexRequest) {
|
||||
|
||||
return Lazy.of(() -> {
|
||||
|
||||
try {
|
||||
return EntityUtils.toString(request.getEntity());
|
||||
} catch (IOException e) {
|
||||
throw new RequestBodyEncodingException("Error encoding request", e);
|
||||
}
|
||||
});
|
||||
return sendRequest(createIndexRequest, requestCreator.indexCreate(), AcknowledgedResponse.class, headers) //
|
||||
.map(AcknowledgedResponse::isAcknowledged) //
|
||||
.next();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> closeIndex(HttpHeaders headers, CloseIndexRequest closeIndexRequest) {
|
||||
|
||||
return sendRequest(closeIndexRequest, requestCreator.indexClose(), AcknowledgedResponse.class, headers) //
|
||||
.then();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Boolean> existsIndex(HttpHeaders headers, GetIndexRequest request) {
|
||||
|
||||
return sendRequest(request, requestCreator.indexExists(), RawActionResponse.class, headers) //
|
||||
.flatMap(response -> response.releaseBody().thenReturn(response.statusCode().is2xxSuccessful())) //
|
||||
.next();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Boolean> deleteIndex(HttpHeaders headers, DeleteIndexRequest request) {
|
||||
|
||||
return sendRequest(request, requestCreator.indexDelete(), AcknowledgedResponse.class, headers) //
|
||||
.map(AcknowledgedResponse::isAcknowledged) //
|
||||
.next();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> flushIndex(HttpHeaders headers, FlushRequest flushRequest) {
|
||||
|
||||
return sendRequest(flushRequest, requestCreator.flushIndex(), FlushResponse.class, headers) //
|
||||
.then();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<GetMappingsResponse> getMapping(HttpHeaders headers, GetMappingsRequest getMappingsRequest) {
|
||||
return sendRequest(getMappingsRequest, requestCreator.getMapping(), GetMappingsResponse.class, headers).next();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<GetSettingsResponse> getSettings(HttpHeaders headers, GetSettingsRequest getSettingsRequest) {
|
||||
return sendRequest(getSettingsRequest, requestCreator.getSettings(), GetSettingsResponse.class, headers).next();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Boolean> putMapping(HttpHeaders headers, PutMappingRequest putMappingRequest) {
|
||||
|
||||
return sendRequest(putMappingRequest, requestCreator.putMapping(), AcknowledgedResponse.class, headers) //
|
||||
.map(AcknowledgedResponse::isAcknowledged) //
|
||||
.next();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> openIndex(HttpHeaders headers, OpenIndexRequest request) {
|
||||
|
||||
return sendRequest(request, requestCreator.indexOpen(), AcknowledgedResponse.class, headers) //
|
||||
.then();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> refreshIndex(HttpHeaders headers, RefreshRequest refreshRequest) {
|
||||
|
||||
return sendRequest(refreshRequest, requestCreator.indexRefresh(), RefreshResponse.class, headers) //
|
||||
.then();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Boolean> updateAliases(HttpHeaders headers, IndicesAliasesRequest indicesAliasesRequest) {
|
||||
return sendRequest(indicesAliasesRequest, requestCreator.updateAlias(), AcknowledgedResponse.class, headers)
|
||||
.map(AcknowledgedResponse::isAcknowledged).next();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<GetAliasesResponse> getAliases(HttpHeaders headers, GetAliasesRequest getAliasesRequest) {
|
||||
return sendRequest(getAliasesRequest, requestCreator.getAlias(), GetAliasesResponse.class, headers).next();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Boolean> putTemplate(HttpHeaders headers, PutIndexTemplateRequest putIndexTemplateRequest) {
|
||||
return sendRequest(putIndexTemplateRequest, requestCreator.putTemplate(), AcknowledgedResponse.class, headers)
|
||||
.map(AcknowledgedResponse::isAcknowledged).next();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<GetIndexTemplatesResponse> getTemplate(HttpHeaders headers,
|
||||
GetIndexTemplatesRequest getIndexTemplatesRequest) {
|
||||
return (sendRequest(getIndexTemplatesRequest, requestCreator.getTemplates(), GetIndexTemplatesResponse.class,
|
||||
headers)).next();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Boolean> existsTemplate(HttpHeaders headers, IndexTemplatesExistRequest indexTemplatesExistRequest) {
|
||||
return sendRequest(indexTemplatesExistRequest, requestCreator.templatesExist(), RawActionResponse.class, headers) //
|
||||
.flatMap(response -> response.releaseBody().thenReturn(response.statusCode().is2xxSuccessful())) //
|
||||
.next();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Boolean> deleteTemplate(HttpHeaders headers, DeleteIndexTemplateRequest deleteIndexTemplateRequest) {
|
||||
return sendRequest(deleteIndexTemplateRequest, requestCreator.deleteTemplate(), AcknowledgedResponse.class, headers)
|
||||
.map(AcknowledgedResponse::isAcknowledged).next();
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region helper functions
|
||||
private <T> Publisher<? extends T> readResponseBody(String logId, Request request, ClientResponse response,
|
||||
Class<T> responseType) {
|
||||
|
||||
@@ -763,7 +759,7 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
|
||||
if (response.statusCode().is4xxClientError()) {
|
||||
|
||||
ClientLogger.logRawResponse(logId, response.statusCode());
|
||||
return handleClientError(logId, request, response, responseType);
|
||||
return handleClientError(logId, response, responseType);
|
||||
}
|
||||
|
||||
return response.body(BodyExtractors.toMono(byte[].class)) //
|
||||
@@ -780,6 +776,10 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
|
||||
|
||||
Method fromXContent = ReflectionUtils.findMethod(responseType, "fromXContent", XContentParser.class);
|
||||
|
||||
if (fromXContent == null) {
|
||||
return Mono.error(new UncategorizedElasticsearchException(
|
||||
"No method named fromXContent found in " + responseType.getCanonicalName()));
|
||||
}
|
||||
return Mono.justOrEmpty(responseType
|
||||
.cast(ReflectionUtils.invokeMethod(fromXContent, responseType, createParser(mediaType, content))));
|
||||
|
||||
@@ -802,55 +802,97 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
|
||||
DeprecationHandler.THROW_UNSUPPORTED_OPERATION, content);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region error and exception handling
|
||||
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) {
|
||||
int statusCode = response.statusCode().value();
|
||||
RestStatus status = RestStatus.fromCode(statusCode);
|
||||
String mediaType = response.headers().contentType().map(MediaType::toString).orElse(XContentType.JSON.mediaType());
|
||||
|
||||
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 -> contentOrError(content, mediaType, status))
|
||||
.flatMap(unused -> Mono
|
||||
.error(new ElasticsearchStatusException(String.format("%s request to %s returned error code %s.",
|
||||
request.getMethod(), request.getEndpoint(), statusCode), status)));
|
||||
}
|
||||
|
||||
private <T> Publisher<? extends T> handleClientError(String logId, ClientResponse response, Class<T> responseType) {
|
||||
|
||||
int statusCode = response.statusCode().value();
|
||||
RestStatus status = RestStatus.fromCode(statusCode);
|
||||
String mediaType = response.headers().contentType().map(MediaType::toString).orElse(XContentType.JSON.mediaType());
|
||||
|
||||
return response.body(BodyExtractors.toMono(byte[].class)) //
|
||||
.map(bytes -> new String(bytes, StandardCharsets.UTF_8)) //
|
||||
.flatMap(content -> contentOrError(content, mediaType, status)) //
|
||||
.doOnNext(content -> ClientLogger.logResponse(logId, response.statusCode(), content)) //
|
||||
.flatMap(content -> doDecode(response, responseType, content));
|
||||
}
|
||||
|
||||
// region ElasticsearchException helper
|
||||
/**
|
||||
* checks if the given content body contains an {@link ElasticsearchException}, if yes it is returned in a Mono.error.
|
||||
* Otherwise the content is returned in the Mono
|
||||
*
|
||||
* @param content the content to analyze
|
||||
* @param mediaType the returned media type
|
||||
* @param status the response status
|
||||
* @return a Mono with the content or an Mono.error
|
||||
*/
|
||||
private static Mono<String> contentOrError(String content, String mediaType, RestStatus status) {
|
||||
|
||||
ElasticsearchException exception = getElasticsearchException(content, mediaType, status);
|
||||
|
||||
if (exception != null) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
buildExceptionMessages(sb, exception);
|
||||
return Mono.error(new ElasticsearchStatusException(sb.toString(), status, exception));
|
||||
}
|
||||
|
||||
return Mono.just(content);
|
||||
}
|
||||
|
||||
/**
|
||||
* tries to parse an {@link ElasticsearchException} from the given body content
|
||||
*
|
||||
* @param content the content to analyse
|
||||
* @param mediaType the type of the body content
|
||||
* @return an {@link ElasticsearchException} or {@literal null}.
|
||||
*/
|
||||
@Nullable
|
||||
private ElasticsearchException getElasticsearchException(ClientResponse response, String content, String mediaType)
|
||||
throws IOException {
|
||||
private static ElasticsearchException getElasticsearchException(String content, String mediaType, RestStatus status) {
|
||||
|
||||
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
|
||||
try {
|
||||
XContentParser parser = createParser(mediaType, content);
|
||||
// we have a JSON object with an error and a status field
|
||||
parser.nextToken(); // Skip START_OBJECT
|
||||
|
||||
do {
|
||||
token = parser.nextToken();
|
||||
XContentParser.Token token;
|
||||
do {
|
||||
token = parser.nextToken();
|
||||
|
||||
if (parser.currentName().equals("error")) {
|
||||
return ElasticsearchException.failureFromXContent(parser);
|
||||
}
|
||||
} while (token == XContentParser.Token.FIELD_NAME);
|
||||
return null;
|
||||
if ("error".equals(parser.currentName())) {
|
||||
return ElasticsearchException.failureFromXContent(parser);
|
||||
}
|
||||
} while (token == XContentParser.Token.FIELD_NAME);
|
||||
|
||||
return null;
|
||||
} catch (IOException e) {
|
||||
return new ElasticsearchStatusException(content, status);
|
||||
}
|
||||
}
|
||||
|
||||
private static void buildExceptionMessages(StringBuilder sb, Throwable t) {
|
||||
@@ -869,7 +911,7 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
class ClientStatus implements Status {
|
||||
static class ClientStatus implements Status {
|
||||
|
||||
private final Collection<ElasticsearchHost> connectedHosts;
|
||||
|
||||
|
||||
+6
-5
@@ -15,7 +15,6 @@
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.client.reactive;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.util.function.Tuple2;
|
||||
@@ -33,6 +32,7 @@ import java.util.function.Supplier;
|
||||
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.http.HttpHeaders;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.web.reactive.function.client.ClientResponse;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
@@ -121,15 +121,15 @@ class MultiNodeHostProvider implements HostProvider {
|
||||
.map(ElasticsearchHost::getEndpoint).next();
|
||||
}
|
||||
|
||||
private ElasticsearchHost updateNodeState(Tuple2<InetSocketAddress, ClientResponse> tuple2) {
|
||||
private ElasticsearchHost updateNodeState(Tuple2<InetSocketAddress, State> tuple2) {
|
||||
|
||||
State state = tuple2.getT2().statusCode().isError() ? State.OFFLINE : State.ONLINE;
|
||||
State state = tuple2.getT2();
|
||||
ElasticsearchHost elasticsearchHost = new ElasticsearchHost(tuple2.getT1(), state);
|
||||
hosts.put(tuple2.getT1(), elasticsearchHost);
|
||||
return elasticsearchHost;
|
||||
}
|
||||
|
||||
private Flux<Tuple2<InetSocketAddress, ClientResponse>> nodes(@Nullable State state) {
|
||||
private Flux<Tuple2<InetSocketAddress, State>> nodes(@Nullable State state) {
|
||||
|
||||
return Flux.fromIterable(hosts()) //
|
||||
.filter(entry -> state == null || entry.getState().equals(state)) //
|
||||
@@ -144,7 +144,8 @@ class MultiNodeHostProvider implements HostProvider {
|
||||
clientProvider.getErrorListener().accept(throwable);
|
||||
});
|
||||
|
||||
return Mono.just(host).zipWith(exchange);
|
||||
return Mono.just(host).zipWith(exchange
|
||||
.flatMap(it -> it.releaseBody().thenReturn(it.statusCode().isError() ? State.OFFLINE : State.ONLINE)));
|
||||
}) //
|
||||
.onErrorContinue((throwable, o) -> clientProvider.getErrorListener().accept(throwable));
|
||||
}
|
||||
|
||||
+11
-1
@@ -15,11 +15,12 @@
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.client.reactive;
|
||||
|
||||
import org.elasticsearch.common.io.stream.StreamOutput;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.elasticsearch.action.ActionResponse;
|
||||
import org.elasticsearch.common.io.stream.StreamOutput;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.client.reactive.ClientHttpResponse;
|
||||
@@ -73,4 +74,13 @@ class RawActionResponse extends ActionResponse {
|
||||
@Override
|
||||
public void writeTo(StreamOutput out) throws IOException {
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure the response body is released to properly release the underlying connection.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Mono<Void> releaseBody() {
|
||||
return delegate.releaseBody();
|
||||
}
|
||||
}
|
||||
|
||||
+440
-23
@@ -22,14 +22,21 @@ import java.net.ConnectException;
|
||||
import java.util.Collection;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequest;
|
||||
import org.elasticsearch.action.admin.indices.alias.get.GetAliasesRequest;
|
||||
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.get.GetMappingsRequest;
|
||||
import org.elasticsearch.action.admin.indices.mapping.get.GetMappingsResponse;
|
||||
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.settings.get.GetSettingsRequest;
|
||||
import org.elasticsearch.action.admin.indices.settings.get.GetSettingsResponse;
|
||||
import org.elasticsearch.action.admin.indices.template.delete.DeleteIndexTemplateRequest;
|
||||
import org.elasticsearch.action.bulk.BulkRequest;
|
||||
import org.elasticsearch.action.bulk.BulkResponse;
|
||||
import org.elasticsearch.action.delete.DeleteRequest;
|
||||
@@ -43,11 +50,17 @@ 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.GetAliasesResponse;
|
||||
import org.elasticsearch.client.indices.GetIndexTemplatesRequest;
|
||||
import org.elasticsearch.client.indices.GetIndexTemplatesResponse;
|
||||
import org.elasticsearch.client.indices.IndexTemplatesExistRequest;
|
||||
import org.elasticsearch.client.indices.PutIndexTemplateRequest;
|
||||
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.elasticsearch.search.aggregations.Aggregation;
|
||||
import org.elasticsearch.search.suggest.Suggest;
|
||||
import org.springframework.data.elasticsearch.client.ClientConfiguration;
|
||||
import org.springframework.data.elasticsearch.client.ElasticsearchHost;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
@@ -63,6 +76,7 @@ import org.springframework.web.reactive.function.client.WebClient;
|
||||
* @author Mark Paluch
|
||||
* @author Peter-Josef Meisch
|
||||
* @author Henrique Amaral
|
||||
* @author Thomas Geese
|
||||
* @since 3.2
|
||||
* @see ClientConfiguration
|
||||
* @see ReactiveRestClients
|
||||
@@ -413,14 +427,73 @@ public interface ReactiveElasticsearchClient {
|
||||
*/
|
||||
Flux<SearchHit> search(HttpHeaders headers, SearchRequest searchRequest);
|
||||
|
||||
/**
|
||||
* Execute the given {@link SearchRequest} against the {@literal search} API returning the whole response in one Mono.
|
||||
*
|
||||
* @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 Mono} emitting the whole {@link SearchResponse}.
|
||||
* @since 4.1
|
||||
*/
|
||||
default Mono<SearchResponse> searchForResponse(SearchRequest searchRequest) {
|
||||
return searchForResponse(HttpHeaders.EMPTY, searchRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the given {@link SearchRequest} against the {@literal search} API returning the whole response in one Mono.
|
||||
*
|
||||
* @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 Mono} emitting the whole {@link SearchResponse}.
|
||||
* @since 4.1
|
||||
*/
|
||||
Mono<SearchResponse> searchForResponse(HttpHeaders headers, SearchRequest searchRequest);
|
||||
|
||||
/**
|
||||
* Execute the given {@link SearchRequest} against the {@literal search} API.
|
||||
*
|
||||
* @param consumer never {@literal null}.
|
||||
* @return the {@link Flux} emitting {@link Suggest suggestions} one by one.
|
||||
* @since 4.1
|
||||
*/
|
||||
default Flux<Suggest> suggest(Consumer<SearchRequest> consumer) {
|
||||
|
||||
SearchRequest request = new SearchRequest();
|
||||
consumer.accept(request);
|
||||
return suggest(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the given {@link SearchRequest} against the {@literal search} API.
|
||||
*
|
||||
* @param searchRequest must not be {@literal null}.
|
||||
* @return the {@link Flux} emitting {@link Suggest suggestions} one by one.
|
||||
* @since 4.1
|
||||
*/
|
||||
default Flux<Suggest> suggest(SearchRequest searchRequest) {
|
||||
return suggest(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}.
|
||||
* @return the {@link Flux} emitting {@link Suggest suggestions} one by one.
|
||||
* @since 4.1
|
||||
*/
|
||||
Flux<Suggest> suggest(HttpHeaders headers, SearchRequest searchRequest);
|
||||
|
||||
/**
|
||||
* Execute the given {@link SearchRequest} with aggregations against the {@literal search} API.
|
||||
*
|
||||
* @param consumer
|
||||
* never {@literal null}.
|
||||
* @param consumer never {@literal null}.
|
||||
* @return the {@link Flux} emitting {@link Aggregation} one by one.
|
||||
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-search.html">Search API on
|
||||
* elastic.co</a>
|
||||
* elastic.co</a>
|
||||
* @since 4.0
|
||||
*/
|
||||
default Flux<Aggregation> aggregate(Consumer<SearchRequest> consumer) {
|
||||
@@ -565,9 +638,11 @@ public interface ReactiveElasticsearchClient {
|
||||
* unavailable.
|
||||
*
|
||||
* @param callback the {@link ReactiveElasticsearchClientCallback callback} wielding the actual command to run.
|
||||
* @param <T> the type emitted by the returned Mono.
|
||||
* @return the {@link Mono} emitting the {@link ClientResponse} once subscribed.
|
||||
*/
|
||||
Mono<ClientResponse> execute(ReactiveElasticsearchClientCallback callback);
|
||||
@SuppressWarnings("JavaDoc")
|
||||
<T> Mono<T> execute(ReactiveElasticsearchClientCallback<T> callback);
|
||||
|
||||
/**
|
||||
* Get the current client {@link Status}. <br />
|
||||
@@ -581,11 +656,12 @@ public interface ReactiveElasticsearchClient {
|
||||
/**
|
||||
* Low level callback interface operating upon {@link WebClient} to send commands towards elasticsearch.
|
||||
*
|
||||
* @param <T> the type emitted by the returned Mono.
|
||||
* @author Christoph Strobl
|
||||
* @since 3.2
|
||||
*/
|
||||
interface ReactiveElasticsearchClientCallback {
|
||||
Mono<ClientResponse> doWithClient(WebClient client);
|
||||
interface ReactiveElasticsearchClientCallback<T> {
|
||||
Mono<T> doWithClient(WebClient client);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -674,7 +750,7 @@ public interface ReactiveElasticsearchClient {
|
||||
* @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) {
|
||||
default Mono<Boolean> deleteIndex(Consumer<DeleteIndexRequest> consumer) {
|
||||
|
||||
DeleteIndexRequest request = new DeleteIndexRequest();
|
||||
consumer.accept(request);
|
||||
@@ -690,7 +766,7 @@ public interface ReactiveElasticsearchClient {
|
||||
* @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) {
|
||||
default Mono<Boolean> deleteIndex(DeleteIndexRequest deleteIndexRequest) {
|
||||
return deleteIndex(HttpHeaders.EMPTY, deleteIndexRequest);
|
||||
}
|
||||
|
||||
@@ -704,18 +780,18 @@ public interface ReactiveElasticsearchClient {
|
||||
* @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);
|
||||
Mono<Boolean> 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.
|
||||
* @return a {@link Mono} signalling successful 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) {
|
||||
default Mono<Boolean> createIndex(Consumer<CreateIndexRequest> consumer) {
|
||||
|
||||
CreateIndexRequest request = new CreateIndexRequest();
|
||||
consumer.accept(request);
|
||||
@@ -726,12 +802,12 @@ public interface ReactiveElasticsearchClient {
|
||||
* 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.
|
||||
* @return a {@link Mono} signalling successful 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) {
|
||||
default Mono<Boolean> createIndex(CreateIndexRequest createIndexRequest) {
|
||||
return createIndex(HttpHeaders.EMPTY, createIndexRequest);
|
||||
}
|
||||
|
||||
@@ -740,12 +816,12 @@ public interface ReactiveElasticsearchClient {
|
||||
*
|
||||
* @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.
|
||||
* @return a {@link Mono} signalling successful 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);
|
||||
Mono<Boolean> createIndex(HttpHeaders headers, CreateIndexRequest createIndexRequest);
|
||||
|
||||
/**
|
||||
* Execute the given {@link OpenIndexRequest} against the {@literal indices} API.
|
||||
@@ -878,12 +954,58 @@ public interface ReactiveElasticsearchClient {
|
||||
* 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>
|
||||
* @deprecated since 4.1, use {@link #putMapping(Consumer)}
|
||||
*/
|
||||
default Mono<Void> updateMapping(Consumer<PutMappingRequest> consumer) {
|
||||
@Deprecated
|
||||
default Mono<Boolean> updateMapping(Consumer<PutMappingRequest> consumer) {
|
||||
return putMapping(consumer);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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>
|
||||
* @deprecated since 4.1, use {@link #putMapping(PutMappingRequest)}
|
||||
*/
|
||||
@Deprecated
|
||||
default Mono<Boolean> updateMapping(PutMappingRequest putMappingRequest) {
|
||||
return putMapping(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>
|
||||
* @deprecated since 4.1, use {@link #putMapping(HttpHeaders, PutMappingRequest)}
|
||||
*/
|
||||
@Deprecated
|
||||
default Mono<Boolean> updateMapping(HttpHeaders headers, PutMappingRequest putMappingRequest) {
|
||||
return putMapping(headers, putMappingRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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<Boolean> putMapping(Consumer<PutMappingRequest> consumer) {
|
||||
|
||||
PutMappingRequest request = new PutMappingRequest();
|
||||
consumer.accept(request);
|
||||
return updateMapping(request);
|
||||
return putMapping(request);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -895,8 +1017,8 @@ public interface ReactiveElasticsearchClient {
|
||||
* @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);
|
||||
default Mono<Boolean> putMapping(PutMappingRequest putMappingRequest) {
|
||||
return putMapping(HttpHeaders.EMPTY, putMappingRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -909,7 +1031,7 @@ public interface ReactiveElasticsearchClient {
|
||||
* @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);
|
||||
Mono<Boolean> putMapping(HttpHeaders headers, PutMappingRequest putMappingRequest);
|
||||
|
||||
/**
|
||||
* Execute the given {@link FlushRequest} against the {@literal indices} API.
|
||||
@@ -951,5 +1073,300 @@ public interface ReactiveElasticsearchClient {
|
||||
* API on elastic.co</a>
|
||||
*/
|
||||
Mono<Void> flushIndex(HttpHeaders headers, FlushRequest flushRequest);
|
||||
|
||||
/**
|
||||
* Execute the given {@link GetSettingsRequest} 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-get-settings.html"> Indices
|
||||
* Flush API on elastic.co</a>
|
||||
* @since 4.1
|
||||
*/
|
||||
default Mono<GetSettingsResponse> getSettings(Consumer<GetSettingsRequest> consumer) {
|
||||
|
||||
GetSettingsRequest request = new GetSettingsRequest();
|
||||
consumer.accept(request);
|
||||
return getSettings(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the given {@link GetSettingsRequest} against the {@literal indices} API.
|
||||
*
|
||||
* @param getSettingsRequest 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-get-settings.html"> Indices
|
||||
* Flush API on elastic.co</a>
|
||||
* @since 4.1
|
||||
*/
|
||||
default Mono<GetSettingsResponse> getSettings(GetSettingsRequest getSettingsRequest) {
|
||||
return getSettings(HttpHeaders.EMPTY, getSettingsRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the given {@link GetSettingsRequest} against the {@literal indices} API.
|
||||
*
|
||||
* @param headers Use {@link HttpHeaders} to provide eg. authentication data. Must not be {@literal null}.
|
||||
* @param getSettingsRequest 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-get-settings.html"> Indices
|
||||
* Flush API on elastic.co</a>
|
||||
* @since 4.1
|
||||
*/
|
||||
Mono<GetSettingsResponse> getSettings(HttpHeaders headers, GetSettingsRequest getSettingsRequest);
|
||||
|
||||
/**
|
||||
* Execute the given {@link GetMappingsRequest} 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-get-mapping.html"> Indices
|
||||
* Flush API on elastic.co</a>
|
||||
* @since 4.1
|
||||
*/
|
||||
default Mono<GetMappingsResponse> getMapping(Consumer<GetMappingsRequest> consumer) {
|
||||
|
||||
GetMappingsRequest request = new GetMappingsRequest();
|
||||
consumer.accept(request);
|
||||
return getMapping(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the given {@link GetMappingsRequest} against the {@literal indices} API.
|
||||
*
|
||||
* @param getMappingsRequest 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-get-mapping.html"> Indices
|
||||
* Flush API on elastic.co</a>
|
||||
* @since 4.1
|
||||
*/
|
||||
default Mono<GetMappingsResponse> getMapping(GetMappingsRequest getMappingsRequest) {
|
||||
return getMapping(HttpHeaders.EMPTY, getMappingsRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the given {@link GetMappingsRequest} against the {@literal indices} API.
|
||||
*
|
||||
* @param headers Use {@link HttpHeaders} to provide eg. authentication data. Must not be {@literal null}.
|
||||
* @param getMappingsRequest 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-get-mapping.html"> Indices
|
||||
* Flush API on elastic.co</a>
|
||||
* @since 4.1
|
||||
*/
|
||||
Mono<GetMappingsResponse> getMapping(HttpHeaders headers, GetMappingsRequest getMappingsRequest);
|
||||
|
||||
/**
|
||||
* Execute the given {@link IndicesAliasesRequest} against the {@literal indices} API.
|
||||
*
|
||||
* @param consumer never {@literal null}.
|
||||
* @return a {@link Mono} signalling operation completion.
|
||||
* @since 4.1
|
||||
*/
|
||||
default Mono<Boolean> updateAliases(Consumer<IndicesAliasesRequest> consumer) {
|
||||
IndicesAliasesRequest indicesAliasesRequest = new IndicesAliasesRequest();
|
||||
consumer.accept(indicesAliasesRequest);
|
||||
return updateAliases(indicesAliasesRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the given {@link IndicesAliasesRequest} against the {@literal indices} API.
|
||||
*
|
||||
* @param indicesAliasesRequest must not be {@literal null}
|
||||
* @return a {@link Mono} signalling operation completion.
|
||||
* @since 4.1
|
||||
*/
|
||||
default Mono<Boolean> updateAliases(IndicesAliasesRequest indicesAliasesRequest) {
|
||||
return updateAliases(HttpHeaders.EMPTY, indicesAliasesRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the given {@link IndicesAliasesRequest} against the {@literal indices} API.
|
||||
*
|
||||
* @param headers Use {@link HttpHeaders} to provide eg. authentication data. Must not be {@literal null}.
|
||||
* @param indicesAliasesRequest must not be {@literal null}
|
||||
* @return a {@link Mono} signalling operation completion.
|
||||
* @since 4.1
|
||||
*/
|
||||
Mono<Boolean> updateAliases(HttpHeaders headers, IndicesAliasesRequest indicesAliasesRequest);
|
||||
|
||||
/**
|
||||
* Execute the given {@link GetAliasesRequest} against the {@literal indices} API.
|
||||
*
|
||||
* @param consumer never {@literal null}.
|
||||
* @return a {@link Mono} signalling operation completion.
|
||||
* @since 4.1
|
||||
*/
|
||||
default Mono<GetAliasesResponse> getAliases(Consumer<GetAliasesRequest> consumer) {
|
||||
GetAliasesRequest getAliasesRequest = new GetAliasesRequest();
|
||||
consumer.accept(getAliasesRequest);
|
||||
return getAliases(getAliasesRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the given {@link GetAliasesRequest} against the {@literal indices} API.
|
||||
*
|
||||
* @param getAliasesRequest must not be {@literal null}
|
||||
* @return a {@link Mono} signalling operation completion.
|
||||
* @since 4.1
|
||||
*/
|
||||
default Mono<GetAliasesResponse> getAliases(GetAliasesRequest getAliasesRequest) {
|
||||
return getAliases(HttpHeaders.EMPTY, getAliasesRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the given {@link GetAliasesRequest} against the {@literal indices} API.
|
||||
*
|
||||
* @param headers Use {@link HttpHeaders} to provide eg. authentication data. Must not be {@literal null}.
|
||||
* @param getAliasesRequest must not be {@literal null}
|
||||
* @return a {@link Mono} signalling operation completion.
|
||||
* @since 4.1
|
||||
*/
|
||||
Mono<GetAliasesResponse> getAliases(HttpHeaders headers, GetAliasesRequest getAliasesRequest);
|
||||
|
||||
/**
|
||||
* Execute the given {@link PutIndexTemplateRequest} against the {@literal indices} API.
|
||||
*
|
||||
* @param consumer never {@literal null}.
|
||||
* @return a {@link Mono} signalling operation completion.
|
||||
* @since 4.1
|
||||
*/
|
||||
default Mono<Boolean> putTemplate(Consumer<PutIndexTemplateRequest> consumer, String templateName) {
|
||||
PutIndexTemplateRequest putIndexTemplateRequest = new PutIndexTemplateRequest(templateName);
|
||||
consumer.accept(putIndexTemplateRequest);
|
||||
return putTemplate(putIndexTemplateRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the given {@link PutIndexTemplateRequest} against the {@literal indices} API.
|
||||
*
|
||||
* @param putIndexTemplateRequest must not be {@literal null}
|
||||
* @return a {@link Mono} signalling operation completion.
|
||||
* @since 4.1
|
||||
*/
|
||||
default Mono<Boolean> putTemplate(PutIndexTemplateRequest putIndexTemplateRequest) {
|
||||
return putTemplate(HttpHeaders.EMPTY, putIndexTemplateRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the given {@link PutIndexTemplateRequest} against the {@literal indices} API.
|
||||
*
|
||||
* @param headers Use {@link HttpHeaders} to provide eg. authentication data. Must not be {@literal null}.
|
||||
* @param putIndexTemplateRequest must not be {@literal null}
|
||||
* @return a {@link Mono} signalling operation completion.
|
||||
* @since 4.1
|
||||
*/
|
||||
Mono<Boolean> putTemplate(HttpHeaders headers, PutIndexTemplateRequest putIndexTemplateRequest);
|
||||
|
||||
/**
|
||||
* Execute the given {@link GetIndexTemplatesRequest} against the {@literal indices} API.
|
||||
*
|
||||
* @param consumer never {@literal null}.
|
||||
* @return a {@link Mono} with the GetIndexTemplatesResponse.
|
||||
* @since 4.1
|
||||
*/
|
||||
default Mono<GetIndexTemplatesResponse> getTemplate(Consumer<GetIndexTemplatesRequest> consumer) {
|
||||
|
||||
GetIndexTemplatesRequest getIndexTemplatesRequest = new GetIndexTemplatesRequest();
|
||||
consumer.accept(getIndexTemplatesRequest);
|
||||
return getTemplate(getIndexTemplatesRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the given {@link GetIndexTemplatesRequest} against the {@literal indices} API.
|
||||
*
|
||||
* @param getIndexTemplatesRequest must not be {@literal null}
|
||||
* @return a {@link Mono} with the GetIndexTemplatesResponse.
|
||||
* @since 4.1
|
||||
*/
|
||||
default Mono<GetIndexTemplatesResponse> getTemplate(GetIndexTemplatesRequest getIndexTemplatesRequest) {
|
||||
return getTemplate(HttpHeaders.EMPTY, getIndexTemplatesRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the given {@link GetIndexTemplatesRequest} against the {@literal indices} API.
|
||||
*
|
||||
* @param headers Use {@link HttpHeaders} to provide eg. authentication data. Must not be {@literal null}.
|
||||
* @param getIndexTemplatesRequest must not be {@literal null}
|
||||
* @return a {@link Mono} with the GetIndexTemplatesResponse.
|
||||
* @since 4.1
|
||||
*/
|
||||
Mono<GetIndexTemplatesResponse> getTemplate(HttpHeaders headers, GetIndexTemplatesRequest getIndexTemplatesRequest);
|
||||
|
||||
/**
|
||||
* Execute the given {@link IndexTemplatesExistRequest} against the {@literal indices} API.
|
||||
*
|
||||
* @param consumer never {@literal null}.
|
||||
* @return the {@link Mono} emitting {@literal true} if the template exists, {@literal false} otherwise.
|
||||
* @since 4.1
|
||||
*/
|
||||
default Mono<Boolean> existsTemplate(Consumer<IndexTemplatesExistRequest> consumer) {
|
||||
|
||||
IndexTemplatesExistRequest indexTemplatesExistRequest = new IndexTemplatesExistRequest();
|
||||
consumer.accept(indexTemplatesExistRequest);
|
||||
return existsTemplate(indexTemplatesExistRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the given {@link IndexTemplatesExistRequest} against the {@literal indices} API.
|
||||
*
|
||||
* @param indexTemplatesExistRequest must not be {@literal null}
|
||||
* @return the {@link Mono} emitting {@literal true} if the template exists, {@literal false} otherwise.
|
||||
* @since 4.1
|
||||
*/
|
||||
default Mono<Boolean> existsTemplate(IndexTemplatesExistRequest indexTemplatesExistRequest) {
|
||||
return existsTemplate(HttpHeaders.EMPTY, indexTemplatesExistRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the given {@link IndexTemplatesExistRequest} against the {@literal indices} API.
|
||||
*
|
||||
* @param headers Use {@link HttpHeaders} to provide eg. authentication data. Must not be {@literal null}.
|
||||
* @param indexTemplatesExistRequest must not be {@literal null}
|
||||
* @return the {@link Mono} emitting {@literal true} if the template exists, {@literal false} otherwise.
|
||||
* @since 4.1
|
||||
*/
|
||||
Mono<Boolean> existsTemplate(HttpHeaders headers, IndexTemplatesExistRequest indexTemplatesExistRequest);
|
||||
|
||||
/**
|
||||
* Execute the given {@link DeleteIndexTemplateRequest} against the {@literal indices} API.
|
||||
*
|
||||
* @param consumer never {@literal null}.
|
||||
* @return the {@link Mono} emitting {@literal true} if the template exists, {@literal false} otherwise.
|
||||
* @since 4.1
|
||||
*/
|
||||
default Mono<Boolean> deleteTemplate(Consumer<DeleteIndexTemplateRequest> consumer) {
|
||||
|
||||
DeleteIndexTemplateRequest deleteIndexTemplateRequest = new DeleteIndexTemplateRequest();
|
||||
consumer.accept(deleteIndexTemplateRequest);
|
||||
return deleteTemplate(deleteIndexTemplateRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the given {@link DeleteIndexTemplateRequest} against the {@literal indices} API.
|
||||
*
|
||||
* @param deleteIndexTemplateRequest must not be {@literal null}
|
||||
* @return the {@link Mono} emitting {@literal true} if the template exists, {@literal false} otherwise.
|
||||
* @since 4.1
|
||||
*/
|
||||
default Mono<Boolean> deleteTemplate(DeleteIndexTemplateRequest deleteIndexTemplateRequest) {
|
||||
return deleteTemplate(HttpHeaders.EMPTY, deleteIndexTemplateRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the given {@link DeleteIndexTemplateRequest} against the {@literal indices} API.
|
||||
*
|
||||
* @param headers Use {@link HttpHeaders} to provide eg. authentication data. Must not be {@literal null}.
|
||||
* @param deleteIndexTemplateRequest must not be {@literal null}
|
||||
* @return the {@link Mono} emitting {@literal true} if the template exists, {@literal false} otherwise.
|
||||
* @since 4.1
|
||||
*/
|
||||
Mono<Boolean> deleteTemplate(HttpHeaders headers, DeleteIndexTemplateRequest deleteIndexTemplateRequest);
|
||||
}
|
||||
}
|
||||
|
||||
+65
-2
@@ -3,14 +3,19 @@ package org.springframework.data.elasticsearch.client.reactive;
|
||||
import java.io.IOException;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequest;
|
||||
import org.elasticsearch.action.admin.indices.alias.get.GetAliasesRequest;
|
||||
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.get.GetMappingsRequest;
|
||||
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.settings.get.GetSettingsRequest;
|
||||
import org.elasticsearch.action.admin.indices.template.delete.DeleteIndexTemplateRequest;
|
||||
import org.elasticsearch.action.bulk.BulkRequest;
|
||||
import org.elasticsearch.action.delete.DeleteRequest;
|
||||
import org.elasticsearch.action.get.GetRequest;
|
||||
@@ -23,8 +28,11 @@ import org.elasticsearch.action.search.SearchScrollRequest;
|
||||
import org.elasticsearch.action.update.UpdateRequest;
|
||||
import org.elasticsearch.client.Request;
|
||||
import org.elasticsearch.client.core.CountRequest;
|
||||
import org.elasticsearch.client.indices.GetIndexTemplatesRequest;
|
||||
import org.elasticsearch.client.indices.IndexTemplatesExistRequest;
|
||||
import org.elasticsearch.client.indices.PutIndexTemplateRequest;
|
||||
import org.elasticsearch.index.reindex.DeleteByQueryRequest;
|
||||
import org.springframework.data.elasticsearch.ElasticsearchException;
|
||||
import org.springframework.data.elasticsearch.UncategorizedElasticsearchException;
|
||||
import org.springframework.data.elasticsearch.client.util.RequestConverters;
|
||||
|
||||
/**
|
||||
@@ -88,7 +96,7 @@ public interface RequestCreator {
|
||||
try {
|
||||
return RequestConverters.bulk(request);
|
||||
} catch (IOException e) {
|
||||
throw new ElasticsearchException("Could not parse request", e);
|
||||
throw new UncategorizedElasticsearchException("Could not parse request", e);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -131,4 +139,59 @@ public interface RequestCreator {
|
||||
return RequestConverters::count;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 4.1
|
||||
*/
|
||||
default Function<GetSettingsRequest, Request> getSettings() {
|
||||
return RequestConverters::getSettings;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 4.1
|
||||
*/
|
||||
default Function<GetMappingsRequest, Request> getMapping() {
|
||||
return RequestConverters::getMapping;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 4.1
|
||||
*/
|
||||
default Function<IndicesAliasesRequest, Request> updateAlias() {
|
||||
return RequestConverters::updateAliases;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 4.1
|
||||
*/
|
||||
default Function<GetAliasesRequest, Request> getAlias() {
|
||||
return RequestConverters::getAlias;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 4.1
|
||||
*/
|
||||
default Function<PutIndexTemplateRequest, Request> putTemplate() {
|
||||
return RequestConverters::putTemplate;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 4.1
|
||||
*/
|
||||
default Function<GetIndexTemplatesRequest, Request> getTemplates() {
|
||||
return RequestConverters::getTemplates;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 4.1
|
||||
*/
|
||||
default Function<IndexTemplatesExistRequest, Request> templatesExist() {
|
||||
return RequestConverters::templatesExist;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 4.1
|
||||
*/
|
||||
default Function<DeleteIndexTemplateRequest, Request> deleteTemplate() {
|
||||
return RequestConverters::deleteTemplate;
|
||||
}
|
||||
}
|
||||
|
||||
+9
-7
@@ -15,7 +15,6 @@
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.client.reactive;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
@@ -25,6 +24,7 @@ import java.util.function.Supplier;
|
||||
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.http.HttpHeaders;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
/**
|
||||
@@ -66,14 +66,14 @@ class SingleNodeHostProvider implements HostProvider {
|
||||
} else {
|
||||
state = ElasticsearchHost.online(endpoint);
|
||||
}
|
||||
return Mono.just(state);
|
||||
return it.releaseBody().thenReturn(state);
|
||||
}).onErrorResume(throwable -> {
|
||||
|
||||
state = ElasticsearchHost.offline(endpoint);
|
||||
clientProvider.getErrorListener().accept(throwable);
|
||||
return Mono.just(state);
|
||||
}) //
|
||||
.flatMap(it -> Mono.just(new ClusterInformation(Collections.singleton(it))));
|
||||
.map(it -> new ClusterInformation(Collections.singleton(it)));
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -96,14 +96,16 @@ class SingleNodeHostProvider implements HostProvider {
|
||||
return Mono.just(endpoint);
|
||||
}
|
||||
|
||||
return clusterInfo().flatMap(it -> {
|
||||
return clusterInfo().handle((information, sink) -> {
|
||||
|
||||
ElasticsearchHost host = it.getNodes().iterator().next();
|
||||
ElasticsearchHost host = information.getNodes().iterator().next();
|
||||
if (host.isOnline()) {
|
||||
return Mono.just(host.getEndpoint());
|
||||
|
||||
sink.next(host.getEndpoint());
|
||||
return;
|
||||
}
|
||||
|
||||
return Mono.error(() -> new NoReachableHostException(Collections.singleton(host)));
|
||||
sink.error(new NoReachableHostException(Collections.singleton(host)));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -60,10 +60,6 @@ import org.elasticsearch.search.aggregations.bucket.range.ParsedRange;
|
||||
import org.elasticsearch.search.aggregations.bucket.range.RangeAggregationBuilder;
|
||||
import org.elasticsearch.search.aggregations.bucket.sampler.InternalSampler;
|
||||
import org.elasticsearch.search.aggregations.bucket.sampler.ParsedSampler;
|
||||
import org.elasticsearch.search.aggregations.bucket.significant.ParsedSignificantLongTerms;
|
||||
import org.elasticsearch.search.aggregations.bucket.significant.ParsedSignificantStringTerms;
|
||||
import org.elasticsearch.search.aggregations.bucket.significant.SignificantLongTerms;
|
||||
import org.elasticsearch.search.aggregations.bucket.significant.SignificantStringTerms;
|
||||
import org.elasticsearch.search.aggregations.bucket.terms.DoubleTerms;
|
||||
import org.elasticsearch.search.aggregations.bucket.terms.LongTerms;
|
||||
import org.elasticsearch.search.aggregations.bucket.terms.ParsedDoubleTerms;
|
||||
@@ -146,8 +142,6 @@ public class NamedXContents {
|
||||
map.put(GeoDistanceAggregationBuilder.NAME, (p, c) -> ParsedGeoDistance.fromXContent(p, (String) c));
|
||||
map.put(FiltersAggregationBuilder.NAME, (p, c) -> ParsedFilters.fromXContent(p, (String) c));
|
||||
map.put(AdjacencyMatrixAggregationBuilder.NAME, (p, c) -> ParsedAdjacencyMatrix.fromXContent(p, (String) c));
|
||||
map.put(SignificantLongTerms.NAME, (p, c) -> ParsedSignificantLongTerms.fromXContent(p, (String) c));
|
||||
map.put(SignificantStringTerms.NAME, (p, c) -> ParsedSignificantStringTerms.fromXContent(p, (String) c));
|
||||
map.put(ScriptedMetricAggregationBuilder.NAME, (p, c) -> ParsedScriptedMetric.fromXContent(p, (String) c));
|
||||
map.put(IpRangeAggregationBuilder.NAME, (p, c) -> ParsedBinaryRange.fromXContent(p, (String) c));
|
||||
map.put(TopHitsAggregationBuilder.NAME, (p, c) -> ParsedTopHits.fromXContent(p, (String) c));
|
||||
|
||||
+122
-3
@@ -25,6 +25,11 @@ import java.util.Locale;
|
||||
import java.util.StringJoiner;
|
||||
|
||||
import org.apache.http.HttpEntity;
|
||||
import org.apache.http.client.methods.HttpDelete;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.client.methods.HttpHead;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.client.methods.HttpPut;
|
||||
import org.apache.http.entity.ByteArrayEntity;
|
||||
import org.apache.http.entity.ContentType;
|
||||
import org.apache.lucene.util.BytesRef;
|
||||
@@ -33,14 +38,19 @@ import org.elasticsearch.action.admin.cluster.health.ClusterHealthRequest;
|
||||
import org.elasticsearch.action.admin.cluster.storedscripts.DeleteStoredScriptRequest;
|
||||
import org.elasticsearch.action.admin.cluster.storedscripts.GetStoredScriptRequest;
|
||||
import org.elasticsearch.action.admin.cluster.storedscripts.PutStoredScriptRequest;
|
||||
import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequest;
|
||||
import org.elasticsearch.action.admin.indices.alias.get.GetAliasesRequest;
|
||||
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.get.GetMappingsRequest;
|
||||
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.settings.get.GetSettingsRequest;
|
||||
import org.elasticsearch.action.admin.indices.template.delete.DeleteIndexTemplateRequest;
|
||||
import org.elasticsearch.action.bulk.BulkRequest;
|
||||
import org.elasticsearch.action.delete.DeleteRequest;
|
||||
import org.elasticsearch.action.explain.ExplainRequest;
|
||||
@@ -61,6 +71,9 @@ import org.elasticsearch.client.Requests;
|
||||
import org.elasticsearch.client.RethrottleRequest;
|
||||
import org.elasticsearch.client.core.CountRequest;
|
||||
import org.elasticsearch.client.indices.AnalyzeRequest;
|
||||
import org.elasticsearch.client.indices.GetIndexTemplatesRequest;
|
||||
import org.elasticsearch.client.indices.IndexTemplatesExistRequest;
|
||||
import org.elasticsearch.client.indices.PutIndexTemplateRequest;
|
||||
import org.elasticsearch.cluster.health.ClusterHealthStatus;
|
||||
import org.elasticsearch.common.Priority;
|
||||
import org.elasticsearch.common.Strings;
|
||||
@@ -737,7 +750,9 @@ public class RequestConverters {
|
||||
public static Request indexRefresh(RefreshRequest refreshRequest) {
|
||||
|
||||
String[] indices = refreshRequest.indices() == null ? Strings.EMPTY_ARRAY : refreshRequest.indices();
|
||||
Request request = new Request(HttpMethod.POST.name(), RequestConverters.endpoint(indices, "_refresh"));
|
||||
// using a GET here as reactor-netty set the transfer-encoding to chunked on POST requests which blocks on
|
||||
// Elasticsearch when no body is sent.
|
||||
Request request = new Request(HttpMethod.GET.name(), RequestConverters.endpoint(indices, "_refresh"));
|
||||
|
||||
Params parameters = new Params(request);
|
||||
parameters.withIndicesOptions(refreshRequest.indicesOptions());
|
||||
@@ -751,12 +766,12 @@ public class RequestConverters {
|
||||
}
|
||||
|
||||
Request request = new Request(HttpMethod.PUT.name(),
|
||||
RequestConverters.endpoint(putMappingRequest.indices(), "_mapping", putMappingRequest.type()));
|
||||
RequestConverters.endpoint(putMappingRequest.indices(), "_mapping"));
|
||||
|
||||
RequestConverters.Params parameters = new RequestConverters.Params(request) //
|
||||
.withTimeout(putMappingRequest.timeout()) //
|
||||
.withMasterTimeout(putMappingRequest.masterNodeTimeout()) //
|
||||
.withIncludeTypeName(true);
|
||||
.withIncludeTypeName(false);
|
||||
request.setEntity(RequestConverters.createEntity(putMappingRequest, RequestConverters.REQUEST_BODY_CONTENT_TYPE));
|
||||
return request;
|
||||
}
|
||||
@@ -772,6 +787,110 @@ public class RequestConverters {
|
||||
return request;
|
||||
}
|
||||
|
||||
public static Request getMapping(GetMappingsRequest getMappingsRequest) {
|
||||
String[] indices = getMappingsRequest.indices() == null ? Strings.EMPTY_ARRAY : getMappingsRequest.indices();
|
||||
String[] types = getMappingsRequest.types() == null ? Strings.EMPTY_ARRAY : getMappingsRequest.types();
|
||||
|
||||
Request request = new Request(HttpMethod.GET.name(), RequestConverters.endpoint(indices, "_mapping", types));
|
||||
|
||||
RequestConverters.Params parameters = new RequestConverters.Params(request);
|
||||
parameters.withMasterTimeout(getMappingsRequest.masterNodeTimeout());
|
||||
parameters.withIndicesOptions(getMappingsRequest.indicesOptions());
|
||||
parameters.withLocal(getMappingsRequest.local());
|
||||
parameters.withIncludeTypeName(false);
|
||||
return request;
|
||||
}
|
||||
|
||||
public static Request getSettings(GetSettingsRequest getSettingsRequest) {
|
||||
String[] indices = getSettingsRequest.indices() == null ? Strings.EMPTY_ARRAY : getSettingsRequest.indices();
|
||||
String[] names = getSettingsRequest.names() == null ? Strings.EMPTY_ARRAY : getSettingsRequest.names();
|
||||
|
||||
Request request = new Request(HttpMethod.GET.name(), RequestConverters.endpoint(indices, "_settings", names));
|
||||
|
||||
RequestConverters.Params parameters = new RequestConverters.Params(request);
|
||||
parameters.withIndicesOptions(getSettingsRequest.indicesOptions());
|
||||
parameters.withLocal(getSettingsRequest.local());
|
||||
parameters.withIncludeDefaults(getSettingsRequest.includeDefaults());
|
||||
parameters.withMasterTimeout(getSettingsRequest.masterNodeTimeout());
|
||||
return request;
|
||||
}
|
||||
|
||||
public static Request updateAliases(IndicesAliasesRequest indicesAliasesRequest) {
|
||||
Request request = new Request(HttpPost.METHOD_NAME, "/_aliases");
|
||||
|
||||
RequestConverters.Params parameters = new RequestConverters.Params(request);
|
||||
parameters.withTimeout(indicesAliasesRequest.timeout());
|
||||
parameters.withMasterTimeout(indicesAliasesRequest.masterNodeTimeout());
|
||||
request
|
||||
.setEntity(RequestConverters.createEntity(indicesAliasesRequest, RequestConverters.REQUEST_BODY_CONTENT_TYPE));
|
||||
return request;
|
||||
}
|
||||
|
||||
public static Request getAlias(GetAliasesRequest getAliasesRequest) {
|
||||
|
||||
String[] indices = getAliasesRequest.indices() == null ? Strings.EMPTY_ARRAY : getAliasesRequest.indices();
|
||||
String[] aliases = getAliasesRequest.aliases() == null ? Strings.EMPTY_ARRAY : getAliasesRequest.aliases();
|
||||
String endpoint = RequestConverters.endpoint(indices, "_alias", aliases);
|
||||
|
||||
Request request = new Request(HttpGet.METHOD_NAME, endpoint);
|
||||
|
||||
RequestConverters.Params params = new RequestConverters.Params(request);
|
||||
params.withIndicesOptions(getAliasesRequest.indicesOptions());
|
||||
params.withLocal(getAliasesRequest.local());
|
||||
return request;
|
||||
}
|
||||
|
||||
public static Request putTemplate(PutIndexTemplateRequest putIndexTemplateRequest) {
|
||||
String endpoint = (new RequestConverters.EndpointBuilder()) //
|
||||
.addPathPartAsIs("_template") //
|
||||
.addPathPart(putIndexTemplateRequest.name()) //
|
||||
.build(); //
|
||||
|
||||
Request request = new Request(HttpPut.METHOD_NAME, endpoint);
|
||||
RequestConverters.Params params = new RequestConverters.Params(request);
|
||||
params.withMasterTimeout(putIndexTemplateRequest.masterNodeTimeout());
|
||||
if (putIndexTemplateRequest.create()) {
|
||||
params.putParam("create", Boolean.TRUE.toString());
|
||||
}
|
||||
|
||||
if (Strings.hasText(putIndexTemplateRequest.cause())) {
|
||||
params.putParam("cause", putIndexTemplateRequest.cause());
|
||||
}
|
||||
|
||||
request.setEntity(
|
||||
RequestConverters.createEntity(putIndexTemplateRequest, RequestConverters.REQUEST_BODY_CONTENT_TYPE));
|
||||
return request;
|
||||
}
|
||||
|
||||
public static Request getTemplates(GetIndexTemplatesRequest getIndexTemplatesRequest) {
|
||||
final String endpoint = new RequestConverters.EndpointBuilder().addPathPartAsIs("_template")
|
||||
.addCommaSeparatedPathParts(getIndexTemplatesRequest.names()).build();
|
||||
final Request request = new Request(HttpGet.METHOD_NAME, endpoint);
|
||||
RequestConverters.Params params = new RequestConverters.Params(request);
|
||||
params.withLocal(getIndexTemplatesRequest.isLocal());
|
||||
params.withMasterTimeout(getIndexTemplatesRequest.getMasterNodeTimeout());
|
||||
return request;
|
||||
}
|
||||
|
||||
public static Request templatesExist(IndexTemplatesExistRequest indexTemplatesExistRequest) {
|
||||
final String endpoint = new RequestConverters.EndpointBuilder().addPathPartAsIs("_template")
|
||||
.addCommaSeparatedPathParts(indexTemplatesExistRequest.names()).build();
|
||||
final Request request = new Request(HttpHead.METHOD_NAME, endpoint);
|
||||
final RequestConverters.Params params = new RequestConverters.Params(request);
|
||||
params.withLocal(indexTemplatesExistRequest.isLocal());
|
||||
params.withMasterTimeout(indexTemplatesExistRequest.getMasterNodeTimeout());
|
||||
return request;
|
||||
}
|
||||
|
||||
public static Request deleteTemplate(DeleteIndexTemplateRequest deleteIndexTemplateRequest) {
|
||||
String name = deleteIndexTemplateRequest.name();
|
||||
String endpoint = new RequestConverters.EndpointBuilder().addPathPartAsIs("_template").addPathPart(name).build();
|
||||
Request request = new Request(HttpDelete.METHOD_NAME, endpoint);
|
||||
RequestConverters.Params params = new RequestConverters.Params(request);
|
||||
params.withMasterTimeout(deleteIndexTemplateRequest.masterNodeTimeout());
|
||||
return request;
|
||||
}
|
||||
|
||||
static HttpEntity createEntity(ToXContent toXContent, XContentType xContentType) {
|
||||
|
||||
try {
|
||||
|
||||
@@ -58,7 +58,7 @@ public class ScrollState {
|
||||
}
|
||||
}
|
||||
|
||||
public void updateScrollId(String scrollId) {
|
||||
public void updateScrollId(@Nullable String scrollId) {
|
||||
|
||||
if (StringUtils.hasText(scrollId)) {
|
||||
|
||||
|
||||
+14
-9
@@ -31,20 +31,25 @@ public abstract class AbstractElasticsearchConfiguration extends ElasticsearchCo
|
||||
|
||||
/**
|
||||
* 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}.
|
||||
*/
|
||||
@Bean
|
||||
public abstract RestHighLevelClient elasticsearchClient();
|
||||
|
||||
/**
|
||||
* Creates {@link ElasticsearchOperations}.
|
||||
*
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
/**
|
||||
* Creates {@link ElasticsearchOperations}. <br/>
|
||||
* NOTE: in version 4.1.2 the second parameter was added, previously this implementation called
|
||||
* {@link #elasticsearchClient()} directly. This is not possible anymore, as the base configuration classes don not
|
||||
* use proxied bean methods anymore.
|
||||
*
|
||||
* @param elasticsearchConverter the {@link ElasticsearchConverter} to use*
|
||||
* @param elasticsearchClient the {@link RestHighLevelClient} to use
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
@Bean(name = { "elasticsearchOperations", "elasticsearchTemplate" })
|
||||
public ElasticsearchOperations elasticsearchOperations(ElasticsearchConverter elasticsearchConverter) {
|
||||
return new ElasticsearchRestTemplate(elasticsearchClient(), elasticsearchConverter);
|
||||
public ElasticsearchOperations elasticsearchOperations(ElasticsearchConverter elasticsearchConverter,
|
||||
RestHighLevelClient elasticsearchClient) {
|
||||
return new ElasticsearchRestTemplate(elasticsearchClient, elasticsearchConverter);
|
||||
}
|
||||
}
|
||||
|
||||
+4
-6
@@ -18,7 +18,6 @@ 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;
|
||||
@@ -31,16 +30,14 @@ import org.springframework.lang.Nullable;
|
||||
* @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}.
|
||||
*/
|
||||
@Bean
|
||||
public abstract ReactiveElasticsearchClient reactiveElasticsearchClient();
|
||||
|
||||
/**
|
||||
@@ -49,9 +46,10 @@ public abstract class AbstractReactiveElasticsearchConfiguration extends Elastic
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
@Bean
|
||||
public ReactiveElasticsearchOperations reactiveElasticsearchTemplate(ElasticsearchConverter elasticsearchConverter) {
|
||||
public ReactiveElasticsearchOperations reactiveElasticsearchTemplate(ElasticsearchConverter elasticsearchConverter,
|
||||
ReactiveElasticsearchClient reactiveElasticsearchClient) {
|
||||
|
||||
ReactiveElasticsearchTemplate template = new ReactiveElasticsearchTemplate(reactiveElasticsearchClient(),
|
||||
ReactiveElasticsearchTemplate template = new ReactiveElasticsearchTemplate(reactiveElasticsearchClient,
|
||||
elasticsearchConverter);
|
||||
template.setIndicesOptions(indicesOptions());
|
||||
template.setRefreshPolicy(refreshPolicy());
|
||||
|
||||
+3
-108
@@ -17,24 +17,16 @@ package org.springframework.data.elasticsearch.config;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.support.AbstractBeanDefinition;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
import org.springframework.data.auditing.IsNewAwareAuditingHandler;
|
||||
import org.springframework.data.auditing.config.AuditingBeanDefinitionRegistrarSupport;
|
||||
import org.springframework.data.auditing.config.AuditingConfiguration;
|
||||
import org.springframework.data.config.ParsingUtils;
|
||||
import org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter;
|
||||
import org.springframework.data.elasticsearch.core.event.AuditingEntityCallback;
|
||||
import org.springframework.data.elasticsearch.core.event.ReactiveAuditingEntityCallback;
|
||||
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.data.repository.util.ReactiveWrappers;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
@@ -45,41 +37,16 @@ import org.springframework.util.Assert;
|
||||
*/
|
||||
class ElasticsearchAuditingRegistrar extends AuditingBeanDefinitionRegistrarSupport {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.auditing.config.AuditingBeanDefinitionRegistrarSupport#getAnnotation()
|
||||
*/
|
||||
@Override
|
||||
protected Class<? extends Annotation> getAnnotation() {
|
||||
return EnableElasticsearchAuditing.class;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.auditing.config.AuditingBeanDefinitionRegistrarSupport#getAuditingHandlerBeanName()
|
||||
*/
|
||||
@Override
|
||||
protected String getAuditingHandlerBeanName() {
|
||||
return "elasticsearchAuditingHandler";
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.auditing.config.AuditingBeanDefinitionRegistrarSupport#registerBeanDefinitions(org.springframework.core.type.AnnotationMetadata, org.springframework.beans.factory.support.BeanDefinitionRegistry)
|
||||
*/
|
||||
@Override
|
||||
public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry registry) {
|
||||
|
||||
Assert.notNull(annotationMetadata, "AnnotationMetadata must not be null!");
|
||||
Assert.notNull(registry, "BeanDefinitionRegistry must not be null!");
|
||||
|
||||
super.registerBeanDefinitions(annotationMetadata, registry);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.auditing.config.AuditingBeanDefinitionRegistrarSupport#getAuditHandlerBeanDefinitionBuilder(org.springframework.data.auditing.config.AuditingConfiguration)
|
||||
*/
|
||||
@Override
|
||||
protected BeanDefinitionBuilder getAuditHandlerBeanDefinitionBuilder(AuditingConfiguration configuration) {
|
||||
|
||||
@@ -87,18 +54,13 @@ class ElasticsearchAuditingRegistrar extends AuditingBeanDefinitionRegistrarSupp
|
||||
|
||||
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(IsNewAwareAuditingHandler.class);
|
||||
|
||||
BeanDefinitionBuilder definition = BeanDefinitionBuilder
|
||||
.genericBeanDefinition(ElasticsearchMappingContextLookup.class);
|
||||
BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(PersistentEntitiesFactoryBean.class);
|
||||
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR);
|
||||
|
||||
builder.addConstructorArgValue(definition.getBeanDefinition());
|
||||
return configureDefaultAuditHandlerAttributes(configuration, builder);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.auditing.config.AuditingBeanDefinitionRegistrarSupport#registerAuditListener(org.springframework.beans.factory.config.BeanDefinition, org.springframework.beans.factory.support.BeanDefinitionRegistry)
|
||||
*/
|
||||
@Override
|
||||
protected void registerAuditListenerBeanDefinition(BeanDefinition auditingHandlerDefinition,
|
||||
BeanDefinitionRegistry registry) {
|
||||
@@ -106,76 +68,9 @@ class ElasticsearchAuditingRegistrar extends AuditingBeanDefinitionRegistrarSupp
|
||||
Assert.notNull(auditingHandlerDefinition, "BeanDefinition must not be null!");
|
||||
Assert.notNull(registry, "BeanDefinitionRegistry must not be null!");
|
||||
|
||||
BeanDefinitionBuilder listenerBeanDefinitionBuilder = BeanDefinitionBuilder
|
||||
.rootBeanDefinition(AuditingEntityCallback.class);
|
||||
listenerBeanDefinitionBuilder
|
||||
.addConstructorArgValue(ParsingUtils.getObjectFactoryBeanDefinition(getAuditingHandlerBeanName(), registry));
|
||||
|
||||
registerInfrastructureBeanWithId(listenerBeanDefinitionBuilder.getBeanDefinition(),
|
||||
AuditingEntityCallback.class.getName(), registry);
|
||||
|
||||
if (ReactiveWrappers.isAvailable(ReactiveWrappers.ReactiveLibrary.PROJECT_REACTOR)) {
|
||||
registerReactiveAuditingEntityCallback(registry, auditingHandlerDefinition.getSource());
|
||||
}
|
||||
}
|
||||
|
||||
private void registerReactiveAuditingEntityCallback(BeanDefinitionRegistry registry, Object source) {
|
||||
|
||||
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(ReactiveAuditingEntityCallback.class);
|
||||
|
||||
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(AuditingEntityCallback.class);
|
||||
builder.addConstructorArgValue(ParsingUtils.getObjectFactoryBeanDefinition(getAuditingHandlerBeanName(), registry));
|
||||
builder.getRawBeanDefinition().setSource(source);
|
||||
|
||||
registerInfrastructureBeanWithId(builder.getBeanDefinition(), ReactiveAuditingEntityCallback.class.getName(),
|
||||
registry);
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple helper to be able to wire the {@link MappingContext} from a {@link MappingElasticsearchConverter} bean
|
||||
* available in the application context.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
static class ElasticsearchMappingContextLookup implements
|
||||
FactoryBean<MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty>> {
|
||||
|
||||
private final MappingElasticsearchConverter converter;
|
||||
|
||||
/**
|
||||
* Creates a new {@link ElasticsearchMappingContextLookup} for the given {@link MappingElasticsearchConverter}.
|
||||
*
|
||||
* @param converter must not be {@literal null}.
|
||||
*/
|
||||
public ElasticsearchMappingContextLookup(MappingElasticsearchConverter converter) {
|
||||
this.converter = converter;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.beans.factory.FactoryBean#getObject()
|
||||
*/
|
||||
@Override
|
||||
public MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> getObject()
|
||||
throws Exception {
|
||||
return converter.getMappingContext();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.beans.factory.FactoryBean#getObjectType()
|
||||
*/
|
||||
@Override
|
||||
public Class<?> getObjectType() {
|
||||
return MappingContext.class;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.beans.factory.FactoryBean#isSingleton()
|
||||
*/
|
||||
@Override
|
||||
public boolean isSingleton() {
|
||||
return true;
|
||||
}
|
||||
registerInfrastructureBeanWithId(builder.getBeanDefinition(), AuditingEntityCallback.class.getName(), registry);
|
||||
}
|
||||
}
|
||||
|
||||
+6
-5
@@ -40,15 +40,16 @@ import org.springframework.util.StringUtils;
|
||||
* @author Peter-Josef Meisch
|
||||
* @since 3.2
|
||||
*/
|
||||
@Configuration
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
public class ElasticsearchConfigurationSupport {
|
||||
|
||||
@Bean
|
||||
public ElasticsearchConverter elasticsearchEntityMapper(
|
||||
SimpleElasticsearchMappingContext elasticsearchMappingContext) {
|
||||
SimpleElasticsearchMappingContext elasticsearchMappingContext, ElasticsearchCustomConversions elasticsearchCustomConversions) {
|
||||
|
||||
MappingElasticsearchConverter elasticsearchConverter = new MappingElasticsearchConverter(
|
||||
elasticsearchMappingContext);
|
||||
elasticsearchConverter.setConversions(elasticsearchCustomConversions());
|
||||
elasticsearchConverter.setConversions(elasticsearchCustomConversions);
|
||||
return elasticsearchConverter;
|
||||
}
|
||||
|
||||
@@ -60,11 +61,11 @@ public class ElasticsearchConfigurationSupport {
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
@Bean
|
||||
public SimpleElasticsearchMappingContext elasticsearchMappingContext() {
|
||||
public SimpleElasticsearchMappingContext elasticsearchMappingContext(ElasticsearchCustomConversions elasticsearchCustomConversions) {
|
||||
|
||||
SimpleElasticsearchMappingContext mappingContext = new SimpleElasticsearchMappingContext();
|
||||
mappingContext.setInitialEntitySet(getInitialEntitySet());
|
||||
mappingContext.setSimpleTypeHolder(elasticsearchCustomConversions().getSimpleTypeHolder());
|
||||
mappingContext.setSimpleTypeHolder(elasticsearchCustomConversions.getSimpleTypeHolder());
|
||||
|
||||
return mappingContext;
|
||||
}
|
||||
|
||||
+2
@@ -65,6 +65,8 @@ public @interface EnableElasticsearchAuditing {
|
||||
* used for setting creation and modification dates.
|
||||
*
|
||||
* @return
|
||||
* @deprecated since 4.1
|
||||
*/
|
||||
@Deprecated
|
||||
String dateTimeProviderRef() default "";
|
||||
}
|
||||
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright 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 java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.data.auditing.DateTimeProvider;
|
||||
import org.springframework.data.domain.AuditorAware;
|
||||
|
||||
/**
|
||||
* Annotation to enable auditing in Elasticsearch using reactive infrastructure via annotation configuration.
|
||||
*
|
||||
* @author Peter-Josef Meisch
|
||||
* @since 4.1
|
||||
*/
|
||||
@Inherited
|
||||
@Documented
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Import(ReactiveElasticsearchAuditingRegistrar.class)
|
||||
public @interface EnableReactiveElasticsearchAuditing {
|
||||
|
||||
/**
|
||||
* Configures the {@link AuditorAware} bean to be used to lookup the current principal.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
String auditorAwareRef() default "";
|
||||
|
||||
/**
|
||||
* Configures whether the creation and modification dates are set. Defaults to {@literal true}.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
boolean setDates() default true;
|
||||
|
||||
/**
|
||||
* Configures whether the entity shall be marked as modified on creation. Defaults to {@literal true}.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
boolean modifyOnCreate() default true;
|
||||
|
||||
/**
|
||||
* Configures a {@link DateTimeProvider} bean name that allows customizing the {@link org.joda.time.DateTime} to be
|
||||
* used for setting creation and modification dates.
|
||||
*
|
||||
* @return
|
||||
* @deprecated since 4.1
|
||||
*/
|
||||
@Deprecated
|
||||
String dateTimeProviderRef() default "";
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright 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.FactoryBean;
|
||||
import org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter;
|
||||
import org.springframework.data.mapping.context.PersistentEntities;
|
||||
|
||||
/**
|
||||
* Simple helper to be able to wire the {@link PersistentEntities} from a {@link MappingElasticsearchConverter} bean
|
||||
* available in the application context.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Mark Paluch
|
||||
* @author Christoph Strobl
|
||||
* @author Peter-Josef Meisch
|
||||
* @since 4.1
|
||||
*/
|
||||
class PersistentEntitiesFactoryBean implements FactoryBean<PersistentEntities> {
|
||||
|
||||
private final MappingElasticsearchConverter converter;
|
||||
|
||||
/**
|
||||
* Creates a new {@link PersistentEntitiesFactoryBean} for the given {@link MappingElasticsearchConverter}.
|
||||
*
|
||||
* @param converter must not be {@literal null}.
|
||||
*/
|
||||
public PersistentEntitiesFactoryBean(MappingElasticsearchConverter converter) {
|
||||
this.converter = converter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PersistentEntities getObject() {
|
||||
return PersistentEntities.of(converter.getMappingContext());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getObjectType() {
|
||||
return PersistentEntities.class;
|
||||
}
|
||||
}
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright 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 java.lang.annotation.Annotation;
|
||||
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.support.AbstractBeanDefinition;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
|
||||
import org.springframework.data.auditing.ReactiveIsNewAwareAuditingHandler;
|
||||
import org.springframework.data.auditing.config.AuditingBeanDefinitionRegistrarSupport;
|
||||
import org.springframework.data.auditing.config.AuditingConfiguration;
|
||||
import org.springframework.data.config.ParsingUtils;
|
||||
import org.springframework.data.elasticsearch.core.event.ReactiveAuditingEntityCallback;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* {@link ImportBeanDefinitionRegistrar} to enable {@link EnableReactiveElasticsearchAuditing} annotation.
|
||||
*
|
||||
* @author Peter-Josef Meisch
|
||||
* @since 4.1
|
||||
*/
|
||||
class ReactiveElasticsearchAuditingRegistrar extends AuditingBeanDefinitionRegistrarSupport {
|
||||
|
||||
@Override
|
||||
protected Class<? extends Annotation> getAnnotation() {
|
||||
return EnableReactiveElasticsearchAuditing.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getAuditingHandlerBeanName() {
|
||||
return "reactiveElasticsearchAuditingHandler";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BeanDefinitionBuilder getAuditHandlerBeanDefinitionBuilder(AuditingConfiguration configuration) {
|
||||
|
||||
Assert.notNull(configuration, "AuditingConfiguration must not be null!");
|
||||
|
||||
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(ReactiveIsNewAwareAuditingHandler.class);
|
||||
|
||||
BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(PersistentEntitiesFactoryBean.class);
|
||||
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR);
|
||||
|
||||
builder.addConstructorArgValue(definition.getBeanDefinition());
|
||||
return configureDefaultAuditHandlerAttributes(configuration, builder);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void registerAuditListenerBeanDefinition(BeanDefinition auditingHandlerDefinition,
|
||||
BeanDefinitionRegistry registry) {
|
||||
|
||||
Assert.notNull(auditingHandlerDefinition, "BeanDefinition must not be null!");
|
||||
Assert.notNull(registry, "BeanDefinitionRegistry must not be null!");
|
||||
|
||||
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(ReactiveAuditingEntityCallback.class);
|
||||
builder.addConstructorArgValue(ParsingUtils.getObjectFactoryBeanDefinition(getAuditingHandlerBeanName(), registry));
|
||||
|
||||
registerInfrastructureBeanWithId(builder.getBeanDefinition(), ReactiveAuditingEntityCallback.class.getName(),
|
||||
registry);
|
||||
}
|
||||
}
|
||||
+87
-79
@@ -17,27 +17,27 @@ package org.springframework.data.elasticsearch.core;
|
||||
|
||||
import static org.springframework.util.StringUtils.*;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import org.elasticsearch.action.admin.indices.settings.get.GetSettingsResponse;
|
||||
import org.elasticsearch.cluster.metadata.AliasMetaData;
|
||||
import org.elasticsearch.common.collect.MapBuilder;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.cluster.metadata.AliasMetadata;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
import org.springframework.data.elasticsearch.ElasticsearchException;
|
||||
import org.springframework.data.elasticsearch.UncategorizedElasticsearchException;
|
||||
import org.springframework.data.elasticsearch.annotations.Mapping;
|
||||
import org.springframework.data.elasticsearch.annotations.Setting;
|
||||
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
|
||||
import org.springframework.data.elasticsearch.core.document.Document;
|
||||
import org.springframework.data.elasticsearch.core.index.AliasData;
|
||||
import org.springframework.data.elasticsearch.core.index.MappingBuilder;
|
||||
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
|
||||
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
||||
import org.springframework.data.elasticsearch.core.query.AliasQuery;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
@@ -55,20 +55,24 @@ abstract class AbstractDefaultIndexOperations implements IndexOperations {
|
||||
protected final RequestFactory requestFactory;
|
||||
|
||||
@Nullable protected final Class<?> boundClass;
|
||||
private final IndexCoordinates boundIndex;
|
||||
@Nullable private final IndexCoordinates boundIndex;
|
||||
|
||||
public AbstractDefaultIndexOperations(ElasticsearchConverter elasticsearchConverter, Class<?> boundClass) {
|
||||
|
||||
Assert.notNull(boundClass, "boundClass may not be null");
|
||||
|
||||
this.elasticsearchConverter = elasticsearchConverter;
|
||||
requestFactory = new RequestFactory(elasticsearchConverter);
|
||||
|
||||
this.boundClass = boundClass;
|
||||
this.boundIndex = getIndexCoordinatesFor(boundClass);
|
||||
this.boundIndex = null;
|
||||
}
|
||||
|
||||
public AbstractDefaultIndexOperations(ElasticsearchConverter elasticsearchConverter, IndexCoordinates boundIndex) {
|
||||
|
||||
Assert.notNull(boundIndex, "boundIndex may not be null");
|
||||
|
||||
this.elasticsearchConverter = elasticsearchConverter;
|
||||
requestFactory = new RequestFactory(elasticsearchConverter);
|
||||
|
||||
this.boundClass = null;
|
||||
this.boundIndex = boundIndex;
|
||||
}
|
||||
@@ -85,48 +89,54 @@ abstract class AbstractDefaultIndexOperations implements IndexOperations {
|
||||
@Override
|
||||
public boolean create() {
|
||||
|
||||
Document settings = null;
|
||||
|
||||
if (boundClass != null) {
|
||||
Class<?> clazz = boundClass;
|
||||
String indexName = getIndexCoordinates().getIndexName();
|
||||
|
||||
if (clazz.isAnnotationPresent(Setting.class)) {
|
||||
String settingPath = clazz.getAnnotation(Setting.class).settingPath();
|
||||
|
||||
if (hasText(settingPath)) {
|
||||
String settings = ResourceUtil.readFileFromClasspath(settingPath);
|
||||
|
||||
if (hasText(settings)) {
|
||||
return doCreate(indexName, Document.parse(settings));
|
||||
}
|
||||
} else {
|
||||
LOGGER.info("settingPath in @Setting has to be defined. Using default instead.");
|
||||
}
|
||||
}
|
||||
return doCreate(indexName, getDefaultSettings(getRequiredPersistentEntity(clazz)));
|
||||
settings = createSettings(boundClass);
|
||||
}
|
||||
return doCreate(getIndexCoordinates().getIndexName(), null);
|
||||
|
||||
return doCreate(getIndexCoordinates(), settings);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Document createSettings(Class<?> clazz) {
|
||||
|
||||
Assert.notNull(clazz, "clazz must not be null");
|
||||
|
||||
Document settings = null;
|
||||
|
||||
if (clazz.isAnnotationPresent(Setting.class)) {
|
||||
String settingPath = clazz.getAnnotation(Setting.class).settingPath();
|
||||
settings = loadSettings(settingPath);
|
||||
}
|
||||
|
||||
if (settings == null) {
|
||||
settings = getRequiredPersistentEntity(clazz).getDefaultSettings();
|
||||
}
|
||||
|
||||
return settings;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean create(Document settings) {
|
||||
return doCreate(getIndexCoordinates().getIndexName(), settings);
|
||||
return doCreate(getIndexCoordinates(), settings);
|
||||
}
|
||||
|
||||
protected abstract boolean doCreate(String indexName, @Nullable Document settings);
|
||||
protected abstract boolean doCreate(IndexCoordinates index, @Nullable Document settings);
|
||||
|
||||
@Override
|
||||
public boolean delete() {
|
||||
return doDelete(getIndexCoordinates().getIndexName());
|
||||
return doDelete(getIndexCoordinates());
|
||||
}
|
||||
|
||||
protected abstract boolean doDelete(String indexName);
|
||||
protected abstract boolean doDelete(IndexCoordinates index);
|
||||
|
||||
@Override
|
||||
public boolean exists() {
|
||||
return doExists(getIndexCoordinates().getIndexName());
|
||||
return doExists(getIndexCoordinates());
|
||||
}
|
||||
|
||||
protected abstract boolean doExists(String indexName);
|
||||
protected abstract boolean doExists(IndexCoordinates index);
|
||||
|
||||
@Override
|
||||
public boolean putMapping(Document mapping) {
|
||||
@@ -149,10 +159,10 @@ abstract class AbstractDefaultIndexOperations implements IndexOperations {
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getSettings(boolean includeDefaults) {
|
||||
return doGetSettings(getIndexCoordinates().getIndexName(), includeDefaults);
|
||||
return doGetSettings(getIndexCoordinates(), includeDefaults);
|
||||
}
|
||||
|
||||
protected abstract Map<String, Object> doGetSettings(String indexName, boolean includeDefaults);
|
||||
protected abstract Map<String, Object> doGetSettings(IndexCoordinates index, boolean includeDefaults);
|
||||
|
||||
@Override
|
||||
public void refresh() {
|
||||
@@ -169,11 +179,11 @@ abstract class AbstractDefaultIndexOperations implements IndexOperations {
|
||||
protected abstract boolean doAddAlias(AliasQuery query, IndexCoordinates index);
|
||||
|
||||
@Override
|
||||
public List<AliasMetaData> queryForAlias() {
|
||||
return doQueryForAlias(getIndexCoordinates().getIndexName());
|
||||
public List<AliasMetadata> queryForAlias() {
|
||||
return doQueryForAlias(getIndexCoordinates());
|
||||
}
|
||||
|
||||
protected abstract List<AliasMetaData> doQueryForAlias(String indexName);
|
||||
protected abstract List<AliasMetadata> doQueryForAlias(IndexCoordinates index);
|
||||
|
||||
@Override
|
||||
public boolean removeAlias(AliasQuery query) {
|
||||
@@ -182,6 +192,25 @@ abstract class AbstractDefaultIndexOperations implements IndexOperations {
|
||||
|
||||
protected abstract boolean doRemoveAlias(AliasQuery query, IndexCoordinates index);
|
||||
|
||||
@Override
|
||||
public Map<String, Set<AliasData>> getAliases(String... aliasNames) {
|
||||
|
||||
Assert.notEmpty(aliasNames, "aliasNames must not be empty");
|
||||
|
||||
return doGetAliases(aliasNames, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Set<AliasData>> getAliasesForIndex(String... indexNames) {
|
||||
|
||||
Assert.notEmpty(indexNames, "indexNames must not be empty");
|
||||
|
||||
return doGetAliases(null, indexNames);
|
||||
}
|
||||
|
||||
protected abstract Map<String, Set<AliasData>> doGetAliases(@Nullable String[] aliasNames,
|
||||
@Nullable String[] indexNames);
|
||||
|
||||
@Override
|
||||
public Document createMapping() {
|
||||
return createMapping(checkForBoundClass());
|
||||
@@ -214,64 +243,43 @@ abstract class AbstractDefaultIndexOperations implements IndexOperations {
|
||||
String mapping = new MappingBuilder(elasticsearchConverter).buildPropertyMapping(clazz);
|
||||
return Document.parse(mapping);
|
||||
} catch (Exception e) {
|
||||
throw new ElasticsearchException("Failed to build mapping for " + clazz.getSimpleName(), e);
|
||||
throw new UncategorizedElasticsearchException("Failed to build mapping for " + clazz.getSimpleName(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Document createSettings() {
|
||||
return createSettings(checkForBoundClass());
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region Helper functions
|
||||
private <T> Document getDefaultSettings(ElasticsearchPersistentEntity<T> persistentEntity) {
|
||||
|
||||
if (persistentEntity.isUseServerConfiguration()) {
|
||||
return Document.create();
|
||||
}
|
||||
|
||||
Map<String, String> map = new MapBuilder<String, String>()
|
||||
.put("index.number_of_shards", String.valueOf(persistentEntity.getShards()))
|
||||
.put("index.number_of_replicas", String.valueOf(persistentEntity.getReplicas()))
|
||||
.put("index.refresh_interval", persistentEntity.getRefreshInterval())
|
||||
.put("index.store.type", persistentEntity.getIndexStoreType()).map();
|
||||
return Document.from(map);
|
||||
}
|
||||
|
||||
ElasticsearchPersistentEntity<?> getRequiredPersistentEntity(Class<?> clazz) {
|
||||
return elasticsearchConverter.getMappingContext().getRequiredPersistentEntity(clazz);
|
||||
}
|
||||
|
||||
/**
|
||||
* get the current {@link IndexCoordinates}. These may change over time when the entity class has a SpEL constructed
|
||||
* index name. When this IndexOperations is not bound to a class, the bound IndexCoordinates are returned.
|
||||
*
|
||||
* @return IndexCoordinates
|
||||
*/
|
||||
protected IndexCoordinates getIndexCoordinates() {
|
||||
return (boundClass != null) ? getIndexCoordinatesFor(boundClass) : boundIndex;
|
||||
@Override
|
||||
public IndexCoordinates getIndexCoordinates() {
|
||||
return (boundClass != null) ? getIndexCoordinatesFor(boundClass) : Objects.requireNonNull(boundIndex);
|
||||
}
|
||||
|
||||
public IndexCoordinates getIndexCoordinatesFor(Class<?> clazz) {
|
||||
return getRequiredPersistentEntity(clazz).getIndexCoordinates();
|
||||
}
|
||||
|
||||
protected Map<String, Object> convertSettingsResponseToMap(GetSettingsResponse response, String indexName) {
|
||||
@Nullable
|
||||
private Document loadSettings(String settingPath) {
|
||||
if (hasText(settingPath)) {
|
||||
String settingsFile = ResourceUtil.readFileFromClasspath(settingPath);
|
||||
|
||||
Map<String, Object> settings = new HashMap<>();
|
||||
|
||||
if (!response.getIndexToDefaultSettings().isEmpty()) {
|
||||
Settings defaultSettings = response.getIndexToDefaultSettings().get(indexName);
|
||||
for (String key : defaultSettings.keySet()) {
|
||||
settings.put(key, defaultSettings.get(key));
|
||||
if (hasText(settingsFile)) {
|
||||
return Document.parse(settingsFile);
|
||||
}
|
||||
} else {
|
||||
LOGGER.info("settingPath in @Setting has to be defined. Using default instead.");
|
||||
}
|
||||
|
||||
if (!response.getIndexToSettings().isEmpty()) {
|
||||
Settings customSettings = response.getIndexToSettings().get(indexName);
|
||||
for (String key : customSettings.keySet()) {
|
||||
settings.put(key, customSettings.get(key));
|
||||
}
|
||||
}
|
||||
|
||||
return settings;
|
||||
return null;
|
||||
}
|
||||
// endregion
|
||||
|
||||
}
|
||||
|
||||
+218
-47
@@ -25,6 +25,7 @@ import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.elasticsearch.action.DocWriteResponse;
|
||||
import org.elasticsearch.action.bulk.BulkItemResponse;
|
||||
import org.elasticsearch.action.bulk.BulkResponse;
|
||||
import org.elasticsearch.action.search.MultiSearchRequest;
|
||||
@@ -32,11 +33,12 @@ import org.elasticsearch.action.search.MultiSearchResponse;
|
||||
import org.elasticsearch.action.search.SearchResponse;
|
||||
import org.elasticsearch.common.unit.TimeValue;
|
||||
import org.elasticsearch.index.query.MoreLikeThisQueryBuilder;
|
||||
import org.elasticsearch.search.suggest.SuggestBuilder;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.data.convert.EntityReader;
|
||||
import org.springframework.data.elasticsearch.ElasticsearchException;
|
||||
import org.springframework.data.elasticsearch.BulkFailureException;
|
||||
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
|
||||
import org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter;
|
||||
import org.springframework.data.elasticsearch.core.document.Document;
|
||||
@@ -48,6 +50,7 @@ import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersiste
|
||||
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
|
||||
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
||||
import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext;
|
||||
import org.springframework.data.elasticsearch.core.query.BulkOptions;
|
||||
import org.springframework.data.elasticsearch.core.query.GetQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.IndexQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.IndexQueryBuilder;
|
||||
@@ -55,7 +58,9 @@ import org.springframework.data.elasticsearch.core.query.MoreLikeThisQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
|
||||
import org.springframework.data.elasticsearch.core.query.Query;
|
||||
import org.springframework.data.elasticsearch.core.query.SeqNoPrimaryTerm;
|
||||
import org.springframework.data.elasticsearch.core.query.UpdateQuery;
|
||||
import org.springframework.data.elasticsearch.support.VersionInfo;
|
||||
import org.springframework.data.mapping.PersistentPropertyAccessor;
|
||||
import org.springframework.data.mapping.callback.EntityCallbacks;
|
||||
import org.springframework.data.util.CloseableIterator;
|
||||
import org.springframework.data.util.Streamable;
|
||||
@@ -69,13 +74,14 @@ import org.springframework.util.Assert;
|
||||
* @author Sascha Woo
|
||||
* @author Peter-Josef Meisch
|
||||
* @author Roman Puchkovskiy
|
||||
* @author Subhobrata Dey
|
||||
*/
|
||||
public abstract class AbstractElasticsearchTemplate implements ElasticsearchOperations, ApplicationContextAware {
|
||||
|
||||
protected @Nullable ElasticsearchConverter elasticsearchConverter;
|
||||
protected @Nullable RequestFactory requestFactory;
|
||||
|
||||
private @Nullable EntityCallbacks entityCallbacks;
|
||||
@Nullable protected ElasticsearchConverter elasticsearchConverter;
|
||||
@Nullable protected RequestFactory requestFactory;
|
||||
@Nullable private EntityOperations entityOperations;
|
||||
@Nullable private EntityCallbacks entityCallbacks;
|
||||
|
||||
// region Initialization
|
||||
protected void initialize(ElasticsearchConverter elasticsearchConverter) {
|
||||
@@ -83,6 +89,7 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
|
||||
Assert.notNull(elasticsearchConverter, "elasticsearchConverter must not be null.");
|
||||
|
||||
this.elasticsearchConverter = elasticsearchConverter;
|
||||
this.entityOperations = new EntityOperations(this.elasticsearchConverter.getMappingContext());
|
||||
requestFactory = new RequestFactory(elasticsearchConverter);
|
||||
|
||||
VersionInfo.logVersions(getClusterVersion());
|
||||
@@ -140,13 +147,14 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
|
||||
Assert.notNull(entity, "entity must not be null");
|
||||
Assert.notNull(index, "index must not be null");
|
||||
|
||||
IndexQuery query = getIndexQuery(entity);
|
||||
index(query, index);
|
||||
T entityAfterBeforeConvert = maybeCallbackBeforeConvert(entity, index);
|
||||
|
||||
// suppressing because it's either entity itself or something of a correct type returned by an entity callback
|
||||
@SuppressWarnings("unchecked")
|
||||
T castResult = (T) query.getObject();
|
||||
return castResult;
|
||||
IndexQuery query = getIndexQuery(entityAfterBeforeConvert);
|
||||
doIndex(query, index);
|
||||
|
||||
T entityAfterAfterSave = maybeCallbackAfterSave(entityAfterBeforeConvert, index);
|
||||
|
||||
return entityAfterAfterSave;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -172,11 +180,9 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (!indexQueries.isEmpty()) {
|
||||
List<String> ids = bulkIndex(indexQueries, index);
|
||||
Iterator<String> idIterator = ids.iterator();
|
||||
entities.forEach(entity -> {
|
||||
setPersistentEntityId(entity, idIterator.next());
|
||||
});
|
||||
List<IndexedObjectInformation> indexedObjectInformations = bulkIndex(indexQueries, index);
|
||||
Iterator<IndexedObjectInformation> iterator = indexedObjectInformations.iterator();
|
||||
entities.forEach(entity -> updateIndexedObject(entity, iterator.next()));
|
||||
}
|
||||
|
||||
return indexQueries.stream().map(IndexQuery::getObject).map(entity -> (T) entity).collect(Collectors.toList());
|
||||
@@ -187,6 +193,20 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
|
||||
return save(Arrays.asList(entities));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String index(IndexQuery query, IndexCoordinates index) {
|
||||
|
||||
maybeCallbackBeforeConvertWithQuery(query, index);
|
||||
|
||||
String documentId = doIndex(query, index);
|
||||
|
||||
maybeCallbackAfterSaveWithQuery(query, index);
|
||||
|
||||
return documentId;
|
||||
}
|
||||
|
||||
public abstract String doIndex(IndexQuery query, IndexCoordinates indexCoordinates);
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public <T> T get(String id, Class<T> clazz) {
|
||||
@@ -199,6 +219,11 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
|
||||
return get(query.getId(), clazz, index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> List<T> multiGet(Query query, Class<T> clazz) {
|
||||
return multiGet(query, clazz, getIndexCoordinatesFor(clazz));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public <T> T queryForObject(GetQuery query, Class<T> clazz) {
|
||||
@@ -226,6 +251,11 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
|
||||
return this.delete(id, getIndexCoordinatesFor(entityType));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(Query query, Class<?> clazz) {
|
||||
delete(query, clazz, getIndexCoordinatesFor(clazz));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String delete(Object entity) {
|
||||
return delete(entity, getIndexCoordinatesFor(entity.getClass()));
|
||||
@@ -235,6 +265,49 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
|
||||
public String delete(Object entity, IndexCoordinates index) {
|
||||
return this.delete(getEntityId(entity), index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<IndexedObjectInformation> bulkIndex(List<IndexQuery> queries, Class<?> clazz) {
|
||||
return bulkIndex(queries, getIndexCoordinatesFor(clazz));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<IndexedObjectInformation> bulkIndex(List<IndexQuery> queries, BulkOptions bulkOptions, Class<?> clazz) {
|
||||
return bulkIndex(queries, bulkOptions, getIndexCoordinatesFor(clazz));
|
||||
}
|
||||
|
||||
@Override
|
||||
public final List<IndexedObjectInformation> bulkIndex(List<IndexQuery> queries, BulkOptions bulkOptions,
|
||||
IndexCoordinates index) {
|
||||
|
||||
Assert.notNull(queries, "List of IndexQuery must not be null");
|
||||
Assert.notNull(bulkOptions, "BulkOptions must not be null");
|
||||
|
||||
return bulkOperation(queries, bulkOptions, index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bulkUpdate(List<UpdateQuery> queries, Class<?> clazz) {
|
||||
bulkUpdate(queries, getIndexCoordinatesFor(clazz));
|
||||
}
|
||||
|
||||
public List<IndexedObjectInformation> bulkOperation(List<?> queries, BulkOptions bulkOptions,
|
||||
IndexCoordinates index) {
|
||||
|
||||
Assert.notNull(queries, "List of IndexQuery must not be null");
|
||||
Assert.notNull(bulkOptions, "BulkOptions must not be null");
|
||||
|
||||
maybeCallbackBeforeConvertWithQueries(queries, index);
|
||||
|
||||
List<IndexedObjectInformation> indexedObjectInformations = doBulkOperation(queries, bulkOptions, index);
|
||||
|
||||
maybeCallbackAfterSaveWithQueries(queries, index);
|
||||
|
||||
return indexedObjectInformations;
|
||||
}
|
||||
|
||||
public abstract List<IndexedObjectInformation> doBulkOperation(List<?> queries, BulkOptions bulkOptions,
|
||||
IndexCoordinates index);
|
||||
// endregion
|
||||
|
||||
// region SearchOperations
|
||||
@@ -258,7 +331,11 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
|
||||
|
||||
long scrollTimeInMillis = TimeValue.timeValueMinutes(1).millis();
|
||||
|
||||
// noinspection ConstantConditions
|
||||
int maxCount = query.isLimiting() ? query.getMaxResults() : 0;
|
||||
|
||||
return StreamQueries.streamResults( //
|
||||
maxCount, //
|
||||
searchScrollStart(scrollTimeInMillis, query, clazz, index), //
|
||||
scrollId -> searchScrollContinue(scrollId, scrollTimeInMillis, clazz, index), //
|
||||
this::searchScrollClear);
|
||||
@@ -278,6 +355,11 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
|
||||
return search(new NativeSearchQueryBuilder().withQuery(moreLikeThisQueryBuilder).build(), clazz, index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> List<SearchHits<T>> multiSearch(List<? extends Query> queries, Class<T> clazz) {
|
||||
return multiSearch(queries, clazz, getIndexCoordinatesFor(clazz));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> List<SearchHits<T>> multiSearch(List<? extends Query> queries, Class<T> clazz, IndexCoordinates index) {
|
||||
MultiSearchRequest request = new MultiSearchRequest();
|
||||
@@ -296,9 +378,46 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
|
||||
return res;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SearchHits<?>> multiSearch(List<? extends Query> queries, List<Class<?>> classes) {
|
||||
|
||||
Assert.notNull(queries, "queries must not be null");
|
||||
Assert.notNull(classes, "classes must not be null");
|
||||
Assert.isTrue(queries.size() == classes.size(), "queries and classes must have the same size");
|
||||
|
||||
MultiSearchRequest request = new MultiSearchRequest();
|
||||
Iterator<Class<?>> it = classes.iterator();
|
||||
for (Query query : queries) {
|
||||
Class<?> clazz = it.next();
|
||||
request.add(requestFactory.searchRequest(query, clazz, getIndexCoordinatesFor(clazz)));
|
||||
}
|
||||
|
||||
MultiSearchResponse.Item[] items = getMultiSearchResult(request);
|
||||
|
||||
List<SearchHits<?>> res = new ArrayList<>(queries.size());
|
||||
int c = 0;
|
||||
Iterator<Class<?>> it1 = classes.iterator();
|
||||
for (Query query : queries) {
|
||||
Class entityClass = it1.next();
|
||||
|
||||
SearchDocumentResponseCallback<SearchHits<?>> callback = new ReadSearchDocumentResponseCallback<>(entityClass,
|
||||
getIndexCoordinatesFor(entityClass));
|
||||
|
||||
SearchResponse response = items[c++].getResponse();
|
||||
res.add(callback.doWith(SearchDocumentResponse.from(response)));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SearchHits<?>> multiSearch(List<? extends Query> queries, List<Class<?>> classes,
|
||||
IndexCoordinates index) {
|
||||
|
||||
Assert.notNull(queries, "queries must not be null");
|
||||
Assert.notNull(classes, "classes must not be null");
|
||||
Assert.notNull(index, "index must not be null");
|
||||
Assert.isTrue(queries.size() == classes.size(), "queries and classes must have the same size");
|
||||
|
||||
MultiSearchRequest request = new MultiSearchRequest();
|
||||
Iterator<Class<?>> it = classes.iterator();
|
||||
for (Query query : queries) {
|
||||
@@ -352,6 +471,12 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
|
||||
abstract protected void searchScrollClear(List<String> scrollIds);
|
||||
|
||||
abstract protected MultiSearchResponse.Item[] getMultiSearchResult(MultiSearchRequest request);
|
||||
|
||||
@Override
|
||||
public SearchResponse suggest(SuggestBuilder suggestion, Class<?> clazz) {
|
||||
return suggest(suggestion, getIndexCoordinatesFor(clazz));
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region Helper methods
|
||||
@@ -392,7 +517,7 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
|
||||
* @param bulkResponse
|
||||
* @return the list of the item id's
|
||||
*/
|
||||
protected List<String> checkForBulkOperationFailure(BulkResponse bulkResponse) {
|
||||
protected List<IndexedObjectInformation> checkForBulkOperationFailure(BulkResponse bulkResponse) {
|
||||
|
||||
if (bulkResponse.hasFailures()) {
|
||||
Map<String, String> failedDocuments = new HashMap<>();
|
||||
@@ -401,23 +526,44 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
|
||||
if (item.isFailed())
|
||||
failedDocuments.put(item.getId(), item.getFailureMessage());
|
||||
}
|
||||
throw new ElasticsearchException(
|
||||
throw new BulkFailureException(
|
||||
"Bulk operation has failures. Use ElasticsearchException.getFailedDocuments() for detailed messages ["
|
||||
+ failedDocuments + ']',
|
||||
failedDocuments);
|
||||
}
|
||||
|
||||
return Stream.of(bulkResponse.getItems()).map(BulkItemResponse::getId).collect(Collectors.toList());
|
||||
return Stream.of(bulkResponse.getItems()).map(bulkItemResponse -> {
|
||||
DocWriteResponse response = bulkItemResponse.getResponse();
|
||||
if (response != null) {
|
||||
return IndexedObjectInformation.of(response.getId(), response.getSeqNo(), response.getPrimaryTerm(),
|
||||
response.getVersion());
|
||||
} else {
|
||||
return IndexedObjectInformation.of(bulkItemResponse.getId(), null, null, null);
|
||||
}
|
||||
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
protected void setPersistentEntityId(Object entity, String id) {
|
||||
|
||||
protected void updateIndexedObject(Object entity, IndexedObjectInformation indexedObjectInformation) {
|
||||
ElasticsearchPersistentEntity<?> persistentEntity = getRequiredPersistentEntity(entity.getClass());
|
||||
PersistentPropertyAccessor<Object> propertyAccessor = persistentEntity.getPropertyAccessor(entity);
|
||||
ElasticsearchPersistentProperty idProperty = persistentEntity.getIdProperty();
|
||||
|
||||
// Only deal with text because ES generated Ids are strings!
|
||||
if (idProperty != null && idProperty.getType().isAssignableFrom(String.class)) {
|
||||
persistentEntity.getPropertyAccessor(entity).setProperty(idProperty, id);
|
||||
propertyAccessor.setProperty(idProperty, indexedObjectInformation.getId());
|
||||
}
|
||||
|
||||
if (indexedObjectInformation.getSeqNo() != null && indexedObjectInformation.getPrimaryTerm() != null
|
||||
&& persistentEntity.hasSeqNoPrimaryTermProperty()) {
|
||||
ElasticsearchPersistentProperty seqNoPrimaryTermProperty = persistentEntity.getSeqNoPrimaryTermProperty();
|
||||
propertyAccessor.setProperty(seqNoPrimaryTermProperty,
|
||||
new SeqNoPrimaryTerm(indexedObjectInformation.getSeqNo(), indexedObjectInformation.getPrimaryTerm()));
|
||||
}
|
||||
|
||||
if (indexedObjectInformation.getVersion() != null && persistentEntity.hasVersionProperty()) {
|
||||
ElasticsearchPersistentProperty versionProperty = persistentEntity.getVersionProperty();
|
||||
propertyAccessor.setProperty(versionProperty, indexedObjectInformation.getVersion());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -427,27 +573,28 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
|
||||
|
||||
@Nullable
|
||||
private String getEntityId(Object entity) {
|
||||
ElasticsearchPersistentEntity<?> persistentEntity = getRequiredPersistentEntity(entity.getClass());
|
||||
ElasticsearchPersistentProperty idProperty = persistentEntity.getIdProperty();
|
||||
|
||||
if (idProperty != null) {
|
||||
return stringIdRepresentation(persistentEntity.getPropertyAccessor(entity).getProperty(idProperty));
|
||||
Object id = entityOperations.forEntity(entity, elasticsearchConverter.getConversionService()).getId();
|
||||
|
||||
if (id != null) {
|
||||
return stringIdRepresentation(id);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getEntityRouting(Object entity) {
|
||||
return entityOperations.forEntity(entity, elasticsearchConverter.getConversionService()).getRouting();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Long getEntityVersion(Object entity) {
|
||||
ElasticsearchPersistentEntity<?> persistentEntity = getRequiredPersistentEntity(entity.getClass());
|
||||
ElasticsearchPersistentProperty versionProperty = persistentEntity.getVersionProperty();
|
||||
|
||||
if (versionProperty != null) {
|
||||
Object version = persistentEntity.getPropertyAccessor(entity).getProperty(versionProperty);
|
||||
Number version = entityOperations.forEntity(entity, elasticsearchConverter.getConversionService()).getVersion();
|
||||
|
||||
if (version != null && Long.class.isAssignableFrom(version.getClass())) {
|
||||
return ((Long) version);
|
||||
}
|
||||
if (version != null && Long.class.isAssignableFrom(version.getClass())) {
|
||||
return ((Long) version);
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -455,18 +602,10 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
|
||||
|
||||
@Nullable
|
||||
private SeqNoPrimaryTerm getEntitySeqNoPrimaryTerm(Object entity) {
|
||||
ElasticsearchPersistentEntity<?> persistentEntity = getRequiredPersistentEntity(entity.getClass());
|
||||
ElasticsearchPersistentProperty property = persistentEntity.getSeqNoPrimaryTermProperty();
|
||||
|
||||
if (property != null) {
|
||||
Object seqNoPrimaryTerm = persistentEntity.getPropertyAccessor(entity).getProperty(property);
|
||||
|
||||
if (seqNoPrimaryTerm != null && SeqNoPrimaryTerm.class.isAssignableFrom(seqNoPrimaryTerm.getClass())) {
|
||||
return (SeqNoPrimaryTerm) seqNoPrimaryTerm;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
EntityOperations.AdaptibleEntity<Object> adaptibleEntity = entityOperations.forEntity(entity,
|
||||
elasticsearchConverter.getConversionService());
|
||||
return adaptibleEntity.hasSeqNoPrimaryTerm() ? adaptibleEntity.getSeqNoPrimaryTerm() : null;
|
||||
}
|
||||
|
||||
private <T> IndexQuery getIndexQuery(T entity) {
|
||||
@@ -486,6 +625,11 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
|
||||
// version cannot be used together with seq_no and primary_term
|
||||
builder.withVersion(getEntityVersion(entity));
|
||||
}
|
||||
|
||||
String routing = getEntityRouting(entity);
|
||||
if (routing != null) {
|
||||
builder.withRouting(routing);
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@@ -518,6 +662,20 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
|
||||
if (queryObject != null) {
|
||||
queryObject = maybeCallbackBeforeConvert(queryObject, index);
|
||||
indexQuery.setObject(queryObject);
|
||||
// the callback might have set som values relevant for the IndexQuery
|
||||
IndexQuery newQuery = getIndexQuery(queryObject);
|
||||
|
||||
if (indexQuery.getRouting() == null && newQuery.getRouting() != null) {
|
||||
indexQuery.setRouting(newQuery.getRouting());
|
||||
}
|
||||
|
||||
if (indexQuery.getSeqNo() == null && newQuery.getSeqNo() != null) {
|
||||
indexQuery.setSeqNo(newQuery.getSeqNo());
|
||||
}
|
||||
|
||||
if (indexQuery.getPrimaryTerm() == null && newQuery.getPrimaryTerm() != null) {
|
||||
indexQuery.setPrimaryTerm(newQuery.getPrimaryTerm());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -567,6 +725,20 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
|
||||
|
||||
// endregion
|
||||
|
||||
protected void updateIndexedObjectsWithQueries(List<?> queries,
|
||||
List<IndexedObjectInformation> indexedObjectInformations) {
|
||||
for (int i = 0; i < queries.size(); i++) {
|
||||
Object query = queries.get(i);
|
||||
if (query instanceof IndexQuery) {
|
||||
IndexQuery indexQuery = (IndexQuery) query;
|
||||
Object queryObject = indexQuery.getObject();
|
||||
if (queryObject != null) {
|
||||
updateIndexedObject(queryObject, indexedObjectInformations.get(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// region Document callbacks
|
||||
protected interface DocumentCallback<T> {
|
||||
@Nullable
|
||||
@@ -620,7 +792,7 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
|
||||
@Override
|
||||
public SearchHits<T> doWith(SearchDocumentResponse response) {
|
||||
List<T> entities = response.getSearchDocuments().stream().map(delegate::doWith).collect(Collectors.toList());
|
||||
return SearchHitMapping.mappingFor(type, elasticsearchConverter.getMappingContext()).mapHits(response, entities);
|
||||
return SearchHitMapping.mappingFor(type, elasticsearchConverter).mapHits(response, entities);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -640,8 +812,7 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
|
||||
@Override
|
||||
public SearchScrollHits<T> doWith(SearchDocumentResponse response) {
|
||||
List<T> entities = response.getSearchDocuments().stream().map(delegate::doWith).collect(Collectors.toList());
|
||||
return SearchHitMapping.mappingFor(type, elasticsearchConverter.getMappingContext()).mapScrollHits(response,
|
||||
entities);
|
||||
return SearchHitMapping.mappingFor(type, elasticsearchConverter).mapScrollHits(response, entities);
|
||||
}
|
||||
}
|
||||
// endregion
|
||||
|
||||
+120
-99
@@ -17,9 +17,11 @@ package org.springframework.data.elasticsearch.core;
|
||||
|
||||
import static org.springframework.data.elasticsearch.core.query.Criteria.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.elasticsearch.common.geo.GeoDistance;
|
||||
import org.elasticsearch.index.query.BoolQueryBuilder;
|
||||
@@ -28,6 +30,7 @@ import org.elasticsearch.index.query.GeoDistanceQueryBuilder;
|
||||
import org.elasticsearch.index.query.QueryBuilder;
|
||||
import org.elasticsearch.index.query.QueryBuilders;
|
||||
import org.springframework.data.elasticsearch.core.geo.GeoBox;
|
||||
import org.springframework.data.elasticsearch.core.geo.GeoJson;
|
||||
import org.springframework.data.elasticsearch.core.geo.GeoPoint;
|
||||
import org.springframework.data.elasticsearch.core.query.Criteria;
|
||||
import org.springframework.data.geo.Box;
|
||||
@@ -47,139 +50,158 @@ import org.springframework.util.Assert;
|
||||
*/
|
||||
class CriteriaFilterProcessor {
|
||||
|
||||
QueryBuilder createFilterFromCriteria(Criteria criteria) {
|
||||
List<QueryBuilder> fbList = new LinkedList<>();
|
||||
QueryBuilder filter = null;
|
||||
@Nullable
|
||||
QueryBuilder createFilter(Criteria criteria) {
|
||||
|
||||
List<QueryBuilder> filterBuilders = new ArrayList<>();
|
||||
|
||||
for (Criteria chainedCriteria : criteria.getCriteriaChain()) {
|
||||
QueryBuilder fb = null;
|
||||
|
||||
if (chainedCriteria.isOr()) {
|
||||
fb = QueryBuilders.boolQuery();
|
||||
for (QueryBuilder f : createFilterFragmentForCriteria(chainedCriteria)) {
|
||||
((BoolQueryBuilder) fb).should(f);
|
||||
}
|
||||
fbList.add(fb);
|
||||
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
|
||||
queriesForEntries(chainedCriteria).forEach(boolQuery::should);
|
||||
filterBuilders.add(boolQuery);
|
||||
} else if (chainedCriteria.isNegating()) {
|
||||
List<QueryBuilder> negationFilters = buildNegationFilter(criteria.getField().getName(),
|
||||
criteria.getFilterCriteriaEntries().iterator());
|
||||
|
||||
if (!negationFilters.isEmpty()) {
|
||||
fbList.addAll(negationFilters);
|
||||
}
|
||||
filterBuilders.addAll(negationFilters);
|
||||
} else {
|
||||
fbList.addAll(createFilterFragmentForCriteria(chainedCriteria));
|
||||
filterBuilders.addAll(queriesForEntries(chainedCriteria));
|
||||
}
|
||||
}
|
||||
|
||||
if (!fbList.isEmpty()) {
|
||||
if (fbList.size() == 1) {
|
||||
filter = fbList.get(0);
|
||||
QueryBuilder filter = null;
|
||||
|
||||
if (!filterBuilders.isEmpty()) {
|
||||
|
||||
if (filterBuilders.size() == 1) {
|
||||
filter = filterBuilders.get(0);
|
||||
} else {
|
||||
filter = QueryBuilders.boolQuery();
|
||||
for (QueryBuilder f : fbList) {
|
||||
((BoolQueryBuilder) filter).must(f);
|
||||
}
|
||||
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
|
||||
filterBuilders.forEach(boolQuery::must);
|
||||
filter = boolQuery;
|
||||
}
|
||||
}
|
||||
|
||||
return filter;
|
||||
}
|
||||
|
||||
private List<QueryBuilder> createFilterFragmentForCriteria(Criteria chainedCriteria) {
|
||||
Iterator<Criteria.CriteriaEntry> it = chainedCriteria.getFilterCriteriaEntries().iterator();
|
||||
List<QueryBuilder> filterList = new LinkedList<>();
|
||||
private List<QueryBuilder> queriesForEntries(Criteria criteria) {
|
||||
|
||||
String fieldName = chainedCriteria.getField().getName();
|
||||
Assert.notNull(criteria.getField(), "criteria must have a field");
|
||||
String fieldName = criteria.getField().getName();
|
||||
Assert.notNull(fieldName, "Unknown field");
|
||||
QueryBuilder filter = null;
|
||||
|
||||
while (it.hasNext()) {
|
||||
Criteria.CriteriaEntry entry = it.next();
|
||||
filter = processCriteriaEntry(entry.getKey(), entry.getValue(), fieldName);
|
||||
filterList.add(filter);
|
||||
}
|
||||
|
||||
return filterList;
|
||||
return criteria.getFilterCriteriaEntries().stream()
|
||||
.map(entry -> queryFor(entry.getKey(), entry.getValue(), fieldName)).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private QueryBuilder processCriteriaEntry(OperationKey key, Object value, String fieldName) {
|
||||
private QueryBuilder queryFor(OperationKey key, Object value, String fieldName) {
|
||||
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
QueryBuilder filter = null;
|
||||
|
||||
switch (key) {
|
||||
case WITHIN: {
|
||||
GeoDistanceQueryBuilder geoDistanceQueryBuilder = QueryBuilders.geoDistanceQuery(fieldName);
|
||||
|
||||
case WITHIN:
|
||||
Assert.isTrue(value instanceof Object[], "Value of a geo distance filter should be an array of two values.");
|
||||
Object[] valArray = (Object[]) value;
|
||||
Assert.noNullElements(valArray, "Geo distance filter takes 2 not null elements array as parameter.");
|
||||
Assert.isTrue(valArray.length == 2, "Geo distance filter takes a 2-elements array as parameter.");
|
||||
Assert.isTrue(valArray[0] instanceof GeoPoint || valArray[0] instanceof String || valArray[0] instanceof Point,
|
||||
"First element of a geo distance filter must be a GeoPoint, a Point or a text");
|
||||
Assert.isTrue(valArray[1] instanceof String || valArray[1] instanceof Distance,
|
||||
"Second element of a geo distance filter must be a text or a Distance");
|
||||
|
||||
StringBuilder dist = new StringBuilder();
|
||||
|
||||
if (valArray[1] instanceof Distance) {
|
||||
extractDistanceString((Distance) valArray[1], dist);
|
||||
} else {
|
||||
dist.append((String) valArray[1]);
|
||||
}
|
||||
|
||||
if (valArray[0] instanceof GeoPoint) {
|
||||
GeoPoint loc = (GeoPoint) valArray[0];
|
||||
geoDistanceQueryBuilder.point(loc.getLat(), loc.getLon()).distance(dist.toString())
|
||||
.geoDistance(GeoDistance.PLANE);
|
||||
} else if (valArray[0] instanceof Point) {
|
||||
GeoPoint loc = GeoPoint.fromPoint((Point) valArray[0]);
|
||||
geoDistanceQueryBuilder.point(loc.getLat(), loc.getLon()).distance(dist.toString())
|
||||
.geoDistance(GeoDistance.PLANE);
|
||||
} else {
|
||||
String loc = (String) valArray[0];
|
||||
if (loc.contains(",")) {
|
||||
String[] c = loc.split(",");
|
||||
geoDistanceQueryBuilder.point(Double.parseDouble(c[0]), Double.parseDouble(c[1])).distance(dist.toString())
|
||||
.geoDistance(GeoDistance.PLANE);
|
||||
} else {
|
||||
geoDistanceQueryBuilder.geohash(loc).distance(dist.toString()).geoDistance(GeoDistance.PLANE);
|
||||
}
|
||||
}
|
||||
filter = geoDistanceQueryBuilder;
|
||||
|
||||
filter = withinQuery(fieldName, (Object[]) value);
|
||||
break;
|
||||
}
|
||||
|
||||
case BBOX: {
|
||||
filter = QueryBuilders.geoBoundingBoxQuery(fieldName);
|
||||
|
||||
case BBOX:
|
||||
Assert.isTrue(value instanceof Object[],
|
||||
"Value of a boundedBy filter should be an array of one or two values.");
|
||||
Object[] valArray = (Object[]) value;
|
||||
Assert.noNullElements(valArray, "Geo boundedBy filter takes a not null element array as parameter.");
|
||||
|
||||
if (valArray.length == 1) {
|
||||
// GeoEnvelop
|
||||
oneParameterBBox((GeoBoundingBoxQueryBuilder) filter, valArray[0]);
|
||||
} else if (valArray.length == 2) {
|
||||
// 2x GeoPoint
|
||||
// 2x text
|
||||
twoParameterBBox((GeoBoundingBoxQueryBuilder) filter, valArray);
|
||||
} else {
|
||||
// error
|
||||
Assert.isTrue(false,
|
||||
"Geo distance filter takes a 1-elements array(GeoBox) or 2-elements array(GeoPoints or Strings(format lat,lon or geohash)).");
|
||||
}
|
||||
filter = boundingBoxQuery(fieldName, (Object[]) value);
|
||||
break;
|
||||
case GEO_INTERSECTS:
|
||||
Assert.isTrue(value instanceof GeoJson<?>, "value of a GEO_INTERSECTS filter must be a GeoJson object");
|
||||
filter = geoJsonQuery(fieldName, (GeoJson<?>) value, "intersects");
|
||||
break;
|
||||
case GEO_IS_DISJOINT:
|
||||
Assert.isTrue(value instanceof GeoJson<?>, "value of a GEO_IS_DISJOINT filter must be a GeoJson object");
|
||||
filter = geoJsonQuery(fieldName, (GeoJson<?>) value, "disjoint");
|
||||
break;
|
||||
case GEO_WITHIN:
|
||||
Assert.isTrue(value instanceof GeoJson<?>, "value of a GEO_WITHIN filter must be a GeoJson object");
|
||||
filter = geoJsonQuery(fieldName, (GeoJson<?>) value, "within");
|
||||
break;
|
||||
case GEO_CONTAINS:
|
||||
Assert.isTrue(value instanceof GeoJson<?>, "value of a GEO_CONTAINS filter must be a GeoJson object");
|
||||
filter = geoJsonQuery(fieldName, (GeoJson<?>) value, "contains");
|
||||
break;
|
||||
}
|
||||
|
||||
return filter;
|
||||
}
|
||||
|
||||
private QueryBuilder withinQuery(String fieldName, Object[] valArray) {
|
||||
|
||||
GeoDistanceQueryBuilder filter = QueryBuilders.geoDistanceQuery(fieldName);
|
||||
|
||||
Assert.noNullElements(valArray, "Geo distance filter takes 2 not null elements array as parameter.");
|
||||
Assert.isTrue(valArray.length == 2, "Geo distance filter takes a 2-elements array as parameter.");
|
||||
Assert.isTrue(valArray[0] instanceof GeoPoint || valArray[0] instanceof String || valArray[0] instanceof Point,
|
||||
"First element of a geo distance filter must be a GeoPoint, a Point or a text");
|
||||
Assert.isTrue(valArray[1] instanceof String || valArray[1] instanceof Distance,
|
||||
"Second element of a geo distance filter must be a text or a Distance");
|
||||
|
||||
StringBuilder dist = new StringBuilder();
|
||||
|
||||
if (valArray[1] instanceof Distance) {
|
||||
extractDistanceString((Distance) valArray[1], dist);
|
||||
} else {
|
||||
dist.append((String) valArray[1]);
|
||||
}
|
||||
|
||||
if (valArray[0] instanceof GeoPoint) {
|
||||
GeoPoint loc = (GeoPoint) valArray[0];
|
||||
filter.point(loc.getLat(), loc.getLon()).distance(dist.toString()).geoDistance(GeoDistance.PLANE);
|
||||
} else if (valArray[0] instanceof Point) {
|
||||
GeoPoint loc = GeoPoint.fromPoint((Point) valArray[0]);
|
||||
filter.point(loc.getLat(), loc.getLon()).distance(dist.toString()).geoDistance(GeoDistance.PLANE);
|
||||
} else {
|
||||
String loc = (String) valArray[0];
|
||||
if (loc.contains(",")) {
|
||||
String[] c = loc.split(",");
|
||||
filter.point(Double.parseDouble(c[0]), Double.parseDouble(c[1])).distance(dist.toString())
|
||||
.geoDistance(GeoDistance.PLANE);
|
||||
} else {
|
||||
filter.geohash(loc).distance(dist.toString()).geoDistance(GeoDistance.PLANE);
|
||||
}
|
||||
}
|
||||
|
||||
return filter;
|
||||
}
|
||||
|
||||
private QueryBuilder boundingBoxQuery(String fieldName, Object[] valArray) {
|
||||
|
||||
Assert.noNullElements(valArray, "Geo boundedBy filter takes a not null element array as parameter.");
|
||||
|
||||
GeoBoundingBoxQueryBuilder filter = QueryBuilders.geoBoundingBoxQuery(fieldName);
|
||||
|
||||
if (valArray.length == 1) {
|
||||
// GeoEnvelop
|
||||
oneParameterBBox(filter, valArray[0]);
|
||||
} else if (valArray.length == 2) {
|
||||
// 2x GeoPoint
|
||||
// 2x text
|
||||
twoParameterBBox(filter, valArray);
|
||||
} else {
|
||||
throw new IllegalArgumentException(
|
||||
"Geo distance filter takes a 1-elements array(GeoBox) or 2-elements array(GeoPoints or Strings(format lat,lon or geohash)).");
|
||||
}
|
||||
|
||||
return filter;
|
||||
}
|
||||
|
||||
private QueryBuilder geoJsonQuery(String fieldName, GeoJson<?> geoJson, String relation) {
|
||||
return QueryBuilders.wrapperQuery(buildJsonQuery(fieldName, geoJson, relation));
|
||||
}
|
||||
|
||||
private String buildJsonQuery(String fieldName, GeoJson<?> geoJson, String relation) {
|
||||
return "{\"geo_shape\": {\"" + fieldName + "\": {\"shape\": " + geoJson.toJson() + ", \"relation\": \"" + relation
|
||||
+ "\"}}}";
|
||||
}
|
||||
|
||||
/**
|
||||
* extract the distance string from a {@link org.springframework.data.geo.Distance} object.
|
||||
*
|
||||
@@ -208,8 +230,7 @@ class CriteriaFilterProcessor {
|
||||
|
||||
GeoBox geoBBox;
|
||||
if (value instanceof Box) {
|
||||
Box sdbox = (Box) value;
|
||||
geoBBox = GeoBox.fromBox(sdbox);
|
||||
geoBBox = GeoBox.fromBox((Box) value);
|
||||
} else {
|
||||
geoBBox = (GeoBox) value;
|
||||
}
|
||||
@@ -218,7 +239,7 @@ class CriteriaFilterProcessor {
|
||||
geoBBox.getBottomRight().getLon());
|
||||
}
|
||||
|
||||
private static boolean isType(Object[] array, Class clazz) {
|
||||
private static boolean isType(Object[] array, Class<?> clazz) {
|
||||
for (Object o : array) {
|
||||
if (!clazz.isInstance(o)) {
|
||||
return false;
|
||||
@@ -247,7 +268,7 @@ class CriteriaFilterProcessor {
|
||||
while (it.hasNext()) {
|
||||
Criteria.CriteriaEntry criteriaEntry = it.next();
|
||||
QueryBuilder notFilter = QueryBuilders.boolQuery()
|
||||
.mustNot(processCriteriaEntry(criteriaEntry.getKey(), criteriaEntry.getValue(), fieldName));
|
||||
.mustNot(queryFor(criteriaEntry.getKey(), criteriaEntry.getValue(), fieldName));
|
||||
notFilterList.add(notFilter);
|
||||
}
|
||||
|
||||
|
||||
+109
-51
@@ -21,15 +21,14 @@ import static org.springframework.data.elasticsearch.core.query.Criteria.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
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.springframework.data.elasticsearch.annotations.FieldType;
|
||||
import org.springframework.data.elasticsearch.core.query.Criteria;
|
||||
import org.springframework.data.elasticsearch.core.query.Field;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@@ -46,63 +45,81 @@ import org.springframework.util.Assert;
|
||||
*/
|
||||
class CriteriaQueryProcessor {
|
||||
|
||||
QueryBuilder createQueryFromCriteria(Criteria criteria) {
|
||||
@Nullable
|
||||
QueryBuilder createQuery(Criteria criteria) {
|
||||
|
||||
Assert.notNull(criteria, "criteria must not be null");
|
||||
|
||||
List<QueryBuilder> shouldQueryBuilderList = new LinkedList<>();
|
||||
List<QueryBuilder> mustNotQueryBuilderList = new LinkedList<>();
|
||||
List<QueryBuilder> mustQueryBuilderList = new LinkedList<>();
|
||||
|
||||
ListIterator<Criteria> chainIterator = criteria.getCriteriaChain().listIterator();
|
||||
List<QueryBuilder> shouldQueryBuilders = new ArrayList<>();
|
||||
List<QueryBuilder> mustNotQueryBuilders = new ArrayList<>();
|
||||
List<QueryBuilder> mustQueryBuilders = new ArrayList<>();
|
||||
|
||||
QueryBuilder firstQuery = null;
|
||||
boolean negateFirstQuery = false;
|
||||
|
||||
while (chainIterator.hasNext()) {
|
||||
Criteria chainedCriteria = chainIterator.next();
|
||||
QueryBuilder queryFragmentForCriteria = createQueryFragmentForCriteria(chainedCriteria);
|
||||
if (queryFragmentForCriteria != null) {
|
||||
for (Criteria chainedCriteria : criteria.getCriteriaChain()) {
|
||||
QueryBuilder queryFragment = queryForEntries(chainedCriteria);
|
||||
|
||||
if (queryFragment != null) {
|
||||
|
||||
if (firstQuery == null) {
|
||||
firstQuery = queryFragmentForCriteria;
|
||||
firstQuery = queryFragment;
|
||||
negateFirstQuery = chainedCriteria.isNegating();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (chainedCriteria.isOr()) {
|
||||
shouldQueryBuilderList.add(queryFragmentForCriteria);
|
||||
shouldQueryBuilders.add(queryFragment);
|
||||
} else if (chainedCriteria.isNegating()) {
|
||||
mustNotQueryBuilderList.add(queryFragmentForCriteria);
|
||||
mustNotQueryBuilders.add(queryFragment);
|
||||
} else {
|
||||
mustQueryBuilderList.add(queryFragmentForCriteria);
|
||||
mustQueryBuilders.add(queryFragment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (Criteria subCriteria : criteria.getSubCriteria()) {
|
||||
|
||||
QueryBuilder subQuery = createQuery(subCriteria);
|
||||
|
||||
if (subQuery != null) {
|
||||
if (criteria.isOr()) {
|
||||
shouldQueryBuilders.add(subQuery);
|
||||
} else if (criteria.isNegating()) {
|
||||
mustNotQueryBuilders.add(subQuery);
|
||||
} else {
|
||||
mustQueryBuilders.add(subQuery);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (firstQuery != null) {
|
||||
if (!shouldQueryBuilderList.isEmpty() && mustNotQueryBuilderList.isEmpty() && mustQueryBuilderList.isEmpty()) {
|
||||
shouldQueryBuilderList.add(0, firstQuery);
|
||||
|
||||
if (!shouldQueryBuilders.isEmpty() && mustNotQueryBuilders.isEmpty() && mustQueryBuilders.isEmpty()) {
|
||||
shouldQueryBuilders.add(0, firstQuery);
|
||||
} else {
|
||||
|
||||
if (negateFirstQuery) {
|
||||
mustNotQueryBuilderList.add(0, firstQuery);
|
||||
mustNotQueryBuilders.add(0, firstQuery);
|
||||
} else {
|
||||
mustQueryBuilderList.add(0, firstQuery);
|
||||
mustQueryBuilders.add(0, firstQuery);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BoolQueryBuilder query = null;
|
||||
|
||||
if (!shouldQueryBuilderList.isEmpty() || !mustNotQueryBuilderList.isEmpty() || !mustQueryBuilderList.isEmpty()) {
|
||||
if (!shouldQueryBuilders.isEmpty() || !mustNotQueryBuilders.isEmpty() || !mustQueryBuilders.isEmpty()) {
|
||||
|
||||
query = boolQuery();
|
||||
|
||||
for (QueryBuilder qb : shouldQueryBuilderList) {
|
||||
for (QueryBuilder qb : shouldQueryBuilders) {
|
||||
query.should(qb);
|
||||
}
|
||||
for (QueryBuilder qb : mustNotQueryBuilderList) {
|
||||
for (QueryBuilder qb : mustNotQueryBuilders) {
|
||||
query.mustNot(qb);
|
||||
}
|
||||
for (QueryBuilder qb : mustQueryBuilderList) {
|
||||
for (QueryBuilder qb : mustQueryBuilders) {
|
||||
query.must(qb);
|
||||
}
|
||||
}
|
||||
@@ -111,46 +128,46 @@ class CriteriaQueryProcessor {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private QueryBuilder createQueryFragmentForCriteria(Criteria chainedCriteria) {
|
||||
if (chainedCriteria.getQueryCriteriaEntries().isEmpty())
|
||||
private QueryBuilder queryForEntries(Criteria criteria) {
|
||||
|
||||
Field field = criteria.getField();
|
||||
|
||||
if (field == null || criteria.getQueryCriteriaEntries().isEmpty())
|
||||
return null;
|
||||
|
||||
Iterator<Criteria.CriteriaEntry> it = chainedCriteria.getQueryCriteriaEntries().iterator();
|
||||
boolean singeEntryCriteria = (chainedCriteria.getQueryCriteriaEntries().size() == 1);
|
||||
|
||||
String fieldName = chainedCriteria.getField().getName();
|
||||
String fieldName = field.getName();
|
||||
Assert.notNull(fieldName, "Unknown field");
|
||||
QueryBuilder query = null;
|
||||
|
||||
if (singeEntryCriteria) {
|
||||
Criteria.CriteriaEntry entry = it.next();
|
||||
query = processCriteriaEntry(entry, fieldName);
|
||||
Iterator<Criteria.CriteriaEntry> it = criteria.getQueryCriteriaEntries().iterator();
|
||||
QueryBuilder query;
|
||||
|
||||
if (criteria.getQueryCriteriaEntries().size() == 1) {
|
||||
query = queryFor(it.next(), field);
|
||||
} else {
|
||||
query = boolQuery();
|
||||
while (it.hasNext()) {
|
||||
Criteria.CriteriaEntry entry = it.next();
|
||||
((BoolQueryBuilder) query).must(processCriteriaEntry(entry, fieldName));
|
||||
((BoolQueryBuilder) query).must(queryFor(entry, field));
|
||||
}
|
||||
}
|
||||
|
||||
addBoost(query, chainedCriteria.getBoost());
|
||||
addBoost(query, criteria.getBoost());
|
||||
return query;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private QueryBuilder processCriteriaEntry(Criteria.CriteriaEntry entry, String fieldName) {
|
||||
private QueryBuilder queryFor(Criteria.CriteriaEntry entry, Field field) {
|
||||
|
||||
String fieldName = field.getName();
|
||||
boolean isKeywordField = FieldType.Keyword == field.getFieldType();
|
||||
|
||||
OperationKey key = entry.getKey();
|
||||
Object value = entry.getValue();
|
||||
|
||||
if (value == null) {
|
||||
|
||||
if (key == OperationKey.EXISTS) {
|
||||
return existsQuery(fieldName);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
if (key == OperationKey.EXISTS) {
|
||||
return existsQuery(fieldName);
|
||||
}
|
||||
|
||||
Object value = entry.getValue();
|
||||
String searchText = QueryParserUtil.escape(value.toString());
|
||||
|
||||
QueryBuilder query = null;
|
||||
@@ -190,11 +207,31 @@ class CriteriaQueryProcessor {
|
||||
case FUZZY:
|
||||
query = fuzzyQuery(fieldName, searchText);
|
||||
break;
|
||||
case MATCHES:
|
||||
query = matchQuery(fieldName, value).operator(org.elasticsearch.index.query.Operator.OR);
|
||||
break;
|
||||
case MATCHES_ALL:
|
||||
query = matchQuery(fieldName, value).operator(org.elasticsearch.index.query.Operator.AND);
|
||||
break;
|
||||
case IN:
|
||||
query = boolQuery().must(termsQuery(fieldName, toStringList((Iterable<Object>) value)));
|
||||
if (value instanceof Iterable) {
|
||||
Iterable<?> iterable = (Iterable<?>) value;
|
||||
if (isKeywordField) {
|
||||
query = boolQuery().must(termsQuery(fieldName, toStringList(iterable)));
|
||||
} else {
|
||||
query = queryStringQuery(orQueryString(iterable)).field(fieldName);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case NOT_IN:
|
||||
query = boolQuery().mustNot(termsQuery(fieldName, toStringList((Iterable<Object>) value)));
|
||||
if (value instanceof Iterable) {
|
||||
Iterable<?> iterable = (Iterable<?>) value;
|
||||
if (isKeywordField) {
|
||||
query = boolQuery().mustNot(termsQuery(fieldName, toStringList(iterable)));
|
||||
} else {
|
||||
query = queryStringQuery("NOT(" + orQueryString(iterable) + ')').field(fieldName);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
return query;
|
||||
@@ -203,15 +240,36 @@ class CriteriaQueryProcessor {
|
||||
private static List<String> toStringList(Iterable<?> iterable) {
|
||||
List<String> list = new ArrayList<>();
|
||||
for (Object item : iterable) {
|
||||
list.add(StringUtils.toString(item));
|
||||
list.add(item != null ? item.toString() : null);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
private void addBoost(QueryBuilder query, float boost) {
|
||||
if (Float.isNaN(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();
|
||||
}
|
||||
|
||||
private void addBoost(@Nullable QueryBuilder query, float boost) {
|
||||
|
||||
if (query == null || Float.isNaN(boost)) {
|
||||
return;
|
||||
}
|
||||
|
||||
query.boost(boost);
|
||||
}
|
||||
}
|
||||
|
||||
+122
-93
@@ -15,40 +15,46 @@
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.core;
|
||||
|
||||
import static org.elasticsearch.client.Requests.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.http.util.EntityUtils;
|
||||
import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequest;
|
||||
import org.elasticsearch.action.admin.indices.alias.get.GetAliasesRequest;
|
||||
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
|
||||
import org.elasticsearch.action.admin.indices.refresh.RefreshRequest;
|
||||
import org.elasticsearch.action.admin.indices.settings.get.GetSettingsRequest;
|
||||
import org.elasticsearch.action.admin.indices.settings.get.GetSettingsResponse;
|
||||
import org.elasticsearch.client.Request;
|
||||
import org.elasticsearch.action.admin.indices.template.delete.DeleteIndexTemplateRequest;
|
||||
import org.elasticsearch.client.GetAliasesResponse;
|
||||
import org.elasticsearch.client.RequestOptions;
|
||||
import org.elasticsearch.client.Response;
|
||||
import org.elasticsearch.client.RestClient;
|
||||
import org.elasticsearch.client.indices.CreateIndexRequest;
|
||||
import org.elasticsearch.client.indices.GetIndexRequest;
|
||||
import org.elasticsearch.client.indices.GetIndexTemplatesRequest;
|
||||
import org.elasticsearch.client.indices.GetIndexTemplatesResponse;
|
||||
import org.elasticsearch.client.indices.GetMappingsRequest;
|
||||
import org.elasticsearch.client.indices.IndexTemplatesExistRequest;
|
||||
import org.elasticsearch.client.indices.PutIndexTemplateRequest;
|
||||
import org.elasticsearch.client.indices.PutMappingRequest;
|
||||
import org.elasticsearch.cluster.metadata.AliasMetaData;
|
||||
import org.springframework.data.elasticsearch.UncategorizedElasticsearchException;
|
||||
import org.springframework.data.elasticsearch.core.client.support.AliasData;
|
||||
import org.elasticsearch.cluster.metadata.AliasMetadata;
|
||||
import org.elasticsearch.cluster.metadata.MappingMetadata;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.data.elasticsearch.core.document.Document;
|
||||
import org.springframework.data.elasticsearch.core.index.AliasActions;
|
||||
import org.springframework.data.elasticsearch.core.index.AliasData;
|
||||
import org.springframework.data.elasticsearch.core.index.DeleteTemplateRequest;
|
||||
import org.springframework.data.elasticsearch.core.index.ExistsTemplateRequest;
|
||||
import org.springframework.data.elasticsearch.core.index.GetTemplateRequest;
|
||||
import org.springframework.data.elasticsearch.core.index.PutTemplateRequest;
|
||||
import org.springframework.data.elasticsearch.core.index.TemplateData;
|
||||
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
||||
import org.springframework.data.elasticsearch.core.query.AliasQuery;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
/**
|
||||
* {@link IndexOperations} implementation using the RestClient.
|
||||
*
|
||||
@@ -58,7 +64,9 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
*/
|
||||
class DefaultIndexOperations extends AbstractDefaultIndexOperations implements IndexOperations {
|
||||
|
||||
private ElasticsearchRestTemplate restTemplate;
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(DefaultIndexOperations.class);
|
||||
|
||||
private final ElasticsearchRestTemplate restTemplate;
|
||||
|
||||
public DefaultIndexOperations(ElasticsearchRestTemplate restTemplate, Class<?> boundClass) {
|
||||
super(restTemplate.getElasticsearchConverter(), boundClass);
|
||||
@@ -71,27 +79,29 @@ class DefaultIndexOperations extends AbstractDefaultIndexOperations implements I
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doCreate(String indexName, @Nullable Document settings) {
|
||||
CreateIndexRequest request = requestFactory.createIndexRequest(indexName, settings);
|
||||
protected boolean doCreate(IndexCoordinates index, @Nullable Document settings) {
|
||||
CreateIndexRequest request = requestFactory.createIndexRequest(index, settings);
|
||||
return restTemplate.execute(client -> client.indices().create(request, RequestOptions.DEFAULT).isAcknowledged());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doDelete(String indexName) {
|
||||
protected boolean doDelete(IndexCoordinates index) {
|
||||
|
||||
Assert.notNull(indexName, "No index defined for delete operation");
|
||||
Assert.notNull(index, "index must not be null");
|
||||
|
||||
if (doExists(indexName)) {
|
||||
DeleteIndexRequest request = new DeleteIndexRequest(indexName);
|
||||
return restTemplate.execute(client -> client.indices().delete(request, RequestOptions.DEFAULT).isAcknowledged());
|
||||
if (doExists(index)) {
|
||||
DeleteIndexRequest deleteIndexRequest = requestFactory.deleteIndexRequest(index);
|
||||
return restTemplate
|
||||
.execute(client -> client.indices().delete(deleteIndexRequest, RequestOptions.DEFAULT).isAcknowledged());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doExists(String indexName) {
|
||||
GetIndexRequest request = new GetIndexRequest(indexName);
|
||||
return restTemplate.execute(client -> client.indices().exists(request, RequestOptions.DEFAULT));
|
||||
protected boolean doExists(IndexCoordinates index) {
|
||||
|
||||
GetIndexRequest getIndexRequest = requestFactory.getIndexRequest(index);
|
||||
return restTemplate.execute(client -> client.indices().exists(getIndexRequest, RequestOptions.DEFAULT));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -109,16 +119,28 @@ class DefaultIndexOperations extends AbstractDefaultIndexOperations implements I
|
||||
|
||||
Assert.notNull(index, "No index defined for getMapping()");
|
||||
|
||||
GetMappingsRequest mappingsRequest = requestFactory.getMappingsRequest(index);
|
||||
|
||||
return restTemplate.execute(client -> {
|
||||
RestClient restClient = client.getLowLevelClient();
|
||||
Request request = new Request("GET", '/' + index.getIndexName() + "/_mapping");
|
||||
Response response = restClient.performRequest(request);
|
||||
return convertMappingResponse(EntityUtils.toString(response.getEntity()));
|
||||
Map<String, MappingMetadata> mappings = client.indices() //
|
||||
.getMapping(mappingsRequest, RequestOptions.DEFAULT) //
|
||||
.mappings(); //
|
||||
|
||||
if (mappings == null || mappings.size() == 0) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
if (mappings.size() > 1) {
|
||||
LOGGER.warn("more than one mapping returned for " + index.getIndexName());
|
||||
}
|
||||
// we have at least one, take the first from the iterator
|
||||
return mappings.entrySet().iterator().next().getValue().getSourceAsMap();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doAddAlias(AliasQuery query, IndexCoordinates index) {
|
||||
|
||||
IndicesAliasesRequest request = requestFactory.indicesAddAliasesRequest(query, index);
|
||||
return restTemplate
|
||||
.execute(client -> client.indices().updateAliases(request, RequestOptions.DEFAULT).isAcknowledged());
|
||||
@@ -136,34 +158,45 @@ class DefaultIndexOperations extends AbstractDefaultIndexOperations implements I
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<AliasMetaData> doQueryForAlias(String indexName) {
|
||||
List<AliasMetaData> aliases = null;
|
||||
protected List<AliasMetadata> doQueryForAlias(IndexCoordinates index) {
|
||||
|
||||
GetAliasesRequest getAliasesRequest = requestFactory.getAliasesRequest(index);
|
||||
|
||||
return restTemplate.execute(client -> {
|
||||
RestClient restClient = client.getLowLevelClient();
|
||||
Response response;
|
||||
String aliasResponse;
|
||||
|
||||
response = restClient.performRequest(new Request("GET", '/' + indexName + "/_alias/*"));
|
||||
aliasResponse = EntityUtils.toString(response.getEntity());
|
||||
|
||||
return convertAliasResponse(aliasResponse);
|
||||
GetAliasesResponse alias = client.indices().getAlias(getAliasesRequest, RequestOptions.DEFAULT);
|
||||
// we only return data for the first index name that was requested (always have done so)
|
||||
String index1 = getAliasesRequest.indices()[0];
|
||||
return new ArrayList<>(alias.getAliases().get(index1));
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Map<String, Object> doGetSettings(String indexName, boolean includeDefaults) {
|
||||
protected Map<String, Set<AliasData>> doGetAliases(@Nullable String[] aliasNames, @Nullable String[] indexNames) {
|
||||
|
||||
Assert.notNull(indexName, "No index defined for getSettings");
|
||||
GetAliasesRequest getAliasesRequest = requestFactory.getAliasesRequest(aliasNames, indexNames);
|
||||
|
||||
GetSettingsRequest request = new GetSettingsRequest() //
|
||||
.indices(indexName) //
|
||||
.includeDefaults(includeDefaults);
|
||||
return restTemplate.execute(client -> requestFactory
|
||||
.convertAliasesResponse(client.indices().getAlias(getAliasesRequest, RequestOptions.DEFAULT).getAliases()));
|
||||
}
|
||||
|
||||
//
|
||||
@Override
|
||||
public boolean alias(AliasActions aliasActions) {
|
||||
|
||||
IndicesAliasesRequest request = requestFactory.indicesAliasesRequest(aliasActions);
|
||||
return restTemplate
|
||||
.execute(client -> client.indices().updateAliases(request, RequestOptions.DEFAULT).isAcknowledged());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Map<String, Object> doGetSettings(IndexCoordinates index, boolean includeDefaults) {
|
||||
|
||||
Assert.notNull(index, "index must not be null");
|
||||
|
||||
GetSettingsRequest getSettingsRequest = requestFactory.getSettingsRequest(index, includeDefaults);
|
||||
GetSettingsResponse response = restTemplate.execute(client -> client.indices() //
|
||||
.getSettings(request, RequestOptions.DEFAULT));
|
||||
.getSettings(getSettingsRequest, RequestOptions.DEFAULT));
|
||||
|
||||
return convertSettingsResponseToMap(response, indexName);
|
||||
return requestFactory.fromSettingsResponse(response, getSettingsRequest.indices()[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -171,61 +204,57 @@ class DefaultIndexOperations extends AbstractDefaultIndexOperations implements I
|
||||
|
||||
Assert.notNull(index, "No index defined for refresh()");
|
||||
|
||||
restTemplate
|
||||
.execute(client -> client.indices().refresh(refreshRequest(index.getIndexNames()), RequestOptions.DEFAULT));
|
||||
RefreshRequest refreshRequest = requestFactory.refreshRequest(index);
|
||||
restTemplate.execute(client -> client.indices().refresh(refreshRequest, RequestOptions.DEFAULT));
|
||||
}
|
||||
|
||||
// region Helper methods
|
||||
private Map<String, Object> convertMappingResponse(String mappingResponse) {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
@Override
|
||||
public boolean putTemplate(PutTemplateRequest putTemplateRequest) {
|
||||
|
||||
try {
|
||||
Map<String, Object> result = null;
|
||||
JsonNode node = mapper.readTree(mappingResponse);
|
||||
Assert.notNull(putTemplateRequest, "putTemplateRequest must not be null");
|
||||
|
||||
node = node.findValue("mappings");
|
||||
result = mapper.readValue(mapper.writeValueAsString(node), HashMap.class);
|
||||
PutIndexTemplateRequest putIndexTemplateRequest = requestFactory.putIndexTemplateRequest(putTemplateRequest);
|
||||
return restTemplate.execute(
|
||||
client -> client.indices().putTemplate(putIndexTemplateRequest, RequestOptions.DEFAULT).isAcknowledged());
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (IOException e) {
|
||||
throw new UncategorizedElasticsearchException("Could not map alias response : " + mappingResponse, e);
|
||||
@Override
|
||||
public TemplateData getTemplate(GetTemplateRequest getTemplateRequest) {
|
||||
|
||||
Assert.notNull(getTemplateRequest, "getTemplateRequest must not be null");
|
||||
|
||||
// getIndexTemplate throws an error on non-existing template names
|
||||
if (!existsTemplate(new ExistsTemplateRequest(getTemplateRequest.getTemplateName()))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
GetIndexTemplatesRequest getIndexTemplatesRequest = requestFactory.getIndexTemplatesRequest(getTemplateRequest);
|
||||
GetIndexTemplatesResponse getIndexTemplatesResponse = restTemplate
|
||||
.execute(client -> client.indices().getIndexTemplate(getIndexTemplatesRequest, RequestOptions.DEFAULT));
|
||||
return requestFactory.getTemplateData(getIndexTemplatesResponse, getTemplateRequest.getTemplateName());
|
||||
}
|
||||
|
||||
/**
|
||||
* It takes two steps to create a List<AliasMetadata> from the elasticsearch http response because the aliases field
|
||||
* is actually a Map by alias name, but the alias name is on the AliasMetadata.
|
||||
*
|
||||
* @param aliasResponse
|
||||
* @return
|
||||
*/
|
||||
private List<AliasMetaData> convertAliasResponse(String aliasResponse) {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
@Override
|
||||
public boolean existsTemplate(ExistsTemplateRequest existsTemplateRequest) {
|
||||
|
||||
try {
|
||||
JsonNode node = mapper.readTree(aliasResponse);
|
||||
node = node.findValue("aliases");
|
||||
Assert.notNull(existsTemplateRequest, "existsTemplateRequest must not be null");
|
||||
|
||||
if (node == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
Map<String, AliasData> aliasData = mapper.readValue(mapper.writeValueAsString(node),
|
||||
new TypeReference<Map<String, AliasData>>() {});
|
||||
|
||||
Iterable<Map.Entry<String, AliasData>> aliasIter = aliasData.entrySet();
|
||||
List<AliasMetaData> aliasMetaDataList = new ArrayList<>();
|
||||
|
||||
for (Map.Entry<String, AliasData> aliasentry : aliasIter) {
|
||||
AliasData data = aliasentry.getValue();
|
||||
aliasMetaDataList.add(AliasMetaData.newAliasMetaDataBuilder(aliasentry.getKey()).filter(data.getFilter())
|
||||
.routing(data.getRouting()).searchRouting(data.getSearch_routing()).indexRouting(data.getIndex_routing())
|
||||
.build());
|
||||
}
|
||||
return aliasMetaDataList;
|
||||
} catch (IOException e) {
|
||||
throw new UncategorizedElasticsearchException("Could not map alias response : " + aliasResponse, e);
|
||||
}
|
||||
IndexTemplatesExistRequest putIndexTemplateRequest = requestFactory
|
||||
.indexTemplatesExistsRequest(existsTemplateRequest);
|
||||
return restTemplate
|
||||
.execute(client -> client.indices().existsTemplate(putIndexTemplateRequest, RequestOptions.DEFAULT));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean deleteTemplate(DeleteTemplateRequest deleteTemplateRequest) {
|
||||
|
||||
Assert.notNull(deleteTemplateRequest, "deleteTemplateRequest must not be null");
|
||||
|
||||
DeleteIndexTemplateRequest deleteIndexTemplateRequest = requestFactory
|
||||
.deleteIndexTemplateRequest(deleteTemplateRequest);
|
||||
return restTemplate.execute(
|
||||
client -> client.indices().deleteTemplate(deleteIndexTemplateRequest, RequestOptions.DEFAULT).isAcknowledged());
|
||||
}
|
||||
|
||||
// endregion
|
||||
}
|
||||
|
||||
+339
@@ -0,0 +1,339 @@
|
||||
/*
|
||||
* Copyright 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.client.Requests.*;
|
||||
import static org.springframework.util.StringUtils.*;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequest;
|
||||
import org.elasticsearch.action.admin.indices.alias.get.GetAliasesRequest;
|
||||
import org.elasticsearch.action.admin.indices.create.CreateIndexRequest;
|
||||
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
|
||||
import org.elasticsearch.action.admin.indices.get.GetIndexRequest;
|
||||
import org.elasticsearch.action.admin.indices.mapping.get.GetMappingsRequest;
|
||||
import org.elasticsearch.action.admin.indices.settings.get.GetSettingsRequest;
|
||||
import org.elasticsearch.action.admin.indices.template.delete.DeleteIndexTemplateRequest;
|
||||
import org.elasticsearch.client.GetAliasesResponse;
|
||||
import org.elasticsearch.client.indices.GetIndexTemplatesRequest;
|
||||
import org.elasticsearch.client.indices.IndexTemplatesExistRequest;
|
||||
import org.elasticsearch.client.indices.PutIndexTemplateRequest;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
import org.springframework.data.elasticsearch.NoSuchIndexException;
|
||||
import org.springframework.data.elasticsearch.annotations.Mapping;
|
||||
import org.springframework.data.elasticsearch.annotations.Setting;
|
||||
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
|
||||
import org.springframework.data.elasticsearch.core.document.Document;
|
||||
import org.springframework.data.elasticsearch.core.index.AliasActions;
|
||||
import org.springframework.data.elasticsearch.core.index.AliasData;
|
||||
import org.springframework.data.elasticsearch.core.index.DeleteTemplateRequest;
|
||||
import org.springframework.data.elasticsearch.core.index.ExistsTemplateRequest;
|
||||
import org.springframework.data.elasticsearch.core.index.GetTemplateRequest;
|
||||
import org.springframework.data.elasticsearch.core.index.MappingBuilder;
|
||||
import org.springframework.data.elasticsearch.core.index.PutTemplateRequest;
|
||||
import org.springframework.data.elasticsearch.core.index.TemplateData;
|
||||
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
|
||||
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* @author Peter-Josef Meisch
|
||||
* @since 4.1
|
||||
*/
|
||||
class DefaultReactiveIndexOperations implements ReactiveIndexOperations {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(DefaultReactiveIndexOperations.class);
|
||||
|
||||
@Nullable private final Class<?> boundClass;
|
||||
private final IndexCoordinates boundIndex;
|
||||
private final RequestFactory requestFactory;
|
||||
private final ReactiveElasticsearchOperations operations;
|
||||
private final ElasticsearchConverter converter;
|
||||
|
||||
public DefaultReactiveIndexOperations(ReactiveElasticsearchOperations operations, IndexCoordinates index) {
|
||||
|
||||
Assert.notNull(operations, "operations must not be null");
|
||||
Assert.notNull(index, "index must not be null");
|
||||
|
||||
this.operations = operations;
|
||||
this.converter = operations.getElasticsearchConverter();
|
||||
this.requestFactory = new RequestFactory(operations.getElasticsearchConverter());
|
||||
this.boundClass = null;
|
||||
this.boundIndex = index;
|
||||
}
|
||||
|
||||
public DefaultReactiveIndexOperations(ReactiveElasticsearchOperations operations, Class<?> clazz) {
|
||||
|
||||
Assert.notNull(operations, "operations must not be null");
|
||||
Assert.notNull(clazz, "clazz must not be null");
|
||||
|
||||
this.operations = operations;
|
||||
this.converter = operations.getElasticsearchConverter();
|
||||
this.requestFactory = new RequestFactory(operations.getElasticsearchConverter());
|
||||
this.boundClass = clazz;
|
||||
this.boundIndex = getIndexCoordinatesFor(clazz);
|
||||
}
|
||||
|
||||
// region index management
|
||||
@Override
|
||||
public Mono<Boolean> create() {
|
||||
|
||||
String indexName = getIndexCoordinates().getIndexName();
|
||||
|
||||
if (boundClass != null) {
|
||||
return createSettings(boundClass).flatMap(settings -> doCreate(indexName, settings));
|
||||
} else {
|
||||
return doCreate(indexName, null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Boolean> create(Document settings) {
|
||||
return doCreate(getIndexCoordinates().getIndexName(), settings);
|
||||
}
|
||||
|
||||
private Mono<Boolean> doCreate(String indexName, @Nullable Document settings) {
|
||||
|
||||
CreateIndexRequest request = requestFactory.createIndexRequestReactive(indexName, settings);
|
||||
return Mono.from(operations.executeWithIndicesClient(client -> client.createIndex(request)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Boolean> delete() {
|
||||
|
||||
return exists() //
|
||||
.flatMap(exists -> {
|
||||
|
||||
if (exists) {
|
||||
DeleteIndexRequest request = requestFactory.deleteIndexRequest(getIndexCoordinates());
|
||||
return Mono.from(operations.executeWithIndicesClient(client -> client.deleteIndex(request)))
|
||||
.onErrorResume(NoSuchIndexException.class, e -> Mono.just(false));
|
||||
} else {
|
||||
return Mono.just(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Boolean> exists() {
|
||||
|
||||
GetIndexRequest request = requestFactory.getIndexRequestReactive(getIndexCoordinates().getIndexName());
|
||||
return Mono.from(operations.executeWithIndicesClient(client -> client.existsIndex(request)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> refresh() {
|
||||
return Mono.from(operations.executeWithIndicesClient(
|
||||
client -> client.refreshIndex(refreshRequest(getIndexCoordinates().getIndexNames()))));
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region mappings
|
||||
@Override
|
||||
public Mono<Document> createMapping() {
|
||||
return createMapping(checkForBoundClass());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Document> createMapping(Class<?> clazz) {
|
||||
|
||||
if (clazz.isAnnotationPresent(Mapping.class)) {
|
||||
String mappingPath = clazz.getAnnotation(Mapping.class).mappingPath();
|
||||
return loadDocument(mappingPath, "@Mapping");
|
||||
}
|
||||
|
||||
String mapping = new MappingBuilder(converter).buildPropertyMapping(clazz);
|
||||
return Mono.just(Document.parse(mapping));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Boolean> putMapping(Mono<Document> mapping) {
|
||||
return mapping.map(document -> requestFactory.putMappingRequestReactive(getIndexCoordinates(), document)) //
|
||||
.flatMap(request -> Mono.from(operations.executeWithIndicesClient(client -> client.putMapping(request))));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Document> getMapping() {
|
||||
|
||||
IndexCoordinates indexCoordinates = getIndexCoordinates();
|
||||
GetMappingsRequest request = requestFactory.getMappingRequestReactive(indexCoordinates);
|
||||
|
||||
return Mono.from(operations.executeWithIndicesClient(client -> client.getMapping(request)))
|
||||
.flatMap(getMappingsResponse -> {
|
||||
Document document = Document.create();
|
||||
document.put("properties",
|
||||
getMappingsResponse.mappings().get(indexCoordinates.getIndexName()).get("properties").getSourceAsMap());
|
||||
return Mono.just(document);
|
||||
});
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region settings
|
||||
|
||||
@Override
|
||||
public Mono<Document> createSettings() {
|
||||
return createSettings(checkForBoundClass());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Document> createSettings(Class<?> clazz) {
|
||||
|
||||
if (clazz.isAnnotationPresent(Setting.class)) {
|
||||
String settingPath = clazz.getAnnotation(Setting.class).settingPath();
|
||||
|
||||
return loadDocument(settingPath, "@Setting");
|
||||
}
|
||||
|
||||
return Mono.just(getRequiredPersistentEntity(clazz).getDefaultSettings());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Document> getSettings(boolean includeDefaults) {
|
||||
|
||||
String indexName = getIndexCoordinates().getIndexName();
|
||||
GetSettingsRequest request = requestFactory.getSettingsRequest(indexName, includeDefaults);
|
||||
|
||||
return Mono.from(operations.executeWithIndicesClient(client -> client.getSettings(request)))
|
||||
.map(getSettingsResponse -> requestFactory.fromSettingsResponse(getSettingsResponse, indexName));
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region aliases
|
||||
@Override
|
||||
public Mono<Boolean> alias(AliasActions aliasActions) {
|
||||
|
||||
IndicesAliasesRequest request = requestFactory.indicesAliasesRequest(aliasActions);
|
||||
return Mono.from(operations.executeWithIndicesClient(client -> client.updateAliases(request)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Map<String, Set<AliasData>>> getAliases(String... aliasNames) {
|
||||
return getAliases(aliasNames, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Map<String, Set<AliasData>>> getAliasesForIndex(String... indexNames) {
|
||||
return getAliases(null, indexNames);
|
||||
}
|
||||
|
||||
private Mono<Map<String, Set<AliasData>>> getAliases(@Nullable String[] aliasNames, @Nullable String[] indexNames) {
|
||||
|
||||
GetAliasesRequest getAliasesRequest = requestFactory.getAliasesRequest(aliasNames, indexNames);
|
||||
return Mono.from(operations.executeWithIndicesClient(client -> client.getAliases(getAliasesRequest)))
|
||||
.map(GetAliasesResponse::getAliases).map(requestFactory::convertAliasesResponse);
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region templates
|
||||
@Override
|
||||
public Mono<Boolean> putTemplate(PutTemplateRequest putTemplateRequest) {
|
||||
|
||||
Assert.notNull(putTemplateRequest, "putTemplateRequest must not be null");
|
||||
|
||||
PutIndexTemplateRequest putIndexTemplateRequest = requestFactory.putIndexTemplateRequest(putTemplateRequest);
|
||||
return Mono.from(operations.executeWithIndicesClient(client -> client.putTemplate(putIndexTemplateRequest)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<TemplateData> getTemplate(GetTemplateRequest getTemplateRequest) {
|
||||
|
||||
Assert.notNull(getTemplateRequest, "getTemplateRequest must not be null");
|
||||
|
||||
GetIndexTemplatesRequest getIndexTemplatesRequest = requestFactory.getIndexTemplatesRequest(getTemplateRequest);
|
||||
return Mono.from(operations.executeWithIndicesClient(client -> client.getTemplate(getIndexTemplatesRequest)))
|
||||
.flatMap(response -> {
|
||||
if (response != null) {
|
||||
TemplateData templateData = requestFactory.getTemplateData(response, getTemplateRequest.getTemplateName());
|
||||
if (templateData != null) {
|
||||
return Mono.just(templateData);
|
||||
}
|
||||
}
|
||||
return Mono.empty();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Boolean> existsTemplate(ExistsTemplateRequest existsTemplateRequest) {
|
||||
|
||||
Assert.notNull(existsTemplateRequest, "existsTemplateRequest must not be null");
|
||||
|
||||
IndexTemplatesExistRequest indexTemplatesExistRequest = requestFactory
|
||||
.indexTemplatesExistsRequest(existsTemplateRequest);
|
||||
return Mono.from(operations.executeWithIndicesClient(client -> client.existsTemplate(indexTemplatesExistRequest)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Boolean> deleteTemplate(DeleteTemplateRequest deleteTemplateRequest) {
|
||||
|
||||
Assert.notNull(deleteTemplateRequest, "deleteTemplateRequest must not be null");
|
||||
|
||||
DeleteIndexTemplateRequest deleteIndexTemplateRequest = requestFactory
|
||||
.deleteIndexTemplateRequest(deleteTemplateRequest);
|
||||
return Mono.from(operations.executeWithIndicesClient(client -> client.deleteTemplate(deleteIndexTemplateRequest)));
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region helper functions
|
||||
@Override
|
||||
public IndexCoordinates getIndexCoordinates() {
|
||||
return (boundClass != null) ? getIndexCoordinatesFor(boundClass) : boundIndex;
|
||||
}
|
||||
|
||||
private IndexCoordinates getIndexCoordinatesFor(Class<?> clazz) {
|
||||
return operations.getElasticsearchConverter().getMappingContext().getRequiredPersistentEntity(clazz)
|
||||
.getIndexCoordinates();
|
||||
}
|
||||
|
||||
private ElasticsearchPersistentEntity<?> getRequiredPersistentEntity(Class<?> clazz) {
|
||||
return converter.getMappingContext().getRequiredPersistentEntity(clazz);
|
||||
}
|
||||
|
||||
private Mono<Document> loadDocument(String path, String annotation) {
|
||||
|
||||
if (hasText(path)) {
|
||||
return ReactiveResourceUtil.readFileFromClasspath(path).flatMap(s -> {
|
||||
if (hasText(s)) {
|
||||
return Mono.just(Document.parse(s));
|
||||
} else {
|
||||
return Mono.just(Document.create());
|
||||
}
|
||||
});
|
||||
} else {
|
||||
LOGGER.info("path in {} has to be defined. Using default instead.", annotation);
|
||||
}
|
||||
|
||||
return Mono.just(Document.create());
|
||||
}
|
||||
|
||||
private Class<?> checkForBoundClass() {
|
||||
if (boundClass == null) {
|
||||
throw new InvalidDataAccessApiUsageException("IndexOperations are not bound");
|
||||
}
|
||||
return boundClass;
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
}
|
||||
+174
-33
@@ -15,24 +15,47 @@
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.core;
|
||||
|
||||
import static org.elasticsearch.client.Requests.*;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequest;
|
||||
import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequestBuilder;
|
||||
import org.elasticsearch.action.admin.indices.alias.get.GetAliasesRequest;
|
||||
import org.elasticsearch.action.admin.indices.create.CreateIndexRequestBuilder;
|
||||
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
|
||||
import org.elasticsearch.action.admin.indices.exists.indices.IndicesExistsRequest;
|
||||
import org.elasticsearch.action.admin.indices.mapping.get.GetMappingsRequest;
|
||||
import org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequestBuilder;
|
||||
import org.elasticsearch.action.admin.indices.refresh.RefreshRequest;
|
||||
import org.elasticsearch.action.admin.indices.settings.get.GetSettingsRequest;
|
||||
import org.elasticsearch.action.admin.indices.settings.get.GetSettingsResponse;
|
||||
import org.elasticsearch.action.admin.indices.template.delete.DeleteIndexTemplateRequest;
|
||||
import org.elasticsearch.action.admin.indices.template.get.GetIndexTemplatesRequest;
|
||||
import org.elasticsearch.action.admin.indices.template.get.GetIndexTemplatesResponse;
|
||||
import org.elasticsearch.action.admin.indices.template.put.PutIndexTemplateRequest;
|
||||
import org.elasticsearch.client.Client;
|
||||
import org.elasticsearch.cluster.metadata.AliasMetaData;
|
||||
import org.springframework.data.elasticsearch.ElasticsearchException;
|
||||
import org.elasticsearch.cluster.metadata.AliasMetadata;
|
||||
import org.elasticsearch.cluster.metadata.IndexTemplateMetadata;
|
||||
import org.elasticsearch.cluster.metadata.MappingMetadata;
|
||||
import org.elasticsearch.common.collect.ImmutableOpenMap;
|
||||
import org.elasticsearch.common.compress.CompressedXContent;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
|
||||
import org.springframework.data.elasticsearch.core.document.Document;
|
||||
import org.springframework.data.elasticsearch.core.index.AliasActions;
|
||||
import org.springframework.data.elasticsearch.core.index.AliasData;
|
||||
import org.springframework.data.elasticsearch.core.index.DeleteTemplateRequest;
|
||||
import org.springframework.data.elasticsearch.core.index.ExistsTemplateRequest;
|
||||
import org.springframework.data.elasticsearch.core.index.GetTemplateRequest;
|
||||
import org.springframework.data.elasticsearch.core.index.PutTemplateRequest;
|
||||
import org.springframework.data.elasticsearch.core.index.TemplateData;
|
||||
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
||||
import org.springframework.data.elasticsearch.core.query.AliasQuery;
|
||||
import org.springframework.lang.Nullable;
|
||||
@@ -47,6 +70,8 @@ import org.springframework.util.Assert;
|
||||
*/
|
||||
class DefaultTransportIndexOperations extends AbstractDefaultIndexOperations implements IndexOperations {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(DefaultTransportIndexOperations.class);
|
||||
|
||||
private final Client client;
|
||||
|
||||
public DefaultTransportIndexOperations(Client client, ElasticsearchConverter elasticsearchConverter,
|
||||
@@ -62,26 +87,29 @@ class DefaultTransportIndexOperations extends AbstractDefaultIndexOperations imp
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doCreate(String indexName, @Nullable Document settings) {
|
||||
CreateIndexRequestBuilder createIndexRequestBuilder = requestFactory.createIndexRequestBuilder(client, indexName,
|
||||
protected boolean doCreate(IndexCoordinates index, @Nullable Document settings) {
|
||||
CreateIndexRequestBuilder createIndexRequestBuilder = requestFactory.createIndexRequestBuilder(client, index,
|
||||
settings);
|
||||
return createIndexRequestBuilder.execute().actionGet().isAcknowledged();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doDelete(String indexName) {
|
||||
protected boolean doDelete(IndexCoordinates index) {
|
||||
|
||||
Assert.notNull(indexName, "No index defined for delete operation");
|
||||
Assert.notNull(index, "No index defined for delete operation");
|
||||
|
||||
if (doExists(indexName)) {
|
||||
return client.admin().indices().delete(new DeleteIndexRequest(indexName)).actionGet().isAcknowledged();
|
||||
if (doExists(index)) {
|
||||
DeleteIndexRequest deleteIndexRequest = requestFactory.deleteIndexRequest(index);
|
||||
return client.admin().indices().delete(deleteIndexRequest).actionGet().isAcknowledged();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doExists(String indexName) {
|
||||
return client.admin().indices().exists(indicesExistsRequest(indexName)).actionGet().isExists();
|
||||
protected boolean doExists(IndexCoordinates index) {
|
||||
|
||||
IndicesExistsRequest indicesExistsRequest = requestFactory.indicesExistsRequest(index);
|
||||
return client.admin().indices().exists(indicesExistsRequest).actionGet().isExists();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -98,14 +126,21 @@ class DefaultTransportIndexOperations extends AbstractDefaultIndexOperations imp
|
||||
|
||||
Assert.notNull(index, "No index defined for getMapping()");
|
||||
|
||||
try {
|
||||
return client.admin().indices().getMappings( //
|
||||
new GetMappingsRequest().indices(index.getIndexNames())).actionGet() //
|
||||
.getMappings().get(index.getIndexName()).get(IndexCoordinates.TYPE) //
|
||||
.getSourceAsMap();
|
||||
} catch (Exception e) {
|
||||
throw new ElasticsearchException("Error while getting mapping for indexName : " + index.getIndexName(), e);
|
||||
GetMappingsRequest mappingsRequest = requestFactory.getMappingsRequest(client, index);
|
||||
|
||||
ImmutableOpenMap<String, ImmutableOpenMap<String, MappingMetadata>> mappings = client.admin().indices().getMappings( //
|
||||
mappingsRequest).actionGet() //
|
||||
.getMappings();
|
||||
|
||||
if (mappings == null || mappings.size() == 0) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
if (mappings.size() > 1) {
|
||||
LOGGER.warn("more than one mapping returned for " + index.getIndexName());
|
||||
}
|
||||
// we have at least one, take the first from the iterator
|
||||
return mappings.iterator().next().value.get(IndexCoordinates.TYPE).getSourceAsMap();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -120,39 +155,145 @@ class DefaultTransportIndexOperations extends AbstractDefaultIndexOperations imp
|
||||
Assert.notNull(index, "No index defined for Alias");
|
||||
Assert.notNull(query.getAliasName(), "No alias defined");
|
||||
|
||||
return client.admin().indices().prepareAliases().removeAlias(index.getIndexName(), query.getAliasName()).execute()
|
||||
.actionGet().isAcknowledged();
|
||||
IndicesAliasesRequestBuilder indicesAliasesRequestBuilder = requestFactory
|
||||
.indicesRemoveAliasesRequestBuilder(client, query, index);
|
||||
return indicesAliasesRequestBuilder.execute().actionGet().isAcknowledged();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<AliasMetaData> doQueryForAlias(String indexName) {
|
||||
return client.admin().indices().getAliases(new GetAliasesRequest().indices(indexName)).actionGet().getAliases()
|
||||
.get(indexName);
|
||||
protected List<AliasMetadata> doQueryForAlias(IndexCoordinates index) {
|
||||
|
||||
GetAliasesRequest getAliasesRequest = requestFactory.getAliasesRequest(index);
|
||||
return client.admin().indices().getAliases(getAliasesRequest).actionGet().getAliases().get(index.getIndexName());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Map<String, Object> doGetSettings(String indexName, boolean includeDefaults) {
|
||||
protected Map<String, Set<AliasData>> doGetAliases(@Nullable String[] aliasNames, @Nullable String[] indexNames) {
|
||||
|
||||
Assert.notNull(indexName, "No index defined for getSettings");
|
||||
GetAliasesRequest getAliasesRequest = requestFactory.getAliasesRequest(aliasNames, indexNames);
|
||||
|
||||
GetSettingsRequest request = new GetSettingsRequest() //
|
||||
.indices(indexName) //
|
||||
.includeDefaults(includeDefaults);
|
||||
ImmutableOpenMap<String, List<AliasMetadata>> aliases = client.admin().indices().getAliases(getAliasesRequest)
|
||||
.actionGet().getAliases();
|
||||
|
||||
Map<String, Set<AliasMetadata>> aliasesResponse = new LinkedHashMap<>();
|
||||
aliases.keysIt().forEachRemaining(index -> aliasesResponse.put(index, new HashSet<>(aliases.get(index))));
|
||||
return requestFactory.convertAliasesResponse(aliasesResponse);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean alias(AliasActions aliasActions) {
|
||||
|
||||
IndicesAliasesRequestBuilder indicesAliasesRequestBuilder = requestFactory.indicesAliasesRequestBuilder(client,
|
||||
aliasActions);
|
||||
return indicesAliasesRequestBuilder.execute().actionGet().isAcknowledged();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Map<String, Object> doGetSettings(IndexCoordinates index, boolean includeDefaults) {
|
||||
|
||||
Assert.notNull(index, "index must not be null");
|
||||
|
||||
GetSettingsRequest getSettingsRequest = requestFactory.getSettingsRequest(index, includeDefaults);
|
||||
GetSettingsResponse response = client.admin() //
|
||||
.indices() //
|
||||
.getSettings(request) //
|
||||
.getSettings(getSettingsRequest) //
|
||||
.actionGet();
|
||||
|
||||
return convertSettingsResponseToMap(response, indexName);
|
||||
return requestFactory.fromSettingsResponse(response, index.getIndexName());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doRefresh(IndexCoordinates index) {
|
||||
|
||||
Assert.notNull(index, "No index defined for refresh()");
|
||||
Assert.notNull(index, "index must not be null");
|
||||
|
||||
client.admin().indices().refresh(refreshRequest(index.getIndexNames())).actionGet();
|
||||
RefreshRequest request = requestFactory.refreshRequest(index);
|
||||
client.admin().indices().refresh(request).actionGet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean putTemplate(PutTemplateRequest putTemplateRequest) {
|
||||
|
||||
Assert.notNull(putTemplateRequest, "putTemplateRequest must not be null");
|
||||
|
||||
PutIndexTemplateRequest putIndexTemplateRequest = requestFactory.putIndexTemplateRequest(client,
|
||||
putTemplateRequest);
|
||||
return client.admin().indices().putTemplate(putIndexTemplateRequest).actionGet().isAcknowledged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TemplateData getTemplate(GetTemplateRequest getTemplateRequest) {
|
||||
|
||||
Assert.notNull(getTemplateRequest, "getTemplateRequest must not be null");
|
||||
|
||||
GetIndexTemplatesRequest getIndexTemplatesRequest = requestFactory.getIndexTemplatesRequest(client,
|
||||
getTemplateRequest);
|
||||
GetIndexTemplatesResponse getIndexTemplatesResponse = client.admin().indices()
|
||||
.getTemplates(getIndexTemplatesRequest).actionGet();
|
||||
for (IndexTemplateMetadata indexTemplateMetadata : getIndexTemplatesResponse.getIndexTemplates()) {
|
||||
|
||||
if (indexTemplateMetadata.getName().equals(getTemplateRequest.getTemplateName())) {
|
||||
|
||||
Document settings = Document.create();
|
||||
Settings templateSettings = indexTemplateMetadata.settings();
|
||||
templateSettings.keySet().forEach(key -> settings.put(key, templateSettings.get(key)));
|
||||
|
||||
Map<String, AliasData> aliases = new LinkedHashMap<>();
|
||||
ImmutableOpenMap<String, AliasMetadata> aliasesResponse = indexTemplateMetadata.aliases();
|
||||
Iterator<String> keysItAliases = aliasesResponse.keysIt();
|
||||
while (keysItAliases.hasNext()) {
|
||||
String key = keysItAliases.next();
|
||||
aliases.put(key, requestFactory.convertAliasMetadata(aliasesResponse.get(key)));
|
||||
}
|
||||
|
||||
Map<String, String> mappingsDoc = new LinkedHashMap<>();
|
||||
ImmutableOpenMap<String, CompressedXContent> mappingsResponse = indexTemplateMetadata.mappings();
|
||||
Iterator<String> keysItMappings = mappingsResponse.keysIt();
|
||||
while (keysItMappings.hasNext()) {
|
||||
String key = keysItMappings.next();
|
||||
mappingsDoc.put(key, mappingsResponse.get(key).string());
|
||||
}
|
||||
String mappingsJson = mappingsDoc.get("_doc");
|
||||
Document mapping = null;
|
||||
if (mappingsJson != null) {
|
||||
try {
|
||||
mapping = Document.from((Map<String, ? extends Object>) Document.parse(mappingsJson).get("_doc"));
|
||||
} catch (Exception e) {
|
||||
LOGGER.warn("Got invalid mappings JSON: {}", mappingsJson);
|
||||
}
|
||||
}
|
||||
|
||||
TemplateData templateData = TemplateData.builder()
|
||||
.withIndexPatterns(indexTemplateMetadata.patterns().toArray(new String[0])) //
|
||||
.withSettings(settings) //
|
||||
.withMapping(mapping) //
|
||||
.withAliases(aliases) //
|
||||
.withOrder(indexTemplateMetadata.order()) //
|
||||
.withVersion(indexTemplateMetadata.version()).build();
|
||||
|
||||
return templateData;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean existsTemplate(ExistsTemplateRequest existsTemplateRequest) {
|
||||
|
||||
Assert.notNull(existsTemplateRequest, "existsTemplateRequest must not be null");
|
||||
|
||||
// client.admin().indices() has no method for checking the existence
|
||||
return getTemplate(new GetTemplateRequest(existsTemplateRequest.getTemplateName())) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean deleteTemplate(DeleteTemplateRequest deleteTemplateRequest) {
|
||||
|
||||
Assert.notNull(deleteTemplateRequest, "deleteTemplateRequest must not be null");
|
||||
|
||||
DeleteIndexTemplateRequest deleteIndexTemplateRequest = requestFactory.deleteIndexTemplateRequest(client,
|
||||
deleteTemplateRequest);
|
||||
return client.admin().indices().deleteTemplate(deleteIndexTemplateRequest).actionGet().isAcknowledged();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,6 +114,16 @@ public interface DocumentOperations {
|
||||
@Nullable
|
||||
<T> T get(String id, Class<T> clazz, IndexCoordinates index);
|
||||
|
||||
/**
|
||||
* Execute a multiGet against elasticsearch for the given ids.
|
||||
*
|
||||
* @param query the query defining the ids of the objects to get
|
||||
* @param clazz the type of the object to be returned
|
||||
* @return list of objects, contains null values for ids that are not found
|
||||
* @since 4.1
|
||||
*/
|
||||
<T> List<T> multiGet(Query query, Class<T> clazz);
|
||||
|
||||
/**
|
||||
* Execute a multiGet against elasticsearch for the given ids.
|
||||
*
|
||||
@@ -146,9 +156,21 @@ public interface DocumentOperations {
|
||||
* Bulk index all objects. Will do save or update.
|
||||
*
|
||||
* @param queries the queries to execute in bulk
|
||||
* @return the ids of the indexed objects
|
||||
* @param clazz the entity class
|
||||
* @return the information about the indexed objects
|
||||
* @since 4.1
|
||||
*/
|
||||
default List<String> bulkIndex(List<IndexQuery> queries, IndexCoordinates index) {
|
||||
default List<IndexedObjectInformation> bulkIndex(List<IndexQuery> queries, Class<?> clazz) {
|
||||
return bulkIndex(queries, BulkOptions.defaultOptions(), clazz);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bulk index all objects. Will do save or update.
|
||||
*
|
||||
* @param queries the queries to execute in bulk
|
||||
* @return the information about of the indexed objects
|
||||
*/
|
||||
default List<IndexedObjectInformation> bulkIndex(List<IndexQuery> queries, IndexCoordinates index) {
|
||||
return bulkIndex(queries, BulkOptions.defaultOptions(), index);
|
||||
}
|
||||
|
||||
@@ -157,9 +179,20 @@ public interface DocumentOperations {
|
||||
*
|
||||
* @param queries the queries to execute in bulk
|
||||
* @param bulkOptions options to be added to the bulk request
|
||||
* @return the ids of the indexed objects
|
||||
* @param clazz the entity class
|
||||
* @return the information about of the indexed objects
|
||||
* @since 4.1
|
||||
*/
|
||||
List<String> bulkIndex(List<IndexQuery> queries, BulkOptions bulkOptions, IndexCoordinates index);
|
||||
List<IndexedObjectInformation> bulkIndex(List<IndexQuery> queries, BulkOptions bulkOptions, Class<?> clazz);
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @return the information about of the indexed objects
|
||||
*/
|
||||
List<IndexedObjectInformation> bulkIndex(List<IndexQuery> queries, BulkOptions bulkOptions, IndexCoordinates index);
|
||||
|
||||
/**
|
||||
* Bulk update all objects. Will do update.
|
||||
@@ -170,6 +203,15 @@ public interface DocumentOperations {
|
||||
bulkUpdate(queries, BulkOptions.defaultOptions(), index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bulk update all objects. Will do update.
|
||||
*
|
||||
* @param clazz the entity class
|
||||
* @param queries the queries to execute in bulk
|
||||
* @since 4.1
|
||||
*/
|
||||
void bulkUpdate(List<UpdateQuery> queries, Class<?> clazz);
|
||||
|
||||
/**
|
||||
* Bulk update all objects. Will do update.
|
||||
*
|
||||
@@ -181,11 +223,24 @@ public interface DocumentOperations {
|
||||
/**
|
||||
* Delete the one object with provided id.
|
||||
*
|
||||
* @param id the document ot delete
|
||||
* @param id the document to delete
|
||||
* @param index the index from which to delete
|
||||
* @return documentId of the document deleted
|
||||
*/
|
||||
String delete(String id, IndexCoordinates index);
|
||||
default String delete(String id, IndexCoordinates index) {
|
||||
return delete(id, null, index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the one object with provided id.
|
||||
*
|
||||
* @param id the document to delete
|
||||
* @param routing the optional routing for the document to be deleted
|
||||
* @param index the index from which to delete
|
||||
* @return documentId of the document deleted
|
||||
* @since 4.1
|
||||
*/
|
||||
String delete(String id, @Nullable String routing, IndexCoordinates index);
|
||||
|
||||
/**
|
||||
* Delete the one object with provided id.
|
||||
@@ -213,6 +268,16 @@ public interface DocumentOperations {
|
||||
*/
|
||||
String delete(Object entity, IndexCoordinates index);
|
||||
|
||||
/**
|
||||
* Delete all records matching the query.
|
||||
*
|
||||
* @param query query defining the objects
|
||||
* @param clazz The entity class, must be annotated with
|
||||
* {@link org.springframework.data.elasticsearch.annotations.Document}
|
||||
* @since 4.1
|
||||
*/
|
||||
void delete(Query query, Class<?> clazz);
|
||||
|
||||
/**
|
||||
* Delete all records matching the query.
|
||||
*
|
||||
|
||||
+6
-2
@@ -23,7 +23,6 @@ import org.elasticsearch.ElasticsearchStatusException;
|
||||
import org.elasticsearch.common.ValidationException;
|
||||
import org.elasticsearch.index.engine.VersionConflictEngineException;
|
||||
import org.elasticsearch.rest.RestStatus;
|
||||
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.dao.DataAccessResourceFailureException;
|
||||
import org.springframework.dao.DataIntegrityViolationException;
|
||||
@@ -105,10 +104,15 @@ public class ElasticsearchExceptionTranslator implements PersistenceExceptionTra
|
||||
|
||||
List<String> metadata = ex.getMetadata("es.index_uuid");
|
||||
if (metadata == null) {
|
||||
|
||||
if (ex.getCause() instanceof ElasticsearchException) {
|
||||
return indexAvailable((ElasticsearchException) ex.getCause());
|
||||
}
|
||||
|
||||
if (ex instanceof ElasticsearchStatusException) {
|
||||
return StringUtils.hasText(ObjectUtils.nullSafeToString(ex.getIndex()));
|
||||
}
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
return !CollectionUtils.contains(metadata.iterator(), "_na_");
|
||||
}
|
||||
|
||||
+26
-16
@@ -19,7 +19,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.elasticsearch.cluster.metadata.AliasMetaData;
|
||||
import org.elasticsearch.cluster.metadata.AliasMetadata;
|
||||
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
|
||||
import org.springframework.data.elasticsearch.core.document.Document;
|
||||
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
||||
@@ -49,7 +49,7 @@ public interface ElasticsearchOperations extends DocumentOperations, SearchOpera
|
||||
IndexOperations indexOps(Class<?> clazz);
|
||||
|
||||
/**
|
||||
* get an {@link IndexOperations} that is bound to the given class
|
||||
* get an {@link IndexOperations} that is bound to the given index
|
||||
*
|
||||
* @return IndexOperations
|
||||
*/
|
||||
@@ -59,18 +59,28 @@ public interface ElasticsearchOperations extends DocumentOperations, SearchOpera
|
||||
|
||||
IndexCoordinates getIndexCoordinatesFor(Class<?> clazz);
|
||||
|
||||
/**
|
||||
* gets the routing for an entity which might be defined by a join-type relation
|
||||
*
|
||||
* @param entity the entity
|
||||
* @return the routing, may be null if not set.
|
||||
* @since 4.1
|
||||
*/
|
||||
@Nullable
|
||||
String getEntityRouting(Object entity);
|
||||
|
||||
// region IndexOperations
|
||||
/**
|
||||
* Create an index for given indexName .
|
||||
*
|
||||
* @param indexName the name of the index
|
||||
* @return {@literal true} if the index was created
|
||||
* @deprecated since 4.0, use {@link IndexOperations#create()}
|
||||
*/
|
||||
@Deprecated
|
||||
default boolean createIndex(String indexName) {
|
||||
return indexOps(IndexCoordinates.of(indexName)).create();
|
||||
}
|
||||
/**
|
||||
* Create an index for given indexName .
|
||||
*
|
||||
* @param indexName the name of the index
|
||||
* @return {@literal true} if the index was created
|
||||
* @deprecated since 4.0, use {@link IndexOperations#create()}
|
||||
*/
|
||||
@Deprecated
|
||||
default boolean createIndex(String indexName) {
|
||||
return indexOps(IndexCoordinates.of(indexName)).create();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an index for given indexName and Settings.
|
||||
@@ -186,7 +196,7 @@ default boolean createIndex(String indexName) {
|
||||
@Deprecated
|
||||
default boolean putMapping(Class<?> clazz) {
|
||||
IndexOperations indexOps = indexOps(clazz);
|
||||
return indexOps.putMapping(indexOps.createMapping(clazz));
|
||||
return indexOps.putMapping(clazz);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -202,7 +212,7 @@ default boolean createIndex(String indexName) {
|
||||
@Deprecated
|
||||
default boolean putMapping(IndexCoordinates index, Class<?> clazz) {
|
||||
IndexOperations indexOps = indexOps(index);
|
||||
return indexOps.putMapping(indexOps.createMapping(clazz));
|
||||
return indexOps.putMapping(clazz);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -291,7 +301,7 @@ default boolean createIndex(String indexName) {
|
||||
* @deprecated since 4.0, use {@link #indexOps(IndexCoordinates)} and {@link IndexOperations#queryForAlias()}
|
||||
*/
|
||||
@Deprecated
|
||||
default List<AliasMetaData> queryForAlias(String indexName) {
|
||||
default List<AliasMetadata> queryForAlias(String indexName) {
|
||||
return indexOps(IndexCoordinates.of(indexName)).queryForAlias();
|
||||
}
|
||||
|
||||
|
||||
+29
-37
@@ -26,6 +26,7 @@ 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.search.ClearScrollRequest;
|
||||
import org.elasticsearch.action.search.MultiSearchRequest;
|
||||
import org.elasticsearch.action.search.MultiSearchResponse;
|
||||
@@ -37,9 +38,10 @@ import org.elasticsearch.client.RequestOptions;
|
||||
import org.elasticsearch.client.RestHighLevelClient;
|
||||
import org.elasticsearch.common.unit.TimeValue;
|
||||
import org.elasticsearch.index.reindex.DeleteByQueryRequest;
|
||||
import org.elasticsearch.search.builder.SearchSourceBuilder;
|
||||
import org.elasticsearch.search.fetch.subphase.FetchSourceContext;
|
||||
import org.elasticsearch.search.suggest.SuggestBuilder;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
|
||||
import org.springframework.data.elasticsearch.core.document.DocumentAdapters;
|
||||
import org.springframework.data.elasticsearch.core.document.SearchDocumentResponse;
|
||||
@@ -88,8 +90,10 @@ import org.springframework.util.Assert;
|
||||
*/
|
||||
public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate {
|
||||
|
||||
private RestHighLevelClient client;
|
||||
private ElasticsearchExceptionTranslator exceptionTranslator;
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(ElasticsearchRestTemplate.class);
|
||||
|
||||
private final RestHighLevelClient client;
|
||||
private final ElasticsearchExceptionTranslator exceptionTranslator;
|
||||
|
||||
// region Initialization
|
||||
public ElasticsearchRestTemplate(RestHighLevelClient client) {
|
||||
@@ -133,23 +137,19 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate {
|
||||
// endregion
|
||||
|
||||
// region DocumentOperations
|
||||
@Override
|
||||
public String index(IndexQuery query, IndexCoordinates index) {
|
||||
|
||||
maybeCallbackBeforeConvertWithQuery(query, index);
|
||||
public String doIndex(IndexQuery query, IndexCoordinates index) {
|
||||
|
||||
IndexRequest request = requestFactory.indexRequest(query, index);
|
||||
String documentId = execute(client -> client.index(request, RequestOptions.DEFAULT).getId());
|
||||
IndexResponse indexResponse = execute(client -> client.index(request, RequestOptions.DEFAULT));
|
||||
|
||||
// We should call this because we are not going through a mapper.
|
||||
Object queryObject = query.getObject();
|
||||
if (queryObject != null) {
|
||||
setPersistentEntityId(queryObject, documentId);
|
||||
updateIndexedObject(queryObject, IndexedObjectInformation.of(indexResponse.getId(), indexResponse.getSeqNo(),
|
||||
indexResponse.getPrimaryTerm(), indexResponse.getVersion()));
|
||||
}
|
||||
|
||||
maybeCallbackAfterSaveWithQuery(query, index);
|
||||
|
||||
return documentId;
|
||||
return indexResponse.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -168,7 +168,7 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate {
|
||||
Assert.notNull(index, "index must not be null");
|
||||
Assert.notEmpty(query.getIds(), "No Id define for Query");
|
||||
|
||||
MultiGetRequest request = requestFactory.multiGetRequest(query, index);
|
||||
MultiGetRequest request = requestFactory.multiGetRequest(query, clazz, index);
|
||||
MultiGetResponse result = execute(client -> client.mget(request, RequestOptions.DEFAULT));
|
||||
|
||||
DocumentCallback<T> callback = new ReadDocumentCallback<>(elasticsearchConverter, clazz, index);
|
||||
@@ -182,15 +182,6 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate {
|
||||
return execute(client -> client.get(request, RequestOptions.DEFAULT).isExists());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> bulkIndex(List<IndexQuery> queries, BulkOptions bulkOptions, IndexCoordinates index) {
|
||||
|
||||
Assert.notNull(queries, "List of IndexQuery must not be null");
|
||||
Assert.notNull(bulkOptions, "BulkOptions must not be null");
|
||||
|
||||
return doBulkOperation(queries, bulkOptions, index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bulkUpdate(List<UpdateQuery> queries, BulkOptions bulkOptions, IndexCoordinates index) {
|
||||
|
||||
@@ -201,12 +192,12 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String delete(String id, IndexCoordinates index) {
|
||||
public String delete(String id, @Nullable String routing, IndexCoordinates index) {
|
||||
|
||||
Assert.notNull(id, "id must not be null");
|
||||
Assert.notNull(index, "index must not be null");
|
||||
|
||||
DeleteRequest request = new DeleteRequest(index.getIndexName(), elasticsearchConverter.convertId(id));
|
||||
DeleteRequest request = requestFactory.deleteRequest(elasticsearchConverter.convertId(id), routing, index);
|
||||
return execute(client -> client.delete(request, RequestOptions.DEFAULT).getId());
|
||||
}
|
||||
|
||||
@@ -231,13 +222,13 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate {
|
||||
return new UpdateResponse(result);
|
||||
}
|
||||
|
||||
private List<String> doBulkOperation(List<?> queries, BulkOptions bulkOptions, IndexCoordinates index) {
|
||||
maybeCallbackBeforeConvertWithQueries(queries, index);
|
||||
public List<IndexedObjectInformation> doBulkOperation(List<?> queries, BulkOptions bulkOptions,
|
||||
IndexCoordinates index) {
|
||||
BulkRequest bulkRequest = requestFactory.bulkRequest(queries, bulkOptions, index);
|
||||
List<String> ids = checkForBulkOperationFailure(
|
||||
List<IndexedObjectInformation> indexedObjectInformationList = checkForBulkOperationFailure(
|
||||
execute(client -> client.bulk(bulkRequest, RequestOptions.DEFAULT)));
|
||||
maybeCallbackAfterSaveWithQueries(queries, index);
|
||||
return ids;
|
||||
updateIndexedObjectsWithQueries(queries, indexedObjectInformationList);
|
||||
return indexedObjectInformationList;
|
||||
}
|
||||
// endregion
|
||||
|
||||
@@ -248,7 +239,7 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate {
|
||||
Assert.notNull(query, "query must not be null");
|
||||
Assert.notNull(index, "index must not be null");
|
||||
|
||||
final boolean trackTotalHits = query.getTrackTotalHits();
|
||||
final Boolean trackTotalHits = query.getTrackTotalHits();
|
||||
query.setTrackTotalHits(true);
|
||||
SearchRequest searchRequest = requestFactory.searchRequest(query, clazz, index);
|
||||
query.setTrackTotalHits(trackTotalHits);
|
||||
@@ -300,17 +291,18 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate {
|
||||
|
||||
@Override
|
||||
public void searchScrollClear(List<String> scrollIds) {
|
||||
ClearScrollRequest request = new ClearScrollRequest();
|
||||
request.scrollIds(scrollIds);
|
||||
execute(client -> client.clearScroll(request, RequestOptions.DEFAULT));
|
||||
try {
|
||||
ClearScrollRequest request = new ClearScrollRequest();
|
||||
request.scrollIds(scrollIds);
|
||||
execute(client -> client.clearScroll(request, RequestOptions.DEFAULT));
|
||||
} catch (Exception e) {
|
||||
LOGGER.warn("Could not clear scroll: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public SearchResponse suggest(SuggestBuilder suggestion, IndexCoordinates index) {
|
||||
SearchRequest searchRequest = new SearchRequest(index.getIndexNames());
|
||||
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
|
||||
sourceBuilder.suggest(suggestion);
|
||||
searchRequest.source(sourceBuilder);
|
||||
SearchRequest searchRequest = requestFactory.searchRequest(suggestion, index);
|
||||
return execute(client -> client.search(searchRequest, RequestOptions.DEFAULT));
|
||||
}
|
||||
|
||||
|
||||
+25
-32
@@ -23,6 +23,7 @@ import org.elasticsearch.action.admin.cluster.node.info.NodesInfoAction;
|
||||
import org.elasticsearch.action.admin.cluster.node.info.NodesInfoRequestBuilder;
|
||||
import org.elasticsearch.action.admin.cluster.node.info.NodesInfoResponse;
|
||||
import org.elasticsearch.action.bulk.BulkRequestBuilder;
|
||||
import org.elasticsearch.action.delete.DeleteRequestBuilder;
|
||||
import org.elasticsearch.action.get.GetRequestBuilder;
|
||||
import org.elasticsearch.action.get.GetResponse;
|
||||
import org.elasticsearch.action.get.MultiGetRequestBuilder;
|
||||
@@ -86,6 +87,7 @@ import org.springframework.util.Assert;
|
||||
public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
|
||||
private static final Logger QUERY_LOGGER = LoggerFactory
|
||||
.getLogger("org.springframework.data.elasticsearch.core.QUERY");
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(ElasticsearchTemplate.class);
|
||||
|
||||
private Client client;
|
||||
@Nullable private String searchTimeout;
|
||||
@@ -142,10 +144,7 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
|
||||
// endregion
|
||||
|
||||
// region DocumentOperations
|
||||
@Override
|
||||
public String index(IndexQuery query, IndexCoordinates index) {
|
||||
|
||||
maybeCallbackBeforeConvertWithQuery(query, index);
|
||||
public String doIndex(IndexQuery query, IndexCoordinates index) {
|
||||
|
||||
IndexRequestBuilder indexRequestBuilder = requestFactory.indexRequestBuilder(client, query, index);
|
||||
ActionFuture<IndexResponse> future = indexRequestBuilder.execute();
|
||||
@@ -160,11 +159,10 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
|
||||
// We should call this because we are not going through a mapper.
|
||||
Object queryObject = query.getObject();
|
||||
if (queryObject != null) {
|
||||
setPersistentEntityId(queryObject, documentId);
|
||||
updateIndexedObject(queryObject, IndexedObjectInformation.of(documentId, response.getSeqNo(),
|
||||
response.getPrimaryTerm(), response.getVersion()));
|
||||
}
|
||||
|
||||
maybeCallbackAfterSaveWithQuery(query, index);
|
||||
|
||||
return documentId;
|
||||
}
|
||||
|
||||
@@ -184,7 +182,7 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
|
||||
Assert.notNull(index, "index must not be null");
|
||||
Assert.notEmpty(query.getIds(), "No Ids defined for Query");
|
||||
|
||||
MultiGetRequestBuilder builder = requestFactory.multiGetRequestBuilder(client, query, index);
|
||||
MultiGetRequestBuilder builder = requestFactory.multiGetRequestBuilder(client, query, clazz, index);
|
||||
|
||||
DocumentCallback<T> callback = new ReadDocumentCallback<>(elasticsearchConverter, clazz, index);
|
||||
List<Document> documents = DocumentAdapters.from(builder.execute().actionGet());
|
||||
@@ -198,19 +196,6 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
|
||||
return getRequestBuilder.execute().actionGet().isExists();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> bulkIndex(List<IndexQuery> queries, BulkOptions bulkOptions, IndexCoordinates index) {
|
||||
|
||||
Assert.notNull(queries, "List of IndexQuery must not be null");
|
||||
Assert.notNull(bulkOptions, "BulkOptions must not be null");
|
||||
|
||||
List<String> ids = doBulkOperation(queries, bulkOptions, index);
|
||||
|
||||
maybeCallbackAfterSaveWithQueries(queries, index);
|
||||
|
||||
return ids;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bulkUpdate(List<UpdateQuery> queries, BulkOptions bulkOptions, IndexCoordinates index) {
|
||||
|
||||
@@ -221,13 +206,14 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String delete(String id, IndexCoordinates index) {
|
||||
public String delete(String id, @Nullable String routing, IndexCoordinates index) {
|
||||
|
||||
Assert.notNull(id, "id must not be null");
|
||||
Assert.notNull(index, "index must not be null");
|
||||
|
||||
return client.prepareDelete(index.getIndexName(), IndexCoordinates.TYPE, elasticsearchConverter.convertId(id))
|
||||
.execute().actionGet().getId();
|
||||
DeleteRequestBuilder deleteRequestBuilder = requestFactory.deleteRequestBuilder(client,
|
||||
elasticsearchConverter.convertId(id), routing, index);
|
||||
return deleteRequestBuilder.execute().actionGet().getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -254,10 +240,13 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
|
||||
return new UpdateResponse(result);
|
||||
}
|
||||
|
||||
private List<String> doBulkOperation(List<?> queries, BulkOptions bulkOptions, IndexCoordinates index) {
|
||||
maybeCallbackBeforeConvertWithQueries(queries, index);
|
||||
public List<IndexedObjectInformation> doBulkOperation(List<?> queries, BulkOptions bulkOptions,
|
||||
IndexCoordinates index) {
|
||||
BulkRequestBuilder bulkRequest = requestFactory.bulkRequestBuilder(client, queries, bulkOptions, index);
|
||||
return checkForBulkOperationFailure(bulkRequest.execute().actionGet());
|
||||
final List<IndexedObjectInformation> indexedObjectInformations = checkForBulkOperationFailure(
|
||||
bulkRequest.execute().actionGet());
|
||||
updateIndexedObjectsWithQueries(queries, indexedObjectInformations);
|
||||
return indexedObjectInformations;
|
||||
}
|
||||
// endregion
|
||||
|
||||
@@ -268,7 +257,7 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
|
||||
Assert.notNull(query, "query must not be null");
|
||||
Assert.notNull(index, "index must not be null");
|
||||
|
||||
final boolean trackTotalHits = query.getTrackTotalHits();
|
||||
final Boolean trackTotalHits = query.getTrackTotalHits();
|
||||
query.setTrackTotalHits(true);
|
||||
SearchRequestBuilder searchRequestBuilder = requestFactory.searchRequestBuilder(client, query, clazz, index);
|
||||
query.setTrackTotalHits(trackTotalHits);
|
||||
@@ -292,8 +281,7 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
|
||||
|
||||
Assert.notNull(query.getPageable(), "pageable of query must not be null.");
|
||||
|
||||
ActionFuture<SearchResponse> action = requestFactory //
|
||||
.searchRequestBuilder(client, query, clazz, index) //
|
||||
ActionFuture<SearchResponse> action = requestFactory.searchRequestBuilder(client, query, clazz, index) //
|
||||
.setScroll(TimeValue.timeValueMillis(scrollTimeInMillis)) //
|
||||
.execute();
|
||||
|
||||
@@ -322,12 +310,17 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
|
||||
|
||||
@Override
|
||||
public void searchScrollClear(List<String> scrollIds) {
|
||||
client.prepareClearScroll().setScrollIds(scrollIds).execute().actionGet();
|
||||
try {
|
||||
client.prepareClearScroll().setScrollIds(scrollIds).execute().actionGet();
|
||||
} catch (Exception e) {
|
||||
LOGGER.warn("Could not clear scroll: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public SearchResponse suggest(SuggestBuilder suggestion, IndexCoordinates index) {
|
||||
return client.prepareSearch(index.getIndexNames()).suggest(suggestion).get();
|
||||
SearchRequestBuilder searchRequestBuilder = requestFactory.searchRequestBuilder(client, suggestion, index);
|
||||
return searchRequestBuilder.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -18,6 +18,7 @@ package org.springframework.data.elasticsearch.core;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.data.elasticsearch.core.join.JoinField;
|
||||
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
|
||||
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
|
||||
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
||||
@@ -28,7 +29,6 @@ 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.
|
||||
@@ -43,6 +43,8 @@ class EntityOperations {
|
||||
|
||||
private static final String ID_FIELD = "id";
|
||||
|
||||
private final MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> context;
|
||||
|
||||
public EntityOperations(
|
||||
MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> context) {
|
||||
|
||||
@@ -51,8 +53,6 @@ class EntityOperations {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
private final MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> context;
|
||||
|
||||
/**
|
||||
* Creates a new {@link Entity} for the given bean.
|
||||
*
|
||||
@@ -100,11 +100,26 @@ class EntityOperations {
|
||||
* @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.
|
||||
* @deprecated since 4.1, use {@link EntityOperations#determineIndex(Entity, String)}
|
||||
*/
|
||||
@Deprecated
|
||||
IndexCoordinates determineIndex(Entity<?> entity, @Nullable String index, @Nullable String type) {
|
||||
return determineIndex(entity.getPersistentEntity(), index, type);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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}.
|
||||
* @return the {@link IndexCoordinates} containing index name and index type.
|
||||
*/
|
||||
IndexCoordinates determineIndex(Entity<?> entity, @Nullable String index) {
|
||||
return determineIndex(entity.getPersistentEntity(), index);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
@@ -115,20 +130,27 @@ class EntityOperations {
|
||||
* @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.
|
||||
* @deprecated since 4.1, use {@link EntityOperations#determineIndex(ElasticsearchPersistentEntity, String)}
|
||||
*/
|
||||
@Deprecated
|
||||
IndexCoordinates determineIndex(ElasticsearchPersistentEntity<?> persistentEntity, @Nullable String index,
|
||||
@Nullable String type) {
|
||||
return persistentEntity.getIndexCoordinates();
|
||||
return determineIndex(persistentEntity, index);
|
||||
}
|
||||
|
||||
private static String indexName(@Nullable ElasticsearchPersistentEntity<?> entity, @Nullable String index) {
|
||||
|
||||
if (StringUtils.isEmpty(index)) {
|
||||
Assert.notNull(entity, "Cannot determine index name");
|
||||
return entity.getIndexCoordinates().getIndexName();
|
||||
}
|
||||
|
||||
return index;
|
||||
/**
|
||||
* 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}.
|
||||
* @return the {@link IndexCoordinates} containing index name and index type.
|
||||
* @since 4.1
|
||||
*/
|
||||
IndexCoordinates determineIndex(ElasticsearchPersistentEntity<?> persistentEntity, @Nullable String index) {
|
||||
return index != null ? IndexCoordinates.of(index) : persistentEntity.getIndexCoordinates();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -214,14 +236,18 @@ class EntityOperations {
|
||||
* Returns whether the entity has a parent.
|
||||
*
|
||||
* @return {@literal true} if the entity has a parent that has an {@literal id}.
|
||||
* @deprecated since 4.1, not supported anymore by Elasticsearch
|
||||
*/
|
||||
@Deprecated
|
||||
boolean hasParent();
|
||||
|
||||
/**
|
||||
* Returns the parent Id. Can be {@literal null}.
|
||||
*
|
||||
* @return can be {@literal null}.
|
||||
* @deprecated since 4.1, not supported anymore by Elasticsearch
|
||||
*/
|
||||
@Deprecated
|
||||
@Nullable
|
||||
Object getParentId();
|
||||
|
||||
@@ -271,9 +297,19 @@ class EntityOperations {
|
||||
* Returns SeqNoPropertyTerm for this entity.
|
||||
*
|
||||
* @return SeqNoPrimaryTerm, may be {@literal null}
|
||||
* @since 4.0
|
||||
*/
|
||||
@Nullable
|
||||
SeqNoPrimaryTerm getSeqNoPrimaryTerm();
|
||||
|
||||
/**
|
||||
* returns the routing for the entity if it is available
|
||||
*
|
||||
* @return routing if available
|
||||
* @since 4.1
|
||||
*/
|
||||
@Nullable
|
||||
String getRouting();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -396,6 +432,11 @@ class EntityOperations {
|
||||
public ElasticsearchPersistentEntity<?> getPersistentEntity() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRouting() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -467,56 +508,32 @@ class EntityOperations {
|
||||
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());
|
||||
return propertyAccessor.getProperty(entity.getVersionProperty());
|
||||
}
|
||||
|
||||
/*
|
||||
* (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;
|
||||
@@ -532,15 +549,17 @@ class EntityOperations {
|
||||
private final ElasticsearchPersistentEntity<?> entity;
|
||||
private final ConvertingPropertyAccessor<T> propertyAccessor;
|
||||
private final IdentifierAccessor identifierAccessor;
|
||||
private final ConversionService conversionService;
|
||||
|
||||
private AdaptibleMappedEntity(ElasticsearchPersistentEntity<?> entity, IdentifierAccessor identifierAccessor,
|
||||
ConvertingPropertyAccessor<T> propertyAccessor) {
|
||||
ConvertingPropertyAccessor<T> propertyAccessor, ConversionService conversionService) {
|
||||
|
||||
super(entity, identifierAccessor, propertyAccessor);
|
||||
|
||||
this.entity = entity;
|
||||
this.propertyAccessor = propertyAccessor;
|
||||
this.identifierAccessor = identifierAccessor;
|
||||
this.conversionService = conversionService;
|
||||
}
|
||||
|
||||
static <T> AdaptibleEntity<T> of(T bean,
|
||||
@@ -552,22 +571,15 @@ class EntityOperations {
|
||||
PersistentPropertyAccessor<T> propertyAccessor = entity.getPropertyAccessor(bean);
|
||||
|
||||
return new AdaptibleMappedEntity<>(entity, identifierAccessor,
|
||||
new ConvertingPropertyAccessor<>(propertyAccessor, conversionService));
|
||||
new ConvertingPropertyAccessor<>(propertyAccessor, conversionService), 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()
|
||||
*/
|
||||
@Deprecated
|
||||
@Override
|
||||
public Object getParentId() {
|
||||
|
||||
@@ -575,10 +587,6 @@ class EntityOperations {
|
||||
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) {
|
||||
@@ -603,17 +611,12 @@ class EntityOperations {
|
||||
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);
|
||||
ElasticsearchPersistentProperty versionProperty = entity.getVersionProperty();
|
||||
return versionProperty != null ? propertyAccessor.getProperty(versionProperty, Number.class) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -629,10 +632,6 @@ class EntityOperations {
|
||||
return propertyAccessor.getProperty(seqNoPrimaryTermProperty, SeqNoPrimaryTerm.class);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.EntityOperations.AdaptibleEntity#initializeVersionProperty()
|
||||
*/
|
||||
@Override
|
||||
public T initializeVersionProperty() {
|
||||
|
||||
@@ -647,10 +646,6 @@ class EntityOperations {
|
||||
return propertyAccessor.getBean();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.EntityOperations.AdaptibleEntity#incrementVersion()
|
||||
*/
|
||||
@Override
|
||||
public T incrementVersion() {
|
||||
|
||||
@@ -662,6 +657,22 @@ class EntityOperations {
|
||||
|
||||
return propertyAccessor.getBean();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRouting() {
|
||||
|
||||
ElasticsearchPersistentProperty joinFieldProperty = entity.getJoinFieldProperty();
|
||||
|
||||
if (joinFieldProperty != null) {
|
||||
JoinField<?> joinField = propertyAccessor.getProperty(joinFieldProperty, JoinField.class);
|
||||
|
||||
if (joinField != null && joinField.getParent() != null) {
|
||||
return conversionService.convert(joinField.getParent(), String.class);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,11 +17,20 @@ package org.springframework.data.elasticsearch.core;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.elasticsearch.cluster.metadata.AliasMetaData;
|
||||
import org.elasticsearch.cluster.metadata.AliasMetadata;
|
||||
import org.springframework.data.elasticsearch.core.document.Document;
|
||||
import org.springframework.data.elasticsearch.core.index.AliasActions;
|
||||
import org.springframework.data.elasticsearch.core.index.AliasData;
|
||||
import org.springframework.data.elasticsearch.core.index.DeleteTemplateRequest;
|
||||
import org.springframework.data.elasticsearch.core.index.ExistsTemplateRequest;
|
||||
import org.springframework.data.elasticsearch.core.index.GetTemplateRequest;
|
||||
import org.springframework.data.elasticsearch.core.index.PutTemplateRequest;
|
||||
import org.springframework.data.elasticsearch.core.index.TemplateData;
|
||||
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
||||
import org.springframework.data.elasticsearch.core.query.AliasQuery;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* The operations for the
|
||||
@@ -36,6 +45,7 @@ import org.springframework.data.elasticsearch.core.query.AliasQuery;
|
||||
*/
|
||||
public interface IndexOperations {
|
||||
|
||||
// region index management
|
||||
/**
|
||||
* Create an index.
|
||||
*
|
||||
@@ -69,7 +79,9 @@ public interface IndexOperations {
|
||||
* Refresh the index(es) this IndexOperations is bound to
|
||||
*/
|
||||
void refresh();
|
||||
// endregion
|
||||
|
||||
// region mappings
|
||||
/**
|
||||
* Creates the index mapping for the entity this IndexOperations is bound to.
|
||||
*
|
||||
@@ -85,6 +97,16 @@ public interface IndexOperations {
|
||||
*/
|
||||
Document createMapping(Class<?> clazz);
|
||||
|
||||
/**
|
||||
* Writes the mapping to the index for the class this IndexOperations is bound to.
|
||||
*
|
||||
* @return {@literal true} if the mapping could be stored
|
||||
* @since 4.1
|
||||
*/
|
||||
default boolean putMapping() {
|
||||
return putMapping(createMapping());
|
||||
}
|
||||
|
||||
/**
|
||||
* writes a mapping to the index
|
||||
*
|
||||
@@ -93,6 +115,36 @@ public interface IndexOperations {
|
||||
*/
|
||||
boolean putMapping(Document mapping);
|
||||
|
||||
/**
|
||||
* Creates the index mapping for the given class and writes it to the index.
|
||||
*
|
||||
* @param clazz the clazz to create a mapping for
|
||||
* @return {@literal true} if the mapping could be stored
|
||||
* @since 4.1
|
||||
*/
|
||||
default boolean putMapping(Class<?> clazz) {
|
||||
return putMapping(createMapping(clazz));
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region settings
|
||||
/**
|
||||
* Creates the index settings for the entity this IndexOperations is bound to.
|
||||
*
|
||||
* @return a settings document.
|
||||
* @since 4.1
|
||||
*/
|
||||
Document createSettings();
|
||||
|
||||
/**
|
||||
* Creates the index settings from the annotations on the given class
|
||||
*
|
||||
* @param clazz the class to create the index settings from
|
||||
* @return a settings document.
|
||||
* @since 4.1
|
||||
*/
|
||||
Document createSettings(Class<?> clazz);
|
||||
|
||||
/**
|
||||
* Get mapping for an index defined by a class.
|
||||
*
|
||||
@@ -100,29 +152,6 @@ public interface IndexOperations {
|
||||
*/
|
||||
Map<String, Object> getMapping();
|
||||
|
||||
/**
|
||||
* Add an alias.
|
||||
*
|
||||
* @param query query defining the alias
|
||||
* @return true if the alias was created
|
||||
*/
|
||||
boolean addAlias(AliasQuery query);
|
||||
|
||||
/**
|
||||
* Get the alias informations for a specified index.
|
||||
*
|
||||
* @return alias information
|
||||
*/
|
||||
List<AliasMetaData> queryForAlias();
|
||||
|
||||
/**
|
||||
* Remove an alias.
|
||||
*
|
||||
* @param query query defining the alias
|
||||
* @return true if the alias was removed
|
||||
*/
|
||||
boolean removeAlias(AliasQuery query);
|
||||
|
||||
/**
|
||||
* Get the index settings.
|
||||
*
|
||||
@@ -131,10 +160,162 @@ public interface IndexOperations {
|
||||
Map<String, Object> getSettings();
|
||||
|
||||
/**
|
||||
* Get settings for a given indexName.
|
||||
* Get the index settings.
|
||||
*
|
||||
* @param includeDefaults wehther or not to include all the default settings
|
||||
* @param includeDefaults whether or not to include all the default settings
|
||||
* @return the settings
|
||||
*/
|
||||
Map<String, Object> getSettings(boolean includeDefaults);
|
||||
// endregion
|
||||
|
||||
// region aliases
|
||||
/**
|
||||
* Add an alias.
|
||||
*
|
||||
* @param query query defining the alias
|
||||
* @return true if the alias was created
|
||||
* @deprecated since 4.1 use {@link #alias(AliasActions)}
|
||||
*/
|
||||
@Deprecated
|
||||
boolean addAlias(AliasQuery query);
|
||||
|
||||
/**
|
||||
* Get the alias information for a specified index.
|
||||
*
|
||||
* @return alias information
|
||||
* @deprecated since 4.1, use {@link #getAliases(String...)} or {@link #getAliasesForIndex(String...)}.
|
||||
*/
|
||||
@Deprecated
|
||||
List<AliasMetadata> queryForAlias();
|
||||
|
||||
/**
|
||||
* Remove an alias.
|
||||
*
|
||||
* @param query query defining the alias
|
||||
* @return true if the alias was removed
|
||||
* @deprecated since 4.1 use {@link #alias(AliasActions)}
|
||||
*/
|
||||
@Deprecated
|
||||
boolean removeAlias(AliasQuery query);
|
||||
|
||||
/**
|
||||
* Executes the given {@link AliasActions}.
|
||||
*
|
||||
* @param aliasActions the actions to execute
|
||||
* @return if the operation is acknowledged by Elasticsearch
|
||||
* @since 4.1
|
||||
*/
|
||||
boolean alias(AliasActions aliasActions);
|
||||
|
||||
/**
|
||||
* gets information about aliases
|
||||
*
|
||||
* @param aliasNames alias names, must not be {@literal null}
|
||||
* @return a {@link Map} from index names to {@link AliasData} for that index
|
||||
* @since 4.1
|
||||
*/
|
||||
Map<String, Set<AliasData>> getAliases(String... aliasNames);
|
||||
|
||||
/**
|
||||
* gets information about aliases
|
||||
*
|
||||
* @param indexNames index names, must not be {@literal null}
|
||||
* @return a {@link Map} from index names to {@link AliasData} for that index
|
||||
* @since 4.1
|
||||
*/
|
||||
Map<String, Set<AliasData>> getAliasesForIndex(String... indexNames);
|
||||
// endregion
|
||||
|
||||
// region templates
|
||||
/**
|
||||
* Creates an index template using the legacy Elasticsearch interface (@see
|
||||
* https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-templates-v1.html).
|
||||
*
|
||||
* @param putTemplateRequest template request parameters
|
||||
* @return true if successful
|
||||
* @since 4.1
|
||||
*/
|
||||
boolean putTemplate(PutTemplateRequest putTemplateRequest);
|
||||
|
||||
/**
|
||||
* gets an index template using the legacy Elasticsearch
|
||||
* interface/Users/peter/Entwicklung/Projekte/spring-data-elasticsearch/src/main/java/org/springframework/data/elasticsearch/core/IndexOperations.java.
|
||||
*
|
||||
* @param templateName the template name
|
||||
* @return TemplateData, {@literal null} if no template with the given name exists.
|
||||
* @since 4.1
|
||||
*/
|
||||
@Nullable
|
||||
default TemplateData getTemplate(String templateName) {
|
||||
return getTemplate(new GetTemplateRequest(templateName));
|
||||
}
|
||||
|
||||
/**
|
||||
* gets an index template using the legacy Elasticsearch
|
||||
* interface/Users/peter/Entwicklung/Projekte/spring-data-elasticsearch/src/main/java/org/springframework/data/elasticsearch/core/IndexOperations.java.
|
||||
*
|
||||
* @param getTemplateRequest the request parameters
|
||||
* @return TemplateData, {@literal null} if no template with the given name exists.
|
||||
* @since 4.1
|
||||
*/
|
||||
@Nullable
|
||||
TemplateData getTemplate(GetTemplateRequest getTemplateRequest);
|
||||
|
||||
/**
|
||||
* check if an index template exists using the legacy Elasticsearch
|
||||
* interface/Users/peter/Entwicklung/Projekte/spring-data-elasticsearch/src/main/java/org/springframework/data/elasticsearch/core/IndexOperations.java.
|
||||
*
|
||||
* @param templateName the template name
|
||||
* @return {@literal true} if the index exists
|
||||
* @since 4.1
|
||||
*/
|
||||
default boolean existsTemplate(String templateName) {
|
||||
return existsTemplate(new ExistsTemplateRequest(templateName));
|
||||
}
|
||||
|
||||
/**
|
||||
* check if an index template exists using the legacy Elasticsearch
|
||||
* interface/Users/peter/Entwicklung/Projekte/spring-data-elasticsearch/src/main/java/org/springframework/data/elasticsearch/core/IndexOperations.java.
|
||||
*
|
||||
* @param existsTemplateRequest the request parameters
|
||||
* @return {@literal true} if the index exists
|
||||
* @since 4.1
|
||||
*/
|
||||
boolean existsTemplate(ExistsTemplateRequest existsTemplateRequest);
|
||||
|
||||
/**
|
||||
* Deletes an index template using the legacy Elasticsearch interface (@see
|
||||
* https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-templates-v1.html).
|
||||
*
|
||||
* @param templateName the template name
|
||||
* @return true if successful
|
||||
* @since 4.1
|
||||
*/
|
||||
default boolean deleteTemplate(String templateName) {
|
||||
return deleteTemplate(new DeleteTemplateRequest(templateName));
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes an index template using the legacy Elasticsearch interface (@see
|
||||
* https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-templates-v1.html).
|
||||
*
|
||||
* @param deleteTemplateRequest template request parameters
|
||||
* @return true if successful
|
||||
* @since 4.1
|
||||
*/
|
||||
boolean deleteTemplate(DeleteTemplateRequest deleteTemplateRequest);
|
||||
|
||||
// endregion
|
||||
|
||||
// region helper functions
|
||||
/**
|
||||
* get the current {@link IndexCoordinates}. These may change over time when the entity class has a SpEL constructed
|
||||
* index name. When this IndexOperations is not bound to a class, the bound IndexCoordinates are returned.
|
||||
*
|
||||
* @return IndexCoordinates
|
||||
* @since 4.1
|
||||
*/
|
||||
IndexCoordinates getIndexCoordinates();
|
||||
|
||||
// endregion
|
||||
}
|
||||
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright 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.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Value class capturing information about a newly indexed document in Elasticsearch.
|
||||
*
|
||||
* @author Peter-Josef Meisch
|
||||
* @author Roman Puchkovskiy
|
||||
* @since 4.1
|
||||
*/
|
||||
public class IndexedObjectInformation {
|
||||
private final String id;
|
||||
@Nullable private final Long seqNo;
|
||||
@Nullable private final Long primaryTerm;
|
||||
@Nullable private final Long version;
|
||||
|
||||
private IndexedObjectInformation(String id, @Nullable Long seqNo, @Nullable Long primaryTerm,
|
||||
@Nullable Long version) {
|
||||
this.id = id;
|
||||
this.seqNo = seqNo;
|
||||
this.primaryTerm = primaryTerm;
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
public static IndexedObjectInformation of(String id, @Nullable Long seqNo, @Nullable Long primaryTerm,
|
||||
@Nullable Long version) {
|
||||
return new IndexedObjectInformation(id, seqNo, primaryTerm, version);
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Long getSeqNo() {
|
||||
return seqNo;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Long getPrimaryTerm() {
|
||||
return primaryTerm;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Long getVersion() {
|
||||
return version;
|
||||
}
|
||||
}
|
||||
+66
-20
@@ -26,6 +26,7 @@ import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
||||
import org.springframework.data.elasticsearch.core.query.BulkOptions;
|
||||
import org.springframework.data.elasticsearch.core.query.Query;
|
||||
import org.springframework.data.elasticsearch.core.query.UpdateQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.UpdateResponse;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
@@ -34,11 +35,12 @@ import org.springframework.util.Assert;
|
||||
*
|
||||
* @author Peter-Josef Meisch
|
||||
* @author Aleksei Arsenev
|
||||
* @author Roman Puchkovskiy
|
||||
* @since 4.0
|
||||
*/
|
||||
public interface ReactiveDocumentOperations {
|
||||
/**
|
||||
* Index the given entity, once available, extracting index and type from entity metadata.
|
||||
* Index the given entity, once available, extracting index from entity metadata.
|
||||
*
|
||||
* @param entityPublisher must not be {@literal null}.
|
||||
* @param <T>
|
||||
@@ -50,15 +52,6 @@ public interface ReactiveDocumentOperations {
|
||||
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.
|
||||
*/
|
||||
<T> Mono<T> save(T entity);
|
||||
|
||||
/**
|
||||
* 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
|
||||
@@ -75,6 +68,15 @@ public interface ReactiveDocumentOperations {
|
||||
return entityPublisher.flatMap(it -> save(it, index));
|
||||
}
|
||||
|
||||
/**
|
||||
* Index the given entity extracting index from entity metadata.
|
||||
*
|
||||
* @param entity must not be {@literal null}.
|
||||
* @param <T>
|
||||
* @return a {@link Mono} emitting the saved entity.
|
||||
*/
|
||||
<T> Mono<T> save(T entity);
|
||||
|
||||
/**
|
||||
* 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}.
|
||||
@@ -87,8 +89,22 @@ public interface ReactiveDocumentOperations {
|
||||
<T> Mono<T> save(T entity, IndexCoordinates index);
|
||||
|
||||
/**
|
||||
* Index entities 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.
|
||||
* Index entities the index extracted from entity metadata.
|
||||
*
|
||||
* @param entities must not be {@literal null}.
|
||||
* @param clazz the entity class, used to determine the index
|
||||
* @return a {@link Flux} emitting saved entities.
|
||||
* @since 4.1
|
||||
*/
|
||||
default <T> Flux<T> saveAll(Iterable<T> entities, Class<T> clazz) {
|
||||
List<T> entityList = new ArrayList<>();
|
||||
entities.forEach(entityList::add);
|
||||
return saveAll(Mono.just(entityList), clazz);
|
||||
}
|
||||
|
||||
/**
|
||||
* Index entities in the given {@literal index}. If the {@literal index} is {@literal null} or empty the index name
|
||||
* provided via entity metadata is used.
|
||||
*
|
||||
* @param entities must not be {@literal null}.
|
||||
* @param index the target index, must not be {@literal null}
|
||||
@@ -103,8 +119,18 @@ public interface ReactiveDocumentOperations {
|
||||
}
|
||||
|
||||
/**
|
||||
* Index entities 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.
|
||||
* Index entities in the index extracted from entity metadata.
|
||||
*
|
||||
* @param entities must not be {@literal null}.
|
||||
* @param clazz the entity class, used to determine the index
|
||||
* @return a {@link Flux} emitting saved entities.
|
||||
* @since 4.1
|
||||
*/
|
||||
<T> Flux<T> saveAll(Mono<? extends Collection<? extends T>> entities, Class<T> clazz);
|
||||
|
||||
/**
|
||||
* Index entities in the given {@literal index}. If the {@literal index} is {@literal null} or empty the index name
|
||||
* provided via entity metadata is used.
|
||||
*
|
||||
* @param entities must not be {@literal null}.
|
||||
* @param index the target index, must not be {@literal null}
|
||||
@@ -114,6 +140,16 @@ public interface ReactiveDocumentOperations {
|
||||
*/
|
||||
<T> Flux<T> saveAll(Mono<? extends Collection<? extends T>> entities, IndexCoordinates index);
|
||||
|
||||
/**
|
||||
* Execute a multiGet against elasticsearch for the given ids.
|
||||
*
|
||||
* @param query the query defining the ids of the objects to get
|
||||
* @param clazz the type of the object to be returned, used to determine the index
|
||||
* @return flux with list of nullable objects
|
||||
* @since 4.1
|
||||
*/
|
||||
<T> Flux<T> multiGet(Query query, Class<T> clazz);
|
||||
|
||||
/**
|
||||
* Execute a multiGet against elasticsearch for the given ids.
|
||||
*
|
||||
@@ -223,7 +259,7 @@ public interface ReactiveDocumentOperations {
|
||||
Mono<Boolean> exists(String id, Class<?> entityType, IndexCoordinates index);
|
||||
|
||||
/**
|
||||
* Delete the given entity extracting index and type from entity metadata.
|
||||
* Delete the given entity extracting index from entity metadata.
|
||||
*
|
||||
* @param entity must not be {@literal null}.
|
||||
* @return a {@link Mono} emitting the {@literal id} of the removed document.
|
||||
@@ -231,7 +267,7 @@ public interface ReactiveDocumentOperations {
|
||||
Mono<String> delete(Object entity);
|
||||
|
||||
/**
|
||||
* Delete the given entity extracting index and type from entity metadata.
|
||||
* Delete the given entity extracting index from entity metadata.
|
||||
*
|
||||
* @param entity must not be {@literal null}.
|
||||
* @param index the target index, must not be {@literal null}
|
||||
@@ -249,7 +285,7 @@ public interface ReactiveDocumentOperations {
|
||||
Mono<String> delete(String id, IndexCoordinates index);
|
||||
|
||||
/**
|
||||
* Delete the entity with given {@literal id} extracting index and type from entity metadata.
|
||||
* Delete the entity with given {@literal id} extracting index from entity metadata.
|
||||
*
|
||||
* @param id must not be {@literal null}.
|
||||
* @param entityType must not be {@literal null}.
|
||||
@@ -259,7 +295,7 @@ public interface ReactiveDocumentOperations {
|
||||
Mono<String> delete(String id, Class<?> entityType);
|
||||
|
||||
/**
|
||||
* Delete the entity with given {@literal id} extracting index and type from entity metadata.
|
||||
* Delete the entity with given {@literal id} extracting index from entity metadata.
|
||||
*
|
||||
* @param id must not be {@literal null}.
|
||||
* @param entityType must not be {@literal null}.
|
||||
@@ -273,7 +309,7 @@ public interface ReactiveDocumentOperations {
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the documents matching the given {@link Query} extracting index and type from entity metadata.
|
||||
* Delete the documents matching the given {@link Query} extracting index from entity metadata.
|
||||
*
|
||||
* @param query must not be {@literal null}.
|
||||
* @param entityType must not be {@literal null}.
|
||||
@@ -282,7 +318,7 @@ public interface ReactiveDocumentOperations {
|
||||
Mono<Long> delete(Query query, Class<?> entityType);
|
||||
|
||||
/**
|
||||
* Delete the documents matching the given {@link Query} extracting index and type from entity metadata.
|
||||
* Delete the documents matching the given {@link Query} extracting index from entity metadata.
|
||||
*
|
||||
* @param query must not be {@literal null}.
|
||||
* @param entityType must not be {@literal null}.
|
||||
@@ -290,4 +326,14 @@ public interface ReactiveDocumentOperations {
|
||||
* @return a {@link Mono} emitting the number of the removed documents.
|
||||
*/
|
||||
Mono<Long> delete(Query query, Class<?> entityType, IndexCoordinates index);
|
||||
|
||||
/**
|
||||
* Partial update of the document.
|
||||
*
|
||||
* @param updateQuery query defining the update
|
||||
* @param index the index where to update the records
|
||||
* @return a {@link Mono} emitting the update response
|
||||
* @since 4.1
|
||||
*/
|
||||
Mono<UpdateResponse> update(UpdateQuery updateQuery, IndexCoordinates index);
|
||||
}
|
||||
|
||||
+38
-1
@@ -40,11 +40,21 @@ public interface ReactiveElasticsearchOperations extends ReactiveDocumentOperati
|
||||
* Execute within a {@link ClientCallback} managing resources and translating errors.
|
||||
*
|
||||
* @param callback must not be {@literal null}.
|
||||
* @param <T>
|
||||
* @param <T> the type the Publisher emits
|
||||
* @return the {@link Publisher} emitting results.
|
||||
*/
|
||||
<T> Publisher<T> execute(ClientCallback<Publisher<T>> callback);
|
||||
|
||||
/**
|
||||
* Execute within a {@link IndicesClientCallback} managing resources and translating errors.
|
||||
*
|
||||
* @param callback must not be {@literal null}.
|
||||
* @param <T> the type the Publisher emits
|
||||
* @return the {@link Publisher} emitting results.
|
||||
* @since 4.1
|
||||
*/
|
||||
<T> Publisher<T> executeWithIndicesClient(IndicesClientCallback<Publisher<T>> callback);
|
||||
|
||||
/**
|
||||
* Get the {@link ElasticsearchConverter} used.
|
||||
*
|
||||
@@ -62,6 +72,22 @@ public interface ReactiveElasticsearchOperations extends ReactiveDocumentOperati
|
||||
*/
|
||||
IndexCoordinates getIndexCoordinatesFor(Class<?> clazz);
|
||||
|
||||
/**
|
||||
* Creates a {@link ReactiveIndexOperations} that is bound to the given index
|
||||
* @param index IndexCoordinates specifying the index
|
||||
* @return ReactiveIndexOperations implementation
|
||||
* @since 4.1
|
||||
*/
|
||||
ReactiveIndexOperations indexOps(IndexCoordinates index);
|
||||
|
||||
/**
|
||||
* Creates a {@link ReactiveIndexOperations} that is bound to the given class
|
||||
* @param clazz the entity clazz specifiying the index information
|
||||
* @return ReactiveIndexOperations implementation
|
||||
* @since 4.1
|
||||
*/
|
||||
ReactiveIndexOperations indexOps(Class<?> clazz);
|
||||
|
||||
/**
|
||||
* Callback interface to be used with {@link #execute(ClientCallback)} for operating directly on
|
||||
* {@link ReactiveElasticsearchClient}.
|
||||
@@ -74,4 +100,15 @@ public interface ReactiveElasticsearchOperations extends ReactiveDocumentOperati
|
||||
|
||||
T doWithClient(ReactiveElasticsearchClient client);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback interface to be used with {@link #executeWithIndicesClient(IndicesClientCallback)} for operating directly on
|
||||
* {@link ReactiveElasticsearchClient.Indices}.
|
||||
*
|
||||
* @param <T> the return type
|
||||
* @since 4.1
|
||||
*/
|
||||
interface IndicesClientCallback<T extends Publisher<?>> {
|
||||
T doWithClient(ReactiveElasticsearchClient.Indices client);
|
||||
}
|
||||
}
|
||||
|
||||
+260
-229
@@ -15,20 +15,17 @@
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.core;
|
||||
|
||||
import static org.elasticsearch.index.VersionType.*;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.util.function.Tuple2;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.elasticsearch.action.DocWriteResponse;
|
||||
import org.elasticsearch.action.bulk.BulkItemResponse;
|
||||
import org.elasticsearch.action.bulk.BulkRequest;
|
||||
import org.elasticsearch.action.bulk.BulkResponse;
|
||||
@@ -41,19 +38,13 @@ 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.action.update.UpdateRequest;
|
||||
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.aggregations.Aggregation;
|
||||
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.elasticsearch.search.suggest.Suggest;
|
||||
import org.elasticsearch.search.suggest.SuggestBuilder;
|
||||
import org.reactivestreams.Publisher;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -61,17 +52,17 @@ import org.springframework.beans.BeansException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.data.convert.EntityReader;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.elasticsearch.ElasticsearchException;
|
||||
import org.springframework.data.elasticsearch.BulkFailureException;
|
||||
import org.springframework.data.elasticsearch.NoSuchIndexException;
|
||||
import org.springframework.data.elasticsearch.UncategorizedElasticsearchException;
|
||||
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.convert.ElasticsearchConverter;
|
||||
import org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter;
|
||||
import org.springframework.data.elasticsearch.core.document.Document;
|
||||
import org.springframework.data.elasticsearch.core.document.DocumentAdapters;
|
||||
import org.springframework.data.elasticsearch.core.document.SearchDocument;
|
||||
import org.springframework.data.elasticsearch.core.document.SearchDocumentResponse;
|
||||
import org.springframework.data.elasticsearch.core.event.ReactiveAfterConvertCallback;
|
||||
import org.springframework.data.elasticsearch.core.event.ReactiveAfterSaveCallback;
|
||||
import org.springframework.data.elasticsearch.core.event.ReactiveBeforeConvertCallback;
|
||||
@@ -80,14 +71,13 @@ import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersiste
|
||||
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
||||
import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext;
|
||||
import org.springframework.data.elasticsearch.core.query.BulkOptions;
|
||||
import org.springframework.data.elasticsearch.core.query.CriteriaQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.IndexQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.Query;
|
||||
import org.springframework.data.elasticsearch.core.query.SeqNoPrimaryTerm;
|
||||
import org.springframework.data.elasticsearch.core.query.StringQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.UpdateQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.UpdateResponse;
|
||||
import org.springframework.data.elasticsearch.support.VersionInfo;
|
||||
import org.springframework.data.mapping.PersistentPropertyAccessor;
|
||||
import org.springframework.data.mapping.callback.ReactiveEntityCallbacks;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.http.HttpStatus;
|
||||
@@ -105,6 +95,7 @@ import org.springframework.util.Assert;
|
||||
* @author Aleksei Arsenev
|
||||
* @author Roman Puchkovskiy
|
||||
* @author Russell Parry
|
||||
* @author Thomas Geese
|
||||
* @since 3.2
|
||||
*/
|
||||
public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOperations, ApplicationContextAware {
|
||||
@@ -159,6 +150,32 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the current {@link RefreshPolicy}.
|
||||
*/
|
||||
@Nullable
|
||||
public RefreshPolicy getRefreshPolicy() {
|
||||
return 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link ReactiveEntityCallbacks} instance to use when invoking {@link ReactiveEntityCallbacks callbacks}
|
||||
* like the {@link ReactiveBeforeConvertCallback}.
|
||||
@@ -175,7 +192,6 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
||||
|
||||
this.entityCallbacks = entityCallbacks;
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region DocumentOperations
|
||||
@@ -193,8 +209,8 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
||||
.map(it -> {
|
||||
T savedEntity = it.getT1();
|
||||
IndexResponse indexResponse = it.getT2();
|
||||
AdaptibleEntity<T> adaptableEntity = operations.forEntity(savedEntity, converter.getConversionService());
|
||||
return adaptableEntity.populateIdIfNecessary(indexResponse.getId());
|
||||
return updateIndexedObject(savedEntity, IndexedObjectInformation.of(indexResponse.getId(),
|
||||
indexResponse.getSeqNo(), indexResponse.getPrimaryTerm(), indexResponse.getVersion()));
|
||||
}).flatMap(saved -> maybeCallAfterSave(saved, index));
|
||||
}
|
||||
|
||||
@@ -203,30 +219,70 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
||||
return save(entity, getIndexCoordinatesFor(entity.getClass()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Flux<T> saveAll(Mono<? extends Collection<? extends T>> entities, Class<T> clazz) {
|
||||
return saveAll(entities, getIndexCoordinatesFor(clazz));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Flux<T> saveAll(Mono<? extends Collection<? extends T>> entitiesPublisher, IndexCoordinates index) {
|
||||
|
||||
Assert.notNull(entitiesPublisher, "Entities must not be null!");
|
||||
|
||||
return entitiesPublisher.flatMapMany(entities -> {
|
||||
return Flux.fromIterable(entities) //
|
||||
.concatMap(entity -> maybeCallBeforeConvert(entity, index));
|
||||
}).collectList().map(Entities::new).flatMapMany(entities -> {
|
||||
if (entities.isEmpty()) {
|
||||
return Flux.empty();
|
||||
}
|
||||
return entitiesPublisher //
|
||||
.flatMapMany(entities -> Flux.fromIterable(entities) //
|
||||
.concatMap(entity -> maybeCallBeforeConvert(entity, index)) //
|
||||
).collectList() //
|
||||
.map(Entities::new) //
|
||||
.flatMapMany(entities -> {
|
||||
|
||||
return doBulkOperation(entities.indexQueries(), BulkOptions.defaultOptions(), index) //
|
||||
.index().flatMap(indexAndResponse -> {
|
||||
T savedEntity = entities.entityAt(indexAndResponse.getT1());
|
||||
BulkItemResponse bulkItemResponse = indexAndResponse.getT2();
|
||||
if (entities.isEmpty()) {
|
||||
return Flux.empty();
|
||||
}
|
||||
|
||||
AdaptibleEntity<T> adaptibleEntity = operations.forEntity(savedEntity, converter.getConversionService());
|
||||
adaptibleEntity.populateIdIfNecessary(bulkItemResponse.getResponse().getId());
|
||||
return doBulkOperation(entities.indexQueries(), BulkOptions.defaultOptions(), index) //
|
||||
.index().flatMap(indexAndResponse -> {
|
||||
T savedEntity = entities.entityAt(indexAndResponse.getT1());
|
||||
BulkItemResponse bulkItemResponse = indexAndResponse.getT2();
|
||||
|
||||
return maybeCallAfterSave(savedEntity, index);
|
||||
});
|
||||
});
|
||||
DocWriteResponse response = bulkItemResponse.getResponse();
|
||||
updateIndexedObject(savedEntity, IndexedObjectInformation.of(response.getId(), response.getSeqNo(),
|
||||
response.getPrimaryTerm(), response.getVersion()));
|
||||
|
||||
return maybeCallAfterSave(savedEntity, index);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private <T> T updateIndexedObject(T entity, IndexedObjectInformation indexedObjectInformation) {
|
||||
AdaptibleEntity<T> adaptibleEntity = operations.forEntity(entity, converter.getConversionService());
|
||||
adaptibleEntity.populateIdIfNecessary(indexedObjectInformation.getId());
|
||||
|
||||
ElasticsearchPersistentEntity<?> persistentEntity = getRequiredPersistentEntity(entity.getClass());
|
||||
PersistentPropertyAccessor<Object> propertyAccessor = persistentEntity.getPropertyAccessor(entity);
|
||||
|
||||
if (indexedObjectInformation.getSeqNo() != null && indexedObjectInformation.getPrimaryTerm() != null
|
||||
&& persistentEntity.hasSeqNoPrimaryTermProperty()) {
|
||||
ElasticsearchPersistentProperty seqNoPrimaryTermProperty = persistentEntity.getSeqNoPrimaryTermProperty();
|
||||
propertyAccessor.setProperty(seqNoPrimaryTermProperty,
|
||||
new SeqNoPrimaryTerm(indexedObjectInformation.getSeqNo(), indexedObjectInformation.getPrimaryTerm()));
|
||||
}
|
||||
|
||||
if (indexedObjectInformation.getVersion() != null && persistentEntity.hasVersionProperty()) {
|
||||
ElasticsearchPersistentProperty versionProperty = persistentEntity.getVersionProperty();
|
||||
propertyAccessor.setProperty(versionProperty, indexedObjectInformation.getVersion());
|
||||
}
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
||||
private ElasticsearchPersistentEntity<?> getRequiredPersistentEntity(Class<?> clazz) {
|
||||
return converter.getMappingContext().getRequiredPersistentEntity(clazz);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Flux<T> multiGet(Query query, Class<T> clazz) {
|
||||
return multiGet(query, clazz, getIndexCoordinatesFor(clazz));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -239,9 +295,9 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
||||
|
||||
DocumentCallback<T> callback = new ReadDocumentCallback<>(converter, clazz, index);
|
||||
|
||||
MultiGetRequest request = requestFactory.multiGetRequest(query, index);
|
||||
MultiGetRequest request = requestFactory.multiGetRequest(query, clazz, index);
|
||||
return Flux.from(execute(client -> client.multiGet(request))) //
|
||||
.concatMap(result -> callback.doWith(DocumentAdapters.from(result)));
|
||||
.concatMap(result -> callback.toEntity(DocumentAdapters.from(result)));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -268,7 +324,8 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
||||
protected Flux<BulkItemResponse> doBulkOperation(List<?> queries, BulkOptions bulkOptions, IndexCoordinates index) {
|
||||
BulkRequest bulkRequest = prepareWriteRequest(requestFactory.bulkRequest(queries, bulkOptions, index));
|
||||
return client.bulk(bulkRequest) //
|
||||
.onErrorMap(e -> new ElasticsearchException("Error while bulk for request: " + bulkRequest.toString(), e)) //
|
||||
.onErrorMap(
|
||||
e -> new UncategorizedElasticsearchException("Error while bulk for request: " + bulkRequest.toString(), e)) //
|
||||
.flatMap(this::checkForBulkOperationFailure) //
|
||||
.flatMapMany(response -> Flux.fromArray(response.getItems()));
|
||||
}
|
||||
@@ -283,7 +340,7 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
||||
failedDocuments.put(item.getId(), item.getFailureMessage());
|
||||
}
|
||||
}
|
||||
ElasticsearchException exception = new ElasticsearchException(
|
||||
BulkFailureException exception = new BulkFailureException(
|
||||
"Bulk operation has failures. Use ElasticsearchException.getFailedDocuments() for detailed messages ["
|
||||
+ failedDocuments + ']',
|
||||
failedDocuments);
|
||||
@@ -315,9 +372,8 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
||||
return doExists(id, index);
|
||||
}
|
||||
|
||||
private Mono<Boolean> doExists(String id, @Nullable IndexCoordinates index) {
|
||||
|
||||
return Mono.defer(() -> doExists(new GetRequest(index.getIndexName(), id)));
|
||||
private Mono<Boolean> doExists(String id, IndexCoordinates index) {
|
||||
return Mono.defer(() -> doExists(requestFactory.getRequest(id, index)));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -334,27 +390,30 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
||||
|
||||
private <T> Mono<Tuple2<T, IndexResponse>> doIndex(T entity, IndexCoordinates index) {
|
||||
|
||||
AdaptibleEntity<?> adaptibleEntity = operations.forEntity(entity, converter.getConversionService());
|
||||
IndexRequest request = getIndexRequest(entity, adaptibleEntity, index);
|
||||
IndexRequest request = requestFactory.indexRequest(getIndexQuery(entity), index);
|
||||
request = prepareIndexRequest(entity, request);
|
||||
return Mono.just(entity).zipWith(doIndex(request));
|
||||
}
|
||||
|
||||
private IndexRequest getIndexRequest(Object value, AdaptibleEntity<?> entity, IndexCoordinates index) {
|
||||
private IndexQuery getIndexQuery(Object value) {
|
||||
AdaptibleEntity<?> entity = operations.forEntity(value, converter.getConversionService());
|
||||
|
||||
Object id = entity.getId();
|
||||
IndexQuery query = new IndexQuery();
|
||||
|
||||
IndexRequest request = id != null ? new IndexRequest(index.getIndexName()).id(converter.convertId(id))
|
||||
: new IndexRequest(index.getIndexName());
|
||||
|
||||
request.source(converter.mapObject(value).toJson(), Requests.INDEX_CONTENT_TYPE);
|
||||
if (id != null) {
|
||||
query.setId(id.toString());
|
||||
}
|
||||
query.setObject(value);
|
||||
|
||||
boolean usingSeqNo = false;
|
||||
|
||||
if (entity.hasSeqNoPrimaryTerm()) {
|
||||
SeqNoPrimaryTerm seqNoPrimaryTerm = entity.getSeqNoPrimaryTerm();
|
||||
|
||||
if (seqNoPrimaryTerm != null) {
|
||||
request.setIfSeqNo(seqNoPrimaryTerm.getSequenceNumber());
|
||||
request.setIfPrimaryTerm(seqNoPrimaryTerm.getPrimaryTerm());
|
||||
query.setSeqNo(seqNoPrimaryTerm.getSequenceNumber());
|
||||
query.setPrimaryTerm(seqNoPrimaryTerm.getPrimaryTerm());
|
||||
usingSeqNo = true;
|
||||
}
|
||||
}
|
||||
@@ -364,32 +423,13 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
||||
|
||||
Number version = entity.getVersion();
|
||||
|
||||
if (version != null) {
|
||||
request.version(version.longValue());
|
||||
request.versionType(EXTERNAL);
|
||||
}
|
||||
}
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
private IndexQuery getIndexQuery(Object value) {
|
||||
AdaptibleEntity<?> entity = operations.forEntity(value, converter.getConversionService());
|
||||
|
||||
Object id = entity.getId();
|
||||
IndexQuery query = new IndexQuery();
|
||||
if (id != null) {
|
||||
query.setId(id.toString());
|
||||
}
|
||||
query.setObject(value);
|
||||
|
||||
if (entity.isVersionedEntity()) {
|
||||
Number version = entity.getVersion();
|
||||
|
||||
if (version != null) {
|
||||
query.setVersion(version.longValue());
|
||||
}
|
||||
}
|
||||
|
||||
query.setRouting(entity.getRouting());
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
@@ -405,14 +445,11 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
||||
|
||||
DocumentCallback<T> callback = new ReadDocumentCallback<>(converter, entityType, index);
|
||||
|
||||
return doGet(id, getPersistentEntityFor(entityType), index)
|
||||
.flatMap(it -> callback.doWith(DocumentAdapters.from(it)));
|
||||
return doGet(id, index).flatMap(it -> callback.toEntity(DocumentAdapters.from(it)));
|
||||
}
|
||||
|
||||
private Mono<GetResult> doGet(String id, ElasticsearchPersistentEntity<?> entity, IndexCoordinates index) {
|
||||
return Mono.defer(() -> {
|
||||
return doGet(new GetRequest(index.getIndexName(), id));
|
||||
});
|
||||
private Mono<GetResult> doGet(String id, IndexCoordinates index) {
|
||||
return Mono.defer(() -> doGet(requestFactory.getRequest(id, index)));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -434,9 +471,17 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
||||
@Override
|
||||
public Mono<String> delete(Object entity, IndexCoordinates index) {
|
||||
|
||||
Entity<?> elasticsearchEntity = operations.forEntity(entity);
|
||||
AdaptibleEntity<?> elasticsearchEntity = operations.forEntity(entity, converter.getConversionService());
|
||||
|
||||
return Mono.defer(() -> doDeleteById(converter.convertId(elasticsearchEntity.getId()), index));
|
||||
if (elasticsearchEntity.getId() == null) {
|
||||
return Mono.error(new IllegalArgumentException("entity must have an id"));
|
||||
}
|
||||
|
||||
return Mono.defer(() -> {
|
||||
String id = converter.convertId(elasticsearchEntity.getId());
|
||||
String routing = elasticsearchEntity.getRouting();
|
||||
return doDeleteById(id, routing, index);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -459,14 +504,14 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
||||
Assert.notNull(id, "id must not be null");
|
||||
Assert.notNull(index, "index must not be null");
|
||||
|
||||
return doDeleteById(id, index);
|
||||
return doDeleteById(id, null, index);
|
||||
}
|
||||
|
||||
private Mono<String> doDeleteById(String id, IndexCoordinates index) {
|
||||
private Mono<String> doDeleteById(String id, @Nullable String routing, IndexCoordinates index) {
|
||||
|
||||
return Mono.defer(() -> {
|
||||
|
||||
return doDelete(prepareDeleteRequest(new DeleteRequest(index.getIndexName(), id)));
|
||||
DeleteRequest request = requestFactory.deleteRequest(id, routing, index);
|
||||
return doDelete(prepareDeleteRequest(request));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -479,8 +524,20 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
||||
|
||||
Assert.notNull(query, "Query must not be null!");
|
||||
|
||||
return doDeleteBy(query, getPersistentEntityFor(entityType), index).map(BulkByScrollResponse::getDeleted)
|
||||
.publishNext();
|
||||
return doDeleteBy(query, entityType, index).map(BulkByScrollResponse::getDeleted).next();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<UpdateResponse> update(UpdateQuery updateQuery, IndexCoordinates index) {
|
||||
|
||||
Assert.notNull(updateQuery, "UpdateQuery must not be null");
|
||||
Assert.notNull(index, "Index must not be null");
|
||||
|
||||
return Mono.defer(() -> {
|
||||
UpdateRequest request = requestFactory.updateRequest(updateQuery, index);
|
||||
return Mono.from(execute(client -> client.update(request)))
|
||||
.map(response -> new UpdateResponse(UpdateResponse.Result.valueOf(response.getResult().name())));
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -488,13 +545,10 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
||||
return delete(query, entityType, getIndexCoordinatesFor(entityType));
|
||||
}
|
||||
|
||||
private Flux<BulkByScrollResponse> doDeleteBy(Query query, ElasticsearchPersistentEntity<?> entity,
|
||||
IndexCoordinates index) {
|
||||
private Flux<BulkByScrollResponse> doDeleteBy(Query query, Class<?> entityType, IndexCoordinates index) {
|
||||
|
||||
return Flux.defer(() -> {
|
||||
DeleteByQueryRequest request = new DeleteByQueryRequest(index.getIndexNames());
|
||||
request.setQuery(mappedQuery(query, entity));
|
||||
|
||||
DeleteByQueryRequest request = requestFactory.deleteByQueryRequest(query, entityType, index);
|
||||
return doDeleteBy(prepareDeleteByRequest(request));
|
||||
});
|
||||
}
|
||||
@@ -552,8 +606,13 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
||||
*/
|
||||
protected DeleteByQueryRequest prepareDeleteByRequest(DeleteByQueryRequest request) {
|
||||
|
||||
if (refreshPolicy != null && !RefreshPolicy.NONE.equals(refreshPolicy)) {
|
||||
request = request.setRefresh(true);
|
||||
if (refreshPolicy != null) {
|
||||
|
||||
if (RefreshPolicy.NONE.equals(refreshPolicy)) {
|
||||
request = request.setRefresh(false);
|
||||
} else {
|
||||
request = request.setRefresh(true);
|
||||
}
|
||||
}
|
||||
|
||||
if (indicesOptions != null) {
|
||||
@@ -598,7 +657,7 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
||||
@Override
|
||||
public <T> Flux<SearchHit<T>> search(Query query, Class<?> entityType, Class<T> resultType, IndexCoordinates index) {
|
||||
SearchDocumentCallback<T> callback = new ReadSearchDocumentCallback<>(resultType, index);
|
||||
return doFind(query, entityType, index).concatMap(callback::doWith);
|
||||
return doFind(query, entityType, index).concatMap(callback::toSearchHit);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -606,11 +665,27 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
||||
return search(query, entityType, returnType, getIndexCoordinatesFor(entityType));
|
||||
}
|
||||
|
||||
private Flux<SearchDocument> doFind(Query query, Class<?> clazz, IndexCoordinates index) {
|
||||
@Override
|
||||
public <T> Mono<SearchPage<T>> searchForPage(Query query, Class<?> entityType, Class<T> resultType) {
|
||||
return searchForPage(query, entityType, resultType, getIndexCoordinatesFor(entityType));
|
||||
}
|
||||
|
||||
if (query instanceof CriteriaQuery) {
|
||||
converter.updateQuery((CriteriaQuery) query, clazz);
|
||||
}
|
||||
@Override
|
||||
public <T> Mono<SearchPage<T>> searchForPage(Query query, Class<?> entityType, Class<T> resultType,
|
||||
IndexCoordinates index) {
|
||||
|
||||
SearchDocumentCallback<T> callback = new ReadSearchDocumentCallback<>(resultType, index);
|
||||
|
||||
return doFindForResponse(query, entityType, index) //
|
||||
.flatMap(searchDocumentResponse -> Flux.fromIterable(searchDocumentResponse.getSearchDocuments()) //
|
||||
.flatMap(callback::toEntity) //
|
||||
.collectList() //
|
||||
.map(entities -> SearchHitMapping.mappingFor(resultType, converter) //
|
||||
.mapHits(searchDocumentResponse, entities))) //
|
||||
.map(searchHits -> SearchHitSupport.searchPageFor(searchHits, query.getPageable()));
|
||||
}
|
||||
|
||||
private Flux<SearchDocument> doFind(Query query, Class<?> clazz, IndexCoordinates index) {
|
||||
|
||||
return Flux.defer(() -> {
|
||||
SearchRequest request = requestFactory.searchRequest(query, clazz, index);
|
||||
@@ -624,6 +699,15 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
||||
});
|
||||
}
|
||||
|
||||
private Mono<SearchDocumentResponse> doFindForResponse(Query query, Class<?> clazz, IndexCoordinates index) {
|
||||
|
||||
return Mono.defer(() -> {
|
||||
SearchRequest request = requestFactory.searchRequest(query, clazz, index);
|
||||
request = prepareSearchRequest(request);
|
||||
return doFindForResponse(request);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<Aggregation> aggregate(Query query, Class<?> entityType) {
|
||||
return aggregate(query, entityType, getIndexCoordinatesFor(entityType));
|
||||
@@ -634,6 +718,23 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
||||
return doAggregate(query, entityType, index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<Suggest> suggest(SuggestBuilder suggestion, Class<?> entityType) {
|
||||
return doSuggest(suggestion, getIndexCoordinatesFor(entityType));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<Suggest> suggest(SuggestBuilder suggestion, IndexCoordinates index) {
|
||||
return doSuggest(suggestion, index);
|
||||
}
|
||||
|
||||
private Flux<Suggest> doSuggest(SuggestBuilder suggestion, IndexCoordinates index) {
|
||||
return Flux.defer(() -> {
|
||||
SearchRequest request = requestFactory.searchRequest(suggestion, index);
|
||||
return Flux.from(execute(client -> client.suggest(request)));
|
||||
});
|
||||
}
|
||||
|
||||
private Flux<Aggregation> doAggregate(Query query, Class<?> entityType, IndexCoordinates index) {
|
||||
return Flux.defer(() -> {
|
||||
SearchRequest request = requestFactory.searchRequest(query, entityType, index);
|
||||
@@ -661,43 +762,6 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
||||
});
|
||||
}
|
||||
|
||||
private CountRequest buildCountRequest(Query query, ElasticsearchPersistentEntity<?> entity, IndexCoordinates index) {
|
||||
|
||||
CountRequest request = new CountRequest(index.getIndexNames());
|
||||
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 NativeSearchQuery && ((NativeSearchQuery) query).getCollapseBuilder() != null) {
|
||||
searchSourceBuilder.collapse(((NativeSearchQuery) 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Customization hook on the actual execution result {@link Publisher}. <br />
|
||||
*
|
||||
@@ -714,6 +778,21 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
||||
.onErrorResume(NoSuchIndexException.class, it -> Mono.empty());
|
||||
}
|
||||
|
||||
/**
|
||||
* Customization hook on the actual execution result {@link Mono}. <br />
|
||||
*
|
||||
* @param request the already prepared {@link SearchRequest} ready to be executed.
|
||||
* @return a {@link Mono} emitting the result of the operation converted to s {@link SearchDocumentResponse}.
|
||||
*/
|
||||
protected Mono<SearchDocumentResponse> doFindForResponse(SearchRequest request) {
|
||||
|
||||
if (QUERY_LOGGER.isDebugEnabled()) {
|
||||
QUERY_LOGGER.debug("Executing doFindForResponse: {}", request);
|
||||
}
|
||||
|
||||
return Mono.from(execute(client1 -> client1.searchForResponse(request))).map(SearchDocumentResponse::from);
|
||||
}
|
||||
|
||||
/**
|
||||
* Customization hook on the actual execution result {@link Publisher}. <br />
|
||||
*
|
||||
@@ -762,61 +841,6 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
||||
.map(DocumentAdapters::from).onErrorResume(NoSuchIndexException.class, it -> Mono.empty());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private QueryBuilder mappedFilterQuery(Query query, ElasticsearchPersistentEntity<?> entity) {
|
||||
|
||||
if (query instanceof NativeSearchQuery) {
|
||||
return ((NativeSearchQuery) query).getFilter();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private QueryBuilder mappedQuery(Query query, ElasticsearchPersistentEntity<?> entity) {
|
||||
|
||||
QueryBuilder elasticsearchQuery = null;
|
||||
|
||||
if (query instanceof CriteriaQuery) {
|
||||
converter.updateQuery((CriteriaQuery) query, entity.getType());
|
||||
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();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Customization hook to modify a generated {@link SearchRequest} prior to its execution. Eg. by setting the
|
||||
* {@link SearchRequest#indicesOptions(IndicesOptions) indices options} if applicable.
|
||||
@@ -845,44 +869,31 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
||||
|
||||
// endregion
|
||||
|
||||
// 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#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#getElasticsearchConverter()
|
||||
*/
|
||||
@Override
|
||||
public <T> Publisher<T> executeWithIndicesClient(IndicesClientCallback<Publisher<T>> callback) {
|
||||
return Flux.defer(() -> callback.doWithClient(getIndicesClient())).onErrorMap(this::translateException);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ElasticsearchConverter getElasticsearchConverter() {
|
||||
return converter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReactiveIndexOperations indexOps(IndexCoordinates index) {
|
||||
return new DefaultReactiveIndexOperations(this, index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReactiveIndexOperations indexOps(Class<?> clazz) {
|
||||
return new DefaultReactiveIndexOperations(this, clazz);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IndexCoordinates getIndexCoordinatesFor(Class<?> clazz) {
|
||||
return getPersistentEntityFor(clazz).getIndexCoordinates();
|
||||
@@ -903,6 +914,20 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
||||
return this.client;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain the {@link ReactiveElasticsearchClient.Indices} to operate upon.
|
||||
*
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
protected ReactiveElasticsearchClient.Indices getIndicesClient() {
|
||||
|
||||
if (client instanceof ReactiveElasticsearchClient.Indices) {
|
||||
return (ReactiveElasticsearchClient.Indices) client;
|
||||
}
|
||||
|
||||
throw new UncategorizedElasticsearchException("No ReactiveElasticsearchClient.Indices implementation available");
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
/**
|
||||
@@ -950,13 +975,12 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
||||
|
||||
return Mono.just(entity);
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
protected interface DocumentCallback<T> {
|
||||
|
||||
@NonNull
|
||||
Mono<T> doWith(@Nullable Document document);
|
||||
Mono<T> toEntity(@Nullable Document document);
|
||||
}
|
||||
|
||||
protected class ReadDocumentCallback<T> implements DocumentCallback<T> {
|
||||
@@ -974,7 +998,7 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Mono<T> doWith(@Nullable Document document) {
|
||||
public Mono<T> toEntity(@Nullable Document document) {
|
||||
if (document == null) {
|
||||
return Mono.empty();
|
||||
}
|
||||
@@ -987,7 +1011,10 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
||||
protected interface SearchDocumentCallback<T> {
|
||||
|
||||
@NonNull
|
||||
Mono<SearchHit<T>> doWith(@NonNull SearchDocument response);
|
||||
Mono<T> toEntity(@NonNull SearchDocument response);
|
||||
|
||||
@NonNull
|
||||
Mono<SearchHit<T>> toSearchHit(@NonNull SearchDocument response);
|
||||
}
|
||||
|
||||
protected class ReadSearchDocumentCallback<T> implements SearchDocumentCallback<T> {
|
||||
@@ -1002,9 +1029,13 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<SearchHit<T>> doWith(SearchDocument response) {
|
||||
return delegate.doWith(response)
|
||||
.map(entity -> SearchHitMapping.mappingFor(type, converter.getMappingContext()).mapHit(response, entity));
|
||||
public Mono<T> toEntity(SearchDocument response) {
|
||||
return delegate.toEntity(response);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<SearchHit<T>> toSearchHit(SearchDocument response) {
|
||||
return toEntity(response).map(entity -> SearchHitMapping.mappingFor(type, converter).mapHit(response, entity));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+288
@@ -0,0 +1,288 @@
|
||||
/*
|
||||
* Copyright 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.Mono;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.data.elasticsearch.core.document.Document;
|
||||
import org.springframework.data.elasticsearch.core.index.AliasActions;
|
||||
import org.springframework.data.elasticsearch.core.index.AliasData;
|
||||
import org.springframework.data.elasticsearch.core.index.DeleteTemplateRequest;
|
||||
import org.springframework.data.elasticsearch.core.index.ExistsTemplateRequest;
|
||||
import org.springframework.data.elasticsearch.core.index.GetTemplateRequest;
|
||||
import org.springframework.data.elasticsearch.core.index.PutTemplateRequest;
|
||||
import org.springframework.data.elasticsearch.core.index.TemplateData;
|
||||
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
||||
|
||||
/**
|
||||
* Interface defining operations on indexes for the reactive stack.
|
||||
*
|
||||
* @author Peter-Josef Meisch
|
||||
* @since 4.1
|
||||
*/
|
||||
public interface ReactiveIndexOperations {
|
||||
|
||||
// region index management
|
||||
/**
|
||||
* Create an index.
|
||||
*
|
||||
* @return a {@link Mono} signalling successful operation completion or an {@link Mono#error(Throwable) error} if eg.
|
||||
* the index already exist.
|
||||
*/
|
||||
Mono<Boolean> create();
|
||||
|
||||
/**
|
||||
* Create an index with the specified settings.
|
||||
*
|
||||
* @param settings index settings
|
||||
* @return a {@link Mono} signalling successful operation completion or an {@link Mono#error(Throwable) error} if eg.
|
||||
* the index already exist.
|
||||
*/
|
||||
Mono<Boolean> create(Document settings);
|
||||
|
||||
/**
|
||||
* Delete an index.
|
||||
*
|
||||
* @return a {@link Mono} signalling operation completion or an {@link Mono#error(Throwable) error}. If the index does
|
||||
* not exist, a value of {@literal false is emitted}.
|
||||
*/
|
||||
Mono<Boolean> delete();
|
||||
|
||||
/**
|
||||
* checks if an index exists
|
||||
*
|
||||
* @return a {@link Mono} with the result of exist check
|
||||
*/
|
||||
Mono<Boolean> exists();
|
||||
|
||||
/**
|
||||
* Refresh the index(es) this IndexOperations is bound to
|
||||
*
|
||||
* @return a {@link Mono} signalling operation completion.
|
||||
*/
|
||||
Mono<Void> refresh();
|
||||
// endregion
|
||||
|
||||
// region mappings
|
||||
/**
|
||||
* Creates the index mapping for the entity this IndexOperations is bound to.
|
||||
*
|
||||
* @return mapping object
|
||||
*/
|
||||
Mono<Document> createMapping();
|
||||
|
||||
/**
|
||||
* Creates the index mapping for the given class
|
||||
*
|
||||
* @param clazz the clazz to create a mapping for
|
||||
* @return a {@link Mono} with the mapping document
|
||||
*/
|
||||
Mono<Document> createMapping(Class<?> clazz);
|
||||
|
||||
/**
|
||||
* Writes the mapping to the index for the class this IndexOperations is bound to.
|
||||
*
|
||||
* @return {@literal true} if the mapping could be stored
|
||||
*/
|
||||
default Mono<Boolean> putMapping() {
|
||||
return putMapping(createMapping());
|
||||
}
|
||||
|
||||
/**
|
||||
* writes a mapping to the index
|
||||
*
|
||||
* @param mapping the Document with the mapping definitions
|
||||
* @return {@literal true} if the mapping could be stored
|
||||
*/
|
||||
Mono<Boolean> putMapping(Mono<Document> mapping);
|
||||
|
||||
/**
|
||||
* Creates the index mapping for the given class and writes it to the index.
|
||||
*
|
||||
* @param clazz the clazz to create a mapping for
|
||||
* @return {@literal true} if the mapping could be stored
|
||||
*/
|
||||
default Mono<Boolean> putMapping(Class<?> clazz) {
|
||||
return putMapping(createMapping(clazz));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get mapping for the index targeted defined by this {@link ReactiveIndexOperations}
|
||||
*
|
||||
* @return the mapping
|
||||
*/
|
||||
Mono<Document> getMapping();
|
||||
// endregion
|
||||
|
||||
// region settings
|
||||
/**
|
||||
* Creates the index settings for the entity this IndexOperations is bound to.
|
||||
*
|
||||
* @return a settings document.
|
||||
* @since 4.1
|
||||
*/
|
||||
Mono<Document> createSettings();
|
||||
|
||||
/**
|
||||
* Creates the index settings from the annotations on the given class
|
||||
*
|
||||
* @param clazz the class to create the index settings from
|
||||
* @return a settings document.
|
||||
* @since 4.1
|
||||
*/
|
||||
Mono<Document> createSettings(Class<?> clazz);
|
||||
|
||||
/**
|
||||
* get the settings for the index
|
||||
*
|
||||
* @return a {@link Mono} with a {@link Document} containing the index settings
|
||||
*/
|
||||
default Mono<Document> getSettings() {
|
||||
return getSettings(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* get the settings for the index
|
||||
*
|
||||
* @param includeDefaults whether or not to include all the default settings
|
||||
* @return a {@link Mono} with a {@link Document} containing the index settings
|
||||
*/
|
||||
Mono<Document> getSettings(boolean includeDefaults);
|
||||
// endregion
|
||||
|
||||
// region aliases
|
||||
/**
|
||||
* Executes the given {@link AliasActions}.
|
||||
*
|
||||
* @param aliasActions the actions to execute
|
||||
* @return if the operation is acknowledged by Elasticsearch
|
||||
* @since 4.1
|
||||
*/
|
||||
Mono<Boolean> alias(AliasActions aliasActions);
|
||||
|
||||
/**
|
||||
* gets information about aliases
|
||||
*
|
||||
* @param aliasNames alias names, must not be {@literal null}
|
||||
* @return a {@link Mono} of {@link Map} from index names to {@link AliasData} for that index
|
||||
* @since 4.1
|
||||
*/
|
||||
Mono<Map<String, Set<AliasData>>> getAliases(String... aliasNames);
|
||||
|
||||
/**
|
||||
* gets information about aliases
|
||||
*
|
||||
* @param indexNames alias names, must not be {@literal null}
|
||||
* @return a {@link Mono} of {@link Map} from index names to {@link AliasData} for that index
|
||||
* @since 4.1
|
||||
*/
|
||||
Mono<Map<String, Set<AliasData>>> getAliasesForIndex(String... indexNames);
|
||||
// endregion
|
||||
|
||||
// region templates
|
||||
/**
|
||||
* Creates an index template using the legacy Elasticsearch interface (@see
|
||||
* https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-templates-v1.html)
|
||||
*
|
||||
* @param putTemplateRequest template request parameters
|
||||
* @return Mono of {@literal true} if the template could be stored
|
||||
* @since 4.1
|
||||
*/
|
||||
Mono<Boolean> putTemplate(PutTemplateRequest putTemplateRequest);
|
||||
|
||||
/**
|
||||
* gets an index template using the legacy Elasticsearch
|
||||
* interface/Users/peter/Entwicklung/Projekte/spring-data-elasticsearch/src/main/java/org/springframework/data/elasticsearch/core/IndexOperations.java.
|
||||
*
|
||||
* @param templateName the template name
|
||||
* @return Mono of TemplateData, {@literal Mono.empty()} if no template with the given name exists.
|
||||
* @since 4.1
|
||||
*/
|
||||
default Mono<TemplateData> getTemplate(String templateName) {
|
||||
return getTemplate(new GetTemplateRequest(templateName));
|
||||
}
|
||||
|
||||
/**
|
||||
* gets an index template using the legacy Elasticsearch
|
||||
* interface/Users/peter/Entwicklung/Projekte/spring-data-elasticsearch/src/main/java/org/springframework/data/elasticsearch/core/IndexOperations.java.
|
||||
*
|
||||
* @param getTemplateRequest the request parameters
|
||||
* @return Mono of TemplateData, {@literal Mono.empty()} if no template with the given name exists.
|
||||
* @since 4.1
|
||||
*/
|
||||
Mono<TemplateData> getTemplate(GetTemplateRequest getTemplateRequest);
|
||||
|
||||
/**
|
||||
* Checks if an index template exists using the legacy Elasticsearch interface (@see
|
||||
* https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-templates-v1.html)
|
||||
*
|
||||
* @param templateName the template name
|
||||
* @return Mono of {@literal true} if the template exists
|
||||
* @since 4.1
|
||||
*/
|
||||
default Mono<Boolean> existsTemplate(String templateName) {
|
||||
return existsTemplate(new ExistsTemplateRequest(templateName));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an index template exists using the legacy Elasticsearch interface (@see
|
||||
* https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-templates-v1.html)
|
||||
*
|
||||
* @param existsTemplateRequest template request parameters
|
||||
* @return Mono of {@literal true} if the template exists
|
||||
* @since 4.1
|
||||
*/
|
||||
Mono<Boolean> existsTemplate(ExistsTemplateRequest existsTemplateRequest);
|
||||
|
||||
/**
|
||||
* Deletes an index template using the legacy Elasticsearch interface (@see
|
||||
* https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-templates-v1.html)
|
||||
*
|
||||
* @param templateName the template name
|
||||
* @return Mono of {@literal true} if the template could be deleted
|
||||
* @since 4.1
|
||||
*/
|
||||
default Mono<Boolean> deleteTemplate(String templateName) {
|
||||
return deleteTemplate(new DeleteTemplateRequest(templateName));
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes an index template using the legacy Elasticsearch interface (@see
|
||||
* https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-templates-v1.html)
|
||||
*
|
||||
* @param deleteTemplateRequest template request parameters
|
||||
* @return Mono of {@literal true} if the template could be deleted
|
||||
* @since 4.1
|
||||
*/
|
||||
Mono<Boolean> deleteTemplate(DeleteTemplateRequest deleteTemplateRequest);
|
||||
|
||||
// endregion
|
||||
|
||||
// region helper functions
|
||||
/**
|
||||
* get the current {@link IndexCoordinates}. These may change over time when the entity class has a SpEL constructed
|
||||
* index name. When this IndexOperations is not bound to a class, the bound IndexCoordinates are returned.
|
||||
*
|
||||
* @return IndexCoordinates
|
||||
* @since 4.1
|
||||
*/
|
||||
IndexCoordinates getIndexCoordinates();
|
||||
|
||||
// endregion
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright 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.Mono;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.buffer.DataBufferUtils;
|
||||
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
|
||||
|
||||
/**
|
||||
* Utility to reactively read {@link org.springframework.core.io.Resource}s.
|
||||
*
|
||||
* @author Peter-Josef Meisch
|
||||
* @since 4.1
|
||||
*/
|
||||
public abstract class ReactiveResourceUtil {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(ReactiveResourceUtil.class);
|
||||
private static final int BUFFER_SIZE = 8_192;
|
||||
|
||||
/**
|
||||
* Read a {@link ClassPathResource} into a {@link reactor.core.publisher.Mono<String>}.
|
||||
*
|
||||
* @param url the resource to read
|
||||
* @return a {@link reactor.core.publisher.Mono} emitting the resources content or an empty Mono on error
|
||||
*/
|
||||
public static Mono<String> readFileFromClasspath(String url) {
|
||||
|
||||
return DataBufferUtils
|
||||
.join(DataBufferUtils.read(new ClassPathResource(url), new DefaultDataBufferFactory(), BUFFER_SIZE))
|
||||
.<String> handle((it, sink) -> {
|
||||
|
||||
try (InputStream is = it.asInputStream();
|
||||
InputStreamReader in = new InputStreamReader(is, Charset.defaultCharset());
|
||||
BufferedReader br = new BufferedReader(in)) {
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
String line;
|
||||
while ((line = br.readLine()) != null) {
|
||||
sb.append(line);
|
||||
}
|
||||
|
||||
sink.next(sb.toString());
|
||||
sink.complete();
|
||||
} catch (Exception e) {
|
||||
LOGGER.warn(String.format("Failed to load file from url: %s: %s", url, e.getMessage()));
|
||||
sink.complete();
|
||||
} finally {
|
||||
DataBufferUtils.release(it);
|
||||
}
|
||||
}).onErrorResume(throwable -> {
|
||||
LOGGER.warn(String.format("Failed to load file from url: %s: %s", url, throwable.getMessage()));
|
||||
return Mono.empty();
|
||||
});
|
||||
}
|
||||
|
||||
// Utility constructor
|
||||
private ReactiveResourceUtil() {}
|
||||
}
|
||||
+96
-20
@@ -20,6 +20,8 @@ import reactor.core.publisher.Mono;
|
||||
|
||||
import org.elasticsearch.index.query.QueryBuilders;
|
||||
import org.elasticsearch.search.aggregations.Aggregation;
|
||||
import org.elasticsearch.search.suggest.Suggest;
|
||||
import org.elasticsearch.search.suggest.SuggestBuilder;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
||||
import org.springframework.data.elasticsearch.core.query.Query;
|
||||
@@ -32,6 +34,7 @@ import org.springframework.data.elasticsearch.core.query.StringQuery;
|
||||
*
|
||||
* @author Peter-Josef Meisch
|
||||
* @author Russell Parry
|
||||
* @author Thomas Geese
|
||||
* @since 4.0
|
||||
*/
|
||||
public interface ReactiveSearchOperations {
|
||||
@@ -131,20 +134,6 @@ public interface ReactiveSearchOperations {
|
||||
*/
|
||||
Mono<Long> count(Query query, Class<?> entityType, IndexCoordinates index);
|
||||
|
||||
/**
|
||||
* 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 wrapped in a {@link SearchHit}.
|
||||
*/
|
||||
<T> Flux<SearchHit<T>> search(Query query, Class<?> entityType, Class<T> returnType);
|
||||
|
||||
/**
|
||||
* 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
|
||||
@@ -161,17 +150,18 @@ public interface ReactiveSearchOperations {
|
||||
}
|
||||
|
||||
/**
|
||||
* Search the index for entities matching the given {@link Query query}.
|
||||
* 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 <T>
|
||||
* @param query must not be {@literal null}.
|
||||
* @param entityType must not be {@literal null}.
|
||||
* @param resultType the projection result type.
|
||||
* @param index the target index, 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 wrapped in a {@link SearchHit}.
|
||||
*/
|
||||
<T> Flux<SearchHit<T>> search(Query query, Class<?> entityType, Class<T> resultType, IndexCoordinates index);
|
||||
<T> Flux<SearchHit<T>> search(Query query, Class<?> entityType, Class<T> returnType);
|
||||
|
||||
/**
|
||||
* Search the index for entities matching the given {@link Query query}.
|
||||
@@ -186,6 +176,74 @@ public interface ReactiveSearchOperations {
|
||||
return search(query, entityType, entityType, index);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 resultType the projection result type.
|
||||
* @param index the target index, must not be {@literal null}
|
||||
* @param <T>
|
||||
* @return a {@link Flux} emitting matching entities one by one wrapped in a {@link SearchHit}.
|
||||
*/
|
||||
<T> Flux<SearchHit<T>> search(Query query, Class<?> entityType, Class<T> resultType, IndexCoordinates index);
|
||||
|
||||
/**
|
||||
* Search the index for entities matching the given {@link Query query}.
|
||||
*
|
||||
* @param <T>
|
||||
* @param query must not be {@literal null}.
|
||||
* @param entityType must not be {@literal null}.
|
||||
* @param <T>
|
||||
* @return a {@link Mono} emitting matching entities in a {@link SearchHits}.
|
||||
* @since 4.1
|
||||
*/
|
||||
default <T> Mono<SearchPage<T>> searchForPage(Query query, Class<T> entityType) {
|
||||
return searchForPage(query, entityType, entityType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search the index for entities matching the given {@link Query query}.
|
||||
*
|
||||
* @param <T>
|
||||
* @param query must not be {@literal null}.
|
||||
* @param entityType must not be {@literal null}.
|
||||
* @param resultType the projection result type.
|
||||
* @param <T>
|
||||
* @return a {@link Mono} emitting matching entities in a {@link SearchHits}.
|
||||
* @since 4.1
|
||||
*/
|
||||
<T> Mono<SearchPage<T>> searchForPage(Query query, Class<?> entityType, Class<T> resultType);
|
||||
|
||||
/**
|
||||
* Search the index for entities matching the given {@link Query query}.
|
||||
*
|
||||
* @param <T>
|
||||
* @param query must not be {@literal null}.
|
||||
* @param entityType must not be {@literal null}.
|
||||
* @param index the target index, must not be {@literal null}
|
||||
* @param <T>
|
||||
* @return a {@link Mono} emitting matching entities in a {@link SearchHits}.
|
||||
* @since 4.1
|
||||
*/
|
||||
default <T> Mono<SearchPage<T>> searchForPage(Query query, Class<T> entityType, IndexCoordinates index) {
|
||||
return searchForPage(query, entityType, entityType, index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search the index for entities matching the given {@link Query query}.
|
||||
*
|
||||
* @param <T>
|
||||
* @param query must not be {@literal null}.
|
||||
* @param entityType must not be {@literal null}.
|
||||
* @param resultType the projection result type.
|
||||
* @param index the target index, must not be {@literal null}
|
||||
* @param <T>
|
||||
* @return a {@link Mono} emitting matching entities in a {@link SearchHits}.
|
||||
* @since 4.1
|
||||
*/
|
||||
<T> Mono<SearchPage<T>> searchForPage(Query query, Class<?> entityType, Class<T> resultType, IndexCoordinates index);
|
||||
|
||||
/**
|
||||
* Perform an aggregation specified by the given {@link Query query}. <br />
|
||||
*
|
||||
@@ -206,4 +264,22 @@ public interface ReactiveSearchOperations {
|
||||
* @since 4.0
|
||||
*/
|
||||
Flux<Aggregation> aggregate(Query query, Class<?> entityType, IndexCoordinates index);
|
||||
|
||||
/**
|
||||
* Does a suggest query
|
||||
*
|
||||
* @param suggestion the query
|
||||
* @param entityType must not be {@literal null}.
|
||||
* @return the suggest response
|
||||
*/
|
||||
Flux<Suggest> suggest(SuggestBuilder suggestion, Class<?> entityType);
|
||||
|
||||
/**
|
||||
* Does a suggest query
|
||||
*
|
||||
* @param suggestion the query
|
||||
* @param index the index to run the query against
|
||||
* @return the suggest response
|
||||
*/
|
||||
Flux<Suggest> suggest(SuggestBuilder suggestion, IndexCoordinates index);
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -38,8 +38,8 @@ public abstract class ResourceUtil {
|
||||
/**
|
||||
* Read a {@link ClassPathResource} into a {@link String}.
|
||||
*
|
||||
* @param url
|
||||
* @return
|
||||
* @param url url the file url
|
||||
* @return the contents of the file or null if it could not be read
|
||||
*/
|
||||
@Nullable
|
||||
public static String readFileFromClasspath(String url) {
|
||||
@@ -48,7 +48,7 @@ public abstract class ResourceUtil {
|
||||
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()));
|
||||
LOGGER.warn(String.format("Failed to load file from url: %s: %s", url, e.getMessage()));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.data.elasticsearch.core.document.NestedMetaData;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@@ -35,24 +36,50 @@ import org.springframework.util.Assert;
|
||||
*/
|
||||
public class SearchHit<T> {
|
||||
|
||||
private final String id;
|
||||
@Nullable private final String index;
|
||||
@Nullable private final String id;
|
||||
private final float score;
|
||||
private final List<Object> sortValues;
|
||||
private final T content;
|
||||
private final Map<String, List<String>> highlightFields = new LinkedHashMap<>();
|
||||
private final Map<String, SearchHits<?>> innerHits = new LinkedHashMap<>();
|
||||
@Nullable private final NestedMetaData nestedMetaData;
|
||||
|
||||
public SearchHit(@Nullable String id, float score, @Nullable Object[] sortValues,
|
||||
public SearchHit(@Nullable String index, @Nullable String id, float score, @Nullable Object[] sortValues,
|
||||
@Nullable Map<String, List<String>> highlightFields, T content) {
|
||||
this(index, id, score, sortValues, highlightFields, null, null, content);
|
||||
}
|
||||
|
||||
public SearchHit(@Nullable String index, @Nullable String id, float score, @Nullable Object[] sortValues,
|
||||
@Nullable Map<String, List<String>> highlightFields, @Nullable Map<String, SearchHits<?>> innerHits,
|
||||
@Nullable NestedMetaData nestedMetaData, T content) {
|
||||
this.index = index;
|
||||
this.id = id;
|
||||
this.score = score;
|
||||
this.sortValues = (sortValues != null) ? Arrays.asList(sortValues) : new ArrayList<>();
|
||||
|
||||
if (highlightFields != null) {
|
||||
this.highlightFields.putAll(highlightFields);
|
||||
}
|
||||
|
||||
if (innerHits != null) {
|
||||
this.innerHits.putAll(innerHits);
|
||||
}
|
||||
|
||||
this.nestedMetaData = nestedMetaData;
|
||||
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the index name where the hit's document was found
|
||||
* @since 4.1
|
||||
*/
|
||||
@Nullable
|
||||
public String getIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getId() {
|
||||
return id;
|
||||
@@ -79,6 +106,9 @@ public class SearchHit<T> {
|
||||
return Collections.unmodifiableList(sortValues);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the map from field names to highlight values, never {@literal null}
|
||||
*/
|
||||
public Map<String, List<String>> getHighlightFields() {
|
||||
return Collections.unmodifiableMap(highlightFields.entrySet().stream()
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, entry -> Collections.unmodifiableList(entry.getValue()))));
|
||||
@@ -97,6 +127,39 @@ public class SearchHit<T> {
|
||||
return Collections.unmodifiableList(highlightFields.getOrDefault(field, Collections.emptyList()));
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the {@link SearchHits} for the inner hits with the given name. If the inner hits could be mapped to a
|
||||
* nested entity class, the returned data will be of this type, otherwise
|
||||
* {{@link org.springframework.data.elasticsearch.core.document.SearchDocument}} instances are returned in this
|
||||
* {@link SearchHits} object.
|
||||
*
|
||||
* @param name the inner hits name
|
||||
* @return {@link SearchHits} if available, otherwise {@literal null}
|
||||
*/
|
||||
@Nullable
|
||||
public SearchHits<?> getInnerHits(String name) {
|
||||
return innerHits.get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the map from inner_hits names to inner hits, in a {@link SearchHits} object, never {@literal null}
|
||||
* @since 4.1
|
||||
*/
|
||||
public Map<String, SearchHits<?>> getInnerHits() {
|
||||
return innerHits;
|
||||
}
|
||||
|
||||
/**
|
||||
* If this is a nested inner hit, return the nested metadata information
|
||||
*
|
||||
* @return {{@link NestedMetaData}
|
||||
* @since 4.1
|
||||
*/
|
||||
@Nullable
|
||||
public NestedMetaData getNestedMetaData() {
|
||||
return nestedMetaData;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SearchHit{" + "id='" + id + '\'' + ", score=" + score + ", sortValues=" + sortValues + ", content="
|
||||
|
||||
@@ -16,11 +16,18 @@
|
||||
package org.springframework.data.elasticsearch.core;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.elasticsearch.search.aggregations.Aggregations;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
|
||||
import org.springframework.data.elasticsearch.core.document.Document;
|
||||
import org.springframework.data.elasticsearch.core.document.NestedMetaData;
|
||||
import org.springframework.data.elasticsearch.core.document.SearchDocument;
|
||||
import org.springframework.data.elasticsearch.core.document.SearchDocumentResponse;
|
||||
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
|
||||
@@ -39,35 +46,24 @@ import org.springframework.util.Assert;
|
||||
* @since 4.0
|
||||
*/
|
||||
class SearchHitMapping<T> {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(SearchHitMapping.class);
|
||||
|
||||
private final Class<T> type;
|
||||
private final ElasticsearchConverter converter;
|
||||
private final MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext;
|
||||
|
||||
private SearchHitMapping(Class<T> type,
|
||||
MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> context) {
|
||||
|
||||
private SearchHitMapping(Class<T> type, ElasticsearchConverter converter) {
|
||||
Assert.notNull(type, "type is null");
|
||||
Assert.notNull(context, "context is null");
|
||||
Assert.notNull(converter, "converter is null");
|
||||
|
||||
this.type = type;
|
||||
this.mappingContext = context;
|
||||
this.converter = converter;
|
||||
this.mappingContext = converter.getMappingContext();
|
||||
}
|
||||
|
||||
static <T> SearchHitMapping<T> mappingFor(Class<T> entityClass,
|
||||
MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> context) {
|
||||
return new SearchHitMapping<>(entityClass, context);
|
||||
}
|
||||
|
||||
SearchHit<T> mapHit(SearchDocument searchDocument, T content) {
|
||||
|
||||
Assert.notNull(searchDocument, "searchDocument is null");
|
||||
Assert.notNull(content, "content is null");
|
||||
|
||||
String id = searchDocument.hasId() ? searchDocument.getId() : null;
|
||||
float score = searchDocument.getScore();
|
||||
Object[] sortValues = searchDocument.getSortValues();
|
||||
Map<String, List<String>> highlightFields = getHighlightsAndRemapFieldNames(searchDocument);
|
||||
|
||||
return new SearchHit<>(id, score, sortValues, highlightFields, content);
|
||||
static <T> SearchHitMapping<T> mappingFor(Class<T> entityClass, ElasticsearchConverter converter) {
|
||||
return new SearchHitMapping<>(entityClass, converter);
|
||||
}
|
||||
|
||||
SearchHits<T> mapHits(SearchDocumentResponse searchDocumentResponse, List<T> contents) {
|
||||
@@ -104,6 +100,21 @@ class SearchHitMapping<T> {
|
||||
return new SearchHitsImpl<>(totalHits, totalHitsRelation, maxScore, scrollId, searchHits, aggregations);
|
||||
}
|
||||
|
||||
SearchHit<T> mapHit(SearchDocument searchDocument, T content) {
|
||||
|
||||
Assert.notNull(searchDocument, "searchDocument is null");
|
||||
Assert.notNull(content, "content is null");
|
||||
|
||||
return new SearchHit<T>(searchDocument.getIndex(), //
|
||||
searchDocument.hasId() ? searchDocument.getId() : null, //
|
||||
searchDocument.getScore(), //
|
||||
searchDocument.getSortValues(), //
|
||||
getHighlightsAndRemapFieldNames(searchDocument), //
|
||||
mapInnerHits(searchDocument), //
|
||||
searchDocument.getNestedMetaData(), //
|
||||
content); //
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Map<String, List<String>> getHighlightsAndRemapFieldNames(SearchDocument searchDocument) {
|
||||
Map<String, List<String>> highlightFields = searchDocument.getHighlightFields();
|
||||
@@ -122,4 +133,131 @@ class SearchHitMapping<T> {
|
||||
return property != null ? property.getName() : entry.getKey();
|
||||
}, Map.Entry::getValue));
|
||||
}
|
||||
|
||||
private Map<String, SearchHits<?>> mapInnerHits(SearchDocument searchDocument) {
|
||||
|
||||
Map<String, SearchHits<?>> innerHits = new LinkedHashMap<>();
|
||||
Map<String, SearchDocumentResponse> documentInnerHits = searchDocument.getInnerHits();
|
||||
|
||||
if (documentInnerHits != null && documentInnerHits.size() > 0) {
|
||||
|
||||
SearchHitMapping<SearchDocument> searchDocumentSearchHitMapping = SearchHitMapping
|
||||
.mappingFor(SearchDocument.class, converter);
|
||||
|
||||
for (Map.Entry<String, SearchDocumentResponse> entry : documentInnerHits.entrySet()) {
|
||||
SearchDocumentResponse searchDocumentResponse = entry.getValue();
|
||||
|
||||
SearchHits<SearchDocument> searchHits = searchDocumentSearchHitMapping
|
||||
.mapHitsFromResponse(searchDocumentResponse, searchDocumentResponse.getSearchDocuments());
|
||||
|
||||
// map Documents to real objects
|
||||
SearchHits<?> mappedSearchHits = mapInnerDocuments(searchHits, type);
|
||||
|
||||
innerHits.put(entry.getKey(), mappedSearchHits);
|
||||
}
|
||||
|
||||
}
|
||||
return innerHits;
|
||||
}
|
||||
|
||||
/**
|
||||
* try to convert the SearchDocument instances to instances of the inner property class.
|
||||
*
|
||||
* @param searchHits {@link SearchHits} containing {@link Document} instances
|
||||
* @param type the class of the containing class
|
||||
* @return a new {@link SearchHits} instance containing the mapped objects or the original inout if any error occurs
|
||||
*/
|
||||
private SearchHits<?> mapInnerDocuments(SearchHits<SearchDocument> searchHits, Class<T> type) {
|
||||
|
||||
if (searchHits.getTotalHits() == 0) {
|
||||
return searchHits;
|
||||
}
|
||||
|
||||
try {
|
||||
NestedMetaData nestedMetaData = searchHits.getSearchHit(0).getContent().getNestedMetaData();
|
||||
ElasticsearchPersistentEntityWithNestedMetaData persistentEntityWithNestedMetaData = getPersistentEntity(
|
||||
mappingContext.getPersistentEntity(type), nestedMetaData);
|
||||
|
||||
if (persistentEntityWithNestedMetaData.entity != null) {
|
||||
List<SearchHit<Object>> convertedSearchHits = new ArrayList<>();
|
||||
Class<?> targetType = persistentEntityWithNestedMetaData.entity.getType();
|
||||
|
||||
// convert the list of SearchHit<SearchDocument> to list of SearchHit<Object>
|
||||
searchHits.getSearchHits().forEach(searchHit -> {
|
||||
SearchDocument searchDocument = searchHit.getContent();
|
||||
|
||||
Object targetObject = converter.read(targetType, searchDocument);
|
||||
convertedSearchHits.add(new SearchHit<Object>(searchDocument.getIndex(), //
|
||||
searchDocument.getId(), //
|
||||
searchDocument.getScore(), //
|
||||
searchDocument.getSortValues(), //
|
||||
searchDocument.getHighlightFields(), //
|
||||
searchHit.getInnerHits(), //
|
||||
persistentEntityWithNestedMetaData.nestedMetaData, //
|
||||
targetObject));
|
||||
});
|
||||
|
||||
String scrollId = null;
|
||||
if (searchHits instanceof SearchHitsImpl) {
|
||||
scrollId = ((SearchHitsImpl<?>) searchHits).getScrollId();
|
||||
}
|
||||
|
||||
return new SearchHitsImpl<>(searchHits.getTotalHits(), //
|
||||
searchHits.getTotalHitsRelation(), //
|
||||
searchHits.getMaxScore(), //
|
||||
scrollId, //
|
||||
convertedSearchHits, //
|
||||
searchHits.getAggregations());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOGGER.warn("Could not map inner_hits", e);
|
||||
}
|
||||
|
||||
return searchHits;
|
||||
}
|
||||
|
||||
/**
|
||||
* find a {@link ElasticsearchPersistentEntity} following the property chain defined by the nested metadata
|
||||
*
|
||||
* @param persistentEntity base entity
|
||||
* @param nestedMetaData nested metadata
|
||||
* @return A {@link ElasticsearchPersistentEntityWithNestedMetaData} containing the found entity or null together with
|
||||
* the {@link NestedMetaData} that has mapped field names.
|
||||
*/
|
||||
private ElasticsearchPersistentEntityWithNestedMetaData getPersistentEntity(
|
||||
@Nullable ElasticsearchPersistentEntity<?> persistentEntity, @Nullable NestedMetaData nestedMetaData) {
|
||||
|
||||
NestedMetaData currentMetaData = nestedMetaData;
|
||||
List<NestedMetaData> mappedNestedMetaDatas = new LinkedList<>();
|
||||
|
||||
while (persistentEntity != null && currentMetaData != null) {
|
||||
ElasticsearchPersistentProperty persistentProperty = persistentEntity
|
||||
.getPersistentPropertyWithFieldName(currentMetaData.getField());
|
||||
|
||||
if (persistentProperty == null) {
|
||||
persistentEntity = null;
|
||||
} else {
|
||||
persistentEntity = mappingContext.getPersistentEntity(persistentProperty.getActualType());
|
||||
mappedNestedMetaDatas.add(0,
|
||||
NestedMetaData.of(persistentProperty.getName(), currentMetaData.getOffset(), null));
|
||||
currentMetaData = currentMetaData.getChild();
|
||||
}
|
||||
}
|
||||
|
||||
NestedMetaData mappedNestedMetaData = mappedNestedMetaDatas.stream().reduce(null,
|
||||
(result, nmd) -> NestedMetaData.of(nmd.getField(), nmd.getOffset(), result));
|
||||
|
||||
return new ElasticsearchPersistentEntityWithNestedMetaData(persistentEntity, mappedNestedMetaData);
|
||||
}
|
||||
|
||||
private static class ElasticsearchPersistentEntityWithNestedMetaData {
|
||||
@Nullable private ElasticsearchPersistentEntity<?> entity;
|
||||
private NestedMetaData nestedMetaData;
|
||||
|
||||
public ElasticsearchPersistentEntityWithNestedMetaData(@Nullable ElasticsearchPersistentEntity<?> entity,
|
||||
NestedMetaData nestedMetaData) {
|
||||
this.entity = entity;
|
||||
this.nestedMetaData = nestedMetaData;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,7 +48,8 @@ public final class SearchHitSupport {
|
||||
* @return a corresponding object where the SearchHits are replaced by their content if possible, otherwise the
|
||||
* original object
|
||||
*/
|
||||
public static Object unwrapSearchHits(Object result) {
|
||||
@Nullable
|
||||
public static Object unwrapSearchHits(@Nullable Object result) {
|
||||
|
||||
if (result == null) {
|
||||
return result;
|
||||
@@ -157,5 +158,13 @@ public final class SearchHitSupport {
|
||||
public SearchHits<T> getSearchHits() {
|
||||
return searchHits;
|
||||
}
|
||||
|
||||
/*
|
||||
* return the same instance as in getSearchHits().getSearchHits()
|
||||
*/
|
||||
@Override
|
||||
public List<SearchHit<T>> getContent() {
|
||||
return searchHits.getSearchHits();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,11 +19,12 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.elasticsearch.search.aggregations.Aggregations;
|
||||
import org.springframework.data.util.Lazy;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Basic implementation of {@link SearchScrollHits}
|
||||
* Basic implementation of {@link SearchScrollHits}
|
||||
*
|
||||
* @param <T> the result data class.
|
||||
* @author Peter-Josef Meisch
|
||||
@@ -35,9 +36,10 @@ public class SearchHitsImpl<T> implements SearchScrollHits<T> {
|
||||
private final long totalHits;
|
||||
private final TotalHitsRelation totalHitsRelation;
|
||||
private final float maxScore;
|
||||
private final String scrollId;
|
||||
@Nullable private final String scrollId;
|
||||
private final List<? extends SearchHit<T>> searchHits;
|
||||
private final Aggregations aggregations;
|
||||
private final Lazy<List<SearchHit<T>>> unmodifiableSearchHits;
|
||||
@Nullable private final Aggregations aggregations;
|
||||
|
||||
/**
|
||||
* @param totalHits the number of total hits for the search
|
||||
@@ -58,6 +60,7 @@ public class SearchHitsImpl<T> implements SearchScrollHits<T> {
|
||||
this.scrollId = scrollId;
|
||||
this.searchHits = searchHits;
|
||||
this.aggregations = aggregations;
|
||||
this.unmodifiableSearchHits = Lazy.of(() -> Collections.unmodifiableList(searchHits));
|
||||
}
|
||||
|
||||
// region getter
|
||||
@@ -84,7 +87,7 @@ public class SearchHitsImpl<T> implements SearchScrollHits<T> {
|
||||
|
||||
@Override
|
||||
public List<SearchHit<T>> getSearchHits() {
|
||||
return Collections.unmodifiableList(searchHits);
|
||||
return unmodifiableSearchHits.get();
|
||||
}
|
||||
// endregion
|
||||
|
||||
|
||||
@@ -241,6 +241,16 @@ public interface SearchOperations {
|
||||
|
||||
// endregion
|
||||
|
||||
/**
|
||||
* Does a suggest query
|
||||
*
|
||||
* @param suggestion the query
|
||||
* @param the entity class
|
||||
* @return the suggest response
|
||||
* @since 4.1
|
||||
*/
|
||||
SearchResponse suggest(SuggestBuilder suggestion, Class<?> clazz);
|
||||
|
||||
/**
|
||||
* Does a suggest query
|
||||
*
|
||||
@@ -277,6 +287,17 @@ public interface SearchOperations {
|
||||
return content.isEmpty() ? null : content.get(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the multi search query against elasticsearch and return result as {@link List} of {@link SearchHits}.
|
||||
*
|
||||
* @param queries the queries to execute
|
||||
* @param clazz the entity clazz
|
||||
* @param <T> element return type
|
||||
* @return list of SearchHits
|
||||
* @since 4.1
|
||||
*/
|
||||
<T> List<SearchHits<T>> multiSearch(List<? extends Query> queries, Class<T> clazz);
|
||||
|
||||
/**
|
||||
* Execute the multi search query against elasticsearch and return result as {@link List} of {@link SearchHits}.
|
||||
*
|
||||
@@ -288,6 +309,16 @@ public interface SearchOperations {
|
||||
*/
|
||||
<T> List<SearchHits<T>> multiSearch(List<? extends Query> queries, Class<T> clazz, IndexCoordinates index);
|
||||
|
||||
/**
|
||||
* Execute the multi search query against elasticsearch and return result as {@link List} of {@link SearchHits}.
|
||||
*
|
||||
* @param queries the queries to execute
|
||||
* @param classes the entity classes
|
||||
* @return list of SearchHits
|
||||
* @since 4.1
|
||||
*/
|
||||
List<SearchHits<?>> multiSearch(List<? extends Query> queries, List<Class<?>> classes);
|
||||
|
||||
/**
|
||||
* Execute the multi search query against elasticsearch and return result as {@link List} of {@link SearchHits}.
|
||||
*
|
||||
|
||||
@@ -15,12 +15,15 @@
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.core;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* This interface is used to expose the current {@code scrollId} from the underlying scroll context.
|
||||
* <p>
|
||||
* Internal use only.
|
||||
*
|
||||
* @author Sascha Woo
|
||||
* @author Peter-Josef Meisch
|
||||
* @param <T>
|
||||
* @since 4.0
|
||||
*/
|
||||
@@ -29,6 +32,7 @@ public interface SearchScrollHits<T> extends SearchHits<T> {
|
||||
/**
|
||||
* @return the scroll id
|
||||
*/
|
||||
@Nullable
|
||||
String getScrollId();
|
||||
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ package org.springframework.data.elasticsearch.core;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
@@ -38,13 +39,15 @@ abstract class StreamQueries {
|
||||
/**
|
||||
* Stream query results using {@link SearchScrollHits}.
|
||||
*
|
||||
* @param maxCount the maximum number of entities to return, a value of 0 means that all available entities are
|
||||
* returned
|
||||
* @param searchHits the initial hits
|
||||
* @param continueScrollFunction function to continue scrolling applies to the current scrollId.
|
||||
* @param clearScrollConsumer consumer to clear the scroll context by accepting the scrollIds to clear.
|
||||
* @param <T>
|
||||
* @param <T> the entity type
|
||||
* @return the {@link SearchHitsIterator}.
|
||||
*/
|
||||
static <T> SearchHitsIterator<T> streamResults(SearchScrollHits<T> searchHits,
|
||||
static <T> SearchHitsIterator<T> streamResults(int maxCount, SearchScrollHits<T> searchHits,
|
||||
Function<String, SearchScrollHits<T>> continueScrollFunction, Consumer<List<String>> clearScrollConsumer) {
|
||||
|
||||
Assert.notNull(searchHits, "searchHits must not be null.");
|
||||
@@ -59,20 +62,14 @@ abstract class StreamQueries {
|
||||
|
||||
return new SearchHitsIterator<T>() {
|
||||
|
||||
// As we couldn't retrieve single result with scroll, store current hits.
|
||||
private volatile Iterator<SearchHit<T>> scrollHits = searchHits.iterator();
|
||||
private volatile boolean continueScroll = scrollHits.hasNext();
|
||||
private volatile AtomicInteger currentCount = new AtomicInteger();
|
||||
private volatile Iterator<SearchHit<T>> currentScrollHits = searchHits.iterator();
|
||||
private volatile boolean continueScroll = currentScrollHits.hasNext();
|
||||
private volatile ScrollState scrollState = new ScrollState(searchHits.getScrollId());
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
try {
|
||||
clearScrollConsumer.accept(scrollState.getScrollIds());
|
||||
} finally {
|
||||
scrollHits = null;
|
||||
scrollState = null;
|
||||
}
|
||||
clearScrollConsumer.accept(scrollState.getScrollIds());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -99,24 +96,25 @@ abstract class StreamQueries {
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
|
||||
if (!continueScroll) {
|
||||
if (!continueScroll || (maxCount > 0 && currentCount.get() >= maxCount)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!scrollHits.hasNext()) {
|
||||
if (!currentScrollHits.hasNext()) {
|
||||
SearchScrollHits<T> nextPage = continueScrollFunction.apply(scrollState.getScrollId());
|
||||
scrollHits = nextPage.iterator();
|
||||
currentScrollHits = nextPage.iterator();
|
||||
scrollState.updateScrollId(nextPage.getScrollId());
|
||||
continueScroll = scrollHits.hasNext();
|
||||
continueScroll = currentScrollHits.hasNext();
|
||||
}
|
||||
|
||||
return scrollHits.hasNext();
|
||||
return currentScrollHits.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SearchHit<T> next() {
|
||||
if (hasNext()) {
|
||||
return scrollHits.next();
|
||||
currentCount.incrementAndGet();
|
||||
return currentScrollHits.next();
|
||||
}
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ package org.springframework.data.elasticsearch.core;
|
||||
|
||||
/**
|
||||
* Enum to represent the relation that Elasticsearch returns for the totalHits value {@see <a href=
|
||||
* "https://www.elastic.co/guide/en/elasticsearch/reference/7.5/search-request-body.html#request-body-search-track-total-hits">Ekasticsearch
|
||||
* "https://www.elastic.co/guide/en/elasticsearch/reference/7.5/search-request-body.html#request-body-search-track-total-hits">Elasticsearch
|
||||
* docs</a>}
|
||||
*
|
||||
* @author Peter-Josef Meisch
|
||||
@@ -26,5 +26,9 @@ package org.springframework.data.elasticsearch.core;
|
||||
*/
|
||||
public enum TotalHitsRelation {
|
||||
EQUAL_TO, //
|
||||
GREATER_THAN_OR_EQUAL_TO
|
||||
GREATER_THAN_OR_EQUAL_TO, //
|
||||
/**
|
||||
* @since 4.1
|
||||
*/
|
||||
OFF
|
||||
}
|
||||
|
||||
-55
@@ -1,55 +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;
|
||||
|
||||
public class AliasData {
|
||||
private String filter = null;
|
||||
private String routing = null;
|
||||
private String search_routing = null;
|
||||
private String index_routing = null;
|
||||
|
||||
public String getFilter() {
|
||||
return filter;
|
||||
}
|
||||
|
||||
public void setFilter(String filter) {
|
||||
this.filter = filter;
|
||||
}
|
||||
|
||||
public String getRouting() {
|
||||
return routing;
|
||||
}
|
||||
|
||||
public void setRouting(String routing) {
|
||||
this.routing = routing;
|
||||
}
|
||||
|
||||
public String getSearch_routing() {
|
||||
return search_routing;
|
||||
}
|
||||
|
||||
public void setSearch_routing(String search_routing) {
|
||||
this.search_routing = search_routing;
|
||||
}
|
||||
|
||||
public String getIndex_routing() {
|
||||
return index_routing;
|
||||
}
|
||||
|
||||
public void setIndex_routing(String index_routing) {
|
||||
this.index_routing = index_routing;
|
||||
}
|
||||
}
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
@org.springframework.lang.NonNullApi
|
||||
@org.springframework.lang.NonNullFields
|
||||
package org.springframework.data.elasticsearch.core.client.support;
|
||||
+12
@@ -34,6 +34,10 @@ public final class DateTimeConverters {
|
||||
|
||||
private static DateTimeFormatter formatter = ISODateTimeFormat.dateTime().withZone(DateTimeZone.UTC);
|
||||
|
||||
/**
|
||||
* @deprecated since 4.1
|
||||
*/
|
||||
@Deprecated
|
||||
public enum JodaDateTimeConverter implements Converter<ReadableInstant, String> {
|
||||
INSTANCE;
|
||||
|
||||
@@ -47,6 +51,10 @@ public final class DateTimeConverters {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated since 4.1
|
||||
*/
|
||||
@Deprecated
|
||||
public enum JodaLocalDateTimeConverter implements Converter<LocalDateTime, String> {
|
||||
INSTANCE;
|
||||
|
||||
@@ -60,6 +68,10 @@ public final class DateTimeConverters {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated since 4.1
|
||||
*/
|
||||
@Deprecated
|
||||
public enum JavaDateConverter implements Converter<Date, String> {
|
||||
INSTANCE;
|
||||
|
||||
|
||||
+28
-4
@@ -20,6 +20,7 @@ import org.springframework.data.elasticsearch.core.document.Document;
|
||||
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
|
||||
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
|
||||
import org.springframework.data.elasticsearch.core.query.CriteriaQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.Query;
|
||||
import org.springframework.data.projection.ProjectionFactory;
|
||||
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
|
||||
import org.springframework.lang.Nullable;
|
||||
@@ -73,21 +74,44 @@ public interface ElasticsearchConverter
|
||||
* @return will not be {@literal null}.
|
||||
*/
|
||||
default Document mapObject(@Nullable Object source) {
|
||||
|
||||
Document target = Document.create();
|
||||
write(source, target);
|
||||
|
||||
if (source != null) {
|
||||
write(source, target);
|
||||
}
|
||||
return target;
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region query
|
||||
/**
|
||||
* Updates a query by renaming the property names in the query to the correct mapped field names and the values to the
|
||||
* converted values if the {@link ElasticsearchPersistentProperty} for a property has a
|
||||
* {@link org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentPropertyConverter}.
|
||||
* {@link org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentPropertyConverter}. If
|
||||
* domainClass is null, it's a noop; handling null here eliminates null checks in the caller.
|
||||
*
|
||||
* @param query the query that is internally updated
|
||||
* @param domainClass the class of the object that is searched with the query
|
||||
*/
|
||||
default void updateQuery(Query query, @Nullable Class<?> domainClass) {
|
||||
|
||||
if (domainClass != null) {
|
||||
|
||||
if (query instanceof CriteriaQuery) {
|
||||
updateCriteriaQuery((CriteriaQuery) query, domainClass);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a {@link CriteriaQuery} by renaming the property names in the query to the correct mapped field names and
|
||||
* the values to the converted values if the {@link ElasticsearchPersistentProperty} for a property has a
|
||||
* {@link org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentPropertyConverter}.
|
||||
*
|
||||
* @param criteriaQuery the query that is internally updated
|
||||
* @param domainClass the class of the object that is searched with the query
|
||||
*/
|
||||
// region query
|
||||
void updateQuery(CriteriaQuery criteriaQuery, Class<?> domainClass);
|
||||
void updateCriteriaQuery(CriteriaQuery criteriaQuery, Class<?> domainClass);
|
||||
// endregion
|
||||
}
|
||||
|
||||
+6
-3
@@ -18,11 +18,13 @@ package org.springframework.data.elasticsearch.core.convert;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.time.Instant;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.temporal.TemporalAccessor;
|
||||
import java.util.Date;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.elasticsearch.common.time.DateFormatter;
|
||||
import org.elasticsearch.common.time.DateFormatters;
|
||||
import org.springframework.data.elasticsearch.annotations.DateFormat;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@@ -103,10 +105,10 @@ final public class ElasticsearchDateConverter {
|
||||
* @return the new created object
|
||||
*/
|
||||
public <T extends TemporalAccessor> T parse(String input, Class<T> type) {
|
||||
TemporalAccessor accessor = dateFormatter.parse(input);
|
||||
ZonedDateTime zonedDateTime = DateFormatters.from(dateFormatter.parse(input));
|
||||
try {
|
||||
Method method = type.getMethod("from", TemporalAccessor.class);
|
||||
Object o = method.invoke(null, accessor);
|
||||
Object o = method.invoke(null, zonedDateTime);
|
||||
return type.cast(o);
|
||||
} catch (NoSuchMethodException e) {
|
||||
throw new ConversionException("no 'from' factory method found in class " + type.getName());
|
||||
@@ -122,6 +124,7 @@ final public class ElasticsearchDateConverter {
|
||||
* @return the new created object
|
||||
*/
|
||||
public Date parse(String input) {
|
||||
return new Date(Instant.from(dateFormatter.parse(input)).toEpochMilli());
|
||||
ZonedDateTime zonedDateTime = DateFormatters.from(dateFormatter.parse(input));
|
||||
return new Date(Instant.from(zonedDateTime).toEpochMilli());
|
||||
}
|
||||
}
|
||||
|
||||
+425
-31
@@ -18,13 +18,25 @@ package org.springframework.data.elasticsearch.core.convert;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
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.GeoJson;
|
||||
import org.springframework.data.elasticsearch.core.geo.GeoJsonGeometryCollection;
|
||||
import org.springframework.data.elasticsearch.core.geo.GeoJsonLineString;
|
||||
import org.springframework.data.elasticsearch.core.geo.GeoJsonMultiLineString;
|
||||
import org.springframework.data.elasticsearch.core.geo.GeoJsonMultiPoint;
|
||||
import org.springframework.data.elasticsearch.core.geo.GeoJsonMultiPolygon;
|
||||
import org.springframework.data.elasticsearch.core.geo.GeoJsonPoint;
|
||||
import org.springframework.data.elasticsearch.core.geo.GeoJsonPolygon;
|
||||
import org.springframework.data.elasticsearch.core.geo.GeoPoint;
|
||||
import org.springframework.data.geo.Point;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.NumberUtils;
|
||||
|
||||
/**
|
||||
@@ -34,19 +46,28 @@ import org.springframework.util.NumberUtils;
|
||||
* @author Peter-Josef Meisch
|
||||
* @since 3.2
|
||||
*/
|
||||
class GeoConverters {
|
||||
public class GeoConverters {
|
||||
|
||||
static Collection<Converter<?, ?>> getConvertersToRegister() {
|
||||
|
||||
return Arrays.asList(PointToMapConverter.INSTANCE, MapToPointConverter.INSTANCE, GeoPointToMapConverter.INSTANCE,
|
||||
MapToGeoPointConverter.INSTANCE);
|
||||
return Arrays.asList(PointToMapConverter.INSTANCE, MapToPointConverter.INSTANCE, //
|
||||
GeoPointToMapConverter.INSTANCE, MapToGeoPointConverter.INSTANCE, //
|
||||
GeoJsonToMapConverter.INSTANCE, MapToGeoJsonConverter.INSTANCE, //
|
||||
GeoJsonPointToMapConverter.INSTANCE, MapToGeoJsonPointConverter.INSTANCE, //
|
||||
GeoJsonMultiPointToMapConverter.INSTANCE, MapToGeoJsonMultiPointConverter.INSTANCE, //
|
||||
GeoJsonLineStringToMapConverter.INSTANCE, MapToGeoJsonLineStringConverter.INSTANCE, //
|
||||
GeoJsonMultiLineStringToMapConverter.INSTANCE, MapToGeoJsonMultiLineStringConverter.INSTANCE, //
|
||||
GeoJsonPolygonToMapConverter.INSTANCE, MapToGeoJsonPolygonConverter.INSTANCE, //
|
||||
GeoJsonMultiPolygonToMapConverter.INSTANCE, MapToGeoJsonMultiPolygonConverter.INSTANCE, //
|
||||
GeoJsonGeometryCollectionToMapConverter.INSTANCE, MapToGeoJsonGeometryCollectionConverter.INSTANCE);
|
||||
}
|
||||
|
||||
// region Point
|
||||
/**
|
||||
* {@link Converter} to write a {@link Point} to {@link Map} using {@code lat/long} properties.
|
||||
*/
|
||||
@WritingConverter
|
||||
enum PointToMapConverter implements Converter<Point, Map<String, Object>> {
|
||||
public enum PointToMapConverter implements Converter<Point, Map<String, Object>> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@@ -54,17 +75,36 @@ class GeoConverters {
|
||||
public Map<String, Object> convert(Point source) {
|
||||
|
||||
Map<String, Object> target = new LinkedHashMap<>();
|
||||
target.put("lat", source.getX());
|
||||
target.put("lon", source.getY());
|
||||
target.put("lat", source.getY());
|
||||
target.put("lon", source.getX());
|
||||
return target;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link Converter} to read a {@link Point} from {@link Map} using {@code lat/long} properties.
|
||||
*/
|
||||
@ReadingConverter
|
||||
public enum MapToPointConverter implements Converter<Map<String, Object>, Point> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public Point convert(Map<String, Object> source) {
|
||||
Double x = NumberUtils.convertNumberToTargetClass((Number) source.get("lon"), Double.class);
|
||||
Double y = NumberUtils.convertNumberToTargetClass((Number) source.get("lat"), Double.class);
|
||||
|
||||
return new Point(x, y);
|
||||
}
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region GeoPoint
|
||||
/**
|
||||
* {@link Converter} to write a {@link GeoPoint} to {@link Map} using {@code lat/long} properties.
|
||||
*/
|
||||
@WritingConverter
|
||||
enum GeoPointToMapConverter implements Converter<GeoPoint, Map<String, Object>> {
|
||||
public enum GeoPointToMapConverter implements Converter<GeoPoint, Map<String, Object>> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@@ -75,39 +115,393 @@ class GeoConverters {
|
||||
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<String, Object> 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> {
|
||||
public enum MapToGeoPointConverter implements Converter<Map<String, Object>, GeoPoint> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public GeoPoint convert(Map<String, Object> source) {
|
||||
Double x = NumberUtils.convertNumberToTargetClass((Number) source.get("lat"), Double.class);
|
||||
Double y = NumberUtils.convertNumberToTargetClass((Number) source.get("lon"), Double.class);
|
||||
Double lat = NumberUtils.convertNumberToTargetClass((Number) source.get("lat"), Double.class);
|
||||
Double lon = NumberUtils.convertNumberToTargetClass((Number) source.get("lon"), Double.class);
|
||||
|
||||
return new GeoPoint(x, y);
|
||||
return new GeoPoint(lat, lon);
|
||||
}
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region GeoJson
|
||||
@WritingConverter
|
||||
public enum GeoJsonToMapConverter implements Converter<GeoJson<? extends Iterable<?>>, Map<String, Object>> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public Map<String, Object> convert(GeoJson<? extends Iterable<?>> source) {
|
||||
if (source instanceof GeoJsonPoint) {
|
||||
return GeoJsonPointToMapConverter.INSTANCE.convert((GeoJsonPoint) source);
|
||||
} else if (source instanceof GeoJsonMultiPoint) {
|
||||
return GeoJsonMultiPointToMapConverter.INSTANCE.convert((GeoJsonMultiPoint) source);
|
||||
} else if (source instanceof GeoJsonLineString) {
|
||||
return GeoJsonLineStringToMapConverter.INSTANCE.convert((GeoJsonLineString) source);
|
||||
} else if (source instanceof GeoJsonMultiLineString) {
|
||||
return GeoJsonMultiLineStringToMapConverter.INSTANCE.convert((GeoJsonMultiLineString) source);
|
||||
} else if (source instanceof GeoJsonPolygon) {
|
||||
return GeoJsonPolygonToMapConverter.INSTANCE.convert((GeoJsonPolygon) source);
|
||||
} else if (source instanceof GeoJsonMultiPolygon) {
|
||||
return GeoJsonMultiPolygonToMapConverter.INSTANCE.convert((GeoJsonMultiPolygon) source);
|
||||
} else if (source instanceof GeoJsonGeometryCollection) {
|
||||
return GeoJsonGeometryCollectionToMapConverter.INSTANCE.convert((GeoJsonGeometryCollection) source);
|
||||
} else {
|
||||
throw new IllegalArgumentException("unknown GeoJson class " + source.getClass().getSimpleName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ReadingConverter
|
||||
public enum MapToGeoJsonConverter implements Converter<Map<String, Object>, GeoJson<? extends Iterable<?>>> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public GeoJson<? extends Iterable<?>> convert(Map<String, Object> source) {
|
||||
|
||||
String type = GeoConverters.getGeoJsonType(source);
|
||||
|
||||
switch (type) {
|
||||
case GeoJsonPoint.TYPE:
|
||||
return MapToGeoJsonPointConverter.INSTANCE.convert(source);
|
||||
case GeoJsonMultiPoint.TYPE:
|
||||
return MapToGeoJsonMultiPointConverter.INSTANCE.convert(source);
|
||||
case GeoJsonLineString.TYPE:
|
||||
return MapToGeoJsonLineStringConverter.INSTANCE.convert(source);
|
||||
case GeoJsonMultiLineString.TYPE:
|
||||
return MapToGeoJsonMultiLineStringConverter.INSTANCE.convert(source);
|
||||
case GeoJsonPolygon.TYPE:
|
||||
return MapToGeoJsonPolygonConverter.INSTANCE.convert(source);
|
||||
case GeoJsonMultiPolygon.TYPE:
|
||||
return MapToGeoJsonMultiPolygonConverter.INSTANCE.convert(source);
|
||||
case GeoJsonGeometryCollection.TYPE:
|
||||
return MapToGeoJsonGeometryCollectionConverter.INSTANCE.convert(source);
|
||||
default:
|
||||
throw new IllegalArgumentException("unknown GeoJson type " + type);
|
||||
}
|
||||
}
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region GeoJsonPoint
|
||||
@WritingConverter
|
||||
public enum GeoJsonPointToMapConverter implements Converter<GeoJsonPoint, Map<String, Object>> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public Map<String, Object> convert(GeoJsonPoint geoJsonPoint) {
|
||||
Map<String, Object> map = new LinkedHashMap<>();
|
||||
map.put("type", geoJsonPoint.getType());
|
||||
map.put("coordinates", geoJsonPoint.getCoordinates());
|
||||
return map;
|
||||
}
|
||||
}
|
||||
|
||||
@ReadingConverter
|
||||
public enum MapToGeoJsonPointConverter implements Converter<Map<String, Object>, GeoJsonPoint> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public GeoJsonPoint convert(Map<String, Object> source) {
|
||||
|
||||
String type = GeoConverters.getGeoJsonType(source);
|
||||
Assert.isTrue(type.equals(GeoJsonPoint.TYPE), "does not contain a type 'Point'");
|
||||
|
||||
Object coordinates = source.get("coordinates");
|
||||
Assert.notNull(coordinates, "Document to convert does not contain coordinates");
|
||||
Assert.isTrue(coordinates instanceof List, "coordinates must be a List of Numbers");
|
||||
// noinspection unchecked
|
||||
List<Number> numbers = (List<Number>) coordinates;
|
||||
Assert.isTrue(numbers.size() >= 2, "coordinates must have at least 2 elements");
|
||||
|
||||
return GeoJsonPoint.of(numbers.get(0).doubleValue(), numbers.get(1).doubleValue());
|
||||
}
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region GeoJsonMultiPoint
|
||||
@WritingConverter
|
||||
public enum GeoJsonMultiPointToMapConverter implements Converter<GeoJsonMultiPoint, Map<String, Object>> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public Map<String, Object> convert(GeoJsonMultiPoint geoJsonMultiPoint) {
|
||||
Map<String, Object> map = new LinkedHashMap<>();
|
||||
map.put("type", geoJsonMultiPoint.getType());
|
||||
map.put("coordinates", pointsToCoordinates(geoJsonMultiPoint.getCoordinates()));
|
||||
return map;
|
||||
}
|
||||
}
|
||||
|
||||
@ReadingConverter
|
||||
public enum MapToGeoJsonMultiPointConverter implements Converter<Map<String, Object>, GeoJsonMultiPoint> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public GeoJsonMultiPoint convert(Map<String, Object> source) {
|
||||
|
||||
String type = GeoConverters.getGeoJsonType(source);
|
||||
Assert.isTrue(type.equals(GeoJsonMultiPoint.TYPE), "does not contain a type 'MultiPoint'");
|
||||
Object coordinates = source.get("coordinates");
|
||||
Assert.notNull(coordinates, "Document to convert does not contain coordinates");
|
||||
Assert.isTrue(coordinates instanceof List, "coordinates must be a List");
|
||||
|
||||
// noinspection unchecked
|
||||
return GeoJsonMultiPoint.of(coordinatesToPoints((List<List<Number>>) coordinates));
|
||||
}
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region GeoJsonLineString
|
||||
@WritingConverter
|
||||
public enum GeoJsonLineStringToMapConverter implements Converter<GeoJsonLineString, Map<String, Object>> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public Map<String, Object> convert(GeoJsonLineString geoJsonLineString) {
|
||||
Map<String, Object> map = new LinkedHashMap<>();
|
||||
map.put("type", geoJsonLineString.getType());
|
||||
map.put("coordinates", pointsToCoordinates(geoJsonLineString.getCoordinates()));
|
||||
return map;
|
||||
}
|
||||
}
|
||||
|
||||
@ReadingConverter
|
||||
public enum MapToGeoJsonLineStringConverter implements Converter<Map<String, Object>, GeoJsonLineString> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public GeoJsonLineString convert(Map<String, Object> source) {
|
||||
|
||||
String type = GeoConverters.getGeoJsonType(source);
|
||||
Assert.isTrue(type.equals(GeoJsonLineString.TYPE), "does not contain a type 'LineString'");
|
||||
Object coordinates = source.get("coordinates");
|
||||
Assert.notNull(coordinates, "Document to convert does not contain coordinates");
|
||||
Assert.isTrue(coordinates instanceof List, "coordinates must be a List");
|
||||
|
||||
// noinspection unchecked
|
||||
return GeoJsonLineString.of(coordinatesToPoints((List<List<Number>>) coordinates));
|
||||
}
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region GeoJsonMultiLineString
|
||||
@WritingConverter
|
||||
public enum GeoJsonMultiLineStringToMapConverter implements Converter<GeoJsonMultiLineString, Map<String, Object>> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public Map<String, Object> convert(GeoJsonMultiLineString source) {
|
||||
return geoJsonLinesStringsToMap(source.getType(), source.getCoordinates());
|
||||
}
|
||||
}
|
||||
|
||||
@ReadingConverter
|
||||
public enum MapToGeoJsonMultiLineStringConverter implements Converter<Map<String, Object>, GeoJsonMultiLineString> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public GeoJsonMultiLineString convert(Map<String, Object> source) {
|
||||
|
||||
String type = GeoConverters.getGeoJsonType(source);
|
||||
Assert.isTrue(type.equals(GeoJsonMultiLineString.TYPE), "does not contain a type 'MultiLineString'");
|
||||
List<GeoJsonLineString> lines = geoJsonLineStringsFromMap(source);
|
||||
return GeoJsonMultiLineString.of(lines);
|
||||
}
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region GeoJsonPolygon
|
||||
@WritingConverter
|
||||
public enum GeoJsonPolygonToMapConverter implements Converter<GeoJsonPolygon, Map<String, Object>> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public Map<String, Object> convert(GeoJsonPolygon source) {
|
||||
return geoJsonLinesStringsToMap(source.getType(), source.getCoordinates());
|
||||
}
|
||||
}
|
||||
|
||||
@ReadingConverter
|
||||
public enum MapToGeoJsonPolygonConverter implements Converter<Map<String, Object>, GeoJsonPolygon> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public GeoJsonPolygon convert(Map<String, Object> source) {
|
||||
|
||||
String type = GeoConverters.getGeoJsonType(source);
|
||||
Assert.isTrue(type.equals(GeoJsonPolygon.TYPE), "does not contain a type 'Polygon'");
|
||||
List<GeoJsonLineString> lines = geoJsonLineStringsFromMap(source);
|
||||
Assert.isTrue(lines.size() > 0, "no linestrings defined in polygon");
|
||||
GeoJsonPolygon geoJsonPolygon = GeoJsonPolygon.of(lines.get(0));
|
||||
for (int i = 1; i < lines.size(); i++) {
|
||||
geoJsonPolygon = geoJsonPolygon.withInnerRing(lines.get(i));
|
||||
}
|
||||
return geoJsonPolygon;
|
||||
}
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region GeoJsonMultiPolygon
|
||||
@WritingConverter
|
||||
public enum GeoJsonMultiPolygonToMapConverter implements Converter<GeoJsonMultiPolygon, Map<String, Object>> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public Map<String, Object> convert(GeoJsonMultiPolygon source) {
|
||||
|
||||
Map<String, Object> map = new LinkedHashMap<>();
|
||||
map.put("type", source.getType());
|
||||
|
||||
List<Object> coordinates = source.getCoordinates().stream() //
|
||||
.map(GeoJsonPolygonToMapConverter.INSTANCE::convert) //
|
||||
.filter(Objects::nonNull) //
|
||||
.map(it -> it.get("coordinates")) //
|
||||
.collect(Collectors.toList()); //
|
||||
map.put("coordinates", coordinates);
|
||||
|
||||
return map;
|
||||
}
|
||||
}
|
||||
|
||||
@ReadingConverter
|
||||
public enum MapToGeoJsonMultiPolygonConverter implements Converter<Map<String, Object>, GeoJsonMultiPolygon> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public GeoJsonMultiPolygon convert(Map<String, Object> source) {
|
||||
|
||||
String type = GeoConverters.getGeoJsonType(source);
|
||||
Assert.isTrue(type.equals(GeoJsonMultiPolygon.TYPE), "does not contain a type 'MultiPolygon'");
|
||||
Object coordinates = source.get("coordinates");
|
||||
Assert.notNull(coordinates, "Document to convert does not contain coordinates");
|
||||
Assert.isTrue(coordinates instanceof List, "coordinates must be a List");
|
||||
|
||||
List<GeoJsonPolygon> geoJsonPolygons = ((List<?>) coordinates).stream().map(it -> {
|
||||
Map<String, Object> map = new LinkedHashMap<>();
|
||||
map.put("type", GeoJsonPolygon.TYPE);
|
||||
map.put("coordinates", it);
|
||||
return map;
|
||||
}).map(MapToGeoJsonPolygonConverter.INSTANCE::convert).collect(Collectors.toList());
|
||||
|
||||
return GeoJsonMultiPolygon.of(geoJsonPolygons);
|
||||
}
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region GeoJsonGeometryCollection
|
||||
@WritingConverter
|
||||
public enum GeoJsonGeometryCollectionToMapConverter
|
||||
implements Converter<GeoJsonGeometryCollection, Map<String, Object>> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public Map<String, Object> convert(GeoJsonGeometryCollection source) {
|
||||
|
||||
Map<String, Object> map = new LinkedHashMap<>();
|
||||
map.put("type", source.getType());
|
||||
List<Map<String, Object>> geometries = source.getGeometries().stream()
|
||||
.map(GeoJsonToMapConverter.INSTANCE::convert).collect(Collectors.toList());
|
||||
map.put("geometries", geometries);
|
||||
|
||||
return map;
|
||||
}
|
||||
}
|
||||
|
||||
@ReadingConverter
|
||||
public enum MapToGeoJsonGeometryCollectionConverter
|
||||
implements Converter<Map<String, Object>, GeoJsonGeometryCollection> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public GeoJsonGeometryCollection convert(Map<String, Object> source) {
|
||||
|
||||
String type = GeoConverters.getGeoJsonType(source);
|
||||
Assert.isTrue(type.equals(GeoJsonGeometryCollection.TYPE), "does not contain a type 'GeometryCollection'");
|
||||
Object geometries = source.get("geometries");
|
||||
Assert.notNull(geometries, "Document to convert does not contain geometries");
|
||||
Assert.isTrue(geometries instanceof List, "geometries must be a List");
|
||||
|
||||
// noinspection unchecked
|
||||
List<GeoJson<?>> geoJsonList = ((List<Map<String, Object>>) geometries).stream()
|
||||
.map(MapToGeoJsonConverter.INSTANCE::convert).collect(Collectors.toList());
|
||||
return GeoJsonGeometryCollection.of(geoJsonList);
|
||||
}
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region helper functions
|
||||
private static String getGeoJsonType(Map<String, Object> source) {
|
||||
|
||||
Object type = source.get("type");
|
||||
Assert.notNull(type, "Document to convert does not contain a type");
|
||||
Assert.isTrue(type instanceof String, "type must be a String");
|
||||
|
||||
return type.toString();
|
||||
}
|
||||
|
||||
private static List<Double> toCoordinates(Point point) {
|
||||
return Arrays.asList(point.getX(), point.getY());
|
||||
}
|
||||
|
||||
private static List<List<Double>> pointsToCoordinates(List<Point> points) {
|
||||
return points.stream().map(GeoConverters::toCoordinates).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private static List<Point> coordinatesToPoints(List<List<Number>> pointList) {
|
||||
|
||||
Assert.isTrue(pointList.size() >= 2, "pointList must have at least 2 elements");
|
||||
|
||||
return pointList.stream().map(numbers -> {
|
||||
Assert.isTrue(numbers.size() >= 2, "coordinates must have at least 2 elements");
|
||||
|
||||
return new Point(numbers.get(0).doubleValue(), numbers.get(1).doubleValue());
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private static Map<String, Object> geoJsonLinesStringsToMap(String type, List<GeoJsonLineString> lineStrings) {
|
||||
Map<String, Object> map = new LinkedHashMap<>();
|
||||
map.put("type", type);
|
||||
List<List<List<Double>>> coordinates = lineStrings.stream()
|
||||
.map(it -> GeoConverters.pointsToCoordinates(it.getCoordinates())).collect(Collectors.toList());
|
||||
map.put("coordinates", coordinates);
|
||||
return map;
|
||||
}
|
||||
|
||||
private static List<GeoJsonLineString> geoJsonLineStringsFromMap(Map<String, Object> source) {
|
||||
Object coordinates = source.get("coordinates");
|
||||
Assert.notNull(coordinates, "Document to convert does not contain coordinates");
|
||||
Assert.isTrue(coordinates instanceof List, "coordinates must be a List");
|
||||
|
||||
// noinspection unchecked
|
||||
List<GeoJsonLineString> lines = ((List<?>) coordinates).stream()
|
||||
.map(it -> GeoJsonLineString.of(coordinatesToPoints((List<List<Number>>) it))).collect(Collectors.toList());
|
||||
return lines;
|
||||
}
|
||||
|
||||
// endregion
|
||||
}
|
||||
|
||||
+203
-72
@@ -15,18 +15,14 @@
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.core.convert;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.time.temporal.TemporalAccessor;
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
@@ -40,10 +36,13 @@ import org.springframework.data.elasticsearch.ElasticsearchException;
|
||||
import org.springframework.data.elasticsearch.annotations.ScriptedField;
|
||||
import org.springframework.data.elasticsearch.core.document.Document;
|
||||
import org.springframework.data.elasticsearch.core.document.SearchDocument;
|
||||
import org.springframework.data.elasticsearch.core.join.JoinField;
|
||||
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
|
||||
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
|
||||
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentPropertyConverter;
|
||||
import org.springframework.data.elasticsearch.core.query.Criteria;
|
||||
import org.springframework.data.elasticsearch.core.query.CriteriaQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.Field;
|
||||
import org.springframework.data.elasticsearch.core.query.SeqNoPrimaryTerm;
|
||||
import org.springframework.data.mapping.PersistentPropertyAccessor;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
@@ -72,18 +71,24 @@ import org.springframework.util.ObjectUtils;
|
||||
* @author Mark Paluch
|
||||
* @author Roman Puchkovskiy
|
||||
* @author Konrad Kurdej
|
||||
* @author Subhobrata Dey
|
||||
* @since 3.2
|
||||
*/
|
||||
public class MappingElasticsearchConverter
|
||||
implements ElasticsearchConverter, ApplicationContextAware, InitializingBean {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(MappingElasticsearchConverter.class);
|
||||
|
||||
private final MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext;
|
||||
private final GenericConversionService conversionService;
|
||||
|
||||
private CustomConversions conversions = new ElasticsearchCustomConversions(Collections.emptyList());
|
||||
private EntityInstantiators instantiators = new EntityInstantiators();
|
||||
// don't access directly, use getConversions(). to prevent null access
|
||||
@Nullable private CustomConversions conversions = null;
|
||||
private final EntityInstantiators instantiators = new EntityInstantiators();
|
||||
|
||||
private ElasticsearchTypeMapper typeMapper;
|
||||
private final ElasticsearchTypeMapper typeMapper;
|
||||
|
||||
private final ConcurrentHashMap<String, Integer> propertyWarnings = new ConcurrentHashMap<>();
|
||||
|
||||
public MappingElasticsearchConverter(
|
||||
MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext) {
|
||||
@@ -129,6 +134,14 @@ public class MappingElasticsearchConverter
|
||||
this.conversions = conversions;
|
||||
}
|
||||
|
||||
private CustomConversions getConversions() {
|
||||
|
||||
if (conversions == null) {
|
||||
conversions = new ElasticsearchCustomConversions(Collections.emptyList());
|
||||
}
|
||||
return conversions;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
|
||||
@@ -136,7 +149,7 @@ public class MappingElasticsearchConverter
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
DateFormatterRegistrar.addDateConverters(conversionService);
|
||||
conversions.registerConvertersIn(conversionService);
|
||||
getConversions().registerConvertersIn(conversionService);
|
||||
}
|
||||
|
||||
// region read
|
||||
@@ -147,7 +160,7 @@ public class MappingElasticsearchConverter
|
||||
TypeInformation<R> typeHint = ClassTypeInformation.from((Class<R>) ClassUtils.getUserClass(type));
|
||||
typeHint = (TypeInformation<R>) typeMapper.readType(source, typeHint);
|
||||
|
||||
if (conversions.hasCustomReadTarget(Map.class, typeHint.getType())) {
|
||||
if (getConversions().hasCustomReadTarget(Map.class, typeHint.getType())) {
|
||||
R converted = conversionService.convert(source, typeHint.getType());
|
||||
if (converted == null) {
|
||||
// EntityReader.read is defined as non nullable , so we cannot return null
|
||||
@@ -173,7 +186,7 @@ public class MappingElasticsearchConverter
|
||||
|
||||
EntityInstantiator instantiator = instantiators.getInstantiatorFor(targetEntity);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@SuppressWarnings({"unchecked", "ConstantConditions"})
|
||||
R instance = (R) instantiator.createInstance(targetEntity,
|
||||
new PersistentEntityParameterValueProvider<>(targetEntity, propertyValueProvider, null));
|
||||
|
||||
@@ -219,6 +232,7 @@ public class MappingElasticsearchConverter
|
||||
if (source instanceof SearchDocument) {
|
||||
SearchDocument searchDocument = (SearchDocument) source;
|
||||
if (targetEntity.hasScoreProperty()) {
|
||||
//noinspection ConstantConditions
|
||||
targetEntity.getPropertyAccessor(result) //
|
||||
.setProperty(targetEntity.getScoreProperty(), searchDocument.getScore());
|
||||
}
|
||||
@@ -267,12 +281,27 @@ public class MappingElasticsearchConverter
|
||||
return null;
|
||||
}
|
||||
|
||||
if (property.hasPropertyConverter() && String.class.isAssignableFrom(source.getClass())) {
|
||||
source = property.getPropertyConverter().read((String) source);
|
||||
Class<R> rawType = targetType.getType();
|
||||
|
||||
if (property.hasPropertyConverter()) {
|
||||
source = propertyConverterRead(property, source);
|
||||
} else if (TemporalAccessor.class.isAssignableFrom(property.getType())
|
||||
&& !getConversions().hasCustomReadTarget(source.getClass(), rawType)) {
|
||||
|
||||
// log at most 5 times
|
||||
String propertyName = property.getOwner().getType().getSimpleName() + '.' + property.getName();
|
||||
String key = propertyName + "-read";
|
||||
int count = propertyWarnings.computeIfAbsent(key, k -> 0);
|
||||
if (count < 5) {
|
||||
LOGGER.warn(
|
||||
"Type {} of property {} is a TemporalAccessor class but has neither a @Field annotation defining the date type nor a registered converter for reading!"
|
||||
+ " It cannot be mapped from a complex object in Elasticsearch!",
|
||||
property.getType().getSimpleName(), propertyName);
|
||||
propertyWarnings.put(key, count + 1);
|
||||
}
|
||||
}
|
||||
|
||||
Class<R> rawType = targetType.getType();
|
||||
if (conversions.hasCustomReadTarget(source.getClass(), rawType)) {
|
||||
if (getConversions().hasCustomReadTarget(source.getClass(), rawType)) {
|
||||
return rawType.cast(conversionService.convert(source, rawType));
|
||||
} else if (source instanceof List) {
|
||||
return readCollectionValue((List<?>) source, property, targetType);
|
||||
@@ -283,6 +312,32 @@ public class MappingElasticsearchConverter
|
||||
return (R) readSimpleValue(source, targetType);
|
||||
}
|
||||
|
||||
private Object propertyConverterRead(ElasticsearchPersistentProperty property, Object source) {
|
||||
ElasticsearchPersistentPropertyConverter propertyConverter = Objects
|
||||
.requireNonNull(property.getPropertyConverter());
|
||||
|
||||
if (source instanceof String[]) {
|
||||
// convert to a List
|
||||
source = Arrays.asList((String[]) source);
|
||||
}
|
||||
|
||||
if (source instanceof List) {
|
||||
source = ((List<?>) source).stream().map(it -> convertOnRead(propertyConverter, it)).collect(Collectors.toList());
|
||||
} else if (source instanceof Set) {
|
||||
source = ((Set<?>) source).stream().map(it -> convertOnRead(propertyConverter, it)).collect(Collectors.toSet());
|
||||
} else {
|
||||
source = convertOnRead(propertyConverter, source);
|
||||
}
|
||||
return source;
|
||||
}
|
||||
|
||||
private Object convertOnRead(ElasticsearchPersistentPropertyConverter propertyConverter, Object source) {
|
||||
if (String.class.isAssignableFrom(source.getClass())) {
|
||||
source = propertyConverter.read((String) source);
|
||||
}
|
||||
return source;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Nullable
|
||||
private <R> R readCollectionValue(@Nullable List<?> source, ElasticsearchPersistentProperty property,
|
||||
@@ -293,14 +348,17 @@ public class MappingElasticsearchConverter
|
||||
}
|
||||
|
||||
Collection<Object> target = createCollectionForValue(targetType, source.size());
|
||||
TypeInformation<?> componentType = targetType.getComponentType();
|
||||
|
||||
for (Object value : source) {
|
||||
|
||||
if (value == null) {
|
||||
target.add(null);
|
||||
} else if (componentType != null && !ClassTypeInformation.OBJECT.equals(componentType)
|
||||
&& isSimpleType(componentType.getType())) {
|
||||
target.add(readSimpleValue(value, componentType));
|
||||
} else if (isSimpleType(value)) {
|
||||
target.add(
|
||||
readSimpleValue(value, targetType.getComponentType() != null ? targetType.getComponentType() : targetType));
|
||||
target.add(readSimpleValue(value, componentType != null ? componentType : targetType));
|
||||
} else {
|
||||
|
||||
if (value instanceof List) {
|
||||
@@ -308,8 +366,6 @@ public class MappingElasticsearchConverter
|
||||
} else if (value instanceof Map) {
|
||||
target
|
||||
.add(readMapValue((Map<String, Object>) value, property, property.getTypeInformation().getActualType()));
|
||||
} else {
|
||||
target.add(readEntity(computeGenericValueTypeForRead(property, value), (Map<String, Object>) value));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -375,7 +431,7 @@ public class MappingElasticsearchConverter
|
||||
return value;
|
||||
}
|
||||
|
||||
if (conversions.hasCustomReadTarget(value.getClass(), target)) {
|
||||
if (getConversions().hasCustomReadTarget(value.getClass(), target)) {
|
||||
return conversionService.convert(value, target);
|
||||
}
|
||||
|
||||
@@ -429,7 +485,7 @@ public class MappingElasticsearchConverter
|
||||
typeMapper.writeType(source.getClass(), sink);
|
||||
}
|
||||
|
||||
Optional<Class<?>> customTarget = conversions.getCustomWriteTarget(entityType, Map.class);
|
||||
Optional<Class<?>> customTarget = getConversions().getCustomWriteTarget(entityType, Map.class);
|
||||
|
||||
if (customTarget.isPresent()) {
|
||||
sink.putAll(conversionService.convert(source, Map.class));
|
||||
@@ -467,12 +523,30 @@ public class MappingElasticsearchConverter
|
||||
Object value = accessor.getProperty(property);
|
||||
|
||||
if (value == null) {
|
||||
|
||||
if (property.storeNullValue()) {
|
||||
sink.set(property, null);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (property.hasPropertyConverter()) {
|
||||
ElasticsearchPersistentPropertyConverter propertyConverter = property.getPropertyConverter();
|
||||
value = propertyConverter.write(value);
|
||||
value = propertyConverterWrite(property, value);
|
||||
} else if (TemporalAccessor.class.isAssignableFrom(property.getActualType())
|
||||
&& !getConversions().hasCustomWriteTarget(value.getClass())) {
|
||||
|
||||
// log at most 5 times
|
||||
String propertyName = entity.getType().getSimpleName() + '.' + property.getName();
|
||||
String key = propertyName + "-write";
|
||||
int count = propertyWarnings.computeIfAbsent(key, k -> 0);
|
||||
if (count < 5) {
|
||||
LOGGER.warn(
|
||||
"Type {} of property {} is a TemporalAccessor class but has neither a @Field annotation defining the date type nor a registered converter for writing!"
|
||||
+ " It will be mapped to a complex object in Elasticsearch!",
|
||||
property.getType().getSimpleName(), propertyName);
|
||||
propertyWarnings.put(key, count + 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (!isSimpleType(value)) {
|
||||
@@ -486,9 +560,23 @@ public class MappingElasticsearchConverter
|
||||
}
|
||||
}
|
||||
|
||||
private Object propertyConverterWrite(ElasticsearchPersistentProperty property, Object value) {
|
||||
ElasticsearchPersistentPropertyConverter propertyConverter = Objects
|
||||
.requireNonNull(property.getPropertyConverter());
|
||||
|
||||
if (value instanceof List) {
|
||||
value = ((List<?>) value).stream().map(propertyConverter::write).collect(Collectors.toList());
|
||||
} else if (value instanceof Set) {
|
||||
value = ((Set<?>) value).stream().map(propertyConverter::write).collect(Collectors.toSet());
|
||||
} else {
|
||||
value = propertyConverter.write(value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
protected void writeProperty(ElasticsearchPersistentProperty property, Object value, MapValueAccessor sink) {
|
||||
|
||||
Optional<Class<?>> customWriteTarget = conversions.getCustomWriteTarget(value.getClass());
|
||||
Optional<Class<?>> customWriteTarget = getConversions().getCustomWriteTarget(value.getClass());
|
||||
|
||||
if (customWriteTarget.isPresent()) {
|
||||
Class<?> writeTarget = customWriteTarget.get();
|
||||
@@ -515,7 +603,7 @@ public class MappingElasticsearchConverter
|
||||
|
||||
@Nullable
|
||||
protected Object getWriteSimpleValue(Object value) {
|
||||
Optional<Class<?>> customTarget = conversions.getCustomWriteTarget(value.getClass());
|
||||
Optional<Class<?>> customTarget = getConversions().getCustomWriteTarget(value.getClass());
|
||||
|
||||
if (customTarget.isPresent()) {
|
||||
return conversionService.convert(value, customTarget.get());
|
||||
@@ -536,13 +624,13 @@ public class MappingElasticsearchConverter
|
||||
}
|
||||
|
||||
if (property.isEntity() || !isSimpleType(value)) {
|
||||
return writeEntity(value, property, typeHint);
|
||||
return writeEntity(value, property);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
private Object writeEntity(Object value, ElasticsearchPersistentProperty property, TypeInformation<?> typeHint) {
|
||||
private Object writeEntity(Object value, ElasticsearchPersistentProperty property) {
|
||||
|
||||
Document target = Document.create();
|
||||
writeEntity(mappingContext.getRequiredPersistentEntity(value.getClass()), value, target,
|
||||
@@ -556,7 +644,9 @@ public class MappingElasticsearchConverter
|
||||
Map<Object, Object> target = new LinkedHashMap<>();
|
||||
Streamable<Entry<String, Object>> mapSource = Streamable.of(value.entrySet());
|
||||
|
||||
if (!typeHint.getActualType().getType().equals(Object.class)
|
||||
TypeInformation<?> actualType = typeHint.getActualType();
|
||||
|
||||
if (actualType != null && !actualType.getType().equals(Object.class)
|
||||
&& isSimpleType(typeHint.getMapValueType().getType())) {
|
||||
mapSource.forEach(it -> {
|
||||
|
||||
@@ -595,8 +685,14 @@ public class MappingElasticsearchConverter
|
||||
: 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);
|
||||
TypeInformation<?> actualType = typeHint.getActualType();
|
||||
Class<?> type = actualType != null ? actualType.getType() : null;
|
||||
|
||||
if (type != null && !type.equals(Object.class) && isSimpleType(type)) {
|
||||
// noinspection ReturnOfNull
|
||||
collectionSource //
|
||||
.map(element -> element != null ? getWriteSimpleValue(element) : null) //
|
||||
.forEach(target::add);
|
||||
} else {
|
||||
|
||||
collectionSource.map(it -> {
|
||||
@@ -662,18 +758,21 @@ public class MappingElasticsearchConverter
|
||||
if (container.equals(type) && type.getType().equals(actualType)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (container.getRawTypeInformation().equals(type)) {
|
||||
Class<?> containerClass = container.getRawTypeInformation().getType();
|
||||
if (containerClass.equals(JoinField.class) && type.getType().equals(actualType)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return !conversions.isSimpleType(type.getType()) && !type.isCollectionLike()
|
||||
&& !conversions.hasCustomWriteTarget(type.getType());
|
||||
return !getConversions().isSimpleType(type.getType()) && !type.isCollectionLike()
|
||||
&& !getConversions().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) {
|
||||
@@ -698,39 +797,62 @@ public class MappingElasticsearchConverter
|
||||
}
|
||||
|
||||
private boolean isSimpleType(Class<?> type) {
|
||||
return conversions.isSimpleType(type);
|
||||
return getConversions().isSimpleType(type);
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region queries
|
||||
@Override
|
||||
public void updateQuery(CriteriaQuery criteriaQuery, Class<?> domainClass) {
|
||||
public void updateCriteriaQuery(CriteriaQuery criteriaQuery, Class<?> domainClass) {
|
||||
ElasticsearchPersistentEntity<?> persistentEntity = mappingContext.getPersistentEntity(domainClass);
|
||||
|
||||
if (persistentEntity != null) {
|
||||
criteriaQuery.getCriteria().getCriteriaChain().forEach(criteria -> {
|
||||
String name = criteria.getField().getName();
|
||||
ElasticsearchPersistentProperty property = persistentEntity.getPersistentProperty(name);
|
||||
for (Criteria chainedCriteria : criteriaQuery.getCriteria().getCriteriaChain()) {
|
||||
updateCriteria(chainedCriteria, persistentEntity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (property != null && property.getName().equals(name)) {
|
||||
criteria.getField().setName(property.getFieldName());
|
||||
private void updateCriteria(Criteria criteria, ElasticsearchPersistentEntity<?> persistentEntity) {
|
||||
Field field = criteria.getField();
|
||||
|
||||
if (property.hasPropertyConverter()) {
|
||||
ElasticsearchPersistentPropertyConverter propertyConverter = property.getPropertyConverter();
|
||||
criteria.getQueryCriteriaEntries().forEach(criteriaEntry -> {
|
||||
Object value = criteriaEntry.getValue();
|
||||
if (value.getClass().isArray()) {
|
||||
Object[] objects = (Object[]) value;
|
||||
for (int i = 0; i < objects.length; i++) {
|
||||
objects[i] = propertyConverter.write(objects[i]);
|
||||
}
|
||||
} else {
|
||||
criteriaEntry.setValue(propertyConverter.write(value));
|
||||
}
|
||||
});
|
||||
if (field == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
String name = field.getName();
|
||||
ElasticsearchPersistentProperty property = persistentEntity.getPersistentProperty(name);
|
||||
|
||||
if (property != null && property.getName().equals(name)) {
|
||||
field.setName(property.getFieldName());
|
||||
|
||||
if (property.hasPropertyConverter()) {
|
||||
ElasticsearchPersistentPropertyConverter propertyConverter = Objects.requireNonNull(property.getPropertyConverter());
|
||||
criteria.getQueryCriteriaEntries().forEach(criteriaEntry -> {
|
||||
Object value = criteriaEntry.getValue();
|
||||
if (value.getClass().isArray()) {
|
||||
Object[] objects = (Object[]) value;
|
||||
for (int i = 0; i < objects.length; i++) {
|
||||
objects[i] = propertyConverter.write(objects[i]);
|
||||
}
|
||||
} else {
|
||||
criteriaEntry.setValue(propertyConverter.write(value));
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
org.springframework.data.elasticsearch.annotations.Field fieldAnnotation = property
|
||||
.findAnnotation(org.springframework.data.elasticsearch.annotations.Field.class);
|
||||
|
||||
if (fieldAnnotation != null) {
|
||||
field.setFieldType(fieldAnnotation.type());
|
||||
}
|
||||
}
|
||||
|
||||
for (Criteria subCriteria : criteria.getSubCriteria()) {
|
||||
for (Criteria chainedCriteria : subCriteria.getCriteriaChain()) {
|
||||
updateCriteria(chainedCriteria, persistentEntity);
|
||||
}
|
||||
}
|
||||
}
|
||||
// endregion
|
||||
@@ -746,13 +868,21 @@ public class MappingElasticsearchConverter
|
||||
@Nullable
|
||||
public Object get(ElasticsearchPersistentProperty property) {
|
||||
|
||||
String fieldName = property.getFieldName();
|
||||
|
||||
if (target instanceof Document) {
|
||||
// nested objects may have properties like 'id' which are recognized as isIdProperty() but they are not
|
||||
// Documents
|
||||
Document document = (Document) target;
|
||||
|
||||
if (property.isIdProperty() && document.hasId()) {
|
||||
return document.getId();
|
||||
Object id = null;
|
||||
|
||||
// take the id property from the document source if available
|
||||
if (!fieldName.contains(".")) {
|
||||
id = target.get(fieldName);
|
||||
}
|
||||
return id != null ? id : document.getId();
|
||||
}
|
||||
|
||||
if (property.isVersionProperty() && document.hasVersion()) {
|
||||
@@ -765,8 +895,6 @@ public class MappingElasticsearchConverter
|
||||
return ((SearchDocument) target).getScore();
|
||||
}
|
||||
|
||||
String fieldName = property.getFieldName();
|
||||
|
||||
if (!fieldName.contains(".")) {
|
||||
return target.get(fieldName);
|
||||
}
|
||||
@@ -787,14 +915,17 @@ public class MappingElasticsearchConverter
|
||||
return result;
|
||||
}
|
||||
|
||||
public void set(ElasticsearchPersistentProperty property, Object value) {
|
||||
public void set(ElasticsearchPersistentProperty property, @Nullable Object value) {
|
||||
|
||||
if (property.isIdProperty()) {
|
||||
((Document) target).setId(value.toString());
|
||||
}
|
||||
if (value != null) {
|
||||
|
||||
if (property.isVersionProperty()) {
|
||||
((Document) target).setVersion((Long) value);
|
||||
if (property.isIdProperty()) {
|
||||
((Document) target).setId(value.toString());
|
||||
}
|
||||
|
||||
if (property.isVersionProperty()) {
|
||||
((Document) target).setVersion((Long) value);
|
||||
}
|
||||
}
|
||||
|
||||
target.put(property.getFieldName(), value);
|
||||
|
||||
@@ -24,7 +24,7 @@ import java.util.function.IntSupplier;
|
||||
import java.util.function.LongSupplier;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.springframework.data.elasticsearch.ElasticsearchException;
|
||||
import org.springframework.data.elasticsearch.core.convert.ConversionException;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@@ -83,7 +83,7 @@ public interface Document extends Map<String, Object> {
|
||||
try {
|
||||
return new MapDocument(MapDocument.OBJECT_MAPPER.readerFor(Map.class).readValue(json));
|
||||
} catch (IOException e) {
|
||||
throw new ElasticsearchException("Cannot parse JSON", e);
|
||||
throw new ConversionException("Cannot parse JSON", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,6 +111,27 @@ public interface Document extends Map<String, Object> {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the index if this document was retrieved from an index
|
||||
* @since 4.1
|
||||
*/
|
||||
@Nullable
|
||||
default String getIndex() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the index name for this document
|
||||
*
|
||||
* @param index index name
|
||||
* <p>
|
||||
* The default implementation throws {@link UnsupportedOperationException}.
|
||||
* @since 4.1
|
||||
*/
|
||||
default void setIndex(@Nullable String index) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the identifier associated with this {@link Document}.
|
||||
* <p>
|
||||
@@ -461,4 +482,5 @@ public interface Document extends Map<String, Object> {
|
||||
* @return a JSON representation of this document.
|
||||
*/
|
||||
String toJson();
|
||||
|
||||
}
|
||||
|
||||
+74
-247
@@ -22,6 +22,7 @@ import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -37,6 +38,7 @@ import org.elasticsearch.common.document.DocumentField;
|
||||
import org.elasticsearch.common.text.Text;
|
||||
import org.elasticsearch.index.get.GetResult;
|
||||
import org.elasticsearch.search.SearchHit;
|
||||
import org.elasticsearch.search.SearchHits;
|
||||
import org.springframework.data.elasticsearch.ElasticsearchException;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -77,11 +79,12 @@ public class DocumentAdapters {
|
||||
}
|
||||
|
||||
if (source.isSourceEmpty()) {
|
||||
return fromDocumentFields(source, source.getId(), source.getVersion(), source.getSeqNo(),
|
||||
return fromDocumentFields(source, source.getIndex(), source.getId(), source.getVersion(), source.getSeqNo(),
|
||||
source.getPrimaryTerm());
|
||||
}
|
||||
|
||||
Document document = Document.from(source.getSourceAsMap());
|
||||
document.setIndex(source.getIndex());
|
||||
document.setId(source.getId());
|
||||
document.setVersion(source.getVersion());
|
||||
document.setSeqNo(source.getSeqNo());
|
||||
@@ -108,11 +111,12 @@ public class DocumentAdapters {
|
||||
}
|
||||
|
||||
if (source.isSourceEmpty()) {
|
||||
return fromDocumentFields(source, source.getId(), source.getVersion(), source.getSeqNo(),
|
||||
return fromDocumentFields(source, source.getIndex(), source.getId(), source.getVersion(), source.getSeqNo(),
|
||||
source.getPrimaryTerm());
|
||||
}
|
||||
|
||||
Document document = Document.from(source.getSource());
|
||||
document.setIndex(source.getIndex());
|
||||
document.setId(source.getId());
|
||||
document.setVersion(source.getVersion());
|
||||
document.setSeqNo(source.getSeqNo());
|
||||
@@ -153,14 +157,32 @@ public class DocumentAdapters {
|
||||
.collect(Collectors.toMap(Map.Entry::getKey,
|
||||
entry -> Arrays.stream(entry.getValue().getFragments()).map(Text::string).collect(Collectors.toList()))));
|
||||
|
||||
Map<String, SearchDocumentResponse> innerHits = new LinkedHashMap<>();
|
||||
Map<String, SearchHits> sourceInnerHits = source.getInnerHits();
|
||||
|
||||
if (sourceInnerHits != null) {
|
||||
sourceInnerHits.forEach((name, searchHits) -> {
|
||||
innerHits.put(name, SearchDocumentResponse.from(searchHits, null, null));
|
||||
});
|
||||
}
|
||||
|
||||
NestedMetaData nestedMetaData = null;
|
||||
|
||||
if (source.getNestedIdentity() != null) {
|
||||
nestedMetaData = from(source.getNestedIdentity());
|
||||
}
|
||||
|
||||
BytesReference sourceRef = source.getSourceRef();
|
||||
|
||||
if (sourceRef == null || sourceRef.length() == 0) {
|
||||
return new SearchDocumentAdapter(source.getScore(), source.getSortValues(), source.getFields(), highlightFields,
|
||||
fromDocumentFields(source, source.getId(), source.getVersion(), source.getSeqNo(), source.getPrimaryTerm()));
|
||||
return new SearchDocumentAdapter(
|
||||
source.getScore(), source.getSortValues(), source.getFields(), highlightFields, fromDocumentFields(source,
|
||||
source.getIndex(), source.getId(), source.getVersion(), source.getSeqNo(), source.getPrimaryTerm()),
|
||||
innerHits, nestedMetaData);
|
||||
}
|
||||
|
||||
Document document = Document.from(source.getSourceAsMap());
|
||||
document.setIndex(source.getIndex());
|
||||
document.setId(source.getId());
|
||||
|
||||
if (source.getVersion() >= 0) {
|
||||
@@ -170,20 +192,33 @@ public class DocumentAdapters {
|
||||
document.setPrimaryTerm(source.getPrimaryTerm());
|
||||
|
||||
return new SearchDocumentAdapter(source.getScore(), source.getSortValues(), source.getFields(), highlightFields,
|
||||
document);
|
||||
document, innerHits, nestedMetaData);
|
||||
}
|
||||
|
||||
private static NestedMetaData from(SearchHit.NestedIdentity nestedIdentity) {
|
||||
|
||||
NestedMetaData child = null;
|
||||
|
||||
if (nestedIdentity.getChild() != null) {
|
||||
child = from(nestedIdentity.getChild());
|
||||
}
|
||||
|
||||
return NestedMetaData.of(nestedIdentity.getField().string(), nestedIdentity.getOffset(), child);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an unmodifiable {@link Document} from {@link Iterable} of {@link DocumentField}s.
|
||||
*
|
||||
* @param documentFields the {@link DocumentField}s backing the {@link Document}.
|
||||
* @param index
|
||||
* @return the adapted {@link Document}.
|
||||
*/
|
||||
public static Document fromDocumentFields(Iterable<DocumentField> documentFields, String id, long version, long seqNo,
|
||||
long primaryTerm) {
|
||||
public static Document fromDocumentFields(Iterable<DocumentField> documentFields, String index, String id,
|
||||
long version, long seqNo, long primaryTerm) {
|
||||
|
||||
if (documentFields instanceof Collection) {
|
||||
return new DocumentFieldAdapter((Collection<DocumentField>) documentFields, id, version, seqNo, primaryTerm);
|
||||
return new DocumentFieldAdapter((Collection<DocumentField>) documentFields, index, id, version, seqNo,
|
||||
primaryTerm);
|
||||
}
|
||||
|
||||
List<DocumentField> fields = new ArrayList<>();
|
||||
@@ -191,58 +226,49 @@ public class DocumentAdapters {
|
||||
fields.add(documentField);
|
||||
}
|
||||
|
||||
return new DocumentFieldAdapter(fields, id, version, seqNo, primaryTerm);
|
||||
return new DocumentFieldAdapter(fields, index, id, version, seqNo, primaryTerm);
|
||||
}
|
||||
|
||||
// TODO: Performance regarding keys/values/entry-set
|
||||
static class DocumentFieldAdapter implements Document {
|
||||
|
||||
private final Collection<DocumentField> documentFields;
|
||||
private final String index;
|
||||
private final String id;
|
||||
private final long version;
|
||||
private final long seqNo;
|
||||
private final long primaryTerm;
|
||||
|
||||
DocumentFieldAdapter(Collection<DocumentField> documentFields, String id, long version, long seqNo,
|
||||
DocumentFieldAdapter(Collection<DocumentField> documentFields, String index, String id, long version, long seqNo,
|
||||
long primaryTerm) {
|
||||
this.documentFields = documentFields;
|
||||
this.index = index;
|
||||
this.id = id;
|
||||
this.version = version;
|
||||
this.seqNo = seqNo;
|
||||
this.primaryTerm = primaryTerm;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.document.Document#hasId()
|
||||
*/
|
||||
@Override
|
||||
public String getIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasId() {
|
||||
return StringUtils.hasLength(id);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.document.Document#getId()
|
||||
*/
|
||||
@Override
|
||||
public String getId() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.document.Document#hasVersion()
|
||||
*/
|
||||
@Override
|
||||
public boolean hasVersion() {
|
||||
return this.version >= 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.document.Document#getVersion()
|
||||
*/
|
||||
@Override
|
||||
public long getVersion() {
|
||||
|
||||
@@ -253,19 +279,11 @@ public class DocumentAdapters {
|
||||
return this.version;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.document.Document#hasSeqNo()
|
||||
*/
|
||||
@Override
|
||||
public boolean hasSeqNo() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.document.Document#getSeqNo()
|
||||
*/
|
||||
@Override
|
||||
public long getSeqNo() {
|
||||
|
||||
@@ -276,19 +294,11 @@ public class DocumentAdapters {
|
||||
return this.seqNo;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.document.Document#hasPrimaryTerm()
|
||||
*/
|
||||
@Override
|
||||
public boolean hasPrimaryTerm() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.document.Document#getPrimaryTerm()
|
||||
*/
|
||||
@Override
|
||||
public long getPrimaryTerm() {
|
||||
|
||||
@@ -299,28 +309,16 @@ public class DocumentAdapters {
|
||||
return this.primaryTerm;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.util.Map#size()
|
||||
*/
|
||||
@Override
|
||||
public int size() {
|
||||
return documentFields.size();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.util.Map#isEmpty()
|
||||
*/
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return documentFields.isEmpty();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.util.Map#containsKey(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public boolean containsKey(Object key) {
|
||||
|
||||
@@ -333,10 +331,6 @@ public class DocumentAdapters {
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.util.Map#containsValue(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public boolean containsValue(Object value) {
|
||||
|
||||
@@ -351,10 +345,6 @@ public class DocumentAdapters {
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.util.Map#get(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
@Nullable
|
||||
public Object get(Object key) {
|
||||
@@ -365,74 +355,42 @@ public class DocumentAdapters {
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.util.Map#put(java.lang.Object, java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public Object put(String key, Object value) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.util.Map#remove(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public Object remove(Object key) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.util.Map#putAll(Map)
|
||||
*/
|
||||
@Override
|
||||
public void putAll(Map<? extends String, ?> m) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.util.Map#clear()
|
||||
*/
|
||||
@Override
|
||||
public void clear() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.util.Map#keySet()
|
||||
*/
|
||||
@Override
|
||||
public Set<String> keySet() {
|
||||
return documentFields.stream().map(DocumentField::getName).collect(Collectors.toCollection(LinkedHashSet::new));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.util.Map#values()
|
||||
*/
|
||||
@Override
|
||||
public Collection<Object> values() {
|
||||
return documentFields.stream().map(DocumentFieldAdapter::getValue).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.util.Map#entrySet()
|
||||
*/
|
||||
@Override
|
||||
public Set<Entry<String, Object>> entrySet() {
|
||||
return documentFields.stream().collect(Collectors.toMap(DocumentField::getName, DocumentFieldAdapter::getValue))
|
||||
.entrySet();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.util.Map#forEach(java.util.function.BiConsumer)
|
||||
*/
|
||||
@Override
|
||||
public void forEach(BiConsumer<? super String, ? super Object> action) {
|
||||
|
||||
@@ -441,10 +399,6 @@ public class DocumentAdapters {
|
||||
documentFields.forEach(field -> action.accept(field.getName(), getValue(field)));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.document.Document#toJson()
|
||||
*/
|
||||
@Override
|
||||
public String toJson() {
|
||||
|
||||
@@ -472,10 +426,6 @@ public class DocumentAdapters {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getSimpleName() + '@' + this.id + '#' + this.version + ' ' + toJson();
|
||||
@@ -506,21 +456,22 @@ public class DocumentAdapters {
|
||||
private final Map<String, List<Object>> fields = new HashMap<>();
|
||||
private final Document delegate;
|
||||
private final Map<String, List<String>> highlightFields = new HashMap<>();
|
||||
private final Map<String, SearchDocumentResponse> innerHits = new HashMap<>();
|
||||
@Nullable private final NestedMetaData nestedMetaData;
|
||||
|
||||
SearchDocumentAdapter(float score, Object[] sortValues, Map<String, DocumentField> fields,
|
||||
Map<String, List<String>> highlightFields, Document delegate) {
|
||||
Map<String, List<String>> highlightFields, Document delegate, Map<String, SearchDocumentResponse> innerHits,
|
||||
@Nullable NestedMetaData nestedMetaData) {
|
||||
|
||||
this.score = score;
|
||||
this.sortValues = sortValues;
|
||||
this.delegate = delegate;
|
||||
fields.forEach((name, documentField) -> this.fields.put(name, documentField.getValues()));
|
||||
this.highlightFields.putAll(highlightFields);
|
||||
this.innerHits.putAll(innerHits);
|
||||
this.nestedMetaData = nestedMetaData;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.document.Document#append(java.lang.String, java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public SearchDocument append(String key, Object value) {
|
||||
delegate.append(key, value);
|
||||
@@ -528,281 +479,173 @@ public class DocumentAdapters {
|
||||
return this;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.document.SearchDocument#getScore()
|
||||
*/
|
||||
@Override
|
||||
public float getScore() {
|
||||
return score;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.document.SearchDocument#getFields()
|
||||
*/
|
||||
@Override
|
||||
public Map<String, List<Object>> getFields() {
|
||||
return fields;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.document.SearchDocument#getSortValues()
|
||||
*/
|
||||
@Override
|
||||
public Object[] getSortValues() {
|
||||
return sortValues;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.document.SearchDocument#getHighlightFields()
|
||||
*/
|
||||
@Override
|
||||
public Map<String, List<String>> getHighlightFields() {
|
||||
return highlightFields;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.document.Document#hasId()
|
||||
*/
|
||||
@Override
|
||||
public String getIndex() {
|
||||
return delegate.getIndex();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasId() {
|
||||
return delegate.hasId();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.document.Document#getId()
|
||||
*/
|
||||
@Override
|
||||
public String getId() {
|
||||
return delegate.getId();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.document.Document#setId(java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public void setId(String id) {
|
||||
delegate.setId(id);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.document.Document#hasVersion()
|
||||
*/
|
||||
@Override
|
||||
public boolean hasVersion() {
|
||||
return delegate.hasVersion();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.document.Document#getVersion()
|
||||
*/
|
||||
@Override
|
||||
public long getVersion() {
|
||||
return delegate.getVersion();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.document.Document#setVersion(long)
|
||||
*/
|
||||
@Override
|
||||
public void setVersion(long version) {
|
||||
delegate.setVersion(version);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.document.Document#hasSeqNo()
|
||||
*/
|
||||
@Override
|
||||
public boolean hasSeqNo() {
|
||||
return delegate.hasSeqNo();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.document.Document#getSeqNo()
|
||||
*/
|
||||
@Override
|
||||
public long getSeqNo() {
|
||||
return delegate.getSeqNo();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.document.Document#setSeqNo(long)
|
||||
*/
|
||||
@Override
|
||||
public void setSeqNo(long seqNo) {
|
||||
delegate.setSeqNo(seqNo);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.document.Document#hasPrimaryTerm()
|
||||
*/
|
||||
@Override
|
||||
public boolean hasPrimaryTerm() {
|
||||
return delegate.hasPrimaryTerm();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.document.Document#getPrimaryTerm()
|
||||
*/
|
||||
@Override
|
||||
public long getPrimaryTerm() {
|
||||
return delegate.getPrimaryTerm();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.document.Document#setPrimaryTerm(long)
|
||||
*/
|
||||
@Override
|
||||
public void setPrimaryTerm(long primaryTerm) {
|
||||
delegate.setPrimaryTerm(primaryTerm);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.document.Document#get(java.lang.Object, java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public Map<String, SearchDocumentResponse> getInnerHits() {
|
||||
return innerHits;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public NestedMetaData getNestedMetaData() {
|
||||
return nestedMetaData;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public <T> T get(Object key, Class<T> type) {
|
||||
return delegate.get(key, type);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.document.Document#toJson()
|
||||
*/
|
||||
@Override
|
||||
public String toJson() {
|
||||
return delegate.toJson();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.util.Map#size()
|
||||
*/
|
||||
@Override
|
||||
public int size() {
|
||||
return delegate.size();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.util.Map#isEmpty()
|
||||
*/
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return delegate.isEmpty();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.util.Map#containsKey(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public boolean containsKey(Object key) {
|
||||
return delegate.containsKey(key);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.util.Map#containsValue(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public boolean containsValue(Object value) {
|
||||
return delegate.containsValue(value);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.util.Map#get(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public Object get(Object key) {
|
||||
return delegate.get(key);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.util.Map#put(java.lang.Object, java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public Object put(String key, Object value) {
|
||||
return delegate.put(key, value);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.util.Map#remove(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public Object remove(Object key) {
|
||||
return delegate.remove(key);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.util.Map#putAll(Map)
|
||||
*/
|
||||
@Override
|
||||
public void putAll(Map<? extends String, ?> m) {
|
||||
delegate.putAll(m);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.util.Map#clear()
|
||||
*/
|
||||
@Override
|
||||
public void clear() {
|
||||
delegate.clear();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.util.Map#keySet()
|
||||
*/
|
||||
@Override
|
||||
public Set<String> keySet() {
|
||||
return delegate.keySet();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.util.Map#values()
|
||||
*/
|
||||
@Override
|
||||
public Collection<Object> values() {
|
||||
return delegate.values();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.util.Map#entrySet()
|
||||
*/
|
||||
@Override
|
||||
public Set<Entry<String, Object>> entrySet() {
|
||||
return delegate.entrySet();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#equals(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
@@ -815,37 +658,21 @@ public class DocumentAdapters {
|
||||
return Float.compare(that.score, score) == 0 && delegate.equals(that.delegate);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#hashCode()
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return delegate.hashCode();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.util.Map#forEach(java.util.function.BiConsumer)
|
||||
*/
|
||||
@Override
|
||||
public void forEach(BiConsumer<? super String, ? super Object> action) {
|
||||
delegate.forEach(action);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.util.Map#remove(java.lang.Object, java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public boolean remove(Object key, Object value) {
|
||||
return delegate.remove(key, value);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@ class MapDocument implements Document {
|
||||
|
||||
private final LinkedHashMap<String, Object> documentAsMap;
|
||||
|
||||
private @Nullable String index;
|
||||
private @Nullable String id;
|
||||
private @Nullable Long version;
|
||||
private @Nullable Long seqNo;
|
||||
@@ -53,6 +54,17 @@ class MapDocument implements Document {
|
||||
this.documentAsMap = new LinkedHashMap<>(documentAsMap);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setIndex(@Nullable String index) {
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String getIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.document.Document#hasId()
|
||||
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright 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.document;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* meta data returned for nested inner hits.
|
||||
*
|
||||
* @author Peter-Josef Meisch
|
||||
*/
|
||||
public class NestedMetaData {
|
||||
|
||||
private final String field;
|
||||
private final int offset;
|
||||
@Nullable private final NestedMetaData child;
|
||||
|
||||
public static NestedMetaData of(String field, int offset, @Nullable NestedMetaData nested) {
|
||||
return new NestedMetaData(field, offset, nested);
|
||||
}
|
||||
|
||||
private NestedMetaData(String field, int offset, @Nullable NestedMetaData child) {
|
||||
this.field = field;
|
||||
this.offset = offset;
|
||||
this.child = child;
|
||||
}
|
||||
|
||||
public String getField() {
|
||||
return field;
|
||||
}
|
||||
|
||||
public int getOffset() {
|
||||
return offset;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public NestedMetaData getChild() {
|
||||
return child;
|
||||
}
|
||||
}
|
||||
+20
-1
@@ -69,5 +69,24 @@ public interface SearchDocument extends Document {
|
||||
*/
|
||||
@Nullable
|
||||
default Map<String, List<String>> getHighlightFields() {
|
||||
return null;}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the innerHits for the SearchHit
|
||||
* @since 4.1
|
||||
*/
|
||||
@Nullable
|
||||
default Map<String, SearchDocumentResponse> getInnerHits() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the nested metadata in case this is a nested inner hit.
|
||||
* @since 4.1
|
||||
*/
|
||||
@Nullable
|
||||
default NestedMetaData getNestedMetaData() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
+49
-19
@@ -15,14 +15,15 @@
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.core.document;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
import org.apache.lucene.search.TotalHits;
|
||||
import org.elasticsearch.action.search.SearchResponse;
|
||||
import org.elasticsearch.search.SearchHit;
|
||||
import org.elasticsearch.search.SearchHits;
|
||||
import org.elasticsearch.search.aggregations.Aggregations;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
@@ -34,15 +35,15 @@ import org.springframework.util.Assert;
|
||||
*/
|
||||
public class SearchDocumentResponse {
|
||||
|
||||
private long totalHits;
|
||||
private String totalHitsRelation;
|
||||
private float maxScore;
|
||||
private final long totalHits;
|
||||
private final String totalHitsRelation;
|
||||
private final float maxScore;
|
||||
private final String scrollId;
|
||||
private final List<SearchDocument> searchDocuments;
|
||||
private final Aggregations aggregations;
|
||||
|
||||
private SearchDocumentResponse(long totalHits, String totalHitsRelation, float maxScore, String scrollId,
|
||||
List<SearchDocument> searchDocuments, Aggregations aggregations) {
|
||||
List<SearchDocument> searchDocuments, Aggregations aggregations) {
|
||||
this.totalHits = totalHits;
|
||||
this.totalHitsRelation = totalHitsRelation;
|
||||
this.maxScore = maxScore;
|
||||
@@ -78,27 +79,56 @@ public class SearchDocumentResponse {
|
||||
/**
|
||||
* creates a SearchDocumentResponse from the {@link SearchResponse}
|
||||
*
|
||||
* @param searchResponse
|
||||
* must not be {@literal null}
|
||||
* @param searchResponse must not be {@literal null}
|
||||
* @return the SearchDocumentResponse
|
||||
*/
|
||||
public static SearchDocumentResponse from(SearchResponse searchResponse) {
|
||||
|
||||
Assert.notNull(searchResponse, "searchResponse must not be null");
|
||||
|
||||
TotalHits responseTotalHits = searchResponse.getHits().getTotalHits();
|
||||
long totalHits = responseTotalHits.value;
|
||||
String totalHitsRelation = responseTotalHits.relation.name();
|
||||
|
||||
float maxScore = searchResponse.getHits().getMaxScore();
|
||||
Aggregations aggregations = searchResponse.getAggregations();
|
||||
String scrollId = searchResponse.getScrollId();
|
||||
|
||||
List<SearchDocument> searchDocuments = StreamSupport.stream(searchResponse.getHits().spliterator(), false) //
|
||||
.filter(Objects::nonNull) //
|
||||
.map(DocumentAdapters::from) //
|
||||
.collect(Collectors.toList());
|
||||
SearchHits searchHits = searchResponse.getHits();
|
||||
|
||||
Aggregations aggregations = searchResponse.getAggregations();
|
||||
SearchDocumentResponse searchDocumentResponse = from(searchHits, scrollId, aggregations);
|
||||
return searchDocumentResponse;
|
||||
}
|
||||
|
||||
/**
|
||||
* creates a {@link SearchDocumentResponse} from {@link SearchHits} with the given scrollId and aggregations
|
||||
*
|
||||
* @param searchHits the {@link SearchHits} to process
|
||||
* @param scrollId scrollId
|
||||
* @param aggregations aggregations
|
||||
* @return the {@link SearchDocumentResponse}
|
||||
* @since 4.1
|
||||
*/
|
||||
public static SearchDocumentResponse from(SearchHits searchHits, @Nullable String scrollId,
|
||||
@Nullable Aggregations aggregations) {
|
||||
TotalHits responseTotalHits = searchHits.getTotalHits();
|
||||
|
||||
long totalHits;
|
||||
String totalHitsRelation;
|
||||
|
||||
if (responseTotalHits != null) {
|
||||
totalHits = responseTotalHits.value;
|
||||
totalHitsRelation = responseTotalHits.relation.name();
|
||||
} else {
|
||||
totalHits = searchHits.getHits().length;
|
||||
totalHitsRelation = "OFF";
|
||||
}
|
||||
|
||||
float maxScore = searchHits.getMaxScore();
|
||||
|
||||
List<SearchDocument> searchDocuments = new ArrayList<>();
|
||||
for (SearchHit searchHit : searchHits) {
|
||||
if (searchHit != null) {
|
||||
searchDocuments.add(DocumentAdapters.from(searchHit));
|
||||
}
|
||||
}
|
||||
|
||||
return new SearchDocumentResponse(totalHits, totalHitsRelation, maxScore, scrollId, searchDocuments, aggregations);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+6
-6
@@ -19,7 +19,7 @@ import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.beans.factory.ObjectFactory;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.data.auditing.IsNewAwareAuditingHandler;
|
||||
import org.springframework.data.auditing.ReactiveIsNewAwareAuditingHandler;
|
||||
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
||||
import org.springframework.data.mapping.callback.EntityCallback;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -33,15 +33,15 @@ import org.springframework.util.Assert;
|
||||
*/
|
||||
public class ReactiveAuditingEntityCallback implements ReactiveBeforeConvertCallback<Object>, Ordered {
|
||||
|
||||
private final ObjectFactory<IsNewAwareAuditingHandler> auditingHandlerFactory;
|
||||
private final ObjectFactory<ReactiveIsNewAwareAuditingHandler> auditingHandlerFactory;
|
||||
|
||||
/**
|
||||
* Creates a new {@link ReactiveAuditingEntityCallback} using the given {@link IsNewAwareAuditingHandler} provided by
|
||||
* the given {@link ObjectFactory}.
|
||||
* Creates a new {@link ReactiveAuditingEntityCallback} using the given {@link ReactiveIsNewAwareAuditingHandler}
|
||||
* provided by the given {@link ObjectFactory}.
|
||||
*
|
||||
* @param auditingHandlerFactory must not be {@literal null}.
|
||||
*/
|
||||
public ReactiveAuditingEntityCallback(ObjectFactory<IsNewAwareAuditingHandler> auditingHandlerFactory) {
|
||||
public ReactiveAuditingEntityCallback(ObjectFactory<ReactiveIsNewAwareAuditingHandler> auditingHandlerFactory) {
|
||||
|
||||
Assert.notNull(auditingHandlerFactory, "IsNewAwareAuditingHandler must not be null!");
|
||||
|
||||
@@ -50,7 +50,7 @@ public class ReactiveAuditingEntityCallback implements ReactiveBeforeConvertCall
|
||||
|
||||
@Override
|
||||
public Mono<Object> onBeforeConvert(Object entity, IndexCoordinates index) {
|
||||
return Mono.just(auditingHandlerFactory.getObject().markAudited(entity));
|
||||
return auditingHandlerFactory.getObject().markAudited(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -13,7 +13,9 @@ import org.springframework.data.geo.Point;
|
||||
|
||||
/**
|
||||
* @author Artur Konaczak
|
||||
* @deprecated since 4.1, not used anymore
|
||||
*/
|
||||
@Deprecated
|
||||
public class CustomGeoModule extends SimpleModule {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright 2015-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.geo;
|
||||
|
||||
import org.springframework.data.elasticsearch.core.convert.ConversionException;
|
||||
import org.springframework.data.elasticsearch.core.convert.GeoConverters;
|
||||
import org.springframework.data.elasticsearch.core.document.Document;
|
||||
|
||||
/**
|
||||
* Interface definition for structures defined in <a href="https://geojson.org/>GeoJSON</a> format. copied from Spring
|
||||
* Data Mongodb
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 1.7
|
||||
*/
|
||||
public interface GeoJson<T extends Iterable<?>> {
|
||||
|
||||
/**
|
||||
* String value representing the type of the {@link GeoJson} object.
|
||||
*
|
||||
* @return will never be {@literal null}.
|
||||
* @see <a href=
|
||||
* "https://geojson.org/geojson-spec.html#geojson-objects">https://geojson.org/geojson-spec.html#geojson-objects</a>
|
||||
*/
|
||||
String getType();
|
||||
|
||||
/**
|
||||
* The value of the coordinates member is always an {@link Iterable}. The structure for the elements within is
|
||||
* determined by {@link #getType()} of geometry.
|
||||
*
|
||||
* @return will never be {@literal null}.
|
||||
* @see <a href=
|
||||
* "https://geojson.org/geojson-spec.html#geometry-objects">https://geojson.org/geojson-spec.html#geometry-objects</a>
|
||||
*/
|
||||
T getCoordinates();
|
||||
|
||||
/**
|
||||
* @param json the JSON string to parse
|
||||
* @return the parsed {@link GeoJson} object
|
||||
* @throws ConversionException on parse erros
|
||||
*/
|
||||
static GeoJson<?> of(String json) {
|
||||
return GeoConverters.MapToGeoJsonConverter.INSTANCE.convert(Document.parse(json));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a JSON representation of this object
|
||||
*/
|
||||
default String toJson() {
|
||||
return Document.from(GeoConverters.GeoJsonToMapConverter.INSTANCE.convert(this)).toJson();
|
||||
}
|
||||
}
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright 2015-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.geo;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Defines a {@link GeoJsonGeometryCollection} that consists of a {@link List} of {@link GeoJson} objects.<br/>
|
||||
* Copied from Spring Data Mongodb
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author Peter-Josef Meisch
|
||||
* @since 4.1
|
||||
* @see <a href=
|
||||
* "https://geojson.org/geojson-spec.html#geometry-collection">https://geojson.org/geojson-spec.html#geometry-collection</a>
|
||||
*/
|
||||
public class GeoJsonGeometryCollection implements GeoJson<Iterable<GeoJson<?>>> {
|
||||
|
||||
public static final String TYPE = "GeometryCollection";
|
||||
|
||||
private final List<GeoJson<?>> geometries = new ArrayList<>();
|
||||
|
||||
private GeoJsonGeometryCollection(List<GeoJson<?>> geometries) {
|
||||
this.geometries.addAll(geometries);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link GeoJsonGeometryCollection} for the given {@link GeoJson} instances.
|
||||
*
|
||||
* @param geometries must not be {@literal null}.
|
||||
*/
|
||||
public static GeoJsonGeometryCollection of(List<GeoJson<?>> geometries) {
|
||||
|
||||
Assert.notNull(geometries, "Geometries must not be null!");
|
||||
|
||||
return new GeoJsonGeometryCollection(geometries);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getType() {
|
||||
return TYPE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<GeoJson<?>> getCoordinates() {
|
||||
return getGeometries();
|
||||
}
|
||||
|
||||
public List<GeoJson<?>> getGeometries() {
|
||||
return Collections.unmodifiableList(this.geometries);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o)
|
||||
return true;
|
||||
if (o == null || getClass() != o.getClass())
|
||||
return false;
|
||||
|
||||
GeoJsonGeometryCollection that = (GeoJsonGeometryCollection) o;
|
||||
|
||||
return geometries.equals(that.geometries);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return geometries.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "GeoJsonGeometryCollection{" + "geometries=" + geometries + '}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
* Copyright 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.geo;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.data.geo.Point;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* {@link GeoJsonLineString} is defined as list of {@link Point}s.<br/>
|
||||
* Copied from Spring Data Mongodb
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author Peter-Josef Meisch
|
||||
* @since 4.1
|
||||
* @see <a href="https://geojson.org/geojson-spec.html#multipoint">https://geojson.org/geojson-spec.html#multipoint</a>
|
||||
*/
|
||||
public class GeoJsonLineString implements GeoJson<Iterable<Point>> {
|
||||
|
||||
public static final String TYPE = "LineString";
|
||||
|
||||
private final List<Point> points;
|
||||
|
||||
private GeoJsonLineString(List<Point> points) {
|
||||
this.points = new ArrayList<>(points);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link GeoJsonLineString} for the given {@link Point}s.
|
||||
*
|
||||
* @param points points must not be {@literal null} and have at least 2 entries.
|
||||
*/
|
||||
public static GeoJsonLineString of(List<Point> points) {
|
||||
|
||||
Assert.notNull(points, "Points must not be null.");
|
||||
Assert.isTrue(points.size() >= 2, "Minimum of 2 Points required.");
|
||||
|
||||
return new GeoJsonLineString(points);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link GeoJsonLineString} for the given {@link Point}s.
|
||||
*
|
||||
* @param first must not be {@literal null}.
|
||||
* @param second must not be {@literal null}.
|
||||
* @param others must not be {@literal null}.
|
||||
*/
|
||||
public static GeoJsonLineString of(Point first, Point second, Point... others) {
|
||||
|
||||
Assert.notNull(first, "First point must not be null!");
|
||||
Assert.notNull(second, "Second point must not be null!");
|
||||
Assert.notNull(others, "Additional points must not be null!");
|
||||
|
||||
List<Point> points = new ArrayList<>();
|
||||
points.add(first);
|
||||
points.add(second);
|
||||
points.addAll(Arrays.asList(others));
|
||||
|
||||
return new GeoJsonLineString(points);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link GeoJsonLineString} for the given {@link GeoPoint}s.
|
||||
*
|
||||
* @param geoPoints geoPoints must not be {@literal null} and have at least 2 entries.
|
||||
*/
|
||||
public static GeoJsonLineString ofGeoPoints(List<GeoPoint> geoPoints) {
|
||||
|
||||
Assert.notNull(geoPoints, "Points must not be null.");
|
||||
Assert.isTrue(geoPoints.size() >= 2, "Minimum of 2 Points required.");
|
||||
|
||||
return new GeoJsonLineString(geoPoints.stream().map(GeoPoint::toPoint).collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link GeoJsonLineString} for the given {@link GeoPoint}s.
|
||||
*
|
||||
* @param first must not be {@literal null}.
|
||||
* @param second must not be {@literal null}.
|
||||
* @param others must not be {@literal null}.
|
||||
*/
|
||||
public static GeoJsonLineString of(GeoPoint first, GeoPoint second, GeoPoint... others) {
|
||||
|
||||
Assert.notNull(first, "First point must not be null!");
|
||||
Assert.notNull(second, "Second point must not be null!");
|
||||
Assert.notNull(others, "Additional points must not be null!");
|
||||
|
||||
List<Point> points = new ArrayList<>();
|
||||
points.add(GeoPoint.toPoint(first));
|
||||
points.add(GeoPoint.toPoint(second));
|
||||
points.addAll(Arrays.stream(others).map(GeoPoint::toPoint).collect(Collectors.toList()));
|
||||
|
||||
return new GeoJsonLineString(points);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getType() {
|
||||
return TYPE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Point> getCoordinates() {
|
||||
return Collections.unmodifiableList(this.points);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o)
|
||||
return true;
|
||||
if (o == null || getClass() != o.getClass())
|
||||
return false;
|
||||
|
||||
GeoJsonLineString that = (GeoJsonLineString) o;
|
||||
|
||||
return points.equals(that.points);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return points.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "GeoJsonLineString{" + "points=" + points + '}';
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user