1
0
mirror of synced 2026-07-07 18:50:00 +00:00

Compare commits

..

82 Commits

Author SHA1 Message Date
Mark Paluch 74c8b79801 Release version 5.0 M1 (2022.0.0).
See #2053
2022-01-14 14:35:11 +01:00
Mark Paluch 4f3c17be68 Prepare 5.0 M1 (2022.0.0).
See #2053
2022-01-14 14:34:48 +01:00
Peter-Josef Meisch 59530ce036 fix javadoc 2022-01-11 17:53:15 +01:00
Peter-Josef Meisch b7c983e39e Branch 5.0.x. 2022-01-11 17:53:15 +01:00
Peter-Josef Meisch b8bda372eb Revert breaking changes in package structure.
Original Pull Request #2052 
Closes #2051
2022-01-09 11:15:13 +01:00
Peter-Josef Meisch 09c386508e Adapt copyright end date to 2022.
Original Pull Request #2047
Closes #2046
2022-01-01 18:50:31 +01:00
Peter-Josef Meisch d95f62e4a4 Mappingbuilder uses the typeKey from the ElasticsearchTypeMapper instance.
Original Pull Request  #2044
2021-12-29 08:41:30 +01:00
Peter-Josef Meisch f914d68478 Fix message in BulkFailureException. 2021-12-27 09:14:01 +01:00
Peter-Josef Meisch 25c9e9d470 Adjust documentation. 2021-12-26 13:26:31 +01:00
Peter-Josef Meisch 45e9fd7f5a Add AfterLoad callback.
Original Pull Request #2039
Closes #2009
2021-12-26 13:11:53 +01:00
Peter-Josef Meisch 170648d467 Fix Blockhound setup for Elasticsearch 7.16, dependency version updates.
Original Pull Request #2038
2021-12-25 14:33:11 +01:00
Peter-Josef Meisch 4835df7958 Upgrade to Elasticsearch 7.16.2.
Original Pull Request #2036
Closes #2034
2021-12-19 20:52:24 +01:00
Peter-Josef Meisch ad904e5e94 Update to log4j 2.17.0 2021-12-18 20:33:12 +01:00
Peter-Josef Meisch 3695b23948 Polishing. 2021-12-18 19:14:24 +01:00
Anton Naydenov 9d574103e2 Remove usage of java.lang.reflect in favour of use of the PersistenceEntity.
Original Pull Request #2033 
Closes #2027
2021-12-18 19:03:38 +01:00
Peter-Josef Meisch 32030043a3 Upgrade to Elasticsearch 7.16.1.
Original Pull Request #2032 
Closes #2030
2021-12-16 08:35:28 +01:00
Peter-Josef Meisch 11c6500967 update log4j dependency version 2021-12-14 13:54:23 +01:00
Peter-Josef Meisch f7a6a97c4e Fix FieldType mapping.
Original PullRequest #2026
Closes #2024
2021-12-13 21:32:26 +01:00
Peter-Josef Meisch d6e966e293 Adapt to Elasticsearch 7.16.0.
Original Pull Request #2022 
Closes #2020
2021-12-10 08:58:42 +01:00
Peter-Josef Meisch 6823a18b80 Improve PropertyValueConverters on writing.
Original Pull Request #2019
Closes #2018
2021-12-05 08:41:47 +01:00
Peter-Josef Meisch 8fad48b3f9 Add reactive SearchHits implementation.
Original Pull Request #2017
Closes #2015
2021-11-30 20:47:29 +01:00
Peter-Josef Meisch 989c2807fb Adjust @since annotation of classes moved to new package.
Original Pull Request #2014 
Closes #2010
2021-11-23 20:53:14 +01:00
Sascha Woo 49324a369a Fix IndexOutOfBoundsException when try to map inner hits with no results returned.
Original Pull Request #1998 
Closes #1997
Co-authored-by: Peter-Josef Meisch <pj.meisch@sothawo.com>
2021-11-23 20:08:50 +01:00
Peter-Josef Meisch 44f9b29b66 Polishing. 2021-11-21 21:08:05 +01:00
vdisk-group a22419c418 Add support for stored fields.
Original Pull Request #2005 
Closes #2004
2021-11-21 20:58:17 +01:00
Anton Naydenov 29d21000a9 Removed getting internal field via class method.
Original Pull Request #2003
Closes #2002
2021-11-20 14:41:42 +01:00
Peter-Josef Meisch 19d9830735 Remove old wrong javadoc. 2021-11-19 17:52:09 +01:00
Peter-Josef Meisch 0c7c686cb1 Migrate to Spring JCL logging.
Original Pull Request #2001
Closes #2000
2021-11-17 23:21:15 +01:00
Peter-Josef Meisch 45b4c99e95 Fix RestStatusException cause.
Original Pull Request #1996
Closes #1995
2021-11-16 07:36:41 +01:00
Peter-Josef Meisch 0b4c5b4155 Remove TransportClient.
Original Pull Request #1992 
Closes #1958
2021-11-12 20:33:29 +01:00
Peter-Josef Meisch 35c608b546 fix package name 2021-11-12 19:23:50 +01:00
Peter-Josef Meisch 94c95ee4da Move classes using Elasticsearch code to separate package.
Original Pull Request #1991 
Closes #1947
2021-11-12 17:30:57 +01:00
Jens Schauder 3eff80e745 After release cleanups.
See #1965
2021-11-12 10:59:45 +01:00
Jens Schauder faff216132 Prepare next development iteration.
See #1965
2021-11-12 10:59:42 +01:00
Jens Schauder 08f7ad85ca Release version 4.3 GA (2021.1.0).
See #1965
2021-11-12 10:49:37 +01:00
Jens Schauder 4db496ca4e Prepare 4.3 GA (2021.1.0).
See #1965
2021-11-12 10:49:16 +01:00
Peter-Josef Meisch 9353de13ee Upgrade to Elasticsearch 7.15.2.
Original Pull Request #1985
Closes #1984
2021-11-11 17:08:59 +01:00
Peter-Josef Meisch 5717d6ee57 Documentation for bulk errors.
Original Pull Request #1982 
Closes #1981
2021-11-01 08:57:32 +01:00
Peter-Josef Meisch bbee64c8a4 Upgrade to Elasticsearch 7.15.1.
Original Pull Request #1980 
Closes #1979
2021-10-30 21:27:44 +02:00
Peter-Josef Meisch 5cf38d064f Remove reflective access to java.time classes.
Original Pull Request #1970 
Closes #1969
2021-10-30 17:34:49 +02:00
Anton Naydenov 8894dd3d21 Added RefreshPolicy setter to the AbstractElasticsearchTemplate copy method.
Original Pull Request #1976 
Closes #1978
2021-10-26 19:12:53 +02:00
jongchan lee f1b4a54bc2 Add scrolltime condition when using SearchRequest.
Original Pull Request #1975
Closes #1974
2021-10-26 18:23:29 +02:00
Peter-Josef Meisch c37bbd5a7e Add possibility to define runtime fields in a search request.
Original Pull Request #1972
Closes #1971
2021-10-24 20:01:43 +02:00
Peter-Josef Meisch 2b5ab64724 Revert Testcontainers to 1.15.3.
the update causes issues when running on macOS, the started containers are not reliably reachable
2021-10-24 17:40:35 +02:00
Peter-Josef Meisch 0d943069e3 Upgrade Testcontainers to 1.16.1.
Original Pull Request #1968
Closes #1967
2021-10-19 17:39:28 +02:00
Mark Paluch ceadec0a3c After release cleanups.
See #1937
2021-10-18 13:55:43 +02:00
Mark Paluch 9b2ff56dad Prepare next development iteration.
See #1937
2021-10-18 13:55:39 +02:00
Mark Paluch d221fa8c49 Release version 4.3 RC1 (2021.1.0).
See #1937
2021-10-18 13:48:15 +02:00
Mark Paluch c4fd0acbc6 Prepare 4.3 RC1 (2021.1.0).
See #1937
2021-10-18 13:47:48 +02:00
Peter-Josef Meisch 288705ca72 Support field exclusion from source.
Original Pull Request #1962 
Closes #769
2021-10-16 13:01:14 +02:00
Mark Paluch 59fdbbeb19 Upgrade to Maven Wrapper 3.8.3.
See #1960
2021-10-11 14:30:32 +02:00
Peter-Josef Meisch 4a8e012e04 Don't expose ElasticsearchStatusException.
Original Pull Request #1959 
Closes #1957
2021-10-11 12:38:55 +02:00
Peter-Josef Meisch 2450d579e9 Support different routing for each id in multiget.
Original Pull Request #1956 
Closes #1954
2021-10-09 19:43:16 +02:00
Peter-Josef Meisch 5e4ce56414 Update documentation 2021-10-08 13:44:58 +02:00
Peter-Josef Meisch b8f8a60fd7 Custom Order class with specific parameters for Elasticsearch.
Original Pull Request #1955 
Closes #1911
2021-10-08 13:38:22 +02:00
Peter-Josef Meisch 7ae55b9e75 Add custom property converters.
Original Pull Request #1953 
Closes #1945
2021-10-05 21:31:41 +02:00
Steven 464fc31d87 AbstractElasticsearchTemplate.searchForStream use Query scrolltime.
Original Pull Request #1951 
Closes #1950
2021-10-03 19:08:49 +02:00
Peter-Josef Meisch 175e7b51ae Add repository search for nullable or empty properties.
Original Pull Request #1946 
Closes #1909
2021-09-25 14:51:40 +02:00
Peter-Josef Meisch b8ae9b4a83 switch other config from jdk 16 to 17.
Original Pull Request #1944 
Closes #1942
2021-09-23 23:19:56 +02:00
Peter-Josef Meisch 53581a9a79 Upgrade to Elasticsearch 7.15.0.
Original Pull Request #1943 
Closes #1941
2021-09-23 23:11:11 +02:00
Peter-Josef Meisch d9b23ede70 Remove Elasticsearch classes from suggest response data.
Original Pull Request #1940 
Closes #1302
2021-09-21 21:15:09 +02:00
Steven d1528ed67f Add @QueryAnnotation meta annotation to @Query.
Original Pull Request #1939 
Closes #1938
2021-09-21 19:49:18 +02:00
Mark Paluch c436c4cd63 After release cleanups.
See #1899
2021-09-17 09:52:22 +02:00
Mark Paluch 8907be835f Prepare next development iteration.
See #1899
2021-09-17 09:52:19 +02:00
Mark Paluch 1d291aeeff Release version 4.3 M3 (2021.1.0).
See #1899
2021-09-17 09:44:58 +02:00
Mark Paluch 56b1343ee7 Prepare 4.3 M3 (2021.1.0).
See #1899
2021-09-17 09:44:35 +02:00
Christoph Strobl bf165a663a Change visibility of PersistentEntitiesFactoryBean.
Closes: #1934
2021-09-16 08:08:55 +02:00
Peter-Josef Meisch 35b6efebf3 Update documentation 2021-09-14 21:45:00 +02:00
Peter-Josef Meisch 3128fab595 upgrade to Elasticsearch 7.14.1.
Original Pull Request #1933 
Closes #1892
2021-09-14 21:38:45 +02:00
Peter-Josef Meisch fa66702980 MappingElasticsearchConverter cleanup.
Original Pull Request #1931
Closes #1923
2021-09-11 11:51:05 +02:00
Peter-Josef Meisch 8ab84fcc7a Improve client configuration callbacks.
Original Pull Request #1930 
Closes #1929
2021-09-10 17:20:15 +02:00
Peter-Josef Meisch 7c35e5327e Fix MappingElasticsearchConverter memory leak.
Original Pull Request #1928
Closes #1924
2021-09-10 16:04:40 +02:00
Peter-Josef Meisch 6941e31ba4 Polishing 2021-09-10 08:29:52 +02:00
Nic Hines 3b8f0c9d56 Change mapping of connectionRequestTimeout to ConnPool leaseTimeout.
Original Pull Request: #1925 
Closes: #1926
2021-09-10 08:10:45 +02:00
Peter-Josef Meisch 64624aec70 Encapsulate client specific aggregation return types.
Original Pull Request #1921
Closes #1920
2021-09-04 13:20:01 +02:00
Peter-Josef Meisch e71758686c Fix @Query method implementation for unpaged queries.
Original Pull Request #1919 
Closes #1917
2021-09-03 21:36:57 +02:00
Jade Peng 35c7ef4fed Correct indexName comment.
Original Pull Request #1916
2021-09-01 20:36:08 +02:00
Peter-Josef Meisch 305d930870 Remove org.elasticsearch dependencies from API classes.
Original Pull Request #1913
Closes #1884
Closes #1885
2021-08-30 21:37:04 +02:00
Daniel Franco e688fc70e0 Update maven wrapper version to 3.8.2
Original Pull Request #1905
2021-08-26 20:13:19 +02:00
Peter-Josef Meisch 50f2d83442 Upgrade to Elasticsearch 7.13.4.
Original Pull Request #1900
Closes #1896
2021-08-12 20:38:27 +02:00
Jens Schauder fd3410bb75 After release cleanups.
See #1876
2021-08-12 15:16:23 +02:00
Jens Schauder 325d3f7a09 Prepare next development iteration.
See #1876
2021-08-12 15:16:22 +02:00
506 changed files with 9567 additions and 6995 deletions
+3 -2
View File
@@ -1,2 +1,3 @@
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.1/apache-maven-3.8.1-bin.zip
wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar
#Mon Oct 11 14:30:32 CEST 2021
wrapperUrl=https\://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar
distributionUrl=https\://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.3/apache-maven-3.8.3-bin.zip
Vendored
+9 -64
View File
@@ -12,10 +12,11 @@ pipeline {
}
stages {
stage("test: baseline (jdk8)") {
stage("test: baseline (jdk17)") {
when {
beforeAgent(true)
anyOf {
branch 'main'
branch(pattern: "main|(\\d\\.\\d\\.x)", comparator: "REGEXP")
not { triggeredBy 'UpstreamCause' }
}
}
@@ -32,7 +33,7 @@ pipeline {
steps {
script {
docker.withRegistry('', 'hub.docker.com-springbuildmaster') {
docker.image('adoptopenjdk/openjdk8:latest').inside('-u root -v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker -v $HOME:/tmp/jenkins-home') {
docker.image('openjdk:17-bullseye').inside('-u root -v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker -v $HOME:/tmp/jenkins-home') {
sh "docker login --username ${DOCKER_HUB_USR} --password ${DOCKER_HUB_PSW}"
sh 'PROFILE=none ci/verify.sh'
sh "ci/clean.sh"
@@ -42,68 +43,11 @@ pipeline {
}
}
stage("Test other configurations") {
when {
allOf {
branch 'main'
not { triggeredBy 'UpstreamCause' }
}
}
parallel {
stage("test: baseline (jdk11)") {
agent {
label 'data'
}
options { timeout(time: 30, unit: 'MINUTES') }
environment {
DOCKER_HUB = credentials('hub.docker.com-springbuildmaster')
ARTIFACTORY = credentials('02bd1690-b54f-4c9f-819d-a77cb7a9822c')
}
steps {
script {
docker.withRegistry('', 'hub.docker.com-springbuildmaster') {
docker.image('adoptopenjdk/openjdk11:latest').inside('-u root -v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker -v $HOME:/tmp/jenkins-home') {
sh "docker login --username ${DOCKER_HUB_USR} --password ${DOCKER_HUB_PSW}"
sh 'PROFILE=java11 ci/verify.sh'
sh "ci/clean.sh"
}
}
}
}
}
stage("test: baseline (jdk16)") {
agent {
label 'data'
}
options { timeout(time: 30, unit: 'MINUTES') }
environment {
DOCKER_HUB = credentials('hub.docker.com-springbuildmaster')
ARTIFACTORY = credentials('02bd1690-b54f-4c9f-819d-a77cb7a9822c')
}
steps {
script {
docker.withRegistry('', 'hub.docker.com-springbuildmaster') {
docker.image('adoptopenjdk/openjdk16:latest').inside('-u root -v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker -v $HOME:/tmp/jenkins-home') {
sh "docker login --username ${DOCKER_HUB_USR} --password ${DOCKER_HUB_PSW}"
sh 'PROFILE=java11 ci/verify.sh'
sh "ci/clean.sh"
}
}
}
}
}
}
}
stage('Release to artifactory') {
when {
beforeAgent(true)
anyOf {
branch 'main'
branch(pattern: "main|(\\d\\.\\d\\.x)", comparator: "REGEXP")
not { triggeredBy 'UpstreamCause' }
}
}
@@ -119,7 +63,7 @@ pipeline {
steps {
script {
docker.withRegistry('', 'hub.docker.com-springbuildmaster') {
docker.image('adoptopenjdk/openjdk8:latest').inside('-v $HOME:/tmp/jenkins-home') {
docker.image('openjdk:17-bullseye').inside('-v $HOME:/tmp/jenkins-home') {
sh 'MAVEN_OPTS="-Duser.name=jenkins -Duser.home=/tmp/jenkins-home" ./mvnw -s settings.xml -Pci,artifactory -Dmaven.repo.local=/tmp/jenkins-home/.m2/spring-data-elasticsearch-non-root ' +
'-Dartifactory.server=https://repo.spring.io ' +
"-Dartifactory.username=${ARTIFACTORY_USR} " +
@@ -133,6 +77,7 @@ pipeline {
}
}
}
stage('Publish documentation') {
when {
branch 'main'
@@ -149,7 +94,7 @@ pipeline {
steps {
script {
docker.withRegistry('', 'hub.docker.com-springbuildmaster') {
docker.image('adoptopenjdk/openjdk8:latest').inside('-v $HOME:/tmp/jenkins-home') {
docker.image('openjdk:17-bullseye').inside('-v $HOME:/tmp/jenkins-home') {
sh 'MAVEN_OPTS="-Duser.name=jenkins -Duser.home=/tmp/jenkins-home" ./mvnw -s settings.xml -Pci,distribute -Dmaven.repo.local=/tmp/jenkins-home/.m2/spring-data-elasticsearch-non-root ' +
'-Dartifactory.server=https://repo.spring.io ' +
"-Dartifactory.username=${ARTIFACTORY_USR} " +
+15 -36
View File
@@ -4,14 +4,16 @@ image:https://spring.io/badges/spring-data-elasticsearch/ga.svg[Spring Data Elas
The primary goal of the https://projects.spring.io/spring-data[Spring Data] project is to make it easier to build Spring-powered applications that use new data access technologies such as non-relational databases, map-reduce frameworks, and cloud based data services.
The Spring Data Elasticsearch project provides integration with the https://www.elastic.co/[Elasticsearch] search engine. Key functional areas of Spring Data Elasticsearch are a POJO centric model for interacting with a Elasticsearch Documents and easily writing a Repository style data access layer.
The Spring Data Elasticsearch project provides integration with the https://www.elastic.co/[Elasticsearch] search engine.
Key functional areas of Spring Data Elasticsearch are a POJO centric model for interacting with a Elasticsearch Documents and easily writing a Repository style data access layer.
This project is lead and maintained by the community.
== Features
* Spring configuration support using Java based `@Configuration` classes or an XML namespace for a ES clients instances.
* `ElasticsearchRestTemplate` helper class that increases productivity performing common ES operations. Includes integrated object mapping between documents and POJOs.
* `ElasticsearchRestTemplate` helper class that increases productivity performing common ES operations.
Includes integrated object mapping between documents and POJOs.
* Feature Rich Object Mapping integrated with Springs Conversion Service
* Annotation based mapping metadata
* Automatic implementation of `Repository` interfaces including support for custom search methods.
@@ -19,7 +21,9 @@ This project is lead and maintained by the community.
== Code of Conduct
This project is governed by the https://github.com/spring-projects/.github/blob/e3cc2ff230d8f1dca06535aa6b5a4a23815861d4/CODE_OF_CONDUCT.md[Spring Code of Conduct]. By participating, you are expected to uphold this code of conduct. Please report unacceptable behavior to spring-code-of-conduct@pivotal.io.
This project is governed by the 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
@@ -58,31 +62,6 @@ public class MyService {
}
----
=== Using Transport Client
NOTE: Usage of the TransportClient is deprecated as of version 4.0, use RestClient instead.
[source,java]
----
@Configuration
public class TransportClientConfig extends ElasticsearchConfigurationSupport {
@Bean
public Client elasticsearchClient() throws UnknownHostException {
Settings settings = Settings.builder().put("cluster.name", "elasticsearch").build();
TransportClient client = new PreBuiltTransportClient(settings);
client.addTransportAddress(new TransportAddress(InetAddress.getByName("127.0.0.1"), 9300));
return client;
}
@Bean(name = { "elasticsearchOperations", "elasticsearchTemplate" })
public ElasticsearchTemplate elasticsearchTemplate() throws UnknownHostException {
return new ElasticsearchTemplate(elasticsearchClient());
}
}
----
=== Using the RestClient
Provide a configuration like this:
@@ -161,7 +140,8 @@ If you'd rather like the latest snapshots of the upcoming major version, use our
== Getting Help
Having trouble with Spring Data? Wed love to help!
Having trouble with Spring Data?
Wed love to help!
* Check the
https://docs.spring.io/spring-data/elasticsearch/docs/current/reference/html/[reference documentation], and https://docs.spring.io/spring-data/elasticsearch/docs/current/api/[Javadocs].
@@ -174,14 +154,16 @@ You can also chat with the community on https://gitter.im/spring-projects/spring
== Reporting Issues
Spring Data uses GitHub as issue tracking system to record bugs and feature requests. If you want to raise an issue, please follow the recommendations below:
Spring Data uses GitHub as issue tracking system to record bugs and feature requests.
If you want to raise an issue, please follow the recommendations below:
* Before you log a bug, please search the
https://github.com/spring-projects/spring-data-elasticsearch/issues[issue tracker] to see if someone has already reported the problem.
* If the issue doesnt already exist, https://github.com/spring-projects/spring-data-elasticsearch/issues/new[create a new issue].
* Please provide as much information as possible with the issue report, we like to know the version of Spring Data Elasticsearch that you are using and JVM version.
* If you need to paste code, or include a stack trace use Markdown +++```+++ escapes before and after your text.
* If possible try to create a test-case or project that replicates the issue. Attach a link to your code or a compressed file containing your code.
* If possible try to create a test-case or project that replicates the issue.
Attach a link to your code or a compressed file containing your code.
== Building from Source
@@ -197,11 +179,8 @@ 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[Contributors Agreement] before submitting your first pull request._
IMPORTANT: When contributing, please make sure an issue exists in https://github.com/spring-projects/spring-data-elasticsearch/issues[issue tracker] 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.
IMPORTANT: When contributing, please make sure an issue exists in https://github.com/spring-projects/spring-data-elasticsearch/issues[issue tracker] 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
+35 -56
View File
@@ -5,12 +5,12 @@
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-elasticsearch</artifactId>
<version>4.3.0-M2</version>
<version>5.0.0-M1</version>
<parent>
<groupId>org.springframework.data.build</groupId>
<artifactId>spring-data-parent</artifactId>
<version>2.6.0-M2</version>
<version>3.0.0-M1</version>
</parent>
<name>Spring Data Elasticsearch</name>
@@ -18,11 +18,11 @@
<url>https://github.com/spring-projects/spring-data-elasticsearch</url>
<properties>
<elasticsearch>7.13.3</elasticsearch>
<log4j>2.14.1</log4j>
<elasticsearch>7.16.2</elasticsearch>
<log4j>2.17.0</log4j>
<netty>4.1.65.Final</netty>
<springdata.commons>2.6.0-M2</springdata.commons>
<testcontainers>1.15.3</testcontainers>
<springdata.commons>3.0.0-M1</springdata.commons>
<testcontainers>1.16.2</testcontainers>
<blockhound-junit>1.0.6.RELEASE</blockhound-junit>
<java-module-name>spring.data.elasticsearch</java-module-name>
@@ -101,12 +101,6 @@
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
@@ -141,19 +135,6 @@
</dependency>
<!-- Elasticsearch -->
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>transport</artifactId>
<version>${elasticsearch}</version>
</dependency>
<dependency>
<!-- required by elasticsearch -->
<groupId>org.elasticsearch.plugin</groupId>
<artifactId>transport-netty4-client</artifactId>
<version>${elasticsearch}</version>
</dependency>
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
@@ -166,21 +147,6 @@
</exclusions>
</dependency>
<!-- Logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>log4j-over-slf4j</artifactId>
<version>${slf4j}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>${log4j}</version>
<scope>test</scope>
</dependency>
<!-- Jackson JSON Mapper -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
@@ -196,7 +162,7 @@
<dependency>
<groupId>org.apache.geronimo.specs</groupId>
<artifactId>geronimo-jcdi_2.0_spec</artifactId>
<version>1.0.1</version>
<version>1.3</version>
<scope>test</scope>
</dependency>
@@ -208,23 +174,40 @@
</dependency>
<dependency>
<groupId>javax.enterprise</groupId>
<artifactId>cdi-api</artifactId>
<groupId>jakarta.enterprise</groupId>
<artifactId>jakarta.enterprise.cdi-api</artifactId>
<version>${cdi}</version>
<scope>provided</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>${javax-annotation-api}</version>
<groupId>jakarta.annotation</groupId>
<artifactId>jakarta.annotation-api</artifactId>
<version>${jakarta-annotation-api}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.openwebbeans</groupId>
<artifactId>openwebbeans-se</artifactId>
<classifier>jakarta</classifier>
<version>${webbeans}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.openwebbeans</groupId>
<artifactId>openwebbeans-spi</artifactId>
<classifier>jakarta</classifier>
<version>${webbeans}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.openwebbeans</groupId>
<artifactId>openwebbeans-impl</artifactId>
<classifier>jakarta</classifier>
<version>${webbeans}</version>
<scope>test</scope>
</dependency>
@@ -242,13 +225,6 @@
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-to-slf4j</artifactId>
<version>${log4j}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.projectreactor.tools</groupId>
<artifactId>blockhound-junit-platform</artifactId>
@@ -269,6 +245,7 @@
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<!--suppress MavenPackageUpdate -->
<version>999999</version>
<scope>test</scope>
</dependency>
@@ -283,7 +260,7 @@
<dependency>
<groupId>com.github.tomakehurst</groupId>
<artifactId>wiremock-jre8</artifactId>
<version>2.26.3</version>
<version>2.32.0</version>
<scope>test</scope>
<exclusions>
<!-- these exclusions are needed because of Elasticsearch JarHell-->
@@ -301,7 +278,7 @@
<dependency>
<groupId>io.specto</groupId>
<artifactId>hoverfly-java-junit5</artifactId>
<version>0.13.1</version>
<version>0.14.1</version>
<scope>test</scope>
</dependency>
@@ -461,7 +438,9 @@
</module>
</checkstyleRules>
<includes>**/*</includes>
<excludes>.git/**/*,target/**/*,**/target/**/*,.idea/**/*,**/spring.schemas,**/*.svg,mvnw,mvnw.cmd,**/*.policy</excludes>
<excludes>
.git/**/*,target/**/*,**/target/**/*,.idea/**/*,**/spring.schemas,**/*.svg,mvnw,mvnw.cmd,**/*.policy
</excludes>
<sourceDirectories>./</sourceDirectories>
</configuration>
</plugin>
+6 -5
View File
@@ -34,12 +34,13 @@ The following table shows the Elasticsearch versions that are used by Spring Dat
[cols="^,^,^,^,^",options="header"]
|===
| Spring Data Release Train | Spring Data Elasticsearch | Elasticsearch | Spring Framework | Spring Boot
| 2021.1 (Q)footnote:cdv[Currently in development] | 4.3.xfootnote:cdv[] | 7.13.3 | 5.3.xfootnote:cdv[] | 2.5.xfootnote:cdv[]
| 2022.0 (Raj) | 4.4.x | 7.16.2 | 5.3.x | 2.7.x
| 2021.1 (Q) | 4.3.x | 7.15.2 | 5.3.x | 2.6.x
| 2021.0 (Pascal) | 4.2.x | 7.12.0 | 5.3.x | 2.5.x
| 2020.0 (Ockham) | 4.1.x | 7.9.3 | 5.3.2 | 2.4.x
| Neumann | 4.0.x | 7.6.2 | 5.2.12 |2.3.x
| Moore | 3.2.x |6.8.12 | 5.2.12| 2.2.x
| Lovelacefootnote:oom[Out of maintenance] | 3.1.xfootnote:oom[] | 6.2.2 | 5.1.19 |2.1.x
| 2020.0 (Ockham)footnote:oom[Out of maintenance] | 4.1.xfootnote:oom[] | 7.9.3 | 5.3.2 | 2.4.x
| Neumannfootnote:oom[] | 4.0.xfootnote:oom[] | 7.6.2 | 5.2.12 |2.3.x
| Moorefootnote:oom[] | 3.2.xfootnote:oom[] |6.8.12 | 5.2.12| 2.2.x
| Lovelacefootnote:oom[] | 3.1.xfootnote:oom[] | 6.2.2 | 5.1.19 |2.1.x
| Kayfootnote:oom[] | 3.0.xfootnote:oom[] | 5.5.0 | 5.0.13 | 2.0.x
| Ingallsfootnote:oom[] | 2.1.xfootnote:oom[] | 2.4.0 | 4.3.25 | 1.5.x
|===
@@ -3,60 +3,13 @@
This chapter illustrates configuration and usage of supported Elasticsearch client implementations.
Spring Data Elasticsearch operates upon an Elasticsearch client that is connected to a single Elasticsearch node or a cluster. Although the Elasticsearch Client can be used to work with the cluster, applications using Spring Data Elasticsearch normally use the higher level abstractions of <<elasticsearch.operations>> and <<elasticsearch.repositories>>.
[[elasticsearch.clients.transport]]
== Transport Client
WARNING: The `TransportClient` is deprecated as of Elasticsearch 7 and will be removed in Elasticsearch 8. (https://www.elastic.co/guide/en/elasticsearch/client/java-api/current/transport-client.html[see the Elasticsearch documentation]). Spring Data Elasticsearch will support the `TransportClient` as long as it is available in the used
Elasticsearch <<elasticsearch.versions,version>> but has deprecated the classes using it since version 4.0.
We strongly recommend to use the <<elasticsearch.clients.rest>> instead of the `TransportClient`.
.Transport Client
====
[source,java]
----
@Configuration
public class TransportClientConfig extends ElasticsearchConfigurationSupport {
@Bean
public Client elasticsearchClient() throws UnknownHostException {
Settings settings = Settings.builder().put("cluster.name", "elasticsearch").build(); <.>
TransportClient client = new PreBuiltTransportClient(settings);
client.addTransportAddress(new TransportAddress(InetAddress.getByName("127.0.0.1"), 9300)); <.>
return client;
}
@Bean(name = { "elasticsearchOperations", "elasticsearchTemplate" })
public ElasticsearchTemplate elasticsearchTemplate() throws UnknownHostException {
ElasticsearchTemplate template = new ElasticsearchTemplate(elasticsearchClient, elasticsearchConverter);
template.setRefreshPolicy(refreshPolicy()); <.>
return template;
}
}
// ...
IndexRequest request = new IndexRequest("spring-data")
.id(randomID())
.source(someObject);
IndexResponse response = client.index(request);
----
<.> The `TransportClient` must be configured with the cluster name.
<.> The host and port to connect the client to.
<.> the RefreshPolicy must be set in the `ElasticsearchTemplate` (override `refreshPolicy()` to not use the default)
====
Spring Data Elasticsearch operates upon an Elasticsearch client that is connected to a single Elasticsearch node or a cluster.
Although the Elasticsearch Client can be used to work with the cluster, applications using Spring Data Elasticsearch normally use the higher level abstractions of <<elasticsearch.operations>> and <<elasticsearch.repositories>>.
[[elasticsearch.clients.rest]]
== High Level REST Client
The Java High Level REST Client is the default client of Elasticsearch, it provides a straight forward replacement for the `TransportClient` as it accepts and returns
the very same request/response objects and therefore depends on the Elasticsearch core project.
Asynchronous calls are operated upon a client managed thread pool and require a callback to be notified when the request is done.
The Java High Level REST Client is the default client of Elasticsearch, it is configured like shown:
.High Level REST Client
====
@@ -93,6 +46,7 @@ IndexRequest request = new IndexRequest("spring-data")
IndexResponse response = highLevelClient.index(request,RequestOptions.DEFAULT);
----
<1> Use the builder to provide cluster addresses, set default `HttpHeaders` or enable SSL.
<2> Create the RestHighLevelClient.
<3> It is also possible to obtain the `lowLevelRest()` client.
@@ -131,6 +85,7 @@ Mono<IndexResponse> response = client.index(request ->
.source(singletonMap("feature", "reactive-client"));
);
----
<.> Use the builder to provide cluster addresses, set default `HttpHeaders` or enable SSL.
====
@@ -162,25 +117,30 @@ ClientConfiguration clientConfiguration = ClientConfiguration.builder()
headers.add("currentTime", LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
return headers;
})
.withWebClientConfigurer(webClient -> { <.>
//...
return webClient;
})
.withHttpClientConfigurer(clientBuilder -> { <.>
//...
.withClientConfigurer( <.>
ReactiveRestClients.WebClientConfigurationCallback.from(webClient -> {
// ...
return webClient;
}))
.withClientConfigurer( <.>
RestClients.RestClientConfigurationCallback.from(clientBuilder -> {
// ...
return clientBuilder;
})
}))
. // ... other options
.build();
----
<.> Define default headers, if they need to be customized
<.> Use the builder to provide cluster addresses, set default `HttpHeaders` or enable SSL.
<.> Optionally enable SSL.
<.> Optionally set a proxy.
<.> Optionally set a path prefix, mostly used when different clusters a behind some reverse proxy.
<.> Set the connection timeout. Default is 10 sec.
<.> Set the socket timeout. Default is 5 sec.
<.> Set the connection timeout.
Default is 10 sec.
<.> Set the socket timeout.
Default is 5 sec.
<.> Optionally set headers.
<.> Add basic authentication.
<.> A `Supplier<Header>` function can be specified which is called every time before a request is sent to Elasticsearch - here, as an example, the current time is written in a header.
@@ -188,13 +148,13 @@ ClientConfiguration clientConfiguration = ClientConfiguration.builder()
<.> for non-reactive setup a function configuring the REST client
====
IMPORTANT: Adding a Header supplier as shown in above example allows to inject headers that may change over the time, like authentication JWT tokens. If this is used in the reactive setup, the supplier function *must not* block!
IMPORTANT: Adding a Header supplier as shown in above example allows to inject headers that may change over the time, like authentication JWT tokens.
If this is used in the reactive setup, the supplier function *must not* block!
[[elasticsearch.clients.logging]]
== Client Logging
To see what is actually sent to and received from the server `Request` / `Response` logging on the transport level needs
to be turned on as outlined in the snippet below.
To see what is actually sent to and received from the server `Request` / `Response` logging on the transport level needs to be turned on as outlined in the snippet below.
.Enable transport layer logging
[source,xml]
@@ -202,4 +162,4 @@ to be turned on as outlined in the snippet below.
<logger name="org.springframework.data.elasticsearch.client.WIRE" level="trace"/>
----
NOTE: The above applies to both the `RestHighLevelClient` and `ReactiveElasticsearchClient` when obtained via `RestClients` respectively `ReactiveRestClients`, is not available for the `TransportClient`.
NOTE: The above applies to both the `RestHighLevelClient` and `ReactiveElasticsearchClient` when obtained via `RestClients` respectively `ReactiveRestClients`.
@@ -13,7 +13,13 @@ Spring Data Elasticsearch uses the `EntityCallback` API internally for its audit
| Reactive/BeforeConvertCallback
| `onBeforeConvert(T entity, IndexCoordinates index)`
| Invoked before a domain object is converted to `org.springframework.data.elasticsearch.core.document.Document`. Can return the `entity` or a modified entity which then will be converted.
| Invoked before a domain object is converted to `org.springframework.data.elasticsearch.core.document.Document`.
Can return the `entity` or a modified entity which then will be converted.
| `Ordered.LOWEST_PRECEDENCE`
| Reactive/AfterLoadCallback
| `onAfterLoad(Document document, Class<T> type, IndexCoordinates indexCoordinates)`
| Invoked after the result from Elasticsearch has been read into a `org.springframework.data.elasticsearch.core.document.Document`.
| `Ordered.LOWEST_PRECEDENCE`
| Reactive/AfterConvertCallback
@@ -32,4 +38,3 @@ Spring Data Elasticsearch uses the `EntityCallback` API internally for its audit
| `Ordered.LOWEST_PRECEDENCE`
|===
@@ -3,12 +3,77 @@
This section describes breaking changes from version 4.2.x to 4.3.x and how removed features can be replaced by new introduced features.
[NOTE]
====
Elasticsearch is working on a new Client that will replace the `RestHighLevelClient` because the `RestHighLevelClient` uses code from Elasticsearch core libraries which are not Apache 2 licensed anymore.
Spring Data Elasticsearch is preparing for this change as well.
This means that internally the implementations for the `*Operations` interfaces need to change - which should be no problem if users program against the interfaces like `ElasticsearchOperations` or `ReactiveElasticsearchOperations`.
If you are using the implementation classes like `ElasticsearchRestTemplate` directly, you will need to adapt to these changes.
Spring Data Elasticsearch also removes or replaces the use of classes from the `org.elasticsearch` packages in it's API classes and methods, only using them in the implementation where the access to Elasticsearch is implemented.
For the user that means, that some enum classes that were used are replaced by enums that live in `org.springframework.data.elasticsearch` with the same values, these are internally mapped onto the Elasticsearch ones.
Places where classes are used that cannot easily be replaced, this usage is marked as deprecated, we are working on replacements.
Check the sections on <<elasticsearch-migration-guide-4.2-4.3.deprecations>> and <<elasticsearch-migration-guide-4.2-4.3.breaking-changes>> for further details.
====
[[elasticsearch-migration-guide-4.2-4.3.deprecations]]
== Deprecations
=== suggest methods
In `SearchOperations`, and so in `ElasticsearchOperations` as well, the `suggest` methods taking a `org.elasticsearch.search.suggest.SuggestBuilder` as argument and returning a `org.elasticsearch.action.search.SearchResponse` have been deprecated.
Use `SearchHits<T> search(Query query, Class<T> clazz)` instead, passing in a `NativeSearchQuery` which can contain a `SuggestBuilder` and read the suggest results from the returned `SearchHit<T>`.
In `ReactiveSearchOperations` the new `suggest` methods return a `Mono<org.springframework.data.elasticsearch.core.suggest.response.Suggest>` now.
Here as well the old methods are deprecated.
[[elasticsearch-migration-guide-4.2-4.3.breaking-changes]]
== Breaking Changes
=== Removal of `org.elasticsearch` classes from the API.
* In the `org.springframework.data.elasticsearch.annotations.CompletionContext` annotation the property `type()` has changed from `org.elasticsearch.search.suggest.completion.context.ContextMapping.Type` to `org.springframework.data.elasticsearch.annotations.CompletionContext.ContextMappingType`, the available enum values are the same.
* In the `org.springframework.data.elasticsearch.annotations.Document` annotation the `versionType()` property has changed to `org.springframework.data.elasticsearch.annotations.Document.VersionType`, the available enum values are the same.
* In the `org.springframework.data.elasticsearch.core.query.Query` interface the `searchType()` property has changed to `org.springframework.data.elasticsearch.core.query.Query.SearchType`, the available enum values are the same.
* In the `org.springframework.data.elasticsearch.core.query.Query` interface the return value of `timeout()` was changed to `java.time.Duration`.
* The `SearchHits<T>`class does not contain the `org.elasticsearch.search.aggregations.Aggregations` anymore.
Instead it now contains an instance of the `org.springframework.data.elasticsearch.core.AggregationsContainer<T>` class where `T` is the concrete aggregations type from the underlying client that is used.
Currently this will be a `org
.springframework.data.elasticsearch.core.clients.elasticsearch7.ElasticsearchAggregations` object; later different implementations will be available.
The same change has been done to the `ReactiveSearchOperations.aggregate()` functions, the now return a `Flux<AggregationContainer<?>>`.
Programs using the aggregations need to be changed to cast the returned value to the appropriate class to further proces it.
* methods that might have thrown a `org.elasticsearch.ElasticsearchStatusException` now will throw `org.springframework.data.elasticsearch.RestStatusException` instead.
=== Handling of field and sourceFilter properties of Query
Up to version 4.2 the `fields` property of a `Query` was interpreted and added to the include list of the `sourceFilter`. This was not correct, as these are different things for Elasticsearch. This has been corrected. As a consequence code might not work anymore that relies on using `fields` to specify which fields should be returned from the document's `_source' and should be changed to use the `sourceFilter`.
Up to version 4.2 the `fields` property of a `Query` was interpreted and added to the include list of the `sourceFilter`.
This was not correct, as these are different things for Elasticsearch.
This has been corrected.
As a consequence code might not work anymore that relies on using `fields` to specify which fields should be returned from the document's `_source' and should be changed to use the `sourceFilter`.
=== search_type default value
The default value for the `search_type` in Elasticsearch is `query_then_fetch`.
This now is also set as default value in the `Query` implementations, it was previously set to `dfs_query_then_fetch`.
=== BulkOptions changes
Some properties of the `org.springframework.data.elasticsearch.core.query.BulkOptions` class have changed their type:
* the type of the `timeout` property has been changed to `java.time.Duration`.
* the type of the`refreshPolicy` property has been changed to `org.springframework.data.elasticsearch.core.RefreshPolicy`.
=== IndicesOptions change
Spring Data Elasticsearch now uses `org.springframework.data.elasticsearch.core.query.IndicesOptions` instead of `org.elasticsearch.action.support.IndicesOptions`.
=== Completion classes
The classes from the package `org.springframework.data.elasticsearch.core.completion` have been moved to `org.springframework.data.elasticsearch.core.suggest`.
=== Other renamings
The `org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentPropertyConverter` interface has been renamed to `org.springframework.data.elasticsearch.core.mapping.PropertyValueConverter`.
Likewise the implementations classes named _XXPersistentPropertyConverter_ have been renamed to _XXPropertyValueConverter_.
@@ -0,0 +1,19 @@
[[elasticsearch-migration-guide-4.3-4.4]]
= Upgrading from 4.3.x to 4.4.x
This section describes breaking changes from version 4.3.x to 4.4.x and how removed features can be replaced by new introduced features.
[[elasticsearch-migration-guide-4.3-4.4.deprecations]]
== Deprecations
[[elasticsearch-migration-guide-4.3-4.4.breaking-changes]]
== Breaking Changes
=== Removal of deprecated classes
==== `ElasticsearchTemplate` has been removed
As of version 4.4 Spring Data Elasticsearch does not use the `TransportClient` from Elasticsearch anymore (which itself is deprecated since Elasticsearch 7.0).
This means that the `ElasticsearchTemplate` class which was deprecated since Spring Data Elasticsearch 4.0 has been removed.
This was the implementation of the `ElasticsearchOperations` interface that was using the `TransportClient`.
Connections to Elasticsearch must be made using either the imperative `ElasticsearchRestTemplate` or the reactive `ReactiveElasticsearchTemplate`.
@@ -7,7 +7,8 @@ It is recommended to add those operations as custom implementation as described
[[elasticsearc.misc.index.settings]]
== Index settings
When creating Elasticsearch indices with Spring Data Elasticsearch different index settings can be defined by using the `@Setting` annotation. The following arguments are available:
When creating Elasticsearch indices with Spring Data Elasticsearch different index settings can be defined by using the `@Setting` annotation.
The following arguments are available:
* `useServerConfiguration` does not send any settings parameters, so the Elasticsearch server configuration determines them.
* `settingPath` refers to a JSON file defining the settings that must be resolvable in the classpath
@@ -42,6 +43,7 @@ class Entity {
// getter and setter...
}
----
<.> when defining sort fields, use the name of the Java property (_firstField_), not the name that might be defined for Elasticsearch (_first_field_)
<.> `sortModes`, `sortOrders` and `sortMissingValues` are optional, but if they are set, the number of entries must match the number of `sortFields` elements
====
@@ -49,13 +51,16 @@ class Entity {
[[elasticsearch.misc.mappings]]
== Index Mapping
When Spring Data Elasticsearch creates the index mapping with the `IndexOperations.createMapping()` methods, it uses the annotations described in <<elasticsearch.mapping.meta-model.annotations>>, especially the `@Field` annotation. In addition to that it is possible to add the `@Mapping` annotation to a class. This annotation has the following properties:
When Spring Data Elasticsearch creates the index mapping with the `IndexOperations.createMapping()` methods, it uses the annotations described in <<elasticsearch.mapping.meta-model.annotations>>, especially the `@Field` annotation.
In addition to that it is possible to add the `@Mapping` annotation to a class.
This annotation has the following properties:
* `mappingPath` a classpath resource in JSON format; if this is not empty it is used as the mapping, no other mapping processing is done.
* `enabled` when set to false, this flag is written to the mapping and no further processing is done.
* `dateDetection` and `numericDetection` set the corresponding properties in the mapping when not set to `DEFAULT`.
* `dynamicDateFormats` when this String array is not empty, it defines the date formats used for automatic date detection.
* `runtimeFieldsPath` a classpath resource in JSON format containing the definition of runtime fields which is written to the index mappings, for example:
====
[source,json]
----
@@ -165,7 +170,10 @@ interface SampleEntityRepository extends Repository<SampleEntity, String> {
[[elasticsearch.misc.sorts]]
== Sort options
In addition to the default sort options described <<repositories.paging-and-sorting>> Spring Data Elasticsearch has a `GeoDistanceOrder` class which can be used to have the result of a search operation ordered by geographical distance.
In addition to the default sort options described in <<repositories.paging-and-sorting>>, Spring Data Elasticsearch provides the class `org.springframework.data.elasticsearch.core.query.Order` which derives from `org.springframework.data.domain.Sort.Order`.
It offers additional parameters that can be sent to Elasticsearch when specifying the sorting of the result (see https://www.elastic.co/guide/en/elasticsearch/reference/7.15/sort-search-results.html).
There also is the `org.springframework.data.elasticsearch.core.query.GeoDistanceOrder` class which can be used to have the result of a search operation ordered by geographical distance.
If the class to be retrieved has a `GeoPoint` property named _location_, the following `Sort` would sort the results by distance to the given point:
@@ -175,3 +183,81 @@ 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.runtime-fields]]
== Runtime Fields
From version 7.12 on Elasticsearch has added the feature of runtime fields (https://www.elastic.co/guide/en/elasticsearch/reference/7.12/runtime.html).
Spring Data Elasticsearch supports this in two ways:
=== Runtime field definitions in the index mappings
The first way to define runtime fields is by adding the definitions to the index mappings (see https://www.elastic.co/guide/en/elasticsearch/reference/7.12/runtime-mapping-fields.html).
To use this approach in Spring Data Elasticsearch the user must provide a JSON file that contains the corresponding definition, for example:
.runtime-fields.json
====
[source,json]
----
{
"day_of_week": {
"type": "keyword",
"script": {
"source": "emit(doc['@timestamp'].value.dayOfWeekEnum.getDisplayName(TextStyle.FULL, Locale.ROOT))"
}
}
}
----
====
The path to this JSON file, which must be present on the classpath, must then be set in the `@Mapping` annotation of the entity:
====
[source,java]
----
@Document(indexName = "runtime-fields")
@Mapping(runtimeFieldsPath = "/runtime-fields.json")
public class RuntimeFieldEntity {
// properties, getter, setter,...
}
----
====
=== Runtime fields definitions set on a Query
The second way to define runtime fields is by adding the definitions to a search query (see https://www.elastic.co/guide/en/elasticsearch/reference/7.12/runtime-search-request.html).
The following code example shows how to do this with Spring Data Elasticsearch :
The entity used is a simple object that has a `price` property:
====
[source,java]
----
@Document(indexName = "some_index_name")
public class SomethingToBuy {
private @Id @Nullable String id;
@Nullable @Field(type = FieldType.Text) private String description;
@Nullable @Field(type = FieldType.Double) private Double price;
// getter and setter
}
----
====
The following query uses a runtime field that calculates a `priceWithTax` value by adding 19% to the price and uses this value in the search query to find all entities where `priceWithTax` is higher or equal than a given value:
====
[source,java]
----
RuntimeField runtimeField = new RuntimeField("priceWithTax", "double", "emit(doc['price'].value * 1.19)");
Query query = new CriteriaQuery(new Criteria("priceWithTax").greaterThanEqual(16.5));
query.addRuntimeField(runtimeField);
SearchHits<SomethingToBuy> searchHits = operations.search(query, SomethingToBuy.class);
----
====
This works with every implementation of the `Query` interface.
@@ -1,6 +1,21 @@
[[new-features]]
= What's new
[[new-features.4-4-0]]
== New in Spring Data Elasticsearch 4.4
* Upgrade to Elasticsearch 7.16.2.
[[new-features.4-3-0]]
== New in Spring Data Elasticsearch 4.3
* Upgrade to Elasticsearch 7.15.2.
* Allow runtime_fields to be defined in the index mapping.
* Add native support for range field types by using a range object.
* Add repository search for nullable or empty properties.
* Enable custom converters for single fields.
* Supply a custom `Sort.Order` providing Elasticsearch specific parameters.
[[new-features.4-2-0]]
== New in Spring Data Elasticsearch 4.2
@@ -56,6 +56,8 @@ This means, that no mapping entry is written for the property and that Elasticse
** `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.
* `@ValueConverter` defines a class to be used to convert the given property.
In difference to a registered Spring `Converter` this only converts the annotated property and not every property of the given type.
The mapping metadata infrastructure is defined in a separate spring-data-commons project that is technology agnostic.
@@ -184,6 +186,7 @@ public class Person { <1>
"lastname" : "Connor"
}
----
<1> By default the domain types class name is used for the type hint.
====
@@ -211,6 +214,7 @@ public class Person {
"id" : ...
}
----
<1> The configured alias is used when writing the entity.
====
@@ -421,6 +425,7 @@ public class Config extends AbstractElasticsearchConfiguration {
"localidad" : { "lat" : 34.118347, "lon" : -118.3026284 }
}
----
<1> Add `Converter` implementations.
<2> Set up the `Converter` used for writing `DomainType` to Elasticsearch.
<3> Set up the `Converter` used for reading `DomainType` from search result.
@@ -20,49 +20,16 @@ The default implementations of the interfaces offer:
[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.
Details of the index that will be created can be set by using the `@Setting` annotation, refer to <<elasticsearc.misc.index.settings>> for further information.
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. Details of the index that will be created
can be set by using the `@Setting` annotation, refer to <<elasticsearc.misc.index.settings>> for further information.
**None of these operations are done automatically** by the implementations of `IndexOperations` or `ElasticsearchOperations`. It is the user's responsibility to call the methods.
**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
NOTE: Usage of the ElasticsearchTemplate is deprecated as of version 4.0, use ElasticsearchRestTemplate instead.
The `ElasticsearchTemplate` is an implementation of the `ElasticsearchOperations` interface using the <<elasticsearch.clients.transport>>.
.ElasticsearchTemplate configuration
====
[source,java]
----
@Configuration
public class TransportClientConfig extends ElasticsearchConfigurationSupport {
@Bean
public Client elasticsearchClient() throws UnknownHostException { <1>
Settings settings = Settings.builder().put("cluster.name", "elasticsearch").build();
TransportClient client = new PreBuiltTransportClient(settings);
client.addTransportAddress(new TransportAddress(InetAddress.getByName("127.0.0.1"), 9300));
return client;
}
@Bean(name = {"elasticsearchOperations", "elasticsearchTemplate"})
public ElasticsearchTemplate elasticsearchTemplate() throws UnknownHostException { <2>
return new ElasticsearchTemplate(elasticsearchClient());
}
}
----
<1> Setting up the <<elasticsearch.clients.transport>>.
Deprecated as of version 4.0.
<2> Creating the `ElasticsearchTemplate` bean, offering both names, _elasticsearchOperations_ and _elasticsearchTemplate_.
====
[[elasticsearch.operations.resttemplate]]
== ElasticsearchRestTemplate
@@ -82,6 +49,7 @@ public class RestClientConfig extends AbstractElasticsearchConfiguration {
// no special bean creation needed <2>
}
----
<1> Setting up the <<elasticsearch.clients.rest>>.
<2> The base class `AbstractElasticsearchConfiguration` already provides the `elasticsearchTemplate` bean.
====
@@ -127,6 +95,7 @@ public class TestController {
}
----
<1> Let Spring inject the provided `ElasticsearchOperations` bean in the constructor.
<2> Store some entity in the Elasticsearch cluster.
<3> Retrieve the entity with a query by id.
@@ -164,6 +133,7 @@ Contains the following information:
* Maximum score
* A list of `SearchHit<T>` objects
* Returned aggregations
* Returned suggest results
.SearchPage<T>
Defines a Spring Data `Page` that contains a `SearchHits<T>` element and can be used for paging access using repository methods.
@@ -174,6 +144,9 @@ Returned by the low level scroll API functions in `ElasticsearchRestTemplate`, i
.SearchHitsIterator<T>
An Iterator returned by the streaming functions of the `SearchOperations` interface.
.ReactiveSearchHits
`ReactiveSearchOperations` has methods returning a `Mono<ReactiveSearchHits<T>>`, this contains the same information as a `SearchHits<T>` object, but will provide the contained `SearchHit<T>` objects as a `Flux<SearchHit<T>>` and not as a list.
[[elasticsearch.operations.queries]]
== Queries
@@ -182,12 +155,12 @@ Almost all of the methods defined in the `SearchOperations` and `ReactiveSearchO
[[elasticsearch.operations.criteriaquery]]
=== 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.
`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):
`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
====
@@ -211,7 +184,7 @@ 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_:
.Get all persons with first name _James_ and last name _Miller_:
====
[source,java]
----
@@ -219,11 +192,13 @@ 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_:
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
====
@@ -236,6 +211,7 @@ Criteria miller = new Criteria("lastName").is("Miller") <.>
);
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_
@@ -281,5 +257,3 @@ Query query = new NativeSearchQueryBuilder()
SearchHits<Person> searchHits = operations.search(query, Person.class);
----
====
@@ -242,10 +242,6 @@ A list of supported keywords for Elasticsearch is shown below.
| `findByNameNotIn(Collection<String>names)`
| `{"query": {"bool": {"must": [{"query_string": {"query": "NOT(\"?\" \"?\")", "fields": ["name"]}}]}}}`
| `Near`
| `findByStoreNear`
| `Not Supported Yet !`
| `True`
| `findByAvailableTrue`
| `{ "query" : {
@@ -277,6 +273,26 @@ A list of supported keywords for Elasticsearch is shown below.
}, "sort":[{"name":{"order":"desc"}}]
}`
| `Exists`
| `findByNameExists`
| `{"query":{"bool":{"must":[{"exists":{"field":"name"}}]}}}`
| `IsNull`
| `findByNameIsNull`
| `{"query":{"bool":{"must_not":[{"exists":{"field":"name"}}]}}}`
| `IsNotNull`
| `findByNameIsNotNull`
| `{"query":{"bool":{"must":[{"exists":{"field":"name"}}]}}}`
| `IsEmpty`
| `findByNameIsEmpty`
| `{"query":{"bool":{"must":[{"bool":{"must":[{"exists":{"field":"name"}}],"must_not":[{"wildcard":{"name":{"wildcard":"*"}}}]}}]}}}`
| `IsNotEmpty`
| `findByNameIsNotEmpty`
| `{"query":{"bool":{"must":[{"wildcard":{"name":{"wildcard":"*"}}}]}}}`
|===
NOTE: Methods names to build Geo-shape queries taking `GeoJson` parameters are not supported.
@@ -1,5 +1,5 @@
/*
* Copyright 2020-2021 the original author or authors.
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2019-2021 the original author or authors.
* Copyright 2019-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2021 the original author or authors.
* Copyright 2021-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -0,0 +1,49 @@
/*
* Copyright 2021-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch;
import org.springframework.dao.DataAccessException;
/**
* Exception class for REST status exceptions independent from the used client/backend.
*
* @author Peter-Josef Meisch
* @since 4.3
*/
public class RestStatusException extends DataAccessException {
// we do not use a dedicated status class from Elasticsearch, OpenSearch, Spring web or webflux here
private final int status;
public RestStatusException(int status, String msg) {
super(msg);
this.status = status;
}
public RestStatusException(int status, String msg, Throwable cause) {
super(msg, cause);
this.status = status;
}
public int getStatus() {
return status;
}
@Override
public String toString() {
return "RestStatusException{" + "status=" + status + "} " + super.toString();
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2020-2021 the original author or authors.
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,3 +1,18 @@
/*
* Copyright 2019-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* 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;
@@ -7,12 +22,11 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.elasticsearch.search.suggest.completion.context.ContextMapping;
/**
* Based on reference doc - https://www.elastic.co/guide/en/elasticsearch/reference/current/suggester-context.html
*
* @author Robert Gruendler
* @author Peter-Josef Meisch
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@@ -22,9 +36,26 @@ public @interface CompletionContext {
String name();
ContextMapping.Type type();
ContextMappingType type();
String precision() default "";
String path() default "";
/**
* @since 4.3
*/
enum ContextMappingType {
CATEGORY("category"), GEO("geo");
private final String mappedName;
ContextMappingType(String mappedName) {
this.mappedName = mappedName;
}
public String getMappedName() {
return mappedName;
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2013-2021 the original author or authors.
* Copyright 2013-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2021 the original author or authors.
* Copyright 2021-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2014-2021 the original author or authors.
* Copyright 2014-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2013-2021 the original author or authors.
* Copyright 2013-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,7 +21,6 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.elasticsearch.index.VersionType;
import org.springframework.data.annotation.Persistent;
/**
@@ -45,7 +44,7 @@ public @interface Document {
* Name of the Elasticsearch index.
* <ul>
* <li>Lowercase only</li>
* <li><Cannot include \, /, *, ?, ", <, >, |, ` ` (space character), ,, #/li>
* <li>Cannot include \, /, *, ?, ", <, >, |, ` ` (space character), ,, #</li>
* <li>Cannot start with -, _, +</li>
* <li>Cannot be . or ..</li>
* <li>Cannot be longer than 255 bytes (note it is bytes, so multi-byte characters will count towards the 255 limit
@@ -116,9 +115,15 @@ public @interface Document {
/**
* Controls how Elasticsearch dynamically adds fields to the document.
*
*
* @since 4.3
*/
Dynamic dynamic() default Dynamic.INHERIT;
/**
* @since 4.3
*/
enum VersionType {
INTERNAL, EXTERNAL, EXTERNAL_GTE
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2021 the original author or authors.
* Copyright 2021-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,7 +17,7 @@ package org.springframework.data.elasticsearch.annotations;
/**
* Values for the {@code dynamic} mapping parameter.
*
*
* @author Sascha Woo
* @since 4.3
*/
@@ -25,26 +25,36 @@ public enum Dynamic {
/**
* New fields are added to the mapping.
*/
TRUE,
TRUE("true"),
/**
* New fields are added to the mapping as
* <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/runtime.html">runtime fields</a>. These
* fields are not indexed, and are loaded from {@code _source} at query time.
*/
RUNTIME,
RUNTIME("runtime"),
/**
* New fields are ignored. These fields will not be indexed or searchable, but will still appear in the
* {@code _source} field of returned hits. These fields will not be added to the mapping, and new fields must be added
* explicitly.
*/
FALSE,
FALSE("false"),
/**
* If new fields are detected, an exception is thrown and the document is rejected. New fields must be explicitly
* added to the mapping.
*/
STRICT,
STRICT("strict"),
/**
* Inherit the dynamic setting from their parent object or from the mapping type.
*/
INHERIT
INHERIT("nherit");
private final String mappedName;
Dynamic(String mappedName) {
this.mappedName = mappedName;
}
public String getMappedName() {
return mappedName;
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2019-2021 the original author or authors.
* Copyright 2019-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,7 +24,7 @@ import java.lang.annotation.Target;
/**
* Annotation to set the dynamic mapping mode
* {@see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/dynamic.html">elasticsearch doc</a>}
*
*
* @author Peter-Josef Meisch
* @author Sascha Woo
* @since 4.0
@@ -1,5 +1,5 @@
/*
* Copyright 2019-2021 the original author or authors.
* Copyright 2019-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,13 +17,23 @@ package org.springframework.data.elasticsearch.annotations;
/**
* values for the {@link DynamicMapping annotation}
*
*
* @author Peter-Josef Meisch
* @author Sascha Woo
* @since 4.0
* @deprecated since 4.3, use {@link Document#dynamic()} or {@link Field#dynamic()} instead.
* @deprecated since 4.3, use {@link Document#dynamic()} or {@link Field#dynamic()} instead.
*/
@Deprecated
public enum DynamicMappingValue {
True, False, Strict
True("true"), False("false"), Strict("strict");
private final String mappedName;
DynamicMappingValue(String mappedName) {
this.mappedName = mappedName;
}
public String getMappedName() {
return mappedName;
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2013-2021 the original author or authors.
* Copyright 2013-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -199,8 +199,16 @@ public @interface Field {
/**
* Controls how Elasticsearch dynamically adds fields to the inner object within the document.<br>
* To be used in combination with {@link FieldType#Object} or {@link FieldType#Nested}
*
*
* @since 4.3
*/
Dynamic dynamic() default Dynamic.INHERIT;
/**
* marks this field to be excluded from the _source in Elasticsearch
* (https://www.elastic.co/guide/en/elasticsearch/reference/7.15/mapping-source-field.html#include-exclude)
*
* @since 4.3
*/
boolean excludeFromSource() default false;
}
@@ -1,5 +1,5 @@
/*
* Copyright 2013-2021 the original author or authors.
* Copyright 2013-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -26,40 +26,51 @@ package org.springframework.data.elasticsearch.annotations;
* @author Morgan Lutz
*/
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("auto"), //
Text("text"), //
Keyword("keyword"), //
Long("long"), //
Integer("integer"), //
Short("short"), //
Byte("byte"), //
Double("double"), //
Float("float"), //
Half_Float("half_float"), //
Scaled_Float("scaled_float"), //
Date("date"), //
Date_Nanos("date_nanos"), //
Boolean("boolean"), //
Binary("binary"), //
Integer_Range("integer_range"), //
Float_Range("float_range"), //
Long_Range("long_range"), //
Double_Range("double_range"), //
Date_Range("date_range"), //
Ip_Range("ip_range"), //
Object("object"), //
Nested("nested"), //
Ip("ip"), //
TokenCount("token_count"), //
Percolator("percolator"), //
Flattened("flattened"), //
Search_As_You_Type("search_as_you_type"), //
/** @since 4.1 */
Rank_Feature, //
Rank_Feature("rank_feature"), //
/** @since 4.1 */
Rank_Features, //
Rank_Features("rank_features"), //
/** since 4.2 */
Wildcard, //
Wildcard("wildcard"), //
/** @since 4.2 */
Dense_Vector //
Dense_Vector("dense_vector") //
;
private final String mappedName;
FieldType(String mappedName) {
this.mappedName = mappedName;
}
public String getMappedName() {
return mappedName;
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2013-2021 the original author or authors.
* Copyright 2013-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2017-2021 the original author or authors.
* Copyright 2017-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2020-2021 the original author or authors.
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2020-2021 the original author or authors.
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2020-2021 the original author or authors.
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2019-2021 the original author or authors.
* Copyright 2019-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2019-2021 the original author or authors.
* Copyright 2019-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2014-2021 the original author or authors.
* Copyright 2014-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -128,7 +128,7 @@ public @interface InnerField {
/**
* to be used in combination with {@link FieldType#Rank_Feature}
*
*
* @since 4.1
*/
boolean positiveScoreImpact() default true;
@@ -1,5 +1,5 @@
/*
* Copyright 2020-2021 the original author or authors.
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2020-2021 the original author or authors.
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2014-2021 the original author or authors.
* Copyright 2014-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2014-2021 the original author or authors.
* Copyright 2014-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2020-2021 the original author or authors.
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2013-2021 the original author or authors.
* Copyright 2013-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,6 +15,7 @@
*/
package org.springframework.data.elasticsearch.annotations;
import org.springframework.data.annotation.QueryAnnotation;
import java.lang.annotation.*;
/**
@@ -23,11 +24,13 @@ import java.lang.annotation.*;
* @author Rizwan Idrees
* @author Mohsin Husen
* @author Peter-Josef Meisch
* @author Steven Pearce
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
@Documented
@QueryAnnotation
public @interface Query {
/**
@@ -50,4 +53,3 @@ public @interface Query {
*/
boolean count() default false;
}
@@ -1,5 +1,5 @@
/*
* Copyright 2014-2021 the original author or authors.
* Copyright 2014-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2019-2021 the original author or authors.
* Copyright 2019-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2019-2021 the original author or authors.
* Copyright 2019-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -0,0 +1,48 @@
/*
* Copyright 2021-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.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;
import org.springframework.data.elasticsearch.core.mapping.PropertyValueConverter;
/**
* Annotation to put on a property of an entity to define a value converter which can convert the property to a type
* that Elasticsearch understands and back.
*
* @author Peter-Josef Meisch
* @since 4.3
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD, ElementType.ANNOTATION_TYPE })
@Documented
@Inherited
public @interface ValueConverter {
/**
* Defines the class implementing the {@link PropertyValueConverter} interface. If this is a normal class, it must
* provide a default constructor with no arguments. If this is an enum and thus implementing a singleton by enum it
* must only have one enum value.
*
* @return the class to use for conversion
*/
Class<? extends PropertyValueConverter> value();
}
@@ -1,5 +1,5 @@
/*
* Copyright 2021 the original author or authors.
* Copyright 2021-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2018-2021 the original author or authors.
* Copyright 2018-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -27,6 +27,7 @@ import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import org.elasticsearch.client.RestClientBuilder.HttpClientConfigCallback;
import org.springframework.data.elasticsearch.client.reactive.ReactiveRestClients;
import org.springframework.http.HttpHeaders;
import org.springframework.lang.Nullable;
import org.springframework.web.reactive.function.client.WebClient;
@@ -53,7 +54,6 @@ public interface ClientConfiguration {
/**
* Creates a new {@link ClientConfiguration} instance configured to localhost.
* <p/>
*
* <pre class="code">
* // "localhost:9200"
@@ -69,7 +69,6 @@ public interface ClientConfiguration {
/**
* Creates a new {@link ClientConfiguration} instance configured to a single host given {@code hostAndPort}.
* <p/>
* For example given the endpoint http://localhost:9200
*
* <pre class="code">
@@ -84,7 +83,6 @@ public interface ClientConfiguration {
/**
* Creates a new {@link ClientConfiguration} instance configured to a single host given {@link InetSocketAddress}.
* <p/>
* For example given the endpoint http://localhost:9200
*
* <pre class="code">
@@ -120,16 +118,16 @@ public interface ClientConfiguration {
boolean useSsl();
/**
* Returns the {@link SSLContext} to use. Can be {@link Optional#empty()} if unconfigured.
* Returns the {@link SSLContext} to use. Can be {@link Optional#empty()} if not configured.
*
* @return the {@link SSLContext} to use. Can be {@link Optional#empty()} if unconfigured.
* @return the {@link SSLContext} to use. Can be {@link Optional#empty()} if not configured.
*/
Optional<SSLContext> getSslContext();
/**
* Returns the {@link HostnameVerifier} to use. Can be {@link Optional#empty()} if unconfigured.
* Returns the {@link HostnameVerifier} to use. Can be {@link Optional#empty()} if not configured.
*
* @return the {@link HostnameVerifier} to use. Can be {@link Optional#empty()} if unconfigured.
* @return the {@link HostnameVerifier} to use. Can be {@link Optional#empty()} if not configured.
*/
Optional<HostnameVerifier> getHostNameVerifier();
@@ -152,7 +150,7 @@ public interface ClientConfiguration {
/**
* Returns the path prefix that should be prepended to HTTP(s) requests for Elasticsearch behind a proxy.
*
*
* @return the path prefix.
* @since 4.0
*/
@@ -161,7 +159,7 @@ public interface ClientConfiguration {
/**
* returns an optionally set proxy in the form host:port
*
*
* @return the optional proxy
* @since 4.0
*/
@@ -173,11 +171,19 @@ public interface ClientConfiguration {
Function<WebClient, WebClient> getWebClientConfigurer();
/**
* @return the client configuration callback.
* @return the Rest Client configuration callback.
* @since 4.2
* @deprecated since 4.3 use {@link #getClientConfigurers()}
*/
@Deprecated
HttpClientConfigCallback getHttpClientConfigurer();
/**
* @return the client configuration callbacks
* @since 4.3
*/
<T> List<ClientConfigurationCallback<?>> getClientConfigurers();
/**
* @return the supplier for custom headers.
*/
@@ -274,7 +280,7 @@ public interface ClientConfiguration {
TerminalClientConfigurationBuilder withDefaultHeaders(HttpHeaders defaultHeaders);
/**
* Configure the {@literal milliseconds} for the connect timeout.
* Configure the {@literal milliseconds} for the connect-timeout.
*
* @param millis the timeout to use.
* @return the {@link TerminalClientConfigurationBuilder}
@@ -327,7 +333,7 @@ public interface ClientConfiguration {
/**
* Configure the path prefix that will be prepended to any HTTP(s) requests
*
*
* @param pathPrefix the pathPrefix.
* @return the {@link TerminalClientConfigurationBuilder}
* @since 4.0
@@ -342,21 +348,36 @@ public interface ClientConfiguration {
/**
* set customization hook in case of a reactive configuration
*
*
* @param webClientConfigurer function to configure the WebClient
* @return the {@link TerminalClientConfigurationBuilder}.
* @deprecated since 4.3, use {@link #withClientConfigurer(ClientConfigurationCallback)} with
* {@link ReactiveRestClients.WebClientConfigurationCallback}
*/
@Deprecated
TerminalClientConfigurationBuilder withWebClientConfigurer(Function<WebClient, WebClient> webClientConfigurer);
/**
* Register a {HttpClientConfigCallback} to configure the non-reactive REST client.
*
*
* @param httpClientConfigurer configuration callback, must not be null.
* @return the {@link TerminalClientConfigurationBuilder}.
* @since 4.2
* @deprecated since 4.3, use {@link #withClientConfigurer(ClientConfigurationCallback)} with
* {@link RestClients.RestClientConfigurationCallback}
*/
@Deprecated
TerminalClientConfigurationBuilder withHttpClientConfigurer(HttpClientConfigCallback httpClientConfigurer);
/**
* Register a {@link ClientConfigurationCallback} to configure the client.
*
* @param clientConfigurer configuration callback, must not be {@literal null}.
* @return the {@link TerminalClientConfigurationBuilder}.
* @since 4.3
*/
TerminalClientConfigurationBuilder withClientConfigurer(ClientConfigurationCallback<?> clientConfigurer);
/**
* set a supplier for custom headers. This is invoked for every HTTP request to Elasticsearch to retrieve headers
* that should be sent with the request. A common use case is passing in authentication headers that may change.
@@ -377,4 +398,15 @@ public interface ClientConfiguration {
*/
ClientConfiguration build();
}
/**
* Callback to be executed to configure a client.
*
* @param <T> the type of the client configuration class.
* @since 4.3
*/
@FunctionalInterface
interface ClientConfigurationCallback<T> {
T configure(T clientConfigurer);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2018-2021 the original author or authors.
* Copyright 2018-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -31,6 +31,7 @@ import org.elasticsearch.client.RestClientBuilder.HttpClientConfigCallback;
import org.springframework.data.elasticsearch.client.ClientConfiguration.ClientConfigurationBuilderWithRequiredEndpoint;
import org.springframework.data.elasticsearch.client.ClientConfiguration.MaybeSecureClientConfigurationBuilder;
import org.springframework.data.elasticsearch.client.ClientConfiguration.TerminalClientConfigurationBuilder;
import org.springframework.data.elasticsearch.client.reactive.ReactiveRestClients;
import org.springframework.http.HttpHeaders;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@@ -49,7 +50,7 @@ import org.springframework.web.reactive.function.client.WebClient;
class ClientConfigurationBuilder
implements ClientConfigurationBuilderWithRequiredEndpoint, MaybeSecureClientConfigurationBuilder {
private List<InetSocketAddress> hosts = new ArrayList<>();
private final List<InetSocketAddress> hosts = new ArrayList<>();
private HttpHeaders headers = HttpHeaders.EMPTY;
private boolean useSsl;
private @Nullable SSLContext sslContext;
@@ -62,7 +63,8 @@ class ClientConfigurationBuilder
private @Nullable String proxy;
private Function<WebClient, WebClient> webClientConfigurer = Function.identity();
private Supplier<HttpHeaders> headersSupplier = () -> HttpHeaders.EMPTY;
private HttpClientConfigCallback httpClientConfigurer = httpClientBuilder -> httpClientBuilder;
@Deprecated private HttpClientConfigCallback httpClientConfigurer = httpClientBuilder -> httpClientBuilder;
private List<ClientConfiguration.ClientConfigurationCallback<?>> clientConfigurers = new ArrayList<>();
/*
* (non-Javadoc)
@@ -206,6 +208,7 @@ class ClientConfigurationBuilder
Assert.notNull(webClientConfigurer, "webClientConfigurer must not be null");
this.webClientConfigurer = webClientConfigurer;
this.clientConfigurers.add(ReactiveRestClients.WebClientConfigurationCallback.from(webClientConfigurer));
return this;
}
@@ -215,6 +218,18 @@ class ClientConfigurationBuilder
Assert.notNull(httpClientConfigurer, "httpClientConfigurer must not be null");
this.httpClientConfigurer = httpClientConfigurer;
this.clientConfigurers
.add(RestClients.RestClientConfigurationCallback.from(httpClientConfigurer::customizeHttpClient));
return this;
}
@Override
public TerminalClientConfigurationBuilder withClientConfigurer(
ClientConfiguration.ClientConfigurationCallback<?> clientConfigurer) {
Assert.notNull(clientConfigurer, "clientConfigurer must not be null");
this.clientConfigurers.add(clientConfigurer);
return this;
}
@@ -242,7 +257,7 @@ class ClientConfigurationBuilder
}
return new DefaultClientConfiguration(hosts, headers, useSsl, sslContext, soTimeout, connectTimeout, pathPrefix,
hostnameVerifier, proxy, webClientConfigurer, httpClientConfigurer, headersSupplier);
hostnameVerifier, proxy, webClientConfigurer, httpClientConfigurer, clientConfigurers, headersSupplier);
}
private static InetSocketAddress parse(String hostAndPort) {
@@ -1,5 +1,5 @@
/*
* Copyright 2018-2021 the original author or authors.
* Copyright 2018-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,16 +17,15 @@ package org.springframework.data.elasticsearch.client;
import java.util.function.Supplier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.http.HttpStatus;
import org.springframework.lang.Nullable;
import org.springframework.util.ObjectUtils;
/**
* Logging Utility to log client requests and responses. Logs client requests and responses to Elasticsearch to a
* dedicated logger: {@code org.springframework.data.elasticsearch.client.WIRE} on {@link org.slf4j.event.Level#TRACE}
* level.
* dedicated logger: {@code org.springframework.data.elasticsearch.client.WIRE} on trace level.
*
* @author Mark Paluch
* @author Christoph Strobl
@@ -35,8 +34,7 @@ import org.springframework.util.ObjectUtils;
public abstract class ClientLogger {
private static final String lineSeparator = System.getProperty("line.separator");
private static final Logger WIRE_LOGGER = LoggerFactory
.getLogger("org.springframework.data.elasticsearch.client.WIRE");
private static final Log WIRE_LOGGER = LogFactory.getLog("org.springframework.data.elasticsearch.client.WIRE");
private ClientLogger() {}
@@ -52,7 +50,7 @@ public abstract class ClientLogger {
/**
* Log an outgoing HTTP request.
*
* @param logId the correlation Id, see {@link #newLogId()}.
* @param logId the correlation id, see {@link #newLogId()}.
* @param method HTTP method
* @param endpoint URI
* @param parameters optional parameters.
@@ -60,16 +58,15 @@ public abstract class ClientLogger {
public static void logRequest(String logId, String method, String endpoint, Object parameters) {
if (isEnabled()) {
WIRE_LOGGER.trace("[{}] Sending request {} {} with parameters: {}", logId, method.toUpperCase(), endpoint,
parameters);
WIRE_LOGGER.trace(String.format("[%s] Sending request %s %s with parameters: %s", logId, method.toUpperCase(),
endpoint, parameters));
}
}
/**
* Log an outgoing HTTP request with a request body.
*
* @param logId the correlation Id, see {@link #newLogId()}.
* @param logId the correlation id, see {@link #newLogId()}.
* @param method HTTP method
* @param endpoint URI
* @param parameters optional parameters.
@@ -79,43 +76,43 @@ public abstract class ClientLogger {
Supplier<Object> body) {
if (isEnabled()) {
WIRE_LOGGER.trace("[{}] Sending request {} {} with parameters: {}{}Request body: {}", logId, method.toUpperCase(),
endpoint, parameters, lineSeparator, body.get());
WIRE_LOGGER.trace(String.format("[%s] Sending request %s %s with parameters: %s%sRequest body: %s", logId,
method.toUpperCase(), endpoint, parameters, lineSeparator, body.get()));
}
}
/**
* Log a raw HTTP response without logging the body.
*
* @param logId the correlation Id, see {@link #newLogId()}.
* @param logId the correlation id, see {@link #newLogId()}.
* @param statusCode the HTTP status code.
*/
public static void logRawResponse(String logId, @Nullable HttpStatus statusCode) {
if (isEnabled()) {
WIRE_LOGGER.trace("[{}] Received raw response: {}", logId, statusCode);
WIRE_LOGGER.trace(String.format("[%s] Received raw response: %s", logId, statusCode));
}
}
/**
* Log a raw HTTP response along with the body.
*
* @param logId the correlation Id, see {@link #newLogId()}.
* @param logId the correlation id, see {@link #newLogId()}.
* @param statusCode the HTTP status code.
* @param body body content.
*/
public static void logResponse(String logId, HttpStatus statusCode, String body) {
if (isEnabled()) {
WIRE_LOGGER.trace("[{}] Received response: {}{}Response body: {}", logId, statusCode, lineSeparator, body);
WIRE_LOGGER.trace(
String.format("[%s] Received response: %s%sResponse body: %s", logId, statusCode, lineSeparator, body));
}
}
/**
* Creates a new, unique correlation Id to improve tracing across log events.
* Creates a new, unique correlation id to improve tracing across log events.
*
* @return a new, unique correlation Id.
* @return a new, unique correlation id.
*/
public static String newLogId() {
@@ -1,102 +0,0 @@
/*
* Copyright 2018-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.client;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Collectors;
import org.elasticsearch.common.transport.TransportAddress;
import org.springframework.data.util.Streamable;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* Value object to represent a list of cluster nodes.
*
* @author Oliver Gierke
* @since 3.1
*/
class ClusterNodes implements Streamable<TransportAddress> {
public static ClusterNodes DEFAULT = ClusterNodes.of("127.0.0.1:9300");
private static final String COLON = ":";
private static final String COMMA = ",";
private final List<TransportAddress> clusterNodes;
/**
* Creates a new {@link ClusterNodes} by parsing the given source.
*
* @param source must not be {@literal null} or empty.
*/
private ClusterNodes(String source) {
Assert.hasText(source, "Cluster nodes source must not be null or empty!");
String[] nodes = StringUtils.delimitedListToStringArray(source, COMMA);
this.clusterNodes = Arrays.stream(nodes).map(node -> {
String[] segments = StringUtils.delimitedListToStringArray(node, COLON);
Assert.isTrue(segments.length == 2,
() -> String.format("Invalid cluster node %s in %s! Must be in the format host:port!", node, source));
String host = segments[0].trim();
String port = segments[1].trim();
Assert.hasText(host, () -> String.format("No host name given cluster node %s!", node));
Assert.hasText(port, () -> String.format("No port given in cluster node %s!", node));
return new TransportAddress(toInetAddress(host), Integer.parseInt(port));
}).collect(Collectors.toList());
}
/**
* Creates a new {@link ClusterNodes} by parsing the given source. The expected format is a comma separated list of
* host-port-combinations separated by a colon: {@code host:port,host:port,…}.
*
* @param source must not be {@literal null} or empty.
* @return
*/
public static ClusterNodes of(String source) {
return new ClusterNodes(source);
}
/*
* (non-Javadoc)
* @see java.lang.Iterable#iterator()
*/
@Override
public Iterator<TransportAddress> iterator() {
return clusterNodes.iterator();
}
private static InetAddress toInetAddress(String host) {
try {
return InetAddress.getByName(host);
} catch (UnknownHostException o_O) {
throw new IllegalArgumentException(o_O);
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2018-2021 the original author or authors.
* Copyright 2018-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -55,12 +55,13 @@ class DefaultClientConfiguration implements ClientConfiguration {
private final Function<WebClient, WebClient> webClientConfigurer;
private final HttpClientConfigCallback httpClientConfigurer;
private final Supplier<HttpHeaders> headersSupplier;
private final List<ClientConfigurationCallback<?>> clientConfigurers;
DefaultClientConfiguration(List<InetSocketAddress> hosts, HttpHeaders headers, boolean useSsl,
@Nullable SSLContext sslContext, Duration soTimeout, Duration connectTimeout, @Nullable String pathPrefix,
@Nullable HostnameVerifier hostnameVerifier, @Nullable String proxy,
Function<WebClient, WebClient> webClientConfigurer, HttpClientConfigCallback httpClientConfigurer,
Supplier<HttpHeaders> headersSupplier) {
List<ClientConfigurationCallback<?>> clientConfigurers, Supplier<HttpHeaders> headersSupplier) {
this.hosts = Collections.unmodifiableList(new ArrayList<>(hosts));
this.headers = new HttpHeaders(headers);
@@ -73,6 +74,7 @@ class DefaultClientConfiguration implements ClientConfiguration {
this.proxy = proxy;
this.webClientConfigurer = webClientConfigurer;
this.httpClientConfigurer = httpClientConfigurer;
this.clientConfigurers = clientConfigurers;
this.headersSupplier = headersSupplier;
}
@@ -127,11 +129,18 @@ class DefaultClientConfiguration implements ClientConfiguration {
return webClientConfigurer;
}
@Deprecated
@Override
public HttpClientConfigCallback getHttpClientConfigurer() {
return httpClientConfigurer;
}
@SuppressWarnings("unchecked")
@Override
public <T> List<ClientConfigurationCallback<?>> getClientConfigurers() {
return clientConfigurers;
}
@Override
public Supplier<HttpHeaders> getHeadersSupplier() {
return headersSupplier;
@@ -1,5 +1,5 @@
/*
* Copyright 2018-2021 the original author or authors.
* Copyright 2018-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2018-2021 the original author or authors.
* Copyright 2018-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2018-2021 the original author or authors.
* Copyright 2018-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2018-2021 the original author or authors.
* Copyright 2018-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,11 +18,11 @@ package org.springframework.data.elasticsearch.client;
import java.net.URL;
import java.util.ArrayList;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.FactoryBeanNotInitializedException;
@@ -38,7 +38,7 @@ import org.springframework.util.Assert;
*/
public class RestClientFactoryBean implements FactoryBean<RestHighLevelClient>, InitializingBean, DisposableBean {
private static final Logger LOGGER = LoggerFactory.getLogger(RestClientFactoryBean.class);
private static final Log LOGGER = LogFactory.getLog(RestClientFactoryBean.class);
private @Nullable RestHighLevelClient client;
private String hosts = "http://localhost:9200";
@@ -1,5 +1,5 @@
/*
* Copyright 2018-2021 the original author or authors.
* Copyright 2018-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,6 +22,7 @@ import java.net.InetSocketAddress;
import java.time.Duration;
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
@@ -36,6 +37,7 @@ import org.apache.http.HttpResponseInterceptor;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.config.RequestConfig.Builder;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.impl.nio.client.HttpAsyncClientBuilder;
import org.apache.http.message.BasicHeader;
import org.apache.http.protocol.HttpContext;
import org.elasticsearch.client.RestClient;
@@ -54,6 +56,7 @@ import org.springframework.util.Assert;
* @author Huw Ayling-Miller
* @author Henrique Amaral
* @author Peter-Josef Meisch
* @author Nic Hines
* @since 3.2
*/
public final class RestClients {
@@ -104,22 +107,27 @@ public final class RestClients {
Duration connectTimeout = clientConfiguration.getConnectTimeout();
if (!connectTimeout.isNegative()) {
requestConfigBuilder.setConnectTimeout(Math.toIntExact(connectTimeout.toMillis()));
requestConfigBuilder.setConnectionRequestTimeout(Math.toIntExact(connectTimeout.toMillis()));
}
Duration timeout = clientConfiguration.getSocketTimeout();
Duration socketTimeout = clientConfiguration.getSocketTimeout();
if (!timeout.isNegative()) {
requestConfigBuilder.setSocketTimeout(Math.toIntExact(timeout.toMillis()));
if (!socketTimeout.isNegative()) {
requestConfigBuilder.setSocketTimeout(Math.toIntExact(socketTimeout.toMillis()));
requestConfigBuilder.setConnectionRequestTimeout(Math.toIntExact(socketTimeout.toMillis()));
}
clientBuilder.setDefaultRequestConfig(requestConfigBuilder.build());
clientConfiguration.getProxy().map(HttpHost::create).ifPresent(clientBuilder::setProxy);
clientBuilder = clientConfiguration.getHttpClientConfigurer().customizeHttpClient(clientBuilder);
for (ClientConfiguration.ClientConfigurationCallback<?> clientConfigurer : clientConfiguration
.getClientConfigurers()) {
if (clientConfigurer instanceof RestClientConfigurationCallback) {
RestClientConfigurationCallback restClientConfigurationCallback = (RestClientConfigurationCallback) clientConfigurer;
clientBuilder = restClientConfigurationCallback.configure(clientBuilder);
}
}
return clientBuilder;
});
@@ -198,7 +206,7 @@ public final class RestClients {
}
ClientLogger.logRequest(logId, request.getRequestLine().getMethod(), request.getRequestLine().getUri(), "",
() -> new String(buffer.toByteArray()));
buffer::toString);
} else {
ClientLogger.logRequest(logId, request.getRequestLine().getMethod(), request.getRequestLine().getUri(), "");
}
@@ -213,7 +221,7 @@ public final class RestClients {
/**
* Interceptor to inject custom supplied headers.
*
*
* @since 4.0
*/
private static class CustomHeaderInjector implements HttpRequestInterceptor {
@@ -233,4 +241,23 @@ public final class RestClients {
}
}
}
/**
* {@link org.springframework.data.elasticsearch.client.ClientConfiguration.ClientConfigurationCallback} to configure
* the RestClient with a {@link HttpAsyncClientBuilder}
*
* @since 4.3
*/
public interface RestClientConfigurationCallback
extends ClientConfiguration.ClientConfigurationCallback<HttpAsyncClientBuilder> {
static RestClientConfigurationCallback from(
Function<HttpAsyncClientBuilder, HttpAsyncClientBuilder> clientBuilderCallback) {
Assert.notNull(clientBuilderCallback, "clientBuilderCallback must not be null");
// noinspection NullableProblems
return clientBuilderCallback::apply;
}
}
}
@@ -1,158 +0,0 @@
/*
* Copyright 2013-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.client;
import java.util.Properties;
import org.elasticsearch.client.transport.TransportClient;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.transport.client.PreBuiltTransportClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.FactoryBeanNotInitializedException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.lang.Nullable;
/**
* TransportClientFactoryBean
*
* @author Rizwan Idrees
* @author Mohsin Husen
* @author Jakub Vavrik
* @author Piotr Betkier
* @author Ilkang Na
* @author Oliver Gierke
* @author Peter-Josef Meisch
* @deprecated as of 4.0
*/
@Deprecated
public class TransportClientFactoryBean implements FactoryBean<TransportClient>, InitializingBean, DisposableBean {
private static final Logger logger = LoggerFactory.getLogger(TransportClientFactoryBean.class);
private ClusterNodes clusterNodes = ClusterNodes.of("127.0.0.1:9300");
private String clusterName = "elasticsearch";
private Boolean clientTransportSniff = true;
private Boolean clientIgnoreClusterName = Boolean.FALSE;
private String clientPingTimeout = "5s";
private String clientNodesSamplerInterval = "5s";
private @Nullable TransportClient client;
private @Nullable Properties properties;
@Override
public void destroy() {
try {
logger.info("Closing elasticSearch client");
if (client != null) {
client.close();
}
} catch (final Exception e) {
logger.error("Error closing ElasticSearch client: ", e);
}
}
@Override
public TransportClient getObject() {
if (clientTransportSniff == null) {
throw new FactoryBeanNotInitializedException();
}
return client;
}
@Override
public Class<TransportClient> getObjectType() {
return TransportClient.class;
}
@Override
public boolean isSingleton() {
return true;
}
@Override
public void afterPropertiesSet() throws Exception {
buildClient();
}
protected void buildClient() {
client = new PreBuiltTransportClient(settings());
clusterNodes.stream() //
.peek(it -> logger.info("Adding transport node : " + it.toString())) //
.forEach(client::addTransportAddress);
client.connectedNodes();
}
private Settings settings() {
if (properties != null) {
Settings.Builder builder = Settings.builder();
properties.forEach((key, value) -> {
builder.put(key.toString(), value.toString());
});
return builder.build();
}
return Settings.builder().put("cluster.name", clusterName).put("client.transport.sniff", clientTransportSniff)
.put("client.transport.ignore_cluster_name", clientIgnoreClusterName)
.put("client.transport.ping_timeout", clientPingTimeout)
.put("client.transport.nodes_sampler_interval", clientNodesSamplerInterval).build();
}
public void setClusterNodes(String clusterNodes) {
this.clusterNodes = ClusterNodes.of(clusterNodes);
}
public void setClusterName(String clusterName) {
this.clusterName = clusterName;
}
public void setClientTransportSniff(Boolean clientTransportSniff) {
this.clientTransportSniff = clientTransportSniff;
}
public String getClientNodesSamplerInterval() {
return clientNodesSamplerInterval;
}
public void setClientNodesSamplerInterval(String clientNodesSamplerInterval) {
this.clientNodesSamplerInterval = clientNodesSamplerInterval;
}
public String getClientPingTimeout() {
return clientPingTimeout;
}
public void setClientPingTimeout(String clientPingTimeout) {
this.clientPingTimeout = clientPingTimeout;
}
public Boolean getClientIgnoreClusterName() {
return clientIgnoreClusterName;
}
public void setClientIgnoreClusterName(Boolean clientIgnoreClusterName) {
this.clientIgnoreClusterName = clientIgnoreClusterName;
}
public void setProperties(Properties properties) {
this.properties = properties;
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2018-2021 the original author or authors.
* Copyright 2018-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -83,11 +83,7 @@ import org.elasticsearch.action.update.UpdateResponse;
import org.elasticsearch.client.GetAliasesResponse;
import org.elasticsearch.client.Request;
import org.elasticsearch.client.indices.*;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.DeprecationHandler;
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.core.TimeValue;
import org.elasticsearch.index.get.GetResult;
import org.elasticsearch.index.reindex.BulkByScrollResponse;
import org.elasticsearch.index.reindex.DeleteByQueryRequest;
@@ -100,8 +96,14 @@ import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.suggest.Suggest;
import org.elasticsearch.xcontent.DeprecationHandler;
import org.elasticsearch.xcontent.NamedXContentRegistry;
import org.elasticsearch.xcontent.XContentParser;
import org.elasticsearch.xcontent.XContentType;
import org.reactivestreams.Publisher;
import org.springframework.data.elasticsearch.RestStatusException;
import org.springframework.data.elasticsearch.UncategorizedElasticsearchException;
import org.springframework.data.elasticsearch.core.ResponseConverter;
import org.springframework.data.elasticsearch.client.ClientConfiguration;
import org.springframework.data.elasticsearch.client.ClientLogger;
import org.springframework.data.elasticsearch.client.ElasticsearchHost;
@@ -145,6 +147,7 @@ import org.springframework.web.reactive.function.client.WebClient.RequestBodySpe
* @see ClientConfiguration
* @see ReactiveRestClients
*/
// todo package private after refactoring
public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearchClient, Indices, Cluster {
private final HostProvider<?> hostProvider;
@@ -287,9 +290,21 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
provider = provider.withPathPrefix(clientConfiguration.getPathPrefix());
}
Function<WebClient, WebClient> webClientConfigurer = webClient -> {
for (ClientConfiguration.ClientConfigurationCallback<?> clientConfigurer : clientConfiguration
.getClientConfigurers()) {
if (clientConfigurer instanceof ReactiveRestClients.WebClientConfigurationCallback) {
ReactiveRestClients.WebClientConfigurationCallback webClientConfigurationCallback = (ReactiveRestClients.WebClientConfigurationCallback) clientConfigurer;
webClient = webClientConfigurationCallback.configure(webClient);
}
}
return webClient;
};
provider = provider //
.withDefaultHeaders(clientConfiguration.getDefaultHeaders()) //
.withWebClientConfigurer(clientConfiguration.getWebClientConfigurer()) //
.withWebClientConfigurer(webClientConfigurer) //
.withRequestConfigurer(requestHeadersSpec -> requestHeadersSpec.headers(httpHeaders -> {
HttpHeaders suppliedHeaders = clientConfiguration.getHeadersSupplier().get();
@@ -485,7 +500,7 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
public Mono<ByQueryResponse> updateBy(HttpHeaders headers, UpdateByQueryRequest updateRequest) {
return sendRequest(updateRequest, requestCreator.updateByQuery(), BulkByScrollResponse.class, headers) //
.next() //
.map(ByQueryResponse::of);
.map(ResponseConverter::byQueryResponseOf);
}
@Override
@@ -823,8 +838,7 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
return Mono.error(BytesRestResponse.errorFromXContent(createParser(mediaType, content)));
} catch (Exception e) {
return Mono
.error(new ElasticsearchStatusException(content, RestStatus.fromCode(response.statusCode().value())));
return Mono.error(new RestStatusException(response.statusCode().value(), content));
}
}
}
@@ -857,14 +871,14 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
String mediaType = response.headers().contentType().map(MediaType::toString).orElse(XContentType.JSON.mediaType());
return response.body(BodyExtractors.toMono(byte[].class)) //
.switchIfEmpty(Mono.error(
new ElasticsearchStatusException(String.format("%s request to %s returned error code %s and no body.",
request.getMethod(), request.getEndpoint(), statusCode), status)))
.switchIfEmpty(Mono.error(new RestStatusException(status.getStatus(),
String.format("%s request to %s returned error code %s and no body.", request.getMethod(),
request.getEndpoint(), statusCode))))
.map(bytes -> new String(bytes, StandardCharsets.UTF_8)) //
.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)));
.error(new RestStatusException(status.getStatus(), String.format("%s request to %s returned error code %s.",
request.getMethod(), request.getEndpoint(), statusCode))));
}
private <T> Publisher<? extends T> handleClientError(String logId, ClientResponse response, Class<T> responseType) {
@@ -896,7 +910,7 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
if (exception != null) {
StringBuilder sb = new StringBuilder();
buildExceptionMessages(sb, exception);
return Mono.error(new ElasticsearchStatusException(sb.toString(), status, exception));
return Mono.error(new RestStatusException(status.getStatus(), sb.toString(), exception));
}
return Mono.just(content);
@@ -1,3 +1,18 @@
/*
* Copyright 2021-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.client.reactive;
/**
@@ -1,5 +1,5 @@
/*
* Copyright 2018-2021 the original author or authors.
* Copyright 2018-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2018-2021 the original author or authors.
* Copyright 2018-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2018-2021 the original author or authors.
* Copyright 2018-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -29,8 +29,8 @@ import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.data.elasticsearch.client.ElasticsearchHost;
import org.springframework.data.elasticsearch.client.ElasticsearchHost.State;
import org.springframework.data.elasticsearch.client.NoReachableHostException;
@@ -48,7 +48,7 @@ import org.springframework.web.reactive.function.client.WebClient;
*/
class MultiNodeHostProvider implements HostProvider<MultiNodeHostProvider> {
private final static Logger LOG = LoggerFactory.getLogger(MultiNodeHostProvider.class);
private final static Log LOGGER = LogFactory.getLog(MultiNodeHostProvider.class);
private final WebClientProvider clientProvider;
private final Map<InetSocketAddress, ElasticsearchHost> hosts;
@@ -61,7 +61,9 @@ class MultiNodeHostProvider implements HostProvider<MultiNodeHostProvider> {
this.hosts.put(endpoint, new ElasticsearchHost(endpoint, State.UNKNOWN));
}
LOG.debug("initialized with " + hosts);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("initialized with " + hosts);
}
}
/*
@@ -90,19 +92,27 @@ class MultiNodeHostProvider implements HostProvider<MultiNodeHostProvider> {
@Override
public Mono<InetSocketAddress> lookupActiveHost(Verification verification) {
LOG.trace("lookupActiveHost " + verification + " from " + hosts());
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("lookupActiveHost " + verification + " from " + hosts());
}
if (Verification.LAZY.equals(verification)) {
for (ElasticsearchHost entry : hosts()) {
if (entry.isOnline()) {
LOG.trace("lookupActiveHost returning " + entry);
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("lookupActiveHost returning " + entry);
}
return Mono.just(entry.getEndpoint());
}
}
LOG.trace("no online host found with LAZY");
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("no online host found with LAZY");
}
}
LOG.trace("searching for active host");
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("searching for active host");
}
return findActiveHostInKnownActives() //
.switchIfEmpty(findActiveHostInUnresolved()) //
.switchIfEmpty(findActiveHostInDead()) //
@@ -127,13 +137,17 @@ class MultiNodeHostProvider implements HostProvider<MultiNodeHostProvider> {
private Mono<InetSocketAddress> findActiveForState(State state) {
LOG.trace("findActiveForState state " + state + ", current hosts: " + hosts);
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("findActiveForState state " + state + ", current hosts: " + hosts);
}
return checkNodes(state) //
.map(this::updateNodeState) //
.filter(ElasticsearchHost::isOnline) //
.map(elasticsearchHost -> {
LOG.trace("findActiveForState returning host " + elasticsearchHost);
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("findActiveForState returning host " + elasticsearchHost);
}
return elasticsearchHost;
}).map(ElasticsearchHost::getEndpoint) //
.takeLast(1) //
@@ -150,21 +164,27 @@ class MultiNodeHostProvider implements HostProvider<MultiNodeHostProvider> {
private Flux<Tuple2<InetSocketAddress, State>> checkNodes(@Nullable State state) {
LOG.trace("checkNodes() with state " + state);
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("checkNodes() with state " + state);
}
return Flux.fromIterable(hosts()) //
.filter(entry -> state == null || entry.getState().equals(state)) //
.map(ElasticsearchHost::getEndpoint) //
.concatMap(host -> {
LOG.trace("checking host " + host);
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("checking host " + host);
}
Mono<ClientResponse> clientResponseMono = createWebClient(host) //
.head().uri("/") //
.exchangeToMono(Mono::just) //
.timeout(Duration.ofSeconds(1)) //
.doOnError(throwable -> {
LOG.trace("error checking host " + host + ", " + throwable.getMessage());
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("error checking host " + host + ", " + throwable.getMessage());
}
hosts.put(host, new ElasticsearchHost(host, State.OFFLINE));
clientProvider.getErrorListener().accept(throwable);
});
@@ -174,7 +194,9 @@ class MultiNodeHostProvider implements HostProvider<MultiNodeHostProvider> {
.thenReturn(it.statusCode().isError() ? State.OFFLINE : State.ONLINE)));
}) //
.map(tuple -> {
LOG.trace("check result " + tuple);
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("check result " + tuple);
}
return tuple;
}).onErrorContinue((throwable, o) -> clientProvider.getErrorListener().accept(throwable));
}
@@ -1,5 +1,5 @@
/*
* Copyright 2018-2021 the original author or authors.
* Copyright 2018-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,7 +21,6 @@ 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;
import org.springframework.web.reactive.function.BodyExtractor;
@@ -72,8 +71,7 @@ class RawActionResponse extends ActionResponse {
* until Elasticsearch 7.4 this empty implementation was available in the abstract base class
*/
@Override
public void writeTo(StreamOutput out) throws IOException {
}
public void writeTo(StreamOutput out) throws IOException {}
/**
* Ensure the response body is released to properly release the underlying connection.
@@ -1,5 +1,5 @@
/*
* Copyright 2018-2021 the original author or authors.
* Copyright 2018-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2018-2021 the original author or authors.
* Copyright 2018-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,8 +15,11 @@
*/
package org.springframework.data.elasticsearch.client.reactive;
import java.util.function.Function;
import org.springframework.data.elasticsearch.client.ClientConfiguration;
import org.springframework.util.Assert;
import org.springframework.web.reactive.function.client.WebClient;
/**
* Utility class for common access to reactive Elasticsearch clients. {@link ReactiveRestClients} consolidates set up
@@ -61,4 +64,21 @@ public final class ReactiveRestClients {
return DefaultReactiveElasticsearchClient.create(clientConfiguration, requestCreator);
}
/**
* {@link org.springframework.data.elasticsearch.client.ClientConfiguration.ClientConfigurationCallback} to configure
* the ReactiveElasticsearchClient with a {@link WebClient}
*
* @since 4.3
*/
public interface WebClientConfigurationCallback extends ClientConfiguration.ClientConfigurationCallback<WebClient> {
static WebClientConfigurationCallback from(Function<WebClient, WebClient> webClientCallback) {
Assert.notNull(webClientCallback, "webClientCallback must not be null");
// noinspection NullableProblems
return webClientCallback::apply;
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2018-2021 the original author or authors.
* Copyright 2018-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,3 +1,18 @@
/*
* Copyright 2021-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.client.reactive;
import java.io.IOException;
@@ -1,5 +1,5 @@
/*
* Copyright 2018-2021 the original author or authors.
* Copyright 2018-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2018-2021 the original author or authors.
* Copyright 2018-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -28,8 +28,7 @@ import org.springframework.web.reactive.function.client.WebClient;
/**
* Provider for {@link WebClient}s using a pre-configured {@code scheme}. This class returns {@link WebClient} for a
* specific {@link InetSocketAddress endpoint} and encapsulates common configuration aspects of {@link WebClient} so
* that code using {@link WebClient} is not required to apply further configuration to the actual client.
* <p/>
* that code using {@link WebClient} is not required to apply further configuration to the actual client. <br/>
* Client instances are typically cached allowing reuse of pooled connections if configured on the
* {@link ClientHttpConnector}.
*
@@ -1,5 +1,5 @@
/*
* Copyright 2020-2021 the original author or authors.
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -26,9 +26,6 @@ import org.elasticsearch.client.analytics.ParsedStringStats;
import org.elasticsearch.client.analytics.ParsedTopMetrics;
import org.elasticsearch.client.analytics.StringStatsAggregationBuilder;
import org.elasticsearch.client.analytics.TopMetricsAggregationBuilder;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.xcontent.ContextParser;
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.bucket.adjacency.AdjacencyMatrixAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.adjacency.ParsedAdjacencyMatrix;
@@ -78,6 +75,9 @@ import org.elasticsearch.search.suggest.phrase.PhraseSuggestion;
import org.elasticsearch.search.suggest.phrase.PhraseSuggestionBuilder;
import org.elasticsearch.search.suggest.term.TermSuggestion;
import org.elasticsearch.search.suggest.term.TermSuggestionBuilder;
import org.elasticsearch.xcontent.ContextParser;
import org.elasticsearch.xcontent.NamedXContentRegistry;
import org.elasticsearch.xcontent.ParseField;
import org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient;
/**
@@ -1,5 +1,5 @@
/*
* Copyright 2018-2021 the original author or authors.
* Copyright 2018-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -78,18 +78,11 @@ import org.elasticsearch.client.indices.PutIndexTemplateRequest;
import org.elasticsearch.cluster.health.ClusterHealthStatus;
import org.elasticsearch.common.Priority;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.SuppressForbidden;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.lucene.uid.Versions;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.DeprecationHandler;
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentHelper;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.core.SuppressForbidden;
import org.elasticsearch.core.TimeValue;
import org.elasticsearch.index.VersionType;
import org.elasticsearch.index.mapper.MapperService;
import org.elasticsearch.index.rankeval.RankEvalRequest;
@@ -101,6 +94,13 @@ import org.elasticsearch.index.seqno.SequenceNumbers;
import org.elasticsearch.script.mustache.SearchTemplateRequest;
import org.elasticsearch.search.fetch.subphase.FetchSourceContext;
import org.elasticsearch.tasks.TaskId;
import org.elasticsearch.xcontent.DeprecationHandler;
import org.elasticsearch.xcontent.NamedXContentRegistry;
import org.elasticsearch.xcontent.ToXContent;
import org.elasticsearch.xcontent.XContent;
import org.elasticsearch.xcontent.XContentBuilder;
import org.elasticsearch.xcontent.XContentParser;
import org.elasticsearch.xcontent.XContentType;
import org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient;
import org.springframework.http.HttpMethod;
import org.springframework.lang.Nullable;
@@ -1232,7 +1232,12 @@ public class RequestConverters {
return this;
}
Params withIndicesOptions(IndicesOptions indicesOptions) {
Params withIndicesOptions(@Nullable IndicesOptions indicesOptions) {
if (indicesOptions == null) {
return this;
}
withIgnoreUnavailable(indicesOptions.ignoreUnavailable());
putParam("allow_no_indices", Boolean.toString(indicesOptions.allowNoIndices()));
String expandWildcards;
@@ -1,5 +1,5 @@
/*
* Copyright 2020-2021 the original author or authors.
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,13 +21,11 @@ import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import org.elasticsearch.action.search.SearchScrollRequest;
import org.elasticsearch.search.Scroll;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;
/**
* Mutable state object holding scrollId to be used for {@link SearchScrollRequest#scroll(Scroll)}
* Mutable state object holding scrollId to be used for scroll requests.
*
* @author Christoph Strobl
* @author Peter-Josef Meisch
@@ -1,5 +1,5 @@
/*
* Copyright 2018-2021 the original author or authors.
* Copyright 2018-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2018-2021 the original author or authors.
* Copyright 2018-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2020-2021 the original author or authors.
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2020-2021 the original author or authors.
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2018-2021 the original author or authors.
* Copyright 2018-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2013-2021 the original author or authors.
* Copyright 2013-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -35,7 +35,6 @@ public class ElasticsearchNamespaceHandler extends NamespaceHandlerSupport {
RepositoryBeanDefinitionParser parser = new RepositoryBeanDefinitionParser(extension);
registerBeanDefinitionParser("repositories", parser);
registerBeanDefinitionParser("transport-client", new TransportClientBeanDefinitionParser());
registerBeanDefinitionParser("rest-client", new RestClientBeanDefinitionParser());
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2020-2021 the original author or authors.
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2020-2021 the original author or authors.
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2020-2021 the original author or authors.
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -29,7 +29,7 @@ import org.springframework.data.mapping.context.PersistentEntities;
* @author Peter-Josef Meisch
* @since 4.1
*/
class PersistentEntitiesFactoryBean implements FactoryBean<PersistentEntities> {
public class PersistentEntitiesFactoryBean implements FactoryBean<PersistentEntities> {
private final MappingElasticsearchConverter converter;
@@ -1,5 +1,5 @@
/*
* Copyright 2020-2021 the original author or authors.
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2018-2021 the original author or authors.
* Copyright 2018-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,8 +16,8 @@
package org.springframework.data.elasticsearch.config;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.data.elasticsearch.client.RestClientFactoryBean;
import org.w3c.dom.Element;
@@ -1,57 +0,0 @@
/*
* Copyright 2013-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.config;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.data.elasticsearch.client.TransportClientFactoryBean;
import org.w3c.dom.Element;
/**
* TransportClientBeanDefinitionParser
*
* @author Rizwan Idrees
* @author Mohsin Husen
* @deprecated as of 4.0
*/
@Deprecated
public class TransportClientBeanDefinitionParser extends AbstractBeanDefinitionParser {
@Override
protected AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(TransportClientFactoryBean.class);
setConfigurations(element, builder);
return getSourcedBeanDefinition(builder, element, parserContext);
}
private void setConfigurations(Element element, BeanDefinitionBuilder builder) {
builder.addPropertyValue("clusterNodes", element.getAttribute("cluster-nodes"));
builder.addPropertyValue("clusterName", element.getAttribute("cluster-name"));
builder.addPropertyValue("clientTransportSniff", Boolean.valueOf(element.getAttribute("client-transport-sniff")));
builder.addPropertyValue("clientIgnoreClusterName", Boolean.valueOf(element.getAttribute("client-transport-ignore-cluster-name")));
builder.addPropertyValue("clientPingTimeout", element.getAttribute("client-transport-ping-timeout"));
builder.addPropertyValue("clientNodesSamplerInterval", element.getAttribute("client-transport-nodes-sampler-interval"));
}
private AbstractBeanDefinition getSourcedBeanDefinition(BeanDefinitionBuilder builder, Element source,
ParserContext context) {
AbstractBeanDefinition definition = builder.getBeanDefinition();
definition.setSource(context.extractSource(source));
return definition;
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2019-2021 the original author or authors.
* Copyright 2019-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,37 +15,24 @@
*/
package org.springframework.data.elasticsearch.core;
import java.util.ArrayList;
import java.time.Duration;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
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;
import org.elasticsearch.action.search.MultiSearchResponse;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.support.WriteRequest;
import org.elasticsearch.action.support.WriteRequestBuilder;
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.BulkFailureException;
import org.springframework.data.elasticsearch.core.document.SearchDocumentResponse;
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.SearchDocumentResponse;
import org.springframework.data.elasticsearch.core.event.AfterConvertCallback;
import org.springframework.data.elasticsearch.core.event.AfterLoadCallback;
import org.springframework.data.elasticsearch.core.event.AfterSaveCallback;
import org.springframework.data.elasticsearch.core.event.BeforeConvertCallback;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
@@ -57,7 +44,6 @@ import org.springframework.data.elasticsearch.core.query.ByQueryResponse;
import org.springframework.data.elasticsearch.core.query.IndexQuery;
import org.springframework.data.elasticsearch.core.query.IndexQueryBuilder;
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;
@@ -73,34 +59,44 @@ import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* AbstractElasticsearchTemplate
* This class contains methods that are common to different implementations of the {@link ElasticsearchOperations}
* interface that use different clients, like RestHighLevelClient and the next Java client from Elasticsearch or some
* future implementation that might use an Opensearch client. This class must not contain imports or use classes that
* are specific to one of these implementations.
* <p>
* <strong>Note:</strong> Although this class is public, it is not considered to be part of the official Spring Data
* Elasticsearch API and so might change at any time.
*
* @author Sascha Woo
* @author Peter-Josef Meisch
* @author Roman Puchkovskiy
* @author Subhobrata Dey
* @author Steven Pearce
* @author Anton Naydenov
*/
public abstract class AbstractElasticsearchTemplate implements ElasticsearchOperations, ApplicationContextAware {
@Nullable protected ElasticsearchConverter elasticsearchConverter;
@Nullable protected RequestFactory requestFactory;
@Nullable private EntityOperations entityOperations;
@Nullable private EntityCallbacks entityCallbacks;
@Nullable private RefreshPolicy refreshPolicy;
@Nullable protected RoutingResolver routingResolver;
protected ElasticsearchConverter elasticsearchConverter;
protected RequestFactory requestFactory;
protected EntityOperations entityOperations;
@Nullable protected EntityCallbacks entityCallbacks;
@Nullable protected RefreshPolicy refreshPolicy;
protected RoutingResolver routingResolver;
// region Initialization
protected void initialize(ElasticsearchConverter elasticsearchConverter) {
public AbstractElasticsearchTemplate() {
this(null);
}
Assert.notNull(elasticsearchConverter, "elasticsearchConverter must not be null.");
public AbstractElasticsearchTemplate(@Nullable ElasticsearchConverter elasticsearchConverter) {
this.elasticsearchConverter = elasticsearchConverter;
this.elasticsearchConverter = elasticsearchConverter != null ? elasticsearchConverter
: createElasticsearchConverter();
MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext = this.elasticsearchConverter
.getMappingContext();
this.entityOperations = new EntityOperations(mappingContext);
this.routingResolver = new DefaultRoutingResolver((SimpleElasticsearchMappingContext) mappingContext);
this.routingResolver = new DefaultRoutingResolver(mappingContext);
requestFactory = new RequestFactory(elasticsearchConverter);
requestFactory = new RequestFactory(this.elasticsearchConverter);
// initialize the VersionInfo class in the initialization phase
// noinspection ResultOfMethodCallIgnored
@@ -119,13 +115,18 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
}
copy.setRoutingResolver(routingResolver);
copy.setRefreshPolicy(refreshPolicy);
return copy;
}
/**
* must return a copy of this instance that will for example be used to set a custom routing resolver without
* modifying the original object.
*/
protected abstract AbstractElasticsearchTemplate doCopy();
protected ElasticsearchConverter createElasticsearchConverter() {
private ElasticsearchConverter createElasticsearchConverter() {
MappingElasticsearchConverter mappingElasticsearchConverter = new MappingElasticsearchConverter(
new SimpleElasticsearchMappingContext());
mappingElasticsearchConverter.afterPropertiesSet();
@@ -147,7 +148,6 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
/**
* Set the {@link EntityCallbacks} instance to use when invoking {@link EntityCallbacks callbacks} like the
* {@link org.springframework.data.elasticsearch.core.event.BeforeConvertCallback}.
* <p />
* Overrides potentially existing {@link EntityCallbacks}.
*
* @param entityCallbacks must not be {@literal null}.
@@ -176,7 +176,7 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
* @since 4.3
*/
public void logVersions() {
VersionInfo.logVersions(getClusterVersion());
VersionInfo.logVersions(getVendor(), getRuntimeLibraryVersion(), getClusterVersion());
}
// endregion
@@ -201,9 +201,8 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
IndexQuery query = getIndexQuery(entityAfterBeforeConvert);
doIndex(query, index);
T entityAfterAfterSave = (T) maybeCallbackAfterSave(query.getObject(), index);
return entityAfterAfterSave;
// noinspection unchecked
return (T) maybeCallbackAfterSave(Objects.requireNonNull(query.getObject()), index);
}
@Override
@@ -232,8 +231,8 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
return Collections.emptyList();
}
List<IndexedObjectInformation> indexedObjectInformations = bulkIndex(indexQueries, index);
Iterator<IndexedObjectInformation> iterator = indexedObjectInformations.iterator();
List<IndexedObjectInformation> indexedObjectInformationList = bulkIndex(indexQueries, index);
Iterator<IndexedObjectInformation> iterator = indexedObjectInformationList.iterator();
// noinspection unchecked
return indexQueries.stream() //
@@ -242,8 +241,9 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
.collect(Collectors.toList()); //
}
@SafeVarargs
@Override
public <T> Iterable<T> save(T... entities) {
public final <T> Iterable<T> save(T... entities) {
return save(Arrays.asList(entities));
}
@@ -305,7 +305,9 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
@Override
public String delete(Object entity, IndexCoordinates index) {
return this.delete(getEntityId(entity), index);
String entityId = getEntityId(entity);
Assert.notNull(entityId, "entity must have an if that is notnull");
return this.delete(entityId, index);
}
@Override
@@ -354,50 +356,16 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
maybeCallbackBeforeConvertWithQueries(queries, index);
List<IndexedObjectInformation> indexedObjectInformations = doBulkOperation(queries, bulkOptions, index);
List<IndexedObjectInformation> indexedObjectInformationList = doBulkOperation(queries, bulkOptions, index);
maybeCallbackAfterSaveWithQueries(queries, index);
return indexedObjectInformations;
return indexedObjectInformationList;
}
public abstract List<IndexedObjectInformation> doBulkOperation(List<?> queries, BulkOptions bulkOptions,
IndexCoordinates index);
/**
* Pre process the write request before it is sent to the server, eg. by setting the
* {@link WriteRequest#setRefreshPolicy(String) refresh policy} if applicable.
*
* @param request must not be {@literal null}.
* @param <R>
* @return the processed {@link WriteRequest}.
*/
protected <R extends WriteRequest<R>> R prepareWriteRequest(R request) {
if (refreshPolicy == null) {
return request;
}
return request.setRefreshPolicy(RequestFactory.toElasticsearchRefreshPolicy(refreshPolicy));
}
/**
* Pre process the write request before it is sent to the server, eg. by setting the
* {@link WriteRequest#setRefreshPolicy(String) refresh policy} if applicable.
*
* @param requestBuilder must not be {@literal null}.
* @param <R>
* @return the processed {@link WriteRequest}.
*/
protected <R extends WriteRequestBuilder<R>> R prepareWriteRequestBuilder(R requestBuilder) {
if (refreshPolicy == null) {
return requestBuilder;
}
return requestBuilder.setRefreshPolicy(RequestFactory.toElasticsearchRefreshPolicy(refreshPolicy));
}
// endregion
// region SearchOperations
@@ -414,8 +382,8 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
@Override
public <T> SearchHitsIterator<T> searchForStream(Query query, Class<T> clazz, IndexCoordinates index) {
long scrollTimeInMillis = TimeValue.timeValueMinutes(1).millis();
Duration scrollTime = query.getScrollTime() != null ? query.getScrollTime() : Duration.ofMinutes(1);
long scrollTimeInMillis = scrollTime.toMillis();
// noinspection ConstantConditions
int maxCount = query.isLimiting() ? query.getMaxResults() : 0;
@@ -436,98 +404,16 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
Assert.notNull(query.getId(), "No document id defined for MoreLikeThisQuery");
MoreLikeThisQueryBuilder moreLikeThisQueryBuilder = requestFactory.moreLikeThisQueryBuilder(query, index);
return search(
new NativeSearchQueryBuilder().withQuery(moreLikeThisQueryBuilder).withPageable(query.getPageable()).build(),
clazz, index);
return doSearch(query, clazz, index);
}
protected abstract <T> SearchHits<T> doSearch(MoreLikeThisQuery query, Class<T> clazz, IndexCoordinates 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();
for (Query query : queries) {
request.add(requestFactory.searchRequest(query, clazz, index));
}
MultiSearchResponse.Item[] items = getMultiSearchResult(request);
SearchDocumentResponseCallback<SearchHits<T>> callback = new ReadSearchDocumentResponseCallback<>(clazz, index);
List<SearchHits<T>> res = new ArrayList<>(queries.size());
int c = 0;
for (Query query : queries) {
res.add(callback.doWith(SearchDocumentResponse.from(items[c++].getResponse())));
}
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) {
request.add(requestFactory.searchRequest(query, it.next(), index));
}
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,
index);
SearchResponse response = items[c++].getResponse();
res.add(callback.doWith(SearchDocumentResponse.from(response)));
}
return res;
}
@Override
public <T> SearchHits<T> search(Query query, Class<T> clazz) {
return search(query, clazz, getIndexCoordinatesFor(clazz));
@@ -557,13 +443,6 @@ 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
@@ -579,9 +458,6 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
* @since 4.0
*/
public RequestFactory getRequestFactory() {
Assert.notNull(requestFactory, "requestfactory not initialized");
return requestFactory;
}
@@ -600,37 +476,6 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
return getRequiredPersistentEntity(clazz).getIndexCoordinates();
}
/**
* @param bulkResponse
* @return the list of the item id's
*/
protected List<IndexedObjectInformation> checkForBulkOperationFailure(BulkResponse bulkResponse) {
if (bulkResponse.hasFailures()) {
Map<String, String> failedDocuments = new HashMap<>();
for (BulkItemResponse item : bulkResponse.getItems()) {
if (item.isFailed())
failedDocuments.put(item.getId(), item.getFailureMessage());
}
throw new BulkFailureException(
"Bulk operation has failures. Use ElasticsearchException.getFailedDocuments() for detailed messages ["
+ failedDocuments + ']',
failedDocuments);
}
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 <T> T updateIndexedObject(T entity, IndexedObjectInformation indexedObjectInformation) {
ElasticsearchPersistentEntity<?> persistentEntity = elasticsearchConverter.getMappingContext()
@@ -661,8 +506,7 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
}
// noinspection unchecked
T updatedEntity = (T) propertyAccessor.getBean();
return updatedEntity;
return (T) propertyAccessor.getBean();
}
return entity;
}
@@ -719,6 +563,7 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
id = elasticsearchConverter.convertId(id);
}
// noinspection ConstantConditions
IndexQueryBuilder builder = new IndexQueryBuilder() //
.withId(id) //
.withObject(entity);
@@ -729,6 +574,7 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
builder.withSeqNoPrimaryTerm(seqNoPrimaryTerm);
} else {
// version cannot be used together with seq_no and primary_term
// noinspection ConstantConditions
builder.withVersion(getEntityVersion(entity));
}
@@ -749,6 +595,18 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
@Nullable
abstract protected String getClusterVersion();
/**
* @return the vendor name of the used cluster and client library
* @since 4.3
*/
abstract protected String getVendor();
/**
* @return the version of the used client runtime library.
* @since 4.3
*/
abstract protected String getRuntimeLibraryVersion();
// endregion
// region Entity callbacks
@@ -831,10 +689,19 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
return entity;
}
protected <T> Document maybeCallbackAfterLoad(Document document, Class<T> type, IndexCoordinates indexCoordinates) {
if (entityCallbacks != null) {
return entityCallbacks.callback(AfterLoadCallback.class, document, type, indexCoordinates);
}
return document;
}
// endregion
protected void updateIndexedObjectsWithQueries(List<?> queries,
List<IndexedObjectInformation> indexedObjectInformations) {
List<IndexedObjectInformation> indexedObjectInformationList) {
for (int i = 0; i < queries.size(); i++) {
Object query = queries.get(i);
@@ -844,7 +711,7 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
Object queryObject = indexQuery.getObject();
if (queryObject != null) {
indexQuery.setObject(updateIndexedObject(queryObject, indexedObjectInformations.get(i)));
indexQuery.setObject(updateIndexedObject(queryObject, indexedObjectInformationList.get(i)));
}
}
}
@@ -877,13 +744,18 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
if (document == null) {
return null;
}
Document documentAfterLoad = maybeCallbackAfterLoad(document, type, index);
T entity = reader.read(type, document);
IndexedObjectInformation indexedObjectInformation = IndexedObjectInformation.of(
document.hasId() ? document.getId() : null, document.getSeqNo(), document.getPrimaryTerm(),
document.getVersion());
T entity = reader.read(type, documentAfterLoad);
IndexedObjectInformation indexedObjectInformation = IndexedObjectInformation.of( //
documentAfterLoad.hasId() ? documentAfterLoad.getId() : null, //
documentAfterLoad.getSeqNo(), //
documentAfterLoad.getPrimaryTerm(), //
documentAfterLoad.getVersion()); //
entity = updateIndexedObject(entity, indexedObjectInformation);
return maybeCallbackAfterConvert(entity, document, index);
return maybeCallbackAfterConvert(entity, documentAfterLoad, index);
}
}
@@ -904,6 +776,7 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
this.type = type;
}
@NonNull
@Override
public SearchHits<T> doWith(SearchDocumentResponse response) {
List<T> entities = response.getSearchDocuments().stream().map(delegate::doWith).collect(Collectors.toList());
@@ -924,6 +797,7 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
this.type = type;
}
@NonNull
@Override
public SearchScrollHits<T> doWith(SearchDocumentResponse response) {
List<T> entities = response.getSearchDocuments().stream().map(delegate::doWith).collect(Collectors.toList());
@@ -1,5 +1,5 @@
/*
* Copyright 2019-2021 the original author or authors.
* Copyright 2019-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,14 +17,10 @@ package org.springframework.data.elasticsearch.core;
import static org.springframework.util.StringUtils.*;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import org.elasticsearch.cluster.metadata.AliasMetadata;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.elasticsearch.UncategorizedElasticsearchException;
@@ -46,9 +42,7 @@ import org.springframework.util.Assert;
* @author Sascha Woo
* @since 4.0
*/
abstract class AbstractIndexTemplate implements IndexOperations {
private static final Logger LOGGER = LoggerFactory.getLogger(AbstractIndexTemplate.class);
public abstract class AbstractIndexTemplate implements IndexOperations {
protected final ElasticsearchConverter elasticsearchConverter;
protected final RequestFactory requestFactory;
@@ -175,8 +169,6 @@ abstract class AbstractIndexTemplate implements IndexOperations {
protected abstract void doRefresh(IndexCoordinates indexCoordinates);
protected abstract List<AliasMetadata> doQueryForAlias(IndexCoordinates index);
@Override
public Map<String, Set<AliasData>> getAliases(String... aliasNames) {
@@ -0,0 +1,41 @@
/*
* Copyright 2021-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.core;
/**
* Class corresponding to the Elasticsearch class, but in the org.springframework.data.elasticsearch package
*
* @author Peter-Josef Meisch
*/
public class ActiveShardCount {
private static final int ACTIVE_SHARD_COUNT_DEFAULT = -2;
private static final int ALL_ACTIVE_SHARDS = -1;
public static final ActiveShardCount DEFAULT = new ActiveShardCount(ACTIVE_SHARD_COUNT_DEFAULT);
public static final ActiveShardCount ALL = new ActiveShardCount(ALL_ACTIVE_SHARDS);
public static final ActiveShardCount NONE = new ActiveShardCount(0);
public static final ActiveShardCount ONE = new ActiveShardCount(1);
private final int value;
public ActiveShardCount(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2020-2021 the original author or authors.
* Copyright 2021-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,13 +13,19 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.core.event;
import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchTemplateConfiguration;
import org.springframework.test.context.ContextConfiguration;
package org.springframework.data.elasticsearch.core;
/**
* Aggregation container used in the Spring Data Elasticsearch API for a single aggregation. The concrete
* implementations must be provided by the code handling the direct communication with Elasticsearch.
*
* @author Peter-Josef Meisch
* @param <T> the aggregation class from the used client implementation.
* @since 4.3
*/
@ContextConfiguration(classes = { ElasticsearchTemplateConfiguration.class, ElasticsearchOperationsCallbackIntegrationTest.Config.class })
class ElasticsearchTransportOperationsCallbackIntegrationTest extends ElasticsearchOperationsCallbackIntegrationTest {}
public interface AggregationContainer<T> {
/**
* @return the concrete aggregations implementation
*/
T aggregation();
}
@@ -0,0 +1,31 @@
/*
* Copyright 2021-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.core;
/**
* Aggregations container used in the Spring Data Elasticsearch API. The concrete implementations must be provided by
* the code handling the direct communication with Elasticsearch.
*
* @author Peter-Josef Meisch
* @param <T> the aggregations class from the used client implementation.
* @since 4.3
*/
public interface AggregationsContainer<T> {
/**
* @return the concrete aggregations implementation
*/
T aggregations();
}
@@ -1,11 +1,11 @@
/*
* Copyright 2013-2021 the original author or authors.
* Copyright 2013-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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,
@@ -1,5 +1,5 @@
/*
* Copyright 2013-2021 the original author or authors.
* Copyright 2013-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -44,6 +44,7 @@ import org.springframework.util.Assert;
* @author Rasmus Faber-Espensen
* @author James Bodkin
* @author Peter-Josef Meisch
* @since 4.4
*/
class CriteriaQueryProcessor {
@@ -165,20 +166,35 @@ class CriteriaQueryProcessor {
@Nullable
private QueryBuilder queryFor(Criteria.CriteriaEntry entry, Field field) {
QueryBuilder query = null;
String fieldName = field.getName();
boolean isKeywordField = FieldType.Keyword == field.getFieldType();
OperationKey key = entry.getKey();
if (key == OperationKey.EXISTS) {
return existsQuery(fieldName);
// operations without a value
switch (key) {
case EXISTS:
query = existsQuery(fieldName);
break;
case EMPTY:
query = boolQuery().must(existsQuery(fieldName)).mustNot(wildcardQuery(fieldName, "*"));
break;
case NOT_EMPTY:
query = wildcardQuery(fieldName, "*");
break;
default:
break;
}
if (query != null) {
return query;
}
// now operation keys with a value
Object value = entry.getValue();
String searchText = QueryParserUtil.escape(value.toString());
QueryBuilder query = null;
switch (key) {
case EQUALS:
query = queryStringQuery(searchText).field(fieldName).defaultOperator(AND);
@@ -1,5 +1,5 @@
/*
* Copyright 2019-2021 the original author or authors.
* Copyright 2019-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,6 +15,7 @@
*/
package org.springframework.data.elasticsearch.core;
import java.util.Collection;
import java.util.List;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
@@ -121,6 +122,8 @@ public interface DocumentOperations {
* @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 {@link MultiGetItem}s
* @see Query#multiGetQuery(Collection)
* @see Query#multiGetQueryWithRouting(List)
* @since 4.1
*/
<T> List<MultiGetItem<T>> multiGet(Query query, Class<T> clazz);
@@ -132,6 +135,8 @@ public interface DocumentOperations {
* @param clazz the type of the object to be returned
* @param index the index(es) from which the objects are read.
* @return list of {@link MultiGetItem}s
* @see Query#multiGetQuery(Collection)
* @see Query#multiGetQueryWithRouting(List)
*/
<T> List<MultiGetItem<T>> multiGet(Query query, Class<T> clazz, IndexCoordinates index);
@@ -159,6 +164,7 @@ public interface DocumentOperations {
* @param queries the queries to execute in bulk
* @param clazz the entity class
* @return the information about the indexed objects
* @throws org.springframework.data.elasticsearch.BulkFailureException with information about the failed operation
* @since 4.1
*/
default List<IndexedObjectInformation> bulkIndex(List<IndexQuery> queries, Class<?> clazz) {
@@ -170,6 +176,7 @@ public interface DocumentOperations {
*
* @param queries the queries to execute in bulk
* @return the information about of the indexed objects
* @throws org.springframework.data.elasticsearch.BulkFailureException with information about the failed operation
*/
default List<IndexedObjectInformation> bulkIndex(List<IndexQuery> queries, IndexCoordinates index) {
return bulkIndex(queries, BulkOptions.defaultOptions(), index);
@@ -182,6 +189,7 @@ public interface DocumentOperations {
* @param bulkOptions options to be added to the bulk request
* @param clazz the entity class
* @return the information about of the indexed objects
* @throws org.springframework.data.elasticsearch.BulkFailureException with information about the failed operation
* @since 4.1
*/
List<IndexedObjectInformation> bulkIndex(List<IndexQuery> queries, BulkOptions bulkOptions, Class<?> clazz);
@@ -192,6 +200,7 @@ public interface DocumentOperations {
* @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
* @throws org.springframework.data.elasticsearch.BulkFailureException with information about the failed operation
*/
List<IndexedObjectInformation> bulkIndex(List<IndexQuery> queries, BulkOptions bulkOptions, IndexCoordinates index);
@@ -199,6 +208,7 @@ public interface DocumentOperations {
* Bulk update all objects. Will do update.
*
* @param queries the queries to execute in bulk
* @throws org.springframework.data.elasticsearch.BulkFailureException with information about the failed operation
*/
default void bulkUpdate(List<UpdateQuery> queries, IndexCoordinates index) {
bulkUpdate(queries, BulkOptions.defaultOptions(), index);
@@ -209,6 +219,7 @@ public interface DocumentOperations {
*
* @param clazz the entity class
* @param queries the queries to execute in bulk
* @throws org.springframework.data.elasticsearch.BulkFailureException with information about the failed operation
* @since 4.1
*/
void bulkUpdate(List<UpdateQuery> queries, Class<?> clazz);
@@ -218,6 +229,7 @@ public interface DocumentOperations {
*
* @param queries the queries to execute in bulk
* @param bulkOptions options to be added to the bulk request
* @throws org.springframework.data.elasticsearch.BulkFailureException with information about the failed operation
*/
void bulkUpdate(List<UpdateQuery> queries, BulkOptions bulkOptions, IndexCoordinates index);
@@ -283,7 +295,7 @@ public interface DocumentOperations {
/**
* 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}
@@ -0,0 +1,40 @@
/*
* Copyright 2021-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.core;
import org.elasticsearch.search.aggregations.Aggregation;
import org.springframework.lang.NonNull;
/**
* AggregationContainer implementation for an Elasticsearch7 aggregation.
*
* @author Peter-Josef Meisch
* @since 4.3
*/
public class ElasticsearchAggregation implements AggregationContainer<Aggregation> {
private final Aggregation aggregation;
public ElasticsearchAggregation(Aggregation aggregation) {
this.aggregation = aggregation;
}
@NonNull
@Override
public Aggregation aggregation() {
return aggregation;
}
}
@@ -0,0 +1,40 @@
/*
* Copyright 2021-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.core;
import org.elasticsearch.search.aggregations.Aggregations;
import org.springframework.lang.NonNull;
/**
* AggregationsContainer implementation for the Elasticsearch7 aggregations.
*
* @author Peter-Josef Meisch
* @since 4.3
*/
public class ElasticsearchAggregations implements AggregationsContainer<Aggregations> {
private final Aggregations aggregations;
public ElasticsearchAggregations(Aggregations aggregations) {
this.aggregations = aggregations;
}
@NonNull
@Override
public Aggregations aggregations() {
return aggregations;
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2018-2021 the original author or authors.
* Copyright 2018-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,13 +22,13 @@ import org.elasticsearch.ElasticsearchException;
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;
import org.springframework.dao.OptimisticLockingFailureException;
import org.springframework.dao.support.PersistenceExceptionTranslator;
import org.springframework.data.elasticsearch.NoSuchIndexException;
import org.springframework.data.elasticsearch.RestStatusException;
import org.springframework.data.elasticsearch.UncategorizedElasticsearchException;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
@@ -38,7 +38,7 @@ import org.springframework.util.StringUtils;
* Simple {@link PersistenceExceptionTranslator} for Elasticsearch. Convert the given runtime exception to an
* appropriate exception from the {@code org.springframework.dao} hierarchy. Return {@literal null} if no translation is
* appropriate: any other exception may have resulted from user code, and should not be translated.
*
*
* @author Christoph Strobl
* @author Peter-Josef Meisch
* @author Roman Puchkovskiy
@@ -63,9 +63,28 @@ public class ElasticsearchExceptionTranslator implements PersistenceExceptionTra
ex);
}
if (elasticsearchException instanceof ElasticsearchStatusException) {
ElasticsearchStatusException elasticsearchStatusException = (ElasticsearchStatusException) elasticsearchException;
return new RestStatusException(elasticsearchStatusException.status().getStatus(),
elasticsearchStatusException.getMessage(), elasticsearchStatusException);
}
return new UncategorizedElasticsearchException(ex.getMessage(), ex);
}
if (ex instanceof RestStatusException) {
RestStatusException restStatusException = (RestStatusException) ex;
Throwable cause = restStatusException.getCause();
if (cause instanceof ElasticsearchException) {
ElasticsearchException elasticsearchException = (ElasticsearchException) cause;
if (!indexAvailable(elasticsearchException)) {
return new NoSuchIndexException(ObjectUtils.nullSafeToString(elasticsearchException.getMetadata("es.index")),
ex);
}
}
}
if (ex instanceof ValidationException) {
return new DataIntegrityViolationException(ex.getMessage(), ex);
}
@@ -80,13 +99,26 @@ public class ElasticsearchExceptionTranslator implements PersistenceExceptionTra
private boolean isSeqNoConflict(Exception exception) {
Integer status = null;
String message = null;
if (exception instanceof ElasticsearchStatusException) {
ElasticsearchStatusException statusException = (ElasticsearchStatusException) exception;
status = statusException.status().getStatus();
message = statusException.getMessage();
}
return statusException.status() == RestStatus.CONFLICT && statusException.getMessage() != null
&& statusException.getMessage().contains("type=version_conflict_engine_exception")
&& statusException.getMessage().contains("version conflict, required seqNo");
if (exception instanceof RestStatusException) {
RestStatusException statusException = (RestStatusException) exception;
status = statusException.getStatus();
message = statusException.getMessage();
}
if (status != null && message != null) {
return status == 409 && message.contains("type=version_conflict_engine_exception")
&& message.contains("version conflict, required seqNo");
}
if (exception instanceof VersionConflictEngineException) {
@@ -1,5 +1,5 @@
/*
* Copyright 2013-2021 the original author or authors.
* Copyright 2013-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2013-2021 the original author or authors.
* Copyright 2013-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,10 +16,21 @@
package org.springframework.data.elasticsearch.core;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.elasticsearch.Version;
import org.elasticsearch.action.DocWriteResponse;
import org.elasticsearch.action.bulk.BulkItemResponse;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.get.GetRequest;
import org.elasticsearch.action.get.GetResponse;
@@ -33,18 +44,21 @@ import org.elasticsearch.action.search.MultiSearchResponse;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.search.SearchScrollRequest;
import org.elasticsearch.action.support.WriteRequest;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.core.TimeValue;
import org.elasticsearch.index.query.MoreLikeThisQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.reindex.BulkByScrollResponse;
import org.elasticsearch.index.reindex.DeleteByQueryRequest;
import org.elasticsearch.index.reindex.UpdateByQueryRequest;
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.BulkFailureException;
import org.springframework.data.elasticsearch.core.cluster.ClusterOperations;
import org.springframework.data.elasticsearch.core.cluster.ElasticsearchClusterOperations;
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
import org.springframework.data.elasticsearch.core.document.DocumentAdapters;
import org.springframework.data.elasticsearch.core.document.SearchDocumentResponse;
@@ -52,10 +66,11 @@ import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.BulkOptions;
import org.springframework.data.elasticsearch.core.query.ByQueryResponse;
import org.springframework.data.elasticsearch.core.query.IndexQuery;
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.UpdateQuery;
import org.springframework.data.elasticsearch.core.query.UpdateResponse;
import org.springframework.data.elasticsearch.support.SearchHitsUtil;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@@ -91,10 +106,11 @@ import org.springframework.util.Assert;
* @author Gyula Attila Csorogi
* @author Massimiliano Poggi
* @author Farid Faoudi
* @since 4.4
*/
public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate {
private static final Logger LOGGER = LoggerFactory.getLogger(ElasticsearchRestTemplate.class);
private static final Log LOGGER = LogFactory.getLog(ElasticsearchRestTemplate.class);
private final RestHighLevelClient client;
private final ElasticsearchExceptionTranslator exceptionTranslator = new ElasticsearchExceptionTranslator();
@@ -105,24 +121,21 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate {
Assert.notNull(client, "Client must not be null!");
this.client = client;
initialize(createElasticsearchConverter());
}
public ElasticsearchRestTemplate(RestHighLevelClient client, ElasticsearchConverter elasticsearchConverter) {
super(elasticsearchConverter);
Assert.notNull(client, "Client must not be null!");
this.client = client;
initialize(elasticsearchConverter);
}
@Override
protected AbstractElasticsearchTemplate doCopy() {
return new ElasticsearchRestTemplate(client, elasticsearchConverter);
}
// endregion
// region IndexOperations
@@ -146,7 +159,7 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate {
// region ClusterOperations
@Override
public ClusterOperations cluster() {
return ClusterOperations.forTemplate(this);
return ElasticsearchClusterOperations.forTemplate(this);
}
// endregion
@@ -181,7 +194,6 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate {
public <T> List<MultiGetItem<T>> multiGet(Query query, Class<T> clazz, IndexCoordinates index) {
Assert.notNull(index, "index must not be null");
Assert.notEmpty(query.getIds(), "No Id defined for Query");
MultiGetRequest request = requestFactory.multiGetRequest(query, clazz, index);
MultiGetResponse result = execute(client -> client.mget(request, RequestOptions.DEFAULT));
@@ -223,7 +235,8 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate {
@Override
public ByQueryResponse delete(Query query, Class<?> clazz, IndexCoordinates index) {
DeleteByQueryRequest deleteByQueryRequest = requestFactory.deleteByQueryRequest(query, clazz, index);
return ByQueryResponse.of(execute(client -> client.deleteByQuery(deleteByQueryRequest, RequestOptions.DEFAULT)));
return ResponseConverter
.byQueryResponseOf(execute(client -> client.deleteByQuery(deleteByQueryRequest, RequestOptions.DEFAULT)));
}
@Override
@@ -261,7 +274,7 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate {
final BulkByScrollResponse bulkByScrollResponse = execute(
client -> client.updateByQuery(updateByQueryRequest, RequestOptions.DEFAULT));
return ByQueryResponse.of(bulkByScrollResponse);
return ResponseConverter.byQueryResponseOf(bulkByScrollResponse);
}
public List<IndexedObjectInformation> doBulkOperation(List<?> queries, BulkOptions bulkOptions,
@@ -272,6 +285,57 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate {
updateIndexedObjectsWithQueries(queries, indexedObjectInformationList);
return indexedObjectInformationList;
}
/**
* Preprocess the write request before it is sent to the server, e.g. by setting the
* {@link WriteRequest#setRefreshPolicy(String) refresh policy} if applicable.
*
* @param request must not be {@literal null}.
* @param <R> the request type
* @return the processed {@link WriteRequest}.
*/
protected <R extends WriteRequest<R>> R prepareWriteRequest(R request) {
if (refreshPolicy == null) {
return request;
}
return request.setRefreshPolicy(RequestFactory.toElasticsearchRefreshPolicy(refreshPolicy));
}
/**
* extract the list of {@link IndexedObjectInformation} from a {@link BulkResponse}.
*
* @param bulkResponse the response to evaluate
* @return the list of the {@link IndexedObjectInformation}s
*/
protected List<IndexedObjectInformation> checkForBulkOperationFailure(BulkResponse bulkResponse) {
if (bulkResponse.hasFailures()) {
Map<String, String> failedDocuments = new HashMap<>();
for (BulkItemResponse item : bulkResponse.getItems()) {
if (item.isFailed())
failedDocuments.put(item.getId(), item.getFailureMessage());
}
throw new BulkFailureException(
"Bulk operation has failures. Use BulkFailureException.getFailedDocuments() for detailed messages ["
+ failedDocuments + ']',
failedDocuments);
}
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());
}
// endregion
// region SearchOperations
@@ -297,8 +361,17 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate {
SearchRequest searchRequest = requestFactory.searchRequest(query, clazz, index);
SearchResponse response = execute(client -> client.search(searchRequest, RequestOptions.DEFAULT));
ReadDocumentCallback<T> documentCallback = new ReadDocumentCallback<>(elasticsearchConverter, clazz, index);
SearchDocumentResponseCallback<SearchHits<T>> callback = new ReadSearchDocumentResponseCallback<>(clazz, index);
return callback.doWith(SearchDocumentResponse.from(response));
return callback.doWith(SearchDocumentResponse.from(response, documentCallback::doWith));
}
protected <T> SearchHits<T> doSearch(MoreLikeThisQuery query, Class<T> clazz, IndexCoordinates index) {
MoreLikeThisQueryBuilder moreLikeThisQueryBuilder = requestFactory.moreLikeThisQueryBuilder(query, index);
return search(
new NativeSearchQueryBuilder().withQuery(moreLikeThisQueryBuilder).withPageable(query.getPageable()).build(),
clazz, index);
}
@Override
@@ -312,9 +385,10 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate {
SearchResponse response = execute(client -> client.search(searchRequest, RequestOptions.DEFAULT));
ReadDocumentCallback<T> documentCallback = new ReadDocumentCallback<>(elasticsearchConverter, clazz, index);
SearchDocumentResponseCallback<SearchScrollHits<T>> callback = new ReadSearchScrollDocumentResponseCallback<>(clazz,
index);
return callback.doWith(SearchDocumentResponse.from(response));
return callback.doWith(SearchDocumentResponse.from(response, documentCallback::doWith));
}
@Override
@@ -326,9 +400,10 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate {
SearchResponse response = execute(client -> client.scroll(request, RequestOptions.DEFAULT));
SearchDocumentResponseCallback<SearchScrollHits<T>> callback = //
new ReadSearchScrollDocumentResponseCallback<>(clazz, index);
return callback.doWith(SearchDocumentResponse.from(response));
ReadDocumentCallback<T> documentCallback = new ReadDocumentCallback<>(elasticsearchConverter, clazz, index);
SearchDocumentResponseCallback<SearchScrollHits<T>> callback = new ReadSearchScrollDocumentResponseCallback<>(clazz,
index);
return callback.doWith(SearchDocumentResponse.from(response, documentCallback::doWith));
}
@Override
@@ -338,7 +413,7 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate {
request.scrollIds(scrollIds);
execute(client -> client.clearScroll(request, RequestOptions.DEFAULT));
} catch (Exception e) {
LOGGER.warn("Could not clear scroll: {}", e.getMessage());
LOGGER.warn(String.format("Could not clear scroll: %s", e.getMessage()));
}
}
@@ -349,8 +424,91 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate {
}
@Override
public <T> List<SearchHits<T>> multiSearch(List<? extends Query> queries, Class<T> clazz, IndexCoordinates index) {
MultiSearchRequest request = new MultiSearchRequest();
for (Query query : queries) {
request.add(requestFactory.searchRequest(query, clazz, index));
}
MultiSearchResponse.Item[] items = getMultiSearchResult(request);
ReadDocumentCallback<T> documentCallback = new ReadDocumentCallback<>(elasticsearchConverter, clazz, index);
SearchDocumentResponseCallback<SearchHits<T>> callback = new ReadSearchDocumentResponseCallback<>(clazz, index);
List<SearchHits<T>> res = new ArrayList<>(queries.size());
for (int i = 0; i < queries.size(); i++) {
res.add(callback.doWith(SearchDocumentResponse.from(items[i].getResponse(), documentCallback::doWith)));
}
return res;
}
@SuppressWarnings({ "unchecked", "rawtypes" })
@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());
Iterator<Class<?>> it1 = classes.iterator();
for (int i = 0; i < queries.size(); i++) {
Class entityClass = it1.next();
IndexCoordinates index = getIndexCoordinatesFor(entityClass);
ReadDocumentCallback<?> documentCallback = new ReadDocumentCallback<>(elasticsearchConverter, entityClass, index);
SearchDocumentResponseCallback<SearchHits<?>> callback = new ReadSearchDocumentResponseCallback<>(entityClass,
index);
SearchResponse response = items[i].getResponse();
res.add(callback.doWith(SearchDocumentResponse.from(response, documentCallback::doWith)));
}
return res;
}
@SuppressWarnings({ "unchecked", "rawtypes" })
@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) {
request.add(requestFactory.searchRequest(query, it.next(), index));
}
MultiSearchResponse.Item[] items = getMultiSearchResult(request);
List<SearchHits<?>> res = new ArrayList<>(queries.size());
Iterator<Class<?>> it1 = classes.iterator();
for (int i = 0; i < queries.size(); i++) {
Class entityClass = it1.next();
ReadDocumentCallback<?> documentCallback = new ReadDocumentCallback<>(elasticsearchConverter, entityClass, index);
SearchDocumentResponseCallback<SearchHits<?>> callback = new ReadSearchDocumentResponseCallback<>(entityClass,
index);
SearchResponse response = items[i].getResponse();
res.add(callback.doWith(SearchDocumentResponse.from(response, documentCallback::doWith)));
}
return res;
}
protected MultiSearchResponse.Item[] getMultiSearchResult(MultiSearchRequest request) {
MultiSearchResponse response = execute(client -> client.multiSearch(request, RequestOptions.DEFAULT));
MultiSearchResponse response = execute(client -> client.msearch(request, RequestOptions.DEFAULT));
MultiSearchResponse.Item[] items = response.getResponses();
Assert.isTrue(items.length == request.requests().size(), "Response should has same length with queries");
return items;
@@ -416,5 +574,36 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate {
} catch (Exception ignored) {}
return null;
}
@Override
public Query matchAllQuery() {
return new NativeSearchQueryBuilder().withQuery(QueryBuilders.matchAllQuery()).build();
}
@Override
public Query idsQuery(List<String> ids) {
Assert.notNull(ids, "ids must not be null");
return new NativeSearchQueryBuilder().withQuery(QueryBuilders.idsQuery().addIds(ids.toArray(new String[] {})))
.build();
}
@Override
protected String getVendor() {
return "Elasticsearch";
}
@Override
protected String getRuntimeLibraryVersion() {
return Version.CURRENT.toString();
}
@Override
@Deprecated
public SearchResponse suggest(SuggestBuilder suggestion, Class<?> clazz) {
return suggest(suggestion, getIndexCoordinatesFor(clazz));
}
// endregion
}
@@ -1,450 +0,0 @@
/*
* Copyright 2013-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.core;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import org.elasticsearch.action.ActionFuture;
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;
import org.elasticsearch.action.index.IndexRequestBuilder;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.MultiSearchRequest;
import org.elasticsearch.action.search.MultiSearchResponse;
import org.elasticsearch.action.search.SearchRequestBuilder;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.update.UpdateRequestBuilder;
import org.elasticsearch.client.Client;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.index.reindex.BulkByScrollResponse;
import org.elasticsearch.index.reindex.UpdateByQueryRequestBuilder;
import org.elasticsearch.search.suggest.SuggestBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.elasticsearch.core.cluster.ClusterOperations;
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
import org.springframework.data.elasticsearch.core.document.DocumentAdapters;
import org.springframework.data.elasticsearch.core.document.SearchDocumentResponse;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.BulkOptions;
import org.springframework.data.elasticsearch.core.query.ByQueryResponse;
import org.springframework.data.elasticsearch.core.query.IndexQuery;
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.data.elasticsearch.support.SearchHitsUtil;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* ElasticsearchTemplate
*
* @author Rizwan Idrees
* @author Mohsin Husen
* @author Artur Konczak
* @author Kevin Leturc
* @author Mason Chan
* @author Young Gu
* @author Oliver Gierke
* @author Mark Janssen
* @author Chris White
* @author Mark Paluch
* @author Ilkang Na
* @author Alen Turkovic
* @author Sascha Woo
* @author Ted Liang
* @author Jean-Baptiste Nizet
* @author Zetang Zeng
* @author Ivan Greene
* @author Christoph Strobl
* @author Dmitriy Yakovlev
* @author Peter-Josef Meisch
* @author Martin Choraine
* @author Farid Azaza
* @author Gyula Attila Csorogi
* @author Roman Puchkovskiy
* @author Farid Faoudi
* @deprecated as of 4.0
*/
@Deprecated
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;
private final ElasticsearchExceptionTranslator exceptionTranslator = new ElasticsearchExceptionTranslator();
// region Initialization
public ElasticsearchTemplate(Client client) {
this.client = client;
initialize(client, createElasticsearchConverter());
}
public ElasticsearchTemplate(Client client, ElasticsearchConverter elasticsearchConverter) {
this.client = client;
initialize(client, elasticsearchConverter);
}
private void initialize(Client client, ElasticsearchConverter elasticsearchConverter) {
Assert.notNull(client, "Client must not be null!");
this.client = client;
initialize(elasticsearchConverter);
}
@Override
protected AbstractElasticsearchTemplate doCopy() {
ElasticsearchTemplate elasticsearchTemplate = new ElasticsearchTemplate(client, elasticsearchConverter);
elasticsearchTemplate.setSearchTimeout(searchTimeout);
return elasticsearchTemplate;
}
// endregion
// region IndexOperations
@Override
public IndexOperations indexOps(Class<?> clazz) {
Assert.notNull(clazz, "clazz must not be null");
return new TransportIndexTemplate(client, elasticsearchConverter, clazz);
}
@Override
public IndexOperations indexOps(IndexCoordinates index) {
Assert.notNull(index, "index must not be null");
return new TransportIndexTemplate(client, elasticsearchConverter, index);
}
// endregion
// region ClusterOperations
@Override
public ClusterOperations cluster() {
return ClusterOperations.forTemplate(this);
}
// endregion
// region getter/setter
@Nullable
public String getSearchTimeout() {
return searchTimeout;
}
public void setSearchTimeout(String searchTimeout) {
this.searchTimeout = searchTimeout;
}
// endregion
// region DocumentOperations
public String doIndex(IndexQuery query, IndexCoordinates index) {
IndexRequestBuilder indexRequestBuilder = requestFactory.indexRequestBuilder(client, query, index);
indexRequestBuilder = prepareWriteRequestBuilder(indexRequestBuilder);
ActionFuture<IndexResponse> future = indexRequestBuilder.execute();
IndexResponse response;
try {
response = future.actionGet();
} catch (RuntimeException e) {
throw translateException(e);
}
String documentId = response.getId();
Object queryObject = query.getObject();
if (queryObject != null) {
query.setObject(updateIndexedObject(queryObject, IndexedObjectInformation.of(documentId, response.getSeqNo(),
response.getPrimaryTerm(), response.getVersion())));
}
return documentId;
}
@Override
@Nullable
public <T> T get(String id, Class<T> clazz, IndexCoordinates index) {
GetRequestBuilder getRequestBuilder = requestFactory.getRequestBuilder(client, id, routingResolver.getRouting(),
index);
GetResponse response = getRequestBuilder.execute().actionGet();
DocumentCallback<T> callback = new ReadDocumentCallback<>(elasticsearchConverter, clazz, index);
return callback.doWith(DocumentAdapters.from(response));
}
@Override
public <T> List<MultiGetItem<T>> multiGet(Query query, Class<T> clazz, IndexCoordinates index) {
Assert.notNull(index, "index must not be null");
Assert.notEmpty(query.getIds(), "No Ids defined for Query");
MultiGetRequestBuilder builder = requestFactory.multiGetRequestBuilder(client, query, clazz, index);
DocumentCallback<T> callback = new ReadDocumentCallback<>(elasticsearchConverter, clazz, index);
return DocumentAdapters.from(builder.execute().actionGet()).stream() //
.map(multiGetItem -> MultiGetItem.of(multiGetItem.isFailed() ? null : callback.doWith(multiGetItem.getItem()),
multiGetItem.getFailure()))
.collect(Collectors.toList());
}
@Override
protected boolean doExists(String id, IndexCoordinates index) {
GetRequestBuilder getRequestBuilder = requestFactory.getRequestBuilder(client, id, routingResolver.getRouting(),
index);
getRequestBuilder.setFetchSource(false);
return getRequestBuilder.execute().actionGet().isExists();
}
@Override
public void bulkUpdate(List<UpdateQuery> queries, BulkOptions bulkOptions, IndexCoordinates index) {
Assert.notNull(queries, "List of UpdateQuery must not be null");
Assert.notNull(bulkOptions, "BulkOptions must not be null");
doBulkOperation(queries, bulkOptions, index);
}
@Override
protected String doDelete(String id, @Nullable String routing, IndexCoordinates index) {
Assert.notNull(id, "id must not be null");
Assert.notNull(index, "index must not be null");
DeleteRequestBuilder deleteRequestBuilder = prepareWriteRequestBuilder(
requestFactory.deleteRequestBuilder(client, elasticsearchConverter.convertId(id), routing, index));
return deleteRequestBuilder.execute().actionGet().getId();
}
@Override
public ByQueryResponse delete(Query query, Class<?> clazz, IndexCoordinates index) {
return ByQueryResponse.of(requestFactory.deleteByQueryRequestBuilder(client, query, clazz, index).get());
}
@Override
public String delete(Object entity, IndexCoordinates index) {
return super.delete(entity, index);
}
@Override
public UpdateResponse update(UpdateQuery query, IndexCoordinates index) {
UpdateRequestBuilder updateRequestBuilder = requestFactory.updateRequestBuilderFor(client, query, index);
if (query.getRefreshPolicy() == null && getRefreshPolicy() != null) {
updateRequestBuilder.setRefreshPolicy(RequestFactory.toElasticsearchRefreshPolicy(getRefreshPolicy()));
}
if (query.getRouting() == null && routingResolver.getRouting() != null) {
updateRequestBuilder.setRouting(routingResolver.getRouting());
}
org.elasticsearch.action.update.UpdateResponse updateResponse = updateRequestBuilder.execute().actionGet();
UpdateResponse.Result result = UpdateResponse.Result.valueOf(updateResponse.getResult().name());
return new UpdateResponse(result);
}
@Override
public ByQueryResponse updateByQuery(UpdateQuery query, IndexCoordinates index) {
Assert.notNull(query, "query must not be null");
Assert.notNull(index, "index must not be null");
final UpdateByQueryRequestBuilder updateByQueryRequestBuilder = requestFactory.updateByQueryRequestBuilder(client,
query, index);
if (query.getRefreshPolicy() == null && getRefreshPolicy() != null) {
updateByQueryRequestBuilder.refresh(getRefreshPolicy() == RefreshPolicy.IMMEDIATE);
}
// UpdateByQueryRequestBuilder has not parameters to set a routing value
final BulkByScrollResponse bulkByScrollResponse = updateByQueryRequestBuilder.execute().actionGet();
return ByQueryResponse.of(bulkByScrollResponse);
}
public List<IndexedObjectInformation> doBulkOperation(List<?> queries, BulkOptions bulkOptions,
IndexCoordinates index) {
// do it in batches; test code on some machines kills the transport node when the size gets too much
Collection<? extends List<?>> queryLists = partitionBasedOnSize(queries, 2500);
List<IndexedObjectInformation> allIndexedObjectInformations = new ArrayList<>(queries.size());
queryLists.forEach(queryList -> {
BulkRequestBuilder bulkRequestBuilder = requestFactory.bulkRequestBuilder(client, queryList, bulkOptions, index);
bulkRequestBuilder = prepareWriteRequestBuilder(bulkRequestBuilder);
final List<IndexedObjectInformation> indexedObjectInformations = checkForBulkOperationFailure(
bulkRequestBuilder.execute().actionGet());
updateIndexedObjectsWithQueries(queryList, indexedObjectInformations);
allIndexedObjectInformations.addAll(indexedObjectInformations);
});
return allIndexedObjectInformations;
}
// endregion
// region SearchOperations
@Override
public long count(Query query, @Nullable Class<?> clazz, IndexCoordinates index) {
Assert.notNull(query, "query must not be null");
Assert.notNull(index, "index must not be null");
final Boolean trackTotalHits = query.getTrackTotalHits();
query.setTrackTotalHits(true);
SearchRequestBuilder searchRequestBuilder = requestFactory.searchRequestBuilder(client, query, clazz, index);
query.setTrackTotalHits(trackTotalHits);
searchRequestBuilder.setSize(0);
return SearchHitsUtil.getTotalCount(getSearchResponse(searchRequestBuilder).getHits());
}
@Override
public <T> SearchHits<T> search(Query query, Class<T> clazz, IndexCoordinates index) {
SearchRequestBuilder searchRequestBuilder = requestFactory.searchRequestBuilder(client, query, clazz, index);
SearchResponse response = getSearchResponse(searchRequestBuilder);
SearchDocumentResponseCallback<SearchHits<T>> callback = new ReadSearchDocumentResponseCallback<>(clazz, index);
return callback.doWith(SearchDocumentResponse.from(response));
}
@Override
public <T> SearchScrollHits<T> searchScrollStart(long scrollTimeInMillis, Query query, Class<T> clazz,
IndexCoordinates index) {
Assert.notNull(query.getPageable(), "pageable of query must not be null.");
ActionFuture<SearchResponse> action = requestFactory.searchRequestBuilder(client, query, clazz, index) //
.setScroll(TimeValue.timeValueMillis(scrollTimeInMillis)) //
.execute();
SearchResponse response = getSearchResponseWithTimeout(action);
SearchDocumentResponseCallback<SearchScrollHits<T>> callback = new ReadSearchScrollDocumentResponseCallback<>(clazz,
index);
return callback.doWith(SearchDocumentResponse.from(response));
}
@Override
public <T> SearchScrollHits<T> searchScrollContinue(@Nullable String scrollId, long scrollTimeInMillis,
Class<T> clazz, IndexCoordinates index) {
ActionFuture<SearchResponse> action = client //
.prepareSearchScroll(scrollId) //
.setScroll(TimeValue.timeValueMillis(scrollTimeInMillis)) //
.execute();
SearchResponse response = getSearchResponseWithTimeout(action);
SearchDocumentResponseCallback<SearchScrollHits<T>> callback = new ReadSearchScrollDocumentResponseCallback<>(clazz,
index);
return callback.doWith(SearchDocumentResponse.from(response));
}
@Override
public void searchScrollClear(List<String> scrollIds) {
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) {
SearchRequestBuilder searchRequestBuilder = requestFactory.searchRequestBuilder(client, suggestion, index);
return searchRequestBuilder.get();
}
@Override
protected MultiSearchResponse.Item[] getMultiSearchResult(MultiSearchRequest request) {
ActionFuture<MultiSearchResponse> future = client.multiSearch(request);
MultiSearchResponse response = future.actionGet();
MultiSearchResponse.Item[] items = response.getResponses();
Assert.isTrue(items.length == request.requests().size(), "Response should have same length with queries");
return items;
}
private SearchResponse getSearchResponse(SearchRequestBuilder requestBuilder) {
if (QUERY_LOGGER.isDebugEnabled()) {
QUERY_LOGGER.debug(requestBuilder.toString());
}
return getSearchResponseWithTimeout(requestBuilder.execute());
}
private SearchResponse getSearchResponseWithTimeout(ActionFuture<SearchResponse> response) {
return searchTimeout == null ? response.actionGet() : response.actionGet(searchTimeout);
}
// endregion
// region helper methods
@Override
protected String getClusterVersion() {
try {
NodesInfoResponse nodesInfoResponse = client.admin().cluster()
.nodesInfo(new NodesInfoRequestBuilder(client, NodesInfoAction.INSTANCE).request()).actionGet();
if (!nodesInfoResponse.getNodes().isEmpty()) {
return nodesInfoResponse.getNodes().get(0).getVersion().toString();
}
} catch (Exception ignored) {}
return null;
}
public Client getClient() {
return client;
}
<T> Collection<List<T>> partitionBasedOnSize(List<T> inputList, int size) {
final AtomicInteger counter = new AtomicInteger(0);
return inputList.stream().collect(Collectors.groupingBy(s -> counter.getAndIncrement() / size)).values();
}
// endregion
/**
* translates an Exception if possible. Exceptions that are no {@link RuntimeException}s are wrapped in a
* RuntimeException
*
* @param exception the Exception to map
* @return the potentially translated RuntimeException.
* @since 4.0
*/
private RuntimeException translateException(Exception exception) {
RuntimeException runtimeException = exception instanceof RuntimeException ? (RuntimeException) exception
: new RuntimeException(exception.getMessage(), exception);
RuntimeException potentiallyTranslatedException = exceptionTranslator
.translateExceptionIfPossible(runtimeException);
return potentiallyTranslatedException != null ? potentiallyTranslatedException : runtimeException;
}
}

Some files were not shown because too many files have changed in this diff Show More