1
0
mirror of synced 2026-07-07 10:40:02 +00:00

Compare commits

..

76 Commits

Author SHA1 Message Date
Mark Paluch bd898f0363 DATAES-770 - Release version 3.2.7 (Moore SR7). 2020-04-28 14:35:55 +02:00
Mark Paluch b1a6dc524c DATAES-770 - Prepare 3.2.7 (Moore SR7). 2020-04-28 14:35:25 +02:00
Mark Paluch 197956c4d4 DATAES-770 - Updated changelog. 2020-04-28 14:35:23 +02:00
Mark Paluch bc0bead9b8 DATAES-755 - Updated changelog. 2020-04-28 11:59:02 +02:00
Peter-Josef Meisch 1459dd491d DATAES-778 - Fix SSL setup in the reactive client.
Original PR: #429

(cherry picked from commit 539c1ee6e7)
2020-04-16 21:47:10 +02:00
Peter-Josef Meisch 952e0c8985 DATAES-780 - Upgrade 3.2.x to Elasticsearch 6.8.8.
Original PR: #420
2020-04-04 16:14:45 +02:00
Mark Paluch f6caa731dd DATAES-762 - Updated changelog. 2020-03-31 15:08:50 +02:00
Mark Paluch b7dbdbf0e0 DATAES-756 - After release cleanups. 2020-03-25 10:58:27 +01:00
Mark Paluch 80a61e27c4 DATAES-756 - Prepare next development iteration. 2020-03-25 10:58:26 +01:00
Mark Paluch 0ce8e7ce0e DATAES-756 - Release version 3.2.6 (Moore SR6). 2020-03-25 10:46:02 +01:00
Mark Paluch fde9f42735 DATAES-756 - Prepare 3.2.6 (Moore SR6). 2020-03-25 10:45:35 +01:00
Mark Paluch 6b2ad6cdc6 DATAES-756 - Updated changelog. 2020-03-25 10:45:34 +01:00
Mark Paluch 7e7ceb885d DATAES-769 - Upgrade to Elasticsearch 6.8.7. 2020-03-23 16:45:41 +01:00
Peter-Josef Meisch 54c80f3375 DATAES-763 - Allow map properties in entity with null values. 2020-03-22 08:58:11 +01:00
Peter-Josef Meisch 39b318caa7 DATAES-765 - Pageable.unpaged() is not used to build a query returning all documents.
Original PR: #409
2020-03-20 19:07:58 +01:00
Peter-Josef Meisch 17af36702f DATAES-764 - Adapt to JUnit4. 2020-03-18 20:49:28 +01:00
Sascha Woo e22e2bbfc3 DATAES-764 - StreamQueries#streamResults does not clear scroll context when finished.
Original PR: #406

(cherry picked from commit f103bdb9d8)
2020-03-18 20:39:30 +01:00
Jens Schauder b11f8d2c38 DATAES-744 - Updated changelog. 2020-03-11 09:59:33 +01:00
Peter-Josef Meisch 6062896568 DATAES-758 - Fix documentation for @Query annotation.
Original PR: #402
2020-03-06 18:44:49 +01:00
Mark Paluch 33057cafb1 DATAES-730 - After release cleanups. 2020-02-26 11:54:09 +01:00
Mark Paluch 643b8b1e64 DATAES-730 - Prepare next development iteration. 2020-02-26 11:54:08 +01:00
Mark Paluch 8f5947bfc7 DATAES-730 - Release version 3.2.5 (Moore SR5). 2020-02-26 11:38:18 +01:00
Mark Paluch af78e0bf3d DATAES-730 - Prepare 3.2.5 (Moore SR5). 2020-02-26 11:37:58 +01:00
Mark Paluch e4f1c8ba58 DATAES-730 - Updated changelog. 2020-02-26 11:37:57 +01:00
Mark Paluch be4924a214 DATAES-729 - Updated changelog. 2020-02-26 11:31:49 +01:00
Mark Paluch 55119989f7 DATAES-752 - Upgrade to Elasticsearch 6.8.6. 2020-02-24 09:06:56 +01:00
Peter-Josef Meisch 068de487b2 DATAES-741 - Tests fail due to Elasticsearch cluster 'blocks' on nearly-full file-systems. 2020-02-15 16:32:58 +01:00
Peter-Josef Meisch bacad5ca0c DATAES-214 - Polishing. 2020-02-12 20:58:41 +01:00
Alexander Shabunevich 864d41cb01 DATAES-214 - ElasticsearchTemplate's prepareSearch(Query query) method should use getOffset().
Original PR: #392
2020-02-12 20:55:36 +01:00
Mark Paluch 8dd7cfcc6e DATAES-732 - Updated changelog. 2020-02-12 15:05:02 +01:00
Mark Paluch 868664aa78 DATAES-731 - Updated changelog. 2020-01-17 09:58:35 +01:00
Mark Paluch 5f9eab9a97 DATAES-663 - Updated changelog. 2020-01-16 16:12:38 +01:00
Mark Paluch 090ed0be5c DATAES-704 - After release cleanups. 2020-01-15 12:48:23 +01:00
Mark Paluch fc9e47ccd7 DATAES-704 - Prepare next development iteration. 2020-01-15 12:48:22 +01:00
Mark Paluch 4c12979bb0 DATAES-704 - Release version 3.2.4 (Moore SR4). 2020-01-15 12:37:44 +01:00
Mark Paluch 2c1613cac0 DATAES-704 - Prepare 3.2.4 (Moore SR4). 2020-01-15 12:37:18 +01:00
Mark Paluch 5a92340829 DATAES-704 - Updated changelog. 2020-01-15 12:37:16 +01:00
Mark Paluch e317bef992 DATAES-703 - Updated changelog. 2020-01-15 10:36:37 +01:00
Mark Paluch be7ae214a4 DATAES-725 - Update copyright years to 2020. 2020-01-07 09:08:12 +01:00
Peter-Josef Meisch 6eda05ddd7 DATAES-720 - SimpleReactiveElasticsearchRepository findAll() returns only 10 elements.
Original PR: #364

(cherry picked from commit b634f318ab)
2019-12-25 11:34:13 +01:00
Peter-Josef Meisch 283b27d170 DATAES-719 - Add customization hook for reactive WebClient.
Original PR: #363

(cherry picked from commit f7a14c1135)
2019-12-25 09:09:43 +01:00
Peter-Josef Meisch b731b47b1b DATAES-719 - Add customization hook for reactive WebClient.
Original PR: #363

(cherry picked from commit f7a14c1135)
2019-12-25 09:07:56 +01:00
Sascha Woo 31a391522a DATAES-702 - Travis CI builds currently broken.
Original PR: #351
2019-12-13 13:08:33 +01:00
Peter-Josef Meisch 0a0ac102cc DATAES-705_-_Add-support-for-PathPrefix-to-clients-in-3_2_x.
Original PR: #354
2019-12-07 15:42:09 +01:00
Jens Schauder 325fdb47c6 DATAES-692 - After release cleanups. 2019-12-04 14:29:17 +01:00
Jens Schauder fdee09dc04 DATAES-692 - Prepare next development iteration. 2019-12-04 14:29:15 +01:00
Jens Schauder 060cab76d5 DATAES-692 - Release version 3.2.3 (Moore SR3). 2019-12-04 14:12:24 +01:00
Jens Schauder afb8a35eac DATAES-692 - Prepare 3.2.3 (Moore SR3). 2019-12-04 14:11:44 +01:00
Jens Schauder 1c0dd71020 DATAES-692 - Updated changelog. 2019-12-04 14:11:42 +01:00
Jens Schauder 1633668d7f DATAES-691 - Updated changelog. 2019-12-04 12:09:51 +01:00
Peter-Josef Meisch 6756f792c8 DATAES-699 - Fix count implementation.
Original PR: #349
2019-12-01 10:03:56 +01:00
Peter-Josef Meisch bae4db8a7f DATAES-700 - Enable proxy support for RestClient. 2019-11-30 23:30:59 +01:00
Mark Paluch c47fd2cfce DATAES-685 - After release cleanups. 2019-11-18 12:42:05 +01:00
Mark Paluch cb08bb7196 DATAES-685 - Prepare next development iteration. 2019-11-18 12:42:04 +01:00
Mark Paluch f3500623ff DATAES-685 - Release version 3.2.2 (Moore SR2). 2019-11-18 12:32:12 +01:00
Mark Paluch 4c4cbed43b DATAES-685 - Prepare 3.2.2 (Moore SR2). 2019-11-18 12:31:53 +01:00
Mark Paluch 23fb5689b7 DATAES-685 - Updated changelog. 2019-11-18 12:31:52 +01:00
Mark Paluch b551466f94 DATAES-683 - Updated changelog. 2019-11-18 12:16:28 +01:00
Peter-Josef Meisch 0a7af69d69 DATAES-680 - Fix missing import. 2019-11-08 17:03:37 +01:00
Peter-Josef Meisch 2cf42a4763 DATAES-680 - ReactiveElasticsearchTemplate-should-use-the-count-API.
Original PR: #341

(cherry picked from commit 62385edaa5)
2019-11-07 17:07:39 +01:00
Peter-Josef Meisch 6f0d1ee9e7 DATAES-684 - Polishing.
(cherry picked from commit 24751972a8)
2019-11-05 10:41:21 +01:00
Henrique Amaral f82dd229d9 DATAES-684 Implement bulk request from reactive client
Original PR: #342

* DATAES-684 Implement bulk request from reactive client

* Update src/main/java/org/springframework/data/elasticsearch/client/reactive/ReactiveElasticsearchClient.java

Co-Authored-By: Peter-Josef Meisch <pj.meisch@sothawo.com>

* DATAES-684 Implement bulk request from reactive client

Added author

(cherry picked from commit 6ae424428c)
2019-11-05 10:39:40 +01:00
Christoph Strobl 3b833f6f63 DATAES-662 - After release cleanups. 2019-11-04 15:34:27 +01:00
Christoph Strobl 2517fd5c90 DATAES-662 - Prepare next development iteration. 2019-11-04 15:34:26 +01:00
Christoph Strobl 4e572679dd DATAES-662 - Release version 3.2.1 (Moore SR1). 2019-11-04 14:55:02 +01:00
Christoph Strobl de9c664d1e DATAES-662 - Prepare 3.2.1 (Moore SR1). 2019-11-04 14:54:16 +01:00
Christoph Strobl 8187c5362a DATAES-662 - Updated changelog. 2019-11-04 14:54:15 +01:00
Christoph Strobl a8eb260bbd DATAES-660 - Updated changelog. 2019-11-04 10:34:54 +01:00
Peter-Josef Meisch bb944f595f DATAES-679 - Upgrade_to_Elasticsearch_6.8.4.
Original PR: #339
2019-10-31 17:40:24 +01:00
Henrique Amaral 598626238b DATAES-673 Create a Ssl Rest Client using SslContext and HostnameVerifier 2019-10-23 19:22:34 +02:00
Gyula Csörögi a451f8dca4 DATAES-671 - Missing indicesOptions support for scrolling queries.
Original PR: #332
2019-10-16 09:00:44 +02:00
Peter-Josef Meisch 4fd070c332 DATAES-670 - Fix version compatibility matrix in documentation.
Original PR: #330
2019-10-12 10:42:27 +02:00
Mark Paluch 091413dd69 DATAES-665 - Fix plugin ordering to deploy Javadoc. 2019-10-01 08:39:10 +02:00
Greg Turnquist ff74425132 DATAES-625 - Create CI job. 2019-09-30 14:23:41 -05:00
Mark Paluch 6f4d1dcc57 DATAES-625 - After release cleanups. 2019-09-30 16:17:54 +02:00
Mark Paluch 9713e33fed DATAES-625 - Prepare next development iteration. 2019-09-30 16:17:53 +02:00
480 changed files with 15266 additions and 27921 deletions
+8
View File
@@ -0,0 +1,8 @@
dist: xenial
language: java
jdk:
- openjdk8
script: "mvn clean dependency:list test -Dsort -U -B"
Vendored
+8 -45
View File
@@ -3,7 +3,7 @@ pipeline {
triggers {
pollSCM 'H/10 * * * *'
upstream(upstreamProjects: "spring-data-commons/2.3.x", threshold: hudson.model.Result.SUCCESS)
upstream(upstreamProjects: "spring-data-commons/2.2.x", threshold: hudson.model.Result.SUCCESS)
}
options {
@@ -12,39 +12,18 @@ pipeline {
}
stages {
stage("test: baseline (jdk8)") {
stage("Test") {
when {
anyOf {
branch '4.0.x'
not { triggeredBy 'UpstreamCause' }
}
}
agent {
docker {
image 'adoptopenjdk/openjdk8:latest'
label 'data'
args '-v $HOME:/tmp/jenkins-home'
}
}
options { timeout(time: 30, unit: 'MINUTES') }
steps {
sh 'rm -rf ?'
sh 'MAVEN_OPTS="-Duser.name=jenkins -Duser.home=/tmp/jenkins-home" ./mvnw clean dependency:list test -Dsort -U -B'
}
}
stage("Test other configurations") {
when {
anyOf {
branch '4.0.x'
branch '3.2.x'
not { triggeredBy 'UpstreamCause' }
}
}
parallel {
stage("test: baseline (jdk11)") {
stage("test: baseline") {
agent {
docker {
image 'adoptopenjdk/openjdk11:latest'
image 'adoptopenjdk/openjdk8:latest'
label 'data'
args '-v $HOME:/tmp/jenkins-home'
}
@@ -52,31 +31,15 @@ pipeline {
options { timeout(time: 30, unit: 'MINUTES') }
steps {
sh 'rm -rf ?'
sh 'MAVEN_OPTS="-Duser.name=jenkins -Duser.home=/tmp/jenkins-home" ./mvnw -Pjava11 clean dependency:list test -Dsort -U -B'
}
}
stage("test: baseline (jdk12)") {
agent {
docker {
image 'adoptopenjdk/openjdk12:latest'
label 'data'
args '-v $HOME:/tmp/jenkins-home'
}
}
options { timeout(time: 30, unit: 'MINUTES') }
steps {
sh 'rm -rf ?'
sh 'MAVEN_OPTS="-Duser.name=jenkins -Duser.home=/tmp/jenkins-home" ./mvnw -Pjava11 clean dependency:list test -Dsort -U -B'
sh 'MAVEN_OPTS="-Duser.name=jenkins -Duser.home=/tmp/jenkins-home" ./mvnw clean dependency:list test -Dsort -U -B'
}
}
}
}
stage('Release to artifactory') {
when {
anyOf {
branch '4.0.x'
branch '3.2.x'
not { triggeredBy 'UpstreamCause' }
}
}
@@ -107,7 +70,7 @@ pipeline {
}
stage('Publish documentation') {
when {
branch '4.0.x'
branch '3.2.x'
}
agent {
docker {
+34 -39
View File
@@ -11,10 +11,10 @@ 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.
* `ElasticsearchTemplate` 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.
* Annotation based mapping metadata but extensible to support other metadata formats
* Automatic implementation of `Repository` interfaces including support for custom finder methods.
* CDI support for repositories
== Code of Conduct
@@ -53,56 +53,51 @@ public class MyService {
repository.save(person);
List<Person> lastNameResults = repository.findByLastname("Gierke");
List<Person> firstNameResults = repository.findByFirstnameLike("Oli");
List<Person> firstNameResults = repository.findByFirstnameLike("Oli*");
}
}
----
=== Using Transport Client
Using Node Client
NOTE: Usage of the TransportClient is deprecated as of version 4.0, use RestClient instead.
[source,java]
[source,xml]
----
@Configuration
public class TransportClientConfig extends ElasticsearchConfigurationSupport {
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:elasticsearch="http://www.springframework.org/schema/data/elasticsearch"
xsi:schemaLocation="http://www.springframework.org/schema/data/elasticsearch https://www.springframework.org/schema/data/elasticsearch/spring-elasticsearch.xsd
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
@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;
}
<elasticsearch:node-client id="client" local="true"/>
@Bean(name = { "elasticsearchOperations", "elasticsearchTemplate" })
public ElasticsearchTemplate elasticsearchTemplate() throws UnknownHostException {
return new ElasticsearchTemplate(elasticsearchClient());
}
}
<bean name="elasticsearchTemplate" class="org.springframework.data.elasticsearch.core.ElasticsearchTemplate">
<constructor-arg name="client" ref="client"/>
</bean>
</beans>
----
=== Using the RestClient
Using Transport Client
Provide a configuration like this:
[source,java]
[source,xml]
----
@Configuration
public class RestClientConfig extends AbstractElasticsearchConfiguration {
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:elasticsearch="http://www.springframework.org/schema/data/elasticsearch"
xsi:schemaLocation="http://www.springframework.org/schema/data/elasticsearch https://www.springframework.org/schema/data/elasticsearch/spring-elasticsearch.xsd
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
@Override
@Bean
public RestHighLevelClient elasticsearchClient() {
<elasticsearch:repositories base-package="com.xyz.acme"/>
final ClientConfiguration clientConfiguration = ClientConfiguration.builder()
.connectedTo("localhost:9200")
.build();
<elasticsearch:transport-client id="client" cluster-nodes="ip:9300,ip:9300" cluster-name="elasticsearch" />
return RestClients.create(clientConfiguration).rest();
}
}
<bean name="elasticsearchTemplate" class="org.springframework.data.elasticsearch.core.ElasticsearchTemplate">
<constructor-arg name="client" ref="client"/>
</bean>
</beans>
----
=== Maven configuration
@@ -195,7 +190,7 @@ You also need JDK 1.8.
If you want to build with the regular `mvn` command, you will need https://maven.apache.org/run-maven/index.html[Maven v3.5.0 or above].
_Also see link:CONTRIBUTING.adoc[CONTRIBUTING.adoc] if you wish to submit pull requests, and in particular please sign the https://cla.pivotal.io/sign/spring[Contributors Agreement] before submitting your first pull request._
_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 your first non-trivial change._
=== Building reference documentation
+7 -32
View File
@@ -3,14 +3,14 @@
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-elasticsearch</artifactId>
<version>4.0.3.RELEASE</version>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-elasticsearch</artifactId>
<version>3.2.7.RELEASE</version>
<parent>
<groupId>org.springframework.data.build</groupId>
<artifactId>spring-data-parent</artifactId>
<version>2.3.3.RELEASE</version>
<version>2.2.7.RELEASE</version>
</parent>
<name>Spring Data Elasticsearch</name>
@@ -19,9 +19,9 @@
<properties>
<commonslang>2.6</commonslang>
<elasticsearch>7.6.2</elasticsearch>
<elasticsearch>6.8.8</elasticsearch>
<log4j>2.9.1</log4j>
<springdata.commons>2.3.3.RELEASE</springdata.commons>
<springdata.commons>2.2.7.RELEASE</springdata.commons>
<netty>4.1.39.Final</netty>
<java-module-name>spring.data.elasticsearch</java-module-name>
</properties>
@@ -144,7 +144,6 @@
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>${jodatime}</version>
<optional>true</optional>
</dependency>
<!-- Elasticsearch -->
@@ -252,7 +251,7 @@
<dependency>
<groupId>com.github.tomakehurst</groupId>
<artifactId>wiremock-jre8</artifactId>
<version>2.26.3</version>
<version>2.25.1</version>
<scope>test</scope>
<exclusions>
<!-- these exclusions are needed because of Elasticsearch JarHell-->
@@ -282,33 +281,9 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>${mockito}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
<includes>
<include>**/versions.properties</include>
</includes>
</resource>
<resource>
<directory>src/main/resources</directory>
<filtering>false</filtering>
<excludes>
<exclude>**/versions.properties</exclude>
</excludes>
</resource>
</resources>
<plugins>
<!--
please do not remove this configuration for surefire - we need that to avoid issue with jar hell
+1 -11
View File
@@ -5,14 +5,13 @@ BioMed Central Development Team; Oliver Drotbohm; Greg Turnquist; Christoph Stro
ifdef::backend-epub3[:front-cover-image: image:epub-cover.png[Front Cover,1050,1600]]
:spring-data-commons-docs: ../../../../spring-data-commons/src/main/asciidoc
(C) 2013-2020 The original author(s).
(C) 2013-2019 The original author(s).
NOTE: Copies of this document may be made for your own use and for distribution to others, provided that you do not charge any fee for such copies and further provided that each copy contains this Copyright Notice, whether distributed in print or electronically.
toc::[]
include::preface.adoc[]
:leveloffset: +1
include::{spring-data-commons-docs}/repositories.adoc[]
:leveloffset: -1
@@ -24,15 +23,7 @@ include::{spring-data-commons-docs}/repositories.adoc[]
include::reference/elasticsearch-clients.adoc[]
include::reference/elasticsearch-object-mapping.adoc[]
include::reference/elasticsearch-operations.adoc[]
include::reference/elasticsearch-repositories.adoc[]
include::{spring-data-commons-docs}/auditing.adoc[]
include::reference/elasticsearch-auditing.adoc[]
include::{spring-data-commons-docs}/entity-callbacks.adoc[]
include::reference/elasticsearch-entity-callbacks.adoc[leveloffset=+1]
include::reference/elasticsearch-misc.adoc[]
:leveloffset: -1
@@ -44,5 +35,4 @@ include::{spring-data-commons-docs}/repository-namespace-reference.adoc[]
include::{spring-data-commons-docs}/repository-populator-namespace-reference.adoc[]
include::{spring-data-commons-docs}/repository-query-keywords-reference.adoc[]
include::{spring-data-commons-docs}/repository-query-return-types-reference.adoc[]
include::reference/migration-guides.adoc[]
:leveloffset: -1
+6 -7
View File
@@ -3,7 +3,7 @@
The Spring Data Elasticsearch project applies core Spring concepts to the development of solutions using the Elasticsearch Search Engine. It provides:
* _Templates_ as a high-level abstraction for storing, searching, sorting documents and building aggregations.
* _Templates_ as a high-level abstraction for storing, querying, sorting and faceting documents.
* _Repositories_ which for example enable the user to express queries by defining interfaces having customized method names (for basic information about repositories see <<repositories>>).
You will notice similarities to the Spring data solr and mongodb support in the Spring Framework.
@@ -34,12 +34,11 @@ Requires an installation of https://www.elastic.co/products/elasticsearch[Elasti
The following table shows the Elasticsearch versions that are used by Spring Data release trains and version of Spring Data Elasticsearch included in that, as well as the Spring Boot versions refering to that particular Spring Data release train:
[cols="^,^,^,^",options="header"]
|===
| Spring Data Release Train |Spring Data Elasticsearch |Elasticsearch | Spring Boot
| Neumannfootnote:cdv[Currently in development] |4.0.xfootnote:cdv[]|7.6.2 |2.3.xfootnote:cdv[]
| Moore | 3.2.x |6.8.6 | 2.2.x
| Lovelace | 3.1.x | 6.2.2 |2.1.x
| Kayfootnote:oom[Out of maintenance] | 3.0.xfootnote:oom[] | 5.5.0 | 2.0.xfootnote:oom[]
| Ingallsfootnote:oom[] | 2.1.xfootnote:oom[] | 2.4.0 | 1.5.xfootnote:oom[]
|Spring Data Release Train |Spring Data Elasticsearch |Elasticsearch | Spring Boot
|Moore |3.2.x |6.8.8 |2.2.x
|Lovelace |3.1.x |6.2.2|2.1.x
|Kayfootnote:oom[Out of maintenance]|3.0.xfootnote:oom[] |5.5.0 |2.0.xfootnote:oom[]
|Ingallsfootnote:oom[]|2.1.xfootnote:oom[] |2.4.0 |1.5.xfootnote:oom[]
|===
Support for upcoming versions of Elasticsearch is being tracked and general compatibility should be given assuming the usage of the <<elasticsearch.clients.rest,high-level REST client>>.
@@ -1,68 +0,0 @@
[[elasticsearch.auditing]]
== Elasticsearch Auditing
=== Preparing entities
In order for the auditing code to be able to decide wether an entity instance is new, the entity must implement the `Persistable<ID>` interface which is defined as follows:
[source,java]
----
package org.springframework.data.domain;
import org.springframework.lang.Nullable;
public interface Persistable<ID> {
@Nullable
ID getId();
boolean isNew();
}
----
As the existence of an Id is not a sufficient criterion to determine if an enitity is new in Elasticsearch, additional information is necessary. One way is to use the creation-relevant auditing fields for this decision:
A `Person` entity might look as follows - omitting getter and setter methods for brevity:
[source,java]
----
@Document(indexName = "person")
public class Person implements Persistable<Long> {
@Id private Long id;
private String lastName;
private String firstName;
@Field(type = FieldType.Date, format = DateFormat.basic_date_time)
private Instant createdDate;
private String createdBy
@Field(type = FieldType.Date, format = DateFormat.basic_date_time)
private Instant lastModifiedDate;
private String lastModifiedBy;
public Long getId() { <1>
return id;
}
@Override
public boolean isNew() {
return id == null || (createdDate == null && createdBy == null); <2>
}
}
----
<1> the getter also is the required implementation from the interface
<2> an object is new if it either has no `id` or none of fields containing creation attributes are set.
=== Activating auditing
After the entities have been set up and providing the `AuditorAware` the Auditing must be activated by setting the `@EnableElasticsearchAuditing` on a configuration class:
[source,java]
----
@Configuration
@EnableElasticsearchRepositories
@EnableElasticsearchAuditing
class MyConfiguration {
// configuration code
}
----
If your code contains more than one `AuditorAware` bean for different types, you must provide the name of the bean to use as an argument to the `auditorAwareRef` parameter of the
`@EnableElasticsearchAuditing` annotation.
@@ -3,13 +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>>.
Spring data Elasticsearch operates upon an Elasticsearch client that is connected to a single Elasticsearch node or a cluster. Although the Elasticsearch Client can be used to work with the cluster, applications using Spring Data Elasticsearch normally use the higher level abstractions of <<elasticsearch.operations>> and <<elasticsearch.repositories>>.
[[elasticsearch.clients.transport]]
== Transport Client
WARNING: The well known `TransportClient` is deprecated as of Elasticsearch 7 and will be removed in Elasticsearch 8. (https://www.elastic.co/guide/en/elasticsearch/client/java-api/current/transport-client.html[see the Elasticsearch documentation]). Spring Data Elasticsearch will support the `TransportClient` as long as it is available in the used
Elasticsearch <<elasticsearch.versions,version>> but has deprecated the classes using it since version 4.0.
Elasticsearch <<elasticsearch.versions,version>>.
We strongly recommend to use the <<elasticsearch.clients.rest>> instead of the `TransportClient`.
@@ -17,21 +17,18 @@ We strongly recommend to use the <<elasticsearch.clients.rest>> instead of the `
====
[source,java]
----
@Configuration
public class TransportClientConfig extends ElasticsearchConfigurationSupport {
static class Config {
@Bean
public Client elasticsearchClient() throws UnknownHostException {
Settings settings = Settings.builder().put("cluster.name", "elasticsearch").build(); <1>
TransportClient client = new PreBuiltTransportClient(settings);
client.addTransportAddress(new TransportAddress(InetAddress.getByName("127.0.0.1"), 9300)); <2>
return client;
}
@Bean(name = { "elasticsearchOperations", "elasticsearchTemplate" })
public ElasticsearchTemplate elasticsearchTemplate() throws UnknownHostException {
return new ElasticsearchTemplate(elasticsearchClient());
}
@Bean
Client client() {
Settings settings = Settings.builder()
.put("cluster.name", "elasticsearch") <1>
.build();
TransportClient client = new PreBuiltTransportClient(settings);
client.addTransportAddress(new TransportAddress(InetAddress.getByName("127.0.0.1")
, 9300)); <2>
return client;
}
}
// ...
@@ -49,7 +46,7 @@ IndexResponse response = client.index(request);
[[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 Java High Level REST Client now is the default client of Elasticsearch, it provides a straight forward replacement for the `TransportClient` as it accepts and returns
the very same request/response objects and therefore depends on the Elasticsearch core project.
Asynchronous calls are operated upon a client managed thread pool and require a callback to be notified when the request is done.
@@ -57,19 +54,18 @@ Asynchronous calls are operated upon a client managed thread pool and require a
====
[source,java]
----
@Configuration
public class RestClientConfig extends AbstractElasticsearchConfiguration {
import org.springframework.beans.factory.annotation.Autowired;@Configuration
static class Config {
@Override
@Bean
public RestHighLevelClient elasticsearchClient() {
@Bean
RestHighLevelClient client() {
final ClientConfiguration clientConfiguration = ClientConfiguration.builder() <1>
.connectedTo("localhost:9200")
.build();
ClientConfiguration clientConfiguration = ClientConfiguration.builder() <1>
.connectedTo("localhost:9200", "localhost:9201")
.build();
return RestClients.create(clientConfiguration).rest(); <2>
}
return RestClients.create(clientConfiguration).rest(); <2>
}
}
// ...
@@ -77,7 +73,7 @@ public class RestClientConfig extends AbstractElasticsearchConfiguration {
@Autowired
RestHighLevelClient highLevelClient;
RestClient lowLevelClient = highLevelClient.lowLevelClient(); <3>
RestClient lowLevelClient = highLevelClient.lowLevelClient(); <3>
// ...
@@ -143,47 +139,36 @@ NOTE: The ReactiveClient response, especially for search operations, is bound to
[[elasticsearch.clients.configuration]]
== Client Configuration
Client behaviour can be changed via the `ClientConfiguration` that allows to set options for SSL, connect and socket timeouts, headers and other parameters.
Client behaviour can be changed via the `ClientConfiguration` that allows to set options for SSL, connect and socket timeouts.
.Client Configuration
====
[source,java]
----
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add("some-header", "on every request") <1>
// optional if Basic Auhtentication is needed
HttpHeaders defaultHeaders = new HttpHeaders();
defaultHeaders.setBasicAuth(USER_NAME, USER_PASS); <1>
ClientConfiguration clientConfiguration = ClientConfiguration.builder()
.connectedTo("localhost:9200", "localhost:9291") <2>
.useSsl() <3>
.withProxy("localhost:8888") <4>
.withPathPrefix("ela") <5>
.withConnectTimeout(Duration.ofSeconds(5)) <6>
.withSocketTimeout(Duration.ofSeconds(3)) <7>
.withDefaultHeaders(defaultHeaders) <8>
.withBasicAuth(username, password) <9>
.withHeaders(() -> { <10>
HttpHeaders headers = new HttpHeaders();
headers.add("currentTime", LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
return headers;
})
.withConnectTimeout(Duration.ofSeconds(5)) <3>
.withSocketTimeout(Duration.ofSeconds(3)) <4>
.useSsl() <5>
.withDefaultHeaders(defaultHeaders) <6>
.withBasicAuth(username, password) <7>
. // ... other options
.build();
----
<1> Define default headers, if they need to be customized
<2> Use the builder to provide cluster addresses, set default `HttpHeaders` or enable SSL.
<3> Optionally enable SSL.
<4> Optionally set a proxy.
<5> Optionally set a path prefix, mostly used when different clusters a behind some reverse proxy.
<6> Set the connection timeout. Default is 10 sec.
<7> Set the socket timeout. Default is 5 sec.
<8> Optionally set headers.
<9> Add basic authentication.
<10> 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.
<3> Set the connection timeout. Default is 10 sec.
<4> Set the socket timeout. Default is 5 sec.
<5> Optionally enable SSL.
<6> Optionally set headers.
<7> Add basic authentication.
====
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
@@ -1,35 +0,0 @@
[[elasticsearch.entity-callbacks]]
= Elasticsearch EntityCallbacks
Spring Data Elasticsearch uses the `EntityCallback` API internally for its auditing support and reacts on the following callbacks:
.Supported Entity Callbacks
[%header,cols="4"]
|===
| Callback
| Method
| Description
| Order
| 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.
| `Ordered.LOWEST_PRECEDENCE`
| Reactive/AfterConvertCallback
| `onAfterConvert(T entity, Document document, IndexCoordinates indexCoordinates)`
| Invoked after a domain object is converted from `org.springframework.data.elasticsearch.core.document.Document` on reading result data from Elasticsearch.
| `Ordered.LOWEST_PRECEDENCE`
| Reactive/AuditingEntityCallback
| `onBeforeConvert(Object entity, IndexCoordinates index)`
| Marks an auditable entity _created_ or _modified_
| 100
| Reactive/AfterSaveCallback
| `T onAfterSave(T entity, IndexCoordinates index)`
| Invoked after a domain object is saved.
| `Ordered.LOWEST_PRECEDENCE`
|===
@@ -1,133 +0,0 @@
[[elasticsearch-migration-guide-3.2-4.0]]
= Upgrading from 3.2.x to 4.0.x
This section describes breaking changes from version 3.2.x to 4.0.x and how removed features can be replaced by new introduced features.
[[elasticsearch-migration-guide-3.2-4.0.jackson-removal]]
== Removal of the used Jackson Mapper
One of the changes in version 4.0.x is that Spring Data Elasticsearch does not use the Jackson Mapper anymore to map an entity to the JSON representation needed for Elasticsearch (see <<elasticsearch.mapping>>). In version 3.2.x the Jackson Mapper was the default that was used. It was possible to switch to the meta-model based converter (named `ElasticsearchEntityMapper`) by explicitly configuring it (<<elasticsearch.mapping.meta-model>>).
In version 4.0.x the meta-model based converter is the only one that is available and does not need to be configured explicitly. If you had a custom configuration to enable the meta-model converter by providing a bean like this:
[source,java]
----
@Bean
@Override
public EntityMapper entityMapper() {
ElasticsearchEntityMapper entityMapper = new ElasticsearchEntityMapper(
elasticsearchMappingContext(), new DefaultConversionService()
);
entityMapper.setConversions(elasticsearchCustomConversions());
return entityMapper;
}
----
You now have to remove this bean, the `ElasticsearchEntityMapper` interface has been removed.
.Entity configuration
Some users had custom Jackson annotations on the entity class, for example in order to define a custom name for the mapped document in Elasticsearch or to configure date conversions. These are not taken into account anymore. The needed functionality is now provided with Spring Data Elasticsearch's `@Field` annotation. Please see <<elasticsearch.mapping.meta-model.annotations>> for detailed information.
[[elasticsearch-migration-guide-3.2-4.0.implicit-index-name]]
== Removal of implicit index name from query objects
In 3.2.x the different query classes like `IndexQuery` or `SearchQuery` had properties that were taking the index name or index names that they were operating upon. If these were not set, the passed in entity was inspected to retrieve the index name that was set in the `@Document` annotation. +
In 4.0.x the index name(s) must now be provided in an additional parameter of type `IndexCoordinates`. By separating this, it now is possible to use one query object against different indices.
So for example the following code:
[source,java]
----
IndexQuery indexQuery = new IndexQueryBuilder()
.withId(person.getId().toString())
.withObject(person)
.build();
String documentId = elasticsearchOperations.index(indexQuery);
----
must be changed to:
[source,java]
----
IndexCoordinates indexCoordinates = elasticsearchOperations.getIndexCoordinatesFor(person.getClass());
IndexQuery indexQuery = new IndexQueryBuilder()
.withId(person.getId().toString())
.withObject(person)
.build();
String documentId = elasticsearchOperations.index(indexQuery, indexCoordinates);
----
To make it easier to work with entities and use the index name that is contained in the entitie's `@Document` annotation, new methods have been added like `DocumentOperations.save(T entity)`;
[[elasticsearch-migration-guide-3.2-4.0.new-operations]]
== The new Operations interfaces
In version 3.2 there was the `ElasticsearchOperations` interface that defined all the methods for the `ElasticsearchTemplate` class. In version 4 the functions have been split into different interfaces, aligning these interfaces with the Elasticsearch API:
* `DocumentOperations` are the functions related documents like saving, or deleting
* `SearchOperations` contains the functions to search in Elasticsearch
* `IndexOperations` define the functions to operate on indexes, like index creation or mappings creation.
`ElasticsearchOperations` now extends `DocumentOperations` and `SearchOperations` and has methods get access to an `IndexOperations` instance.
NOTE: All the functions from the `ElasticsearchOperations` interface in version 3.2 that are now moved to the `IndexOperations` interface are still available, they are marked as deprecated and have default implementations that delegate to the new implementation:
[source,java]
----
/**
* Create an index for given indexName.
*
* @param indexName the name of the index
* @return {@literal true} if the index was created
* @deprecated since 4.0, use {@link IndexOperations#create()}
*/
@Deprecated
default boolean createIndex(String indexName) {
return indexOps(IndexCoordinates.of(indexName)).create();
}
----
[[elasticsearch-migration-guide-3.2-4.0.deprecations]]
== Deprecations
=== Methods and classes
Many functions and classes have been deprecated. These functions still work, but the Javadocs show with what they should be replaced.
.Example from ElasticsearchOperations
[source,java]
----
/*
* Retrieves an object from an index.
*
* @param query the query defining the id of the object to get
* @param clazz the type of the object to be returned
* @return the found object
* @deprecated since 4.0, use {@link #get(String, Class, IndexCoordinates)}
*/
@Deprecated
@Nullable
<T> T queryForObject(GetQuery query, Class<T> clazz);
----
=== Elasticsearch deprecations
Since version 7 the Elasticsearch `TransportClient` is deprecated, it will be removed with Elasticsearch version 8. Spring Data Elasticsearch deprecates the `ElasticsearchTemplate` class which uses the `TransportClient` in version 4.0.
Mapping types were removed from Elasticsearch 7, they still exist as deprecated values in the Spring Data `@Document` annotation and the `IndexCoordinates` class but they are not used anymore internally.
[[elasticsearch-migration-guide-3.2-4.0.removal]]
== Removals
* As already described, the `ElasticsearchEntityMapper` interface has been removed.
* The `SearchQuery` interface has been merged into it's base interface `Query`, so it's occurrences can just be replaced with `Query`.
* The method `org.springframework.data.elasticsearch.core.ElasticsearchOperations.query(SearchQuery query, ResultsExtractor<T> resultsExtractor);` and the `org.springframework.data.elasticsearch.core.ResultsExtractor` interface have been removed. These could be used to parse the result from Elasticsearch for cases in which the response mapping done with the Jackson based mapper was not enough. Since version 4.0, there are the new <<elasticsearch.operations.searchresulttypes>> to return the information from an Elasticsearch response, so there is no need to expose this low level functionality.
* The low level methods `startScroll`, `continueScroll` and `clearScroll` have been removed from the `ElasticsearchOperations` interface. For low level scroll API access, there now are `searchScrollStart`, `searchScrollContinue` and `searchScrollClear` methods on the `ElasticsearchRestTemplate` class.
@@ -11,92 +11,67 @@ Filter Builder improves query speed.
====
[source,java]
----
private ElasticsearchOperations operations;
IndexCoordinates index = IndexCoordinates.of("sample-index");
private ElasticsearchTemplate elasticsearchTemplate;
SearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(matchAllQuery())
.withFilter(boolFilter().must(termFilter("id", documentId)))
.build();
Page<SampleEntity> sampleEntities = operations.searchForPage(searchQuery, SampleEntity.class, index);
Page<SampleEntity> sampleEntities =
elasticsearchTemplate.queryForPage(searchQuery,SampleEntity.class);
----
====
[[elasticsearch.scroll]]
== Using Scroll For Big Result Set
Elasticsearch has a scroll API for getting big result set in chunks. This is internally used by Spring Data Elasticsearch to provide the implementations of the `<T> SearchHitsIterator<T> SearchOperations.searchForStream(Query query, Class<T> clazz, IndexCoordinates index)` method.
Elasticsearch has a scroll API for getting big result set in chunks. `ElasticsearchTemplate` has startScroll and continueScroll methods that can be used as below.
.Using startScroll and continueScroll
====
[source,java]
----
IndexCoordinates index = IndexCoordinates.of("sample-index");
SearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(matchAllQuery())
.withIndices(INDEX_NAME)
.withTypes(TYPE_NAME)
.withFields("message")
.withPageable(PageRequest.of(0, 10))
.build();
SearchHitsIterator<SampleEntity> stream = elasticsearchTemplate.searchForStream(searchQuery, SampleEntity.class, index);
ScrolledPage<SampleEntity> scroll = elasticsearchTemplate.startScroll(1000, searchQuery, SampleEntity.class);
String scrollId = scroll.getScrollId();
List<SampleEntity> sampleEntities = new ArrayList<>();
while (scroll.hasContent()) {
sampleEntities.addAll(scroll.getContent());
scrollId = scroll.getScrollId();
scroll = elasticsearchTemplate.continueScroll(scrollId, 1000, SampleEntity.class);
}
elasticsearchTemplate.clearScroll(scrollId);
----
====
`ElasticsearchTemplate` additionally has the stream method which wraps the scan and scroll operations into a CloseableIterator.
.Using stream
====
[source,java]
----
SearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(matchAllQuery())
.withIndices(INDEX_NAME)
.withTypes(TYPE_NAME)
.withFields("message")
.withPageable(PageRequest.of(0, 10))
.build();
CloseableIterator<SampleEntity> stream = elasticsearchTemplate.stream(searchQuery, SampleEntity.class);
List<SampleEntity> sampleEntities = new ArrayList<>();
while (stream.hasNext()) {
sampleEntities.add(stream.next());
}
stream.close();
----
There are no methods in the `SearchOperations` API to access the scroll id, if it should be necessary to access this, the following methods of the `ElasticsearchRestTemplate` can be used:
[source,java]
----
@Autowired ElasticsearchRestTemplate template;
IndexCoordinates index = IndexCoordinates.of("sample-index");
SearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(matchAllQuery())
.withFields("message")
.withPageable(PageRequest.of(0, 10))
.build();
SearchScrollHits<SampleEntity> scroll = template.searchScrollStart(1000, searchQuery, SampleEntity.class, index);
String scrollId = scroll.getScrollId();
List<SampleEntity> sampleEntities = new ArrayList<>();
while (scroll.hasSearchHits()) {
sampleEntities.addAll(scroll.getSearchHits());
scrollId = scroll.getScrollId();
scroll = template.searchScrollContinue(scrollId, 1000, SampleEntity.class);
}
template.searchScrollClear(scrollId);
----
To use the Scroll API with repository methods, the return type must defined as `Stream` in the Elasticsearch Repository. The implementation of the method will then use the scroll methods from the ElasticsearchTemplate.
[source,java]
----
interface SampleEntityRepository extends Repository<SampleEntity, String> {
Stream<SampleEntity> findBy();
}
----
[[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.
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:
[source,java]
----
Sort.by(new GeoDistanceOrder("location", new GeoPoint(48.137154, 11.5761247)))
----
====
@@ -1,23 +1,6 @@
[[new-features]]
= What's new
[[new-features.4-0-0]]
== New in Spring Data Elasticsearch 4.0
* Uses Spring 5.2.
* Upgrade to Elasticsearch 7.6.2.
* Deprecation of `TransportClient` usage.
* Implements most of the mapping-types available for the index mappings.
* Removal of the Jackson `ObjectMapper`, now using the <<elasticsearch.mapping.meta-model,MappingElasticsearchConverter>>
* Cleanup of the API in the `*Operations` interfaces, grouping and renaming methods so that they match the Elasticsearch API, deprecating the old methods, aligning with other Spring Data modules.
* Introduction of `SearchHit<T>` class to represent a found document together with the relevant result metadata for this document (i.e. _sortValues_).
* Introduction of the `SearchHits<T>` class to represent a whole search result together with the
metadata for the complete search result (i.e. _max_score_).
* Introduction of `SearchPage<T>` class to represent a paged result containing a `SearchHits<T>` instance.
* Introduction of the `GeoDistanceOrder` class to be able to create sorting by geographical distance
* Implementation of Auditing Support
* Implementation of lifecycle entity callbacks
[[new-features.3-2-0]]
== New in Spring Data Elasticsearch 3.2
@@ -1,18 +1,36 @@
[[elasticsearch.mapping]]
= Elasticsearch Object Mapping
Spring Data Elasticsearch Object Mapping is the process that maps a Java object - the domain entity - into the JSON representation that is stored in Elasticsearch and back.
Spring Data Elasticsearch allows to choose between two mapping implementations abstracted via the `EntityMapper` interface:
Earlier versions of Spring Data Elasticsearch used a Jackson based conversion, Spring Data Elasticsearch 3.2.x introduced the <<elasticsearch.mapping.meta-model>>. As of version 4.0 only the Meta Object Mapping is used, the Jackson based mapper is not available anymore and the `MappingElasticsearchConverter` is used.
* <<elasticsearch.mapping.jackson2>>
* <<elasticsearch.mapping.meta-model>>
The main reasons for the removal of the Jackson based mapper are:
[[elasticsearch.mapping.jackson2]]
== Jackson Object Mapping
* Custom mappings of fields needed to be done with annotations like `@JsonFormat` or `@JsonInclude`. This often caused problems when the same object was used in different JSON based datastores or sent over a JSON based API.
* Custom field types and formats also need to be stored into the Elasticsearch index mappings. The Jackson based annotations did not fully provide all the information that is necessary to represent the types of Elasticsearch.
* Fields must be mapped not only when converting from and to entities, but also in query argument, returned data and on other places.
The Jackson2 based approach (used by default) utilizes a customized `ObjectMapper` instance with spring data specific modules.
Extensions to the actual mapping need to be customized via Jackson annotations like `@JsonInclude`.
Using the `MappingElasticsearchConverter` now covers all these cases.
.Jackson2 Object Mapping Configuration
====
[source,java]
----
@Configuration
public class Config extends AbstractElasticsearchConfiguration { <1>
@Override
public RestHighLevelClient elasticsearchClient() {
return RestClients.create(ClientConfiguration.create("localhost:9200")).rest();
}
}
----
<1> `AbstractElasticsearchConfiguration` already defines a Jackson2 based `entityMapper` via `ElasticsearchConfigurationSupport`.
====
[WARNING]
`CustomConversions`, `@ReadingConverter` & `@WritingConverter` cannot be applied when using the Jackson based `EntityMapper`. +
Setting the name of a mapped field with `@Field(name="custom-name")` also cannot be used with this Mapper.
[[elasticsearch.mapping.meta-model]]
== Meta Model Object Mapping
@@ -20,36 +38,63 @@ Using the `MappingElasticsearchConverter` now covers all these cases.
The Metamodel based approach uses domain type information for reading/writing from/to Elasticsearch.
This allows to register `Converter` instances for specific domain type mapping.
.Meta Model Object Mapping Configuration
====
[source,java]
----
@Configuration
public class Config extends AbstractElasticsearchConfiguration {
@Override
public RestHighLevelClient elasticsearchClient() {
return RestClients.create(ClientConfiguration.create("localhost:9200")).rest()
}
@Bean
@Override
public EntityMapper entityMapper() { <1>
ElasticsearchEntityMapper entityMapper = new ElasticsearchEntityMapper(
elasticsearchMappingContext(), new DefaultConversionService() <2>
);
entityMapper.setConversions(elasticsearchCustomConversions()); <3>
return entityMapper;
}
}
----
<1> Overwrite the default `EntityMapper` from `ElasticsearchConfigurationSupport` and expose it as bean.
<2> Use the provided `SimpleElasticsearchMappingContext` to avoid inconsistencies and provide a `GenericConversionService`
for `Converter` registration.
<3> Optionally set `CustomConversions` if applicable.
====
[[elasticsearch.mapping.meta-model.annotations]]
=== Mapping Annotation Overview
The `MappingElasticsearchConverter` uses metadata to drive the mapping of objects to documents. The metadata is taken from the entity's properties which can be annotated.
The following annotations are available:
The `ElasticsearchEntityMapper` can use metadata to drive the mapping of objects to documents. The following annotations are available:
* `@Id`: Applied at the field level to mark the field used for identity purpose.
* `@Document`: Applied at the class level to indicate this class is a candidate for mapping to the database. The most important attributes are:
** `indexName`: the name of the index to store this entity in
** `type`: [line-through]#the mapping type. If not set, the lowercased simple name of the class is used.# (deprecated since version 4.0)
** `type`: the mapping type. If not set, the lowercased simple name of the class is used.
** `shards`: the number of shards for the index.
** `replicas`: the number of replicas for the index.
** `refreshIntervall`: Refresh interval for the index. Used for index creation. Default value is _"1s"_.
** `indexStoreType`: Index storage type for the index. Used for index creation. Default value is _"fs"_.
** `createIndex`: Configuration whether to create an index on repository bootstrapping. Default value is _true_.
** `versionType`: Configuration of version management. Default value is _EXTERNAL_.
* `@Id`: Applied at the field level to mark the field used for identity purpose.
* `@Transient`: By default all fields are mapped to the document when it is stored or retrieved, this annotation excludes the field.
* `@Transient`: By default all private fields are mapped to the document, this annotation excludes the field where it is applied from being stored in the database
* `@PersistenceConstructor`: Marks a given constructor - even a package protected one - to use when instantiating the object from the database. Constructor arguments are mapped by name to the key values in the retrieved Document.
* `@Field`: Applied at the field level and defines properties of the field, most of the attributes map to the respective https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping.html[Elasticsearch Mapping] definitions (the following list is not complete, check the annotation Javadoc for a complete reference):
* `@Field`: Applied at the field level and defines properties of the field, most of the attributes map to the respective https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping.html[Elasticsearch Mapping] definitions:
** `name`: The name of the field as it will be represented in the Elasticsearch document, if not set, the Java field name is used.
** `type`: the field type, can be one of _Text, Keyword, Long, Integer, Short, Byte, Double, Float, Half_Float, Scaled_Float, Date, Date_Nanos, Boolean, Binary, Integer_Range, Float_Range, Long_Range, Double_Range, Date_Range, Ip_Range, Object, Nested, Ip, TokenCount, Percolator, Flattened, Search_As_You_Type_. See https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-types.html[Elasticsearch Mapping Types]
** `format` and `pattern` definitions for the _Date_ type. `format` must be defined for date types.
** `type`: the field type, can be one of _Text, Integer, Long, Date, Float, Double, Boolean, Object, Auto, Nested, Ip, Attachment, Keyword_.
** `format` and `pattern` custom definitions for the _Date_ type.
** `store`: Flag wether the original field value should be store in Elasticsearch, default value is _false_.
** `analyzer`, `searchAnalyzer`, `normalizer` for specifying custom custom analyzers and normalizer.
** `copy_to`: the target field to copy multiple document fields to.
* `@GeoPoint`: marks a field as _geo_point_ datatype. Can be omitted if the field is an instance of the `GeoPoint` class.
NOTE: Properties that derive from `TemporalAccessor` must either have a `@Field` annotation of type `FieldType.Date` or a custom converter must be registerd for this type. +
If you are using a custom date format, you need to use _uuuu_ for the year instead of _yyyy_. This is due to a https://www.elastic.co/guide/en/elasticsearch/reference/current/migrate-to-java-time.html#java-time-migration-incompatible-date-formats[change in Elasticsearch 7].
The mapping metadata infrastructure is defined in a separate spring-data-commons project that is technology agnostic.
@@ -214,12 +259,23 @@ public class Config extends AbstractElasticsearchConfiguration {
@Bean
@Override
public ElasticsearchCustomConversions elasticsearchCustomConversions() {
return new ElasticsearchCustomConversions(
Arrays.asList(new AddressToMap(), new MapToAddress())); <1>
public EntityMapper entityMapper() {
ElasticsearchEntityMapper entityMapper = new ElasticsearchEntityMapper(
elasticsearchMappingContext(), new DefaultConversionService());
entityMapper.setConversions(elasticsearchCustomConversions()); <1>
return entityMapper;
}
@WritingConverter <2>
@Bean
@Override
public ElasticsearchCustomConversions elasticsearchCustomConversions() {
return new ElasticsearchCustomConversions(
Arrays.asList(new AddressToMap(), new MapToAddress())); <2>
}
@WritingConverter <3>
static class AddressToMap implements Converter<Address, Map<String, Object>> {
@Override
@@ -233,7 +289,7 @@ public class Config extends AbstractElasticsearchConfiguration {
}
}
@ReadingConverter <3>
@ReadingConverter <4>
static class MapToAddress implements Converter<Map<String, Object>, Address> {
@Override
@@ -253,7 +309,8 @@ 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.
<1> Register `ElasticsearchCustomConversions` with the `EntityMapper`.
<2> Add `Converter` implementations.
<3> Set up the `Converter` used for writing `DomainType` to Elasticsearch.
<4> Set up the `Converter` used for reading `DomainType` from search result.
====
@@ -1,18 +1,10 @@
[[elasticsearch.operations]]
= Elasticsearch Operations
Spring Data Elasticsearch uses several interfaces to define the operations that can be called against an Elasticsearch index (for a description of the reactive interfaces see <<elasticsearch.reactive.operations>>).
* `IndexOperations` defines actions on index level like creating or deleting an index.
* `DocumentOperations` defines actions to store, update and retrieve entities based on their id.
* `SearchOperations` define the actions to search for multiple entities using queries
* `ElasticsearchOperations` combines the `DocumentOperations` and `SearchOperations` interfaces.
These interfaces correspond to the structuring of the https://www.elastic.co/guide/en/elasticsearch/reference/current/rest-apis.html[Elasticsearch API].
Spring Data Elasticsearch uses two interfaces to define the operations that can be called against an Elasticsearch index. These are `ElasticsearchOperations` and `ReactiveElasticsearchOperations`. Whereas the first is used with the classic synchronous implementations, the second one uses reactive infrastructure.
The default implementations of the interfaces offer:
* index management functionality.
* Read/Write mapping support for domain types.
* A rich query and criteria api.
* Resource management and Exception translation.
@@ -20,8 +12,6 @@ The default implementations of the interfaces offer:
[[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
@@ -41,12 +31,23 @@ public class TransportClientConfig extends ElasticsearchConfigurationSupport {
@Bean(name = {"elasticsearchOperations", "elasticsearchTemplate"})
public ElasticsearchTemplate elasticsearchTemplate() throws UnknownHostException { <2>
return new ElasticsearchTemplate(elasticsearchClient());
return new ElasticsearchTemplate(elasticsearchClient(), entityMapper());
}
// use the ElasticsearchEntityMapper
@Bean
@Override
public EntityMapper entityMapper() { <3>
ElasticsearchEntityMapper entityMapper = new ElasticsearchEntityMapper(elasticsearchMappingContext(),
new DefaultConversionService());
entityMapper.setConversions(elasticsearchCustomConversions());
return entityMapper;
}
}
----
<1> Setting up the <<elasticsearch.clients.transport>>. Deprecated as of version 4.0.
<1> Setting up the <<elasticsearch.clients.transport>>.
<2> Creating the `ElasticsearchTemplate` bean, offering both names, _elasticsearchOperations_ and _elasticsearchTemplate_.
<3> Using the <<elasticsearch.mapping.meta-model>> ElasticsearchMapper.
====
[[elasticsearch.operations.resttemplate]]
@@ -66,16 +67,29 @@ public class RestClientConfig extends AbstractElasticsearchConfiguration {
}
// no special bean creation needed <2>
// use the ElasticsearchEntityMapper
@Bean
@Override
public EntityMapper entityMapper() { <3>
ElasticsearchEntityMapper entityMapper = new ElasticsearchEntityMapper(elasticsearchMappingContext(),
new DefaultConversionService());
entityMapper.setConversions(elasticsearchCustomConversions());
return entityMapper;
}
}
----
<1> Setting up the <<elasticsearch.clients.rest>>.
<2> The base class `AbstractElasticsearchConfiguration` already provides the `elasticsearchTemplate` bean.
<3> Using the <<elasticsearch.mapping.meta-model>> ElasticsearchMapper.
====
[[elasticsearch.operations.usage]]
== Usage examples
As both `ElasticsearchTemplate` and `ElasticsearchRestTemplate` implement the `ElasticsearchOperations` interface, the code to use them is not different. The example shows how to use an injected `ElasticsearchOperations` instance in a Spring REST controller. The decision, if this is using the `TransportClient` or the `RestClient` is made by providing the corresponding Bean with one of the configurations shown above.
As both `ElasticsearchTemplate` and `ElasticsearchRestTemplate` implement the `ElasticsearchOperations` interface, the code to use them is not different. The example shows how to use an injected `ElasticsearchOperations` instance in a Spring REST controller. The decision, if this is using the `TransportClient` or the `RestClient` is made by providing the
corresponding Bean with one of the configurations shown above.
.ElasticsearchOperations usage
====
@@ -119,38 +133,3 @@ public class TestController {
To see the full possibilities of `ElasticsearchOperations` please refer to the API documentation.
include::reactive-elasticsearch-operations.adoc[leveloffset=+1]
[[elasticsearch.operations.searchresulttypes]]
== Search Result Types
When a document is retrieved with the methods of the `DocumentOperations` interface, just the found entity will be returned. When searching with the methods of the `SearchOperations` interface, additional information is available for each entity, for example the _score_ or the _sortValues_ of the found entity.
In order to return this information, each entity is wrapped in a `SearchHit` object that contains this entity-specific additional information. These `SearchHit` objects themselves are returned within a `SearchHits` object which additionally contains informations about the whole search like the _maxScore_ or requested aggregations. The following classes and interfaces are now available:
.SearchHit<T>
Contains the following information:
* Id
* Score
* Sort Values
* Highlight fields
* The retrieved entity of type <T>
.SearchHits<T>
Contains the following information:
* Number of total hits
* Total hits relation
* Maximum score
* A list of `SearchHit<T>` objects
* Returned aggregations
.SearchPage<T>
Defines a Spring Data `Page` that contains a `SearchHits<T>` element and can be used for paging access using repository methods.
.SearchScrollHits<T>
Returned by the low level scroll API functions in `ElasticsearchRestTemplate`, it enriches a `SearchHits<T>` with the Elasticsearch scroll id.
.SearchHitsIterator<T>
An Iterator returned by the streaming functions of the `SearchOperations` interface.
@@ -3,58 +3,8 @@
This chapter includes details of the Elasticsearch repository implementation.
.The sample `Book` entity
====
[source,java]
----
@Document(indexName="books")
class Book {
@Id
private String id;
@Field(type = FieldType.text)
private String name;
@Field(type = FieldType.text)
private String summary;
@Field(type = FieldType.Integer)
private Integer price;
// getter/setter ...
}
----
====
include::elasticsearch-repository-queries.adoc[leveloffset=+1]
include::reactive-elasticsearch-repositories.adoc[leveloffset=+1]
[[elasticsearch.repositories.annotations]]
== Annotations for repository methods
=== @Highlight
The `@Highlight` annotation on a repository method defines for which fields of the returned entity highlighting should be included. To search for some text in a `Book` 's name or summary and have the found data highlighted, the following repository method can be used:
====
[source,java]
----
interface BookRepository extends Repository<Book, String> {
@Highlight(fields = {
@HighlightField(name = "name"),
@HighlightField(name = "summary")
})
List<SearchHit<Book>> findByNameOrSummary(String text, String summary);
}
----
====
It is possible to define multiple fields to be highlighted like above, and both the `@Highlight` and the `@HighlightField` annotation can further be customized with a `@HighlightParameters` annotation. Check the Javadocs for the possible configuration options.
In the search results the highlight data can be retrieved from the `SearchHit` class.
[[elasticsearch.annotation]]
== Annotation based configuration
@@ -90,8 +40,7 @@ class ProductService {
}
----
<1> The `EnableElasticsearchRepositories` annotation activates the Repository support.
If no base package is configured, it will use the one of the configuration class it is put on.
<1> The `EnableElasticsearchRepositories` annotation activates the Repository support. If no base package is configured, it will use the one of the configuration class it is put on.
<2> Provide a Bean named `elasticsearchTemplate` of type `ElasticsearchOperations` by using one of the configurations shown in the <<elasticsearch.operations>> chapter.
<3> Let Spring inject the Repository bean into your class.
====
@@ -196,3 +145,5 @@ Using the `Transport Client` or `Rest Client` element registers an instance of `
</beans>
----
====
include::reactive-elasticsearch-repositories.adoc[leveloffset=+1]
@@ -272,10 +272,7 @@ Repository methods can be defined to have the following return types for returni
* `List<T>`
* `Stream<T>`
* `SearchHits<T>`
* `List<SearchHit<T>>`
* `Stream<SearchHit<T>>`
* `SearchPage<T>`
* `AggregatedPage<T>`
[[elasticsearch.query-methods.at-query]]
== Using @Query Annotation
@@ -1,6 +0,0 @@
[[elasticsearch.migration]]
= Appendix E: Migration Guides
:leveloffset: +1
include::elasticsearch-migration-guide-3.2-4.0.adoc[]
:leveloffset: -1
@@ -82,7 +82,7 @@ Consider the following:
====
[source,java]
----
@Document(indexName = "marvel")
@Document(indexName = "marvel", type = "characters")
public class Person {
private @Id String id;
@@ -1,37 +0,0 @@
/*
* Copyright 2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch;
import org.springframework.dao.DataRetrievalFailureException;
import java.util.Map;
/**
* @author Peter-Josef Meisch
* @since 4.0.1 (ported back from master (4.1) branch)
*/
public class BulkFailureException extends DataRetrievalFailureException {
private final Map<String, String> failedDocuments;
public BulkFailureException(String msg, Map<String, String> failedDocuments) {
super(msg);
this.failedDocuments = failedDocuments;
}
public Map<String, String> getFailedDocuments() {
return failedDocuments;
}
}
@@ -17,20 +17,15 @@ package org.springframework.data.elasticsearch;
import java.util.Map;
import org.springframework.lang.Nullable;
/**
* ElasticsearchException
*
* @author Rizwan Idrees
* @author Mohsin Husen
* @author Peter-Josef Meisch
* @deprecated since 4.0, use {@link org.springframework.dao.UncategorizedDataAccessException}
*/
@Deprecated
public class ElasticsearchException extends RuntimeException {
@Nullable private Map<String, String> failedDocuments;
private Map<String, String> failedDocuments;
public ElasticsearchException(String message) {
super(message);
@@ -50,7 +45,6 @@ public class ElasticsearchException extends RuntimeException {
this.failedDocuments = failedDocuments;
}
@Nullable
public Map<String, String> getFailedDocuments() {
return failedDocuments;
}
@@ -1,5 +1,7 @@
package org.springframework.data.elasticsearch.annotations;
import org.elasticsearch.search.suggest.completion.context.ContextMapping;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
@@ -7,8 +9,6 @@ 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
*
@@ -26,5 +26,4 @@ public @interface CompletionContext {
String precision() default "";
String path() default "";
}
@@ -22,6 +22,7 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.elasticsearch.index.VersionType;
import org.springframework.data.annotation.Persistent;
/**
@@ -32,7 +33,6 @@ import org.springframework.data.annotation.Persistent;
* @author Mason Chan
* @author Ivan Greene
* @author Mark Paluch
* @author Peter-Josef Meisch
*/
@Persistent
@Inherited
@@ -54,14 +54,8 @@ public @interface Document {
String indexName();
/**
* Mapping type name. <br/>
* deprecated as Elasticsearch does not support this anymore
* (@see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/7.3/removal-of-types.html">Elastisearch removal of types documentation</a>) and will remove it in
* Elasticsearch 8.
*
* @deprecated since 4.0
* Mapping type name.
*/
@Deprecated
String type() default "";
/**
@@ -70,11 +64,9 @@ public @interface Document {
boolean useServerConfiguration() default false;
/**
* Number of shards for the index {@link #indexName()}. Used for index creation. <br/>
* With version 4.0, the default value is changed from 5 to 1 to reflect the change in the default settings of
* Elasticsearch which changed to 1 as well in Elasticsearch 7.0.
* Number of shards for the index {@link #indexName()}. Used for index creation.
*/
short shards() default 1;
short shards() default 5;
/**
* Number of replicas for the index {@link #indexName()}. Used for index creation.
@@ -1,37 +0,0 @@
/*
* Copyright 2019-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.annotations;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 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
* @since 4.0
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.FIELD })
@Documented
public @interface DynamicMapping {
DynamicMappingValue value() default DynamicMappingValue.True;
}
@@ -15,6 +15,8 @@
*/
package org.springframework.data.elasticsearch.annotations;
import org.springframework.core.annotation.AliasFor;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
@@ -22,8 +24,6 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
/**
* @author Rizwan Idrees
* @author Mohsin Husen
@@ -32,8 +32,6 @@ import org.springframework.core.annotation.AliasFor;
* @author Jakub Vavrik
* @author Kevin Leturc
* @author Peter-Josef Meisch
* @author Xiao Yu
* @author Aleksei Arsenev
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@@ -43,7 +41,6 @@ public @interface Field {
/**
* Alias for {@link #name}.
*
* @since 3.2
*/
@AliasFor("name")
@@ -51,9 +48,7 @@ public @interface Field {
/**
* The <em>name</em> to be used to store the field inside the document.
* <p>
* √5 If not set, the name of the annotated property is used.
*
* <p>If not set, the name of the annotated property is used.
* @since 3.2
*/
@AliasFor("value")
@@ -82,76 +77,4 @@ public @interface Field {
boolean includeInParent() default false;
String[] copyTo() default {};
/**
* @since 4.0
*/
int ignoreAbove() default -1;
/**
* @since 4.0
*/
boolean coerce() default true;
/**
* @since 4.0
*/
boolean docValues() default true;
/**
* @since 4.0
*/
boolean ignoreMalformed() default false;
/**
* @since 4.0
*/
IndexOptions indexOptions() default IndexOptions.none;
/**
* @since 4.0
*/
boolean indexPhrases() default false;
/**
* implemented as array to enable the empty default value
*
* @since 4.0
*/
IndexPrefixes[] indexPrefixes() default {};
/**
* @since 4.0
*/
boolean norms() default true;
/**
* @since 4.0
*/
String nullValue() default "";
/**
* @since 4.0
*/
int positionIncrementGap() default -1;
/**
* @since 4.0
*/
Similarity similarity() default Similarity.Default;
/**
* @since 4.0
*/
TermVector termVector() default TermVector.none;
/**
* @since 4.0
*/
double scalingFactor() default 1;
/**
* @since 4.0
*/
int maxShingleSize() default -1;
}
@@ -20,36 +20,22 @@ package org.springframework.data.elasticsearch.annotations;
* @author Mohsin Husen
* @author Artur Konczak
* @author Zeng Zetang
* @author Peter-Josef Meisch
* @author Aleksei Arsenev
*/
public enum FieldType {
Auto, //
Text, //
Keyword, //
Long, //
Integer, //
Short, //
Byte, //
Double, //
Float, //
Half_Float, //
Scaled_Float, //
Date, //
Date_Nanos, //
Boolean, //
Binary, //
Integer_Range, //
Float_Range, //
Long_Range, //
Double_Range, //
Date_Range, //
Ip_Range, //
Object, //
Nested, //
Ip, //
TokenCount, //
Percolator, //
Flattened, //
Search_As_You_Type //
Text,
Byte,
Short,
Integer,
Long,
Date,
Half_Float,
Float,
Double,
Boolean,
Object,
Auto,
Nested,
Ip,
Attachment,
Keyword
}
@@ -1,36 +0,0 @@
/*
* Copyright 2020-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.annotations;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author Peter-Josef Meisch
* @since 4.0
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Highlight {
HighlightParameters parameters() default @HighlightParameters;
HighlightField[] fields();
}
@@ -1,37 +0,0 @@
/*
* Copyright 2020-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.annotations;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* @author Peter-Josef Meisch
* @since 4.0
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface HighlightField {
/**
* The name of the field to apply highlighting to. This must be the field name of the entity's property, not the name
* of the field in the index mappings.
*/
String name() default "";
HighlightParameters parameters() default @HighlightParameters;
}
@@ -1,78 +0,0 @@
/*
* Copyright 2020-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.annotations;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* @author Peter-Josef Meisch
* @since 4.0
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface HighlightParameters {
String boundaryChars() default "";
int boundaryMaxScan() default -1;
String boundaryScanner() default "";
String boundaryScannerLocale() default "";
/**
* only used for {@link Highlight}s.
*/
String encoder() default "";
boolean forceSource() default false;
String fragmenter() default "";
/**
* only used for {@link HighlightField}s.
*/
int fragmentOffset() default -1;
int fragmentSize() default -1;
/**
* only used for {@link HighlightField}s.
*/
String[] matchedFields() default {};
int noMatchSize() default -1;
int numberOfFragments() default -1;
String order() default "";
int phraseLimit() default -1;
String[] preTags() default {};
String[] postTags() default {};
boolean requireFieldMatch() default true;
/**
* only used for {@link Highlight}s.
*/
String tagsSchema() default "";
String type() default "";
}
@@ -24,12 +24,9 @@ import java.lang.annotation.Target;
* @author Artur Konczak
* @author Mohsin Husen
* @author Sascha Woo
* @author Xiao Yu
* @author Peter-Josef Meisch
* @author Aleksei Arsenev
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
@Target(ElementType.FIELD)
public @interface InnerField {
String suffix();
@@ -51,76 +48,4 @@ public @interface InnerField {
String analyzer() default "";
String normalizer() default "";
/**
* @since 4.0
*/
int ignoreAbove() default -1;
/**
* @since 4.0
*/
boolean coerce() default true;
/**
* @since 4.0
*/
boolean docValues() default true;
/**
* @since 4.0
*/
boolean ignoreMalformed() default false;
/**
* @since 4.0
*/
IndexOptions indexOptions() default IndexOptions.none;
/**
* @since 4.0
*/
boolean indexPhrases() default false;
/**
* implemented as array to enable the empty default value
*
* @since 4.0
*/
IndexPrefixes[] indexPrefixes() default {};
/**
* @since 4.0
*/
boolean norms() default true;
/**
* @since 4.0
*/
String nullValue() default "";
/**
* @since 4.0
*/
int positionIncrementGap() default -1;
/**
* @since 4.0
*/
Similarity similarity() default Similarity.Default;
/**
* @since 4.0
*/
TermVector termVector() default TermVector.none;
/**
* @since 4.0
*/
double scalingFactor() default 1;
/**
* @since 4.0
*/
int maxShingleSize() default -1;
}
@@ -15,25 +15,20 @@
*/
package org.springframework.data.elasticsearch.annotations;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.annotation.*;
/**
* @author Rizwan Idrees
* @author Mohsin Husen
* @author Artur Konczak
* @author Jonathan Yan
* @author Xiao Yu
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Documented
public @interface MultiField {
Field mainField();
public Field mainField();
InnerField[] otherFields() default {};
public InnerField[] otherFields() default {};
}
@@ -8,21 +8,16 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.data.annotation.ReadOnlyProperty;
import org.springframework.data.elasticsearch.core.SearchHit;
/**
* Specifies that this field is used for storing the document score.
*
* @author Sascha Woo
* @author Peter-Josef Meisch
* @since 3.1
* @deprecated since 4.0, use {@link SearchHit#getScore()} instead
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Documented
@Inherited
@ReadOnlyProperty
@Deprecated
public @interface Score {
}
public @interface Score {}
@@ -1,36 +0,0 @@
/*
* Copyright 2019-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.annotations;
/**
* @author Peter-Josef Meisch
* @since 4.0
*/
public enum Similarity {
Default("default"), BM25("BM25"), classic("classic"), Boolean("boolean");
// need to use a custom name because 'boolean' can't be used as enum name
private final String toStringName;
Similarity(String name) {
this.toStringName = name;
}
@Override
public String toString() {
return toStringName;
}
}
@@ -1,3 +0,0 @@
@org.springframework.lang.NonNullApi
@org.springframework.lang.NonNullFields
package org.springframework.data.elasticsearch.annotations;
@@ -21,13 +21,11 @@ import java.time.Duration;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Supplier;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import org.springframework.http.HttpHeaders;
import org.springframework.lang.Nullable;
import org.springframework.web.reactive.function.client.WebClient;
/**
@@ -151,18 +149,17 @@ 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
* @since 3.2.4
*/
@Nullable
String getPathPrefix();
/**
* returns an optionally set proxy in the form host:port
*
*
* @return the optional proxy
* @since 4.0
* @since 3.2.4
*/
Optional<String> getProxy();
@@ -171,11 +168,6 @@ public interface ClientConfiguration {
*/
Function<WebClient, WebClient> getWebClientConfigurer();
/**
* @return the supplier for custom headers.
*/
Supplier<HttpHeaders> getHeadersSupplier();
/**
* @author Christoph Strobl
*/
@@ -320,10 +312,10 @@ 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
* @since 3.2.4
*/
TerminalClientConfigurationBuilder withPathPrefix(String pathPrefix);
@@ -341,19 +333,6 @@ public interface ClientConfiguration {
*/
TerminalClientConfigurationBuilder withWebClientConfigurer(Function<WebClient, WebClient> webClientConfigurer);
/**
* 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.
* <br/>
* Note: When used in a reactive environment, the calling of {@link Supplier#get()} function must not do any
* blocking operations. It may return {@literal null}.
*
* @param headers supplier function for headers, must not be {@literal null}
* @return the {@link TerminalClientConfigurationBuilder}.
* @since 4.0
*/
TerminalClientConfigurationBuilder withHeaders(Supplier<HttpHeaders> headers);
/**
* Build the {@link ClientConfiguration} object.
*
@@ -21,7 +21,6 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import javax.net.ssl.HostnameVerifier;
@@ -55,12 +54,11 @@ class ClientConfigurationBuilder
private @Nullable HostnameVerifier hostnameVerifier;
private Duration connectTimeout = Duration.ofSeconds(10);
private Duration soTimeout = Duration.ofSeconds(5);
private @Nullable String username;
private @Nullable String password;
private @Nullable String pathPrefix;
private @Nullable String proxy;
private Function<WebClient, WebClient> webClientConfigurer = Function.identity();
private Supplier<HttpHeaders> headersSupplier = () -> HttpHeaders.EMPTY;
private String username;
private String password;
private String pathPrefix;
private String proxy;
private Function<WebClient, WebClient> webClientConfigurer;
/*
* (non-Javadoc)
@@ -198,8 +196,7 @@ class ClientConfigurationBuilder
}
@Override
public TerminalClientConfigurationBuilder withWebClientConfigurer(
Function<WebClient, WebClient> webClientConfigurer) {
public TerminalClientConfigurationBuilder withWebClientConfigurer(Function<WebClient, WebClient> webClientConfigurer) {
Assert.notNull(webClientConfigurer, "webClientConfigurer must not be null");
@@ -207,15 +204,6 @@ class ClientConfigurationBuilder
return this;
}
@Override
public TerminalClientConfigurationBuilder withHeaders(Supplier<HttpHeaders> headers) {
Assert.notNull(headers, "headersSupplier must not be null");
this.headersSupplier = headers;
return this;
}
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.client.ClientConfiguration.ClientConfigurationBuilderWithOptionalDefaultHeaders#build()
@@ -231,7 +219,7 @@ class ClientConfigurationBuilder
}
return new DefaultClientConfiguration(hosts, headers, useSsl, sslContext, soTimeout, connectTimeout, pathPrefix,
hostnameVerifier, proxy, webClientConfigurer, headersSupplier);
hostnameVerifier, proxy, webClientConfigurer);
}
private static InetSocketAddress parse(String hostAndPort) {
@@ -20,7 +20,6 @@ import java.util.function.Supplier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.lang.Nullable;
import org.springframework.util.ObjectUtils;
/**
@@ -91,7 +90,7 @@ public abstract class ClientLogger {
* @param logId the correlation Id, see {@link #newLogId()}.
* @param statusCode the HTTP status code.
*/
public static void logRawResponse(String logId, @Nullable HttpStatus statusCode) {
public static void logRawResponse(String logId, HttpStatus statusCode) {
if (isEnabled()) {
WIRE_LOGGER.trace("[{}] Received raw response: {}", logId, statusCode);
@@ -66,7 +66,7 @@ class ClusterNodes implements Streamable<TransportAddress> {
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));
return new TransportAddress(toInetAddress(host), Integer.valueOf(port));
}).collect(Collectors.toList());
}
@@ -22,7 +22,6 @@ import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Supplier;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
@@ -48,16 +47,14 @@ class DefaultClientConfiguration implements ClientConfiguration {
private final @Nullable SSLContext sslContext;
private final Duration soTimeout;
private final Duration connectTimeout;
private final @Nullable String pathPrefix;
private final String pathPrefix;
private final @Nullable HostnameVerifier hostnameVerifier;
private final @Nullable String proxy;
private final String proxy;
private final Function<WebClient, WebClient> webClientConfigurer;
private final Supplier<HttpHeaders> headersSupplier;
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, Supplier<HttpHeaders> headersSupplier) {
@Nullable HostnameVerifier hostnameVerifier, String proxy, Function<WebClient, WebClient> webClientConfigurer) {
this.hosts = Collections.unmodifiableList(new ArrayList<>(hosts));
this.headers = new HttpHeaders(headers);
@@ -69,7 +66,6 @@ class DefaultClientConfiguration implements ClientConfiguration {
this.hostnameVerifier = hostnameVerifier;
this.proxy = proxy;
this.webClientConfigurer = webClientConfigurer;
this.headersSupplier = headersSupplier;
}
@Override
@@ -107,7 +103,6 @@ class DefaultClientConfiguration implements ClientConfiguration {
return this.soTimeout;
}
@Nullable
@Override
public String getPathPrefix() {
return this.pathPrefix;
@@ -120,11 +115,6 @@ class DefaultClientConfiguration implements ClientConfiguration {
@Override
public Function<WebClient, WebClient> getWebClientConfigurer() {
return webClientConfigurer;
}
@Override
public Supplier<HttpHeaders> getHeadersSupplier() {
return headersSupplier;
return webClientConfigurer != null ? webClientConfigurer : Function.identity();
}
}
@@ -104,7 +104,7 @@ public class ElasticsearchHost {
@Override
public String toString() {
return "ElasticsearchHost(" + endpoint + ", " + state.name() + ')';
return "ElasticsearchHost(" + endpoint + ", " + state.name() + ")";
}
public enum State {
@@ -15,10 +15,11 @@
*/
package org.springframework.data.elasticsearch.client;
import static java.util.Arrays.*;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
import java.util.Collections;
import org.elasticsearch.client.Client;
import org.elasticsearch.client.node.NodeClient;
@@ -32,9 +33,7 @@ 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;
import org.springframework.util.StringUtils;
/**
@@ -43,33 +42,28 @@ import org.springframework.util.StringUtils;
* @author Rizwan Idrees
* @author Mohsin Husen
* @author Ilkang Na
* @author Peter-Josef Meisch
*/
public class NodeClientFactoryBean implements FactoryBean<Client>, InitializingBean, DisposableBean {
private static final Logger logger = LoggerFactory.getLogger(NodeClientFactoryBean.class);
private boolean local;
private boolean enableHttp;
private @Nullable String clusterName;
private @Nullable Node node;
private @Nullable NodeClient nodeClient;
private @Nullable String pathData;
private @Nullable String pathHome;
private @Nullable String pathConfiguration;
private String clusterName;
private NodeClient nodeClient;
private String pathData;
private String pathHome;
private String pathConfiguration;
public static class TestNode extends Node {
private static final String DEFAULT_NODE_NAME = "spring-data-elasticsearch-nodeclientfactorybean-test";
public TestNode(Settings preparedSettings, Collection<Class<? extends Plugin>> classpathPlugins) {
super(InternalSettingsPreparer.prepareEnvironment(preparedSettings, Collections.emptyMap(), null,
() -> DEFAULT_NODE_NAME), classpathPlugins, false);
super(InternalSettingsPreparer.prepareEnvironment(preparedSettings, null), classpathPlugins, false);
}
protected void registerDerivedNodeNameWithLogger(String nodeName) {
try {
LogConfigurator.setNodeName(nodeName);
LogConfigurator.setNodeName(nodeName);
} catch (Exception e) {
// nagh - just forget about it
}
@@ -83,12 +77,7 @@ public class NodeClientFactoryBean implements FactoryBean<Client>, InitializingB
}
@Override
public NodeClient getObject() {
if (nodeClient == null) {
throw new FactoryBeanNotInitializedException();
}
public NodeClient getObject() throws Exception {
return nodeClient;
}
@@ -105,17 +94,10 @@ public class NodeClientFactoryBean implements FactoryBean<Client>, InitializingB
@Override
public void afterPropertiesSet() throws Exception {
Settings settings = Settings.builder() //
.put(loadConfig()) //
.put("transport.type", "netty4") //
.put("http.type", "netty4") //
.put("path.home", this.pathHome) //
.put("path.data", this.pathData) //
.put("cluster.name", this.clusterName) //
.put("node.max_local_storage_nodes", 100) //
.build();
node = new TestNode(settings, Collections.singletonList(Netty4Plugin.class));
nodeClient = (NodeClient) node.start().client();
nodeClient = (NodeClient) new TestNode(Settings.builder().put(loadConfig()).put("transport.type", "netty4")
.put("http.type", "netty4").put("path.home", this.pathHome).put("path.data", this.pathData)
.put("cluster.name", this.clusterName).put("node.max_local_storage_nodes", 100).build(),
asList(Netty4Plugin.class)).start().client();
}
private Settings loadConfig() throws IOException {
@@ -155,14 +137,11 @@ public class NodeClientFactoryBean implements FactoryBean<Client>, InitializingB
}
@Override
public void destroy() {
public void destroy() throws Exception {
try {
// NodeClient.close() is a noop, no need to call it here
nodeClient = null;
logger.info("Closing elasticSearch node");
if (node != null) {
node.close();
node = null;
logger.info("Closing elasticSearch client");
if (nodeClient != null) {
nodeClient.close();
}
} catch (final Exception e) {
logger.error("Error closing ElasticSearch client: ", e);
@@ -15,44 +15,40 @@
*/
package org.springframework.data.elasticsearch.client;
import lombok.extern.slf4j.Slf4j;
import java.net.URL;
import java.util.ArrayList;
import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.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;
import org.springframework.util.Assert;
/**
* RestClientFactoryBean
*
*
* @author Don Wellington
* @author Peter-Josef Meisch
*/
@Slf4j
public class RestClientFactoryBean implements FactoryBean<RestHighLevelClient>, InitializingBean, DisposableBean {
private static final Logger LOGGER = LoggerFactory.getLogger(RestClientFactoryBean.class);
private @Nullable RestHighLevelClient client;
private RestHighLevelClient client;
private String hosts = "http://localhost:9200";
static final String COMMA = ",";
@Override
public void destroy() {
public void destroy() throws Exception {
try {
LOGGER.info("Closing elasticSearch client");
log.info("Closing elasticSearch client");
if (client != null) {
client.close();
}
} catch (final Exception e) {
LOGGER.error("Error closing ElasticSearch client: ", e);
log.error("Error closing ElasticSearch client: ", e);
}
}
@@ -62,12 +58,7 @@ public class RestClientFactoryBean implements FactoryBean<RestHighLevelClient>,
}
@Override
public RestHighLevelClient getObject() {
if (client == null) {
throw new FactoryBeanNotInitializedException();
}
public RestHighLevelClient getObject() throws Exception {
return client;
}
@@ -84,8 +75,7 @@ public class RestClientFactoryBean implements FactoryBean<RestHighLevelClient>,
protected void buildClient() throws Exception {
Assert.hasText(hosts, "[Assertion Failed] At least one host must be set.");
ArrayList<HttpHost> httpHosts = new ArrayList<>();
ArrayList<HttpHost> httpHosts = new ArrayList<HttpHost>();
for (String host : hosts.split(COMMA)) {
URL hostUrl = new URL(host);
httpHosts.add(new HttpHost(hostUrl.getHost(), hostUrl.getPort(), hostUrl.getProtocol()));
@@ -20,11 +20,13 @@ import java.io.Closeable;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.time.Duration;
import java.util.Arrays;
import java.util.List;
import java.util.function.Supplier;
import java.util.Optional;
import java.util.stream.Collectors;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpEntityEnclosingRequest;
@@ -85,23 +87,31 @@ public final class RestClients {
HttpHeaders headers = clientConfiguration.getDefaultHeaders();
if (!headers.isEmpty()) {
builder.setDefaultHeaders(toHeaderArray(headers));
Header[] httpHeaders = headers.toSingleValueMap().entrySet().stream()
.map(it -> new BasicHeader(it.getKey(), it.getValue())).toArray(Header[]::new);
builder.setDefaultHeaders(httpHeaders);
}
builder.setHttpClientConfigCallback(clientBuilder -> {
clientConfiguration.getSslContext().ifPresent(clientBuilder::setSSLContext);
clientConfiguration.getHostNameVerifier().ifPresent(clientBuilder::setSSLHostnameVerifier);
clientBuilder.addInterceptorLast(new CustomHeaderInjector(clientConfiguration.getHeadersSupplier()));
Optional<SSLContext> sslContext = clientConfiguration.getSslContext();
Optional<HostnameVerifier> hostNameVerifier = clientConfiguration.getHostNameVerifier();
sslContext.ifPresent(clientBuilder::setSSLContext);
hostNameVerifier.ifPresent(clientBuilder::setSSLHostnameVerifier);
if (ClientLogger.isEnabled()) {
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
clientBuilder.addInterceptorLast((HttpRequestInterceptor) interceptor);
clientBuilder.addInterceptorLast((HttpResponseInterceptor) interceptor);
}
Builder requestConfigBuilder = RequestConfig.custom();
Duration connectTimeout = clientConfiguration.getConnectTimeout();
Duration timeout = clientConfiguration.getSocketTimeout();
Builder requestConfigBuilder = RequestConfig.custom();
if (!connectTimeout.isNegative()) {
@@ -109,8 +119,6 @@ public final class RestClients {
requestConfigBuilder.setConnectionRequestTimeout(Math.toIntExact(connectTimeout.toMillis()));
}
Duration timeout = clientConfiguration.getSocketTimeout();
if (!timeout.isNegative()) {
requestConfigBuilder.setSocketTimeout(Math.toIntExact(timeout.toMillis()));
}
@@ -126,16 +134,8 @@ public final class RestClients {
return () -> client;
}
private static Header[] toHeaderArray(HttpHeaders headers) {
return headers.entrySet().stream() //
.flatMap(entry -> entry.getValue().stream() //
.map(value -> new BasicHeader(entry.getKey(), value))) //
.toArray(Header[]::new);
}
private static List<String> formattedHosts(List<InetSocketAddress> hosts, boolean useSsl) {
return hosts.stream().map(it -> (useSsl ? "https" : "http") + "://" + it.getHostString() + ":" + it.getPort())
.collect(Collectors.toList());
return hosts.stream().map(it -> (useSsl ? "https" : "http") + "://" + it).collect(Collectors.toList());
}
/**
@@ -180,6 +180,7 @@ public final class RestClients {
String logId = (String) context.getAttribute(RestClients.LOG_ID_ATTRIBUTE);
if (logId == null) {
logId = ClientLogger.newLogId();
context.setAttribute(RestClients.LOG_ID_ATTRIBUTE, logId);
}
@@ -204,31 +205,10 @@ public final class RestClients {
@Override
public void process(HttpResponse response, HttpContext context) {
String logId = (String) context.getAttribute(RestClients.LOG_ID_ATTRIBUTE);
ClientLogger.logRawResponse(logId, HttpStatus.resolve(response.getStatusLine().getStatusCode()));
}
}
/**
* Interceptor to inject custom supplied headers.
*
* @since 4.0
*/
private static class CustomHeaderInjector implements HttpRequestInterceptor {
public CustomHeaderInjector(Supplier<HttpHeaders> headersSupplier) {
this.headersSupplier = headersSupplier;
}
private final Supplier<HttpHeaders> headersSupplier;
@Override
public void process(HttpRequest request, HttpContext context) {
HttpHeaders httpHeaders = headersSupplier.get();
if (httpHeaders != null && httpHeaders != HttpHeaders.EMPTY) {
Arrays.stream(toHeaderArray(httpHeaders)).forEach(request::addHeader);
}
}
}
}
@@ -24,9 +24,7 @@ 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
@@ -37,10 +35,7 @@ import org.springframework.lang.Nullable;
* @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);
@@ -50,11 +45,11 @@ public class TransportClientFactoryBean implements FactoryBean<TransportClient>,
private Boolean clientIgnoreClusterName = Boolean.FALSE;
private String clientPingTimeout = "5s";
private String clientNodesSamplerInterval = "5s";
private @Nullable TransportClient client;
private @Nullable Properties properties;
private TransportClient client;
private Properties properties;
@Override
public void destroy() {
public void destroy() throws Exception {
try {
logger.info("Closing elasticSearch client");
if (client != null) {
@@ -66,11 +61,7 @@ public class TransportClientFactoryBean implements FactoryBean<TransportClient>,
}
@Override
public TransportClient getObject() {
if (clientTransportSniff == null) {
throw new FactoryBeanNotInitializedException();
}
public TransportClient getObject() throws Exception {
return client;
}
@@ -89,14 +80,14 @@ public class TransportClientFactoryBean implements FactoryBean<TransportClient>,
buildClient();
}
protected void buildClient() {
protected void buildClient() throws Exception {
client = new PreBuiltTransportClient(settings());
clusterNodes.stream() //
.peek(it -> logger.info("Adding transport node : " + it.toString())) //
.forEach(client::addTransportAddress);
client.connectedNodes();
}
@@ -110,10 +101,13 @@ public class TransportClientFactoryBean implements FactoryBean<TransportClient>,
return builder.build();
}
return Settings.builder().put("cluster.name", clusterName).put("client.transport.sniff", clientTransportSniff)
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();
.put("client.transport.nodes_sampler_interval", clientNodesSamplerInterval)
.build();
}
public void setClusterNodes(String clusterNodes) {
@@ -1,3 +0,0 @@
@org.springframework.lang.NonNullApi
@org.springframework.lang.NonNullFields
package org.springframework.data.elasticsearch.client;
@@ -27,7 +27,6 @@ import reactor.core.publisher.Flux;
import reactor.core.publisher.FluxSink;
import reactor.core.publisher.Mono;
import reactor.netty.http.client.HttpClient;
import reactor.netty.tcp.ProxyProvider;
import reactor.netty.tcp.TcpClient;
import java.io.IOException;
@@ -36,17 +35,18 @@ import java.net.ConnectException;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.function.Supplier;
import javax.net.ssl.SSLContext;
import org.apache.http.util.EntityUtils;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.ElasticsearchStatusException;
import org.elasticsearch.action.ActionRequest;
import org.elasticsearch.action.admin.indices.close.CloseIndexRequest;
@@ -80,6 +80,8 @@ import org.elasticsearch.action.support.master.AcknowledgedResponse;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.action.update.UpdateResponse;
import org.elasticsearch.client.Request;
import org.elasticsearch.client.core.CountRequest;
import org.elasticsearch.client.core.CountResponse;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.DeprecationHandler;
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
@@ -90,18 +92,18 @@ import org.elasticsearch.index.reindex.BulkByScrollResponse;
import org.elasticsearch.index.reindex.DeleteByQueryRequest;
import org.elasticsearch.rest.BytesRestResponse;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.search.Scroll;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.Aggregation;
import org.reactivestreams.Publisher;
import org.springframework.data.elasticsearch.ElasticsearchException;
import org.springframework.data.elasticsearch.client.ClientConfiguration;
import org.springframework.data.elasticsearch.client.ClientLogger;
import org.springframework.data.elasticsearch.client.ElasticsearchHost;
import org.springframework.data.elasticsearch.client.NoReachableHostException;
import org.springframework.data.elasticsearch.client.reactive.HostProvider.Verification;
import org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient.Indices;
import org.springframework.data.elasticsearch.client.util.NamedXContents;
import org.springframework.data.elasticsearch.client.util.ScrollState;
import org.springframework.data.elasticsearch.client.util.RequestConverters;
import org.springframework.data.util.Lazy;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
@@ -112,6 +114,8 @@ import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.client.HttpServerErrorException;
import org.springframework.web.reactive.function.BodyExtractors;
import org.springframework.web.reactive.function.client.ClientRequest;
import org.springframework.web.reactive.function.client.ClientResponse;
@@ -126,8 +130,6 @@ import org.springframework.web.reactive.function.client.WebClient.RequestBodySpe
* @author Peter-Josef Meisch
* @author Huw Ayling-Miller
* @author Henrique Amaral
* @author Roman Puchkovskiy
* @author Russell Parry
* @since 3.2
* @see ClientConfiguration
* @see ReactiveRestClients
@@ -135,8 +137,6 @@ import org.springframework.web.reactive.function.client.WebClient.RequestBodySpe
public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearchClient, Indices {
private final HostProvider hostProvider;
private final RequestCreator requestCreator;
private Supplier<HttpHeaders> headersSupplier = () -> HttpHeaders.EMPTY;
/**
* Create a new {@link DefaultReactiveElasticsearchClient} using the given {@link HostProvider} to obtain server
@@ -145,30 +145,10 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
* @param hostProvider must not be {@literal null}.
*/
public DefaultReactiveElasticsearchClient(HostProvider hostProvider) {
this(hostProvider, new DefaultRequestCreator());
}
/**
* Create a new {@link DefaultReactiveElasticsearchClient} using the given {@link HostProvider} to obtain server
* connections and the given {@link RequestCreator}.
*
* @param hostProvider must not be {@literal null}.
* @param requestCreator must not be {@literal null}.
*/
public DefaultReactiveElasticsearchClient(HostProvider hostProvider, RequestCreator requestCreator) {
Assert.notNull(hostProvider, "HostProvider must not be null");
Assert.notNull(requestCreator, "RequestCreator must not be null");
this.hostProvider = hostProvider;
this.requestCreator = requestCreator;
}
public void setHeadersSupplier(Supplier<HttpHeaders> headersSupplier) {
Assert.notNull(headersSupplier, "headersSupplier must not be null");
this.headersSupplier = headersSupplier;
}
/**
@@ -199,35 +179,14 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
* @return new instance of {@link DefaultReactiveElasticsearchClient}.
*/
public static ReactiveElasticsearchClient create(ClientConfiguration clientConfiguration) {
return create(clientConfiguration, new DefaultRequestCreator());
}
/**
* Create a new {@link DefaultReactiveElasticsearchClient} given {@link ClientConfiguration} and
* {@link RequestCreator}. <br />
* <strong>NOTE</strong> If the cluster requires authentication be sure to provide the according {@link HttpHeaders}
* correctly.
*
* @param clientConfiguration Client configuration. Must not be {@literal null}.
* @param requestCreator Request creator. Must not be {@literal null}.
* @return new instance of {@link DefaultReactiveElasticsearchClient}.
*/
public static ReactiveElasticsearchClient create(ClientConfiguration clientConfiguration,
RequestCreator requestCreator) {
Assert.notNull(clientConfiguration, "ClientConfiguration must not be null");
Assert.notNull(requestCreator, "RequestCreator must not be null");
WebClientProvider provider = getWebClientProvider(clientConfiguration);
HostProvider hostProvider = HostProvider.provider(provider, clientConfiguration.getHeadersSupplier(),
HostProvider hostProvider = HostProvider.provider(provider,
clientConfiguration.getEndpoints().toArray(new InetSocketAddress[0]));
DefaultReactiveElasticsearchClient client = new DefaultReactiveElasticsearchClient(hostProvider, requestCreator);
client.setHeadersSupplier(clientConfiguration.getHeadersSupplier());
return client;
return new DefaultReactiveElasticsearchClient(hostProvider);
}
private static WebClientProvider getWebClientProvider(ClientConfiguration clientConfiguration) {
@@ -242,22 +201,12 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
}
if (!soTimeout.isNegative()) {
tcpClient = tcpClient.doOnConnected(connection -> connection //
.addHandlerLast(new ReadTimeoutHandler(soTimeout.toMillis(), TimeUnit.MILLISECONDS))
.addHandlerLast(new WriteTimeoutHandler(soTimeout.toMillis(), TimeUnit.MILLISECONDS)));
}
if (clientConfiguration.getProxy().isPresent()) {
String proxy = clientConfiguration.getProxy().get();
String[] hostPort = proxy.split(":");
if (hostPort.length != 2) {
throw new IllegalArgumentException("invalid proxy configuration " + proxy + ", should be \"host:port\"");
}
tcpClient = tcpClient.proxy(proxyOptions -> proxyOptions.type(ProxyProvider.Proxy.HTTP).host(hostPort[0])
.port(Integer.parseInt(hostPort[1])));
}
String scheme = "http";
HttpClient httpClient = HttpClient.from(tcpClient);
@@ -296,7 +245,7 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
@Override
public Mono<Boolean> ping(HttpHeaders headers) {
return sendRequest(new MainRequest(), requestCreator.ping(), RawActionResponse.class, headers) //
return sendRequest(new MainRequest(), RequestCreator.ping(), RawActionResponse.class, headers) //
.map(response -> response.statusCode().is2xxSuccessful()) //
.onErrorResume(NoReachableHostException.class, error -> Mono.just(false)).next();
}
@@ -308,7 +257,7 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
@Override
public Mono<MainResponse> info(HttpHeaders headers) {
return sendRequest(new MainRequest(), requestCreator.info(), MainResponse.class, headers) //
return sendRequest(new MainRequest(), RequestCreator.info(), MainResponse.class, headers) //
.next();
}
@@ -319,7 +268,7 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
@Override
public Mono<GetResult> get(HttpHeaders headers, GetRequest getRequest) {
return sendRequest(getRequest, requestCreator.get(), GetResponse.class, headers) //
return sendRequest(getRequest, RequestCreator.get(), GetResponse.class, headers) //
.filter(GetResponse::isExists) //
.map(DefaultReactiveElasticsearchClient::getResponseToGetResult) //
.next();
@@ -332,7 +281,7 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
@Override
public Flux<GetResult> multiGet(HttpHeaders headers, MultiGetRequest multiGetRequest) {
return sendRequest(multiGetRequest, requestCreator.multiGet(), MultiGetResponse.class, headers)
return sendRequest(multiGetRequest, RequestCreator.multiGet(), MultiGetResponse.class, headers)
.map(MultiGetResponse::getResponses) //
.flatMap(Flux::fromArray) //
.filter(it -> !it.isFailed() && it.getResponse().isExists()) //
@@ -346,7 +295,7 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
@Override
public Mono<Boolean> exists(HttpHeaders headers, GetRequest getRequest) {
return sendRequest(getRequest, requestCreator.exists(), RawActionResponse.class, headers) //
return sendRequest(getRequest, RequestCreator.exists(), RawActionResponse.class, headers) //
.map(response -> response.statusCode().is2xxSuccessful()) //
.next();
}
@@ -357,7 +306,7 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
*/
@Override
public Mono<IndexResponse> index(HttpHeaders headers, IndexRequest indexRequest) {
return sendRequest(indexRequest, requestCreator.index(), IndexResponse.class, headers).publishNext();
return sendRequest(indexRequest, RequestCreator.index(), IndexResponse.class, headers).publishNext();
}
/*
@@ -375,7 +324,7 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
*/
@Override
public Mono<UpdateResponse> update(HttpHeaders headers, UpdateRequest updateRequest) {
return sendRequest(updateRequest, requestCreator.update(), UpdateResponse.class, headers).publishNext();
return sendRequest(updateRequest, RequestCreator.update(), UpdateResponse.class, headers).publishNext();
}
/*
@@ -385,7 +334,7 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
@Override
public Mono<DeleteResponse> delete(HttpHeaders headers, DeleteRequest deleteRequest) {
return sendRequest(deleteRequest, requestCreator.delete(), DeleteResponse.class, headers) //
return sendRequest(deleteRequest, RequestCreator.delete(), DeleteResponse.class, headers) //
.publishNext();
}
@@ -394,13 +343,9 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
* @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient#count(org.springframework.http.HttpHeaders, org.elasticsearch.action.search.SearchRequest)
*/
@Override
public Mono<Long> count(HttpHeaders headers, SearchRequest searchRequest) {
searchRequest.source().trackTotalHits(true);
searchRequest.source().size(0);
searchRequest.source().fetchSource(false);
return sendRequest(searchRequest, requestCreator.search(), SearchResponse.class, headers) //
.map(SearchResponse::getHits) //
.map(searchHits -> searchHits.getTotalHits().value) //
public Mono<Long> count(HttpHeaders headers, CountRequest countRequest) {
return sendRequest(countRequest, RequestCreator.count(), CountResponse.class, headers) //
.map(CountResponse::getCount) //
.next();
}
@@ -411,29 +356,11 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
@Override
public Flux<SearchHit> search(HttpHeaders headers, SearchRequest searchRequest) {
return sendRequest(searchRequest, requestCreator.search(), SearchResponse.class, headers) //
return sendRequest(searchRequest, RequestCreator.search(), SearchResponse.class, headers) //
.map(SearchResponse::getHits) //
.flatMap(Flux::fromIterable);
}
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient#aggregate(org.springframework.http.HttpHeaders, org.elasticsearch.action.search.SearchRequest)
*/
@Override
public Flux<Aggregation> aggregate(HttpHeaders headers, SearchRequest searchRequest) {
Assert.notNull(headers, "headers must not be null");
Assert.notNull(searchRequest, "searchRequest must not be null");
searchRequest.source().size(0);
searchRequest.source().trackTotalHits(false);
return sendRequest(searchRequest, requestCreator.search(), SearchResponse.class, headers) //
.map(SearchResponse::getAggregations) //
.flatMap(Flux::fromIterable);
}
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient#scroll(org.springframework.http.HttpHeaders, org.elasticsearch.action.search.SearchRequest)
@@ -456,11 +383,11 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
Flux<SearchResponse> exchange = outbound.startWith(searchRequest).flatMap(it -> {
if (it instanceof SearchRequest) {
return sendRequest((SearchRequest) it, requestCreator.search(), SearchResponse.class, headers);
return sendRequest((SearchRequest) it, RequestCreator.search(), SearchResponse.class, headers);
} else if (it instanceof SearchScrollRequest) {
return sendRequest((SearchScrollRequest) it, requestCreator.scroll(), SearchResponse.class, headers);
return sendRequest((SearchScrollRequest) it, RequestCreator.scroll(), SearchResponse.class, headers);
} else if (it instanceof ClearScrollRequest) {
return sendRequest((ClearScrollRequest) it, requestCreator.clearScroll(), ClearScrollResponse.class, headers)
return sendRequest((ClearScrollRequest) it, RequestCreator.clearScroll(), ClearScrollResponse.class, headers)
.flatMap(discard -> Flux.empty());
}
@@ -513,17 +440,16 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
clearScrollRequest.scrollIds(state.getScrollIds());
// just send the request, resources get cleaned up anyways after scrollTimeout has been reached.
return sendRequest(clearScrollRequest, requestCreator.clearScroll(), ClearScrollResponse.class, headers);
return sendRequest(clearScrollRequest, RequestCreator.clearScroll(), ClearScrollResponse.class, headers);
}
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient#ping(org.springframework.http.HttpHeaders, org.elasticsearch.index.reindex.DeleteByQueryRequest)
*/
@Override
public Mono<BulkByScrollResponse> deleteBy(HttpHeaders headers, DeleteByQueryRequest deleteRequest) {
return sendRequest(deleteRequest, requestCreator.deleteByQuery(), BulkByScrollResponse.class, headers) //
return sendRequest(deleteRequest, RequestCreator.deleteByQuery(), BulkByScrollResponse.class, headers) //
.publishNext();
}
@@ -533,7 +459,7 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
*/
@Override
public Mono<BulkResponse> bulk(HttpHeaders headers, BulkRequest bulkRequest) {
return sendRequest(bulkRequest, requestCreator.bulk(), BulkResponse.class, headers) //
return sendRequest(bulkRequest, RequestCreator.bulk(), BulkResponse.class, headers) //
.publishNext();
}
@@ -546,7 +472,7 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
@Override
public Mono<Boolean> existsIndex(HttpHeaders headers, GetIndexRequest request) {
return sendRequest(request, requestCreator.indexExists(), RawActionResponse.class, headers) //
return sendRequest(request, RequestCreator.indexExists(), RawActionResponse.class, headers) //
.map(response -> response.statusCode().is2xxSuccessful()) //
.next();
}
@@ -558,7 +484,7 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
@Override
public Mono<Void> deleteIndex(HttpHeaders headers, DeleteIndexRequest request) {
return sendRequest(request, requestCreator.indexDelete(), AcknowledgedResponse.class, headers) //
return sendRequest(request, RequestCreator.indexDelete(), AcknowledgedResponse.class, headers) //
.then();
}
@@ -569,7 +495,7 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
@Override
public Mono<Void> createIndex(HttpHeaders headers, CreateIndexRequest createIndexRequest) {
return sendRequest(createIndexRequest, requestCreator.indexCreate(), AcknowledgedResponse.class, headers) //
return sendRequest(createIndexRequest, RequestCreator.indexCreate(), AcknowledgedResponse.class, headers) //
.then();
}
@@ -580,7 +506,7 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
@Override
public Mono<Void> openIndex(HttpHeaders headers, OpenIndexRequest request) {
return sendRequest(request, requestCreator.indexOpen(), AcknowledgedResponse.class, headers) //
return sendRequest(request, RequestCreator.indexOpen(), AcknowledgedResponse.class, headers) //
.then();
}
@@ -591,7 +517,7 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
@Override
public Mono<Void> closeIndex(HttpHeaders headers, CloseIndexRequest closeIndexRequest) {
return sendRequest(closeIndexRequest, requestCreator.indexClose(), AcknowledgedResponse.class, headers) //
return sendRequest(closeIndexRequest, RequestCreator.indexClose(), AcknowledgedResponse.class, headers) //
.then();
}
@@ -602,7 +528,7 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
@Override
public Mono<Void> refreshIndex(HttpHeaders headers, RefreshRequest refreshRequest) {
return sendRequest(refreshRequest, requestCreator.indexRefresh(), RefreshResponse.class, headers) //
return sendRequest(refreshRequest, RequestCreator.indexRefresh(), RefreshResponse.class, headers) //
.then();
}
@@ -613,7 +539,7 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
@Override
public Mono<Void> updateMapping(HttpHeaders headers, PutMappingRequest putMappingRequest) {
return sendRequest(putMappingRequest, requestCreator.putMapping(), AcknowledgedResponse.class, headers) //
return sendRequest(putMappingRequest, RequestCreator.putMapping(), AcknowledgedResponse.class, headers) //
.then();
}
@@ -624,7 +550,7 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
@Override
public Mono<Void> flushIndex(HttpHeaders headers, FlushRequest flushRequest) {
return sendRequest(flushRequest, requestCreator.flushIndex(), FlushResponse.class, headers) //
return sendRequest(flushRequest, RequestCreator.flushIndex(), FlushResponse.class, headers) //
.then();
}
@@ -662,7 +588,7 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
return new GetResult(response.getIndex(), response.getType(), response.getId(), response.getSeqNo(),
response.getPrimaryTerm(), response.getVersion(), response.isExists(), response.getSourceAsBytesRef(),
response.getFields(), null);
response.getFields());
}
// -->
@@ -707,12 +633,6 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
request.getOptions().getHeaders().forEach(it -> theHeaders.add(it.getName(), it.getValue()));
}
}
// plus the ones from the supplier
HttpHeaders suppliedHeaders = headersSupplier.get();
if (suppliedHeaders != null && suppliedHeaders != HttpHeaders.EMPTY) {
theHeaders.addAll(suppliedHeaders);
}
});
if (request.getEntity() != null) {
@@ -723,7 +643,7 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
body::get);
requestBodySpec.contentType(MediaType.valueOf(request.getEntity().getContentType().getValue()));
requestBodySpec.body(Mono.fromSupplier(body), String.class);
requestBodySpec.body(Mono.fromSupplier(body::get), String.class);
} else {
ClientLogger.logRequest(logId, request.getMethod().toUpperCase(), request.getEndpoint(), request.getParameters());
}
@@ -760,12 +680,6 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
return handleServerError(request, response);
}
if (response.statusCode().is4xxClientError()) {
ClientLogger.logRawResponse(logId, response.statusCode());
return handleClientError(logId, request, response, responseType);
}
return response.body(BodyExtractors.toMono(byte[].class)) //
.map(it -> new String(it, StandardCharsets.UTF_8)) //
.doOnNext(it -> ClientLogger.logResponse(logId, response.statusCode(), it)) //
@@ -796,103 +710,129 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
}
private static XContentParser createParser(String mediaType, String content) throws IOException {
return XContentType.fromMediaTypeOrFormat(mediaType) //
.xContent() //
.createParser(new NamedXContentRegistry(NamedXContents.getDefaultNamedXContents()),
DeprecationHandler.THROW_UNSUPPORTED_OPERATION, content);
.createParser(NamedXContentRegistry.EMPTY, DeprecationHandler.THROW_UNSUPPORTED_OPERATION, content);
}
private <T> Publisher<? extends T> handleServerError(Request request, ClientResponse response) {
private static <T> Publisher<? extends T> handleServerError(Request request, ClientResponse response) {
int statusCode = response.statusCode().value();
RestStatus status = RestStatus.fromCode(statusCode);
String mediaType = response.headers().contentType().map(MediaType::toString).orElse(XContentType.JSON.mediaType());
return response.body(BodyExtractors.toMono(byte[].class)) //
.map(bytes -> new String(bytes, StandardCharsets.UTF_8)) //
.flatMap(content -> contentOrError(content, mediaType, status))
.flatMap(unused -> Mono
.error(new ElasticsearchStatusException(String.format("%s request to %s returned error code %s.",
request.getMethod(), request.getEndpoint(), statusCode), status)));
return Mono.error(
new HttpServerErrorException(response.statusCode(), String.format("%s request to %s returned error code %s.",
request.getMethod(), request.getEndpoint(), response.statusCode().value())));
}
private <T> Publisher<? extends T> handleClientError(String logId, Request request, ClientResponse response,
Class<T> responseType) {
static class RequestCreator {
int statusCode = response.statusCode().value();
RestStatus status = RestStatus.fromCode(statusCode);
String mediaType = response.headers().contentType().map(MediaType::toString).orElse(XContentType.JSON.mediaType());
return response.body(BodyExtractors.toMono(byte[].class)) //
.map(bytes -> new String(bytes, StandardCharsets.UTF_8)) //
.flatMap(content -> contentOrError(content, mediaType, status)) //
.doOnNext(content -> ClientLogger.logResponse(logId, response.statusCode(), content)) //
.flatMap(content -> doDecode(response, responseType, content));
}
// region ElasticsearchException helper
/**
* checks if the given content body contains an {@link ElasticsearchException}, if yes it is returned in a Mono.error.
* Otherwise the content is returned in the Mono
*
* @param content the content to analyze
* @param mediaType the returned media type
* @param status the response status
* @return a Mono with the content or an Mono.error
*/
private static Mono<String> contentOrError(String content, String mediaType, RestStatus status) {
ElasticsearchException exception = getElasticsearchException(content, mediaType, status);
if (exception != null) {
StringBuilder sb = new StringBuilder();
buildExceptionMessages(sb, exception);
return Mono.error(new ElasticsearchStatusException(sb.toString(), status, exception));
static Function<SearchRequest, Request> search() {
return RequestConverters::search;
}
return Mono.just(content);
}
static Function<SearchScrollRequest, Request> scroll() {
return RequestConverters::searchScroll;
}
/**
* tries to parse an {@link ElasticsearchException} from the given body content
*
* @param content the content to analyse
* @param mediaType the type of the body content
* @return an {@link ElasticsearchException} or {@literal null}.
*/
@Nullable
private static ElasticsearchException getElasticsearchException(String content, String mediaType, RestStatus status) {
static Function<ClearScrollRequest, Request> clearScroll() {
return RequestConverters::clearScroll;
}
try {
XContentParser parser = createParser(mediaType, content);
// we have a JSON object with an error and a status field
XContentParser.Token token = parser.nextToken(); // Skip START_OBJECT
static Function<IndexRequest, Request> index() {
return RequestConverters::index;
}
do {
token = parser.nextToken();
static Function<GetRequest, Request> get() {
return RequestConverters::get;
}
if (parser.currentName().equals("error")) {
return ElasticsearchException.failureFromXContent(parser);
static Function<MainRequest, Request> ping() {
return (request) -> RequestConverters.ping();
}
static Function<MainRequest, Request> info() {
return (request) -> RequestConverters.info();
}
static Function<MultiGetRequest, Request> multiGet() {
return RequestConverters::multiGet;
}
static Function<GetRequest, Request> exists() {
return RequestConverters::exists;
}
static Function<UpdateRequest, Request> update() {
return RequestConverters::update;
}
static Function<DeleteRequest, Request> delete() {
return RequestConverters::delete;
}
static Function<DeleteByQueryRequest, Request> deleteByQuery() {
return request -> {
try {
return RequestConverters.deleteByQuery(request);
} catch (IOException e) {
throw new ElasticsearchException("Could not parse request", e);
}
} while (token == XContentParser.Token.FIELD_NAME);
return null;
} catch (IOException e) {
return new ElasticsearchStatusException(content, status);
};
}
static Function<BulkRequest, Request> bulk() {
return request -> {
try {
return RequestConverters.bulk(request);
} catch (IOException e) {
throw new ElasticsearchException("Could not parse request", e);
}
};
}
// --> INDICES
static Function<GetIndexRequest, Request> indexExists() {
return RequestConverters::indexExists;
}
static Function<DeleteIndexRequest, Request> indexDelete() {
return RequestConverters::indexDelete;
}
static Function<CreateIndexRequest, Request> indexCreate() {
return RequestConverters::indexCreate;
}
static Function<OpenIndexRequest, Request> indexOpen() {
return RequestConverters::indexOpen;
}
static Function<CloseIndexRequest, Request> indexClose() {
return RequestConverters::indexClose;
}
static Function<RefreshRequest, Request> indexRefresh() {
return RequestConverters::indexRefresh;
}
static Function<PutMappingRequest, Request> putMapping() {
return RequestConverters::putMapping;
}
static Function<FlushRequest, Request> flushIndex() {
return RequestConverters::flushIndex;
}
static Function<CountRequest, Request> count() {
return RequestConverters::count;
}
}
private static void buildExceptionMessages(StringBuilder sb, Throwable t) {
sb.append(t.getMessage());
for (Throwable throwable : t.getSuppressed()) {
sb.append(", ");
buildExceptionMessages(sb, throwable);
}
}
// endregion
// region internal classes
/**
* Reactive client {@link ReactiveElasticsearchClient.Status} implementation.
*
@@ -916,5 +856,40 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
}
}
// endregion
/**
* Mutable state object holding scrollId to be used for {@link SearchScrollRequest#scroll(Scroll)}
*
* @author Christoph Strobl
* @since 3.2
*/
private static class ScrollState {
private final Object lock = new Object();
private final List<String> pastIds = new ArrayList<>(1);
private String scrollId;
String getScrollId() {
return scrollId;
}
List<String> getScrollIds() {
synchronized (lock) {
return Collections.unmodifiableList(new ArrayList<>(pastIds));
}
}
void updateScrollId(String scrollId) {
if (StringUtils.hasText(scrollId)) {
synchronized (lock) {
this.scrollId = scrollId;
pastIds.add(scrollId);
}
}
}
}
}
@@ -1,7 +0,0 @@
package org.springframework.data.elasticsearch.client.reactive;
/**
* @author Roman Puchkovskiy
* @since 4.0
*/
class DefaultRequestCreator implements RequestCreator {}
@@ -61,12 +61,12 @@ class DefaultWebClientProvider implements WebClientProvider {
/**
* Create new {@link DefaultWebClientProvider} with empty {@link HttpHeaders} and no-op {@literal error listener}.
*
* @param pathPrefix can be {@literal null}
* @param scheme must not be {@literal null}.
* @param connector can be {@literal null}.
* @param errorListener must not be {@literal null}.
* @param headers must not be {@literal null}.
* @param webClientConfigurer must not be {@literal null}.
* @param connector can be {@literal null}.
* @param errorListener must not be {@literal null}.
* @param headers must not be {@literal null}.
* @param pathPrefix can be {@literal null}
* @param webClientConfigurer must not be {@literal null}.
*/
private DefaultWebClientProvider(String scheme, @Nullable ClientHttpConnector connector,
Consumer<Throwable> errorListener, HttpHeaders headers, @Nullable String pathPrefix,
@@ -20,11 +20,9 @@ import reactor.core.publisher.Mono;
import java.net.InetSocketAddress;
import java.util.Collections;
import java.util.Set;
import java.util.function.Supplier;
import org.springframework.data.elasticsearch.client.ElasticsearchHost;
import org.springframework.data.elasticsearch.client.NoReachableHostException;
import org.springframework.http.HttpHeaders;
import org.springframework.util.Assert;
import org.springframework.web.reactive.function.client.WebClient;
@@ -42,20 +40,18 @@ public interface HostProvider {
* Create a new {@link HostProvider} best suited for the given {@link WebClientProvider} and number of hosts.
*
* @param clientProvider must not be {@literal null} .
* @param headersSupplier to supply custom headers, must not be {@literal null}
* @param endpoints must not be {@literal null} nor empty.
* @return new instance of {@link HostProvider}.
*/
static HostProvider provider(WebClientProvider clientProvider, Supplier<HttpHeaders> headersSupplier,
InetSocketAddress... endpoints) {
static HostProvider provider(WebClientProvider clientProvider, InetSocketAddress... endpoints) {
Assert.notNull(clientProvider, "WebClientProvider must not be null");
Assert.notEmpty(endpoints, "Please provide at least one endpoint to connect to.");
if (endpoints.length == 1) {
return new SingleNodeHostProvider(clientProvider, headersSupplier, endpoints[0]);
return new SingleNodeHostProvider(clientProvider, endpoints[0]);
} else {
return new MultiNodeHostProvider(clientProvider,headersSupplier, endpoints);
return new MultiNodeHostProvider(clientProvider, endpoints);
}
}
@@ -15,7 +15,6 @@
*/
package org.springframework.data.elasticsearch.client.reactive;
import org.springframework.http.HttpHeaders;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.util.function.Tuple2;
@@ -28,7 +27,6 @@ import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Supplier;
import org.springframework.data.elasticsearch.client.ElasticsearchHost;
import org.springframework.data.elasticsearch.client.ElasticsearchHost.State;
@@ -47,13 +45,11 @@ import org.springframework.web.reactive.function.client.WebClient;
class MultiNodeHostProvider implements HostProvider {
private final WebClientProvider clientProvider;
private final Supplier<HttpHeaders> headersSupplier;
private final Map<InetSocketAddress, ElasticsearchHost> hosts;
MultiNodeHostProvider(WebClientProvider clientProvider, Supplier<HttpHeaders> headersSupplier, InetSocketAddress... endpoints) {
MultiNodeHostProvider(WebClientProvider clientProvider, InetSocketAddress... endpoints) {
this.clientProvider = clientProvider;
this.headersSupplier = headersSupplier;
this.hosts = new ConcurrentHashMap<>();
for (InetSocketAddress endpoint : endpoints) {
this.hosts.put(endpoint, new ElasticsearchHost(endpoint, State.UNKNOWN));
@@ -64,7 +60,6 @@ class MultiNodeHostProvider implements HostProvider {
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.client.reactive.HostProvider#clusterInfo()
*/
@Override
public Mono<ClusterInformation> clusterInfo() {
return nodes(null).map(this::updateNodeState).buffer(hosts.size())
.then(Mono.just(new ClusterInformation(new LinkedHashSet<>(this.hosts.values()))));
@@ -137,9 +132,8 @@ class MultiNodeHostProvider implements HostProvider {
.flatMap(host -> {
Mono<ClientResponse> exchange = createWebClient(host) //
.head().uri("/") //
.headers(httpHeaders -> httpHeaders.addAll(headersSupplier.get())) //
.exchange().doOnError(throwable -> {
.head().uri("/").exchange().doOnError(throwable -> {
hosts.put(host, new ElasticsearchHost(host, State.OFFLINE));
clientProvider.getErrorListener().accept(throwable);
});
@@ -15,10 +15,6 @@
*/
package org.springframework.data.elasticsearch.client.reactive;
import org.elasticsearch.common.io.stream.StreamOutput;
import java.io.IOException;
import org.elasticsearch.action.ActionResponse;
import org.springframework.http.HttpStatus;
@@ -65,12 +61,4 @@ class RawActionResponse extends ActionResponse {
public <T> T body(BodyExtractor<T, ? super ClientHttpResponse> extractor) {
return delegate.body(extractor);
}
/*
* (non-Javadoc)
* until Elasticsearch 7.4 this empty implementation was available in the abstract base class
*/
@Override
public void writeTo(StreamOutput out) throws IOException {
}
}
@@ -43,15 +43,14 @@ import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.action.update.UpdateResponse;
import org.elasticsearch.client.core.CountRequest;
import org.elasticsearch.index.get.GetResult;
import org.elasticsearch.index.reindex.BulkByScrollResponse;
import org.elasticsearch.index.reindex.DeleteByQueryRequest;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.aggregations.Aggregation;
import org.springframework.data.elasticsearch.client.ClientConfiguration;
import org.springframework.data.elasticsearch.client.ElasticsearchHost;
import org.springframework.http.HttpHeaders;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.WebClient;
@@ -341,39 +340,39 @@ public interface ReactiveElasticsearchClient {
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-count.html">Count API on
* elastic.co</a>
* @return the {@link Mono} emitting the count result.
* @since 4.0
* @since 3.2.4
*/
default Mono<Long> count(Consumer<SearchRequest> consumer) {
default Mono<Long> count(Consumer<CountRequest> consumer) {
SearchRequest searchRequest = new SearchRequest();
consumer.accept(searchRequest);
return count(searchRequest);
CountRequest countRequest = new CountRequest();
consumer.accept(countRequest);
return count(countRequest);
}
/**
* Execute a {@link SearchRequest} against the {@literal search} API.
* Execute a {@link SearchRequest} against the {@literal count} API.
*
* @param searchRequest must not be {@literal null}.
* @param countRequest must not be {@literal null}.
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-count.html">Count API on
* elastic.co</a>
* @return the {@link Mono} emitting the count result.
* @since 4.0
* @since 3.2.4
*/
default Mono<Long> count(SearchRequest searchRequest) {
return count(HttpHeaders.EMPTY, searchRequest);
default Mono<Long> count(CountRequest countRequest) {
return count(HttpHeaders.EMPTY, countRequest);
}
/**
* Execute a {@link SearchRequest} against the {@literal search} API.
* Execute a {@link SearchRequest} against the {@literal count} API.
*
* @param headers Use {@link HttpHeaders} to provide eg. authentication data. Must not be {@literal null}.
* @param searchRequest must not be {@literal null}.
* @param countRequest must not be {@literal null}.
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-count.html">Count API on
* elastic.co</a>
* @return the {@link Mono} emitting the count result.
* @since 4.0
* @since 3.2.4
*/
Mono<Long> count(HttpHeaders headers, SearchRequest searchRequest);
Mono<Long> count(HttpHeaders headers, CountRequest countRequest);
/**
* Execute a {@link SearchRequest} against the {@literal search} API.
@@ -413,50 +412,6 @@ public interface ReactiveElasticsearchClient {
*/
Flux<SearchHit> search(HttpHeaders headers, SearchRequest searchRequest);
/**
* Execute the given {@link SearchRequest} with aggregations against the {@literal search} API.
*
* @param consumer
* never {@literal null}.
* @return the {@link Flux} emitting {@link Aggregation} one by one.
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-search.html">Search API on
* elastic.co</a>
* @since 4.0
*/
default Flux<Aggregation> aggregate(Consumer<SearchRequest> consumer) {
Assert.notNull(consumer, "consumer must not be null");
SearchRequest request = new SearchRequest();
consumer.accept(request);
return aggregate(request);
}
/**
* Execute the given {@link SearchRequest} with aggregations against the {@literal search} API.
*
* @param searchRequest must not be {@literal null}.
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-search.html">Search API on
* elastic.co</a>
* @return the {@link Flux} emitting {@link Aggregation} one by one.
* @since 4.0
*/
default Flux<Aggregation> aggregate(SearchRequest searchRequest) {
return aggregate(HttpHeaders.EMPTY, searchRequest);
}
/**
* Execute the given {@link SearchRequest} with aggregations against the {@literal search} API.
*
* @param headers Use {@link HttpHeaders} to provide eg. authentication data. Must not be {@literal null}.
* @param searchRequest must not be {@literal null}.
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-search.html">Search API on
* elastic.co</a>
* @return the {@link Flux} emitting {@link Aggregation} one by one.
* @since 4.0
*/
Flux<Aggregation> aggregate(HttpHeaders headers, SearchRequest searchRequest);
/**
* Execute the given {@link SearchRequest} against the {@literal search scroll} API.
*
@@ -24,7 +24,6 @@ import org.springframework.util.Assert;
*
* @author Christoph Strobl
* @author Mark Paluch
* @author Roman Puchkovskiy
* @since 3.2
*/
public final class ReactiveRestClients {
@@ -34,8 +33,6 @@ public final class ReactiveRestClients {
/**
* Start here to create a new client tailored to your needs.
*
* @param clientConfiguration client configuration to use for building {@link ReactiveElasticsearchClient}; must not
* be {@literal null}.
* @return new instance of {@link ReactiveElasticsearchClient}.
*/
public static ReactiveElasticsearchClient create(ClientConfiguration clientConfiguration) {
@@ -44,21 +41,4 @@ public final class ReactiveRestClients {
return DefaultReactiveElasticsearchClient.create(clientConfiguration);
}
/**
* Start here to create a new client tailored to your needs.
*
* @param clientConfiguration client configuration to use for building {@link ReactiveElasticsearchClient}; must not
* be {@literal null}.
* @param requestCreator request creator to use in the client; must not be {@literal null}.
* @return new instance of {@link ReactiveElasticsearchClient}.
*/
public static ReactiveElasticsearchClient create(ClientConfiguration clientConfiguration,
RequestCreator requestCreator) {
Assert.notNull(clientConfiguration, "ClientConfiguration must not be null!");
Assert.notNull(requestCreator, "RequestCreator must not be null!");
return DefaultReactiveElasticsearchClient.create(clientConfiguration, requestCreator);
}
}
@@ -1,134 +0,0 @@
package org.springframework.data.elasticsearch.client.reactive;
import java.io.IOException;
import java.util.function.Function;
import org.elasticsearch.action.admin.indices.close.CloseIndexRequest;
import org.elasticsearch.action.admin.indices.create.CreateIndexRequest;
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
import org.elasticsearch.action.admin.indices.flush.FlushRequest;
import org.elasticsearch.action.admin.indices.get.GetIndexRequest;
import org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest;
import org.elasticsearch.action.admin.indices.open.OpenIndexRequest;
import org.elasticsearch.action.admin.indices.refresh.RefreshRequest;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.get.GetRequest;
import org.elasticsearch.action.get.MultiGetRequest;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.main.MainRequest;
import org.elasticsearch.action.search.ClearScrollRequest;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchScrollRequest;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.client.Request;
import org.elasticsearch.client.core.CountRequest;
import org.elasticsearch.index.reindex.DeleteByQueryRequest;
import org.springframework.data.elasticsearch.ElasticsearchException;
import org.springframework.data.elasticsearch.client.util.RequestConverters;
/**
* @author Roman Puchkovskiy
* @since 4.0
*/
public interface RequestCreator {
default Function<SearchRequest, Request> search() {
return RequestConverters::search;
}
default Function<SearchScrollRequest, Request> scroll() {
return RequestConverters::searchScroll;
}
default Function<ClearScrollRequest, Request> clearScroll() {
return RequestConverters::clearScroll;
}
default Function<IndexRequest, Request> index() {
return RequestConverters::index;
}
default Function<GetRequest, Request> get() {
return RequestConverters::get;
}
default Function<MainRequest, Request> ping() {
return (request) -> RequestConverters.ping();
}
default Function<MainRequest, Request> info() {
return (request) -> RequestConverters.info();
}
default Function<MultiGetRequest, Request> multiGet() {
return RequestConverters::multiGet;
}
default Function<GetRequest, Request> exists() {
return RequestConverters::exists;
}
default Function<UpdateRequest, Request> update() {
return RequestConverters::update;
}
default Function<DeleteRequest, Request> delete() {
return RequestConverters::delete;
}
default Function<DeleteByQueryRequest, Request> deleteByQuery() {
return RequestConverters::deleteByQuery;
}
default Function<BulkRequest, Request> bulk() {
return request -> {
try {
return RequestConverters.bulk(request);
} catch (IOException e) {
throw new ElasticsearchException("Could not parse request", e);
}
};
}
// --> INDICES
default Function<GetIndexRequest, Request> indexExists() {
return RequestConverters::indexExists;
}
default Function<DeleteIndexRequest, Request> indexDelete() {
return RequestConverters::indexDelete;
}
default Function<CreateIndexRequest, Request> indexCreate() {
return RequestConverters::indexCreate;
}
default Function<OpenIndexRequest, Request> indexOpen() {
return RequestConverters::indexOpen;
}
default Function<CloseIndexRequest, Request> indexClose() {
return RequestConverters::indexClose;
}
default Function<RefreshRequest, Request> indexRefresh() {
return RequestConverters::indexRefresh;
}
default Function<PutMappingRequest, Request> putMapping() {
return RequestConverters::putMapping;
}
default Function<FlushRequest, Request> flushIndex() {
return RequestConverters::flushIndex;
}
default Function<CountRequest, Request> count() {
return RequestConverters::count;
}
}
@@ -15,12 +15,10 @@
*/
package org.springframework.data.elasticsearch.client.reactive;
import org.springframework.http.HttpHeaders;
import reactor.core.publisher.Mono;
import java.net.InetSocketAddress;
import java.util.Collections;
import java.util.function.Supplier;
import org.springframework.data.elasticsearch.client.ElasticsearchHost;
import org.springframework.data.elasticsearch.client.ElasticsearchHost.State;
@@ -37,14 +35,12 @@ import org.springframework.web.reactive.function.client.WebClient;
class SingleNodeHostProvider implements HostProvider {
private final WebClientProvider clientProvider;
private final Supplier<HttpHeaders> headersSupplier;
private final InetSocketAddress endpoint;
private volatile ElasticsearchHost state;
SingleNodeHostProvider(WebClientProvider clientProvider, Supplier<HttpHeaders> headersSupplier, InetSocketAddress endpoint) {
SingleNodeHostProvider(WebClientProvider clientProvider, InetSocketAddress endpoint) {
this.clientProvider = clientProvider;
this.headersSupplier = headersSupplier;
this.endpoint = endpoint;
this.state = new ElasticsearchHost(this.endpoint, State.UNKNOWN);
}
@@ -57,10 +53,9 @@ class SingleNodeHostProvider implements HostProvider {
public Mono<ClusterInformation> clusterInfo() {
return createWebClient(endpoint) //
.head().uri("/")
.headers(httpHeaders -> httpHeaders.addAll(headersSupplier.get())) //
.exchange() //
.head().uri("/").exchange() //
.flatMap(it -> {
if (it.statusCode().isError()) {
state = ElasticsearchHost.offline(endpoint);
} else {
@@ -101,11 +101,10 @@ public interface WebClientProvider {
/**
* Obtain the {@link String pathPrefix} to be used.
*
*
* @return the pathPrefix if set.
* @since 4.0
* @since 3.2.4
*/
@Nullable
String getPathPrefix();
/**
@@ -126,20 +125,18 @@ public interface WebClientProvider {
/**
* Create a new instance of {@link WebClientProvider} where HTTP requests are called with the given path prefix.
*
*
* @param pathPrefix Path prefix to add to requests
* @return new instance of {@link WebClientProvider}
* @since 4.0
* @since 3.2.4
*/
WebClientProvider withPathPrefix(String pathPrefix);
/**
* Create a new instance of {@link WebClientProvider} calling the given {@link Function} to configure the
* {@link WebClient}.
*
* Create a new instance of {@link WebClientProvider} calling the given {@link Function} to configure the {@link WebClient}.
* @param webClientConfigurer configuration function
* @return new instance of {@link WebClientProvider}
* @since 4.0
* @since 3.2.4
*/
WebClientProvider withWebClientConfigurer(Function<WebClient, WebClient> webClientConfigurer);
}
@@ -1,3 +1,6 @@
/**
* Everything required for a Reactive Elasticsearch client.
*/
@org.springframework.lang.NonNullApi
@org.springframework.lang.NonNullFields
package org.springframework.data.elasticsearch.client.reactive;
@@ -1,169 +0,0 @@
/*
* Copyright 2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.client.util;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
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;
import org.elasticsearch.search.aggregations.bucket.composite.CompositeAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.composite.ParsedComposite;
import org.elasticsearch.search.aggregations.bucket.filter.FilterAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.filter.FiltersAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.filter.ParsedFilter;
import org.elasticsearch.search.aggregations.bucket.filter.ParsedFilters;
import org.elasticsearch.search.aggregations.bucket.geogrid.GeoHashGridAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.geogrid.GeoTileGridAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.geogrid.ParsedGeoHashGrid;
import org.elasticsearch.search.aggregations.bucket.geogrid.ParsedGeoTileGrid;
import org.elasticsearch.search.aggregations.bucket.global.GlobalAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.global.ParsedGlobal;
import org.elasticsearch.search.aggregations.bucket.histogram.AutoDateHistogramAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.histogram.DateHistogramAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.histogram.HistogramAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.histogram.ParsedAutoDateHistogram;
import org.elasticsearch.search.aggregations.bucket.histogram.ParsedDateHistogram;
import org.elasticsearch.search.aggregations.bucket.histogram.ParsedHistogram;
import org.elasticsearch.search.aggregations.bucket.missing.MissingAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.missing.ParsedMissing;
import org.elasticsearch.search.aggregations.bucket.nested.NestedAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedReverseNested;
import org.elasticsearch.search.aggregations.bucket.nested.ReverseNestedAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.range.DateRangeAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.range.GeoDistanceAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.range.IpRangeAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.range.ParsedBinaryRange;
import org.elasticsearch.search.aggregations.bucket.range.ParsedDateRange;
import org.elasticsearch.search.aggregations.bucket.range.ParsedGeoDistance;
import org.elasticsearch.search.aggregations.bucket.range.ParsedRange;
import org.elasticsearch.search.aggregations.bucket.range.RangeAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.sampler.InternalSampler;
import org.elasticsearch.search.aggregations.bucket.sampler.ParsedSampler;
import org.elasticsearch.search.aggregations.bucket.significant.ParsedSignificantLongTerms;
import org.elasticsearch.search.aggregations.bucket.significant.ParsedSignificantStringTerms;
import org.elasticsearch.search.aggregations.bucket.significant.SignificantLongTerms;
import org.elasticsearch.search.aggregations.bucket.significant.SignificantStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.DoubleTerms;
import org.elasticsearch.search.aggregations.bucket.terms.LongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedDoubleTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.StringTerms;
import org.elasticsearch.search.aggregations.metrics.*;
import org.elasticsearch.search.aggregations.pipeline.*;
import org.elasticsearch.search.suggest.Suggest;
import org.elasticsearch.search.suggest.completion.CompletionSuggestion;
import org.elasticsearch.search.suggest.completion.CompletionSuggestionBuilder;
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.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient;
/**
* <p>
* Original implementation source {@link org.elasticsearch.client.RestHighLevelClient#getDefaultNamedXContents()} by
* {@literal Elasticsearch} (<a href="https://www.elastic.co">https://www.elastic.co</a>) licensed under the Apache
* License, Version 2.0.
* </p>
* Modified for usage with {@link ReactiveElasticsearchClient}.
* <p>
* Only intended for internal use.
*
* @author Russell Parry
* @since 4.0
*/
public class NamedXContents {
private NamedXContents() {
// contains only utility methods
}
public static List<NamedXContentRegistry.Entry> getDefaultNamedXContents() {
Map<String, ContextParser<Object, ? extends Aggregation>> map = new HashMap<>();
map.put(CardinalityAggregationBuilder.NAME, (p, c) -> ParsedCardinality.fromXContent(p, (String) c));
map.put(InternalHDRPercentiles.NAME, (p, c) -> ParsedHDRPercentiles.fromXContent(p, (String) c));
map.put(InternalHDRPercentileRanks.NAME, (p, c) -> ParsedHDRPercentileRanks.fromXContent(p, (String) c));
map.put(InternalTDigestPercentiles.NAME, (p, c) -> ParsedTDigestPercentiles.fromXContent(p, (String) c));
map.put(InternalTDigestPercentileRanks.NAME, (p, c) -> ParsedTDigestPercentileRanks.fromXContent(p, (String) c));
map.put(PercentilesBucketPipelineAggregationBuilder.NAME,
(p, c) -> ParsedPercentilesBucket.fromXContent(p, (String) c));
map.put(MedianAbsoluteDeviationAggregationBuilder.NAME,
(p, c) -> ParsedMedianAbsoluteDeviation.fromXContent(p, (String) c));
map.put(MinAggregationBuilder.NAME, (p, c) -> ParsedMin.fromXContent(p, (String) c));
map.put(MaxAggregationBuilder.NAME, (p, c) -> ParsedMax.fromXContent(p, (String) c));
map.put(SumAggregationBuilder.NAME, (p, c) -> ParsedSum.fromXContent(p, (String) c));
map.put(AvgAggregationBuilder.NAME, (p, c) -> ParsedAvg.fromXContent(p, (String) c));
map.put(WeightedAvgAggregationBuilder.NAME, (p, c) -> ParsedWeightedAvg.fromXContent(p, (String) c));
map.put(ValueCountAggregationBuilder.NAME, (p, c) -> ParsedValueCount.fromXContent(p, (String) c));
map.put(InternalSimpleValue.NAME, (p, c) -> ParsedSimpleValue.fromXContent(p, (String) c));
map.put(DerivativePipelineAggregationBuilder.NAME, (p, c) -> ParsedDerivative.fromXContent(p, (String) c));
map.put(InternalBucketMetricValue.NAME, (p, c) -> ParsedBucketMetricValue.fromXContent(p, (String) c));
map.put(StatsAggregationBuilder.NAME, (p, c) -> ParsedStats.fromXContent(p, (String) c));
map.put(StatsBucketPipelineAggregationBuilder.NAME, (p, c) -> ParsedStatsBucket.fromXContent(p, (String) c));
map.put(ExtendedStatsAggregationBuilder.NAME, (p, c) -> ParsedExtendedStats.fromXContent(p, (String) c));
map.put(ExtendedStatsBucketPipelineAggregationBuilder.NAME,
(p, c) -> ParsedExtendedStatsBucket.fromXContent(p, (String) c));
map.put(GeoBoundsAggregationBuilder.NAME, (p, c) -> ParsedGeoBounds.fromXContent(p, (String) c));
map.put(GeoCentroidAggregationBuilder.NAME, (p, c) -> ParsedGeoCentroid.fromXContent(p, (String) c));
map.put(HistogramAggregationBuilder.NAME, (p, c) -> ParsedHistogram.fromXContent(p, (String) c));
map.put(DateHistogramAggregationBuilder.NAME, (p, c) -> ParsedDateHistogram.fromXContent(p, (String) c));
map.put(AutoDateHistogramAggregationBuilder.NAME, (p, c) -> ParsedAutoDateHistogram.fromXContent(p, (String) c));
map.put(StringTerms.NAME, (p, c) -> ParsedStringTerms.fromXContent(p, (String) c));
map.put(LongTerms.NAME, (p, c) -> ParsedLongTerms.fromXContent(p, (String) c));
map.put(DoubleTerms.NAME, (p, c) -> ParsedDoubleTerms.fromXContent(p, (String) c));
map.put(MissingAggregationBuilder.NAME, (p, c) -> ParsedMissing.fromXContent(p, (String) c));
map.put(NestedAggregationBuilder.NAME, (p, c) -> ParsedNested.fromXContent(p, (String) c));
map.put(ReverseNestedAggregationBuilder.NAME, (p, c) -> ParsedReverseNested.fromXContent(p, (String) c));
map.put(GlobalAggregationBuilder.NAME, (p, c) -> ParsedGlobal.fromXContent(p, (String) c));
map.put(FilterAggregationBuilder.NAME, (p, c) -> ParsedFilter.fromXContent(p, (String) c));
map.put(InternalSampler.PARSER_NAME, (p, c) -> ParsedSampler.fromXContent(p, (String) c));
map.put(GeoHashGridAggregationBuilder.NAME, (p, c) -> ParsedGeoHashGrid.fromXContent(p, (String) c));
map.put(GeoTileGridAggregationBuilder.NAME, (p, c) -> ParsedGeoTileGrid.fromXContent(p, (String) c));
map.put(RangeAggregationBuilder.NAME, (p, c) -> ParsedRange.fromXContent(p, (String) c));
map.put(DateRangeAggregationBuilder.NAME, (p, c) -> ParsedDateRange.fromXContent(p, (String) c));
map.put(GeoDistanceAggregationBuilder.NAME, (p, c) -> ParsedGeoDistance.fromXContent(p, (String) c));
map.put(FiltersAggregationBuilder.NAME, (p, c) -> ParsedFilters.fromXContent(p, (String) c));
map.put(AdjacencyMatrixAggregationBuilder.NAME, (p, c) -> ParsedAdjacencyMatrix.fromXContent(p, (String) c));
map.put(SignificantLongTerms.NAME, (p, c) -> ParsedSignificantLongTerms.fromXContent(p, (String) c));
map.put(SignificantStringTerms.NAME, (p, c) -> ParsedSignificantStringTerms.fromXContent(p, (String) c));
map.put(ScriptedMetricAggregationBuilder.NAME, (p, c) -> ParsedScriptedMetric.fromXContent(p, (String) c));
map.put(IpRangeAggregationBuilder.NAME, (p, c) -> ParsedBinaryRange.fromXContent(p, (String) c));
map.put(TopHitsAggregationBuilder.NAME, (p, c) -> ParsedTopHits.fromXContent(p, (String) c));
map.put(CompositeAggregationBuilder.NAME, (p, c) -> ParsedComposite.fromXContent(p, (String) c));
List<NamedXContentRegistry.Entry> entries = map.entrySet().stream().map(
entry -> new NamedXContentRegistry.Entry(Aggregation.class, new ParseField(entry.getKey()), entry.getValue()))
.collect(Collectors.toList());
entries.add(
new NamedXContentRegistry.Entry(Suggest.Suggestion.class, new ParseField(TermSuggestionBuilder.SUGGESTION_NAME),
(parser, context) -> TermSuggestion.fromXContent(parser, (String) context)));
entries.add(new NamedXContentRegistry.Entry(Suggest.Suggestion.class,
new ParseField(PhraseSuggestionBuilder.SUGGESTION_NAME),
(parser, context) -> PhraseSuggestion.fromXContent(parser, (String) context)));
entries.add(new NamedXContentRegistry.Entry(Suggest.Suggestion.class,
new ParseField(CompletionSuggestionBuilder.SUGGESTION_NAME),
(parser, context) -> CompletionSuggestion.fromXContent(parser, (String) context)));
return entries;
}
}
@@ -33,6 +33,7 @@ import org.elasticsearch.action.admin.cluster.health.ClusterHealthRequest;
import org.elasticsearch.action.admin.cluster.storedscripts.DeleteStoredScriptRequest;
import org.elasticsearch.action.admin.cluster.storedscripts.GetStoredScriptRequest;
import org.elasticsearch.action.admin.cluster.storedscripts.PutStoredScriptRequest;
import org.elasticsearch.action.admin.indices.analyze.AnalyzeRequest;
import org.elasticsearch.action.admin.indices.close.CloseIndexRequest;
import org.elasticsearch.action.admin.indices.create.CreateIndexRequest;
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
@@ -60,7 +61,6 @@ import org.elasticsearch.client.Request;
import org.elasticsearch.client.Requests;
import org.elasticsearch.client.RethrottleRequest;
import org.elasticsearch.client.core.CountRequest;
import org.elasticsearch.client.indices.AnalyzeRequest;
import org.elasticsearch.cluster.health.ClusterHealthStatus;
import org.elasticsearch.common.Priority;
import org.elasticsearch.common.Strings;
@@ -77,7 +77,6 @@ import org.elasticsearch.common.xcontent.XContentHelper;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.VersionType;
import org.elasticsearch.index.mapper.MapperService;
import org.elasticsearch.index.rankeval.RankEvalRequest;
import org.elasticsearch.index.reindex.AbstractBulkByScrollRequest;
import org.elasticsearch.index.reindex.DeleteByQueryRequest;
@@ -104,7 +103,6 @@ import org.springframework.lang.Nullable;
* @author Peter-Josef Meisch
* @since 3.2
*/
@SuppressWarnings("JavadocReference")
public class RequestConverters {
private static final XContentType REQUEST_BODY_CONTENT_TYPE = XContentType.JSON;
@@ -184,9 +182,7 @@ public class RequestConverters {
metadata.field("_index", action.index());
}
if (Strings.hasLength(action.type())) {
if (MapperService.SINGLE_MAPPING_NAME.equals(action.type()) == false) {
metadata.field("_type", action.type());
}
metadata.field("_type", action.type());
}
if (Strings.hasLength(action.id())) {
metadata.field("_id", action.id());
@@ -204,14 +200,11 @@ public class RequestConverters {
metadata.field("version_type", "external");
} else if (versionType == VersionType.EXTERNAL_GTE) {
metadata.field("version_type", "external_gte");
} else if (versionType == VersionType.FORCE) {
metadata.field("version_type", "force");
}
}
if (action.ifSeqNo() != SequenceNumbers.UNASSIGNED_SEQ_NO) {
metadata.field("if_seq_no", action.ifSeqNo());
metadata.field("if_primary_term", action.ifPrimaryTerm());
}
if (opType == DocWriteRequest.OpType.INDEX || opType == DocWriteRequest.OpType.CREATE) {
IndexRequest indexRequest = (IndexRequest) action;
if (Strings.hasLength(indexRequest.getPipeline())) {
@@ -371,7 +364,7 @@ public class RequestConverters {
XContentType upsertContentType = updateRequest.upsertRequest().getContentType();
if ((xContentType != null) && (xContentType != upsertContentType)) {
throw new IllegalStateException("Update request cannot have different content types for doc [" + xContentType
+ ']' + " and upsert [" + upsertContentType + "] documents");
+ "]" + " and upsert [" + upsertContentType + "] documents");
} else {
xContentType = upsertContentType;
}
@@ -398,10 +391,10 @@ public class RequestConverters {
/**
* Creates a count request.
*
*
* @param countRequest the search defining the data to be counted
* @return Elasticsearch count request
* @since 4.0
* @since 3.2
*/
public static Request count(CountRequest countRequest) {
Request request = new Request(HttpMethod.POST.name(),
@@ -467,7 +460,7 @@ public class RequestConverters {
return request;
}
public static Request explain(ExplainRequest explainRequest) {
public static Request explain(ExplainRequest explainRequest) throws IOException {
Request request = new Request(HttpMethod.GET.name(),
endpoint(explainRequest.index(), explainRequest.type(), explainRequest.id(), "_explain"));
@@ -489,7 +482,7 @@ public class RequestConverters {
return request;
}
public static Request rankEval(RankEvalRequest rankEvalRequest) {
public static Request rankEval(RankEvalRequest rankEvalRequest) throws IOException {
Request request = new Request(HttpMethod.GET.name(),
endpoint(rankEvalRequest.indices(), Strings.EMPTY_ARRAY, "_rank_eval"));
@@ -508,7 +501,8 @@ public class RequestConverters {
return prepareReindexRequest(reindexRequest, false);
}
private static Request prepareReindexRequest(ReindexRequest reindexRequest, boolean waitForCompletion) {
private static Request prepareReindexRequest(ReindexRequest reindexRequest, boolean waitForCompletion)
throws IOException {
String endpoint = new EndpointBuilder().addPathPart("_reindex").build();
Request request = new Request(HttpMethod.POST.name(), endpoint);
Params params = new Params(request).withWaitForCompletion(waitForCompletion).withRefresh(reindexRequest.isRefresh())
@@ -522,7 +516,7 @@ public class RequestConverters {
return request;
}
public static Request updateByQuery(UpdateByQueryRequest updateByQueryRequest) {
public static Request updateByQuery(UpdateByQueryRequest updateByQueryRequest) throws IOException {
String endpoint = endpoint(updateByQueryRequest.indices(), updateByQueryRequest.getDocTypes(), "_update_by_query");
Request request = new Request(HttpMethod.POST.name(), endpoint);
Params params = new Params(request).withRouting(updateByQueryRequest.getRouting())
@@ -547,7 +541,7 @@ public class RequestConverters {
return request;
}
public static Request deleteByQuery(DeleteByQueryRequest deleteByQueryRequest) {
public static Request deleteByQuery(DeleteByQueryRequest deleteByQueryRequest) throws IOException {
String endpoint = endpoint(deleteByQueryRequest.indices(), deleteByQueryRequest.getDocTypes(), "_delete_by_query");
Request request = new Request(HttpMethod.POST.name(), endpoint);
Params params = new Params(request).withRouting(deleteByQueryRequest.getRouting())
@@ -593,7 +587,7 @@ public class RequestConverters {
return request;
}
public static Request putScript(PutStoredScriptRequest putStoredScriptRequest) {
public static Request putScript(PutStoredScriptRequest putStoredScriptRequest) throws IOException {
String endpoint = new EndpointBuilder().addPathPartAsIs("_scripts").addPathPart(putStoredScriptRequest.id())
.build();
Request request = new Request(HttpMethod.POST.name(), endpoint);
@@ -607,7 +601,7 @@ public class RequestConverters {
return request;
}
public static Request analyze(AnalyzeRequest request) {
public static Request analyze(AnalyzeRequest request) throws IOException {
EndpointBuilder builder = new EndpointBuilder();
String index = request.index();
if (index != null) {
@@ -753,10 +747,10 @@ public class RequestConverters {
Request request = new Request(HttpMethod.PUT.name(),
RequestConverters.endpoint(putMappingRequest.indices(), "_mapping", putMappingRequest.type()));
RequestConverters.Params parameters = new RequestConverters.Params(request) //
.withTimeout(putMappingRequest.timeout()) //
.withMasterTimeout(putMappingRequest.masterNodeTimeout()) //
.withIncludeTypeName(true);
RequestConverters.Params parameters = new RequestConverters.Params(request);
parameters.withTimeout(putMappingRequest.timeout());
parameters.withMasterTimeout(putMappingRequest.masterNodeTimeout());
request.setEntity(RequestConverters.createEntity(putMappingRequest, RequestConverters.REQUEST_BODY_CONTENT_TYPE));
return request;
}
@@ -977,10 +971,7 @@ public class RequestConverters {
Params withWaitForActiveShards(ActiveShardCount activeShardCount, ActiveShardCount defaultActiveShardCount) {
if (activeShardCount != null && activeShardCount != defaultActiveShardCount) {
// in Elasticsearch 7, "default" cannot be sent anymore, so it needs to be mapped to the default value of 1
String value = activeShardCount == ActiveShardCount.DEFAULT ? "1"
: activeShardCount.toString().toLowerCase(Locale.ROOT);
return putParam("wait_for_active_shards", value);
return putParam("wait_for_active_shards", activeShardCount.toString().toLowerCase(Locale.ROOT));
}
return this;
}
@@ -1120,18 +1111,6 @@ public class RequestConverters {
}
return this;
}
/**
* sets the include_type_name parameter. Needed for Elasticsearch 7 to be used with the mapping types still
* available. Will be removed again when Elasticsearch drops the support for this parameter in Elasticsearch 8.
*
* @deprecated since 4.0
* @since 4.0
*/
@Deprecated
Params withIncludeTypeName(boolean includeTypeName) {
return putParam("include_type_name", String.valueOf(includeTypeName));
}
}
/**
@@ -1151,7 +1130,7 @@ public class RequestConverters {
}
if (requestContentType != xContentType) {
throw new IllegalArgumentException("Mismatching content-type found for request with content-type ["
+ requestContentType + "], previous requests have content-type [" + xContentType + ']');
+ requestContentType + "], previous requests have content-type [" + xContentType + "]");
}
return xContentType;
}
@@ -1200,7 +1179,7 @@ public class RequestConverters {
// encode each part (e.g. index, type and id) separately before merging them into the path
// we prepend "/" to the path part to make this path absolute, otherwise there can be issues with
// paths that start with `-` or contain `:`
URI uri = new URI(null, null, null, -1, '/' + pathPart, null, null);
URI uri = new URI(null, null, null, -1, "/" + pathPart, null, null);
// manually encode any slash that each part may contain
return uri.getRawPath().substring(1).replaceAll("/", "%2F");
} catch (URISyntaxException e) {
@@ -1,72 +0,0 @@
/*
* Copyright 2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.client.util;
import java.util.ArrayList;
import java.util.Collections;
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)}
*
* @author Christoph Strobl
* @author Peter-Josef Meisch
* @since 3.2
*/
public class ScrollState {
private final Object lock = new Object();
private final Set<String> pastIds = new LinkedHashSet<>();
@Nullable private String scrollId;
public ScrollState() {}
public ScrollState(String scrollId) {
updateScrollId(scrollId);
}
@Nullable
public String getScrollId() {
return scrollId;
}
public List<String> getScrollIds() {
synchronized (lock) {
return Collections.unmodifiableList(new ArrayList<>(pastIds));
}
}
public void updateScrollId(String scrollId) {
if (StringUtils.hasText(scrollId)) {
synchronized (lock) {
this.scrollId = scrollId;
pastIds.add(scrollId);
}
}
}
}
@@ -1,3 +0,0 @@
@org.springframework.lang.NonNullApi
@org.springframework.lang.NonNullFields
package org.springframework.data.elasticsearch.client.util;
@@ -19,7 +19,6 @@ import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.context.annotation.Bean;
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
/**
* @author Christoph Strobl
@@ -43,8 +42,8 @@ public abstract class AbstractElasticsearchConfiguration extends ElasticsearchCo
*
* @return never {@literal null}.
*/
@Bean(name = { "elasticsearchOperations", "elasticsearchTemplate" })
public ElasticsearchOperations elasticsearchOperations(ElasticsearchConverter elasticsearchConverter) {
return new ElasticsearchRestTemplate(elasticsearchClient(), elasticsearchConverter);
@Bean(name = {"elasticsearchOperations", "elasticsearchTemplate"})
public ElasticsearchOperations elasticsearchOperations() {
return new ElasticsearchRestTemplate(elasticsearchClient(), elasticsearchConverter(), resultsMapper());
}
}
@@ -22,12 +22,10 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient;
import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations;
import org.springframework.data.elasticsearch.core.ReactiveElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
import org.springframework.lang.Nullable;
/**
* @author Christoph Strobl
* @author Peter-Josef Meisch
* @since 3.2
* @see ElasticsearchConfigurationSupport
*/
@@ -49,10 +47,10 @@ public abstract class AbstractReactiveElasticsearchConfiguration extends Elastic
* @return never {@literal null}.
*/
@Bean
public ReactiveElasticsearchOperations reactiveElasticsearchTemplate(ElasticsearchConverter elasticsearchConverter) {
public ReactiveElasticsearchOperations reactiveElasticsearchTemplate() {
ReactiveElasticsearchTemplate template = new ReactiveElasticsearchTemplate(reactiveElasticsearchClient(),
elasticsearchConverter);
elasticsearchConverter(), resultsMapper());
template.setIndicesOptions(indicesOptions());
template.setRefreshPolicy(refreshPolicy());
@@ -1,109 +0,0 @@
/*
* Copyright 2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.config;
import static org.springframework.data.config.ParsingUtils.*;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.data.auditing.config.IsNewAwareAuditingHandlerBeanDefinitionParser;
import org.springframework.data.elasticsearch.core.event.AuditingEntityCallback;
import org.springframework.data.elasticsearch.core.event.ReactiveAuditingEntityCallback;
import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext;
import org.springframework.data.repository.util.ReactiveWrappers;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;
import org.w3c.dom.Element;
/**
* {@link BeanDefinitionParser} to register a {@link AuditingEntityCallback} to transparently set auditing information
* on an entity.
*
* @author Peter-Josef Meisch
*/
public class ElasticsearchAuditingBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
private static String MAPPING_CONTEXT_BEAN_NAME = "simpleElasticsearchMappingContext";
/*
* (non-Javadoc)
* @see org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser#getBeanClass(org.w3c.dom.Element)
*/
@Override
protected Class<?> getBeanClass(Element element) {
return AuditingEntityCallback.class;
}
/*
* (non-Javadoc)
* @see org.springframework.beans.factory.xml.AbstractBeanDefinitionParser#shouldGenerateId()
*/
@Override
protected boolean shouldGenerateId() {
return true;
}
/*
* (non-Javadoc)
* @see org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser#doParse(org.w3c.dom.Element, org.springframework.beans.factory.xml.ParserContext, org.springframework.beans.factory.support.BeanDefinitionBuilder)
*/
@Override
protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
String mappingContextRef = element.getAttribute("mapping-context-ref");
if (!StringUtils.hasText(mappingContextRef)) {
BeanDefinitionRegistry registry = parserContext.getRegistry();
if (!registry.containsBeanDefinition(MAPPING_CONTEXT_BEAN_NAME)) {
registry.registerBeanDefinition(MAPPING_CONTEXT_BEAN_NAME,
new RootBeanDefinition(SimpleElasticsearchMappingContext.class));
}
mappingContextRef = MAPPING_CONTEXT_BEAN_NAME;
}
IsNewAwareAuditingHandlerBeanDefinitionParser parser = new IsNewAwareAuditingHandlerBeanDefinitionParser(
mappingContextRef);
parser.parse(element, parserContext);
AbstractBeanDefinition isNewAwareAuditingHandler = getObjectFactoryBeanDefinition(parser.getResolvedBeanName(),
parserContext.extractSource(element));
builder.addConstructorArgValue(isNewAwareAuditingHandler);
if (ReactiveWrappers.isAvailable(ReactiveWrappers.ReactiveLibrary.PROJECT_REACTOR)) {
registerReactiveAuditingEntityCallback(parserContext.getRegistry(), isNewAwareAuditingHandler,
parserContext.extractSource(element));
}
}
private void registerReactiveAuditingEntityCallback(BeanDefinitionRegistry registry,
AbstractBeanDefinition isNewAwareAuditingHandler, @Nullable Object source) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(ReactiveAuditingEntityCallback.class);
builder.addConstructorArgValue(isNewAwareAuditingHandler);
builder.getRawBeanDefinition().setSource(source);
registry.registerBeanDefinition(ReactiveAuditingEntityCallback.class.getName(), builder.getBeanDefinition());
}
}
@@ -1,181 +0,0 @@
/*
* Copyright 2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.config;
import java.lang.annotation.Annotation;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.data.auditing.IsNewAwareAuditingHandler;
import org.springframework.data.auditing.config.AuditingBeanDefinitionRegistrarSupport;
import org.springframework.data.auditing.config.AuditingConfiguration;
import org.springframework.data.config.ParsingUtils;
import org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter;
import org.springframework.data.elasticsearch.core.event.AuditingEntityCallback;
import org.springframework.data.elasticsearch.core.event.ReactiveAuditingEntityCallback;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.repository.util.ReactiveWrappers;
import org.springframework.util.Assert;
/**
* {@link ImportBeanDefinitionRegistrar} to enable {@link EnableElasticsearchAuditing} annotation.
*
* @author Peter-Josef Meisch
* @since 4.0
*/
class ElasticsearchAuditingRegistrar extends AuditingBeanDefinitionRegistrarSupport {
/*
* (non-Javadoc)
* @see org.springframework.data.auditing.config.AuditingBeanDefinitionRegistrarSupport#getAnnotation()
*/
@Override
protected Class<? extends Annotation> getAnnotation() {
return EnableElasticsearchAuditing.class;
}
/*
* (non-Javadoc)
* @see org.springframework.data.auditing.config.AuditingBeanDefinitionRegistrarSupport#getAuditingHandlerBeanName()
*/
@Override
protected String getAuditingHandlerBeanName() {
return "elasticsearchAuditingHandler";
}
/*
* (non-Javadoc)
* @see org.springframework.data.auditing.config.AuditingBeanDefinitionRegistrarSupport#registerBeanDefinitions(org.springframework.core.type.AnnotationMetadata, org.springframework.beans.factory.support.BeanDefinitionRegistry)
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry registry) {
Assert.notNull(annotationMetadata, "AnnotationMetadata must not be null!");
Assert.notNull(registry, "BeanDefinitionRegistry must not be null!");
super.registerBeanDefinitions(annotationMetadata, registry);
}
/*
* (non-Javadoc)
* @see org.springframework.data.auditing.config.AuditingBeanDefinitionRegistrarSupport#getAuditHandlerBeanDefinitionBuilder(org.springframework.data.auditing.config.AuditingConfiguration)
*/
@Override
protected BeanDefinitionBuilder getAuditHandlerBeanDefinitionBuilder(AuditingConfiguration configuration) {
Assert.notNull(configuration, "AuditingConfiguration must not be null!");
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(IsNewAwareAuditingHandler.class);
BeanDefinitionBuilder definition = BeanDefinitionBuilder
.genericBeanDefinition(ElasticsearchMappingContextLookup.class);
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR);
builder.addConstructorArgValue(definition.getBeanDefinition());
return configureDefaultAuditHandlerAttributes(configuration, builder);
}
/*
* (non-Javadoc)
* @see org.springframework.data.auditing.config.AuditingBeanDefinitionRegistrarSupport#registerAuditListener(org.springframework.beans.factory.config.BeanDefinition, org.springframework.beans.factory.support.BeanDefinitionRegistry)
*/
@Override
protected void registerAuditListenerBeanDefinition(BeanDefinition auditingHandlerDefinition,
BeanDefinitionRegistry registry) {
Assert.notNull(auditingHandlerDefinition, "BeanDefinition must not be null!");
Assert.notNull(registry, "BeanDefinitionRegistry must not be null!");
BeanDefinitionBuilder listenerBeanDefinitionBuilder = BeanDefinitionBuilder
.rootBeanDefinition(AuditingEntityCallback.class);
listenerBeanDefinitionBuilder
.addConstructorArgValue(ParsingUtils.getObjectFactoryBeanDefinition(getAuditingHandlerBeanName(), registry));
registerInfrastructureBeanWithId(listenerBeanDefinitionBuilder.getBeanDefinition(),
AuditingEntityCallback.class.getName(), registry);
if (ReactiveWrappers.isAvailable(ReactiveWrappers.ReactiveLibrary.PROJECT_REACTOR)) {
registerReactiveAuditingEntityCallback(registry, auditingHandlerDefinition.getSource());
}
}
private void registerReactiveAuditingEntityCallback(BeanDefinitionRegistry registry, Object source) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(ReactiveAuditingEntityCallback.class);
builder.addConstructorArgValue(ParsingUtils.getObjectFactoryBeanDefinition(getAuditingHandlerBeanName(), registry));
builder.getRawBeanDefinition().setSource(source);
registerInfrastructureBeanWithId(builder.getBeanDefinition(), ReactiveAuditingEntityCallback.class.getName(),
registry);
}
/**
* Simple helper to be able to wire the {@link MappingContext} from a {@link MappingElasticsearchConverter} bean
* available in the application context.
*
* @author Oliver Gierke
*/
static class ElasticsearchMappingContextLookup implements
FactoryBean<MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty>> {
private final MappingElasticsearchConverter converter;
/**
* Creates a new {@link ElasticsearchMappingContextLookup} for the given {@link MappingElasticsearchConverter}.
*
* @param converter must not be {@literal null}.
*/
public ElasticsearchMappingContextLookup(MappingElasticsearchConverter converter) {
this.converter = converter;
}
/*
* (non-Javadoc)
* @see org.springframework.beans.factory.FactoryBean#getObject()
*/
@Override
public MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> getObject()
throws Exception {
return converter.getMappingContext();
}
/*
* (non-Javadoc)
* @see org.springframework.beans.factory.FactoryBean#getObjectType()
*/
@Override
public Class<?> getObjectType() {
return MappingContext.class;
}
/*
* (non-Javadoc)
* @see org.springframework.beans.factory.FactoryBean#isSingleton()
*/
@Override
public boolean isSingleton() {
return true;
}
}
}
@@ -15,6 +15,8 @@
*/
package org.springframework.data.elasticsearch.config;
import lombok.SneakyThrows;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
@@ -28,6 +30,10 @@ import org.springframework.core.convert.converter.Converter;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.data.annotation.Persistent;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.core.DefaultEntityMapper;
import org.springframework.data.elasticsearch.core.DefaultResultMapper;
import org.springframework.data.elasticsearch.core.EntityMapper;
import org.springframework.data.elasticsearch.core.ResultsMapper;
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
import org.springframework.data.elasticsearch.core.convert.ElasticsearchCustomConversions;
import org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter;
@@ -37,19 +43,14 @@ import org.springframework.util.StringUtils;
/**
* @author Christoph Strobl
* @author Peter-Josef Meisch
* @since 3.2
*/
@Configuration
public class ElasticsearchConfigurationSupport {
@Bean
public ElasticsearchConverter elasticsearchEntityMapper(
SimpleElasticsearchMappingContext elasticsearchMappingContext) {
MappingElasticsearchConverter elasticsearchConverter = new MappingElasticsearchConverter(
elasticsearchMappingContext);
elasticsearchConverter.setConversions(elasticsearchCustomConversions());
return elasticsearchConverter;
public ElasticsearchConverter elasticsearchConverter() {
return new MappingElasticsearchConverter(elasticsearchMappingContext());
}
/**
@@ -60,6 +61,7 @@ public class ElasticsearchConfigurationSupport {
* @return never {@literal null}.
*/
@Bean
@SneakyThrows
public SimpleElasticsearchMappingContext elasticsearchMappingContext() {
SimpleElasticsearchMappingContext mappingContext = new SimpleElasticsearchMappingContext();
@@ -69,6 +71,35 @@ public class ElasticsearchConfigurationSupport {
return mappingContext;
}
/**
* Returns the {@link EntityMapper} used for mapping between the source and domain type. <br />
* <strong>Hint</strong>: you can use {@link org.springframework.data.elasticsearch.core.ElasticsearchEntityMapper} as
* an alternative to the {@link DefaultEntityMapper}.
*
* <pre class="code">
* ElasticsearchEntityMapper entityMapper = new ElasticsearchEntityMapper(elasticsearchMappingContext(),
* new DefaultConversionService());
* entityMapper.setConversions(elasticsearchCustomConversions());
* </pre>
*
* @return never {@literal null}.
*/
@Bean
public EntityMapper entityMapper() {
return new DefaultEntityMapper(elasticsearchMappingContext());
}
/**
* Returns the {@link ResultsMapper} to be used for search responses.
*
* @see #entityMapper()
* @return never {@literal null}.
*/
@Bean
public ResultsMapper resultsMapper() {
return new DefaultResultMapper(elasticsearchMappingContext(), entityMapper());
}
/**
* Register custom {@link Converter}s in a {@link ElasticsearchCustomConversions} object if required.
*
@@ -100,8 +131,9 @@ public class ElasticsearchConfigurationSupport {
*
* @see #getMappingBasePackages()
* @return never {@literal null}.
* @throws ClassNotFoundException
*/
protected Set<Class<?>> getInitialEntitySet() {
protected Set<Class<?>> getInitialEntitySet() throws ClassNotFoundException {
Set<Class<?>> initialEntitySet = new HashSet<>();
@@ -118,29 +150,27 @@ public class ElasticsearchConfigurationSupport {
*
* @param basePackage must not be {@literal null}.
* @return never {@literal null}.
* @throws ClassNotFoundException
*/
protected Set<Class<?>> scanForEntities(String basePackage) {
protected Set<Class<?>> scanForEntities(String basePackage) throws ClassNotFoundException {
if (!StringUtils.hasText(basePackage)) {
return Collections.emptySet();
}
Set<Class<?>> initialEntitySet = new HashSet<>();
Set<Class<?>> initialEntitySet = new HashSet<Class<?>>();
ClassPathScanningCandidateComponentProvider componentProvider = new ClassPathScanningCandidateComponentProvider(
false);
componentProvider.addIncludeFilter(new AnnotationTypeFilter(Document.class));
componentProvider.addIncludeFilter(new AnnotationTypeFilter(Persistent.class));
if (StringUtils.hasText(basePackage)) {
for (BeanDefinition candidate : componentProvider.findCandidateComponents(basePackage)) {
ClassPathScanningCandidateComponentProvider componentProvider = new ClassPathScanningCandidateComponentProvider(
false);
componentProvider.addIncludeFilter(new AnnotationTypeFilter(Document.class));
componentProvider.addIncludeFilter(new AnnotationTypeFilter(Persistent.class));
String beanClassName = candidate.getBeanClassName();
for (BeanDefinition candidate : componentProvider.findCandidateComponents(basePackage)) {
if (beanClassName != null) {
try {
initialEntitySet.add(
ClassUtils.forName(beanClassName, AbstractReactiveElasticsearchConfiguration.class.getClassLoader()));
} catch (ClassNotFoundException | LinkageError ignored) {}
initialEntitySet.add(ClassUtils.forName(candidate.getBeanClassName(),
AbstractReactiveElasticsearchConfiguration.class.getClassLoader()));
}
}
@@ -1,70 +0,0 @@
/*
* Copyright 2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.config;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Import;
import org.springframework.data.auditing.DateTimeProvider;
import org.springframework.data.domain.AuditorAware;
/**
* Annotation to enable auditing in Elasticsearch via annotation configuration.
*
* @author Peter-Josef Meisch
* @since 4.0
*/
@Inherited
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(ElasticsearchAuditingRegistrar.class)
public @interface EnableElasticsearchAuditing {
/**
* Configures the {@link AuditorAware} bean to be used to lookup the current principal.
*
* @return
*/
String auditorAwareRef() default "";
/**
* Configures whether the creation and modification dates are set. Defaults to {@literal true}.
*
* @return
*/
boolean setDates() default true;
/**
* Configures whether the entity shall be marked as modified on creation. Defaults to {@literal true}.
*
* @return
*/
boolean modifyOnCreate() default true;
/**
* Configures a {@link DateTimeProvider} bean name that allows customizing the {@link org.joda.time.DateTime} to be
* used for setting creation and modification dates.
*
* @return
*/
String dateTimeProviderRef() default "";
}
@@ -27,9 +27,8 @@ import org.w3c.dom.Element;
*
* @author Rizwan Idrees
* @author Mohsin Husen
* @deprecated as of 4.0
*/
@Deprecated
public class TransportClientBeanDefinitionParser extends AbstractBeanDefinitionParser {
@Override
@@ -1,3 +0,0 @@
@org.springframework.lang.NonNullApi
@org.springframework.lang.NonNullFields
package org.springframework.data.elasticsearch.config;
@@ -1,277 +0,0 @@
/*
* Copyright 2019-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.core;
import static org.springframework.util.StringUtils.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.elasticsearch.action.admin.indices.settings.get.GetSettingsResponse;
import org.elasticsearch.cluster.metadata.AliasMetaData;
import org.elasticsearch.common.collect.MapBuilder;
import org.elasticsearch.common.settings.Settings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.elasticsearch.ElasticsearchException;
import org.springframework.data.elasticsearch.annotations.Mapping;
import org.springframework.data.elasticsearch.annotations.Setting;
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
import org.springframework.data.elasticsearch.core.document.Document;
import org.springframework.data.elasticsearch.core.index.MappingBuilder;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.AliasQuery;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;
/**
* Base implementation of {@link IndexOperations} common to Transport and Rest based Implementations of IndexOperations.
*
* @author Peter-Josef Meisch
* @author Sascha Woo
* @since 4.0
*/
abstract class AbstractDefaultIndexOperations implements IndexOperations {
private static final Logger LOGGER = LoggerFactory.getLogger(AbstractDefaultIndexOperations.class);
protected final ElasticsearchConverter elasticsearchConverter;
protected final RequestFactory requestFactory;
@Nullable protected final Class<?> boundClass;
private final IndexCoordinates boundIndex;
public AbstractDefaultIndexOperations(ElasticsearchConverter elasticsearchConverter, Class<?> boundClass) {
this.elasticsearchConverter = elasticsearchConverter;
requestFactory = new RequestFactory(elasticsearchConverter);
this.boundClass = boundClass;
this.boundIndex = getIndexCoordinatesFor(boundClass);
}
public AbstractDefaultIndexOperations(ElasticsearchConverter elasticsearchConverter, IndexCoordinates boundIndex) {
this.elasticsearchConverter = elasticsearchConverter;
requestFactory = new RequestFactory(elasticsearchConverter);
this.boundClass = null;
this.boundIndex = boundIndex;
}
protected Class<?> checkForBoundClass() {
if (boundClass == null) {
throw new InvalidDataAccessApiUsageException("IndexOperations are not bound");
}
return boundClass;
}
// region IndexOperations
@Override
public boolean create() {
if (boundClass != null) {
Class<?> clazz = boundClass;
String indexName = getIndexCoordinates().getIndexName();
if (clazz.isAnnotationPresent(Setting.class)) {
String settingPath = clazz.getAnnotation(Setting.class).settingPath();
if (hasText(settingPath)) {
String settings = ResourceUtil.readFileFromClasspath(settingPath);
if (hasText(settings)) {
return doCreate(indexName, Document.parse(settings));
}
} else {
LOGGER.info("settingPath in @Setting has to be defined. Using default instead.");
}
}
return doCreate(indexName, getDefaultSettings(getRequiredPersistentEntity(clazz)));
}
return doCreate(getIndexCoordinates().getIndexName(), null);
}
@Override
public boolean create(Document settings) {
return doCreate(getIndexCoordinates().getIndexName(), settings);
}
protected abstract boolean doCreate(String indexName, @Nullable Document settings);
@Override
public boolean delete() {
return doDelete(getIndexCoordinates().getIndexName());
}
protected abstract boolean doDelete(String indexName);
@Override
public boolean exists() {
return doExists(getIndexCoordinates().getIndexName());
}
protected abstract boolean doExists(String indexName);
@Override
public boolean putMapping(Document mapping) {
return doPutMapping(getIndexCoordinates(), mapping);
}
protected abstract boolean doPutMapping(IndexCoordinates index, Document mapping);
@Override
public Map<String, Object> getMapping() {
return doGetMapping(getIndexCoordinates());
}
abstract protected Map<String, Object> doGetMapping(IndexCoordinates index);
@Override
public Map<String, Object> getSettings() {
return getSettings(false);
}
@Override
public Map<String, Object> getSettings(boolean includeDefaults) {
return doGetSettings(getIndexCoordinates().getIndexName(), includeDefaults);
}
protected abstract Map<String, Object> doGetSettings(String indexName, boolean includeDefaults);
@Override
public void refresh() {
doRefresh(getIndexCoordinates());
}
protected abstract void doRefresh(IndexCoordinates indexCoordinates);
@Override
public boolean addAlias(AliasQuery query) {
return doAddAlias(query, getIndexCoordinates());
}
protected abstract boolean doAddAlias(AliasQuery query, IndexCoordinates index);
@Override
public List<AliasMetaData> queryForAlias() {
return doQueryForAlias(getIndexCoordinates().getIndexName());
}
protected abstract List<AliasMetaData> doQueryForAlias(String indexName);
@Override
public boolean removeAlias(AliasQuery query) {
return doRemoveAlias(query, getIndexCoordinates());
}
protected abstract boolean doRemoveAlias(AliasQuery query, IndexCoordinates index);
@Override
public Document createMapping() {
return createMapping(checkForBoundClass());
}
@Override
public Document createMapping(Class<?> clazz) {
return buildMapping(clazz);
}
protected Document buildMapping(Class<?> clazz) {
// load mapping specified in Mapping annotation if present
if (clazz.isAnnotationPresent(Mapping.class)) {
String mappingPath = clazz.getAnnotation(Mapping.class).mappingPath();
if (!StringUtils.isEmpty(mappingPath)) {
String mappings = ResourceUtil.readFileFromClasspath(mappingPath);
if (!StringUtils.isEmpty(mappings)) {
return Document.parse(mappings);
}
} else {
LOGGER.info("mappingPath in @Mapping has to be defined. Building mappings using @Field");
}
}
// build mapping from field annotations
try {
String mapping = new MappingBuilder(elasticsearchConverter).buildPropertyMapping(clazz);
return Document.parse(mapping);
} catch (Exception e) {
throw new ElasticsearchException("Failed to build mapping for " + clazz.getSimpleName(), e);
}
}
// endregion
// region Helper functions
private <T> Document getDefaultSettings(ElasticsearchPersistentEntity<T> persistentEntity) {
if (persistentEntity.isUseServerConfiguration()) {
return Document.create();
}
Map<String, String> map = new MapBuilder<String, String>()
.put("index.number_of_shards", String.valueOf(persistentEntity.getShards()))
.put("index.number_of_replicas", String.valueOf(persistentEntity.getReplicas()))
.put("index.refresh_interval", persistentEntity.getRefreshInterval())
.put("index.store.type", persistentEntity.getIndexStoreType()).map();
return Document.from(map);
}
ElasticsearchPersistentEntity<?> getRequiredPersistentEntity(Class<?> clazz) {
return elasticsearchConverter.getMappingContext().getRequiredPersistentEntity(clazz);
}
/**
* get the current {@link IndexCoordinates}. These may change over time when the entity class has a SpEL constructed
* index name. When this IndexOperations is not bound to a class, the bound IndexCoordinates are returned.
*
* @return IndexCoordinates
*/
protected IndexCoordinates getIndexCoordinates() {
return (boundClass != null) ? getIndexCoordinatesFor(boundClass) : boundIndex;
}
public IndexCoordinates getIndexCoordinatesFor(Class<?> clazz) {
return getRequiredPersistentEntity(clazz).getIndexCoordinates();
}
protected Map<String, Object> convertSettingsResponseToMap(GetSettingsResponse response, String indexName) {
Map<String, Object> settings = new HashMap<>();
if (!response.getIndexToDefaultSettings().isEmpty()) {
Settings defaultSettings = response.getIndexToDefaultSettings().get(indexName);
for (String key : defaultSettings.keySet()) {
settings.put(key, defaultSettings.get(key));
}
}
if (!response.getIndexToSettings().isEmpty()) {
Settings customSettings = response.getIndexToSettings().get(indexName);
for (String key : customSettings.keySet()) {
settings.put(key, customSettings.get(key));
}
}
return settings;
}
// endregion
}
@@ -1,652 +1,54 @@
/*
* Copyright 2019-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.core;
import java.util.ArrayList;
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.stream.Collectors;
import java.util.stream.Stream;
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.common.unit.TimeValue;
import org.elasticsearch.index.query.MoreLikeThisQueryBuilder;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.elasticsearch.ElasticsearchException;
import org.springframework.data.elasticsearch.annotations.Mapping;
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
import org.springframework.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.AfterSaveCallback;
import org.springframework.data.elasticsearch.core.event.BeforeConvertCallback;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext;
import org.springframework.data.elasticsearch.core.query.GetQuery;
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.support.VersionInfo;
import org.springframework.data.mapping.callback.EntityCallbacks;
import org.springframework.data.util.CloseableIterator;
import org.springframework.data.util.Streamable;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* AbstractElasticsearchTemplate
*
*
* @author Sascha Woo
* @author Peter-Josef Meisch
* @author Roman Puchkovskiy
*/
public abstract class AbstractElasticsearchTemplate implements ElasticsearchOperations, ApplicationContextAware {
public abstract class AbstractElasticsearchTemplate {
protected @Nullable ElasticsearchConverter elasticsearchConverter;
protected @Nullable RequestFactory requestFactory;
static final Integer INDEX_MAX_RESULT_WINDOW = 10_000;
private @Nullable EntityCallbacks entityCallbacks;
private static final Logger LOGGER = LoggerFactory.getLogger(AbstractElasticsearchTemplate.class);
// region Initialization
protected void initialize(ElasticsearchConverter elasticsearchConverter) {
protected ElasticsearchConverter elasticsearchConverter;
public AbstractElasticsearchTemplate(ElasticsearchConverter elasticsearchConverter) {
Assert.notNull(elasticsearchConverter, "elasticsearchConverter must not be null.");
this.elasticsearchConverter = elasticsearchConverter;
requestFactory = new RequestFactory(elasticsearchConverter);
VersionInfo.logVersions(getClusterVersion());
}
protected ElasticsearchConverter createElasticsearchConverter() {
MappingElasticsearchConverter mappingElasticsearchConverter = new MappingElasticsearchConverter(
new SimpleElasticsearchMappingContext());
mappingElasticsearchConverter.afterPropertiesSet();
return mappingElasticsearchConverter;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
if (entityCallbacks == null) {
setEntityCallbacks(EntityCallbacks.create(applicationContext));
}
if (elasticsearchConverter instanceof ApplicationContextAware) {
((ApplicationContextAware) elasticsearchConverter).setApplicationContext(applicationContext);
}
}
/**
* 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}.
* @throws IllegalArgumentException if the given instance is {@literal null}.
* @since 4.0
*/
public void setEntityCallbacks(EntityCallbacks entityCallbacks) {
Assert.notNull(entityCallbacks, "entityCallbacks must not be null");
this.entityCallbacks = entityCallbacks;
}
// endregion
// region DocumentOperations
@Override
public <T> T save(T entity) {
Assert.notNull(entity, "entity must not be null");
return save(entity, getIndexCoordinatesFor(entity.getClass()));
}
@Override
public <T> T save(T entity, IndexCoordinates index) {
Assert.notNull(entity, "entity must not be null");
Assert.notNull(index, "index must not be null");
IndexQuery query = getIndexQuery(entity);
index(query, index);
// suppressing because it's either entity itself or something of a correct type returned by an entity callback
@SuppressWarnings("unchecked")
T castResult = (T) query.getObject();
return castResult;
}
@Override
public <T> Iterable<T> save(Iterable<T> entities) {
Assert.notNull(entities, "entities must not be null");
Iterator<T> iterator = entities.iterator();
if (iterator.hasNext()) {
return save(entities, getIndexCoordinatesFor(iterator.next().getClass()));
}
return entities;
}
@Override
public <T> Iterable<T> save(Iterable<T> entities, IndexCoordinates index) {
Assert.notNull(entities, "entities must not be null");
Assert.notNull(index, "index must not be null");
List<IndexQuery> indexQueries = Streamable.of(entities).stream().map(this::getIndexQuery)
.collect(Collectors.toList());
if (!indexQueries.isEmpty()) {
List<String> ids = bulkIndex(indexQueries, index);
Iterator<String> idIterator = ids.iterator();
entities.forEach(entity -> {
setPersistentEntityId(entity, idIterator.next());
});
}
return indexQueries.stream().map(IndexQuery::getObject).map(entity -> (T) entity).collect(Collectors.toList());
}
@Override
public <T> Iterable<T> save(T... entities) {
return save(Arrays.asList(entities));
}
@Override
@Nullable
public <T> T get(String id, Class<T> clazz) {
return get(id, clazz, getIndexCoordinatesFor(clazz));
}
@Override
@Nullable
public <T> T get(GetQuery query, Class<T> clazz, IndexCoordinates index) {
return get(query.getId(), clazz, index);
}
@Override
@Nullable
public <T> T queryForObject(GetQuery query, Class<T> clazz) {
return get(query.getId(), clazz, getIndexCoordinatesFor(clazz));
}
@Override
public boolean exists(String id, Class<?> clazz) {
return exists(id, getIndexCoordinatesFor(clazz));
}
@Override
public boolean exists(String id, IndexCoordinates index) {
return doExists(id, index);
}
abstract protected boolean doExists(String id, IndexCoordinates index);
@Override
public String delete(String id, Class<?> entityType) {
Assert.notNull(id, "id must not be null");
Assert.notNull(entityType, "entityType must not be null");
return this.delete(id, getIndexCoordinatesFor(entityType));
}
@Override
public String delete(Object entity) {
return delete(entity, getIndexCoordinatesFor(entity.getClass()));
}
@Override
public String delete(Object entity, IndexCoordinates index) {
return this.delete(getEntityId(entity), index);
}
// endregion
// region SearchOperations
@Override
public long count(Query query, Class<?> clazz) {
return count(query, clazz, getIndexCoordinatesFor(clazz));
}
@Override
public <T> CloseableIterator<T> stream(Query query, Class<T> clazz, IndexCoordinates index) {
return (CloseableIterator<T>) SearchHitSupport.unwrapSearchHits(searchForStream(query, clazz, index));
}
@Override
public <T> SearchHitsIterator<T> searchForStream(Query query, Class<T> clazz) {
return searchForStream(query, clazz, getIndexCoordinatesFor(clazz));
}
@Override
public <T> SearchHitsIterator<T> searchForStream(Query query, Class<T> clazz, IndexCoordinates index) {
long scrollTimeInMillis = TimeValue.timeValueMinutes(1).millis();
// noinspection ConstantConditions
int maxCount = query.isLimiting() ? query.getMaxResults() : 0;
return StreamQueries.streamResults( //
maxCount, //
searchScrollStart(scrollTimeInMillis, query, clazz, index), //
scrollId -> searchScrollContinue(scrollId, scrollTimeInMillis, clazz, index), //
this::searchScrollClear);
}
@Override
public <T> SearchHits<T> search(MoreLikeThisQuery query, Class<T> clazz) {
return search(query, clazz, getIndexCoordinatesFor(clazz));
}
@Override
public <T> SearchHits<T> search(MoreLikeThisQuery query, Class<T> clazz, IndexCoordinates index) {
Assert.notNull(query.getId(), "No document id defined for MoreLikeThisQuery");
MoreLikeThisQueryBuilder moreLikeThisQueryBuilder = requestFactory.moreLikeThisQueryBuilder(query, index);
return search(new NativeSearchQueryBuilder().withQuery(moreLikeThisQueryBuilder).build(), clazz, index);
}
@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,
IndexCoordinates index) {
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));
}
/*
* internal use only, not for public API
*/
abstract protected <T> SearchScrollHits<T> searchScrollStart(long scrollTimeInMillis, Query query, Class<T> clazz,
IndexCoordinates index);
/*
* internal use only, not for public API
*/
abstract protected <T> SearchScrollHits<T> searchScrollContinue(@Nullable String scrollId, long scrollTimeInMillis,
Class<T> clazz, IndexCoordinates index);
/*
* internal use only, not for public API
*/
protected void searchScrollClear(String scrollId) {
searchScrollClear(Collections.singletonList(scrollId));
}
/*
* internal use only, not for public API
*/
abstract protected void searchScrollClear(List<String> scrollIds);
abstract protected MultiSearchResponse.Item[] getMultiSearchResult(MultiSearchRequest request);
// endregion
// region Helper methods
@Override
public ElasticsearchConverter getElasticsearchConverter() {
Assert.notNull(elasticsearchConverter, "elasticsearchConverter is not initialized.");
return elasticsearchConverter;
}
/**
* @since 4.0
*/
public RequestFactory getRequestFactory() {
Assert.notNull(requestFactory, "requestfactory not initialized");
return requestFactory;
}
protected static String[] toArray(List<String> values) {
String[] valuesAsArray = new String[values.size()];
return values.toArray(valuesAsArray);
}
/**
* @param clazz the entity class
* @return the IndexCoordinates defined on the entity.
* @since 4.0
*/
@Override
public IndexCoordinates getIndexCoordinatesFor(Class<?> clazz) {
return getRequiredPersistentEntity(clazz).getIndexCoordinates();
}
/**
* @param bulkResponse
* @return the list of the item id's
*/
protected List<String> 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::getId).collect(Collectors.toList());
}
protected void setPersistentEntityId(Object entity, String id) {
ElasticsearchPersistentEntity<?> persistentEntity = getRequiredPersistentEntity(entity.getClass());
ElasticsearchPersistentProperty idProperty = persistentEntity.getIdProperty();
// Only deal with text because ES generated Ids are strings!
if (idProperty != null && idProperty.getType().isAssignableFrom(String.class)) {
persistentEntity.getPropertyAccessor(entity).setProperty(idProperty, id);
}
}
ElasticsearchPersistentEntity<?> getRequiredPersistentEntity(Class<?> clazz) {
return elasticsearchConverter.getMappingContext().getRequiredPersistentEntity(clazz);
}
@Nullable
private String getEntityId(Object entity) {
ElasticsearchPersistentEntity<?> persistentEntity = getRequiredPersistentEntity(entity.getClass());
ElasticsearchPersistentProperty idProperty = persistentEntity.getIdProperty();
if (idProperty != null) {
return stringIdRepresentation(persistentEntity.getPropertyAccessor(entity).getProperty(idProperty));
}
return null;
}
@Nullable
private Long getEntityVersion(Object entity) {
ElasticsearchPersistentEntity<?> persistentEntity = getRequiredPersistentEntity(entity.getClass());
ElasticsearchPersistentProperty versionProperty = persistentEntity.getVersionProperty();
if (versionProperty != null) {
Object version = persistentEntity.getPropertyAccessor(entity).getProperty(versionProperty);
if (version != null && Long.class.isAssignableFrom(version.getClass())) {
return ((Long) version);
protected String buildMapping(Class<?> clazz) {
// load mapping specified in Mapping annotation if present
if (clazz.isAnnotationPresent(Mapping.class)) {
String mappingPath = clazz.getAnnotation(Mapping.class).mappingPath();
if (!StringUtils.isEmpty(mappingPath)) {
String mappings = ResourceUtil.readFileFromClasspath(mappingPath);
if (!StringUtils.isEmpty(mappings)) {
return mappings;
}
} else {
LOGGER.info("mappingPath in @Mapping has to be defined. Building mappings using @Field");
}
}
return null;
}
@Nullable
private SeqNoPrimaryTerm getEntitySeqNoPrimaryTerm(Object entity) {
ElasticsearchPersistentEntity<?> persistentEntity = getRequiredPersistentEntity(entity.getClass());
ElasticsearchPersistentProperty property = persistentEntity.getSeqNoPrimaryTermProperty();
if (property != null) {
Object seqNoPrimaryTerm = persistentEntity.getPropertyAccessor(entity).getProperty(property);
if (seqNoPrimaryTerm != null && SeqNoPrimaryTerm.class.isAssignableFrom(seqNoPrimaryTerm.getClass())) {
return (SeqNoPrimaryTerm) seqNoPrimaryTerm;
}
}
return null;
}
private <T> IndexQuery getIndexQuery(T entity) {
String id = getEntityId(entity);
if (id != null) {
id = elasticsearchConverter.convertId(id);
}
IndexQueryBuilder builder = new IndexQueryBuilder() //
.withId(id) //
.withObject(entity);
SeqNoPrimaryTerm seqNoPrimaryTerm = getEntitySeqNoPrimaryTerm(entity);
if (seqNoPrimaryTerm != null) {
builder.withSeqNoPrimaryTerm(seqNoPrimaryTerm);
} else {
// version cannot be used together with seq_no and primary_term
builder.withVersion(getEntityVersion(entity));
}
return builder.build();
}
/**
* tries to extract the version of the Elasticsearch cluster
*
* @return the version as string if it can be retrieved
*/
@Nullable
abstract protected String getClusterVersion();
// endregion
// region Entity callbacks
protected <T> T maybeCallbackBeforeConvert(T entity, IndexCoordinates index) {
if (entityCallbacks != null) {
return entityCallbacks.callback(BeforeConvertCallback.class, entity, index);
}
return entity;
}
protected void maybeCallbackBeforeConvertWithQuery(Object query, IndexCoordinates index) {
if (query instanceof IndexQuery) {
IndexQuery indexQuery = (IndexQuery) query;
Object queryObject = indexQuery.getObject();
if (queryObject != null) {
queryObject = maybeCallbackBeforeConvert(queryObject, index);
indexQuery.setObject(queryObject);
}
// build mapping from field annotations
try {
MappingBuilder mappingBuilder = new MappingBuilder(elasticsearchConverter);
return mappingBuilder.buildPropertyMapping(clazz);
} catch (Exception e) {
throw new ElasticsearchException("Failed to build mapping for " + clazz.getSimpleName(), e);
}
}
// this can be called with either a List<IndexQuery> or a List<UpdateQuery>; these query classes
// don't have a common base class, therefore the List<?> argument
protected void maybeCallbackBeforeConvertWithQueries(List<?> queries, IndexCoordinates index) {
queries.forEach(query -> maybeCallbackBeforeConvertWithQuery(query, index));
}
protected <T> T maybeCallbackAfterSave(T entity, IndexCoordinates index) {
if (entityCallbacks != null) {
return entityCallbacks.callback(AfterSaveCallback.class, entity, index);
}
return entity;
}
protected void maybeCallbackAfterSaveWithQuery(Object query, IndexCoordinates index) {
if (query instanceof IndexQuery) {
IndexQuery indexQuery = (IndexQuery) query;
Object queryObject = indexQuery.getObject();
if (queryObject != null) {
queryObject = maybeCallbackAfterSave(queryObject, index);
indexQuery.setObject(queryObject);
}
}
}
// this can be called with either a List<IndexQuery> or a List<UpdateQuery>; these query classes
// don't have a common base class, therefore the List<?> argument
protected void maybeCallbackAfterSaveWithQueries(List<?> queries, IndexCoordinates index) {
queries.forEach(query -> maybeCallbackAfterSaveWithQuery(query, index));
}
protected <T> T maybeCallbackAfterConvert(T entity, Document document, IndexCoordinates index) {
if (entityCallbacks != null) {
return entityCallbacks.callback(AfterConvertCallback.class, entity, document, index);
}
return entity;
}
// endregion
// region Document callbacks
protected interface DocumentCallback<T> {
@Nullable
T doWith(@Nullable Document document);
}
protected class ReadDocumentCallback<T> implements DocumentCallback<T> {
private final EntityReader<? super T, Document> reader;
private final Class<T> type;
private final IndexCoordinates index;
public ReadDocumentCallback(EntityReader<? super T, Document> reader, Class<T> type, IndexCoordinates index) {
Assert.notNull(reader, "reader is null");
Assert.notNull(type, "type is null");
this.reader = reader;
this.type = type;
this.index = index;
}
@Nullable
public T doWith(@Nullable Document document) {
if (document == null) {
return null;
}
T entity = reader.read(type, document);
return maybeCallbackAfterConvert(entity, document, index);
}
}
protected interface SearchDocumentResponseCallback<T> {
@NonNull
T doWith(@NonNull SearchDocumentResponse response);
}
protected class ReadSearchDocumentResponseCallback<T> implements SearchDocumentResponseCallback<SearchHits<T>> {
private final DocumentCallback<T> delegate;
private final Class<T> type;
public ReadSearchDocumentResponseCallback(Class<T> type, IndexCoordinates index) {
Assert.notNull(type, "type is null");
this.delegate = new ReadDocumentCallback<>(elasticsearchConverter, type, index);
this.type = type;
}
@Override
public SearchHits<T> doWith(SearchDocumentResponse response) {
List<T> entities = response.getSearchDocuments().stream().map(delegate::doWith).collect(Collectors.toList());
return SearchHitMapping.mappingFor(type, elasticsearchConverter.getMappingContext()).mapHits(response, entities);
}
}
protected class ReadSearchScrollDocumentResponseCallback<T>
implements SearchDocumentResponseCallback<SearchScrollHits<T>> {
private final DocumentCallback<T> delegate;
private final Class<T> type;
public ReadSearchScrollDocumentResponseCallback(Class<T> type, IndexCoordinates index) {
Assert.notNull(type, "type is null");
this.delegate = new ReadDocumentCallback<>(elasticsearchConverter, type, index);
this.type = type;
}
@Override
public SearchScrollHits<T> doWith(SearchDocumentResponse response) {
List<T> entities = response.getSearchDocuments().stream().map(delegate::doWith).collect(Collectors.toList());
return SearchHitMapping.mappingFor(type, elasticsearchConverter.getMappingContext()).mapScrollHits(response,
entities);
}
}
// endregion
}
@@ -0,0 +1,73 @@
/*
* Copyright 2014-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.core;
import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
import org.springframework.util.Assert;
/**
* @author Artur Konczak
* @author Christoph Strobl
*/
public abstract class AbstractResultMapper implements ResultsMapper {
private final EntityMapper entityMapper;
private final ProjectionFactory projectionFactory;
/**
* Create a new {@link AbstractResultMapper}.
*
* @param entityMapper must not be {@literal null}.
*/
public AbstractResultMapper(EntityMapper entityMapper) {
this(entityMapper, new SpelAwareProxyProjectionFactory());
}
/**
* Create a new {@link AbstractResultMapper}.
*
* @param entityMapper must not be {@literal null}.
* @param projectionFactory must not be {@literal null}.
* @since 3.2
*/
public AbstractResultMapper(EntityMapper entityMapper, ProjectionFactory projectionFactory) {
Assert.notNull(entityMapper, "EntityMapper must not be null!");
Assert.notNull(projectionFactory, "ProjectionFactory must not be null!");
this.entityMapper = entityMapper;
this.projectionFactory = projectionFactory;
}
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.core.ResultsMapper#getEntityMapper()
*/
@Override
public EntityMapper getEntityMapper() {
return this.entityMapper;
}
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.core.ResultsMapper#getProjectionFactory()
*/
@Override
public ProjectionFactory getProjectionFactory() {
return this.projectionFactory;
}
}
@@ -20,13 +20,10 @@ import static org.springframework.data.elasticsearch.core.query.Criteria.*;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import org.elasticsearch.common.geo.GeoDistance;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.GeoBoundingBoxQueryBuilder;
import org.elasticsearch.index.query.GeoDistanceQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.*;
import org.springframework.data.elasticsearch.core.geo.GeoBox;
import org.springframework.data.elasticsearch.core.geo.GeoPoint;
import org.springframework.data.elasticsearch.core.query.Criteria;
@@ -34,7 +31,6 @@ import org.springframework.data.geo.Box;
import org.springframework.data.geo.Distance;
import org.springframework.data.geo.Metrics;
import org.springframework.data.geo.Point;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
@@ -43,25 +39,28 @@ import org.springframework.util.Assert;
* @author Franck Marchand
* @author Mohsin Husen
* @author Artur Konczak
* @author Peter-Josef Meisch
*
*/
class CriteriaFilterProcessor {
QueryBuilder createFilterFromCriteria(Criteria criteria) {
List<QueryBuilder> fbList = new LinkedList<>();
QueryBuilder filter = null;
for (Criteria chainedCriteria : criteria.getCriteriaChain()) {
ListIterator<Criteria> chainIterator = criteria.getCriteriaChain().listIterator();
while (chainIterator.hasNext()) {
QueryBuilder fb = null;
Criteria chainedCriteria = chainIterator.next();
if (chainedCriteria.isOr()) {
fb = QueryBuilders.boolQuery();
for (QueryBuilder f : createFilterFragmentForCriteria(chainedCriteria)) {
((BoolQueryBuilder) fb).should(f);
for(QueryBuilder f: createFilterFragmentForCriteria(chainedCriteria)){
((BoolQueryBuilder)fb).should(f);
}
fbList.add(fb);
} else if (chainedCriteria.isNegating()) {
List<QueryBuilder> negationFilters = buildNegationFilter(criteria.getField().getName(),
criteria.getFilterCriteriaEntries().iterator());
List<QueryBuilder> negationFilters = buildNegationFilter(criteria.getField().getName(), criteria.getFilterCriteriaEntries().iterator());
if (!negationFilters.isEmpty()) {
fbList.addAll(negationFilters);
@@ -76,14 +75,15 @@ class CriteriaFilterProcessor {
filter = fbList.get(0);
} else {
filter = QueryBuilders.boolQuery();
for (QueryBuilder f : fbList) {
((BoolQueryBuilder) filter).must(f);
for(QueryBuilder f: fbList) {
((BoolQueryBuilder)filter).must(f);
}
}
}
return filter;
}
private List<QueryBuilder> createFilterFragmentForCriteria(Criteria chainedCriteria) {
Iterator<Criteria.CriteriaEntry> it = chainedCriteria.getFilterCriteriaEntries().iterator();
List<QueryBuilder> filterList = new LinkedList<>();
@@ -101,9 +101,8 @@ class CriteriaFilterProcessor {
return filterList;
}
@Nullable
private QueryBuilder processCriteriaEntry(OperationKey key, Object value, String fieldName) {
private QueryBuilder processCriteriaEntry(OperationKey key, Object value, String fieldName) {
if (value == null) {
return null;
}
@@ -117,10 +116,8 @@ class CriteriaFilterProcessor {
Object[] valArray = (Object[]) value;
Assert.noNullElements(valArray, "Geo distance filter takes 2 not null elements array as parameter.");
Assert.isTrue(valArray.length == 2, "Geo distance filter takes a 2-elements array as parameter.");
Assert.isTrue(valArray[0] instanceof GeoPoint || valArray[0] instanceof String || valArray[0] instanceof Point,
"First element of a geo distance filter must be a GeoPoint, a Point or a text");
Assert.isTrue(valArray[1] instanceof String || valArray[1] instanceof Distance,
"Second element of a geo distance filter must be a text or a Distance");
Assert.isTrue(valArray[0] instanceof GeoPoint || valArray[0] instanceof String || valArray[0] instanceof Point, "First element of a geo distance filter must be a GeoPoint, a Point or a text");
Assert.isTrue(valArray[1] instanceof String || valArray[1] instanceof Distance, "Second element of a geo distance filter must be a text or a Distance");
StringBuilder dist = new StringBuilder();
@@ -132,18 +129,15 @@ class CriteriaFilterProcessor {
if (valArray[0] instanceof GeoPoint) {
GeoPoint loc = (GeoPoint) valArray[0];
geoDistanceQueryBuilder.point(loc.getLat(), loc.getLon()).distance(dist.toString())
.geoDistance(GeoDistance.PLANE);
geoDistanceQueryBuilder.point(loc.getLat(),loc.getLon()).distance(dist.toString()).geoDistance(GeoDistance.PLANE);
} else if (valArray[0] instanceof Point) {
GeoPoint loc = GeoPoint.fromPoint((Point) valArray[0]);
geoDistanceQueryBuilder.point(loc.getLat(), loc.getLon()).distance(dist.toString())
.geoDistance(GeoDistance.PLANE);
geoDistanceQueryBuilder.point(loc.getLat(), loc.getLon()).distance(dist.toString()).geoDistance(GeoDistance.PLANE);
} else {
String loc = (String) valArray[0];
if (loc.contains(",")) {
String[] c = loc.split(",");
geoDistanceQueryBuilder.point(Double.parseDouble(c[0]), Double.parseDouble(c[1])).distance(dist.toString())
.geoDistance(GeoDistance.PLANE);
String c[] = loc.split(",");
geoDistanceQueryBuilder.point(Double.parseDouble(c[0]), Double.parseDouble(c[1])).distance(dist.toString()).geoDistance(GeoDistance.PLANE);
} else {
geoDistanceQueryBuilder.geohash(loc).distance(dist.toString()).geoDistance(GeoDistance.PLANE);
}
@@ -156,22 +150,20 @@ class CriteriaFilterProcessor {
case BBOX: {
filter = QueryBuilders.geoBoundingBoxQuery(fieldName);
Assert.isTrue(value instanceof Object[],
"Value of a boundedBy filter should be an array of one or two values.");
Assert.isTrue(value instanceof Object[], "Value of a boundedBy filter should be an array of one or two values.");
Object[] valArray = (Object[]) value;
Assert.noNullElements(valArray, "Geo boundedBy filter takes a not null element array as parameter.");
if (valArray.length == 1) {
// GeoEnvelop
//GeoEnvelop
oneParameterBBox((GeoBoundingBoxQueryBuilder) filter, valArray[0]);
} else if (valArray.length == 2) {
// 2x GeoPoint
// 2x text
//2x GeoPoint
//2x text
twoParameterBBox((GeoBoundingBoxQueryBuilder) filter, valArray);
} else {
// error
Assert.isTrue(false,
"Geo distance filter takes a 1-elements array(GeoBox) or 2-elements array(GeoPoints or Strings(format lat,lon or geohash)).");
//error
Assert.isTrue(false, "Geo distance filter takes a 1-elements array(GeoBox) or 2-elements array(GeoPoints or Strings(format lat,lon or geohash)).");
}
break;
}
@@ -180,6 +172,7 @@ class CriteriaFilterProcessor {
return filter;
}
/**
* extract the distance string from a {@link org.springframework.data.geo.Distance} object.
*
@@ -203,8 +196,7 @@ class CriteriaFilterProcessor {
}
private void oneParameterBBox(GeoBoundingBoxQueryBuilder filter, Object value) {
Assert.isTrue(value instanceof GeoBox || value instanceof Box,
"single-element of boundedBy filter must be type of GeoBox or Box");
Assert.isTrue(value instanceof GeoBox || value instanceof Box, "single-element of boundedBy filter must be type of GeoBox or Box");
GeoBox geoBBox;
if (value instanceof Box) {
@@ -214,8 +206,7 @@ class CriteriaFilterProcessor {
geoBBox = (GeoBox) value;
}
filter.setCorners(geoBBox.getTopLeft().getLat(), geoBBox.getTopLeft().getLon(), geoBBox.getBottomRight().getLat(),
geoBBox.getBottomRight().getLon());
filter.setCorners(geoBBox.getTopLeft().getLat(), geoBBox.getTopLeft().getLon(), geoBBox.getBottomRight().getLat(), geoBBox.getBottomRight().getLon());
}
private static boolean isType(Object[] array, Class clazz) {
@@ -228,8 +219,7 @@ class CriteriaFilterProcessor {
}
private void twoParameterBBox(GeoBoundingBoxQueryBuilder filter, Object[] values) {
Assert.isTrue(isType(values, GeoPoint.class) || isType(values, String.class),
" both elements of boundedBy filter must be type of GeoPoint or text(format lat,lon or geohash)");
Assert.isTrue(isType(values, GeoPoint.class) || isType(values, String.class), " both elements of boundedBy filter must be type of GeoPoint or text(format lat,lon or geohash)");
if (values[0] instanceof GeoPoint) {
GeoPoint topLeft = (GeoPoint) values[0];
GeoPoint bottomRight = (GeoPoint) values[1];
@@ -246,8 +236,7 @@ class CriteriaFilterProcessor {
while (it.hasNext()) {
Criteria.CriteriaEntry criteriaEntry = it.next();
QueryBuilder notFilter = QueryBuilders.boolQuery()
.mustNot(processCriteriaEntry(criteriaEntry.getKey(), criteriaEntry.getValue(), fieldName));
QueryBuilder notFilter = QueryBuilders.boolQuery().mustNot(processCriteriaEntry(criteriaEntry.getKey(), criteriaEntry.getValue(), fieldName));
notFilterList.add(notFilter);
}
@@ -15,22 +15,18 @@
*/
package org.springframework.data.elasticsearch.core;
import static org.elasticsearch.index.query.Operator.*;
import static org.elasticsearch.index.query.Operator.AND;
import static org.elasticsearch.index.query.QueryBuilders.*;
import static org.springframework.data.elasticsearch.core.query.Criteria.*;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import org.apache.lucene.queryparser.flexible.core.util.StringUtils;
import org.apache.lucene.queryparser.flexible.standard.QueryParserUtil;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.*;
import org.springframework.data.elasticsearch.core.query.Criteria;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
@@ -40,15 +36,13 @@ import org.springframework.util.Assert;
* @author Mohsin Husen
* @author Franck Marchand
* @author Artur Konczak
* @author Rasmus Faber-Espensen
* @author James Bodkin
* @author Peter-Josef Meisch
*/
class CriteriaQueryProcessor {
QueryBuilder createQueryFromCriteria(Criteria criteria) {
Assert.notNull(criteria, "criteria must not be null");
QueryBuilder createQueryFromCriteria(Criteria criteria) {
if (criteria == null)
return null;
List<QueryBuilder> shouldQueryBuilderList = new LinkedList<>();
List<QueryBuilder> mustNotQueryBuilderList = new LinkedList<>();
@@ -110,7 +104,7 @@ class CriteriaQueryProcessor {
return query;
}
@Nullable
private QueryBuilder createQueryFragmentForCriteria(Criteria chainedCriteria) {
if (chainedCriteria.getQueryCriteriaEntries().isEmpty())
return null;
@@ -137,39 +131,34 @@ class CriteriaQueryProcessor {
return query;
}
@Nullable
private QueryBuilder processCriteriaEntry(Criteria.CriteriaEntry entry, String fieldName) {
OperationKey key = entry.getKey();
private QueryBuilder processCriteriaEntry(Criteria.CriteriaEntry entry,/* OperationKey key, Object value,*/ String fieldName) {
Object value = entry.getValue();
if (value == null) {
if (key == OperationKey.EXISTS) {
return existsQuery(fieldName);
} else {
return null;
}
return null;
}
String searchText = QueryParserUtil.escape(value.toString());
OperationKey key = entry.getKey();
QueryBuilder query = null;
String searchText = StringUtils.toString(value);
Iterable<Object> collection = null;
switch (key) {
case EQUALS:
query = queryStringQuery(searchText).field(fieldName).defaultOperator(AND);
break;
case CONTAINS:
query = queryStringQuery('*' + searchText + '*').field(fieldName).analyzeWildcard(true);
query = queryStringQuery("*" + searchText + "*").field(fieldName).analyzeWildcard(true);
break;
case STARTS_WITH:
query = queryStringQuery(searchText + '*').field(fieldName).analyzeWildcard(true);
query = queryStringQuery(searchText + "*").field(fieldName).analyzeWildcard(true);
break;
case ENDS_WITH:
query = queryStringQuery('*' + searchText).field(fieldName).analyzeWildcard(true);
query = queryStringQuery("*" + searchText).field(fieldName).analyzeWildcard(true);
break;
case EXPRESSION:
query = queryStringQuery(value.toString()).field(fieldName);
query = queryStringQuery(searchText).field(fieldName);
break;
case LESS_EQUAL:
query = rangeQuery(fieldName).lte(value);
@@ -191,23 +180,23 @@ class CriteriaQueryProcessor {
query = fuzzyQuery(fieldName, searchText);
break;
case IN:
query = boolQuery().must(termsQuery(fieldName, toStringList((Iterable<Object>) value)));
query = boolQuery();
collection = (Iterable<Object>) value;
for (Object item : collection) {
((BoolQueryBuilder) query).should(queryStringQuery(item.toString()).field(fieldName));
}
break;
case NOT_IN:
query = boolQuery().mustNot(termsQuery(fieldName, toStringList((Iterable<Object>) value)));
query = boolQuery();
collection = (Iterable<Object>) value;
for (Object item : collection) {
((BoolQueryBuilder) query).mustNot(queryStringQuery(item.toString()).field(fieldName));
}
break;
}
return query;
}
private static List<String> toStringList(Iterable<?> iterable) {
List<String> list = new ArrayList<>();
for (Object item : iterable) {
list.add(StringUtils.toString(item));
}
return list;
}
private void addBoost(QueryBuilder query, float boost) {
if (Float.isNaN(boost)) {
return;
@@ -0,0 +1,189 @@
/*
* Copyright 2014-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.core;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.data.annotation.ReadOnlyProperty;
import org.springframework.data.elasticsearch.core.geo.CustomGeoModule;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
import org.springframework.data.mapping.MappingException;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.util.Assert;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationConfig;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.BeanPropertyWriter;
import com.fasterxml.jackson.databind.ser.BeanSerializerModifier;
/**
* EntityMapper based on a Jackson {@link ObjectMapper}.
*
* @author Artur Konczak
* @author Petar Tahchiev
* @author Oliver Gierke
* @author Christoph Strobl
*/
public class DefaultEntityMapper implements EntityMapper {
private ObjectMapper objectMapper;
/**
* Creates a new {@link DefaultEntityMapper} using the given {@link MappingContext}.
*
* @param context must not be {@literal null}.
*/
public DefaultEntityMapper(
MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> context) {
Assert.notNull(context, "MappingContext must not be null!");
objectMapper = new ObjectMapper();
objectMapper.registerModule(new SpringDataElasticsearchModule(context));
objectMapper.registerModule(new CustomGeoModule());
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
}
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.core.EntityMapper#mapToString(java.lang.Object)
*/
@Override
public String mapToString(Object object) throws IOException {
return objectMapper.writeValueAsString(object);
}
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.core.EntityMapper#mapObject(java.lang.Object)
*/
@Override
public Map<String, Object> mapObject(Object source) {
try {
return objectMapper.readValue(mapToString(source), HashMap.class);
} catch (IOException e) {
throw new MappingException(e.getMessage(), e);
}
}
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.core.EntityMapper#mapToObject(java.lang.String, java.lang.Class)
*/
@Override
public <T> T mapToObject(String source, Class<T> clazz) throws IOException {
return objectMapper.readValue(source, clazz);
}
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.core.EntityMapper#readObject(java.util.Map, java.lang.Class)
*/
@Override
public <T> T readObject (Map<String, Object> source, Class<T> targetType) {
try {
return mapToObject(mapToString(source), targetType);
} catch (IOException e) {
throw new MappingException(e.getMessage(), e);
}
}
/**
* A simple Jackson module to register the {@link SpringDataSerializerModifier}.
*
* @author Oliver Gierke
* @since 3.1
*/
private static class SpringDataElasticsearchModule extends SimpleModule {
private static final long serialVersionUID = -9168968092458058966L;
/**
* Creates a new {@link SpringDataElasticsearchModule} using the given {@link MappingContext}.
*
* @param context must not be {@literal null}.
*/
public SpringDataElasticsearchModule(
MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> context) {
Assert.notNull(context, "MappingContext must not be null!");
setSerializerModifier(new SpringDataSerializerModifier(context));
}
/**
* A {@link BeanSerializerModifier} that will drop properties annotated with {@link ReadOnlyProperty} for
* serialization.
*
* @author Oliver Gierke
* @since 3.1
*/
private static class SpringDataSerializerModifier extends BeanSerializerModifier {
private final MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> context;
public SpringDataSerializerModifier(
MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> context) {
Assert.notNull(context, "MappingContext must not be null!");
this.context = context;
}
/*
* (non-Javadoc)
* @see com.fasterxml.jackson.databind.ser.BeanSerializerModifier#changeProperties(com.fasterxml.jackson.databind.SerializationConfig, com.fasterxml.jackson.databind.BeanDescription, java.util.List)
*/
@Override
public List<BeanPropertyWriter> changeProperties(SerializationConfig config, BeanDescription description,
List<BeanPropertyWriter> properties) {
Class<?> type = description.getBeanClass();
ElasticsearchPersistentEntity<?> entity = context.getPersistentEntity(type);
if (entity == null) {
return super.changeProperties(config, description, properties);
}
List<BeanPropertyWriter> result = new ArrayList<>(properties.size());
for (BeanPropertyWriter beanPropertyWriter : properties) {
ElasticsearchPersistentProperty property = entity.getPersistentProperty(beanPropertyWriter.getName());
if (property != null && property.isWritable()) {
result.add(beanPropertyWriter);
}
}
return result;
}
}
}
}
@@ -1,231 +0,0 @@
/*
* Copyright 2019-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.core;
import static org.elasticsearch.client.Requests.*;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.http.util.EntityUtils;
import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequest;
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
import org.elasticsearch.action.admin.indices.settings.get.GetSettingsRequest;
import org.elasticsearch.action.admin.indices.settings.get.GetSettingsResponse;
import org.elasticsearch.client.Request;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.Response;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.indices.CreateIndexRequest;
import org.elasticsearch.client.indices.GetIndexRequest;
import org.elasticsearch.client.indices.PutMappingRequest;
import org.elasticsearch.cluster.metadata.AliasMetaData;
import org.springframework.data.elasticsearch.UncategorizedElasticsearchException;
import org.springframework.data.elasticsearch.core.client.support.AliasData;
import org.springframework.data.elasticsearch.core.document.Document;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.AliasQuery;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* {@link IndexOperations} implementation using the RestClient.
*
* @author Peter-Josef Meisch
* @author Sascha Woo
* @since 4.0
*/
class DefaultIndexOperations extends AbstractDefaultIndexOperations implements IndexOperations {
private ElasticsearchRestTemplate restTemplate;
public DefaultIndexOperations(ElasticsearchRestTemplate restTemplate, Class<?> boundClass) {
super(restTemplate.getElasticsearchConverter(), boundClass);
this.restTemplate = restTemplate;
}
public DefaultIndexOperations(ElasticsearchRestTemplate restTemplate, IndexCoordinates boundIndex) {
super(restTemplate.getElasticsearchConverter(), boundIndex);
this.restTemplate = restTemplate;
}
@Override
protected boolean doCreate(String indexName, @Nullable Document settings) {
CreateIndexRequest request = requestFactory.createIndexRequest(indexName, settings);
return restTemplate.execute(client -> client.indices().create(request, RequestOptions.DEFAULT).isAcknowledged());
}
@Override
protected boolean doDelete(String indexName) {
Assert.notNull(indexName, "No index defined for delete operation");
if (doExists(indexName)) {
DeleteIndexRequest request = new DeleteIndexRequest(indexName);
return restTemplate.execute(client -> client.indices().delete(request, RequestOptions.DEFAULT).isAcknowledged());
}
return false;
}
@Override
protected boolean doExists(String indexName) {
GetIndexRequest request = new GetIndexRequest(indexName);
return restTemplate.execute(client -> client.indices().exists(request, RequestOptions.DEFAULT));
}
@Override
protected boolean doPutMapping(IndexCoordinates index, Document mapping) {
Assert.notNull(index, "No index defined for putMapping()");
PutMappingRequest request = requestFactory.putMappingRequest(index, mapping);
return restTemplate
.execute(client -> client.indices().putMapping(request, RequestOptions.DEFAULT).isAcknowledged());
}
@Override
protected Map<String, Object> doGetMapping(IndexCoordinates index) {
Assert.notNull(index, "No index defined for getMapping()");
return restTemplate.execute(client -> {
RestClient restClient = client.getLowLevelClient();
Request request = new Request("GET", '/' + index.getIndexName() + "/_mapping");
Response response = restClient.performRequest(request);
return convertMappingResponse(EntityUtils.toString(response.getEntity()));
});
}
@Override
protected boolean doAddAlias(AliasQuery query, IndexCoordinates index) {
IndicesAliasesRequest request = requestFactory.indicesAddAliasesRequest(query, index);
return restTemplate
.execute(client -> client.indices().updateAliases(request, RequestOptions.DEFAULT).isAcknowledged());
}
@Override
protected boolean doRemoveAlias(AliasQuery query, IndexCoordinates index) {
Assert.notNull(index, "No index defined for Alias");
Assert.notNull(query.getAliasName(), "No alias defined");
IndicesAliasesRequest indicesAliasesRequest = requestFactory.indicesRemoveAliasesRequest(query, index);
return restTemplate.execute(
client -> client.indices().updateAliases(indicesAliasesRequest, RequestOptions.DEFAULT).isAcknowledged());
}
@Override
protected List<AliasMetaData> doQueryForAlias(String indexName) {
List<AliasMetaData> aliases = null;
return restTemplate.execute(client -> {
RestClient restClient = client.getLowLevelClient();
Response response;
String aliasResponse;
response = restClient.performRequest(new Request("GET", '/' + indexName + "/_alias/*"));
aliasResponse = EntityUtils.toString(response.getEntity());
return convertAliasResponse(aliasResponse);
});
}
@Override
protected Map<String, Object> doGetSettings(String indexName, boolean includeDefaults) {
Assert.notNull(indexName, "No index defined for getSettings");
GetSettingsRequest request = new GetSettingsRequest() //
.indices(indexName) //
.includeDefaults(includeDefaults);
//
GetSettingsResponse response = restTemplate.execute(client -> client.indices() //
.getSettings(request, RequestOptions.DEFAULT));
return convertSettingsResponseToMap(response, indexName);
}
@Override
protected void doRefresh(IndexCoordinates index) {
Assert.notNull(index, "No index defined for refresh()");
restTemplate
.execute(client -> client.indices().refresh(refreshRequest(index.getIndexNames()), RequestOptions.DEFAULT));
}
// region Helper methods
private Map<String, Object> convertMappingResponse(String mappingResponse) {
ObjectMapper mapper = new ObjectMapper();
try {
Map<String, Object> result = null;
JsonNode node = mapper.readTree(mappingResponse);
node = node.findValue("mappings");
result = mapper.readValue(mapper.writeValueAsString(node), HashMap.class);
return result;
} catch (IOException e) {
throw new UncategorizedElasticsearchException("Could not map alias response : " + mappingResponse, e);
}
}
/**
* It takes two steps to create a List<AliasMetadata> from the elasticsearch http response because the aliases field
* is actually a Map by alias name, but the alias name is on the AliasMetadata.
*
* @param aliasResponse
* @return
*/
private List<AliasMetaData> convertAliasResponse(String aliasResponse) {
ObjectMapper mapper = new ObjectMapper();
try {
JsonNode node = mapper.readTree(aliasResponse);
node = node.findValue("aliases");
if (node == null) {
return Collections.emptyList();
}
Map<String, AliasData> aliasData = mapper.readValue(mapper.writeValueAsString(node),
new TypeReference<Map<String, AliasData>>() {});
Iterable<Map.Entry<String, AliasData>> aliasIter = aliasData.entrySet();
List<AliasMetaData> aliasMetaDataList = new ArrayList<>();
for (Map.Entry<String, AliasData> aliasentry : aliasIter) {
AliasData data = aliasentry.getValue();
aliasMetaDataList.add(AliasMetaData.newAliasMetaDataBuilder(aliasentry.getKey()).filter(data.getFilter())
.routing(data.getRouting()).searchRouting(data.getSearch_routing()).indexRouting(data.getIndex_routing())
.build());
}
return aliasMetaDataList;
} catch (IOException e) {
throw new UncategorizedElasticsearchException("Could not map alias response : " + aliasResponse, e);
}
}
// endregion
}
@@ -0,0 +1,253 @@
/*
* Copyright 2014-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.core;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.get.MultiGetItemResponse;
import org.elasticsearch.action.get.MultiGetResponse;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.common.document.DocumentField;
import org.elasticsearch.search.SearchHit;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.ElasticsearchException;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.ScriptedField;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPageImpl;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext;
import org.springframework.data.mapping.PersistentPropertyAccessor;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import com.fasterxml.jackson.core.JsonEncoding;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
/**
* @author Artur Konczak
* @author Petar Tahchiev
* @author Young Gu
* @author Oliver Gierke
* @author Chris White
* @author Mark Paluch
* @author Ilkang Na
* @author Sascha Woo
* @author Christoph Strobl
* @author Dmitriy Yakovlev
*/
public class DefaultResultMapper extends AbstractResultMapper {
private final MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext;
private final ConversionService conversionService = new DefaultConversionService();
public DefaultResultMapper() {
this(new SimpleElasticsearchMappingContext());
}
public DefaultResultMapper(
MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext) {
this(mappingContext, initEntityMapper(mappingContext));
}
public DefaultResultMapper(EntityMapper entityMapper) {
this(new SimpleElasticsearchMappingContext(), entityMapper);
}
public DefaultResultMapper(
MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext,
@Nullable EntityMapper entityMapper) {
super(entityMapper != null ? entityMapper : initEntityMapper(mappingContext));
this.mappingContext = mappingContext;
}
private static EntityMapper initEntityMapper(
MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext) {
Assert.notNull(mappingContext, "MappingContext must not be null!");
return new DefaultEntityMapper(mappingContext);
}
@Override
public <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> clazz, Pageable pageable) {
long totalHits = response.getHits().getTotalHits();
float maxScore = response.getHits().getMaxScore();
List<T> results = new ArrayList<>();
for (SearchHit hit : response.getHits()) {
if (hit != null) {
T result = null;
String hitSourceAsString = hit.getSourceAsString();
if (!StringUtils.isEmpty(hitSourceAsString)) {
result = mapEntity(hitSourceAsString, clazz);
} else {
result = mapEntity(hit.getFields().values(), clazz);
}
setPersistentEntityId(result, hit.getId(), clazz);
setPersistentEntityVersion(result, hit.getVersion(), clazz);
setPersistentEntityScore(result, hit.getScore(), clazz);
populateScriptFields(result, hit);
results.add(result);
}
}
return new AggregatedPageImpl<T>(results, pageable, totalHits, response.getAggregations(), response.getScrollId(),
maxScore);
}
private <T> void populateScriptFields(T result, SearchHit hit) {
if (hit.getFields() != null && !hit.getFields().isEmpty() && result != null) {
for (java.lang.reflect.Field field : result.getClass().getDeclaredFields()) {
ScriptedField scriptedField = field.getAnnotation(ScriptedField.class);
if (scriptedField != null) {
String name = scriptedField.name().isEmpty() ? field.getName() : scriptedField.name();
DocumentField searchHitField = hit.getFields().get(name);
if (searchHitField != null) {
field.setAccessible(true);
try {
field.set(result, searchHitField.getValue());
} catch (IllegalArgumentException e) {
throw new ElasticsearchException(
"failed to set scripted field: " + name + " with value: " + searchHitField.getValue(), e);
} catch (IllegalAccessException e) {
throw new ElasticsearchException("failed to access scripted field: " + name, e);
}
}
}
}
}
}
private <T> T mapEntity(Collection<DocumentField> values, Class<T> clazz) {
return mapEntity(buildJSONFromFields(values), clazz);
}
private String buildJSONFromFields(Collection<DocumentField> values) {
JsonFactory nodeFactory = new JsonFactory();
try {
ByteArrayOutputStream stream = new ByteArrayOutputStream();
JsonGenerator generator = nodeFactory.createGenerator(stream, JsonEncoding.UTF8);
generator.writeStartObject();
for (DocumentField value : values) {
if (value.getValues().size() > 1) {
generator.writeArrayFieldStart(value.getName());
for (Object val : value.getValues()) {
generator.writeObject(val);
}
generator.writeEndArray();
} else {
generator.writeObjectField(value.getName(), value.getValue());
}
}
generator.writeEndObject();
generator.flush();
return new String(stream.toByteArray(), Charset.forName("UTF-8"));
} catch (IOException e) {
return null;
}
}
@Override
public <T> T mapResult(GetResponse response, Class<T> clazz) {
T result = mapEntity(response.getSourceAsString(), clazz);
if (result != null) {
setPersistentEntityId(result, response.getId(), clazz);
setPersistentEntityVersion(result, response.getVersion(), clazz);
}
return result;
}
@Override
public <T> List<T> mapResults(MultiGetResponse responses, Class<T> clazz) {
List<T> list = new ArrayList<>();
for (MultiGetItemResponse response : responses.getResponses()) {
if (!response.isFailed() && response.getResponse().isExists()) {
T result = mapEntity(response.getResponse().getSourceAsString(), clazz);
setPersistentEntityId(result, response.getResponse().getId(), clazz);
setPersistentEntityVersion(result, response.getResponse().getVersion(), clazz);
list.add(result);
}
}
return list;
}
private <T> void setPersistentEntityId(T result, String id, Class<T> clazz) {
if (clazz.isAnnotationPresent(Document.class)) {
ElasticsearchPersistentEntity<?> persistentEntity = mappingContext.getRequiredPersistentEntity(clazz);
ElasticsearchPersistentProperty idProperty = persistentEntity.getIdProperty();
PersistentPropertyAccessor<T> accessor = new ConvertingPropertyAccessor<>(
persistentEntity.getPropertyAccessor(result), conversionService);
// Only deal with String because ES generated Ids are strings !
if (idProperty != null && idProperty.getType().isAssignableFrom(String.class)) {
accessor.setProperty(idProperty, id);
}
}
}
private <T> void setPersistentEntityVersion(T result, long version, Class<T> clazz) {
if (clazz.isAnnotationPresent(Document.class)) {
ElasticsearchPersistentEntity<?> persistentEntity = mappingContext.getPersistentEntity(clazz);
ElasticsearchPersistentProperty versionProperty = persistentEntity.getVersionProperty();
// Only deal with Long because ES versions are longs !
if (versionProperty != null && versionProperty.getType().isAssignableFrom(Long.class)) {
// check that a version was actually returned in the response, -1 would indicate that
// a search didn't request the version ids in the response, which would be an issue
Assert.isTrue(version != -1, "Version in response is -1");
persistentEntity.getPropertyAccessor(result).setProperty(versionProperty, version);
}
}
}
private <T> void setPersistentEntityScore(T result, float score, Class<T> clazz) {
if (clazz.isAnnotationPresent(Document.class)) {
ElasticsearchPersistentEntity<?> entity = mappingContext.getRequiredPersistentEntity(clazz);
if (!entity.hasScoreProperty()) {
return;
}
entity.getPropertyAccessor(result) //
.setProperty(entity.getScoreProperty(), score);
}
}
}
@@ -1,158 +0,0 @@
/*
* Copyright 2019-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.core;
import static org.elasticsearch.client.Requests.*;
import java.util.List;
import java.util.Map;
import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequest;
import org.elasticsearch.action.admin.indices.alias.get.GetAliasesRequest;
import org.elasticsearch.action.admin.indices.create.CreateIndexRequestBuilder;
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
import org.elasticsearch.action.admin.indices.mapping.get.GetMappingsRequest;
import org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequestBuilder;
import org.elasticsearch.action.admin.indices.settings.get.GetSettingsRequest;
import org.elasticsearch.action.admin.indices.settings.get.GetSettingsResponse;
import org.elasticsearch.client.Client;
import org.elasticsearch.cluster.metadata.AliasMetaData;
import org.springframework.data.elasticsearch.ElasticsearchException;
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
import org.springframework.data.elasticsearch.core.document.Document;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.AliasQuery;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* {@link IndexOperations} implementation using the TransportClient.
*
* @author Peter-Josef Meisch
* @author Sascha Woo
* @since 4.0
*/
class DefaultTransportIndexOperations extends AbstractDefaultIndexOperations implements IndexOperations {
private final Client client;
public DefaultTransportIndexOperations(Client client, ElasticsearchConverter elasticsearchConverter,
Class<?> boundClass) {
super(elasticsearchConverter, boundClass);
this.client = client;
}
public DefaultTransportIndexOperations(Client client, ElasticsearchConverter elasticsearchConverter,
IndexCoordinates boundIndex) {
super(elasticsearchConverter, boundIndex);
this.client = client;
}
@Override
protected boolean doCreate(String indexName, @Nullable Document settings) {
CreateIndexRequestBuilder createIndexRequestBuilder = requestFactory.createIndexRequestBuilder(client, indexName,
settings);
return createIndexRequestBuilder.execute().actionGet().isAcknowledged();
}
@Override
protected boolean doDelete(String indexName) {
Assert.notNull(indexName, "No index defined for delete operation");
if (doExists(indexName)) {
return client.admin().indices().delete(new DeleteIndexRequest(indexName)).actionGet().isAcknowledged();
}
return false;
}
@Override
protected boolean doExists(String indexName) {
return client.admin().indices().exists(indicesExistsRequest(indexName)).actionGet().isExists();
}
@Override
protected boolean doPutMapping(IndexCoordinates index, Document mapping) {
Assert.notNull(index, "No index defined for putMapping()");
PutMappingRequestBuilder requestBuilder = requestFactory.putMappingRequestBuilder(client, index, mapping);
return requestBuilder.execute().actionGet().isAcknowledged();
}
@Override
protected Map<String, Object> doGetMapping(IndexCoordinates index) {
Assert.notNull(index, "No index defined for getMapping()");
try {
return client.admin().indices().getMappings( //
new GetMappingsRequest().indices(index.getIndexNames())).actionGet() //
.getMappings().get(index.getIndexName()).get(IndexCoordinates.TYPE) //
.getSourceAsMap();
} catch (Exception e) {
throw new ElasticsearchException("Error while getting mapping for indexName : " + index.getIndexName(), e);
}
}
@Override
protected boolean doAddAlias(AliasQuery query, IndexCoordinates index) {
IndicesAliasesRequest.AliasActions aliasAction = requestFactory.aliasAction(query, index);
return client.admin().indices().prepareAliases().addAliasAction(aliasAction).execute().actionGet().isAcknowledged();
}
@Override
protected boolean doRemoveAlias(AliasQuery query, IndexCoordinates index) {
Assert.notNull(index, "No index defined for Alias");
Assert.notNull(query.getAliasName(), "No alias defined");
return client.admin().indices().prepareAliases().removeAlias(index.getIndexName(), query.getAliasName()).execute()
.actionGet().isAcknowledged();
}
@Override
protected List<AliasMetaData> doQueryForAlias(String indexName) {
return client.admin().indices().getAliases(new GetAliasesRequest().indices(indexName)).actionGet().getAliases()
.get(indexName);
}
@Override
protected Map<String, Object> doGetSettings(String indexName, boolean includeDefaults) {
Assert.notNull(indexName, "No index defined for getSettings");
GetSettingsRequest request = new GetSettingsRequest() //
.indices(indexName) //
.includeDefaults(includeDefaults);
GetSettingsResponse response = client.admin() //
.indices() //
.getSettings(request) //
.actionGet();
return convertSettingsResponseToMap(response, indexName);
}
@Override
protected void doRefresh(IndexCoordinates index) {
Assert.notNull(index, "No index defined for refresh()");
client.admin().indices().refresh(refreshRequest(index.getIndexNames())).actionGet();
}
}
@@ -1,271 +0,0 @@
/*
* Copyright 2019-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.core;
import java.util.List;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.BulkOptions;
import org.springframework.data.elasticsearch.core.query.DeleteQuery;
import org.springframework.data.elasticsearch.core.query.GetQuery;
import org.springframework.data.elasticsearch.core.query.IndexQuery;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.data.elasticsearch.core.query.UpdateQuery;
import org.springframework.data.elasticsearch.core.query.UpdateResponse;
import org.springframework.lang.Nullable;
/**
* The operations for the
* <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/docs.html">Elasticsearch Document APIs</a>.
*
* @author Peter-Josef Meisch
* @since 4.0
*/
public interface DocumentOperations {
/**
* Saves an entity to the index specified in the entity's Document annotation
*
* @param entity the entity to save, must not be {@literal null}
* @param <T> the entity type
* @return the saved entity
*/
<T> T save(T entity);
/**
* Saves an entity to the index specified in the entity's Document annotation
*
* @param entity the entity to save, must not be {@literal null}
* @param index the index to save the entity in, must not be {@literal null}
* @param <T> the entity type
* @return the saved entity
*/
<T> T save(T entity, IndexCoordinates index);
/**
* saves the given entities to the index retrieved from the entities' Document annotation
*
* @param entities must not be {@literal null}
* @param <T> the entity type
* @return the saved entites
*/
<T> Iterable<T> save(Iterable<T> entities);
/**
* saves the given entities to the given index
*
* @param entities must not be {@literal null}
* @param index the index to save the entities in, must not be {@literal null}
* @param <T> the entity type
* @return the saved entities
*/
<T> Iterable<T> save(Iterable<T> entities, IndexCoordinates index);
/**
* saves the given entities to the index retrieved from the entities' Document annotation
*
* @param entities must not be {@literal null}
* @param <T> the entity type
* @return the saved entities as Iterable
*/
<T> Iterable<T> save(T... entities);
/**
* Index an object. Will do save or update.
*
* @param query the query defining the object
* @param index the index where the object is stored.
* @return returns the document id
*/
String index(IndexQuery query, IndexCoordinates index);
/**
* Retrieves an object from the index specified in the entity's Document annotation.
*
* @param id the id of the object
* @param clazz the entity class,
* @param <T> the entity type
* @return the entity
*/
@Nullable
<T> T get(String id, Class<T> clazz);
/**
* Retrieves an object from the index specified in the entity's Document annotation.
*
* @param id the id of the object
* @param clazz the entity class,
* @param index the index from which the object is read.
* @return the entity
*/
@Nullable
<T> T get(String id, Class<T> clazz, IndexCoordinates index);
/**
* Execute a multiGet against elasticsearch for the given ids.
*
* @param query the query defining the ids of the objects to get
* @param clazz the type of the object to be returned
* @param index the index(es) from which the objects are read.
* @return list of objects, contains null values for ids that are not found
*/
<T> List<T> multiGet(Query query, Class<T> clazz, IndexCoordinates index);
/**
* Check if an entity with given {@literal id} exists.
*
* @param id the {@literal _id} of the document to look for.
* @param clazz the domain type used.
* @return {@literal true} if a matching document exists, {@literal false} otherwise.
*/
boolean exists(String id, Class<?> clazz);
/**
* Check if an entity with given {@literal id} exists.
*
* @param id the {@literal _id} of the document to look for.
* @param index the target index, must not be {@literal null}
* @return {@literal true} if a matching document exists, {@literal false} otherwise.
*/
boolean exists(String id, IndexCoordinates index);
/**
* Bulk index all objects. Will do save or update.
*
* @param queries the queries to execute in bulk
* @return the ids of the indexed objects
*/
default List<String> bulkIndex(List<IndexQuery> queries, IndexCoordinates index) {
return bulkIndex(queries, BulkOptions.defaultOptions(), index);
}
/**
* Bulk index all objects. Will do save or update.
*
* @param queries the queries to execute in bulk
* @param bulkOptions options to be added to the bulk request
* @return the ids of the indexed objects
*/
List<String> bulkIndex(List<IndexQuery> queries, BulkOptions bulkOptions, IndexCoordinates index);
/**
* Bulk update all objects. Will do update.
*
* @param queries the queries to execute in bulk
*/
default void bulkUpdate(List<UpdateQuery> queries, IndexCoordinates index) {
bulkUpdate(queries, BulkOptions.defaultOptions(), index);
}
/**
* Bulk update all objects. Will do update.
*
* @param queries the queries to execute in bulk
* @param bulkOptions options to be added to the bulk request
*/
void bulkUpdate(List<UpdateQuery> queries, BulkOptions bulkOptions, IndexCoordinates index);
/**
* Delete the one object with provided id.
*
* @param id the document ot delete
* @param index the index from which to delete
* @return documentId of the document deleted
*/
String delete(String id, IndexCoordinates index);
/**
* Delete the one object with provided id.
*
* @param id the document ot delete
* @param entityType must not be {@literal null}.
* @return documentId of the document deleted
*/
String delete(String id, Class<?> entityType);
/**
* Deletes the given entity
*
* @param entity the entity to delete
* @return documentId of the document deleted
*/
String delete(Object entity);
/**
* Deletes the given entity
*
* @param entity the entity to delete
* @param index the index from which to delete
* @return documentId of the document deleted
*/
String delete(Object entity, IndexCoordinates index);
/**
* Delete all records matching the query.
*
* @param query query defining the objects
* @param clazz The entity class, must be annotated with
* {@link org.springframework.data.elasticsearch.annotations.Document}
* @param index the index from which to delete
*/
void delete(Query query, Class<?> clazz, IndexCoordinates index);
/**
* Partial update of the document.
*
* @param updateQuery query defining the update
* @param index the index where to update the records
* @return the update response
*/
UpdateResponse update(UpdateQuery updateQuery, IndexCoordinates index);
// region deprecated
/**
* Delete all records matching the query.
*
* @param query query defining the objects
* @param index the index where to delete the records
* @deprecated since 4.0, use {@link #delete(Query, Class, IndexCoordinates)}
*/
@Deprecated
void delete(DeleteQuery query, IndexCoordinates index);
/**
* Retrieves an object from an index.
*
* @param query the query defining the id of the object to get
* @param clazz the type of the object to be returned
* @param index the index from which the object is read.
* @return the found object
* @deprecated since 4.0, use {@link #get(String, Class, IndexCoordinates)}
*/
@Deprecated
@Nullable
<T> T get(GetQuery query, Class<T> clazz, IndexCoordinates index);
/**
* Retrieves an object from an index.
*
* @param query the query defining the id of the object to get
* @param clazz the type of the object to be returned
* @return the found object
* @deprecated since 4.0, use {@link #get(String, Class, IndexCoordinates)}
*/
@Deprecated
@Nullable
<T> T queryForObject(GetQuery query, Class<T> clazz);
// endregion
}
@@ -0,0 +1,718 @@
/*
* Copyright 2019-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.core;
import lombok.RequiredArgsConstructor;
import java.io.IOException;
import java.util.*;
import java.util.Map.Entry;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.CollectionFactory;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.data.convert.CustomConversions;
import org.springframework.data.convert.EntityConverter;
import org.springframework.data.convert.EntityInstantiator;
import org.springframework.data.convert.EntityInstantiators;
import org.springframework.data.convert.EntityReader;
import org.springframework.data.convert.EntityWriter;
import org.springframework.data.elasticsearch.core.convert.ElasticsearchCustomConversions;
import org.springframework.data.elasticsearch.core.convert.ElasticsearchTypeMapper;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
import org.springframework.data.mapping.PersistentPropertyAccessor;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
import org.springframework.data.mapping.model.PersistentEntityParameterValueProvider;
import org.springframework.data.mapping.model.PropertyValueProvider;
import org.springframework.data.util.ClassTypeInformation;
import org.springframework.data.util.Streamable;
import org.springframework.data.util.TypeInformation;
import org.springframework.format.datetime.DateFormatterRegistrar;
import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectReader;
import com.fasterxml.jackson.databind.ObjectWriter;
/**
* Elasticsearch specific {@link EntityReader} & {@link EntityWriter} implementation based on domain type
* {@link ElasticsearchPersistentEntity metadata}.
*
* @author Christoph Strobl
* @author Peter-Josef Meisch
* @since 3.2
*/
public class ElasticsearchEntityMapper implements
EntityConverter<ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty, Object, Map<String, Object>>,
EntityWriter<Object, Map<String, Object>>, EntityReader<Object, Map<String, Object>>, InitializingBean,
EntityMapper {
private final MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext;
private final GenericConversionService conversionService;
private final ObjectReader objectReader;
private final ObjectWriter objectWriter;
private CustomConversions conversions = new ElasticsearchCustomConversions(Collections.emptyList());
private EntityInstantiators instantiators = new EntityInstantiators();
private ElasticsearchTypeMapper typeMapper;
public ElasticsearchEntityMapper(
MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext,
@Nullable GenericConversionService conversionService) {
this.mappingContext = mappingContext;
this.conversionService = conversionService != null ? conversionService : new DefaultConversionService();
this.typeMapper = ElasticsearchTypeMapper.create(mappingContext);
ObjectMapper objectMapper = new ObjectMapper();
objectReader = objectMapper.readerFor(HashMap.class);
objectWriter = objectMapper.writer();
}
// --> GETTERS / SETTERS
@Override
public MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> getMappingContext() {
return mappingContext;
}
@Override
public ConversionService getConversionService() {
return conversionService;
}
/**
* Set the {@link CustomConversions} to be applied during the mapping process. <br />
* Conversions are registered after {@link #afterPropertiesSet() bean initialization}.
*
* @param conversions must not be {@literal null}.
*/
public void setConversions(CustomConversions conversions) {
this.conversions = conversions;
}
/**
* Set the {@link ElasticsearchTypeMapper} to use for reading / writing type hints.
*
* @param typeMapper must not be {@literal null}.
*/
public void setTypeMapper(ElasticsearchTypeMapper typeMapper) {
this.typeMapper = typeMapper;
}
/*
* (non-Javadoc)
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
*/
@Override
public void afterPropertiesSet() {
DateFormatterRegistrar.addDateConverters(conversionService);
conversions.registerConvertersIn(conversionService);
}
// --> READ
@Override
public <T> T readObject(Map<String, Object> source, Class<T> targetType) {
return read(targetType, source);
}
@SuppressWarnings("unchecked")
@Override
@Nullable
public <R> R read(Class<R> type, Map<String, Object> source) {
return doRead(source, ClassTypeInformation.from((Class<R>) ClassUtils.getUserClass(type)));
}
@SuppressWarnings("unchecked")
@Nullable
protected <R> R doRead(Map<String, Object> source, TypeInformation<R> typeHint) {
if (source == null) {
return null;
}
typeHint = (TypeInformation<R>) typeMapper.readType(source, typeHint);
if (conversions.hasCustomReadTarget(Map.class, typeHint.getType())) {
return conversionService.convert(source, typeHint.getType());
}
if (typeHint.isMap() || ClassTypeInformation.OBJECT.equals(typeHint)) {
return (R) source;
}
ElasticsearchPersistentEntity<?> entity = mappingContext.getRequiredPersistentEntity(typeHint);
return readEntity(entity, source);
}
@SuppressWarnings("unchecked")
protected <R> R readEntity(ElasticsearchPersistentEntity<?> entity, Map<String, Object> source) {
ElasticsearchPersistentEntity<?> targetEntity = computeClosestEntity(entity, source);
ElasticsearchPropertyValueProvider propertyValueProvider = new ElasticsearchPropertyValueProvider(
new MapValueAccessor(source));
EntityInstantiator instantiator = instantiators.getInstantiatorFor(targetEntity);
R instance = (R) instantiator.createInstance(targetEntity,
new PersistentEntityParameterValueProvider<>(targetEntity, propertyValueProvider, null));
return targetEntity.requiresPropertyPopulation() ? readProperties(targetEntity, instance, propertyValueProvider)
: instance;
}
protected <R> R readProperties(ElasticsearchPersistentEntity<?> entity, R instance,
ElasticsearchPropertyValueProvider valueProvider) {
PersistentPropertyAccessor<R> accessor = new ConvertingPropertyAccessor<>(entity.getPropertyAccessor(instance),
conversionService);
for (ElasticsearchPersistentProperty prop : entity) {
if (entity.isConstructorArgument(prop) || prop.isScoreProperty()) {
continue;
}
Object value = valueProvider.getPropertyValue(prop);
if (value != null) {
accessor.setProperty(prop, valueProvider.getPropertyValue(prop));
}
}
return accessor.getBean();
}
@SuppressWarnings("unchecked")
protected <R> R readValue(@Nullable Object source, ElasticsearchPersistentProperty property,
TypeInformation<R> targetType) {
if (source == null) {
return null;
}
Class<R> rawType = targetType.getType();
if (conversions.hasCustomReadTarget(source.getClass(), rawType)) {
return rawType.cast(conversionService.convert(source, rawType));
} else if (source instanceof List) {
return readCollectionValue((List) source, property, targetType);
} else if (source instanceof Map) {
return readMapValue((Map<String, Object>) source, property, targetType);
}
return (R) readSimpleValue(source, targetType);
}
@SuppressWarnings("unchecked")
private <R> R readMapValue(@Nullable Map<String, Object> source, ElasticsearchPersistentProperty property,
TypeInformation<R> targetType) {
TypeInformation information = typeMapper.readType(source);
if (property.isEntity() && !property.isMap() || information != null) {
ElasticsearchPersistentEntity<?> targetEntity = information != null
? mappingContext.getRequiredPersistentEntity(information)
: mappingContext.getRequiredPersistentEntity(property);
return readEntity(targetEntity, source);
}
Map<String, Object> target = new LinkedHashMap<>();
for (Entry<String, Object> entry : source.entrySet()) {
String entryKey = entry.getKey();
Object entryValue = entry.getValue();
if (entryValue == null) {
target.put(entryKey, null);
} else if (isSimpleType(entryValue)) {
target.put(entryKey,
readSimpleValue(entryValue, targetType.isMap() ? targetType.getComponentType() : targetType));
} else {
ElasticsearchPersistentEntity<?> targetEntity = computeGenericValueTypeForRead(property, entryValue);
if (targetEntity.getTypeInformation().isMap()) {
Map<String, Object> valueMap = (Map) entryValue;
if (typeMapper.containsTypeInformation(valueMap)) {
target.put(entryKey, readEntity(targetEntity, (Map) entryValue));
} else {
target.put(entryKey, readValue(valueMap, property, targetEntity.getTypeInformation()));
}
} else if (targetEntity.getTypeInformation().isCollectionLike()) {
target.put(entryKey, readValue(entryValue, property, targetEntity.getTypeInformation().getActualType()));
} else {
target.put(entryKey, readEntity(targetEntity, (Map) entryValue));
}
}
}
return (R) target;
}
@SuppressWarnings("unchecked")
private <R> R readCollectionValue(@Nullable List<?> source, ElasticsearchPersistentProperty property,
TypeInformation<R> targetType) {
if (source == null) {
return null;
}
Collection<Object> target = createCollectionForValue(targetType, source.size());
for (Object value : source) {
if (value == null) {
target.add(null);
} else if (isSimpleType(value)) {
target.add(
readSimpleValue(value, targetType.getComponentType() != null ? targetType.getComponentType() : targetType));
} else {
if (value instanceof List) {
target.add(readValue(value, property, property.getTypeInformation().getActualType()));
} else {
target.add(readEntity(computeGenericValueTypeForRead(property, value), (Map) value));
}
}
}
return (R) target;
}
@SuppressWarnings("unchecked")
private Object readSimpleValue(@Nullable Object value, TypeInformation<?> targetType) {
Class<?> target = targetType.getType();
if (value == null || target == null || ClassUtils.isAssignableValue(target, value)) {
return value;
}
if (conversions.hasCustomReadTarget(value.getClass(), target)) {
return conversionService.convert(value, target);
}
if (Enum.class.isAssignableFrom(target)) {
return Enum.valueOf((Class<Enum>) target, value.toString());
}
return conversionService.convert(value, target);
}
// --> WRITE
@Override
public Map<String, Object> mapObject(Object source) {
LinkedHashMap<String, Object> target = new LinkedHashMap<>();
write(source, target);
return target;
}
@SuppressWarnings("unchecked")
@Override
public void write(@Nullable Object source, Map<String, Object> sink) {
if (source == null) {
return;
}
if (source instanceof Map) {
sink.putAll((Map<String, Object>) source);
return;
}
Class<?> entityType = ClassUtils.getUserClass(source.getClass());
TypeInformation<?> type = ClassTypeInformation.from(entityType);
if (requiresTypeHint(type, source.getClass(), null)) {
typeMapper.writeType(source.getClass(), sink);
}
doWrite(source, sink, type);
}
protected void doWrite(@Nullable Object source, Map<String, Object> sink,
@Nullable TypeInformation<? extends Object> typeHint) {
if (source == null) {
return;
}
Class<?> entityType = source.getClass();
Optional<Class<?>> customTarget = conversions.getCustomWriteTarget(entityType, Map.class);
if (customTarget.isPresent()) {
sink.putAll(conversionService.convert(source, Map.class));
return;
}
if (typeHint != null) {
ElasticsearchPersistentEntity<?> entity = typeHint.getType().equals(entityType)
? mappingContext.getRequiredPersistentEntity(typeHint)
: mappingContext.getRequiredPersistentEntity(entityType);
writeEntity(entity, source, sink, null);
return;
}
// write Entity
ElasticsearchPersistentEntity<?> entity = mappingContext.getRequiredPersistentEntity(entityType);
writeEntity(entity, source, sink, null);
}
protected void writeEntity(ElasticsearchPersistentEntity<?> entity, Object source, Map<String, Object> sink,
@Nullable TypeInformation containingStructure) {
PersistentPropertyAccessor<?> accessor = entity.getPropertyAccessor(source);
if (requiresTypeHint(entity.getTypeInformation(), source.getClass(), containingStructure)) {
typeMapper.writeType(source.getClass(), sink);
}
writeProperties(entity, accessor, sink);
}
protected void writeProperties(ElasticsearchPersistentEntity<?> entity, PersistentPropertyAccessor<?> accessor,
Map<String, Object> sink) {
for (ElasticsearchPersistentProperty property : entity) {
if (!property.isWritable()) {
continue;
}
Object value = accessor.getProperty(property);
if (value == null) {
continue;
}
if (!isSimpleType(value)) {
writeProperty(property, value, sink);
} else {
sink.put(property.getFieldName(), getWriteSimpleValue(value));
}
}
}
protected void writeProperty(ElasticsearchPersistentProperty property, Object value, Map<String, Object> sink) {
Optional<Class<?>> customWriteTarget = conversions.getCustomWriteTarget(value.getClass());
if (customWriteTarget.isPresent()) {
Class<?> writeTarget = customWriteTarget.get();
sink.put(property.getFieldName(), conversionService.convert(value, writeTarget));
return;
}
TypeInformation<?> typeHint = property.getTypeInformation();
if (typeHint.equals(ClassTypeInformation.OBJECT)) {
if (value instanceof List) {
typeHint = ClassTypeInformation.LIST;
} else if (value instanceof Map) {
typeHint = ClassTypeInformation.MAP;
} else if (value instanceof Set) {
typeHint = ClassTypeInformation.SET;
} else if (value instanceof Collection) {
typeHint = ClassTypeInformation.COLLECTION;
}
}
sink.put(property.getFieldName(), getWriteComplexValue(property, typeHint, value));
}
protected Object getWriteSimpleValue(Object value) {
if (value == null) {
return null;
}
Optional<Class<?>> customTarget = conversions.getCustomWriteTarget(value.getClass());
if (customTarget.isPresent()) {
return conversionService.convert(value, customTarget.get());
}
return Enum.class.isAssignableFrom(value.getClass()) ? ((Enum<?>) value).name() : value;
}
@SuppressWarnings("unchecked")
protected Object getWriteComplexValue(ElasticsearchPersistentProperty property, TypeInformation<?> typeHint,
Object value) {
if (typeHint.isCollectionLike() || value instanceof Iterable) {
return writeCollectionValue(value, property, typeHint);
}
if (typeHint.isMap()) {
return writeMapValue((Map<String, Object>) value, property, typeHint);
}
if (property.isEntity() || !isSimpleType(value)) {
return writeEntity(value, property, typeHint);
}
return value;
}
private Object writeEntity(Object value, ElasticsearchPersistentProperty property, TypeInformation<?> typeHint) {
Map<String, Object> target = new LinkedHashMap<>();
writeEntity(mappingContext.getRequiredPersistentEntity(value.getClass()), value, target,
property.getTypeInformation());
return target;
}
private Object writeMapValue(Map<String, Object> value, ElasticsearchPersistentProperty property,
TypeInformation<?> typeHint) {
Map<Object, Object> target = new LinkedHashMap<>();
Streamable<Entry<String, Object>> mapSource = Streamable.of(value.entrySet());
if (!typeHint.getActualType().getType().equals(Object.class)
&& isSimpleType(typeHint.getMapValueType().getType())) {
mapSource.forEach(it -> {
if (it.getValue() == null) {
target.put(it.getKey(), null);
} else {
target.put(it.getKey(), getWriteSimpleValue(it.getValue()));
}
});
} else {
mapSource.forEach(it -> {
Object converted = null;
if (it.getValue() != null) {
if (isSimpleType(it.getValue())) {
converted = getWriteSimpleValue(it.getValue());
} else {
converted = getWriteComplexValue(property, ClassTypeInformation.from(it.getValue().getClass()),
it.getValue());
}
}
target.put(it.getKey(), converted);
});
}
return target;
}
private Object writeCollectionValue(Object value, ElasticsearchPersistentProperty property,
TypeInformation<?> typeHint) {
Streamable<?> collectionSource = value instanceof Iterable ? Streamable.of((Iterable<?>) value)
: Streamable.of(ObjectUtils.toObjectArray(value));
List<Object> target = new ArrayList<>();
if (!typeHint.getActualType().getType().equals(Object.class) && isSimpleType(typeHint.getActualType().getType())) {
collectionSource.map(this::getWriteSimpleValue).forEach(target::add);
} else {
collectionSource.map(it -> {
if (it == null) {
return null;
}
if (isSimpleType(it)) {
return getWriteSimpleValue(it);
}
return getWriteComplexValue(property, ClassTypeInformation.from(it.getClass()), it);
}).forEach(target::add);
}
return target;
}
// --> LEGACY
@Override
public String mapToString(Object source) throws IOException {
Map<String, Object> sink = new LinkedHashMap<>();
write(source, sink);
return objectWriter.writeValueAsString(sink);
}
@Override
public <T> T mapToObject(String source, Class<T> clazz) throws IOException {
return read(clazz, objectReader.readValue(source));
}
// --> PRIVATE HELPERS
private boolean requiresTypeHint(TypeInformation<?> type, Class<?> actualType,
@Nullable TypeInformation<?> container) {
if (container != null) {
if (container.isCollectionLike()) {
if (type.equals(container.getActualType()) && type.getType().equals(actualType)) {
return false;
}
}
if (container.isMap()) {
if (type.equals(container.getMapValueType()) && type.getType().equals(actualType)) {
return false;
}
}
if (container.equals(type) && type.getType().equals(actualType)) {
return false;
}
}
return !conversions.isSimpleType(type.getType()) && !type.isCollectionLike()
&& !conversions.hasCustomWriteTarget(type.getType());
}
/**
* Compute the type to use by checking the given entity against the store type;
*
* @param entity
* @param source
* @return
*/
private ElasticsearchPersistentEntity<?> computeClosestEntity(ElasticsearchPersistentEntity<?> entity,
Map<String, Object> source) {
TypeInformation<?> typeToUse = typeMapper.readType(source);
if (typeToUse == null) {
return entity;
}
if (!entity.getTypeInformation().getType().isInterface() && !entity.getTypeInformation().isCollectionLike()
&& !entity.getTypeInformation().isMap()
&& !ClassUtils.isAssignableValue(entity.getType(), typeToUse.getType())) {
return entity;
}
return mappingContext.getRequiredPersistentEntity(typeToUse);
}
private ElasticsearchPersistentEntity<?> computeGenericValueTypeForRead(ElasticsearchPersistentProperty property,
Object value) {
return ClassTypeInformation.OBJECT.equals(property.getTypeInformation().getActualType())
? mappingContext.getRequiredPersistentEntity(value.getClass())
: mappingContext.getRequiredPersistentEntity(property.getTypeInformation().getActualType());
}
private Collection<Object> createCollectionForValue(TypeInformation<?> collectionTypeInformation, int size) {
Class<?> collectionType = collectionTypeInformation.isSubTypeOf(Collection.class) //
? collectionTypeInformation.getType() //
: List.class;
TypeInformation<?> componentType = collectionTypeInformation.getComponentType() != null //
? collectionTypeInformation.getComponentType() //
: ClassTypeInformation.OBJECT;
return collectionTypeInformation.getType().isArray() //
? new ArrayList<>(size) //
: CollectionFactory.createCollection(collectionType, componentType.getType(), size);
}
private boolean isSimpleType(Object value) {
return isSimpleType(value.getClass());
}
private boolean isSimpleType(Class<?> type) {
return conversions.isSimpleType(type);
}
// --> OHTER STUFF
@RequiredArgsConstructor
class ElasticsearchPropertyValueProvider implements PropertyValueProvider<ElasticsearchPersistentProperty> {
final MapValueAccessor mapValueAccessor;
@SuppressWarnings("unchecked")
@Override
public <T> T getPropertyValue(ElasticsearchPersistentProperty property) {
return (T) readValue(mapValueAccessor.get(property), property, property.getTypeInformation());
}
}
static class MapValueAccessor {
final Map<String, Object> target;
MapValueAccessor(Map<String, Object> target) {
this.target = target;
}
public Object get(ElasticsearchPersistentProperty property) {
String fieldName = property.getFieldName();
if (!fieldName.contains(".")) {
return target.get(fieldName);
}
Iterator<String> parts = Arrays.asList(fieldName.split("\\.")).iterator();
Map<String, Object> source = target;
Object result = null;
while (source != null && parts.hasNext()) {
result = source.get(parts.next());
if (parts.hasNext()) {
source = getAsMap(result);
}
}
return result;
}
@SuppressWarnings("unchecked")
private Map<String, Object> getAsMap(Object result) {
if (result instanceof Map) {
return (Map) result;
}
throw new IllegalArgumentException(String.format("%s is not a Map.", result));
}
}
}
@@ -13,37 +13,24 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.core;
import java.io.IOException;
import java.net.ConnectException;
import java.util.List;
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.UncategorizedElasticsearchException;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
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
* @author Mark Paluch
* @since 3.2
*/
public class ElasticsearchExceptionTranslator implements PersistenceExceptionTranslator {
@@ -51,10 +38,6 @@ public class ElasticsearchExceptionTranslator implements PersistenceExceptionTra
@Override
public DataAccessException translateExceptionIfPossible(RuntimeException ex) {
if (isSeqNoConflict(ex)) {
return new OptimisticLockingFailureException("Cannot index a document due to seq_no+primary_term conflict", ex);
}
if (ex instanceof ElasticsearchException) {
ElasticsearchException elasticsearchException = (ElasticsearchException) ex;
@@ -63,52 +46,22 @@ public class ElasticsearchExceptionTranslator implements PersistenceExceptionTra
return new NoSuchIndexException(ObjectUtils.nullSafeToString(elasticsearchException.getMetadata("es.index")),
ex);
}
return new UncategorizedElasticsearchException(ex.getMessage(), ex);
}
if (ex instanceof ValidationException) {
return new DataIntegrityViolationException(ex.getMessage(), ex);
}
Throwable cause = ex.getCause();
if (cause instanceof IOException) {
if (ex.getCause() instanceof ConnectException) {
return new DataAccessResourceFailureException(ex.getMessage(), ex);
}
return null;
}
private boolean isSeqNoConflict(Exception exception) {
if (exception instanceof ElasticsearchStatusException) {
ElasticsearchStatusException statusException = (ElasticsearchStatusException) exception;
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 VersionConflictEngineException) {
VersionConflictEngineException versionConflictEngineException = (VersionConflictEngineException) exception;
return versionConflictEngineException.getMessage() != null
&& versionConflictEngineException.getMessage().contains("version conflict, required seqNo");
}
return false;
}
private boolean indexAvailable(ElasticsearchException ex) {
List<String> metadata = ex.getMetadata("es.index_uuid");
if (metadata == null) {
if (ex instanceof ElasticsearchStatusException) {
return StringUtils.hasText(ObjectUtils.nullSafeToString(ex.getIndex()));
return StringUtils.hasText(ObjectUtils.nullSafeToString(((ElasticsearchStatusException) ex).getIndex()));
}
return false;
}
return !CollectionUtils.contains(metadata.iterator(), "_na_");
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,57 @@
/*
* Copyright 2014-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.core;
import java.io.IOException;
import java.util.Map;
import org.springframework.lang.Nullable;
/**
* DocumentMapper interface, it will allow to customize how we mapping object to json
*
* @author Artur Konczak
* @author Rizwan Idrees
* @author Mohsin Husen
* @author Christoph Strobl
*/
public interface EntityMapper {
String mapToString(Object object) throws IOException;
<T> T mapToObject(String source, Class<T> clazz) throws IOException;
/**
* Map the given {@literal source} to {@link Map}.
*
* @param source must not be {@literal null}.
* @return never {@literal null}
* @since 3.2
*/
Map<String, Object> mapObject(Object source);
/**
* Map the given {@link Map} into an instance of the {@literal targetType}.
*
* @param source must not be {@literal null}.
* @param targetType must not be {@literal null}.
* @param <T>
* @return can be {@literal null}.
* @since 3.2
*/
@Nullable
<T> T readObject(Map<String, Object> source, Class<T> targetType);
}
@@ -15,13 +15,16 @@
*/
package org.springframework.data.elasticsearch.core;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import java.util.Map;
import org.springframework.core.convert.ConversionService;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.SeqNoPrimaryTerm;
import org.springframework.data.mapping.IdentifierAccessor;
import org.springframework.data.mapping.PersistentPropertyAccessor;
import org.springframework.data.mapping.context.MappingContext;
@@ -35,23 +38,14 @@ import org.springframework.util.StringUtils;
*
* @author Mark Paluch
* @author Christoph Strobl
* @author Peter-Josef Meisch
* @author Roman Puchkovskiy
* @since 3.2
*/
@RequiredArgsConstructor
class EntityOperations {
private static final String ID_FIELD = "id";
public EntityOperations(
MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> context) {
Assert.notNull(context, "context must not be null");
this.context = context;
}
private final MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> context;
private final @NonNull MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> context;
/**
* Creates a new {@link Entity} for the given bean.
@@ -100,6 +94,8 @@ class EntityOperations {
* @param index index name override can be {@literal null}.
* @param type index type override can be {@literal null}.
* @return the {@link IndexCoordinates} containing index name and index type.
* @see ElasticsearchPersistentEntity#getIndexName()
* @see ElasticsearchPersistentEntity#getIndexType()
*/
IndexCoordinates determineIndex(Entity<?> entity, @Nullable String index, @Nullable String type) {
return determineIndex(entity.getPersistentEntity(), index, type);
@@ -115,22 +111,34 @@ class EntityOperations {
* @param index index name override can be {@literal null}.
* @param type index type override can be {@literal null}.
* @return the {@link IndexCoordinates} containing index name and index type.
* @see ElasticsearchPersistentEntity#getIndexName()
* @see ElasticsearchPersistentEntity#getIndexType()
*/
IndexCoordinates determineIndex(ElasticsearchPersistentEntity<?> persistentEntity, @Nullable String index,
@Nullable String type) {
return persistentEntity.getIndexCoordinates();
return new IndexCoordinates(indexName(persistentEntity, index), typeName(persistentEntity, type));
}
private static String indexName(@Nullable ElasticsearchPersistentEntity<?> entity, @Nullable String index) {
if (StringUtils.isEmpty(index)) {
Assert.notNull(entity, "Cannot determine index name");
return entity.getIndexCoordinates().getIndexName();
return entity.getIndexName();
}
return index;
}
private static String typeName(@Nullable ElasticsearchPersistentEntity<?> entity, @Nullable String type) {
if (StringUtils.isEmpty(type)) {
Assert.notNull(entity, "Cannot determine index type");
return entity.getIndexType();
}
return type;
}
/**
* A representation of information about an entity.
*
@@ -255,25 +263,8 @@ class EntityOperations {
* @return the current version or {@literal null} in case it's uninitialized or the entity doesn't expose a version
* property.
*/
@Override
@Nullable
Number getVersion();
/**
* Returns whether there is a property with type SeqNoPrimaryTerm in this entity.
*
* @return true if there is SeqNoPrimaryTerm property
* @since 4.0
*/
boolean hasSeqNoPrimaryTerm();
/**
* Returns SeqNoPropertyTerm for this entity.
*
* @return SeqNoPrimaryTerm, may be {@literal null}
*/
@Nullable
SeqNoPrimaryTerm getSeqNoPrimaryTerm();
}
/**
@@ -281,15 +272,9 @@ class EntityOperations {
* @author Christoph Strobl
* @since 3.2
*/
@RequiredArgsConstructor
private static class MapBackedEntity<T extends Map<String, Object>> implements AdaptibleEntity<T> {
public MapBackedEntity(T map) {
Assert.notNull(map, "map must not be null");
this.map = map;
}
private final T map;
/*
@@ -351,16 +336,6 @@ class EntityOperations {
return null;
}
@Override
public boolean hasSeqNoPrimaryTerm() {
return false;
}
@Override
public SeqNoPrimaryTerm getSeqNoPrimaryTerm() {
return null;
}
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.core.EntityOperations.AdaptibleEntity#incrementVersion()
@@ -439,24 +414,13 @@ class EntityOperations {
* @param <T>
* @since 3.2
*/
@RequiredArgsConstructor(access = AccessLevel.PROTECTED)
private static class MappedEntity<T> implements Entity<T> {
private final ElasticsearchPersistentEntity<?> entity;
private final IdentifierAccessor idAccessor;
private final PersistentPropertyAccessor<T> propertyAccessor;
private MappedEntity(ElasticsearchPersistentEntity<?> entity, IdentifierAccessor idAccessor,
PersistentPropertyAccessor<T> propertyAccessor) {
Assert.notNull(entity, "entity must not ne null");
Assert.notNull(idAccessor, "idAccessor must not ne null");
Assert.notNull(propertyAccessor, "propertyAccessor must not ne null");
this.entity = entity;
this.idAccessor = idAccessor;
this.propertyAccessor = propertyAccessor;
}
private static <T> MappedEntity<T> of(T bean,
MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> context) {
@@ -616,19 +580,6 @@ class EntityOperations {
return propertyAccessor.getProperty(versionProperty, Number.class);
}
@Override
public boolean hasSeqNoPrimaryTerm() {
return entity.hasSeqNoPrimaryTermProperty();
}
@Override
public SeqNoPrimaryTerm getSeqNoPrimaryTerm() {
ElasticsearchPersistentProperty seqNoPrimaryTermProperty = entity.getRequiredSeqNoPrimaryTermProperty();
return propertyAccessor.getProperty(seqNoPrimaryTermProperty, SeqNoPrimaryTerm.class);
}
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.core.EntityOperations.AdaptibleEntity#initializeVersionProperty()
@@ -664,4 +615,14 @@ class EntityOperations {
}
}
/**
* Value object encapsulating index name and index type.
*/
@RequiredArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
static class IndexCoordinates {
private final String indexName;
private final String typeName;
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 the original author or authors.
* Copyright 2018-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,12 +13,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.annotations;
package org.springframework.data.elasticsearch.core;
/**
* @author Peter-Josef Meisch
* @since 4.0
* EsClient interface. Specify what client an ElasticSearchTemplate will return from getClient().
*
* @author Don Wellington
* @param <C>
*/
public enum TermVector {
none, no, yes, with_positions, with_offsets, with_positions_offsets, with_positions_payloads, with_positions_offets_payloads
public interface EsClient<C> {
C getClient();
}
@@ -1,5 +1,5 @@
/*
* Copyright 2020 the original author or authors.
* Copyright 2014-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,20 +15,23 @@
*/
package org.springframework.data.elasticsearch.core;
import java.util.List;
import org.springframework.data.domain.Page;
import org.springframework.data.elasticsearch.core.facet.FacetResult;
/**
* This interface is used to expose the current {@code scrollId} from the underlying scroll context.
* <p>
* Internal use only.
*
* @author Sascha Woo
* @param <T>
* @since 4.0
* @author Rizwan Idrees
* @author Mohsin Husen
* @author Artur Konczak
* @author Jonathan Yan
*/
public interface SearchScrollHits<T> extends SearchHits<T> {
@Deprecated
public interface FacetedPage<T> extends Page<T> {
/**
* @return the scroll id
*/
String getScrollId();
boolean hasFacets();
List<FacetResult> getFacets();
FacetResult getFacet(String name);
}
@@ -0,0 +1,168 @@
/*
* Copyright 2014-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.core;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.histogram.Histogram;
import org.elasticsearch.search.aggregations.bucket.range.Range;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.metrics.stats.extended.ExtendedStats;
import org.elasticsearch.search.aggregations.metrics.sum.Sum;
import org.joda.time.DateTime;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.facet.AbstractFacetRequest;
import org.springframework.data.elasticsearch.core.facet.FacetResult;
import org.springframework.data.elasticsearch.core.facet.request.RangeFacetRequest;
import org.springframework.data.elasticsearch.core.facet.result.HistogramResult;
import org.springframework.data.elasticsearch.core.facet.result.IntervalUnit;
import org.springframework.data.elasticsearch.core.facet.result.RangeResult;
import org.springframework.data.elasticsearch.core.facet.result.StatisticalResult;
import org.springframework.data.elasticsearch.core.facet.result.Term;
import org.springframework.data.elasticsearch.core.facet.result.TermResult;
import static java.util.Optional.ofNullable;
/**
* Container for query result and facet results
*
* @author Rizwan Idrees
* @author Mohsin Husen
* @author Artur Konczak
* @author Jonathan Yan
* @author Philipp Kräutli
* @author Remco Zigterman
*/
@Deprecated
public abstract class FacetedPageImpl<T> extends PageImpl<T> implements FacetedPage<T>, AggregatedPage<T> {
private List<FacetResult> facets;
private Map<String, FacetResult> mapOfFacets = new HashMap<>();
public FacetedPageImpl(List<T> content) {
super(content);
}
public FacetedPageImpl(List<T> content, Pageable pageable, long total) {
super(content, ofNullable(pageable).orElse(Pageable.unpaged()), total);
}
@Override
public boolean hasFacets() {
processAggregations();
return facets != null && facets.size() > 0;
}
@Override
public List<FacetResult> getFacets() {
processAggregations();
return facets;
}
@Override
public FacetResult getFacet(String name) {
processAggregations();
return mapOfFacets.get(name);
}
private void addFacet(FacetResult facetResult) {
facets.add(facetResult);
mapOfFacets.put(facetResult.getName(), facetResult);
}
/**
* Lazy conversion from aggregation to old facets
*/
private void processAggregations() {
if (facets != null) {
return;
}
facets = new ArrayList<>();
Aggregations aggregations = getAggregations();
if (aggregations == null) {
return;
}
for (Aggregation agg : aggregations) {
processAggregation(agg);
}
}
private void processAggregation(Aggregation agg)
{
if (agg instanceof Terms) {
processTermAggregation((Terms) agg);
}
if (agg instanceof Range) {
processRangeAggregation((Range) agg);
}
if (agg instanceof ExtendedStats) {
processExtendedStatsAggregation((ExtendedStats) agg);
}
if (agg instanceof Histogram) {
processHistogramAggregation((Histogram) agg);
}
}
private void processTermAggregation(Terms agg)
{
List<Term> terms = new ArrayList<>();
for (Terms.Bucket t : agg.getBuckets()) {
terms.add(new Term(t.getKeyAsString(), t.getDocCount()));
}
addFacet(new TermResult(agg.getName(), terms, terms.size(), agg.getSumOfOtherDocCounts(), 0));
}
private void processRangeAggregation(Range agg)
{
List<? extends Range.Bucket> buckets = ((Range) agg).getBuckets();
List<org.springframework.data.elasticsearch.core.facet.result.Range> ranges = new ArrayList<>();
for (Range.Bucket b : buckets) {
ExtendedStats rStats = b.getAggregations().get(AbstractFacetRequest.INTERNAL_STATS);
if (rStats != null) {
Sum sum = b.getAggregations().get(RangeFacetRequest.RANGE_INTERNAL_SUM);
ranges.add(new org.springframework.data.elasticsearch.core.facet.result.Range((Double) b.getFrom(), (Double) b.getTo(), b.getDocCount(), sum != null ? sum.getValue() : rStats.getSum(), rStats.getCount(), rStats.getMin(), rStats.getMax()));
} else {
ranges.add(new org.springframework.data.elasticsearch.core.facet.result.Range((Double) b.getFrom(), (Double) b.getTo(), b.getDocCount(), 0, 0, 0, 0));
}
}
addFacet(new RangeResult(agg.getName(), ranges));
}
private void processExtendedStatsAggregation(ExtendedStats agg)
{
addFacet(new StatisticalResult(agg.getName(), agg.getCount(), agg.getMax(), agg.getMin(), agg.getAvg(), agg.getStdDeviation(), agg.getSumOfSquares(), agg.getSum(), agg.getVariance()));
}
private void processHistogramAggregation(Histogram agg)
{
List<IntervalUnit> intervals = new ArrayList<>();
for (Histogram.Bucket h : agg.getBuckets()) {
ExtendedStats hStats = h.getAggregations().get(AbstractFacetRequest.INTERNAL_STATS);
if (hStats != null) {
intervals.add(new IntervalUnit(((DateTime) h.getKey()).getMillis(), h.getDocCount(), h.getDocCount(), hStats.getSum(), hStats.getAvg(), hStats.getMin(), hStats.getMax()));
} else {
intervals.add(new IntervalUnit(((DateTime) h.getKey()).getMillis(), h.getDocCount(), h.getDocCount(), 0, 0, 0, 0));
}
}
addFacet(new HistogramResult(agg.getName(), intervals));
}
}
@@ -0,0 +1,42 @@
/*
* Copyright 2014-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.core;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.index.get.GetResult;
import org.springframework.lang.Nullable;
/**
* @author Artur Konczak
* @author Mohsin Husen
* @author Christoph Strobl
*/
public interface GetResultMapper {
<T> T mapResult(GetResponse response, Class<T> clazz);
/**
* Map a single {@link GetResult} to the given {@link Class type}.
*
* @param getResult must not be {@literal null}.
* @param type must not be {@literal null}.
* @param <T>
* @return can be {@literal null}.
* @since 3.2
*/
@Nullable
<T> T mapGetResult(GetResult getResult, Class<T> type);
}
@@ -1,140 +0,0 @@
/*
* Copyright 2019-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.core;
import java.util.List;
import java.util.Map;
import org.elasticsearch.cluster.metadata.AliasMetaData;
import org.springframework.data.elasticsearch.core.document.Document;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.AliasQuery;
/**
* The operations for the
* <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices.html">Elasticsearch Index APIs</a>.
* <br/>
* IndexOperations are bound to an entity class or an IndexCoordinate by
* {@link ElasticsearchOperations#indexOps(IndexCoordinates)} or {@link ElasticsearchOperations#indexOps(Class)}
*
* @author Peter-Josef Meisch
* @author Sascha Woo
* @since 4.0
*/
public interface IndexOperations {
/**
* Create an index.
*
* @return {@literal true} if the index was created
*/
boolean create();
/**
* Create an index for given Settings.
*
* @param settings the index settings
* @return {@literal true} if the index was created
*/
boolean create(Document settings);
/**
* Deletes the index this {@link IndexOperations} is bound to
*
* @return {@literal true} if the index was deleted
*/
boolean delete();
/**
* Checks if the index this IndexOperations is bound to exists
*
* @return {@literal true} if the index exists
*/
boolean exists();
/**
* Refresh the index(es) this IndexOperations is bound to
*/
void refresh();
/**
* Creates the index mapping for the entity this IndexOperations is bound to.
*
* @return mapping object
*/
Document createMapping();
/**
* Creates the index mapping for the given class
*
* @param clazz the clazz to create a mapping for
* @return mapping object
*/
Document createMapping(Class<?> clazz);
/**
* writes a mapping to the index
*
* @param mapping the Document with the mapping definitions
* @return {@literal true} if the mapping could be stored
*/
boolean putMapping(Document mapping);
/**
* Get mapping for an index defined by a class.
*
* @return the mapping
*/
Map<String, Object> getMapping();
/**
* Add an alias.
*
* @param query query defining the alias
* @return true if the alias was created
*/
boolean addAlias(AliasQuery query);
/**
* Get the alias informations for a specified index.
*
* @return alias information
*/
List<AliasMetaData> queryForAlias();
/**
* Remove an alias.
*
* @param query query defining the alias
* @return true if the alias was removed
*/
boolean removeAlias(AliasQuery query);
/**
* Get the index settings.
*
* @return the settings
*/
Map<String, Object> getSettings();
/**
* Get settings for a given indexName.
*
* @param includeDefaults wehther or not to include all the default settings
* @return the settings
*/
Map<String, Object> getSettings(boolean includeDefaults);
}
@@ -13,15 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.core.index;
package org.springframework.data.elasticsearch.core;
import static org.elasticsearch.common.xcontent.XContentFactory.*;
import static org.springframework.data.elasticsearch.core.index.MappingParameters.*;
import static org.springframework.util.StringUtils.*;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.Iterator;
@@ -29,12 +27,12 @@ import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.annotation.Transient;
import org.springframework.data.elasticsearch.ElasticsearchException;
import org.springframework.data.elasticsearch.annotations.CompletionContext;
import org.springframework.data.elasticsearch.annotations.CompletionField;
import org.springframework.data.elasticsearch.annotations.DynamicMapping;
import org.springframework.data.elasticsearch.annotations.DateFormat;
import org.springframework.data.elasticsearch.annotations.DynamicTemplates;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
@@ -42,14 +40,11 @@ import org.springframework.data.elasticsearch.annotations.GeoPointField;
import org.springframework.data.elasticsearch.annotations.InnerField;
import org.springframework.data.elasticsearch.annotations.Mapping;
import org.springframework.data.elasticsearch.annotations.MultiField;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.ResourceUtil;
import org.springframework.data.elasticsearch.core.completion.Completion;
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
import org.springframework.data.elasticsearch.core.geo.GeoPoint;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
import org.springframework.data.mapping.MappingException;
import org.springframework.data.mapping.PropertyHandler;
import org.springframework.data.util.TypeInformation;
import org.springframework.lang.Nullable;
@@ -72,16 +67,22 @@ import com.fasterxml.jackson.databind.ObjectMapper;
* @author Robert Gruendler
* @author Petr Kukral
* @author Peter-Josef Meisch
* @author Xiao Yu
*/
public class MappingBuilder {
class MappingBuilder {
private static final String FIELD_DATA = "fielddata";
private static final String FIELD_STORE = "store";
private static final String FIELD_TYPE = "type";
private static final String FIELD_INDEX = "index";
private static final String FIELD_FORMAT = "format";
private static final String FIELD_SEARCH_ANALYZER = "search_analyzer";
private static final String FIELD_INDEX_ANALYZER = "analyzer";
private static final String FIELD_NORMALIZER = "normalizer";
private static final String FIELD_PROPERTIES = "properties";
private static final String FIELD_PARENT = "_parent";
private static final String FIELD_COPY_TO = "copy_to";
private static final String FIELD_CONTEXT_NAME = "name";
private static final String FIELD_CONTEXT_TYPE = "type";
private static final String FIELD_CONTEXT_PATH = "path";
private static final String FIELD_CONTEXT_PRECISION = "precision";
private static final String FIELD_DYNAMIC_TEMPLATES = "dynamic_templates";
@@ -90,7 +91,6 @@ public class MappingBuilder {
private static final String COMPLETION_MAX_INPUT_LENGTH = "max_input_length";
private static final String COMPLETION_CONTEXTS = "contexts";
private static final String TYPE_DYNAMIC = "dynamic";
private static final String TYPE_VALUE_KEYWORD = "keyword";
private static final String TYPE_VALUE_GEO_POINT = "geo_point";
private static final String TYPE_VALUE_COMPLETION = "completion";
@@ -99,7 +99,7 @@ public class MappingBuilder {
private final ElasticsearchConverter elasticsearchConverter;
public MappingBuilder(ElasticsearchConverter elasticsearchConverter) {
MappingBuilder(ElasticsearchConverter elasticsearchConverter) {
this.elasticsearchConverter = elasticsearchConverter;
}
@@ -107,75 +107,64 @@ public class MappingBuilder {
* builds the Elasticsearch mapping for the given clazz.
*
* @return JSON string
* @throws ElasticsearchException on errors while building the mapping
* @throws IOException
*/
public String buildPropertyMapping(Class<?> clazz) throws ElasticsearchException {
String buildPropertyMapping(Class<?> clazz) throws IOException {
try {
ElasticsearchPersistentEntity<?> entity = elasticsearchConverter.getMappingContext()
.getRequiredPersistentEntity(clazz);
ElasticsearchPersistentEntity<?> entity = elasticsearchConverter.getMappingContext()
.getRequiredPersistentEntity(clazz);
XContentBuilder builder = jsonBuilder().startObject();
XContentBuilder builder = jsonBuilder().startObject().startObject(entity.getIndexType());
// Dynamic templates
addDynamicTemplatesMapping(builder, entity);
// Dynamic templates
addDynamicTemplatesMapping(builder, entity);
// Parent
String parentType = entity.getParentType();
if (hasText(parentType)) {
builder.startObject(FIELD_PARENT).field(FIELD_PARAM_TYPE, parentType).endObject();
}
mapEntity(builder, entity, true, "", false, FieldType.Auto, null, entity.findAnnotation(DynamicMapping.class));
builder.endObject() // root object
.close();
return builder.getOutputStream().toString();
} catch (MappingException | IOException e) {
throw new ElasticsearchException("could not build mapping", e);
// Parent
String parentType = entity.getParentType();
if (hasText(parentType)) {
builder.startObject(FIELD_PARENT).field(FIELD_TYPE, parentType).endObject();
}
// Properties
builder.startObject(FIELD_PROPERTIES);
mapEntity(builder, entity, true, "", false, FieldType.Auto, null);
builder.endObject() // FIELD_PROPERTIES
.endObject() // indexType
.endObject() // root object
.close();
return builder.getOutputStream().toString();
}
private void mapEntity(XContentBuilder builder, @Nullable ElasticsearchPersistentEntity<?> entity,
boolean isRootObject, String nestedObjectFieldName, boolean nestedOrObjectField, FieldType fieldType,
@Nullable Field parentFieldAnnotation, @Nullable DynamicMapping dynamicMapping) throws IOException {
private void mapEntity(XContentBuilder builder, @Nullable ElasticsearchPersistentEntity entity, boolean isRootObject,
String nestedObjectFieldName, boolean nestedOrObjectField, FieldType fieldType,
@Nullable Field parentFieldAnnotation) throws IOException {
boolean writeNestedProperties = !isRootObject && (isAnyPropertyAnnotatedWithField(entity) || nestedOrObjectField);
if (writeNestedProperties) {
String type = nestedOrObjectField ? fieldType.toString().toLowerCase()
: FieldType.Object.toString().toLowerCase();
builder.startObject(nestedObjectFieldName).field(FIELD_PARAM_TYPE, type);
builder.startObject(nestedObjectFieldName).field(FIELD_TYPE, type);
if (nestedOrObjectField && FieldType.Nested == fieldType && parentFieldAnnotation != null
&& parentFieldAnnotation.includeInParent()) {
builder.field("include_in_parent", true);
builder.field("include_in_parent", parentFieldAnnotation.includeInParent());
}
builder.startObject(FIELD_PROPERTIES);
}
if (dynamicMapping != null) {
builder.field(TYPE_DYNAMIC, dynamicMapping.value().name().toLowerCase());
}
builder.startObject(FIELD_PROPERTIES);
if (entity != null) {
entity.doWithProperties((PropertyHandler<ElasticsearchPersistentProperty>) property -> {
try {
if (property.isAnnotationPresent(Transient.class) || isInIgnoreFields(property, parentFieldAnnotation)) {
return;
}
if (property.isSeqNoPrimaryTermProperty()) {
if (property.isAnnotationPresent(Field.class)) {
logger.warn("Property {} of {} is annotated for inclusion in mapping, but its type is " + //
"SeqNoPrimaryTerm that is never mapped, so it is skipped", //
property.getFieldName(), entity.getType());
}
return;
}
buildPropertyMapping(builder, isRootObject, property);
} catch (IOException e) {
logger.warn("error mapping property with name {}", property.getName(), e);
@@ -183,12 +172,9 @@ public class MappingBuilder {
});
}
builder.endObject();
if (writeNestedProperties) {
builder.endObject();
builder.endObject().endObject();
}
}
private void buildPropertyMapping(XContentBuilder builder, boolean isRootObject,
@@ -207,35 +193,37 @@ public class MappingBuilder {
}
}
if (isGeoPointProperty(property)) {
applyGeoPointFieldMapping(builder, property);
return;
}
Field fieldAnnotation = property.findAnnotation(Field.class);
boolean isGeoPointProperty = isGeoPointProperty(property);
boolean isCompletionProperty = isCompletionProperty(property);
boolean isNestedOrObjectProperty = isNestedOrObjectProperty(property);
if (!isCompletionProperty && property.isEntity() && hasRelevantAnnotation(property)) {
Field fieldAnnotation = property.findAnnotation(Field.class);
if (!isGeoPointProperty && !isCompletionProperty && property.isEntity() && hasRelevantAnnotation(property)) {
if (fieldAnnotation == null) {
return;
}
if (isNestedOrObjectProperty) {
Iterator<? extends TypeInformation<?>> iterator = property.getPersistentEntityTypes().iterator();
ElasticsearchPersistentEntity<?> persistentEntity = iterator.hasNext()
? elasticsearchConverter.getMappingContext().getPersistentEntity(iterator.next())
: null;
Iterator<? extends TypeInformation<?>> iterator = property.getPersistentEntityTypes().iterator();
ElasticsearchPersistentEntity<?> persistentEntity = iterator.hasNext()
? elasticsearchConverter.getMappingContext().getPersistentEntity(iterator.next())
: null;
mapEntity(builder, persistentEntity, false, property.getFieldName(), isNestedOrObjectProperty,
fieldAnnotation.type(), fieldAnnotation, property.findAnnotation(DynamicMapping.class));
mapEntity(builder, persistentEntity, false, property.getFieldName(), isNestedOrObjectProperty,
fieldAnnotation.type(), fieldAnnotation);
if (isNestedOrObjectProperty) {
return;
}
}
MultiField multiField = property.findAnnotation(MultiField.class);
if (isGeoPointProperty) {
applyGeoPointFieldMapping(builder, property);
return;
}
if (isCompletionProperty) {
CompletionField completionField = property.findAnnotation(CompletionField.class);
applyCompletionFieldMapping(builder, property, completionField);
@@ -260,14 +248,14 @@ public class MappingBuilder {
private void applyGeoPointFieldMapping(XContentBuilder builder, ElasticsearchPersistentProperty property)
throws IOException {
builder.startObject(property.getFieldName()).field(FIELD_PARAM_TYPE, TYPE_VALUE_GEO_POINT).endObject();
builder.startObject(property.getFieldName()).field(FIELD_TYPE, TYPE_VALUE_GEO_POINT).endObject();
}
private void applyCompletionFieldMapping(XContentBuilder builder, ElasticsearchPersistentProperty property,
@Nullable CompletionField annotation) throws IOException {
builder.startObject(property.getFieldName());
builder.field(FIELD_PARAM_TYPE, TYPE_VALUE_COMPLETION);
builder.field(FIELD_TYPE, TYPE_VALUE_COMPLETION);
if (annotation != null) {
@@ -275,10 +263,10 @@ public class MappingBuilder {
builder.field(COMPLETION_PRESERVE_POSITION_INCREMENTS, annotation.preservePositionIncrements());
builder.field(COMPLETION_PRESERVE_SEPARATORS, annotation.preserveSeparators());
if (!StringUtils.isEmpty(annotation.searchAnalyzer())) {
builder.field(FIELD_PARAM_SEARCH_ANALYZER, annotation.searchAnalyzer());
builder.field(FIELD_SEARCH_ANALYZER, annotation.searchAnalyzer());
}
if (!StringUtils.isEmpty(annotation.analyzer())) {
builder.field(FIELD_PARAM_INDEX_ANALYZER, annotation.analyzer());
builder.field(FIELD_INDEX_ANALYZER, annotation.analyzer());
}
if (annotation.contexts().length > 0) {
@@ -289,15 +277,9 @@ public class MappingBuilder {
builder.startObject();
builder.field(FIELD_CONTEXT_NAME, context.name());
builder.field(FIELD_CONTEXT_TYPE, context.type().name().toLowerCase());
if (context.precision().length() > 0) {
builder.field(FIELD_CONTEXT_PRECISION, context.precision());
}
if (StringUtils.hasText(context.path())) {
builder.field(FIELD_CONTEXT_PATH, context.path());
}
builder.endObject();
}
builder.endArray();
@@ -310,7 +292,7 @@ public class MappingBuilder {
private void applyDefaultIdFieldMapping(XContentBuilder builder, ElasticsearchPersistentProperty property)
throws IOException {
builder.startObject(property.getFieldName()).field(FIELD_PARAM_TYPE, TYPE_VALUE_KEYWORD).field(FIELD_INDEX, true)
builder.startObject(property.getFieldName()).field(FIELD_TYPE, TYPE_VALUE_KEYWORD).field(FIELD_INDEX, true)
.endObject();
}
@@ -322,15 +304,6 @@ public class MappingBuilder {
private void addSingleFieldMapping(XContentBuilder builder, ElasticsearchPersistentProperty property,
Field annotation, boolean nestedOrObjectField) throws IOException {
// build the property json, if empty skip it as this is no valid mapping
XContentBuilder propertyBuilder = jsonBuilder().startObject();
addFieldMappingParameters(propertyBuilder, annotation, nestedOrObjectField);
propertyBuilder.endObject().close();
if ("{}".equals(propertyBuilder.getOutputStream().toString())) {
return;
}
builder.startObject(property.getFieldName());
addFieldMappingParameters(builder, annotation, nestedOrObjectField);
builder.endObject();
@@ -360,15 +333,77 @@ public class MappingBuilder {
builder.endObject();
}
private void addFieldMappingParameters(XContentBuilder builder, Annotation annotation, boolean nestedOrObjectField)
private void addFieldMappingParameters(XContentBuilder builder, Object annotation, boolean nestedOrObjectField)
throws IOException {
MappingParameters mappingParameters = MappingParameters.from(annotation);
boolean index = true;
boolean store = false;
boolean fielddata = false;
FieldType type = null;
DateFormat dateFormat = null;
String datePattern = null;
String analyzer = null;
String searchAnalyzer = null;
String normalizer = null;
String[] copyTo = null;
if (!nestedOrObjectField && mappingParameters.isStore()) {
builder.field(FIELD_PARAM_STORE, true);
if (annotation instanceof Field) {
// @Field
Field fieldAnnotation = (Field) annotation;
index = fieldAnnotation.index();
store = fieldAnnotation.store();
fielddata = fieldAnnotation.fielddata();
type = fieldAnnotation.type();
dateFormat = fieldAnnotation.format();
datePattern = fieldAnnotation.pattern();
analyzer = fieldAnnotation.analyzer();
searchAnalyzer = fieldAnnotation.searchAnalyzer();
normalizer = fieldAnnotation.normalizer();
copyTo = fieldAnnotation.copyTo();
} else if (annotation instanceof InnerField) {
// @InnerField
InnerField fieldAnnotation = (InnerField) annotation;
index = fieldAnnotation.index();
store = fieldAnnotation.store();
fielddata = fieldAnnotation.fielddata();
type = fieldAnnotation.type();
dateFormat = fieldAnnotation.format();
datePattern = fieldAnnotation.pattern();
analyzer = fieldAnnotation.analyzer();
searchAnalyzer = fieldAnnotation.searchAnalyzer();
normalizer = fieldAnnotation.normalizer();
} else {
throw new IllegalArgumentException("annotation must be an instance of @Field or @InnerField");
}
if (!nestedOrObjectField) {
builder.field(FIELD_STORE, store);
}
if (fielddata) {
builder.field(FIELD_DATA, fielddata);
}
if (type != FieldType.Auto) {
builder.field(FIELD_TYPE, type.name().toLowerCase());
if (type == FieldType.Date && dateFormat != DateFormat.none) {
builder.field(FIELD_FORMAT, dateFormat == DateFormat.custom ? datePattern : dateFormat.toString());
}
}
if (!index) {
builder.field(FIELD_INDEX, index);
}
if (!StringUtils.isEmpty(analyzer)) {
builder.field(FIELD_INDEX_ANALYZER, analyzer);
}
if (!StringUtils.isEmpty(searchAnalyzer)) {
builder.field(FIELD_SEARCH_ANALYZER, searchAnalyzer);
}
if (!StringUtils.isEmpty(normalizer)) {
builder.field(FIELD_NORMALIZER, normalizer);
}
if (copyTo != null && copyTo.length > 0) {
builder.field(FIELD_COPY_TO, copyTo);
}
mappingParameters.writeTypeAndParametersTo(builder);
}
/**
@@ -1,5 +1,5 @@
/*
* Copyright 2020 the original author or authors.
* Copyright 2014-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,16 +13,17 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.core;
import org.springframework.data.domain.Page;
import java.util.List;
import org.elasticsearch.action.get.MultiGetResponse;
/**
* Page definition for repositories that need to return a paged SearchHits.
*
* @author Peter-Josef Meisch
* @since 4.0
* @author Mohsin Husen
*/
public interface SearchPage<T> extends Page<SearchHit<T>> {
SearchHits<T> getSearchHits();
public interface MultiGetResultMapper {
<T> List<T> mapResults(MultiGetResponse responses, Class<T> clazz);
}
@@ -1,293 +0,0 @@
/*
* Copyright 2019-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.core;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.BulkOptions;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.data.elasticsearch.core.query.UpdateQuery;
import org.springframework.util.Assert;
/**
* The reactive operations for the
* <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/docs.html">Elasticsearch Document APIs</a>.
*
* @author Peter-Josef Meisch
* @author Aleksei Arsenev
* @since 4.0
*/
public interface ReactiveDocumentOperations {
/**
* Index the given entity, once available, extracting index and type from entity metadata.
*
* @param entityPublisher must not be {@literal null}.
* @param <T>
* @return a {@link Mono} emitting the saved entity.
*/
default <T> Mono<T> save(Mono<? extends T> entityPublisher) {
Assert.notNull(entityPublisher, "EntityPublisher must not be null!");
return entityPublisher.flatMap(this::save);
}
/**
* Index the given entity extracting index and type from entity metadata.
*
* @param entity must not be {@literal null}.
* @param <T>
* @return a {@link Mono} emitting the saved entity.
*/
<T> Mono<T> save(T entity);
/**
* Index the entity, once available, under the given {@literal type} in the given {@literal index}. If the
* {@literal index} is {@literal null} or empty the index name provided via entity metadata is used. Same for the
* {@literal type}.
*
* @param entityPublisher must not be {@literal null}.
* @param index the target index, must not be {@literal null}
* @param <T>
* @return a {@link Mono} emitting the saved entity.
*/
default <T> Mono<T> save(Mono<? extends T> entityPublisher, IndexCoordinates index) {
Assert.notNull(entityPublisher, "EntityPublisher must not be null!");
return entityPublisher.flatMap(it -> save(it, index));
}
/**
* Index the entity under the given {@literal type} in the given {@literal index}. If the {@literal index} is
* {@literal null} or empty the index name provided via entity metadata is used. Same for the {@literal type}.
*
* @param entity must not be {@literal null}.
* @param index the target index, must not be {@literal null}
* @param <T>
* @return a {@link Mono} emitting the saved entity.
*/
<T> Mono<T> save(T entity, IndexCoordinates index);
/**
* Index entities under the given {@literal type} in the given {@literal index}. If the {@literal index} is
* {@literal null} or empty the index name provided via entity metadata is used.
*
* @param entities must not be {@literal null}.
* @param index the target index, must not be {@literal null}
* @param <T>
* @return a {@link Flux} emitting saved entities.
* @since 4.0
*/
default <T> Flux<T> saveAll(Iterable<T> entities, IndexCoordinates index) {
List<T> entityList = new ArrayList<>();
entities.forEach(entityList::add);
return saveAll(Mono.just(entityList), index);
}
/**
* Index entities under the given {@literal type} in the given {@literal index}. If the {@literal index} is
* {@literal null} or empty the index name provided via entity metadata is used.
*
* @param entities must not be {@literal null}.
* @param index the target index, must not be {@literal null}
* @param <T>
* @return a {@link Flux} emitting saved entities.
* @since 4.0
*/
<T> Flux<T> saveAll(Mono<? extends Collection<? extends T>> entities, IndexCoordinates index);
/**
* Execute a multiGet against elasticsearch for the given ids.
*
* @param query the query defining the ids of the objects to get
* @param clazz the type of the object to be returned
* @param index the index(es) from which the objects are read.
* @return flux with list of nullable objects
* @since 4.0
*/
<T> Flux<T> multiGet(Query query, Class<T> clazz, IndexCoordinates index);
/**
* Bulk update all objects. Will do update.
*
* @param queries the queries to execute in bulk
* @since 4.0
*/
default Mono<Void> bulkUpdate(List<UpdateQuery> queries, IndexCoordinates index) {
return bulkUpdate(queries, BulkOptions.defaultOptions(), index);
}
/**
* Bulk update all objects. Will do update.
*
* @param queries the queries to execute in bulk
* @param bulkOptions options to be added to the bulk request
* @since 4.0
*/
Mono<Void> bulkUpdate(List<UpdateQuery> queries, BulkOptions bulkOptions, IndexCoordinates index);
/**
* Find the document with the given {@literal id} mapped onto the given {@literal entityType}.
*
* @param id the {@literal _id} of the document to fetch.
* @param entityType the domain type used for mapping the document.
* @param <T>
* @return {@link Mono#empty()} if not found.
* @deprecated since 4.0 use {@link #get(String, Class)}
*/
@Deprecated
default <T> Mono<T> findById(String id, Class<T> entityType) {
return get(id, entityType);
}
/**
* Fetch the entity with given {@literal id}.
*
* @param id must not be {@literal null}.
* @param index the target index, must not be {@literal null}
* @param <T>
* @return the {@link Mono} emitting the entity or signalling completion if none found.
* @deprecated since 4.0, use {@link #get(String, Class, IndexCoordinates)}
*/
@Deprecated
default <T> Mono<T> findById(String id, Class<T> entityType, IndexCoordinates index) {
return get(id, entityType, index);
}
/**
* Find the document with the given {@literal id} mapped onto the given {@literal entityType}.
*
* @param id the {@literal _id} of the document to fetch.
* @param entityType the domain type used for mapping the document.
* @param <T>
* @return {@link Mono#empty()} if not found.
* @since 4.0
*/
<T> Mono<T> get(String id, Class<T> entityType);
/**
* Fetch the entity with given {@literal id}.
*
* @param id must not be {@literal null}.
* @param index the target index, must not be {@literal null}
* @param <T>
* @return the {@link Mono} emitting the entity or signalling completion if none found.
*/
<T> Mono<T> get(String id, Class<T> entityType, IndexCoordinates index);
/**
* Check if an entity with given {@literal id} exists.
*
* @param id the {@literal _id} of the document to look for.
* @param entityType the domain type used.
* @return a {@link Mono} emitting {@literal true} if a matching document exists, {@literal false} otherwise.
*/
Mono<Boolean> exists(String id, Class<?> entityType);
/**
* Check if an entity with given {@literal id} exists.
*
* @param id the {@literal _id} of the document to look for.
* @param index the target index, must not be {@literal null}
* @return a {@link Mono} emitting {@literal true} if a matching document exists, {@literal false} otherwise.
*/
Mono<Boolean> exists(String id, IndexCoordinates index);
/**
* Check if an entity with given {@literal id} exists.
*
* @param id the {@literal _id} of the document to look for.
* @param index the target index, must not be {@literal null}
* @return a {@link Mono} emitting {@literal true} if a matching document exists, {@literal false} otherwise.
* @deprecated since 4.0, use {@link #exists(String, Class)} or {@link #exists(String, IndexCoordinates)}
*/
@Deprecated
Mono<Boolean> exists(String id, Class<?> entityType, IndexCoordinates index);
/**
* Delete the given entity extracting index and type from entity metadata.
*
* @param entity must not be {@literal null}.
* @return a {@link Mono} emitting the {@literal id} of the removed document.
*/
Mono<String> delete(Object entity);
/**
* Delete the given entity extracting index and type from entity metadata.
*
* @param entity must not be {@literal null}.
* @param index the target index, must not be {@literal null}
* @return a {@link Mono} emitting the {@literal id} of the removed document.
*/
Mono<String> delete(Object entity, IndexCoordinates index);
/**
* Delete the entity with given {@literal id}.
*
* @param id must not be {@literal null}.
* @param index the target index, must not be {@literal null}
* @return a {@link Mono} emitting the {@literal id} of the removed document.
*/
Mono<String> delete(String id, IndexCoordinates index);
/**
* Delete the entity with given {@literal id} extracting index and type from entity metadata.
*
* @param id must not be {@literal null}.
* @param entityType must not be {@literal null}.
* @return a {@link Mono} emitting the {@literal id} of the removed document.
* @since 4.0
*/
Mono<String> delete(String id, Class<?> entityType);
/**
* Delete the entity with given {@literal id} extracting index and type from entity metadata.
*
* @param id must not be {@literal null}.
* @param entityType must not be {@literal null}.
* @param index the target index, must not be {@literal null}
* @return a {@link Mono} emitting the {@literal id} of the removed document.
* @deprecated since 4.0, use {@link #delete(String, Class)} or {@link #deleteById(String, IndexCoordinates)}
*/
@Deprecated
default Mono<String> delete(String id, Class<?> entityType, IndexCoordinates index) {
return delete(id, index);
}
/**
* Delete the documents matching the given {@link Query} extracting index and type from entity metadata.
*
* @param query must not be {@literal null}.
* @param entityType must not be {@literal null}.
* @return a {@link Mono} emitting the number of the removed documents.
*/
Mono<Long> delete(Query query, Class<?> entityType);
/**
* Delete the documents matching the given {@link Query} extracting index and type from entity metadata.
*
* @param query must not be {@literal null}.
* @param entityType must not be {@literal null}.
* @param index the target index, must not be {@literal null}
* @return a {@link Mono} emitting the number of the removed documents.
*/
Mono<Long> delete(Query query, Class<?> entityType, IndexCoordinates index);
}
@@ -15,12 +15,18 @@
*/
package org.springframework.data.elasticsearch.core;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.elasticsearch.index.query.QueryBuilders;
import org.reactivestreams.Publisher;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient;
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.data.elasticsearch.core.query.StringQuery;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* Interface that specifies a basic set of Elasticsearch operations executed in a reactive way.
@@ -31,10 +37,9 @@ import org.springframework.lang.Nullable;
* {@link Publisher}.
*
* @author Christoph Strobl
* @author Peter-Josef Meisch
* @since 3.2
*/
public interface ReactiveElasticsearchOperations extends ReactiveDocumentOperations, ReactiveSearchOperations {
public interface ReactiveElasticsearchOperations {
/**
* Execute within a {@link ClientCallback} managing resources and translating errors.
@@ -45,6 +50,408 @@ public interface ReactiveElasticsearchOperations extends ReactiveDocumentOperati
*/
<T> Publisher<T> execute(ClientCallback<Publisher<T>> callback);
/**
* Index the given entity, once available, extracting index and type from entity metadata.
*
* @param entityPublisher must not be {@literal null}.
* @param <T>
* @return a {@link Mono} emitting the saved entity.
*/
default <T> Mono<T> save(Mono<? extends T> entityPublisher) {
Assert.notNull(entityPublisher, "EntityPublisher must not be null!");
return entityPublisher.flatMap(this::save);
}
/**
* Index the given entity extracting index and type from entity metadata.
*
* @param entity must not be {@literal null}.
* @param <T>
* @return a {@link Mono} emitting the saved entity.
*/
default <T> Mono<T> save(T entity) {
return save(entity, null);
}
/**
* Index the entity, once available, in the given {@literal index}. If the index is {@literal null} or empty the index
* name provided via entity metadata is used.
*
* @param entityPublisher must not be {@literal null}.
* @param index the name of the target index. Can be {@literal null}.
* @param <T>
* @return a {@link Mono} emitting the saved entity.
*/
default <T> Mono<T> save(Mono<? extends T> entityPublisher, String index) {
Assert.notNull(entityPublisher, "EntityPublisher must not be null!");
return entityPublisher.flatMap(it -> save(it, index));
}
/**
* Index the entity in the given {@literal index}. If the index is {@literal null} or empty the index name provided
* via entity metadata is used.
*
* @param entity must not be {@literal null}.
* @param index the name of the target index. Can be {@literal null}.
* @param <T>
* @return a {@link Mono} emitting the saved entity.
*/
default <T> Mono<T> save(T entity, @Nullable String index) {
return save(entity, index, null);
}
/**
* Index the entity, once available, under the given {@literal type} in the given {@literal index}. If the
* {@literal index} is {@literal null} or empty the index name provided via entity metadata is used. Same for the
* {@literal type}.
*
* @param entityPublisher must not be {@literal null}.
* @param index the name of the target index. Can be {@literal null}.
* @param type the name of the type within the index. Can be {@literal null}.
* @param <T>
* @return a {@link Mono} emitting the saved entity.
*/
default <T> Mono<T> save(Mono<? extends T> entityPublisher, @Nullable String index, @Nullable String type) {
Assert.notNull(entityPublisher, "EntityPublisher must not be null!");
return entityPublisher.flatMap(it -> save(it, index, type));
}
/**
* Index the entity under the given {@literal type} in the given {@literal index}. If the {@literal index} is
* {@literal null} or empty the index name provided via entity metadata is used. Same for the {@literal type}.
*
* @param entity must not be {@literal null}.
* @param index the name of the target index. Can be {@literal null}.
* @param type the name of the type within the index. Can be {@literal null}.
* @param <T>
* @return a {@link Mono} emitting the saved entity.
*/
<T> Mono<T> save(T entity, @Nullable String index, @Nullable String type);
/**
* Find the document with the given {@literal id} mapped onto the given {@literal entityType}.
*
* @param id the {@literal _id} of the document to fetch.
* @param entityType the domain type used for mapping the document.
* @param <T>
* @return {@link Mono#empty()} if not found.
*/
default <T> Mono<T> findById(String id, Class<T> entityType) {
return findById(id, entityType, null);
}
/**
* Fetch the entity with given {@literal id}.
*
* @param id the {@literal _id} of the document to fetch.
* @param entityType the domain type used for mapping the document.
* @param index the name of the target index. Overwrites document metadata from {@literal entityType} if not
* {@literal null}.
* @param <T>
* @return {@link Mono#empty()} if not found.
*/
default <T> Mono<T> findById(String id, Class<T> entityType, @Nullable String index) {
return findById(id, entityType, index, null);
}
/**
* Fetch the entity with given {@literal id}.
*
* @param id must not be {@literal null}.
* @param index the name of the target index. Overwrites document metadata from {@literal entityType} if not
* {@literal null}.
* @param type the name of the target type. Overwrites document metadata from {@literal entityType} if not
* {@literal null}.
* @param <T>
* @return the {@link Mono} emitting the entity or signalling completion if none found.
*/
<T> Mono<T> findById(String id, Class<T> entityType, @Nullable String index, @Nullable String type);
/**
* Check if an entity with given {@literal id} exists.
*
* @param id the {@literal _id} of the document to look for.
* @param entityType the domain type used.
* @return a {@link Mono} emitting {@literal true} if a matching document exists, {@literal false} otherwise.
*/
default Mono<Boolean> exists(String id, Class<?> entityType) {
return exists(id, entityType, null);
}
/**
* Check if an entity with given {@literal id} exists.
*
* @param id the {@literal _id} of the document to look for.
* @param entityType the domain type used.
* @param index the name of the target index. Overwrites document metadata from {@literal entityType} if not
* {@literal null}.
* @return a {@link Mono} emitting {@literal true} if a matching document exists, {@literal false} otherwise.
*/
default Mono<Boolean> exists(String id, Class<?> entityType, @Nullable String index) {
return exists(id, entityType, index, null);
}
/**
* Check if an entity with given {@literal id} exists.
*
* @param id the {@literal _id} of the document to look for.
* @param index the name of the target index. Overwrites document metadata from {@literal entityType} if not
* {@literal null}.
* @param type the name of the target type. Overwrites document metadata from {@literal entityType} if not
* {@literal null}.
* @return a {@link Mono} emitting {@literal true} if a matching document exists, {@literal false} otherwise.
*/
Mono<Boolean> exists(String id, Class<?> entityType, @Nullable String index, @Nullable String type);
/**
* Search the index for entities matching the given {@link Query query}. <br />
* {@link Pageable#isUnpaged() Unpaged} queries may overrule elasticsearch server defaults for page size by either
* delegating to the scroll API or using a max {@link org.elasticsearch.search.builder.SearchSourceBuilder#size(int)
* size}.
*
* @param query must not be {@literal null}.
* @param entityType must not be {@literal null}.
* @param <T>
* @return a {@link Flux} emitting matching entities one by one.
*/
default <T> Flux<T> find(Query query, Class<T> entityType) {
return find(query, entityType, entityType);
}
/**
* Search the index for entities matching the given {@link Query query}. <br />
* {@link Pageable#isUnpaged() Unpaged} queries may overrule elasticsearch server defaults for page size by either *
* delegating to the scroll API or using a max {@link org.elasticsearch.search.builder.SearchSourceBuilder#size(int) *
* size}.
*
* @param query must not be {@literal null}.
* @param entityType The entity type for mapping the query. Must not be {@literal null}.
* @param returnType The mapping target type. Must not be {@literal null}. Th
* @param <T>
* @return a {@link Flux} emitting matching entities one by one.
*/
default <T> Flux<T> find(Query query, Class<?> entityType, Class<T> returnType) {
return find(query, entityType, null, null, returnType);
}
/**
* Search the index for entities matching the given {@link Query query}.
*
* @param query must not be {@literal null}.
* @param entityType must not be {@literal null}.
* @param <T>
* @return a {@link Flux} emitting matching entities one by one.
*/
default <T> Flux<T> find(Query query, Class<T> entityType, @Nullable String index) {
return find(query, entityType, index, null);
}
/**
* Search the index for entities matching the given {@link Query query}.
*
* @param query must not be {@literal null}.
* @param entityType must not be {@literal null}.
* @param index the name of the target index. Overwrites document metadata from {@literal entityType} if not
* {@literal null}.
* @param type the name of the target type. Overwrites document metadata from {@literal entityType} if not
* {@literal null}.
* @param <T>
* @returnm a {@link Flux} emitting matching entities one by one.
*/
default <T> Flux<T> find(Query query, Class<T> entityType, @Nullable String index, @Nullable String type) {
return find(query, entityType, index, type, entityType);
}
/**
* Search the index for entities matching the given {@link Query query}.
*
* @param query must not be {@literal null}.
* @param entityType must not be {@literal null}.
* @param index the name of the target index. Overwrites document metadata from {@literal entityType} if not
* {@literal null}.
* @param type the name of the target type. Overwrites document metadata from {@literal entityType} if not
* {@literal null}.
* @param resultType the projection result type.
* @param <T>
* @return a {@link Flux} emitting matching entities one by one.
*/
<T> Flux<T> find(Query query, Class<?> entityType, @Nullable String index, @Nullable String type,
Class<T> resultType);
/**
* Count the number of documents matching the given {@link Query}.
*
* @param entityType must not be {@literal null}.
* @return a {@link Mono} emitting the nr of matching documents.
*/
default Mono<Long> count(Class<?> entityType) {
return count(new StringQuery(QueryBuilders.matchAllQuery().toString()), entityType, null);
}
/**
* Count the number of documents matching the given {@link Query}.
*
* @param query must not be {@literal null}.
* @param entityType must not be {@literal null}.
* @return a {@link Mono} emitting the nr of matching documents.
*/
default Mono<Long> count(Query query, Class<?> entityType) {
return count(query, entityType, null);
}
/**
* Count the number of documents matching the given {@link Query}.
*
* @param query must not be {@literal null}.
* @param entityType must not be {@literal null}.
* @param index the name of the target index. Overwrites document metadata from {@literal entityType} if not
* {@literal null}.
* @return a {@link Mono} emitting the nr of matching documents.
*/
default Mono<Long> count(Query query, Class<?> entityType, @Nullable String index) {
return count(query, entityType, index, null);
}
/**
* Count the number of documents matching the given {@link Query}.
*
* @param query must not be {@literal null}.
* @param entityType must not be {@literal null}.
* @param index the name of the target index. Overwrites document metadata from {@literal entityType} if not
* {@literal null}.
* @param type the name of the target type. Overwrites document metadata from {@literal entityType} if not
* {@literal null}.
* @return a {@link Mono} emitting the nr of matching documents.
*/
Mono<Long> count(Query query, Class<?> entityType, @Nullable String index, @Nullable String type);
/**
* Delete the given entity extracting index and type from entity metadata.
*
* @param entity must not be {@literal null}.
* @return a {@link Mono} emitting the {@literal id} of the removed document.
*/
default Mono<String> delete(Object entity) {
return delete(entity, null);
}
/**
* Delete the given entity extracting index and type from entity metadata.
*
* @param entity must not be {@literal null}.
* @param index the name of the target index. Overwrites document metadata from {@literal entityType} if not
* {@literal null}.
* @return a {@link Mono} emitting the {@literal id} of the removed document.
*/
default Mono<String> delete(Object entity, @Nullable String index) {
return delete(entity, index, null);
}
/**
* Delete the given entity extracting index and type from entity metadata.
*
* @param entity must not be {@literal null}.
* @param index the name of the target index. Overwrites document metadata from {@literal entityType} if not
* {@literal null}.
* @param type the name of the target type. Overwrites document metadata from {@literal entityType} if not
* {@literal null}.
* @return a {@link Mono} emitting the {@literal id} of the removed document.
*/
Mono<String> delete(Object entity, @Nullable String index, @Nullable String type);
/**
* Delete the entity with given {@literal id}.
*
* @param id must not be {@literal null}.
* @param index the name of the target index.
* @param type the name of the target type.
* @return a {@link Mono} emitting the {@literal id} of the removed document.
*/
default Mono<String> deleteById(String id, String index, String type) {
Assert.notNull(index, "Index must not be null!");
Assert.notNull(type, "Type must not be null!");
return deleteById(id, Object.class, index, type);
}
/**
* Delete the entity with given {@literal id} extracting index and type from entity metadata.
*
* @param id must not be {@literal null}.
* @param entityType must not be {@literal null}.
* @return a {@link Mono} emitting the {@literal id} of the removed document.
*/
default Mono<String> deleteById(String id, Class<?> entityType) {
return deleteById(id, entityType, null);
}
/**
* Delete the entity with given {@literal id} extracting index and type from entity metadata.
*
* @param id must not be {@literal null}.
* @param entityType must not be {@literal null}.
* @param index the name of the target index. Overwrites document metadata from {@literal entityType} if not
* {@literal null}.
* @return a {@link Mono} emitting the {@literal id} of the removed document.
*/
default Mono<String> deleteById(String id, Class<?> entityType, @Nullable String index) {
return deleteById(id, entityType, index, null);
}
/**
* Delete the entity with given {@literal id} extracting index and type from entity metadata.
*
* @param id must not be {@literal null}.
* @param entityType must not be {@literal null}.
* @param index the name of the target index. Overwrites document metadata from {@literal entityType} if not
* {@literal null}.
* @param type the name of the target type. Overwrites document metadata from {@literal entityType} if not
* {@literal null}.
* @return a {@link Mono} emitting the {@literal id} of the removed document.
*/
Mono<String> deleteById(String id, Class<?> entityType, @Nullable String index, @Nullable String type);
/**
* Delete the documents matching the given {@link Query} extracting index and type from entity metadata.
*
* @param query must not be {@literal null}.
* @param entityType must not be {@literal null}.
* @return a {@link Mono} emitting the number of the removed documents.
*/
default Mono<Long> deleteBy(Query query, Class<?> entityType) {
return deleteBy(query, entityType, null);
}
/**
* Delete the documents matching the given {@link Query} extracting index and type from entity metadata.
*
* @param query must not be {@literal null}.
* @param entityType must not be {@literal null}.
* @param index the name of the target index. Overwrites document metadata from {@literal entityType} if not
* {@literal null}.
* @return a {@link Mono} emitting the number of the removed documents.
*/
default Mono<Long> deleteBy(Query query, Class<?> entityType, @Nullable String index) {
return deleteBy(query, entityType, index, null);
}
/**
* Delete the documents matching the given {@link Query} extracting index and type from entity metadata.
*
* @param query must not be {@literal null}.
* @param entityType must not be {@literal null}.
* @param index the name of the target index. Overwrites document metadata from {@literal entityType} if not
* {@literal null}.
* @param type the name of the target type. Overwrites document metadata from {@literal entityType} if not
* {@literal null}.
* @return a {@link Mono} emitting the number of the removed documents.
*/
Mono<Long> deleteBy(Query query, Class<?> entityType, @Nullable String index, @Nullable String type);
/**
* Get the {@link ElasticsearchConverter} used.
*
@@ -52,16 +459,6 @@ public interface ReactiveElasticsearchOperations extends ReactiveDocumentOperati
*/
ElasticsearchConverter getElasticsearchConverter();
@Nullable
ElasticsearchPersistentEntity<?> getPersistentEntityFor(Class<?> clazz);
/**
* @param clazz
* @return the IndexCoordinates defined on the entity.
* @since 4.0
*/
IndexCoordinates getIndexCoordinatesFor(Class<?> clazz);
/**
* Callback interface to be used with {@link #execute(ClientCallback)} for operating directly on
* {@link ReactiveElasticsearchClient}.
@@ -1,209 +0,0 @@
/*
* Copyright 2019-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.core;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.Aggregation;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.data.elasticsearch.core.query.StringQuery;
/**
* The reactive operations for the
* <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search.html">Elasticsearch Document
* APIs</a>.
*
* @author Peter-Josef Meisch
* @author Russell Parry
* @since 4.0
*/
public interface ReactiveSearchOperations {
/**
* Search the index for entities matching the given {@link Query query}. <br />
* {@link Pageable#isUnpaged() Unpaged} queries may overrule elasticsearch server defaults for page size by either
* delegating to the scroll API or using a max {@link org.elasticsearch.search.builder.SearchSourceBuilder#size(int)
* size}.
*
* @param query must not be {@literal null}.
* @param entityType must not be {@literal null}.
* @param <T>
* @return a {@link Flux} emitting matching entities one by one wrapped in a {@link SearchHit}.
* @deprecated since 4.0, use {@link #search(Query, Class)}.
*/
@Deprecated
default <T> Flux<T> find(Query query, Class<T> entityType) {
return find(query, entityType, entityType);
}
/**
* Search the index for entities matching the given {@link Query query}. <br />
* {@link Pageable#isUnpaged() Unpaged} queries may overrule elasticsearch server defaults for page size by either *
* delegating to the scroll API or using a max {@link org.elasticsearch.search.builder.SearchSourceBuilder#size(int) *
* size}.
*
* @param query must not be {@literal null}.
* @param entityType The entity type for mapping the query. Must not be {@literal null}.
* @param returnType The mapping target type. Must not be {@literal null}. Th
* @param <T>
* @return a {@link Flux} emitting matching entities one by one wrapped in a {@link SearchHit}.
* @deprecated since 4.0, use {@link #search(Query, Class, Class)}.
*/
@Deprecated
default <T> Flux<T> find(Query query, Class<?> entityType, Class<T> returnType) {
return search(query, entityType, returnType).map(SearchHit::getContent);
}
/**
* Search the index for entities matching the given {@link Query query}.
*
* @param query must not be {@literal null}.
* @param entityType must not be {@literal null}.
* @param index the target index, must not be {@literal null}
* @param <T>
* @return a {@link Flux} emitting matching entities one by one wrapped in a {@link SearchHit}.
* @deprecated since 4.0, use {@link #search(Query, Class, IndexCoordinates)}
*/
@Deprecated
default <T> Flux<T> find(Query query, Class<T> entityType, IndexCoordinates index) {
return find(query, entityType, entityType, index);
}
/**
* Search the index for entities matching the given {@link Query query}.
*
* @param <T>
* @param query must not be {@literal null}.
* @param entityType must not be {@literal null}.
* @param resultType the projection result type.
* @param index the target index, must not be {@literal null}
* @param <T>
* @return a {@link Flux} emitting matching entities one by one wrapped in a {@link SearchHit}.
* @deprecated since 4.0, use {@link #search(Query, Class, Class, IndexCoordinates)}.
*/
@Deprecated
default <T> Flux<T> find(Query query, Class<?> entityType, Class<T> resultType, IndexCoordinates index) {
return search(query, entityType, resultType, index).map(SearchHit::getContent);
}
/**
* Count the number of documents matching the given {@link Query}.
*
* @param entityType must not be {@literal null}.
* @return a {@link Mono} emitting the nr of matching documents.
*/
default Mono<Long> count(Class<?> entityType) {
return count(new StringQuery(QueryBuilders.matchAllQuery().toString()), entityType);
}
/**
* Count the number of documents matching the given {@link Query}.
*
* @param query must not be {@literal null}.
* @param entityType must not be {@literal null}.
* @return a {@link Mono} emitting the nr of matching documents.
*/
Mono<Long> count(Query query, Class<?> entityType);
/**
* Count the number of documents matching the given {@link Query}.
*
* @param query must not be {@literal null}.
* @param entityType must not be {@literal null}.
* @param index the target index, must not be {@literal null}
* @return a {@link Mono} emitting the nr of matching documents.
*/
Mono<Long> count(Query query, Class<?> entityType, IndexCoordinates index);
/**
* Search the index for entities matching the given {@link Query query}. <br />
* {@link Pageable#isUnpaged() Unpaged} queries may overrule elasticsearch server defaults for page size by either *
* delegating to the scroll API or using a max {@link org.elasticsearch.search.builder.SearchSourceBuilder#size(int) *
* size}.
*
* @param query must not be {@literal null}.
* @param entityType The entity type for mapping the query. Must not be {@literal null}.
* @param returnType The mapping target type. Must not be {@literal null}. Th
* @param <T>
* @return a {@link Flux} emitting matching entities one by one wrapped in a {@link SearchHit}.
*/
<T> Flux<SearchHit<T>> search(Query query, Class<?> entityType, Class<T> returnType);
/**
* Search the index for entities matching the given {@link Query query}. <br />
* {@link Pageable#isUnpaged() Unpaged} queries may overrule elasticsearch server defaults for page size by either
* delegating to the scroll API or using a max {@link org.elasticsearch.search.builder.SearchSourceBuilder#size(int)
* size}.
*
* @param query must not be {@literal null}.
* @param entityType must not be {@literal null}.
* @param <T>
* @return a {@link Flux} emitting matching entities one by one wrapped in a {@link SearchHit}.
*/
default <T> Flux<SearchHit<T>> search(Query query, Class<T> entityType) {
return search(query, entityType, entityType);
}
/**
* Search the index for entities matching the given {@link Query query}.
*
* @param <T>
* @param query must not be {@literal null}.
* @param entityType must not be {@literal null}.
* @param resultType the projection result type.
* @param index the target index, must not be {@literal null}
* @param <T>
* @return a {@link Flux} emitting matching entities one by one wrapped in a {@link SearchHit}.
*/
<T> Flux<SearchHit<T>> search(Query query, Class<?> entityType, Class<T> resultType, IndexCoordinates index);
/**
* Search the index for entities matching the given {@link Query query}.
*
* @param query must not be {@literal null}.
* @param entityType must not be {@literal null}.
* @param index the target index, must not be {@literal null}
* @param <T>
* @return a {@link Flux} emitting matching entities one by one wrapped in a {@link SearchHit}.
*/
default <T> Flux<SearchHit<T>> search(Query query, Class<T> entityType, IndexCoordinates index) {
return search(query, entityType, entityType, index);
}
/**
* Perform an aggregation specified by the given {@link Query query}. <br />
*
* @param query must not be {@literal null}.
* @param entityType must not be {@literal null}.
* @return a {@link Flux} emitting matching aggregations one by one.
* @since 4.0
*/
Flux<Aggregation> aggregate(Query query, Class<?> entityType);
/**
* Perform an aggregation specified by the given {@link Query query}. <br />
*
* @param query must not be {@literal null}.
* @param entityType must not be {@literal null}.
* @param index the target index, must not be {@literal null}
* @return a {@link Flux} emitting matching aggregations one by one.
* @since 4.0
*/
Flux<Aggregation> aggregate(Query query, Class<?> entityType, IndexCoordinates index);
}
File diff suppressed because it is too large Load Diff
@@ -20,35 +20,33 @@ import java.nio.charset.Charset;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.ClassPathResource;
import org.springframework.lang.Nullable;
import org.springframework.util.StreamUtils;
/**
* Utility to read {@link org.springframework.core.io.Resource}s.
*
* @author Mark Paluch
* @author Peter-Josef Meisch
* @since 3.2
*/
public abstract class ResourceUtil {
abstract class ResourceUtil {
private static final Logger LOGGER = LoggerFactory.getLogger(ResourceUtil.class);
/**
* Read a {@link ClassPathResource} into a {@link String}.
*
* @param url url the file url
* @return the contents of the file or null if it could not be read
* @param url
* @return
*/
@Nullable
public static String readFileFromClasspath(String url) {
ClassPathResource classPathResource = new ClassPathResource(url);
try (InputStream is = classPathResource.getInputStream()) {
return StreamUtils.copyToString(is, Charset.defaultCharset());
} catch (Exception e) {
LOGGER.warn(String.format("Failed to load file from url: %s: %s", url, e.getMessage()));
LOGGER.debug(String.format("Failed to load file from url: %s: %s", url, e.getMessage()));
return null;
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 the original author or authors.
* Copyright 2013-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,17 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.annotations;
/**
* @author Peter-Josef Meisch
* @since 4.0
*/
public @interface IndexPrefixes {
int MIN_DEFAULT = 2;
int MAX_DEFAULT = 2;
package org.springframework.data.elasticsearch.core;
int minChars() default MIN_DEFAULT;
import org.elasticsearch.action.search.SearchResponse;
int maxChars() default MAX_DEFAULT;
public interface ResultsExtractor<T> {
T extract(SearchResponse response);
}
@@ -0,0 +1,132 @@
/*
* Copyright 2013-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.core;
import java.io.IOException;
import java.util.Map;
import org.elasticsearch.index.get.GetResult;
import org.elasticsearch.search.SearchHit;
import org.springframework.data.elasticsearch.ElasticsearchException;
import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
/**
* ResultsMapper
*
* @author Rizwan Idrees
* @author Mohsin Husen
* @author Artur Konczak
* @author Christoph Strobl
*/
public interface ResultsMapper extends SearchResultMapper, GetResultMapper, MultiGetResultMapper {
EntityMapper getEntityMapper();
/**
* Get the configured {@link ProjectionFactory}. <br />
* <strong>NOTE</strong> Should be overwritten in implementation to make use of the type cache.
*
* @since 3.2
*/
default ProjectionFactory getProjectionFactory() {
return new SpelAwareProxyProjectionFactory();
}
@Nullable
default <T> T mapEntity(String source, Class<T> clazz) {
if (StringUtils.isEmpty(source)) {
return null;
}
try {
return getEntityMapper().mapToObject(source, clazz);
} catch (IOException e) {
throw new ElasticsearchException("failed to map source [ " + source + "] to class " + clazz.getSimpleName(), e);
}
}
/**
* Map a single {@link GetResult} to an instance of the given type.
*
* @param getResult must not be {@literal null}.
* @param type must not be {@literal null}.
* @param <T>
* @return can be {@literal null} if the {@link GetResult#isSourceEmpty() is empty}.
* @since 3.2
*/
@Nullable
default <T> T mapGetResult(GetResult getResult, Class<T> type) {
if (getResult.isSourceEmpty()) {
return null;
}
Map<String, Object> source = getResult.getSource();
if (!source.containsKey("id") || source.get("id") == null) {
source.put("id", getResult.getId());
}
Object mappedResult = getEntityMapper().readObject(source, type);
if (mappedResult == null) {
return (T) null;
}
if (type.isInterface() || !ClassUtils.isAssignableValue(type, mappedResult)) {
return getProjectionFactory().createProjection(type, mappedResult);
}
return type.cast(mappedResult);
}
/**
* Map a single {@link SearchHit} to an instance of the given type.
*
* @param searchHit must not be {@literal null}.
* @param type must not be {@literal null}.
* @param <T>
* @return can be {@literal null} if the {@link SearchHit} does not have {@link SearchHit#hasSource() a source}.
* @since 3.2
*/
@Nullable
default <T> T mapSearchHit(SearchHit searchHit, Class<T> type) {
if (!searchHit.hasSource()) {
return null;
}
Map<String, Object> source = searchHit.getSourceAsMap();
if (!source.containsKey("id") || source.get("id") == null) {
source.put("id", searchHit.getId());
}
Object mappedResult = getEntityMapper().readObject(source, type);
if (mappedResult == null) {
return null;
}
if (type.isInterface()) {
return getProjectionFactory().createProjection(type, mappedResult);
}
return type.cast(mappedResult);
}
}

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